From 278a55b1b343f45c380383352164af1f6fd98163 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Fri, 28 Aug 2026 11:58:04 +0200 Subject: [PATCH 1/3] Add support for marshaling pointers Needed for libvips/libvips@e320215. --- examples/draw_line.py | 19 +++++++++++++++++ pyvips/gvalue.py | 6 ++++++ pyvips/gvalue.pyi | 1 + pyvips/vdecls.py | 49 +++++++++++++++++++++++++------------------ tests/test_gvalue.py | 8 +++++++ 5 files changed, 63 insertions(+), 20 deletions(-) create mode 100755 examples/draw_line.py diff --git a/examples/draw_line.py b/examples/draw_line.py new file mode 100755 index 0000000..312660c --- /dev/null +++ b/examples/draw_line.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 + +# import logging +# logging.basicConfig(level = logging.DEBUG) + +import pyvips +from pyvips import ffi + + +def custom_draw(image, ink, x, y, client): + assert ffi.from_handle(client) == 42 + print(x, y) + + +cb = ffi.callback('VipsDrawPoint', custom_draw) +client = ffi.new_handle(42) + +im = pyvips.Image.black(100, 100) +im = im.draw_line([100], 0, 0, 100, 0, draw_point=cb, client=client) diff --git a/pyvips/gvalue.py b/pyvips/gvalue.py index 969f647..0550984 100644 --- a/pyvips/gvalue.py +++ b/pyvips/gvalue.py @@ -31,6 +31,7 @@ class GValue(object): guint64_type = type_from_name('guint64') gdouble_type = type_from_name('gdouble') gstr_type = type_from_name('gchararray') + gpointer_type = type_from_name('gpointer') genum_type = type_from_name('GEnum') gflags_type = type_from_name('GFlags') gobject_type = type_from_name('GObject') @@ -57,6 +58,7 @@ class GValue(object): guint64_type: 'int', gdouble_type: 'float', gstr_type: 'str', + gpointer_type: 'ffi.CData', refstr_type: 'str', genum_type: 'str', gflags_type: 'int', @@ -188,6 +190,8 @@ def set(self, value): GValue.to_flag(gtype, value)) elif gtype == GValue.gstr_type: gobject_lib.g_value_set_string(self.gvalue, _to_bytes(value)) + elif gtype == GValue.gpointer_type: + gobject_lib.g_value_set_pointer(self.gvalue, value) elif gtype == GValue.refstr_type: vips_lib.vips_value_set_ref_string(self.gvalue, _to_bytes(value)) elif fundamental == GValue.gobject_type: @@ -266,6 +270,8 @@ def get(self): if pointer != ffi.NULL: result = _to_string(pointer) + elif gtype == GValue.gpointer_type: + result = gobject_lib.g_value_get_pointer(self.gvalue) elif gtype == GValue.refstr_type: psize = ffi.new('size_t *') pointer = vips_lib.vips_value_get_ref_string(self.gvalue, psize) diff --git a/pyvips/gvalue.pyi b/pyvips/gvalue.pyi index 8a9bb61..4c05d95 100644 --- a/pyvips/gvalue.pyi +++ b/pyvips/gvalue.pyi @@ -13,6 +13,7 @@ class GValue(object): guint64_type: ClassVar[int] gdouble_type: ClassVar[int] gstr_type: ClassVar[int] + gpointer_type: ClassVar[int] genum_type: ClassVar[int] gflags_type: ClassVar[int] gobject_type: ClassVar[int] diff --git a/pyvips/vdecls.py b/pyvips/vdecls.py index 05db851..65a61c1 100644 --- a/pyvips/vdecls.py +++ b/pyvips/vdecls.py @@ -99,7 +99,7 @@ def cdefs(features): GType gtype, const char* str); int vips_flags_from_nick (const char* domain, GType gtype, const char* nick); - const char *vips_enum_nick (GType gtype, int value); + const char* vips_enum_nick (GType gtype, int value); void g_value_set_boolean (GValue* value, int v_boolean); void g_value_set_int (GValue* value, int i); @@ -108,13 +108,14 @@ def cdefs(features): void g_value_set_enum (GValue* value, int e); void g_value_set_flags (GValue* value, unsigned int f); void g_value_set_string (GValue* value, const char* str); + void g_value_set_pointer (GValue* value, void* v_pointer); void vips_value_set_ref_string (GValue* value, const char* str); void g_value_set_object (GValue* value, void* object); void vips_value_set_array_double (GValue* value, const double* array, int n ); void vips_value_set_array_int (GValue* value, const int* array, int n ); - void vips_value_set_array_image (GValue *value, int n); + void vips_value_set_array_image (GValue* value, int n); int g_value_get_boolean (const GValue* value); int g_value_get_int (GValue* value); @@ -123,6 +124,7 @@ def cdefs(features): int g_value_get_enum (GValue* value); unsigned int g_value_get_flags (GValue* value); const char* g_value_get_string (GValue* value); + void* g_value_get_pointer (GValue* value); const char* vips_value_get_ref_string (const GValue* value, size_t* length); void* g_value_get_object (GValue* value); @@ -143,14 +145,14 @@ def cdefs(features): } GTypeClass; typedef struct _GTypeInstance { - GTypeClass *g_class; + GTypeClass* g_class; } GTypeInstance; typedef struct _GObject { GTypeInstance g_type_instance; unsigned int ref_count; - GData *qdata; + GData* qdata; } GObject; typedef struct _GParamSpec { @@ -173,8 +175,8 @@ def cdefs(features): typedef struct _GEnumValue { int value; - const char *value_name; - const char *value_nick; + const char* value_name; + const char* value_nick; } GEnumValue; typedef struct _GEnumClass { @@ -183,14 +185,14 @@ def cdefs(features): int minimum; int maximum; unsigned int n_values; - GEnumValue *values; + GEnumValue* values; } GEnumClass; typedef struct _GFlagsValue { unsigned int value; - const char *value_name; - const char *value_nick; + const char* value_name; + const char* value_nick; } GFlagsValue; typedef struct _GFlagsClass { @@ -198,7 +200,7 @@ def cdefs(features): unsigned int mask; unsigned int n_values; - GFlagsValue *values; + GFlagsValue* values; } GFlagsClass; void* g_type_class_ref (GType type); @@ -208,14 +210,14 @@ def cdefs(features): void g_object_unref (void* object); void g_object_set_property (GObject* object, - const char *name, GValue* value); + const char* name, GValue* value); void g_object_get_property (GObject* object, const char* name, GValue* value); void vips_image_invalidate_all (VipsImage* image); - typedef void (*GCallback)(void); - typedef void (*GClosureNotify)(void* data, struct _GClosure *); + typedef void (*GCallback) (void); + typedef void (*GClosureNotify) (void* data, struct _GClosure* ); long g_signal_connect_data (GObject* object, const char* detailed_signal, GCallback c_handler, @@ -247,7 +249,7 @@ def cdefs(features): typedef ... VipsObjectClass; typedef struct _VipsArgument { - GParamSpec *pspec; + GParamSpec* pspec; } VipsArgument; typedef struct _VipsArgumentInstance { @@ -271,14 +273,14 @@ def cdefs(features): typedef struct _VipsArgumentClass { VipsArgument parent; - VipsObjectClass *object_class; + VipsObjectClass* object_class; VipsArgumentFlags flags; int priority; unsigned int offset; } VipsArgumentClass; int vips_object_get_argument (VipsObject* object, - const char *name, GParamSpec** pspec, + const char* name, GParamSpec** pspec, VipsArgumentClass** argument_class, VipsArgumentInstance** argument_instance); @@ -365,7 +367,7 @@ def cdefs(features): # vips_value_set_blob_free in a backwards compatible way if not features['api']: code += ''' - typedef void (*FreeFn)(void* a); + typedef void (*FreeFn) (void* a); void vips_value_set_blob (GValue* value, FreeFn free_fn, void* data, size_t length); ''' @@ -443,7 +445,7 @@ def cdefs(features): extern "Python" void _marshal_finish (VipsTarget*, void*); - const char* vips_foreign_find_load_source (VipsSource *source); + const char* vips_foreign_find_load_source (VipsSource* source); const char* vips_foreign_find_save_target (const char* suffix); ''' @@ -454,16 +456,23 @@ def cdefs(features): void*); void vips_block_untrusted_set (int state); - void vips_operation_block_set (const char *name, int state); + void vips_operation_block_set (const char* name, int state); ''' if _at_least(features, 8, 18): code += ''' - VipsImage *vips_image_get_gainmap(VipsImage *image); + VipsImage* vips_image_get_gainmap(VipsImage* image); ''' + if _at_least(features, 8, 19): + code += ''' + typedef unsigned char VipsPel; + typedef void (*VipsDrawPoint) (VipsImage* image, VipsPel* ink, + int x, int y, void* client); + ''' + # we must only define these in API mode ... in ABI mode we need to call # these things earlier if features['api']: diff --git a/tests/test_gvalue.py b/tests/test_gvalue.py index 7bc5703..5c2bc5c 100644 --- a/tests/test_gvalue.py +++ b/tests/test_gvalue.py @@ -78,6 +78,14 @@ def test_string(self): value = gv.get() assert value == 'banana' + def test_pointer(self): + pointer = pyvips.ffi.new_handle(42) + gv = pyvips.GValue() + gv.set_type(pyvips.GValue.gpointer_type) + gv.set(pointer) + value = gv.get() + assert pyvips.ffi.from_handle(value) == 42 + def test_array_int(self): gv = pyvips.GValue() gv.set_type(pyvips.GValue.array_int_type) From d6dc0d7695f8a38c7b841b2f876c4d341555e6bd Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Sat, 29 Aug 2026 14:02:29 +0200 Subject: [PATCH 2/3] Appease mypy --- examples/draw_line.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/draw_line.py b/examples/draw_line.py index 312660c..814a9de 100755 --- a/examples/draw_line.py +++ b/examples/draw_line.py @@ -16,4 +16,5 @@ def custom_draw(image, ink, x, y, client): client = ffi.new_handle(42) im = pyvips.Image.black(100, 100) -im = im.draw_line([100], 0, 0, 100, 0, draw_point=cb, client=client) +im = im.draw_line([100], 0, 0, 100, 0, + draw_point=cb, client=client) # type: ignore[call-arg] From 839c048622ae7b3ccb5e58abb889dcdc8211485a Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Sat, 29 Aug 2026 15:03:08 +0200 Subject: [PATCH 3/3] Add changelog note --- CHANGELOG.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 53f66bd..35ffceb 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,7 @@ - add test coverage for type stubs [JoshCLWren] - add `Image.pil()` to convert to a PIL image [jonashaag] - add `pyvips.concurrency_set()`, `pyvips.concurrency_get()` [NewUserHa] +- add support for marshaling pointers [kleisauke] ## Version 3.1.1 (released 9 December 2025)