diff --git a/CHANGELOG.md b/CHANGELOG.md index 950805b..f2d86ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to `:vips` will be documented in this file. ## master - improve FFI startup [West14] +- revise example use of composer [jcupitt] ## 2.1.1 - 2022-11-13 diff --git a/examples/addconst.php b/examples/addconst.php index 971d757..e9d330e 100755 --- a/examples/addconst.php +++ b/examples/addconst.php @@ -1,7 +1,7 @@ #!/usr/bin/env php Vips\Access::SEQUENTIAL]); $im = $im->crop(100, 100, $im->width - 200, $im->height - 200); diff --git a/examples/class.php b/examples/class.php index c98b72a..47c5b3b 100755 --- a/examples/class.php +++ b/examples/class.php @@ -1,7 +1,7 @@ #!/usr/bin/env php Vips\Access::SEQUENTIAL]); /** diff --git a/examples/streaming-bench.php b/examples/streaming-bench.php old mode 100644 new mode 100755 diff --git a/examples/streaming.php b/examples/streaming.php old mode 100644 new mode 100755 diff --git a/examples/vips-magick.php b/examples/vips-magick.php index 5d355c6..42e6691 100755 --- a/examples/vips-magick.php +++ b/examples/vips-magick.php @@ -1,10 +1,15 @@ #!/usr/bin/env php self::$gobject->type("GObject*"), "GClosure" => self::$gobject->type("GClosure"), + "GCallback" => self::$gobject->type("GCallback"), + "GCallback_read" => self::$gobject->type("GCallback_read"), + "GCallback_progress" => self::$gobject->type("GCallback_progress"), + "GType" => self::$gobject->type("GType"), "GParamSpec" => self::$gobject->type("GParamSpec*"), "VipsObject" => self::$vips->type("VipsObject*"), "VipsOperation" => self::$vips->type("VipsOperation*"), "VipsImage" => self::$vips->type("VipsImage*"), "VipsInterpolate" => self::$vips->type("VipsInterpolate*"), + "VipsProgress" => self::$vips->type("VipsProgress*"), "VipsConnection" => self::$vips->type("VipsConnection*"), "VipsSource" => self::$vips->type("VipsSource*"), "VipsSourceCustom" => self::$vips->type("VipsSourceCustom*"), @@ -816,6 +824,7 @@ private static function init(): void ]; self::$gtypes = [ + "gpointer" => self::$gobject->g_type_from_name("gpointer"), "gboolean" => self::$gobject->g_type_from_name("gboolean"), "gint" => self::$gobject->g_type_from_name("gint"), "gint64" => self::$gobject->g_type_from_name("gint64"), diff --git a/src/GObject.php b/src/GObject.php index 8a747fc..c3c2301 100644 --- a/src/GObject.php +++ b/src/GObject.php @@ -53,6 +53,11 @@ */ abstract class GObject { + /** + */ + private static array $handleTable = []; + private static int $nextIndex = 0; + /** * A pointer to the underlying GObject. * @@ -60,6 +65,16 @@ abstract class GObject */ private CData $pointer; + /** + * Upstream objects we must keep alive. + * + * A set of references to other php objects which this object refers to + * via ffi, ie. references which the php GC cannot find automatically. + * + * @internal + */ + private array $_references = []; + /** * Wrap a GObject around an underlying vips resource. The GObject takes * ownership of the pointer and will unref it on finalize. @@ -99,19 +114,107 @@ public function unref(): void /** * Connect to a signal on this object. - * The callback will be triggered every time this signal is issued on this instance. + * + * The callback will be triggered every time this signal is issued on this + * instance. + * * @throws Exception */ public function signalConnect(string $name, Closure $callback): void { - $marshaler = self::getMarshaler($name, $callback); - if ($marshaler === null) { - throw new Exception("unsupported signal $name"); + echo "signalConnect:\n"; + + $marshal = self::getMarshal($name); + $handle = self::getHandle($callback); + + $sig = \FFI::arrayType(FFI::ctypes("GCallback"), [1]); + $c_callback = \FFI::new($sig); + $c_callback[0] = $marshal; + + $id = FFI::gobject()->g_signal_connect_data($this->pointer, + $name, + $c_callback[0], + $handle, + null, + 0); + if ($id === 0) { + throw new Exception("unable to connect signal $name"); + } + + echo "signalConnect: done\n"; + } + + /* Ideally, we'd use the "user" pointer of signal_connect to hold the + * callback closure, but unfortunately php-ffi has no way (I think) to + * turn a C pointer to function back into a php function, so we have to + * build a custom closure for every signal connect with the callback + * embedded within it. + */ + private static function getMarshal(string $name) : CData + { + switch ($name) { + case 'preeval': + case 'eval': + case 'posteval': + $marshal = static function ( + CData $imagePointer, + CData $progressPointer, + CData $handle) : void { + $image = new Image($imagePointer); + // Image() will unref on gc, so we must ref + FFI::gobject()->g_object_ref($imagePointer); + + // FIXME ... maybe wrap VipsProgress as a php class? + $progress = \FFI::cast(FFI::ctypes("VipsProgress"), + $progressPointer); + + $callback = self::fromHandle($handle); + + $callback($image, $progress); + }; + + $sig = \FFI::arrayType(FFI::ctypes("GCallback_progress"), [1]); + $c_callback = \FFI::new($sig); + $c_callback[0] = $marshal; + + return \FFI::cast(FFI::ctypes("GCallback"), $c_callback[0]); + + case 'read': + if (FFI::atLeast(8, 9)) { + $marshal = function ( + CData $sourcePointer, + CData $bufferPointer, + int $bufferLength, + CData $handle) : int { + echo "hello from read marshal!\n"; + $callback = self::fromHandle($handle); + $result = 0; + + $returnBuffer = $callback($bufferLength); + if ($returnBuffer !== null) { + $result = strlen($returnBuffer); + \FFI::memcpy($bufferPointer, + $returnBuffer, + $result + ); + } + + return $result; + }; + + $sig = \FFI::arrayType(FFI::ctypes("GCallback_read"), [1]); + $c_callback = \FFI::new($sig); + $c_callback[0] = $marshal; + + echo "c_callback[0] = "; + print_r($c_callback[0]); + echo "\n"; + + return \FFI::cast(FFI::ctypes("GCallback"), $c_callback[0]); + } } - $gc = FFI::gobject()->g_closure_new_simple(\FFI::sizeof(FFI::ctypes('GClosure')), null); - $gc->marshal = $marshaler; - FFI::gobject()->g_signal_connect_closure($this->pointer, $name, $gc, 0); + throw new Exception("unsupported signal $name"); } private static function getMarshaler(string $name, Closure $callback): ?Closure @@ -144,34 +247,7 @@ private static function getMarshaler(string $name, Closure $callback): ?Closure ); $callback($image, $pr); }; - case 'read': - if (FFI::atLeast(8, 9)) { - return static function ( - CData $gClosure, - CData $returnValue, - int $numberOfParams, - CData $params, - CData $hint, - ?CData $data - ) use (&$callback): void { - assert($numberOfParams === 4); - /* - * Signature: gint64(VipsSourceCustom* source, void* buffer, gint64 length, void* handle) - */ - $bufferLength = (int)FFI::gobject()->g_value_get_int64(\FFI::addr($params[2])); - $returnBuffer = $callback($bufferLength); - $returnBufferLength = 0; - if ($returnBuffer !== null) { - $returnBufferLength = strlen($returnBuffer); - $bufferPointer = FFI::gobject()->g_value_get_pointer(\FFI::addr($params[1])); - \FFI::memcpy($bufferPointer, $returnBuffer, $returnBufferLength); - } - FFI::gobject()->g_value_set_int64($returnValue, $returnBufferLength); - }; - } - - return null; case 'seek': if (FFI::atLeast(8, 9)) { return static function ( @@ -258,6 +334,28 @@ private static function getMarshaler(string $name, Closure $callback): ?Closure return null; } } + + private static function getHandle($object) : CData + { + $index = self::$nextIndex; + self::$nextIndex += 1; + + self::$handleTable[$index] = $object; + + // hide the index inside a void* + $x = \FFI::new(FFI::ctypes("GType")); + $x->cdata = $index; + + return \FFI::cast("void*", $x); + } + + private static function fromHandle($handle) + { + // recover the index from a void* + $index = $handle->cdata; + + return self::$handleTable[$index]; + } } /* diff --git a/src/Image.php b/src/Image.php index a7b53e1..e68ad04 100644 --- a/src/Image.php +++ b/src/Image.php @@ -1296,6 +1296,20 @@ public function remove(string $name): void } } + /** + * Enable progress reporting on an image. + * + * When progress reporting is enabled, evaluation of the most downstream + * image from this image will report progress using the ::preeval, ::eval, + * and ::posteval signals. + * + * @param bool $progress True to enable progress reporting + */ + public function setProgress($progress): void + { + FFI::vips()->vips_image_set_progress($this->pointer, $progress); + } + /** * Makes a string-ified version of the Image. *