From dc46e1cb15b91fb8b7f0f5312b29ed821c32053a Mon Sep 17 00:00:00 2001 From: L3tum <9307432+L3tum@users.noreply.github.com> Date: Sun, 26 Feb 2023 16:54:25 +0100 Subject: [PATCH 01/15] feat: Implement true streaming and signal handling --- src/Connection.php | 36 +++++++++++++++++++++++ src/FFI.php | 6 ---- src/GObject.php | 52 ++++++++++++++++++++++++++++++++-- src/VipsObject.php | 2 +- src/VipsSource.php | 58 ++++++++++++++++++++++++++++++++++++++ src/VipsSourceCustom.php | 53 ++++++++++++++++++++++++++++++++++ src/VipsSourceResource.php | 41 +++++++++++++++++++++++++++ src/VipsTarget.php | 38 +++++++++++++++++++++++++ src/VipsTargetCustom.php | 55 ++++++++++++++++++++++++++++++++++++ src/VipsTargetResource.php | 53 ++++++++++++++++++++++++++++++++++ 10 files changed, 384 insertions(+), 10 deletions(-) create mode 100644 src/Connection.php create mode 100644 src/VipsSource.php create mode 100644 src/VipsSourceCustom.php create mode 100644 src/VipsSourceResource.php create mode 100644 src/VipsTarget.php create mode 100644 src/VipsTargetCustom.php create mode 100644 src/VipsTargetResource.php diff --git a/src/Connection.php b/src/Connection.php new file mode 100644 index 0000000..36f7a1d --- /dev/null +++ b/src/Connection.php @@ -0,0 +1,36 @@ +pointer); + $pointer = FFI::vips()->vips_connection_filename($so); + + if (\FFI::isNull($pointer)) { + return null; + } + + return \FFI::string($pointer); + } + + /** + * Make a human-readable name for a connection suitable for error messages. + */ + public function nick(): ?string + { + $so = \FFI::cast(FFI::ctypes('VipsConnection'), $this->pointer); + $pointer = FFI::vips()->vips_connection_nick($so); + + if (\FFI::isNull($pointer)) { + return null; + } + + return \FFI::string($pointer); + } +} diff --git a/src/FFI.php b/src/FFI.php index b76c8bf..fa7a0d0 100644 --- a/src/FFI.php +++ b/src/FFI.php @@ -703,12 +703,6 @@ private static function init(): void VipsSourceCustom* vips_source_custom_new (void); -// FIXME ... these need porting to php-ffi -// extern "Python" gint64 _marshal_read (VipsSource*, -// void*, gint64, void*); -// extern "Python" gint64 _marshal_seek (VipsSource*, -// gint64, int, void*); - typedef struct _VipsTarget { VipsConnection parent_object; diff --git a/src/GObject.php b/src/GObject.php index 4ebc500..c87fd92 100644 --- a/src/GObject.php +++ b/src/GObject.php @@ -38,6 +38,9 @@ namespace Jcupitt\Vips; +use Closure; +use FFI\CData; + /** * This class holds a pointer to a GObject and manages object lifetime. * @@ -55,7 +58,7 @@ abstract class GObject * * @internal */ - private \FFI\CData $pointer; + private CData $pointer; /** * Wrap a GObject around an underlying vips resource. The GObject takes @@ -69,7 +72,7 @@ abstract class GObject * * @internal */ - public function __construct(\FFI\CData $pointer) + public function __construct(CData $pointer) { $this->pointer = \FFI::cast(FFI::ctypes("GObject"), $pointer); } @@ -94,7 +97,50 @@ public function unref(): void FFI::gobject()->g_object_unref($this->pointer); } - // TODO signal marshalling to go in + public function signalConnect(string $name, Closure $callback): void + { + static $marshalers = null; + + if ($marshalers === null) { + $imageProgressCb = static function (CData $vi, CData $progress, CData $handle) { + FFI::gobject()->g_object_ref($vi); + $image = new Image($vi); + $progress = \FFI::cast(FFI::ctypes('VipsProgress'), $progress); + $handle($image, $progress); + }; + $marshalers = ['preeval' => $imageProgressCb, 'eval' => $imageProgressCb, 'posteval' => $imageProgressCb]; + + if (FFI::atLeast(8, 9)) { + $marshalers['read'] = static function (CData $gObject, CData $pointer, int $length, CData $handle): int { + $buffer = \FFI::string($pointer, $length); + return $handle($buffer); + }; + $marshalers['seek'] = static function (CData $gObject, int $offset, int $whence, CData $handle): int { + return $handle($offset, $whence); + }; + $marshalers['write'] = static function (CData $gObject, CData $pointer, int $length, CData $handle): int { + $buffer = \FFI::string($pointer, $length); + return $handle($buffer); + }; + $marshalers['finish'] = static function (CData $gObject, CData $handle): void { + $handle(); + }; + } + + if (FFI::atLeast(8, 13)) { + $marshalers['end'] = static function (CData $gObject, CData $handle): int { + return $handle(); + }; + } + } + + if (!isset($marshalers[$name])) { + throw new Exception("unsupported signal $name"); + } + + $go = \FFI::cast(FFI::ctypes('GObject'), $this->pointer); + FFI::gobject()->g_signal_connect_data($go, $name, $marshalers[$name], $callback, null, 0); + } } /* diff --git a/src/VipsObject.php b/src/VipsObject.php index 74bb83b..e82a0e2 100644 --- a/src/VipsObject.php +++ b/src/VipsObject.php @@ -56,7 +56,7 @@ abstract class VipsObject extends GObject * * @internal */ - private \FFI\CData $pointer; + protected \FFI\CData $pointer; /** * A pointer to the underlying GObject. This is the same as the diff --git a/src/VipsSource.php b/src/VipsSource.php new file mode 100644 index 0000000..0691edb --- /dev/null +++ b/src/VipsSource.php @@ -0,0 +1,58 @@ +vips_source_new_from_descriptor($descriptor); + + if (\FFI::isNull($pointer)) { + throw new Exception("can't create source from descriptor $descriptor"); + } + + return new self($pointer); + } + + /** + * Make a new source from a filename. + * Make a new source that is attached to the named file. For example: + * source = pyvips.Source.new_from_file("myfile.jpg") + * You can pass this source to (for example) :meth:`new_from_source`. + * @throws Exception + */ + public static function newFromFile(string $filename): self + { + $pointer = FFI::vips()->vips_source_new_from_file($filename); + + if (\FFI::isNull($pointer)) { + throw new Exception("can't create source from filename $filename"); + } + + return new self($pointer); + } + + /** + * @TODO Not sure how best to implement this since PHP does not have buffers like Python + * @throws Exception + */ + public static function newFromMemory(string $data): self + { + $pointer = FFI::vips()->vips_source_new_from_memory($data, strlen($data)); + + if (\FFI::isNull($pointer)) { + throw new Exception("can't create source from memory"); + } + + return new self($pointer); + } +} diff --git a/src/VipsSourceCustom.php b/src/VipsSourceCustom.php new file mode 100644 index 0000000..b2841c5 --- /dev/null +++ b/src/VipsSourceCustom.php @@ -0,0 +1,53 @@ +vips_source_custom_new()); + parent::__construct($source); + } + + /** + * Attach a read handler. + * The interface is exactly as io.read() in Python. The handler is given a number + * of bytes to fetch, and should return a bytes-like object containing up + * to that number of bytes. If there is no more data available, it should + * return None. + */ + public function onRead(Closure $callback): void + { + $this->signalConnect('read', static function (string &$buffer) use ($callback): int { + $chunk = $callback(strlen($buffer)); + + if ($chunk === null) { + return 0; + } + + $buffer = substr_replace($buffer, $chunk, 0); + return strlen($chunk); + }); + } + + /** + * Attach a seek handler. + * The interface is the same as fseek, so the handler is passed + * parameters for $offset and $whence with the same meanings. + * However, the handler MUST return the new seek position. A simple way + * to do this is to call ftell() and return that result. + * Seek handlers are optional. If you do not set one, your source will be + * treated as unseekable and libvips will do extra caching. + * $whence in particular: + * 0 => start + * 1 => current position + * 2 => end + */ + public function onSeek(Closure $callback): void + { + $this->signalConnect('seek', $callback); + } +} diff --git a/src/VipsSourceResource.php b/src/VipsSourceResource.php new file mode 100644 index 0000000..f26cae4 --- /dev/null +++ b/src/VipsSourceResource.php @@ -0,0 +1,41 @@ +resource = $resource; + parent::__construct(); + + $this->onRead(static function (int $length) use ($resource): ?string { + return fread($resource, $length) ?: null; + }); + + if (stream_get_meta_data($resource)['seekable']) { + $this->onSeek(static function (int $offset, int $whence) use ($resource): int { + fseek($resource, $offset, $whence); + return ftell($resource); + }); + } + } + + public function __destruct() + { + fclose($this->resource); + parent::__destruct(); // TODO: Change the autogenerated stub + } +} diff --git a/src/VipsTarget.php b/src/VipsTarget.php new file mode 100644 index 0000000..354fd85 --- /dev/null +++ b/src/VipsTarget.php @@ -0,0 +1,38 @@ +vips_target_new_to_descriptor($descriptor); + if (\FFI::isNull($pointer)) { + throw new Exception("can't create output target from descriptor $descriptor"); + } + + return new self($pointer); + } + + public static function newToFile(string $filename): self + { + $pointer = FFI::vips()->vips_target_new_to_file($filename); + + if (\FFI::isNull($pointer)) { + throw new Exception("can't create output target from filename $filename"); + } + + return new self($pointer); + } + + public static function newToMemory(): self + { + $pointer = FFI::vips()->vips_target_new_to_memory(); + + if (\FFI::isNull($pointer)) { + throw new Exception("can't create output target from memory"); + } + + return new self($pointer); + } +} diff --git a/src/VipsTargetCustom.php b/src/VipsTargetCustom.php new file mode 100644 index 0000000..75e3cd2 --- /dev/null +++ b/src/VipsTargetCustom.php @@ -0,0 +1,55 @@ +vips_target_custom_new()); + parent::__construct($pointer); + } + + public function onWrite(Closure $callback): void + { + $this->signalConnect('write', $callback); + } + + public function onRead(Closure $callback): void + { + if (FFI::atLeast(8, 13)) { + $this->signalConnect('read', static function (string &$buffer) use ($callback): int { + $chunk = $callback(strlen($buffer)); + + if ($chunk === null) { + return 0; + } + $buffer = substr_replace($buffer, $chunk, 0); + return strlen($chunk); + }); + } + } + + public function onSeek(Closure $callback): void + { + if (FFI::atLeast(8, 13)) { + $this->signalConnect('seek', $callback); + } + } + + public function onEnd(Closure $callback): void + { + if (FFI::atLeast(8, 13)) { + $this->signalConnect('end', $callback); + } else { + $this->onFinish($callback); + } + } + + public function onFinish(Closure $callback): void + { + $this->signalConnect('finish', $callback); + } +} diff --git a/src/VipsTargetResource.php b/src/VipsTargetResource.php new file mode 100644 index 0000000..712c4ca --- /dev/null +++ b/src/VipsTargetResource.php @@ -0,0 +1,53 @@ +resource = $resource; + parent::__construct(); + + $this->onWrite(static function (string $buffer) use ($resource): int { + return fwrite($resource, $buffer) ?: 0; + }); + + $this->onEnd(static function () use ($resource): void { + fclose($resource); + }); + + $meta = stream_get_meta_data($resource); + // See: https://www.php.net/manual/en/function.fopen.php + if (substr($meta['mode'], -1) === '+') { + $this->onRead(static function (int $length) use ($resource): ?string { + return fread($resource, $length) ?: null; + }); + } + + if ($meta['seekable']) { + $this->onSeek(static function (int $offset, int $whence) use ($resource): int { + fseek($resource, $offset, $whence); + return ftell($resource); + }); + } + } + + public function __destruct() + { + if (is_resource($this->resource)) { + fclose($this->resource); + } + parent::__destruct(); // TODO: Change the autogenerated stub + } +} From a852510fae4418ff56ebec50f644216d284208de Mon Sep 17 00:00:00 2001 From: L3tum <9307432+L3tum@users.noreply.github.com> Date: Sun, 26 Feb 2023 20:25:02 +0100 Subject: [PATCH 02/15] fix: Some cleanup and making pointers usable --- src/Connection.php | 32 ++++++++-------- src/FFI.php | 7 ++++ src/Image.php | 54 ++++++++++++++++++++++++++ src/VipsObject.php | 2 +- src/VipsSource.php | 14 +++++++ src/VipsSourceCustom.php | 12 +++++- src/VipsSourceResource.php | 3 +- src/VipsTarget.php | 14 +++++++ src/VipsTargetCustom.php | 12 +++++- src/VipsTargetResource.php | 3 +- tests/StreamingTest.php | 77 ++++++++++++++++++++++++++++++++++++++ 11 files changed, 207 insertions(+), 23 deletions(-) create mode 100644 tests/StreamingTest.php diff --git a/src/Connection.php b/src/Connection.php index 36f7a1d..ea38166 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -4,19 +4,26 @@ abstract class Connection extends VipsObject { + /** + * A pointer to the underlying Connection. This is the same as the + * GObject, just cast to Connection to help FFI. + * + * @internal + */ + public \FFI\CData $pointer; + + public function __construct(\FFI\CData $pointer) + { + $this->pointer = \FFI::cast(FFI::ctypes('VipsConnection'), $pointer); + parent::__construct($pointer); + } + /** * Get the filename associated with a connection. Return null if there is no associated file. */ public function filename(): ?string { - $so = \FFI::cast(FFI::ctypes('VipsConnection'), $this->pointer); - $pointer = FFI::vips()->vips_connection_filename($so); - - if (\FFI::isNull($pointer)) { - return null; - } - - return \FFI::string($pointer); + return FFI::vips()->vips_connection_filename($this->pointer); } /** @@ -24,13 +31,6 @@ public function filename(): ?string */ public function nick(): ?string { - $so = \FFI::cast(FFI::ctypes('VipsConnection'), $this->pointer); - $pointer = FFI::vips()->vips_connection_nick($so); - - if (\FFI::isNull($pointer)) { - return null; - } - - return \FFI::string($pointer); + return FFI::vips()->vips_connection_nick($this->pointer); } } diff --git a/src/FFI.php b/src/FFI.php index fa7a0d0..c396f25 100644 --- a/src/FFI.php +++ b/src/FFI.php @@ -751,6 +751,11 @@ private static function init(): void "VipsOperation" => self::$vips->type("VipsOperation*"), "VipsImage" => self::$vips->type("VipsImage*"), "VipsInterpolate" => self::$vips->type("VipsInterpolate*"), + "VipsConnection" => self::$vips->type("VipsConnection*"), + "VipsSource" => self::$vips->type("VipsSource*"), + "VipsSourceCustom" => self::$vips->type("VipsSourceCustom*"), + "VipsTarget" => self::$vips->type("VipsTarget*"), + "VipsTargetCustom" => self::$vips->type("VipsTargetCustom*"), ]; self::$gtypes = [ @@ -774,6 +779,8 @@ private static function init(): void "GObject" => self::$gobject->g_type_from_name("GObject"), "VipsImage" => self::$gobject->g_type_from_name("VipsImage"), + + "GCallback" => self::$gobject->g_type_from_name("GCallback"), ]; // map vips format names to c type names diff --git a/src/Image.php b/src/Image.php index bbddf07..a7b53e1 100644 --- a/src/Image.php +++ b/src/Image.php @@ -901,6 +901,38 @@ public function newFromImage($value): Image ]); } + /** + * Find the name of the load operation vips will use to load a VipsSource, for + * example 'VipsForeignLoadJpegSource'. You can use this to work out what + * options to pass to newFromSource(). + * + * @param VipsSource $source The source to test + * @return string|null The name of the load operation, or null. + */ + public static function findLoadSource(VipsSource $source): ?string + { + return FFI::vips()->vips_foreign_find_load_source(\FFI::cast(FFI::ctypes('VipsSource'), $source->pointer)); + } + + /** + * @throws Exception + */ + public static function newFromSource(VipsSource $source, string $string_options = '', array $options = []): self + { + $loader = self::findLoadSource($source); + if ($loader === null) { + throw new Exception('unable to load from source'); + } + + if ($string_options !== '') { + $options = array_merge([ + "string_options" => $string_options, + ], $options); + } + + return VipsOperation::call($loader, null, [$source], $options); + } + /** * Write an image to a file. * @@ -1040,6 +1072,28 @@ public function writeToArray(): array return $result; } + /** + * @throws Exception + */ + public function writeToTarget(VipsTarget $target, string $suffix, array $options = []): void + { + $filename = Utils::filenameGetFilename($suffix); + $string_options = Utils::filenameGetOptions($suffix); + $saver = FFI::vips()->vips_foreign_find_save_target($filename); + + if ($saver === '') { + throw new Exception("can't save to target with filename $filename"); + } + + if ($string_options !== '') { + $options = array_merge([ + "string_options" => $string_options, + ], $options); + } + + VipsOperation::call($saver, $this, [$target], $options); + } + /** * Copy to memory. * diff --git a/src/VipsObject.php b/src/VipsObject.php index e82a0e2..74bb83b 100644 --- a/src/VipsObject.php +++ b/src/VipsObject.php @@ -56,7 +56,7 @@ abstract class VipsObject extends GObject * * @internal */ - protected \FFI\CData $pointer; + private \FFI\CData $pointer; /** * A pointer to the underlying GObject. This is the same as the diff --git a/src/VipsSource.php b/src/VipsSource.php index 0691edb..a382217 100644 --- a/src/VipsSource.php +++ b/src/VipsSource.php @@ -4,6 +4,20 @@ class VipsSource extends Connection { + /** + * A pointer to the underlying VipsSource. This is the same as the + * GObject, just cast to VipsSource to help FFI. + * + * @internal + */ + public \FFI\CData $pointer; + + public function __construct(\FFI\CData $pointer) + { + $this->pointer = \FFI::cast(FFI::ctypes('VipsSource'), $pointer); + parent::__construct($pointer); + } + /** * Make a new source from a file descriptor (a small integer). * Make a new source that is attached to the descriptor. For example: diff --git a/src/VipsSourceCustom.php b/src/VipsSourceCustom.php index b2841c5..fd4f39c 100644 --- a/src/VipsSourceCustom.php +++ b/src/VipsSourceCustom.php @@ -6,10 +6,18 @@ class VipsSourceCustom extends VipsSource { + /** + * A pointer to the underlying VipsSourceCustom. This is the same as the + * GObject, just cast to VipsSourceCustom to help FFI. + * + * @internal + */ + public \FFI\CData $pointer; + public function __construct() { - $source = \FFI::cast(FFI::ctypes('VipsSource'), FFI::vips()->vips_source_custom_new()); - parent::__construct($source); + $this->pointer = FFI::vips()->vips_source_custom_new(); + parent::__construct($this->pointer); } /** diff --git a/src/VipsSourceResource.php b/src/VipsSourceResource.php index f26cae4..6027c01 100644 --- a/src/VipsSourceResource.php +++ b/src/VipsSourceResource.php @@ -12,7 +12,8 @@ class VipsSourceResource extends VipsSourceCustom private $resource; /** - * The resource passed in will become "owned" by this class. On destruction of this class, the resource will be closed. + * The resource passed in will become "owned" by this class. + * On destruction of this class, the resource will be closed. * * @param resource $resource */ diff --git a/src/VipsTarget.php b/src/VipsTarget.php index 354fd85..e7b0687 100644 --- a/src/VipsTarget.php +++ b/src/VipsTarget.php @@ -4,6 +4,20 @@ class VipsTarget extends Connection { + /** + * A pointer to the underlying VipsTarget. This is the same as the + * GObject, just cast to VipsTarget to help FFI. + * + * @internal + */ + public \FFI\CData $pointer; + + public function __construct(\FFI\CData $pointer) + { + $this->pointer = \FFI::cast(FFI::ctypes('VipsTarget'), $pointer); + parent::__construct($pointer); + } + public static function newToDescriptor(int $descriptor): self { $pointer = FFI::vips()->vips_target_new_to_descriptor($descriptor); diff --git a/src/VipsTargetCustom.php b/src/VipsTargetCustom.php index 75e3cd2..5901b72 100644 --- a/src/VipsTargetCustom.php +++ b/src/VipsTargetCustom.php @@ -6,10 +6,18 @@ class VipsTargetCustom extends VipsTarget { + /** + * A pointer to the underlying VipsTargetCustom. This is the same as the + * GObject, just cast to VipsTargetCustom to help FFI. + * + * @internal + */ + public \FFI\CData $pointer; + public function __construct() { - $pointer = \FFI::cast(FFI::ctypes('VipsTarget'), FFI::vips()->vips_target_custom_new()); - parent::__construct($pointer); + $this->pointer = FFI::vips()->vips_target_custom_new(); + parent::__construct($this->pointer); } public function onWrite(Closure $callback): void diff --git a/src/VipsTargetResource.php b/src/VipsTargetResource.php index 712c4ca..57d3dfa 100644 --- a/src/VipsTargetResource.php +++ b/src/VipsTargetResource.php @@ -10,7 +10,8 @@ class VipsTargetResource extends VipsTargetCustom private $resource; /** - * The resource passed in will become "owned" by this class. On destruction of this class, the resource will be closed. + * The resource passed in will become "owned" by this class. + * On destruction of this class, the resource will be closed. * * @param resource $resource */ diff --git a/tests/StreamingTest.php b/tests/StreamingTest.php new file mode 100644 index 0000000..c61882b --- /dev/null +++ b/tests/StreamingTest.php @@ -0,0 +1,77 @@ + fn() => VipsSource::newFromFile(__DIR__ . '/images/img_0076.jpg'), + 'Memory' => fn() => VipsSource::newFromMemory(file_get_contents(__DIR__ . '/images/img_0076.jpg')), + 'Resource' => fn() => new VipsSourceResource(fopen(__DIR__ . '/images/img_0076.jpg', 'rb')) + ]; + $targets = [ + 'File' => fn() => VipsTarget::newToFile(tempnam(sys_get_temp_dir(), 'image')), + 'Memory' => fn() => VipsTarget::newToMemory(), + 'Resource' => fn() => new VipsTargetResource(fopen('php://memory', 'wb+')), + 'Resource(Not Readable)' => fn() => new VipsTargetResource(fopen('php://memory', 'wb')) + ]; + + foreach ($sources as $sourceName => $source) { + foreach ($targets as $targetName => $target) { + yield "$sourceName => $targetName" => [$source(), $target()]; + } + } + } + + /** + * @dataProvider sourceAndTargetProvider + */ + public function testFromSourceToTarget(VipsSource $source, VipsTarget $target): void + { + $image = Image::newFromSource($source); + $image->writeToTarget($target, '.jpg[Q=95]'); + + // Try delete temporary file + if ($target->filename() !== null) { + @unlink($target->filename()); + } + } + + /** + * This test case is extra since it's the easiest to make sure we can "reload" the saved image + */ + public function testFromFileToFile(): void + { + $source = VipsSource::newFromFile(__DIR__ . '/images/img_0076.jpg'); + $target = VipsTarget::newToFile(tempnam(sys_get_temp_dir(), 'image')); + $image = Image::newFromSource($source); + $image->writeToTarget($target, '.jpg[Q=95]'); + + // Make sure we can load the file + $image = Image::newFromFile($target->filename()); + $image->writeToBuffer('.jpg[Q=95]'); + unlink($target->filename()); + } + + public function testFromFileToDescriptor(): void + { + // NOTE(L3tum): There is no way to get a file descriptor in PHP :) + // In theory we could use the known fds like stdin or stdout, + // but that would spam those channels full with an entire image file. + // Because of that I've chosen to omit this test. + } +} From f6287c17506c45b376f50f3bde5fd4cc9cb8712f Mon Sep 17 00:00:00 2001 From: L3tum <9307432+L3tum@users.noreply.github.com> Date: Mon, 27 Feb 2023 16:36:28 +0100 Subject: [PATCH 03/15] fix: Callbacks are working now --- src/FFI.php | 17 +++++- src/GObject.php | 140 ++++++++++++++++++++++++++++++++++----------- src/GValue.php | 4 +- src/VipsSource.php | 7 ++- 4 files changed, 130 insertions(+), 38 deletions(-) diff --git a/src/FFI.php b/src/FFI.php index c396f25..f00d4d3 100644 --- a/src/FFI.php +++ b/src/FFI.php @@ -302,6 +302,7 @@ private static function init(): void typedef int32_t gint32; typedef uint64_t guint64; typedef int64_t gint64; +typedef void* gpointer; typedef $gtype GType; @@ -366,6 +367,7 @@ private static function init(): void void g_value_set_flags (GValue* value, unsigned int f); void g_value_set_string (GValue* value, const char* str); void g_value_set_object (GValue* value, void* object); +void g_value_set_pointer (GValue* value, gpointer pointer); bool g_value_get_boolean (const GValue* value); int g_value_get_int (GValue* value); @@ -376,6 +378,7 @@ private static function init(): void unsigned int g_value_get_flags (GValue* value); const char* g_value_get_string (GValue* value); void* g_value_get_object (GValue* value); +gpointer g_value_get_pointer (GValue* value); typedef struct _GEnumValue { int value; @@ -429,6 +432,17 @@ private static function init(): void int connect_flags); const char* g_param_spec_get_blurb (GParamSpec* psp); + +typedef struct _GClosure GClosure; +typedef void (*marshaler)(struct GClosure* closure, GValue* return_value, int n_param_values, const GValue* param_values, void* invocation_hint, void* marshal_data); +struct _GClosure { + int in_marshal : 1; + int is_invalid : 1; + marshaler marshal; +}; +long g_signal_connect_closure(GObject* object, const char* detailed_signal, GClosure *closure, bool after); +GClosure* g_closure_ref(GClosure* closure); +GClosure* g_closure_new_simple (int sizeof_closure, void* data); EOS; # the whole libvips API, mostly adapted from pyvips @@ -746,6 +760,7 @@ private static function init(): void // look these up in advance self::$ctypes = [ "GObject" => self::$gobject->type("GObject*"), + "GClosure" => self::$gobject->type("GClosure"), "GParamSpec" => self::$gobject->type("GParamSpec*"), "VipsObject" => self::$vips->type("VipsObject*"), "VipsOperation" => self::$vips->type("VipsOperation*"), @@ -780,7 +795,7 @@ private static function init(): void "GObject" => self::$gobject->g_type_from_name("GObject"), "VipsImage" => self::$gobject->g_type_from_name("VipsImage"), - "GCallback" => self::$gobject->g_type_from_name("GCallback"), + "GClosure" => self::$gobject->g_type_from_name("GClosure"), ]; // map vips format names to c type names diff --git a/src/GObject.php b/src/GObject.php index c87fd92..3a24698 100644 --- a/src/GObject.php +++ b/src/GObject.php @@ -67,7 +67,7 @@ abstract class GObject * Don't call this yourself, users should stick to (for example) * Image::newFromFile(). * - * @param FFI\CData $pointer The underlying pointer that this + * @param CData $pointer The underlying pointer that this * object should wrap. * * @internal @@ -99,39 +99,111 @@ public function unref(): void public function signalConnect(string $name, Closure $callback): void { - static $marshalers = null; - - if ($marshalers === null) { - $imageProgressCb = static function (CData $vi, CData $progress, CData $handle) { - FFI::gobject()->g_object_ref($vi); - $image = new Image($vi); - $progress = \FFI::cast(FFI::ctypes('VipsProgress'), $progress); - $handle($image, $progress); + $imageProgressCb = static function ( + CData $gClosure, + ?CData $returnValue, + int $numberOfParams, + CData $params, + CData $hint, + ?CData $data + ) use ($callback) { + assert($numberOfParams === 3); + /** + * Marshal-Signature: void(VipsImage*, void*, void*) + */ + $vi = \FFI::cast(FFI::ctypes('GObject'), FFI::gobject()->g_value_get_pointer(\FFI::addr($params[1]))); + FFI::gobject()->g_object_ref($vi); + $image = new Image($vi); + $pr = \FFI::cast(FFI::ctypes('VipsProgress'), FFI::gobject()->g_value_get_pointer(\FFI::addr($params[2]))); + $callback($image, $pr); + }; + $marshalers = ['preeval' => $imageProgressCb, 'eval' => $imageProgressCb, 'posteval' => $imageProgressCb]; + + if (FFI::atLeast(8, 9)) { + $marshalers['read'] = static function ( + CData $gClosure, + CData $returnValue, + int $numberOfParams, + CData $params, + CData $hint, + ?CData $data + ) use (&$callback): void { + assert($numberOfParams === 4); + /* + * Marshal-Signature: gint64(VipsSourceCustom*, void*, gint64, void*) + */ + $bufferPointer = FFI::gobject()->g_value_get_pointer(\FFI::addr($params[1])); + $bufferLength = (int) FFI::gobject()->g_value_get_int64(\FFI::addr($params[2])); + $buffer = \FFI::string($bufferPointer, $bufferLength); + $returnBufferLength = $callback($buffer); + \FFI::memcpy($bufferPointer, $buffer, $returnBufferLength); + FFI::gobject()->g_value_set_int64($returnValue, $returnBufferLength); + FFI::gobject()->g_value_set_pointer(\FFI::addr($params[1]), $bufferPointer); }; - $marshalers = ['preeval' => $imageProgressCb, 'eval' => $imageProgressCb, 'posteval' => $imageProgressCb]; - - if (FFI::atLeast(8, 9)) { - $marshalers['read'] = static function (CData $gObject, CData $pointer, int $length, CData $handle): int { - $buffer = \FFI::string($pointer, $length); - return $handle($buffer); - }; - $marshalers['seek'] = static function (CData $gObject, int $offset, int $whence, CData $handle): int { - return $handle($offset, $whence); - }; - $marshalers['write'] = static function (CData $gObject, CData $pointer, int $length, CData $handle): int { - $buffer = \FFI::string($pointer, $length); - return $handle($buffer); - }; - $marshalers['finish'] = static function (CData $gObject, CData $handle): void { - $handle(); - }; - } + $marshalers['seek'] = static function ( + CData $gClosure, + CData $returnValue, + int $numberOfParams, + CData $params, + CData $hint, + ?CData $data + ) use (&$callback): void { + assert($numberOfParams === 4); + /* + * Marshal-Signature: gint64(VipsSourceCustom*, gint64, int, void*) + */ + $offset = (int) FFI::gobject()->g_value_get_int64(\FFI::addr($params[1])); + $whence = (int) FFI::gobject()->g_value_get_int(\FFI::addr($params[2])); + FFI::gobject()->g_value_set_int64($returnValue, $callback($offset, $whence)); + }; + $marshalers['write'] = static function ( + CData $gClosure, + CData $returnValue, + int $numberOfParams, + CData $params, + CData $hint, + ?CData $data + ) use (&$callback): void { + assert($numberOfParams === 4); + /* + * Marshal-Signature: gint64(VipsTargetCustom*, void*, gint64, void*) + */ + $bufferPointer = FFI::gobject()->g_value_get_pointer(\FFI::addr($params[1])); + $bufferLength = (int) FFI::gobject()->g_value_get_int64(\FFI::addr($params[2])); + $buffer = \FFI::string($bufferPointer, $bufferLength); + FFI::gobject()->g_value_set_int64($returnValue, $callback($buffer)); + }; + $marshalers['finish'] = static function ( + CData $gClosure, + ?CData $returnValue, + int $numberOfParams, + CData $params, + CData $hint, + ?CData $data + ) use (&$callback): void { + assert($numberOfParams === 2); + /** + * Marshal-Signature: void(VipsTargetCustom*, void*) + */ + $callback(); + }; + } - if (FFI::atLeast(8, 13)) { - $marshalers['end'] = static function (CData $gObject, CData $handle): int { - return $handle(); - }; - } + if (FFI::atLeast(8, 13)) { + $marshalers['end'] = static function ( + CData $gClosure, + CData $returnValue, + int $numberOfParams, + CData $params, + CData $hint, + ?CData $data + ) use (&$callback): void { + assert($numberOfParams === 2); + /** + * Marshal-Signature: int(VipsTargetCustom*, void*) + */ + FFI::gobject()->g_value_set_int($returnValue, $callback()); + }; } if (!isset($marshalers[$name])) { @@ -139,7 +211,9 @@ public function signalConnect(string $name, Closure $callback): void } $go = \FFI::cast(FFI::ctypes('GObject'), $this->pointer); - FFI::gobject()->g_signal_connect_data($go, $name, $marshalers[$name], $callback, null, 0); + $gc = FFI::gobject()->g_closure_new_simple(64, null); + $gc->marshal = $marshalers[$name]; + FFI::gobject()->g_signal_connect_closure($go, $name, $gc, 0); } } diff --git a/src/GValue.php b/src/GValue.php index 6478352..c310e6a 100644 --- a/src/GValue.php +++ b/src/GValue.php @@ -188,9 +188,7 @@ public function set($value): void # can own and free $n = strlen($value); $memory = \FFI::new("char[$n]", false, true); - for ($i = 0; $i < $n; $i++) { - $memory[$i] = $value[$i]; - } + \FFI::memcpy($memory, $value, $n); FFI::vips()-> vips_value_set_blob_free($this->pointer, $memory, $n); break; diff --git a/src/VipsSource.php b/src/VipsSource.php index a382217..7bb4e17 100644 --- a/src/VipsSource.php +++ b/src/VipsSource.php @@ -61,7 +61,12 @@ public static function newFromFile(string $filename): self */ public static function newFromMemory(string $data): self { - $pointer = FFI::vips()->vips_source_new_from_memory($data, strlen($data)); + # we need to set the memory to a copy of the data that vips_lib + # can own and free + $n = strlen($data); + $memory = \FFI::new("char[$n]", false, true); + \FFI::memcpy($memory, $data, $n); + $pointer = FFI::vips()->vips_source_new_from_memory($memory, $n); if (\FFI::isNull($pointer)) { throw new Exception("can't create source from memory"); From 1a5f7f003faf07a31e1677708f5a5d7c9f02f370 Mon Sep 17 00:00:00 2001 From: L3tum <9307432+L3tum@users.noreply.github.com> Date: Mon, 27 Feb 2023 20:23:47 +0100 Subject: [PATCH 04/15] fix: PHP already converts null fix: Buffer needs to be copied over fix: sizeof(GClosure) works now feat: Added more tests and an example --- examples/streaming.php | 12 ++++++++++ src/FFI.php | 53 +++++++++++++++++++++++++++++++++++++---- src/GObject.php | 7 +++--- src/VipsSource.php | 6 ++--- src/VipsTarget.php | 15 +++++++++--- tests/StreamingTest.php | 19 +++++++++++++++ 6 files changed, 98 insertions(+), 14 deletions(-) create mode 100644 examples/streaming.php diff --git a/examples/streaming.php b/examples/streaming.php new file mode 100644 index 0000000..585185d --- /dev/null +++ b/examples/streaming.php @@ -0,0 +1,12 @@ +#!/usr/bin/env php +writeToTarget($target, '.jpg[Q=95]'); diff --git a/src/FFI.php b/src/FFI.php index f00d4d3..6a76d9f 100644 --- a/src/FFI.php +++ b/src/FFI.php @@ -434,11 +434,54 @@ private static function init(): void const char* g_param_spec_get_blurb (GParamSpec* psp); typedef struct _GClosure GClosure; -typedef void (*marshaler)(struct GClosure* closure, GValue* return_value, int n_param_values, const GValue* param_values, void* invocation_hint, void* marshal_data); -struct _GClosure { - int in_marshal : 1; - int is_invalid : 1; - marshaler marshal; +typedef void (*marshaler)( + struct GClosure* closure, + GValue* return_value, + int n_param_values, + const GValue* param_values, + void* invocation_hint, + void* marshal_data +); + +typedef struct _GClosureNotifyData GClosureNotifyData; +struct _GClosureNotifyData +{ + void* data; + GClosureNotify notify; +}; +struct _GClosure +{ + /*< private >*/ + int ref_count : 15; /* (atomic) */ + /* meta_marshal is not used anymore but must be zero for historical reasons + as it was exposed in the G_CLOSURE_N_NOTIFIERS macro */ + int meta_marshal_nouse : 1; /* (atomic) */ + int n_guards : 1; /* (atomic) */ + int n_fnotifiers : 2; /* finalization notifiers (atomic) */ + int n_inotifiers : 8; /* invalidation notifiers (atomic) */ + int in_inotify : 1; /* (atomic) */ + int floating : 1; /* (atomic) */ + /*< protected >*/ + int derivative_flag : 1; /* (atomic) */ + /*< public >*/ + int in_marshal : 1; /* (atomic) */ + int is_invalid : 1; /* (atomic) */ + + /*< private >*/ marshaler marshal; + /*< protected >*/ void* data; + + /*< private >*/ GClosureNotifyData *notifiers; + + /* invariants/constraints: + * - ->marshal and ->data are _invalid_ as soon as ->is_invalid==TRUE + * - invocation of all inotifiers occurs prior to fnotifiers + * - order of inotifiers is random + * inotifiers may _not_ free/invalidate parameter values (e.g. ->data) + * - order of fnotifiers is random + * - each notifier may only be removed before or during its invocation + * - reference counting may only happen prior to fnotify invocation + * (in that sense, fnotifiers are really finalization handlers) + */ }; long g_signal_connect_closure(GObject* object, const char* detailed_signal, GClosure *closure, bool after); GClosure* g_closure_ref(GClosure* closure); diff --git a/src/GObject.php b/src/GObject.php index 3a24698..45bd964 100644 --- a/src/GObject.php +++ b/src/GObject.php @@ -138,7 +138,6 @@ public function signalConnect(string $name, Closure $callback): void $returnBufferLength = $callback($buffer); \FFI::memcpy($bufferPointer, $buffer, $returnBufferLength); FFI::gobject()->g_value_set_int64($returnValue, $returnBufferLength); - FFI::gobject()->g_value_set_pointer(\FFI::addr($params[1]), $bufferPointer); }; $marshalers['seek'] = static function ( CData $gClosure, @@ -171,7 +170,9 @@ public function signalConnect(string $name, Closure $callback): void $bufferPointer = FFI::gobject()->g_value_get_pointer(\FFI::addr($params[1])); $bufferLength = (int) FFI::gobject()->g_value_get_int64(\FFI::addr($params[2])); $buffer = \FFI::string($bufferPointer, $bufferLength); - FFI::gobject()->g_value_set_int64($returnValue, $callback($buffer)); + $returnBufferLength = $callback($buffer); + \FFI::memcpy($bufferPointer, $buffer, $returnBufferLength); + FFI::gobject()->g_value_set_int64($returnValue, $returnBufferLength); }; $marshalers['finish'] = static function ( CData $gClosure, @@ -211,7 +212,7 @@ public function signalConnect(string $name, Closure $callback): void } $go = \FFI::cast(FFI::ctypes('GObject'), $this->pointer); - $gc = FFI::gobject()->g_closure_new_simple(64, null); + $gc = FFI::gobject()->g_closure_new_simple(\FFI::sizeof(FFI::ctypes('GClosure')), null); $gc->marshal = $marshalers[$name]; FFI::gobject()->g_signal_connect_closure($go, $name, $gc, 0); } diff --git a/src/VipsSource.php b/src/VipsSource.php index 7bb4e17..ee3ef71 100644 --- a/src/VipsSource.php +++ b/src/VipsSource.php @@ -30,7 +30,7 @@ public static function newFromDescriptor(int $descriptor): self { $pointer = FFI::vips()->vips_source_new_from_descriptor($descriptor); - if (\FFI::isNull($pointer)) { + if ($pointer === null) { throw new Exception("can't create source from descriptor $descriptor"); } @@ -48,7 +48,7 @@ public static function newFromFile(string $filename): self { $pointer = FFI::vips()->vips_source_new_from_file($filename); - if (\FFI::isNull($pointer)) { + if ($pointer === null) { throw new Exception("can't create source from filename $filename"); } @@ -68,7 +68,7 @@ public static function newFromMemory(string $data): self \FFI::memcpy($memory, $data, $n); $pointer = FFI::vips()->vips_source_new_from_memory($memory, $n); - if (\FFI::isNull($pointer)) { + if ($pointer === null) { throw new Exception("can't create source from memory"); } diff --git a/src/VipsTarget.php b/src/VipsTarget.php index e7b0687..a67e663 100644 --- a/src/VipsTarget.php +++ b/src/VipsTarget.php @@ -18,32 +18,41 @@ public function __construct(\FFI\CData $pointer) parent::__construct($pointer); } + /** + * @throws Exception + */ public static function newToDescriptor(int $descriptor): self { $pointer = FFI::vips()->vips_target_new_to_descriptor($descriptor); - if (\FFI::isNull($pointer)) { + if ($pointer === null) { throw new Exception("can't create output target from descriptor $descriptor"); } return new self($pointer); } + /** + * @throws Exception + */ public static function newToFile(string $filename): self { $pointer = FFI::vips()->vips_target_new_to_file($filename); - if (\FFI::isNull($pointer)) { + if ($pointer === null) { throw new Exception("can't create output target from filename $filename"); } return new self($pointer); } + /** + * @throws Exception + */ public static function newToMemory(): self { $pointer = FFI::vips()->vips_target_new_to_memory(); - if (\FFI::isNull($pointer)) { + if ($pointer === null) { throw new Exception("can't create output target from memory"); } diff --git a/tests/StreamingTest.php b/tests/StreamingTest.php index c61882b..741ee74 100644 --- a/tests/StreamingTest.php +++ b/tests/StreamingTest.php @@ -67,6 +67,25 @@ public function testFromFileToFile(): void unlink($target->filename()); } + public function testNoLeak(): void + { + $lastUsage = 0; + for ($i = 0; $i < 10; $i++) { + $filename = tempnam(sys_get_temp_dir(), 'image'); + $source = new VipsSourceResource(fopen(__DIR__ . '/images/img_0076.jpg', 'rb')); + $target = new VipsTargetResource(fopen($filename, 'wb+')); + $image = Image::newFromSource($source); + $image->writeToTarget($target, '.jpg[Q=95]'); + unlink($filename); + $usage = memory_get_peak_usage(true); + $diff = $usage - $lastUsage; + if ($lastUsage !== 0 && $diff > 0) { + echo "LEAK LEAK LEAK" . PHP_EOL; + } + $lastUsage = $usage; + } + } + public function testFromFileToDescriptor(): void { // NOTE(L3tum): There is no way to get a file descriptor in PHP :) From 2feeb9ac08255ad7c255f54c2c79446e71f3b79e Mon Sep 17 00:00:00 2001 From: L3tum <9307432+L3tum@users.noreply.github.com> Date: Tue, 28 Feb 2023 15:36:29 +0100 Subject: [PATCH 05/15] chore: Generate autodoc methods for source/target feat: Add benchmark example --- examples/generate_phpdoc.py | 8 +- examples/streaming-bench.php | 131 ++++++++++++++++++++++++++++++++ src/ForeignDzLayout.php | 1 - src/GsfOutputCsvQuotingMode.php | 54 +++++++++++++ src/ImageAutodoc.php | 88 ++++++++------------- src/OperationMath.php | 6 -- src/OperationMath2.php | 1 - tests/StreamingTest.php | 5 +- 8 files changed, 225 insertions(+), 69 deletions(-) create mode 100644 examples/streaming-bench.php create mode 100644 src/GsfOutputCsvQuotingMode.php diff --git a/examples/generate_phpdoc.py b/examples/generate_phpdoc.py index 0f2c60f..006a34e 100755 --- a/examples/generate_phpdoc.py +++ b/examples/generate_phpdoc.py @@ -31,7 +31,9 @@ GValue.array_int_type: 'integer[]|integer', GValue.array_double_type: 'float[]|float', GValue.array_image_type: 'Image[]|Image', - GValue.blob_type: 'string' + GValue.blob_type: 'string', + GValue.source_type: 'VipsSource', + GValue.target_type: 'VipsTarget' } # php result type names are different, annoyingly, and very restricted @@ -48,7 +50,9 @@ GValue.array_int_type: 'array', GValue.array_double_type: 'array', GValue.array_image_type: 'array', - GValue.blob_type: 'string' + GValue.blob_type: 'string', + GValue.source_type: 'VipsSource', + GValue.target_type: 'VipsTarget' } # values for VipsArgumentFlags diff --git a/examples/streaming-bench.php b/examples/streaming-bench.php new file mode 100644 index 0000000..cbf22f1 --- /dev/null +++ b/examples/streaming-bench.php @@ -0,0 +1,131 @@ +#!/usr/bin/env php + 'sequential']; + $sourceOptionString = 'access=sequential'; + $iterations = 100; + $targetWidth = 100.0; + $targetSuffix = '.jpg'; + $targetOptions = ['optimize-coding' => true, 'strip' => true, 'Q' => 100, 'profile' => 'srgb']; + $targetFile = dirname(__DIR__) . "/tests/images/target.jpg"; + $sourceFile = dirname(__DIR__) . '/tests/images/img_0076.jpg'; + +### Callbacks + $start = microtime(true); + + for ($i = 0; $i < $iterations; $i++) { + $source = new VipsSourceResource(fopen($sourceFile, 'rb')); + $target = new VipsTargetResource(fopen($targetFile, 'wb+')); + $image = Image::newFromSource($source, '', $sourceOptions); + $image = $image->resize($targetWidth / $image->width); + $image->writeToTarget( + $target, + $targetSuffix, + $targetOptions + ); + unlink($targetFile); + } + + echo (microtime(true) - $start) . ' Seconds for Streaming with callbacks' . PHP_EOL; + +### Builtin + $start = microtime(true); + + for ($i = 0; $i < $iterations; $i++) { + $source = VipsSource::newFromFile($sourceFile); + $target = VipsTarget::newToFile($targetFile); + $image = Image::newFromSource($source, '', $sourceOptions); + $image = $image->resize($targetWidth / $image->width); + $image->writeToTarget( + $target, + $targetSuffix, + $targetOptions + ); + unlink($targetFile); + } + + echo (microtime(true) - $start) . ' Seconds for Streaming with builtin source/target' . PHP_EOL; + +### Callbacks Thumbnail + $start = microtime(true); + + for ($i = 0; $i < $iterations; $i++) { + $source = new VipsSourceResource(fopen($sourceFile, 'rb')); + $target = new VipsTargetResource(fopen($targetFile, 'wb+')); + $image = Image::thumbnail_source($source, $targetWidth); + $image->writeToTarget( + $target, + $targetSuffix, + $targetOptions + ); + unlink($targetFile); + } + + echo (microtime(true) - $start) . ' Seconds for Streaming Thumbnail with callbacks' . PHP_EOL; + +### Builtin Thumbnail + $start = microtime(true); + + for ($i = 0; $i < $iterations; $i++) { + $source = VipsSource::newFromFile($sourceFile); + $target = VipsTarget::newToFile($targetFile); + $image = Image::thumbnail_source($source, $targetWidth); + $image->writeToTarget( + $target, + $targetSuffix, + $targetOptions + ); + unlink($targetFile); + } + + echo (microtime(true) - $start) . ' Seconds for Streaming Thumbnail with builtin source/target' . PHP_EOL; + +### Thumbnail + $start = microtime(true); + + for ($i = 0; $i < $iterations; $i++) { + $image = Image::thumbnail($sourceFile . "[$sourceOptionString]", $targetWidth); + $image->writeToFile( + $targetFile, + $targetOptions + ); + unlink($targetFile); + } + + echo (microtime(true) - $start) . ' Seconds for Thumbnail API' . PHP_EOL; + +### Classic + $start = microtime(true); + + for ($i = 0; $i < $iterations; $i++) { + $image = Image::newFromFile($sourceFile, $sourceOptions); + $image = $image->resize($targetWidth / $image->width); + $image->writeToFile( + $targetFile, + $targetOptions + ); + unlink($targetFile); + } + + echo (microtime(true) - $start) . ' Seconds for Classic API' . PHP_EOL; +}; + +$doBenchmark(); + +echo "=== NOW NO CACHE ===" . PHP_EOL; + +Config::cacheSetMax(0); +Config::cacheSetMaxFiles(0); +Config::cacheSetMaxMem(0); + +$doBenchmark(); diff --git a/src/ForeignDzLayout.php b/src/ForeignDzLayout.php index ae36259..021d75f 100644 --- a/src/ForeignDzLayout.php +++ b/src/ForeignDzLayout.php @@ -53,5 +53,4 @@ abstract class ForeignDzLayout const ZOOMIFY = 'zoomify'; const GOOGLE = 'google'; const IIIF = 'iiif'; - const IIIF3 = 'iiif3'; } diff --git a/src/GsfOutputCsvQuotingMode.php b/src/GsfOutputCsvQuotingMode.php new file mode 100644 index 0000000..a8a08f0 --- /dev/null +++ b/src/GsfOutputCsvQuotingMode.php @@ -0,0 +1,54 @@ + + * @copyright 2016 John Cupitt + * @license https://opensource.org/licenses/MIT MIT + * @link https://github.com/jcupitt/php-vips + */ + +namespace Jcupitt\Vips; + +/** + * The GsfOutputCsvQuotingMode enum. + * @category Images + * @package Jcupitt\Vips + * @author John Cupitt + * @copyright 2016 John Cupitt + * @license https://opensource.org/licenses/MIT MIT + * @link https://github.com/jcupitt/php-vips + */ +abstract class GsfOutputCsvQuotingMode +{ + const NEVER = 'never'; + const AUTO = 'auto'; +} diff --git a/src/ImageAutodoc.php b/src/ImageAutodoc.php index 529c834..5162dae 100644 --- a/src/ImageAutodoc.php +++ b/src/ImageAutodoc.php @@ -171,11 +171,11 @@ * @throws Exception * @method static Image csvload(string $filename, array $options = []) Load csv. * @throws Exception - * @method static Image csvload_source(string $source, array $options = []) Load csv. + * @method static Image csvload_source(VipsSource $source, array $options = []) Load csv. * @throws Exception * @method void csvsave(string $filename, array $options = []) Save image to csv. * @throws Exception - * @method void csvsave_target(string $target, array $options = []) Save image to csv. + * @method void csvsave_target(VipsTarget $target, array $options = []) Save image to csv. * @throws Exception * @method Image dE00(Image $right, array $options = []) Calculate dE00. * @throws Exception @@ -203,8 +203,6 @@ * @throws Exception * @method string dzsave_buffer(array $options = []) Save image to dz buffer. * @throws Exception - * @method void dzsave_target(string $target, array $options = []) Save image to deepzoom target. - * @throws Exception * @method Image embed(integer $x, integer $y, integer $width, integer $height, array $options = []) Embed an image in a larger image. * @throws Exception * @method Image extract_area(integer $left, integer $top, integer $width, integer $height, array $options = []) Extract an area from an image. @@ -229,7 +227,7 @@ * @throws Exception * @method static Image fitsload(string $filename, array $options = []) Load a FITS image. * @throws Exception - * @method static Image fitsload_source(string $source, array $options = []) Load FITS from a source. + * @method static Image fitsload_source(VipsSource $source, array $options = []) Load FITS from a source. * @throws Exception * @method void fitssave(string $filename, array $options = []) Save image to fits file. * @throws Exception @@ -260,13 +258,7 @@ * @throws Exception * @method static Image gifload_buffer(string $buffer, array $options = []) Load GIF with libnsgif. * @throws Exception - * @method static Image gifload_source(string $source, array $options = []) Load gif from source. - * @throws Exception - * @method void gifsave(string $filename, array $options = []) Save as gif. - * @throws Exception - * @method string gifsave_buffer(array $options = []) Save as gif. - * @throws Exception - * @method void gifsave_target(string $target, array $options = []) Save as gif. + * @method static Image gifload_source(VipsSource $source, array $options = []) Load gif from source. * @throws Exception * @method Image globalbalance(array $options = []) Global balance an image mosaic. * @throws Exception @@ -281,13 +273,13 @@ * @throws Exception * @method static Image heifload_buffer(string $buffer, array $options = []) Load a HEIF image. * @throws Exception - * @method static Image heifload_source(string $source, array $options = []) Load a HEIF image. + * @method static Image heifload_source(VipsSource $source, array $options = []) Load a HEIF image. * @throws Exception * @method void heifsave(string $filename, array $options = []) Save image in HEIF format. * @throws Exception * @method string heifsave_buffer(array $options = []) Save image in HEIF format. * @throws Exception - * @method void heifsave_target(string $target, array $options = []) Save image in HEIF format. + * @method void heifsave_target(VipsTarget $target, array $options = []) Save image in HEIF format. * @throws Exception * @method Image hist_cum(array $options = []) Form cumulative histogram. * @throws Exception @@ -334,23 +326,11 @@ * @method Image join(Image $in2, string $direction, array $options = []) Join a pair of images. * @see Direction for possible values for $direction * @throws Exception - * @method static Image jp2kload(string $filename, array $options = []) Load JPEG2000 image. - * @throws Exception - * @method static Image jp2kload_buffer(string $buffer, array $options = []) Load JPEG2000 image. - * @throws Exception - * @method static Image jp2kload_source(string $source, array $options = []) Load JPEG2000 image. - * @throws Exception - * @method void jp2ksave(string $filename, array $options = []) Save image in JPEG2000 format. - * @throws Exception - * @method string jp2ksave_buffer(array $options = []) Save image in JPEG2000 format. - * @throws Exception - * @method void jp2ksave_target(string $target, array $options = []) Save image in JPEG2000 format. - * @throws Exception * @method static Image jpegload(string $filename, array $options = []) Load jpeg from file. * @throws Exception * @method static Image jpegload_buffer(string $buffer, array $options = []) Load jpeg from buffer. * @throws Exception - * @method static Image jpegload_source(string $source, array $options = []) Load image from jpeg source. + * @method static Image jpegload_source(VipsSource $source, array $options = []) Load image from jpeg source. * @throws Exception * @method void jpegsave(string $filename, array $options = []) Save image to jpeg file. * @throws Exception @@ -358,19 +338,19 @@ * @throws Exception * @method void jpegsave_mime(array $options = []) Save image to jpeg mime. * @throws Exception - * @method void jpegsave_target(string $target, array $options = []) Save image to jpeg target. + * @method void jpegsave_target(VipsTarget $target, array $options = []) Save image to jpeg target. * @throws Exception * @method static Image jxlload(string $filename, array $options = []) Load JPEG-XL image. * @throws Exception * @method static Image jxlload_buffer(string $buffer, array $options = []) Load JPEG-XL image. * @throws Exception - * @method static Image jxlload_source(string $source, array $options = []) Load JPEG-XL image. + * @method static Image jxlload_source(VipsSource $source, array $options = []) Load JPEG-XL image. * @throws Exception * @method void jxlsave(string $filename, array $options = []) Save image in JPEG-XL format. * @throws Exception * @method string jxlsave_buffer(array $options = []) Save image in JPEG-XL format. * @throws Exception - * @method void jxlsave_target(string $target, array $options = []) Save image in JPEG-XL format. + * @method void jxlsave_target(VipsTarget $target, array $options = []) Save image in JPEG-XL format. * @throws Exception * @method Image labelregions(array $options = []) Label regions in an image. * @throws Exception @@ -378,7 +358,7 @@ * @throws Exception * @method Image linecache(array $options = []) Cache an image as a set of lines. * @throws Exception - * @method static Image logmat(float $sigma, float $min_ampl, array $options = []) Make a Laplacian of Gaussian image. + * @method static Image logmat(float $sigma, float $min_ampl, array $options = []) Make a laplacian of gaussian image. * @throws Exception * @method static Image magickload(string $filename, array $options = []) Load file with ImageMagick. * @throws Exception @@ -429,13 +409,13 @@ * @throws Exception * @method static Image matrixload(string $filename, array $options = []) Load matrix. * @throws Exception - * @method static Image matrixload_source(string $source, array $options = []) Load matrix. + * @method static Image matrixload_source(VipsSource $source, array $options = []) Load matrix. * @throws Exception * @method void matrixprint(array $options = []) Print matrix. * @throws Exception * @method void matrixsave(string $filename, array $options = []) Save image to matrix. * @throws Exception - * @method void matrixsave_target(string $target, array $options = []) Save image to matrix. + * @method void matrixsave_target(VipsTarget $target, array $options = []) Save image to matrix. * @throws Exception * @method float max(array $options = []) Find image maximum. * @throws Exception @@ -457,23 +437,17 @@ * @throws Exception * @method Image msb(array $options = []) Pick most-significant byte from an image. * @throws Exception - * @method static Image niftiload(string $filename, array $options = []) Load NIfTI volume. - * @throws Exception - * @method static Image niftiload_source(string $source, array $options = []) Load NIfTI volumes. - * @throws Exception - * @method void niftisave(string $filename, array $options = []) Save image to nifti file. - * @throws Exception * @method static Image openexrload(string $filename, array $options = []) Load an OpenEXR image. * @throws Exception * @method static Image openslideload(string $filename, array $options = []) Load file with OpenSlide. * @throws Exception - * @method static Image openslideload_source(string $source, array $options = []) Load source with OpenSlide. + * @method static Image openslideload_source(VipsSource $source, array $options = []) Load source with OpenSlide. * @throws Exception * @method static Image pdfload(string $filename, array $options = []) Load PDF from file. * @throws Exception * @method static Image pdfload_buffer(string $buffer, array $options = []) Load PDF from buffer. * @throws Exception - * @method static Image pdfload_source(string $source, array $options = []) Load PDF from source. + * @method static Image pdfload_source(VipsSource $source, array $options = []) Load PDF from source. * @throws Exception * @method integer percent(float $percent, array $options = []) Find threshold for percent of pixels. * @throws Exception @@ -485,21 +459,21 @@ * @throws Exception * @method static Image pngload_buffer(string $buffer, array $options = []) Load png from buffer. * @throws Exception - * @method static Image pngload_source(string $source, array $options = []) Load png from source. + * @method static Image pngload_source(VipsSource $source, array $options = []) Load png from source. * @throws Exception - * @method void pngsave(string $filename, array $options = []) Save image to file as PNG. + * @method void pngsave(string $filename, array $options = []) Save image to png file. * @throws Exception - * @method string pngsave_buffer(array $options = []) Save image to buffer as PNG. + * @method string pngsave_buffer(array $options = []) Save image to png buffer. * @throws Exception - * @method void pngsave_target(string $target, array $options = []) Save image to target as PNG. + * @method void pngsave_target(VipsTarget $target, array $options = []) Save image to target as PNG. * @throws Exception * @method static Image ppmload(string $filename, array $options = []) Load ppm from file. * @throws Exception - * @method static Image ppmload_source(string $source, array $options = []) Load ppm base class. + * @method static Image ppmload_source(VipsSource $source, array $options = []) Load ppm base class. * @throws Exception * @method void ppmsave(string $filename, array $options = []) Save image to ppm file. * @throws Exception - * @method void ppmsave_target(string $target, array $options = []) Save to ppm. + * @method void ppmsave_target(VipsTarget $target, array $options = []) Save to ppm. * @throws Exception * @method Image premultiply(array $options = []) Premultiply image alpha. * @throws Exception @@ -525,13 +499,13 @@ * @throws Exception * @method static Image radload_buffer(string $buffer, array $options = []) Load rad from buffer. * @throws Exception - * @method static Image radload_source(string $source, array $options = []) Load rad from source. + * @method static Image radload_source(VipsSource $source, array $options = []) Load rad from source. * @throws Exception * @method void radsave(string $filename, array $options = []) Save image to Radiance file. * @throws Exception * @method string radsave_buffer(array $options = []) Save image to Radiance buffer. * @throws Exception - * @method void radsave_target(string $target, array $options = []) Save image to Radiance target. + * @method void radsave_target(VipsTarget $target, array $options = []) Save image to Radiance target. * @throws Exception * @method Image rank(integer $width, integer $height, integer $index, array $options = []) Rank filter. * @throws Exception @@ -619,7 +593,7 @@ * @throws Exception * @method static Image svgload_buffer(string $buffer, array $options = []) Load SVG with rsvg. * @throws Exception - * @method static Image svgload_source(string $source, array $options = []) Load svg from source. + * @method static Image svgload_source(VipsSource $source, array $options = []) Load svg from source. * @throws Exception * @method static Image switch(Image[]|Image $tests, array $options = []) Find the index of the first non-zero pixel in tests. * @throws Exception @@ -633,20 +607,18 @@ * @throws Exception * @method Image thumbnail_image(integer $width, array $options = []) Generate thumbnail from image. * @throws Exception - * @method static Image thumbnail_source(string $source, integer $width, array $options = []) Generate thumbnail from source. + * @method static Image thumbnail_source(VipsSource $source, integer $width, array $options = []) Generate thumbnail from source. * @throws Exception * @method static Image tiffload(string $filename, array $options = []) Load tiff from file. * @throws Exception * @method static Image tiffload_buffer(string $buffer, array $options = []) Load tiff from buffer. * @throws Exception - * @method static Image tiffload_source(string $source, array $options = []) Load tiff from source. + * @method static Image tiffload_source(VipsSource $source, array $options = []) Load tiff from source. * @throws Exception * @method void tiffsave(string $filename, array $options = []) Save image to tiff file. * @throws Exception * @method string tiffsave_buffer(array $options = []) Save image to tiff buffer. * @throws Exception - * @method void tiffsave_target(string $target, array $options = []) Save image to tiff target. - * @throws Exception * @method Image tilecache(array $options = []) Cache an image as a set of tiles. * @throws Exception * @method static Image tonelut(array $options = []) Build a look-up table. @@ -657,23 +629,23 @@ * @throws Exception * @method static Image vipsload(string $filename, array $options = []) Load vips from file. * @throws Exception - * @method static Image vipsload_source(string $source, array $options = []) Load vips from source. + * @method static Image vipsload_source(VipsSource $source, array $options = []) Load vips from source. * @throws Exception * @method void vipssave(string $filename, array $options = []) Save image to file in vips format. * @throws Exception - * @method void vipssave_target(string $target, array $options = []) Save image to target in vips format. + * @method void vipssave_target(VipsTarget $target, array $options = []) Save image to target in vips format. * @throws Exception * @method static Image webpload(string $filename, array $options = []) Load webp from file. * @throws Exception * @method static Image webpload_buffer(string $buffer, array $options = []) Load webp from buffer. * @throws Exception - * @method static Image webpload_source(string $source, array $options = []) Load webp from source. + * @method static Image webpload_source(VipsSource $source, array $options = []) Load webp from source. * @throws Exception * @method void webpsave(string $filename, array $options = []) Save image to webp file. * @throws Exception * @method string webpsave_buffer(array $options = []) Save image to webp buffer. * @throws Exception - * @method void webpsave_target(string $target, array $options = []) Save image to webp target. + * @method void webpsave_target(VipsTarget $target, array $options = []) Save image to webp target. * @throws Exception * @method static Image worley(integer $width, integer $height, array $options = []) Make a worley noise image. * @throws Exception diff --git a/src/OperationMath.php b/src/OperationMath.php index 857b488..52ffef1 100644 --- a/src/OperationMath.php +++ b/src/OperationMath.php @@ -59,10 +59,4 @@ abstract class OperationMath const LOG10 = 'log10'; const EXP = 'exp'; const EXP10 = 'exp10'; - const SINH = 'sinh'; - const COSH = 'cosh'; - const TANH = 'tanh'; - const ASINH = 'asinh'; - const ACOSH = 'acosh'; - const ATANH = 'atanh'; } diff --git a/src/OperationMath2.php b/src/OperationMath2.php index b1fc69e..9e86093 100644 --- a/src/OperationMath2.php +++ b/src/OperationMath2.php @@ -51,5 +51,4 @@ abstract class OperationMath2 { const POW = 'pow'; const WOP = 'wop'; - const ATAN2 = 'atan2'; } diff --git a/tests/StreamingTest.php b/tests/StreamingTest.php index 741ee74..694ccd2 100644 --- a/tests/StreamingTest.php +++ b/tests/StreamingTest.php @@ -70,6 +70,7 @@ public function testFromFileToFile(): void public function testNoLeak(): void { $lastUsage = 0; + $leaked = false; for ($i = 0; $i < 10; $i++) { $filename = tempnam(sys_get_temp_dir(), 'image'); $source = new VipsSourceResource(fopen(__DIR__ . '/images/img_0076.jpg', 'rb')); @@ -80,10 +81,12 @@ public function testNoLeak(): void $usage = memory_get_peak_usage(true); $diff = $usage - $lastUsage; if ($lastUsage !== 0 && $diff > 0) { - echo "LEAK LEAK LEAK" . PHP_EOL; + $leaked = true; } $lastUsage = $usage; } + + $this->assertFalse($leaked, 'Streaming leaked memory'); } public function testFromFileToDescriptor(): void From b2d90b5430f26a2e68d1ee2376582d23d2de912c Mon Sep 17 00:00:00 2001 From: L3tum <9307432+L3tum@users.noreply.github.com> Date: Tue, 28 Feb 2023 23:02:07 +0100 Subject: [PATCH 06/15] fix: Avoid extra memcpy in write callback fix: Avoid extra cast in signal_connect --- examples/streaming-bench.php | 14 +++++++------- src/GObject.php | 4 +--- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/examples/streaming-bench.php b/examples/streaming-bench.php index cbf22f1..26c414c 100644 --- a/examples/streaming-bench.php +++ b/examples/streaming-bench.php @@ -122,10 +122,10 @@ $doBenchmark(); -echo "=== NOW NO CACHE ===" . PHP_EOL; - -Config::cacheSetMax(0); -Config::cacheSetMaxFiles(0); -Config::cacheSetMaxMem(0); - -$doBenchmark(); +//echo "=== NOW NO CACHE ===" . PHP_EOL; +// +//Config::cacheSetMax(0); +//Config::cacheSetMaxFiles(0); +//Config::cacheSetMaxMem(0); +// +//$doBenchmark(); diff --git a/src/GObject.php b/src/GObject.php index 45bd964..61759b2 100644 --- a/src/GObject.php +++ b/src/GObject.php @@ -171,7 +171,6 @@ public function signalConnect(string $name, Closure $callback): void $bufferLength = (int) FFI::gobject()->g_value_get_int64(\FFI::addr($params[2])); $buffer = \FFI::string($bufferPointer, $bufferLength); $returnBufferLength = $callback($buffer); - \FFI::memcpy($bufferPointer, $buffer, $returnBufferLength); FFI::gobject()->g_value_set_int64($returnValue, $returnBufferLength); }; $marshalers['finish'] = static function ( @@ -211,10 +210,9 @@ public function signalConnect(string $name, Closure $callback): void throw new Exception("unsupported signal $name"); } - $go = \FFI::cast(FFI::ctypes('GObject'), $this->pointer); $gc = FFI::gobject()->g_closure_new_simple(\FFI::sizeof(FFI::ctypes('GClosure')), null); $gc->marshal = $marshalers[$name]; - FFI::gobject()->g_signal_connect_closure($go, $name, $gc, 0); + FFI::gobject()->g_signal_connect_closure($this->pointer, $name, $gc, 0); } } From 03ab2a6cf01018da59982b695449c1cadf996f18 Mon Sep 17 00:00:00 2001 From: L3tum <9307432+L3tum@users.noreply.github.com> Date: Sat, 4 Mar 2023 15:29:42 +0100 Subject: [PATCH 07/15] fix: Implement better read functionality fix: Use resource as reference for callbacks --- src/FFI.php | 1 - src/GObject.php | 95 ++++++++++++++++++++------------------ src/VipsSourceCustom.php | 13 +----- src/VipsSourceResource.php | 4 +- src/VipsTargetCustom.php | 10 +--- src/VipsTargetResource.php | 8 ++-- 6 files changed, 60 insertions(+), 71 deletions(-) diff --git a/src/FFI.php b/src/FFI.php index 6a76d9f..c701bf9 100644 --- a/src/FFI.php +++ b/src/FFI.php @@ -484,7 +484,6 @@ private static function init(): void */ }; long g_signal_connect_closure(GObject* object, const char* detailed_signal, GClosure *closure, bool after); -GClosure* g_closure_ref(GClosure* closure); GClosure* g_closure_new_simple (int sizeof_closure, void* data); EOS; diff --git a/src/GObject.php b/src/GObject.php index 61759b2..decfdaa 100644 --- a/src/GObject.php +++ b/src/GObject.php @@ -97,93 +97,100 @@ public function unref(): void FFI::gobject()->g_object_unref($this->pointer); } + /** + * @throws Exception + */ public function signalConnect(string $name, Closure $callback): void { $imageProgressCb = static function ( - CData $gClosure, + CData $gClosure, ?CData $returnValue, - int $numberOfParams, - CData $params, - CData $hint, + int $numberOfParams, + CData $params, + CData $hint, ?CData $data - ) use ($callback) { + ) use (&$callback) { assert($numberOfParams === 3); /** - * Marshal-Signature: void(VipsImage*, void*, void*) + * Marshal-Signature: void(VipsImage* image, void* progress, void* handle) */ - $vi = \FFI::cast(FFI::ctypes('GObject'), FFI::gobject()->g_value_get_pointer(\FFI::addr($params[1]))); + $vi = \FFI::cast(FFI::ctypes('GObject'), FFI::gobject()->g_value_get_pointer(\FFI::addr($params[0]))); FFI::gobject()->g_object_ref($vi); $image = new Image($vi); - $pr = \FFI::cast(FFI::ctypes('VipsProgress'), FFI::gobject()->g_value_get_pointer(\FFI::addr($params[2]))); + $pr = \FFI::cast(FFI::ctypes('VipsProgress'), FFI::gobject()->g_value_get_pointer(\FFI::addr($params[1]))); $callback($image, $pr); }; $marshalers = ['preeval' => $imageProgressCb, 'eval' => $imageProgressCb, 'posteval' => $imageProgressCb]; if (FFI::atLeast(8, 9)) { $marshalers['read'] = static function ( - CData $gClosure, - CData $returnValue, - int $numberOfParams, - CData $params, - CData $hint, + CData $gClosure, + CData $returnValue, + int $numberOfParams, + CData $params, + CData $hint, ?CData $data ) use (&$callback): void { assert($numberOfParams === 4); /* - * Marshal-Signature: gint64(VipsSourceCustom*, void*, gint64, void*) + * Marshal-Signature: gint64(VipsSourceCustom* source, void* buffer, gint64 length, void* handle) */ $bufferPointer = FFI::gobject()->g_value_get_pointer(\FFI::addr($params[1])); - $bufferLength = (int) FFI::gobject()->g_value_get_int64(\FFI::addr($params[2])); - $buffer = \FFI::string($bufferPointer, $bufferLength); - $returnBufferLength = $callback($buffer); - \FFI::memcpy($bufferPointer, $buffer, $returnBufferLength); + $bufferLength = (int)FFI::gobject()->g_value_get_int64(\FFI::addr($params[2])); + $returnBuffer = $callback($bufferLength); + $returnBufferLength = 0; + + if ($returnBuffer !== null) { + $returnBufferLength = strlen($returnBuffer); + \FFI::memcpy($bufferPointer, $returnBuffer, $returnBufferLength); + } FFI::gobject()->g_value_set_int64($returnValue, $returnBufferLength); }; $marshalers['seek'] = static function ( - CData $gClosure, - CData $returnValue, - int $numberOfParams, - CData $params, - CData $hint, + CData $gClosure, + CData $returnValue, + int $numberOfParams, + CData $params, + CData $hint, ?CData $data ) use (&$callback): void { assert($numberOfParams === 4); /* - * Marshal-Signature: gint64(VipsSourceCustom*, gint64, int, void*) + * Marshal-Signature: gint64(VipsSourceCustom* source, gint64 offset, int whence, void* handle) */ - $offset = (int) FFI::gobject()->g_value_get_int64(\FFI::addr($params[1])); - $whence = (int) FFI::gobject()->g_value_get_int(\FFI::addr($params[2])); + $offset = (int)FFI::gobject()->g_value_get_int64(\FFI::addr($params[1])); + $whence = (int)FFI::gobject()->g_value_get_int(\FFI::addr($params[2])); FFI::gobject()->g_value_set_int64($returnValue, $callback($offset, $whence)); }; $marshalers['write'] = static function ( - CData $gClosure, - CData $returnValue, - int $numberOfParams, - CData $params, - CData $hint, + CData $gClosure, + CData $returnValue, + int $numberOfParams, + CData $params, + CData $hint, ?CData $data ) use (&$callback): void { assert($numberOfParams === 4); /* - * Marshal-Signature: gint64(VipsTargetCustom*, void*, gint64, void*) + * Marshal-Signature: gint64(VipsTargetCustom* target, void* buffer, gint64 length, void* handle) */ $bufferPointer = FFI::gobject()->g_value_get_pointer(\FFI::addr($params[1])); - $bufferLength = (int) FFI::gobject()->g_value_get_int64(\FFI::addr($params[2])); + $bufferLength = (int)FFI::gobject()->g_value_get_int64(\FFI::addr($params[2])); $buffer = \FFI::string($bufferPointer, $bufferLength); $returnBufferLength = $callback($buffer); FFI::gobject()->g_value_set_int64($returnValue, $returnBufferLength); }; $marshalers['finish'] = static function ( - CData $gClosure, + CData $gClosure, ?CData $returnValue, - int $numberOfParams, - CData $params, - CData $hint, + int $numberOfParams, + CData $params, + CData $hint, ?CData $data ) use (&$callback): void { assert($numberOfParams === 2); /** - * Marshal-Signature: void(VipsTargetCustom*, void*) + * Marshal-Signature: void(VipsTargetCustom* target, void* handle) */ $callback(); }; @@ -191,16 +198,16 @@ public function signalConnect(string $name, Closure $callback): void if (FFI::atLeast(8, 13)) { $marshalers['end'] = static function ( - CData $gClosure, - CData $returnValue, - int $numberOfParams, - CData $params, - CData $hint, + CData $gClosure, + CData $returnValue, + int $numberOfParams, + CData $params, + CData $hint, ?CData $data ) use (&$callback): void { assert($numberOfParams === 2); /** - * Marshal-Signature: int(VipsTargetCustom*, void*) + * Marshal-Signature: int(VipsTargetCustom* target, void* handle) */ FFI::gobject()->g_value_set_int($returnValue, $callback()); }; diff --git a/src/VipsSourceCustom.php b/src/VipsSourceCustom.php index fd4f39c..2c40a46 100644 --- a/src/VipsSourceCustom.php +++ b/src/VipsSourceCustom.php @@ -22,23 +22,14 @@ public function __construct() /** * Attach a read handler. - * The interface is exactly as io.read() in Python. The handler is given a number + * The interface is similar to fread. The handler is given a number * of bytes to fetch, and should return a bytes-like object containing up * to that number of bytes. If there is no more data available, it should * return None. */ public function onRead(Closure $callback): void { - $this->signalConnect('read', static function (string &$buffer) use ($callback): int { - $chunk = $callback(strlen($buffer)); - - if ($chunk === null) { - return 0; - } - - $buffer = substr_replace($buffer, $chunk, 0); - return strlen($chunk); - }); + $this->signalConnect('read', $callback); } /** diff --git a/src/VipsSourceResource.php b/src/VipsSourceResource.php index 6027c01..396051c 100644 --- a/src/VipsSourceResource.php +++ b/src/VipsSourceResource.php @@ -22,12 +22,12 @@ public function __construct($resource) $this->resource = $resource; parent::__construct(); - $this->onRead(static function (int $length) use ($resource): ?string { + $this->onRead(static function (int $length) use (&$resource): ?string { return fread($resource, $length) ?: null; }); if (stream_get_meta_data($resource)['seekable']) { - $this->onSeek(static function (int $offset, int $whence) use ($resource): int { + $this->onSeek(static function (int $offset, int $whence) use (&$resource): int { fseek($resource, $offset, $whence); return ftell($resource); }); diff --git a/src/VipsTargetCustom.php b/src/VipsTargetCustom.php index 5901b72..d6f96b8 100644 --- a/src/VipsTargetCustom.php +++ b/src/VipsTargetCustom.php @@ -28,15 +28,7 @@ public function onWrite(Closure $callback): void public function onRead(Closure $callback): void { if (FFI::atLeast(8, 13)) { - $this->signalConnect('read', static function (string &$buffer) use ($callback): int { - $chunk = $callback(strlen($buffer)); - - if ($chunk === null) { - return 0; - } - $buffer = substr_replace($buffer, $chunk, 0); - return strlen($chunk); - }); + $this->signalConnect('read', $callback); } } diff --git a/src/VipsTargetResource.php b/src/VipsTargetResource.php index 57d3dfa..667f1eb 100644 --- a/src/VipsTargetResource.php +++ b/src/VipsTargetResource.php @@ -20,24 +20,24 @@ public function __construct($resource) $this->resource = $resource; parent::__construct(); - $this->onWrite(static function (string $buffer) use ($resource): int { + $this->onWrite(static function (string $buffer) use (&$resource): int { return fwrite($resource, $buffer) ?: 0; }); - $this->onEnd(static function () use ($resource): void { + $this->onEnd(static function () use (&$resource): void { fclose($resource); }); $meta = stream_get_meta_data($resource); // See: https://www.php.net/manual/en/function.fopen.php if (substr($meta['mode'], -1) === '+') { - $this->onRead(static function (int $length) use ($resource): ?string { + $this->onRead(static function (int $length) use (&$resource): ?string { return fread($resource, $length) ?: null; }); } if ($meta['seekable']) { - $this->onSeek(static function (int $offset, int $whence) use ($resource): int { + $this->onSeek(static function (int $offset, int $whence) use (&$resource): int { fseek($resource, $offset, $whence); return ftell($resource); }); From cdfb09e23634c6036ab1b1d15e607d0276b03b87 Mon Sep 17 00:00:00 2001 From: L3tum <9307432+L3tum@users.noreply.github.com> Date: Mon, 13 Mar 2023 12:27:59 +0100 Subject: [PATCH 08/15] feat: Cleanup GObject.php chore: Add docs --- src/GObject.php | 263 +++++++++++++++++++++---------------- src/VipsSource.php | 13 +- src/VipsSourceCustom.php | 2 +- src/VipsSourceResource.php | 2 +- src/VipsTarget.php | 14 ++ src/VipsTargetCustom.php | 41 ++++++ src/VipsTargetResource.php | 2 +- 7 files changed, 216 insertions(+), 121 deletions(-) diff --git a/src/GObject.php b/src/GObject.php index decfdaa..8a747fc 100644 --- a/src/GObject.php +++ b/src/GObject.php @@ -98,129 +98,166 @@ 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. * @throws Exception */ public function signalConnect(string $name, Closure $callback): void { - $imageProgressCb = static function ( - CData $gClosure, - ?CData $returnValue, - int $numberOfParams, - CData $params, - CData $hint, - ?CData $data - ) use (&$callback) { - assert($numberOfParams === 3); - /** - * Marshal-Signature: void(VipsImage* image, void* progress, void* handle) - */ - $vi = \FFI::cast(FFI::ctypes('GObject'), FFI::gobject()->g_value_get_pointer(\FFI::addr($params[0]))); - FFI::gobject()->g_object_ref($vi); - $image = new Image($vi); - $pr = \FFI::cast(FFI::ctypes('VipsProgress'), FFI::gobject()->g_value_get_pointer(\FFI::addr($params[1]))); - $callback($image, $pr); - }; - $marshalers = ['preeval' => $imageProgressCb, 'eval' => $imageProgressCb, 'posteval' => $imageProgressCb]; - - if (FFI::atLeast(8, 9)) { - $marshalers['read'] = static function ( - CData $gClosure, - CData $returnValue, - int $numberOfParams, - CData $params, - CData $hint, - ?CData $data - ) use (&$callback): void { - assert($numberOfParams === 4); - /* - * Marshal-Signature: gint64(VipsSourceCustom* source, void* buffer, gint64 length, void* handle) - */ - $bufferPointer = FFI::gobject()->g_value_get_pointer(\FFI::addr($params[1])); - $bufferLength = (int)FFI::gobject()->g_value_get_int64(\FFI::addr($params[2])); - $returnBuffer = $callback($bufferLength); - $returnBufferLength = 0; - - if ($returnBuffer !== null) { - $returnBufferLength = strlen($returnBuffer); - \FFI::memcpy($bufferPointer, $returnBuffer, $returnBufferLength); - } - FFI::gobject()->g_value_set_int64($returnValue, $returnBufferLength); - }; - $marshalers['seek'] = static function ( - CData $gClosure, - CData $returnValue, - int $numberOfParams, - CData $params, - CData $hint, - ?CData $data - ) use (&$callback): void { - assert($numberOfParams === 4); - /* - * Marshal-Signature: gint64(VipsSourceCustom* source, gint64 offset, int whence, void* handle) - */ - $offset = (int)FFI::gobject()->g_value_get_int64(\FFI::addr($params[1])); - $whence = (int)FFI::gobject()->g_value_get_int(\FFI::addr($params[2])); - FFI::gobject()->g_value_set_int64($returnValue, $callback($offset, $whence)); - }; - $marshalers['write'] = static function ( - CData $gClosure, - CData $returnValue, - int $numberOfParams, - CData $params, - CData $hint, - ?CData $data - ) use (&$callback): void { - assert($numberOfParams === 4); - /* - * Marshal-Signature: gint64(VipsTargetCustom* target, void* buffer, gint64 length, void* handle) - */ - $bufferPointer = FFI::gobject()->g_value_get_pointer(\FFI::addr($params[1])); - $bufferLength = (int)FFI::gobject()->g_value_get_int64(\FFI::addr($params[2])); - $buffer = \FFI::string($bufferPointer, $bufferLength); - $returnBufferLength = $callback($buffer); - FFI::gobject()->g_value_set_int64($returnValue, $returnBufferLength); - }; - $marshalers['finish'] = static function ( - CData $gClosure, - ?CData $returnValue, - int $numberOfParams, - CData $params, - CData $hint, - ?CData $data - ) use (&$callback): void { - assert($numberOfParams === 2); - /** - * Marshal-Signature: void(VipsTargetCustom* target, void* handle) - */ - $callback(); - }; - } - - if (FFI::atLeast(8, 13)) { - $marshalers['end'] = static function ( - CData $gClosure, - CData $returnValue, - int $numberOfParams, - CData $params, - CData $hint, - ?CData $data - ) use (&$callback): void { - assert($numberOfParams === 2); - /** - * Marshal-Signature: int(VipsTargetCustom* target, void* handle) - */ - FFI::gobject()->g_value_set_int($returnValue, $callback()); - }; - } - - if (!isset($marshalers[$name])) { + $marshaler = self::getMarshaler($name, $callback); + if ($marshaler === null) { throw new Exception("unsupported signal $name"); } $gc = FFI::gobject()->g_closure_new_simple(\FFI::sizeof(FFI::ctypes('GClosure')), null); - $gc->marshal = $marshalers[$name]; + $gc->marshal = $marshaler; FFI::gobject()->g_signal_connect_closure($this->pointer, $name, $gc, 0); } + + private static function getMarshaler(string $name, Closure $callback): ?Closure + { + switch ($name) { + case 'preeval': + case 'eval': + case 'posteval': + return static function ( + CData $gClosure, + ?CData $returnValue, + int $numberOfParams, + CData $params, + CData $hint, + ?CData $data + ) use (&$callback) { + assert($numberOfParams === 3); + /** + * Signature: void(VipsImage* image, void* progress, void* handle) + */ + $vi = \FFI::cast( + FFI::ctypes('GObject'), + FFI::gobject()->g_value_get_pointer(\FFI::addr($params[0])) + ); + FFI::gobject()->g_object_ref($vi); + $image = new Image($vi); + $pr = \FFI::cast( + FFI::ctypes('VipsProgress'), + FFI::gobject()->g_value_get_pointer(\FFI::addr($params[1])) + ); + $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 ( + CData $gClosure, + CData $returnValue, + int $numberOfParams, + CData $params, + CData $hint, + ?CData $data + ) use (&$callback): void { + assert($numberOfParams === 4); + /* + * Signature: gint64(VipsSourceCustom* source, gint64 offset, int whence, void* handle) + */ + $offset = (int)FFI::gobject()->g_value_get_int64(\FFI::addr($params[1])); + $whence = (int)FFI::gobject()->g_value_get_int(\FFI::addr($params[2])); + FFI::gobject()->g_value_set_int64($returnValue, $callback($offset, $whence)); + }; + } + + return null; + case 'write': + 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(VipsTargetCustom* target, void* buffer, gint64 length, void* handle) + */ + $bufferPointer = FFI::gobject()->g_value_get_pointer(\FFI::addr($params[1])); + $bufferLength = (int)FFI::gobject()->g_value_get_int64(\FFI::addr($params[2])); + $buffer = \FFI::string($bufferPointer, $bufferLength); + $returnBufferLength = $callback($buffer); + FFI::gobject()->g_value_set_int64($returnValue, $returnBufferLength); + }; + } + + return null; + case 'finish': + 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 === 2); + /** + * Signature: void(VipsTargetCustom* target, void* handle) + */ + $callback(); + }; + } + + return null; + case 'end': + if (FFI::atLeast(8, 13)) { + return static function ( + CData $gClosure, + CData $returnValue, + int $numberOfParams, + CData $params, + CData $hint, + ?CData $data + ) use (&$callback): void { + assert($numberOfParams === 2); + /** + * Signature: int(VipsTargetCustom* target, void* handle) + */ + FFI::gobject()->g_value_set_int($returnValue, $callback()); + }; + } + + return null; + default: + return null; + } + } } /* diff --git a/src/VipsSource.php b/src/VipsSource.php index ee3ef71..1d15dda 100644 --- a/src/VipsSource.php +++ b/src/VipsSource.php @@ -21,9 +21,9 @@ public function __construct(\FFI\CData $pointer) /** * Make a new source from a file descriptor (a small integer). * Make a new source that is attached to the descriptor. For example: - * source = pyvips.Source.new_from_descriptor(0) + * $source = VipsSource::newFromDescriptor(0) * Makes a descriptor attached to stdin. - * You can pass this source to (for example) :meth:`new_from_source`. + * You can pass this source to (for example) @see Image::newFromSource() * @throws Exception */ public static function newFromDescriptor(int $descriptor): self @@ -40,8 +40,8 @@ public static function newFromDescriptor(int $descriptor): self /** * Make a new source from a filename. * Make a new source that is attached to the named file. For example: - * source = pyvips.Source.new_from_file("myfile.jpg") - * You can pass this source to (for example) :meth:`new_from_source`. + * $source = VipsSource::newFromFile("myfile.jpg") + * You can pass this source to (for example) @see Image::newFromSource() * @throws Exception */ public static function newFromFile(string $filename): self @@ -56,7 +56,10 @@ public static function newFromFile(string $filename): self } /** - * @TODO Not sure how best to implement this since PHP does not have buffers like Python + * Make a new source from a filename. + * Make a new source that uses the provided $data. For example: + * $source = VipsSource::newFromFile(file_get_contents("myfile.jpg")) + * You can pass this source to (for example) @see Image::newFromSource() * @throws Exception */ public static function newFromMemory(string $data): self diff --git a/src/VipsSourceCustom.php b/src/VipsSourceCustom.php index 2c40a46..8c40a5b 100644 --- a/src/VipsSourceCustom.php +++ b/src/VipsSourceCustom.php @@ -25,7 +25,7 @@ public function __construct() * The interface is similar to fread. The handler is given a number * of bytes to fetch, and should return a bytes-like object containing up * to that number of bytes. If there is no more data available, it should - * return None. + * return null. */ public function onRead(Closure $callback): void { diff --git a/src/VipsSourceResource.php b/src/VipsSourceResource.php index 396051c..a521fbd 100644 --- a/src/VipsSourceResource.php +++ b/src/VipsSourceResource.php @@ -37,6 +37,6 @@ public function __construct($resource) public function __destruct() { fclose($this->resource); - parent::__destruct(); // TODO: Change the autogenerated stub + parent::__destruct(); } } diff --git a/src/VipsTarget.php b/src/VipsTarget.php index a67e663..3c7f1c4 100644 --- a/src/VipsTarget.php +++ b/src/VipsTarget.php @@ -19,6 +19,12 @@ public function __construct(\FFI\CData $pointer) } /** + * Make a new target to write to a file descriptor (a small integer). + * Make a new target that is attached to the descriptor. For example:: + * $target = VipsTarget.newToDescriptor(1) + * Makes a descriptor attached to stdout. + * You can pass this target to (for example) @see Image::writeToTarget() + * @throws Exception */ public static function newToDescriptor(int $descriptor): self @@ -32,6 +38,10 @@ public static function newToDescriptor(int $descriptor): self } /** + * Make a new target to write to a file name. + * Make a new target that is attached to the file name. For example:: + * $target = VipsTarget.newToFile("myfile.jpg") + * You can pass this target to (for example) @see Image::writeToTarget() * @throws Exception */ public static function newToFile(string $filename): self @@ -46,6 +56,10 @@ public static function newToFile(string $filename): self } /** + * Make a new target to write to a memory buffer. + * For example:: + * $target = VipsTarget.newToMemory() + * You can pass this target to (for example) @see Image::writeToTarget() * @throws Exception */ public static function newToMemory(): self diff --git a/src/VipsTargetCustom.php b/src/VipsTargetCustom.php index d6f96b8..112231b 100644 --- a/src/VipsTargetCustom.php +++ b/src/VipsTargetCustom.php @@ -20,11 +20,27 @@ public function __construct() parent::__construct($this->pointer); } + /** + * Attach a write handler. + * The interface is exactly as fwrite. The handler is given a bytes-like object to write, + * and should return the number of bytes written. + * @throws Exception + */ public function onWrite(Closure $callback): void { $this->signalConnect('write', $callback); } + /** + * Attach a read handler. + * The interface is similar to fread. The handler is given a number + * of bytes to fetch, and should return a bytes-like object containing up + * to that number of bytes. If there is no more data available, it should + * return null. + * Read handlers on VipsTarget are optional. If you do not set one, your + * target will be treated as unreadable and libvips will be unable to + * write some file types (just TIFF, as of the time of writing). + */ public function onRead(Closure $callback): void { if (FFI::atLeast(8, 13)) { @@ -32,6 +48,19 @@ public function onRead(Closure $callback): void } } + /** + * Attach a seek handler. + * The interface is the same as fseek, so the handler is passed + * parameters for $offset and $whence with the same meanings. + * However, the handler MUST return the new seek position. A simple way + * to do this is to call ftell() and return that result. + * Seek handlers are optional. If you do not set one, your source will be + * treated as unseekable and libvips will do extra caching. + * $whence in particular: + * 0 => start + * 1 => current position + * 2 => end + */ public function onSeek(Closure $callback): void { if (FFI::atLeast(8, 13)) { @@ -39,6 +68,13 @@ public function onSeek(Closure $callback): void } } + /** + * Attach an end handler. + * This optional handler is called at the end of write. It should do any + * cleaning up necessary, and return 0 on success and -1 on error. + * Automatically falls back to onFinish if libvips <8.13 + * @throws Exception + */ public function onEnd(Closure $callback): void { if (FFI::atLeast(8, 13)) { @@ -48,6 +84,11 @@ public function onEnd(Closure $callback): void } } + /** + * Attach a finish handler. + * For libvips 8.13 and later, this method is deprecated in favour of @see VipsTargetCustom::onEnd() + * @throws Exception + */ public function onFinish(Closure $callback): void { $this->signalConnect('finish', $callback); diff --git a/src/VipsTargetResource.php b/src/VipsTargetResource.php index 667f1eb..ffcaa04 100644 --- a/src/VipsTargetResource.php +++ b/src/VipsTargetResource.php @@ -49,6 +49,6 @@ public function __destruct() if (is_resource($this->resource)) { fclose($this->resource); } - parent::__destruct(); // TODO: Change the autogenerated stub + parent::__destruct(); } } From 83fe8e0dfd0d95ae5303e52d45b241ef938b2e4d Mon Sep 17 00:00:00 2001 From: L3tum <9307432+L3tum@users.noreply.github.com> Date: Wed, 31 May 2023 09:11:25 +0200 Subject: [PATCH 09/15] fix: Do not use Vips Prefix --- examples/generate_phpdoc.py | 8 +-- examples/streaming-bench.php | 24 ++++----- examples/streaming.php | 6 +-- src/Image.php | 8 +-- src/ImageAutodoc.php | 54 +++++++++---------- src/{VipsSource.php => Source.php} | 2 +- ...{VipsSourceCustom.php => SourceCustom.php} | 2 +- ...sSourceResource.php => SourceResource.php} | 2 +- src/{VipsTarget.php => Target.php} | 2 +- ...{VipsTargetCustom.php => TargetCustom.php} | 6 +-- ...sTargetResource.php => TargetResource.php} | 2 +- tests/StreamingTest.php | 32 +++++------ 12 files changed, 74 insertions(+), 74 deletions(-) rename src/{VipsSource.php => Source.php} (98%) rename src/{VipsSourceCustom.php => SourceCustom.php} (97%) rename src/{VipsSourceResource.php => SourceResource.php} (94%) rename src/{VipsTarget.php => Target.php} (98%) rename src/{VipsTargetCustom.php => TargetCustom.php} (96%) rename src/{VipsTargetResource.php => TargetResource.php} (96%) diff --git a/examples/generate_phpdoc.py b/examples/generate_phpdoc.py index 006a34e..632db36 100755 --- a/examples/generate_phpdoc.py +++ b/examples/generate_phpdoc.py @@ -32,8 +32,8 @@ GValue.array_double_type: 'float[]|float', GValue.array_image_type: 'Image[]|Image', GValue.blob_type: 'string', - GValue.source_type: 'VipsSource', - GValue.target_type: 'VipsTarget' + GValue.source_type: 'Source', + GValue.target_type: 'Target' } # php result type names are different, annoyingly, and very restricted @@ -51,8 +51,8 @@ GValue.array_double_type: 'array', GValue.array_image_type: 'array', GValue.blob_type: 'string', - GValue.source_type: 'VipsSource', - GValue.target_type: 'VipsTarget' + GValue.source_type: 'Source', + GValue.target_type: 'Target' } # values for VipsArgumentFlags diff --git a/examples/streaming-bench.php b/examples/streaming-bench.php index 26c414c..1fcf04c 100644 --- a/examples/streaming-bench.php +++ b/examples/streaming-bench.php @@ -3,10 +3,10 @@ use Jcupitt\Vips\Config; use Jcupitt\Vips\Image; -use Jcupitt\Vips\VipsSource; -use Jcupitt\Vips\VipsSourceResource; -use Jcupitt\Vips\VipsTarget; -use Jcupitt\Vips\VipsTargetResource; +use Jcupitt\Vips\Source; +use Jcupitt\Vips\SourceResource; +use Jcupitt\Vips\Target; +use Jcupitt\Vips\TargetResource; require dirname(__DIR__) . '/vendor/autoload.php'; @@ -24,8 +24,8 @@ $start = microtime(true); for ($i = 0; $i < $iterations; $i++) { - $source = new VipsSourceResource(fopen($sourceFile, 'rb')); - $target = new VipsTargetResource(fopen($targetFile, 'wb+')); + $source = new SourceResource(fopen($sourceFile, 'rb')); + $target = new TargetResource(fopen($targetFile, 'wb+')); $image = Image::newFromSource($source, '', $sourceOptions); $image = $image->resize($targetWidth / $image->width); $image->writeToTarget( @@ -42,8 +42,8 @@ $start = microtime(true); for ($i = 0; $i < $iterations; $i++) { - $source = VipsSource::newFromFile($sourceFile); - $target = VipsTarget::newToFile($targetFile); + $source = Source::newFromFile($sourceFile); + $target = Target::newToFile($targetFile); $image = Image::newFromSource($source, '', $sourceOptions); $image = $image->resize($targetWidth / $image->width); $image->writeToTarget( @@ -60,8 +60,8 @@ $start = microtime(true); for ($i = 0; $i < $iterations; $i++) { - $source = new VipsSourceResource(fopen($sourceFile, 'rb')); - $target = new VipsTargetResource(fopen($targetFile, 'wb+')); + $source = new SourceResource(fopen($sourceFile, 'rb')); + $target = new TargetResource(fopen($targetFile, 'wb+')); $image = Image::thumbnail_source($source, $targetWidth); $image->writeToTarget( $target, @@ -77,8 +77,8 @@ $start = microtime(true); for ($i = 0; $i < $iterations; $i++) { - $source = VipsSource::newFromFile($sourceFile); - $target = VipsTarget::newToFile($targetFile); + $source = Source::newFromFile($sourceFile); + $target = Target::newToFile($targetFile); $image = Image::thumbnail_source($source, $targetWidth); $image->writeToTarget( $target, diff --git a/examples/streaming.php b/examples/streaming.php index 585185d..f5628e2 100644 --- a/examples/streaming.php +++ b/examples/streaming.php @@ -4,9 +4,9 @@ require dirname(__DIR__) . '/vendor/autoload.php'; use Jcupitt\Vips; -use Jcupitt\Vips\VipsSource; +use Jcupitt\Vips\Source; -$source = VipsSource::newFromFile(dirname(__DIR__) . '/tests/images/img_0076.jpg'); -$target = Vips\VipsTarget::newToFile(dirname(__DIR__) . "/tests/images/target.jpg"); +$source = Source::newFromFile(dirname(__DIR__) . '/tests/images/img_0076.jpg'); +$target = Vips\Target::newToFile(dirname(__DIR__) . "/tests/images/target.jpg"); $image = Vips\Image::newFromSource($source); $image->writeToTarget($target, '.jpg[Q=95]'); diff --git a/src/Image.php b/src/Image.php index a7b53e1..64d9902 100644 --- a/src/Image.php +++ b/src/Image.php @@ -906,10 +906,10 @@ public function newFromImage($value): Image * example 'VipsForeignLoadJpegSource'. You can use this to work out what * options to pass to newFromSource(). * - * @param VipsSource $source The source to test + * @param Source $source The source to test * @return string|null The name of the load operation, or null. */ - public static function findLoadSource(VipsSource $source): ?string + public static function findLoadSource(Source $source): ?string { return FFI::vips()->vips_foreign_find_load_source(\FFI::cast(FFI::ctypes('VipsSource'), $source->pointer)); } @@ -917,7 +917,7 @@ public static function findLoadSource(VipsSource $source): ?string /** * @throws Exception */ - public static function newFromSource(VipsSource $source, string $string_options = '', array $options = []): self + public static function newFromSource(Source $source, string $string_options = '', array $options = []): self { $loader = self::findLoadSource($source); if ($loader === null) { @@ -1075,7 +1075,7 @@ public function writeToArray(): array /** * @throws Exception */ - public function writeToTarget(VipsTarget $target, string $suffix, array $options = []): void + public function writeToTarget(Target $target, string $suffix, array $options = []): void { $filename = Utils::filenameGetFilename($suffix); $string_options = Utils::filenameGetOptions($suffix); diff --git a/src/ImageAutodoc.php b/src/ImageAutodoc.php index 5162dae..36f017b 100644 --- a/src/ImageAutodoc.php +++ b/src/ImageAutodoc.php @@ -171,11 +171,11 @@ * @throws Exception * @method static Image csvload(string $filename, array $options = []) Load csv. * @throws Exception - * @method static Image csvload_source(VipsSource $source, array $options = []) Load csv. + * @method static Image csvload_source(Source $source, array $options = []) Load csv. * @throws Exception * @method void csvsave(string $filename, array $options = []) Save image to csv. * @throws Exception - * @method void csvsave_target(VipsTarget $target, array $options = []) Save image to csv. + * @method void csvsave_target(Target $target, array $options = []) Save image to csv. * @throws Exception * @method Image dE00(Image $right, array $options = []) Calculate dE00. * @throws Exception @@ -227,7 +227,7 @@ * @throws Exception * @method static Image fitsload(string $filename, array $options = []) Load a FITS image. * @throws Exception - * @method static Image fitsload_source(VipsSource $source, array $options = []) Load FITS from a source. + * @method static Image fitsload_source(Source $source, array $options = []) Load FITS from a source. * @throws Exception * @method void fitssave(string $filename, array $options = []) Save image to fits file. * @throws Exception @@ -258,7 +258,7 @@ * @throws Exception * @method static Image gifload_buffer(string $buffer, array $options = []) Load GIF with libnsgif. * @throws Exception - * @method static Image gifload_source(VipsSource $source, array $options = []) Load gif from source. + * @method static Image gifload_source(Source $source, array $options = []) Load gif from source. * @throws Exception * @method Image globalbalance(array $options = []) Global balance an image mosaic. * @throws Exception @@ -273,13 +273,13 @@ * @throws Exception * @method static Image heifload_buffer(string $buffer, array $options = []) Load a HEIF image. * @throws Exception - * @method static Image heifload_source(VipsSource $source, array $options = []) Load a HEIF image. + * @method static Image heifload_source(Source $source, array $options = []) Load a HEIF image. * @throws Exception * @method void heifsave(string $filename, array $options = []) Save image in HEIF format. * @throws Exception * @method string heifsave_buffer(array $options = []) Save image in HEIF format. * @throws Exception - * @method void heifsave_target(VipsTarget $target, array $options = []) Save image in HEIF format. + * @method void heifsave_target(Target $target, array $options = []) Save image in HEIF format. * @throws Exception * @method Image hist_cum(array $options = []) Form cumulative histogram. * @throws Exception @@ -330,7 +330,7 @@ * @throws Exception * @method static Image jpegload_buffer(string $buffer, array $options = []) Load jpeg from buffer. * @throws Exception - * @method static Image jpegload_source(VipsSource $source, array $options = []) Load image from jpeg source. + * @method static Image jpegload_source(Source $source, array $options = []) Load image from jpeg source. * @throws Exception * @method void jpegsave(string $filename, array $options = []) Save image to jpeg file. * @throws Exception @@ -338,19 +338,19 @@ * @throws Exception * @method void jpegsave_mime(array $options = []) Save image to jpeg mime. * @throws Exception - * @method void jpegsave_target(VipsTarget $target, array $options = []) Save image to jpeg target. + * @method void jpegsave_target(Target $target, array $options = []) Save image to jpeg target. * @throws Exception * @method static Image jxlload(string $filename, array $options = []) Load JPEG-XL image. * @throws Exception * @method static Image jxlload_buffer(string $buffer, array $options = []) Load JPEG-XL image. * @throws Exception - * @method static Image jxlload_source(VipsSource $source, array $options = []) Load JPEG-XL image. + * @method static Image jxlload_source(Source $source, array $options = []) Load JPEG-XL image. * @throws Exception * @method void jxlsave(string $filename, array $options = []) Save image in JPEG-XL format. * @throws Exception * @method string jxlsave_buffer(array $options = []) Save image in JPEG-XL format. * @throws Exception - * @method void jxlsave_target(VipsTarget $target, array $options = []) Save image in JPEG-XL format. + * @method void jxlsave_target(Target $target, array $options = []) Save image in JPEG-XL format. * @throws Exception * @method Image labelregions(array $options = []) Label regions in an image. * @throws Exception @@ -409,13 +409,13 @@ * @throws Exception * @method static Image matrixload(string $filename, array $options = []) Load matrix. * @throws Exception - * @method static Image matrixload_source(VipsSource $source, array $options = []) Load matrix. + * @method static Image matrixload_source(Source $source, array $options = []) Load matrix. * @throws Exception * @method void matrixprint(array $options = []) Print matrix. * @throws Exception * @method void matrixsave(string $filename, array $options = []) Save image to matrix. * @throws Exception - * @method void matrixsave_target(VipsTarget $target, array $options = []) Save image to matrix. + * @method void matrixsave_target(Target $target, array $options = []) Save image to matrix. * @throws Exception * @method float max(array $options = []) Find image maximum. * @throws Exception @@ -441,13 +441,13 @@ * @throws Exception * @method static Image openslideload(string $filename, array $options = []) Load file with OpenSlide. * @throws Exception - * @method static Image openslideload_source(VipsSource $source, array $options = []) Load source with OpenSlide. + * @method static Image openslideload_source(Source $source, array $options = []) Load source with OpenSlide. * @throws Exception * @method static Image pdfload(string $filename, array $options = []) Load PDF from file. * @throws Exception * @method static Image pdfload_buffer(string $buffer, array $options = []) Load PDF from buffer. * @throws Exception - * @method static Image pdfload_source(VipsSource $source, array $options = []) Load PDF from source. + * @method static Image pdfload_source(Source $source, array $options = []) Load PDF from source. * @throws Exception * @method integer percent(float $percent, array $options = []) Find threshold for percent of pixels. * @throws Exception @@ -459,21 +459,21 @@ * @throws Exception * @method static Image pngload_buffer(string $buffer, array $options = []) Load png from buffer. * @throws Exception - * @method static Image pngload_source(VipsSource $source, array $options = []) Load png from source. + * @method static Image pngload_source(Source $source, array $options = []) Load png from source. * @throws Exception * @method void pngsave(string $filename, array $options = []) Save image to png file. * @throws Exception * @method string pngsave_buffer(array $options = []) Save image to png buffer. * @throws Exception - * @method void pngsave_target(VipsTarget $target, array $options = []) Save image to target as PNG. + * @method void pngsave_target(Target $target, array $options = []) Save image to target as PNG. * @throws Exception * @method static Image ppmload(string $filename, array $options = []) Load ppm from file. * @throws Exception - * @method static Image ppmload_source(VipsSource $source, array $options = []) Load ppm base class. + * @method static Image ppmload_source(Source $source, array $options = []) Load ppm base class. * @throws Exception * @method void ppmsave(string $filename, array $options = []) Save image to ppm file. * @throws Exception - * @method void ppmsave_target(VipsTarget $target, array $options = []) Save to ppm. + * @method void ppmsave_target(Target $target, array $options = []) Save to ppm. * @throws Exception * @method Image premultiply(array $options = []) Premultiply image alpha. * @throws Exception @@ -499,13 +499,13 @@ * @throws Exception * @method static Image radload_buffer(string $buffer, array $options = []) Load rad from buffer. * @throws Exception - * @method static Image radload_source(VipsSource $source, array $options = []) Load rad from source. + * @method static Image radload_source(Source $source, array $options = []) Load rad from source. * @throws Exception * @method void radsave(string $filename, array $options = []) Save image to Radiance file. * @throws Exception * @method string radsave_buffer(array $options = []) Save image to Radiance buffer. * @throws Exception - * @method void radsave_target(VipsTarget $target, array $options = []) Save image to Radiance target. + * @method void radsave_target(Target $target, array $options = []) Save image to Radiance target. * @throws Exception * @method Image rank(integer $width, integer $height, integer $index, array $options = []) Rank filter. * @throws Exception @@ -593,7 +593,7 @@ * @throws Exception * @method static Image svgload_buffer(string $buffer, array $options = []) Load SVG with rsvg. * @throws Exception - * @method static Image svgload_source(VipsSource $source, array $options = []) Load svg from source. + * @method static Image svgload_source(Source $source, array $options = []) Load svg from source. * @throws Exception * @method static Image switch(Image[]|Image $tests, array $options = []) Find the index of the first non-zero pixel in tests. * @throws Exception @@ -607,13 +607,13 @@ * @throws Exception * @method Image thumbnail_image(integer $width, array $options = []) Generate thumbnail from image. * @throws Exception - * @method static Image thumbnail_source(VipsSource $source, integer $width, array $options = []) Generate thumbnail from source. + * @method static Image thumbnail_source(Source $source, integer $width, array $options = []) Generate thumbnail from source. * @throws Exception * @method static Image tiffload(string $filename, array $options = []) Load tiff from file. * @throws Exception * @method static Image tiffload_buffer(string $buffer, array $options = []) Load tiff from buffer. * @throws Exception - * @method static Image tiffload_source(VipsSource $source, array $options = []) Load tiff from source. + * @method static Image tiffload_source(Source $source, array $options = []) Load tiff from source. * @throws Exception * @method void tiffsave(string $filename, array $options = []) Save image to tiff file. * @throws Exception @@ -629,23 +629,23 @@ * @throws Exception * @method static Image vipsload(string $filename, array $options = []) Load vips from file. * @throws Exception - * @method static Image vipsload_source(VipsSource $source, array $options = []) Load vips from source. + * @method static Image vipsload_source(Source $source, array $options = []) Load vips from source. * @throws Exception * @method void vipssave(string $filename, array $options = []) Save image to file in vips format. * @throws Exception - * @method void vipssave_target(VipsTarget $target, array $options = []) Save image to target in vips format. + * @method void vipssave_target(Target $target, array $options = []) Save image to target in vips format. * @throws Exception * @method static Image webpload(string $filename, array $options = []) Load webp from file. * @throws Exception * @method static Image webpload_buffer(string $buffer, array $options = []) Load webp from buffer. * @throws Exception - * @method static Image webpload_source(VipsSource $source, array $options = []) Load webp from source. + * @method static Image webpload_source(Source $source, array $options = []) Load webp from source. * @throws Exception * @method void webpsave(string $filename, array $options = []) Save image to webp file. * @throws Exception * @method string webpsave_buffer(array $options = []) Save image to webp buffer. * @throws Exception - * @method void webpsave_target(VipsTarget $target, array $options = []) Save image to webp target. + * @method void webpsave_target(Target $target, array $options = []) Save image to webp target. * @throws Exception * @method static Image worley(integer $width, integer $height, array $options = []) Make a worley noise image. * @throws Exception diff --git a/src/VipsSource.php b/src/Source.php similarity index 98% rename from src/VipsSource.php rename to src/Source.php index 1d15dda..60975fe 100644 --- a/src/VipsSource.php +++ b/src/Source.php @@ -2,7 +2,7 @@ namespace Jcupitt\Vips; -class VipsSource extends Connection +class Source extends Connection { /** * A pointer to the underlying VipsSource. This is the same as the diff --git a/src/VipsSourceCustom.php b/src/SourceCustom.php similarity index 97% rename from src/VipsSourceCustom.php rename to src/SourceCustom.php index 8c40a5b..80d67b8 100644 --- a/src/VipsSourceCustom.php +++ b/src/SourceCustom.php @@ -4,7 +4,7 @@ use Closure; -class VipsSourceCustom extends VipsSource +class SourceCustom extends Source { /** * A pointer to the underlying VipsSourceCustom. This is the same as the diff --git a/src/VipsSourceResource.php b/src/SourceResource.php similarity index 94% rename from src/VipsSourceResource.php rename to src/SourceResource.php index a521fbd..342e6ae 100644 --- a/src/VipsSourceResource.php +++ b/src/SourceResource.php @@ -4,7 +4,7 @@ use Closure; -class VipsSourceResource extends VipsSourceCustom +class SourceResource extends SourceCustom { /** * @var resource diff --git a/src/VipsTarget.php b/src/Target.php similarity index 98% rename from src/VipsTarget.php rename to src/Target.php index 3c7f1c4..568de55 100644 --- a/src/VipsTarget.php +++ b/src/Target.php @@ -2,7 +2,7 @@ namespace Jcupitt\Vips; -class VipsTarget extends Connection +class Target extends Connection { /** * A pointer to the underlying VipsTarget. This is the same as the diff --git a/src/VipsTargetCustom.php b/src/TargetCustom.php similarity index 96% rename from src/VipsTargetCustom.php rename to src/TargetCustom.php index 112231b..595bf9b 100644 --- a/src/VipsTargetCustom.php +++ b/src/TargetCustom.php @@ -4,7 +4,7 @@ use Closure; -class VipsTargetCustom extends VipsTarget +class TargetCustom extends Target { /** * A pointer to the underlying VipsTargetCustom. This is the same as the @@ -86,8 +86,8 @@ public function onEnd(Closure $callback): void /** * Attach a finish handler. - * For libvips 8.13 and later, this method is deprecated in favour of @see VipsTargetCustom::onEnd() - * @throws Exception + * For libvips 8.13 and later, this method is deprecated in favour of @throws Exception + * @see TargetCustom::onEnd() */ public function onFinish(Closure $callback): void { diff --git a/src/VipsTargetResource.php b/src/TargetResource.php similarity index 96% rename from src/VipsTargetResource.php rename to src/TargetResource.php index ffcaa04..b8e90a6 100644 --- a/src/VipsTargetResource.php +++ b/src/TargetResource.php @@ -2,7 +2,7 @@ namespace Jcupitt\Vips; -class VipsTargetResource extends VipsTargetCustom +class TargetResource extends TargetCustom { /** * @var resource diff --git a/tests/StreamingTest.php b/tests/StreamingTest.php index 694ccd2..9d07184 100644 --- a/tests/StreamingTest.php +++ b/tests/StreamingTest.php @@ -5,10 +5,10 @@ use Generator; use Jcupitt\Vips\Exception; use Jcupitt\Vips\Image; -use Jcupitt\Vips\VipsSource; -use Jcupitt\Vips\VipsSourceResource; -use Jcupitt\Vips\VipsTarget; -use Jcupitt\Vips\VipsTargetResource; +use Jcupitt\Vips\Source; +use Jcupitt\Vips\SourceResource; +use Jcupitt\Vips\Target; +use Jcupitt\Vips\TargetResource; use PHPUnit\Framework\TestCase; class StreamingTest extends TestCase @@ -19,15 +19,15 @@ class StreamingTest extends TestCase public function sourceAndTargetProvider(): Generator { $sources = [ - 'File' => fn() => VipsSource::newFromFile(__DIR__ . '/images/img_0076.jpg'), - 'Memory' => fn() => VipsSource::newFromMemory(file_get_contents(__DIR__ . '/images/img_0076.jpg')), - 'Resource' => fn() => new VipsSourceResource(fopen(__DIR__ . '/images/img_0076.jpg', 'rb')) + 'File' => fn() => Source::newFromFile(__DIR__ . '/images/img_0076.jpg'), + 'Memory' => fn() => Source::newFromMemory(file_get_contents(__DIR__ . '/images/img_0076.jpg')), + 'Resource' => fn() => new SourceResource(fopen(__DIR__ . '/images/img_0076.jpg', 'rb')) ]; $targets = [ - 'File' => fn() => VipsTarget::newToFile(tempnam(sys_get_temp_dir(), 'image')), - 'Memory' => fn() => VipsTarget::newToMemory(), - 'Resource' => fn() => new VipsTargetResource(fopen('php://memory', 'wb+')), - 'Resource(Not Readable)' => fn() => new VipsTargetResource(fopen('php://memory', 'wb')) + 'File' => fn() => Target::newToFile(tempnam(sys_get_temp_dir(), 'image')), + 'Memory' => fn() => Target::newToMemory(), + 'Resource' => fn() => new TargetResource(fopen('php://memory', 'wb+')), + 'Resource(Not Readable)' => fn() => new TargetResource(fopen('php://memory', 'wb')) ]; foreach ($sources as $sourceName => $source) { @@ -40,7 +40,7 @@ public function sourceAndTargetProvider(): Generator /** * @dataProvider sourceAndTargetProvider */ - public function testFromSourceToTarget(VipsSource $source, VipsTarget $target): void + public function testFromSourceToTarget(Source $source, Target $target): void { $image = Image::newFromSource($source); $image->writeToTarget($target, '.jpg[Q=95]'); @@ -56,8 +56,8 @@ public function testFromSourceToTarget(VipsSource $source, VipsTarget $target): */ public function testFromFileToFile(): void { - $source = VipsSource::newFromFile(__DIR__ . '/images/img_0076.jpg'); - $target = VipsTarget::newToFile(tempnam(sys_get_temp_dir(), 'image')); + $source = Source::newFromFile(__DIR__ . '/images/img_0076.jpg'); + $target = Target::newToFile(tempnam(sys_get_temp_dir(), 'image')); $image = Image::newFromSource($source); $image->writeToTarget($target, '.jpg[Q=95]'); @@ -73,8 +73,8 @@ public function testNoLeak(): void $leaked = false; for ($i = 0; $i < 10; $i++) { $filename = tempnam(sys_get_temp_dir(), 'image'); - $source = new VipsSourceResource(fopen(__DIR__ . '/images/img_0076.jpg', 'rb')); - $target = new VipsTargetResource(fopen($filename, 'wb+')); + $source = new SourceResource(fopen(__DIR__ . '/images/img_0076.jpg', 'rb')); + $target = new TargetResource(fopen($filename, 'wb+')); $image = Image::newFromSource($source); $image->writeToTarget($target, '.jpg[Q=95]'); unlink($filename); From 965a4bdba14998b990a8f2b36a899b1d315e1e0c Mon Sep 17 00:00:00 2001 From: L3tum <9307432+L3tum@users.noreply.github.com> Date: Wed, 31 May 2023 09:13:38 +0200 Subject: [PATCH 10/15] fix: Indentation --- src/FFI.php | 66 ++++++++++++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/FFI.php b/src/FFI.php index c701bf9..e684aec 100644 --- a/src/FFI.php +++ b/src/FFI.php @@ -446,42 +446,42 @@ private static function init(): void typedef struct _GClosureNotifyData GClosureNotifyData; struct _GClosureNotifyData { - void* data; - GClosureNotify notify; + void* data; + GClosureNotify notify; }; struct _GClosure { - /*< private >*/ - int ref_count : 15; /* (atomic) */ - /* meta_marshal is not used anymore but must be zero for historical reasons - as it was exposed in the G_CLOSURE_N_NOTIFIERS macro */ - int meta_marshal_nouse : 1; /* (atomic) */ - int n_guards : 1; /* (atomic) */ - int n_fnotifiers : 2; /* finalization notifiers (atomic) */ - int n_inotifiers : 8; /* invalidation notifiers (atomic) */ - int in_inotify : 1; /* (atomic) */ - int floating : 1; /* (atomic) */ - /*< protected >*/ - int derivative_flag : 1; /* (atomic) */ - /*< public >*/ - int in_marshal : 1; /* (atomic) */ - int is_invalid : 1; /* (atomic) */ - - /*< private >*/ marshaler marshal; - /*< protected >*/ void* data; - - /*< private >*/ GClosureNotifyData *notifiers; - - /* invariants/constraints: - * - ->marshal and ->data are _invalid_ as soon as ->is_invalid==TRUE - * - invocation of all inotifiers occurs prior to fnotifiers - * - order of inotifiers is random - * inotifiers may _not_ free/invalidate parameter values (e.g. ->data) - * - order of fnotifiers is random - * - each notifier may only be removed before or during its invocation - * - reference counting may only happen prior to fnotify invocation - * (in that sense, fnotifiers are really finalization handlers) - */ + /*< private >*/ + int ref_count : 15; /* (atomic) */ + /* meta_marshal is not used anymore but must be zero for historical reasons + as it was exposed in the G_CLOSURE_N_NOTIFIERS macro */ + int meta_marshal_nouse : 1; /* (atomic) */ + int n_guards : 1; /* (atomic) */ + int n_fnotifiers : 2; /* finalization notifiers (atomic) */ + int n_inotifiers : 8; /* invalidation notifiers (atomic) */ + int in_inotify : 1; /* (atomic) */ + int floating : 1; /* (atomic) */ + /*< protected >*/ + int derivative_flag : 1; /* (atomic) */ + /*< public >*/ + int in_marshal : 1; /* (atomic) */ + int is_invalid : 1; /* (atomic) */ + + /*< private >*/ marshaler marshal; + /*< protected >*/ void* data; + + /*< private >*/ GClosureNotifyData *notifiers; + + /* invariants/constraints: + * - ->marshal and ->data are _invalid_ as soon as ->is_invalid==TRUE + * - invocation of all inotifiers occurs prior to fnotifiers + * - order of inotifiers is random + * inotifiers may _not_ free/invalidate parameter values (e.g. ->data) + * - order of fnotifiers is random + * - each notifier may only be removed before or during its invocation + * - reference counting may only happen prior to fnotify invocation + * (in that sense, fnotifiers are really finalization handlers) + */ }; long g_signal_connect_closure(GObject* object, const char* detailed_signal, GClosure *closure, bool after); GClosure* g_closure_new_simple (int sizeof_closure, void* data); From eb3d0b237419d4200414a14199719edce8e64de7 Mon Sep 17 00:00:00 2001 From: L3tum <9307432+L3tum@users.noreply.github.com> Date: Wed, 31 May 2023 09:15:22 +0200 Subject: [PATCH 11/15] fix: Use g_value_get_object instead of cast --- src/GObject.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/GObject.php b/src/GObject.php index 8a747fc..7acb5f4 100644 --- a/src/GObject.php +++ b/src/GObject.php @@ -132,10 +132,7 @@ private static function getMarshaler(string $name, Closure $callback): ?Closure /** * Signature: void(VipsImage* image, void* progress, void* handle) */ - $vi = \FFI::cast( - FFI::ctypes('GObject'), - FFI::gobject()->g_value_get_pointer(\FFI::addr($params[0])) - ); + $vi = FFI::gobject()->g_value_get_object(\FFI::addr($params[0])); FFI::gobject()->g_object_ref($vi); $image = new Image($vi); $pr = \FFI::cast( From 238ea36eda2ec1902def4ed246a86c250ae1ee0b Mon Sep 17 00:00:00 2001 From: L3tum <9307432+L3tum@users.noreply.github.com> Date: Wed, 31 May 2023 09:16:49 +0200 Subject: [PATCH 12/15] fix: Better error description --- src/Image.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Image.php b/src/Image.php index 64d9902..5701de2 100644 --- a/src/Image.php +++ b/src/Image.php @@ -1082,7 +1082,7 @@ public function writeToTarget(Target $target, string $suffix, array $options = [ $saver = FFI::vips()->vips_foreign_find_save_target($filename); if ($saver === '') { - throw new Exception("can't save to target with filename $filename"); + throw new Exception("can't save to target with given suffix $filename"); } if ($string_options !== '') { From e5e4e3072eeb695238d3699283a24c65cec81bcb Mon Sep 17 00:00:00 2001 From: L3tum <9307432+L3tum@users.noreply.github.com> Date: Wed, 31 May 2023 09:18:01 +0200 Subject: [PATCH 13/15] fix: Comment Indentation --- src/Target.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Target.php b/src/Target.php index 568de55..9b31e61 100644 --- a/src/Target.php +++ b/src/Target.php @@ -24,7 +24,6 @@ public function __construct(\FFI\CData $pointer) * $target = VipsTarget.newToDescriptor(1) * Makes a descriptor attached to stdout. * You can pass this target to (for example) @see Image::writeToTarget() - * @throws Exception */ public static function newToDescriptor(int $descriptor): self From fdcbf623d0840a5e9547c5620ae97394cb506691 Mon Sep 17 00:00:00 2001 From: L3tum <9307432+L3tum@users.noreply.github.com> Date: Wed, 31 May 2023 09:23:55 +0200 Subject: [PATCH 14/15] fix: Replace Closure with callable --- src/GObject.php | 4 ++-- src/SourceCustom.php | 6 ++---- src/SourceResource.php | 2 -- src/TargetCustom.php | 12 +++++------- 4 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/GObject.php b/src/GObject.php index 7acb5f4..88998d4 100644 --- a/src/GObject.php +++ b/src/GObject.php @@ -102,7 +102,7 @@ public function unref(): void * The callback will be triggered every time this signal is issued on this instance. * @throws Exception */ - public function signalConnect(string $name, Closure $callback): void + public function signalConnect(string $name, callable $callback): void { $marshaler = self::getMarshaler($name, $callback); if ($marshaler === null) { @@ -114,7 +114,7 @@ public function signalConnect(string $name, Closure $callback): void FFI::gobject()->g_signal_connect_closure($this->pointer, $name, $gc, 0); } - private static function getMarshaler(string $name, Closure $callback): ?Closure + private static function getMarshaler(string $name, callable $callback): ?Closure { switch ($name) { case 'preeval': diff --git a/src/SourceCustom.php b/src/SourceCustom.php index 80d67b8..47d9bb4 100644 --- a/src/SourceCustom.php +++ b/src/SourceCustom.php @@ -2,8 +2,6 @@ namespace Jcupitt\Vips; -use Closure; - class SourceCustom extends Source { /** @@ -27,7 +25,7 @@ public function __construct() * to that number of bytes. If there is no more data available, it should * return null. */ - public function onRead(Closure $callback): void + public function onRead(callable $callback): void { $this->signalConnect('read', $callback); } @@ -45,7 +43,7 @@ public function onRead(Closure $callback): void * 1 => current position * 2 => end */ - public function onSeek(Closure $callback): void + public function onSeek(callable $callback): void { $this->signalConnect('seek', $callback); } diff --git a/src/SourceResource.php b/src/SourceResource.php index 342e6ae..9eaefdd 100644 --- a/src/SourceResource.php +++ b/src/SourceResource.php @@ -2,8 +2,6 @@ namespace Jcupitt\Vips; -use Closure; - class SourceResource extends SourceCustom { /** diff --git a/src/TargetCustom.php b/src/TargetCustom.php index 595bf9b..dedb149 100644 --- a/src/TargetCustom.php +++ b/src/TargetCustom.php @@ -2,8 +2,6 @@ namespace Jcupitt\Vips; -use Closure; - class TargetCustom extends Target { /** @@ -26,7 +24,7 @@ public function __construct() * and should return the number of bytes written. * @throws Exception */ - public function onWrite(Closure $callback): void + public function onWrite(callable $callback): void { $this->signalConnect('write', $callback); } @@ -41,7 +39,7 @@ public function onWrite(Closure $callback): void * target will be treated as unreadable and libvips will be unable to * write some file types (just TIFF, as of the time of writing). */ - public function onRead(Closure $callback): void + public function onRead(callable $callback): void { if (FFI::atLeast(8, 13)) { $this->signalConnect('read', $callback); @@ -61,7 +59,7 @@ public function onRead(Closure $callback): void * 1 => current position * 2 => end */ - public function onSeek(Closure $callback): void + public function onSeek(callable $callback): void { if (FFI::atLeast(8, 13)) { $this->signalConnect('seek', $callback); @@ -75,7 +73,7 @@ public function onSeek(Closure $callback): void * Automatically falls back to onFinish if libvips <8.13 * @throws Exception */ - public function onEnd(Closure $callback): void + public function onEnd(callable $callback): void { if (FFI::atLeast(8, 13)) { $this->signalConnect('end', $callback); @@ -89,7 +87,7 @@ public function onEnd(Closure $callback): void * For libvips 8.13 and later, this method is deprecated in favour of @throws Exception * @see TargetCustom::onEnd() */ - public function onFinish(Closure $callback): void + public function onFinish(callable $callback): void { $this->signalConnect('finish', $callback); } From 3e3d140efde7959e81233d58afde00b53bd51dc2 Mon Sep 17 00:00:00 2001 From: L3tum <9307432+L3tum@users.noreply.github.com> Date: Wed, 31 May 2023 10:00:49 +0200 Subject: [PATCH 15/15] fix: Remove GClosure implementation details and use hardcoded sizeof --- src/FFI.php | 56 +++++++++++++------------------------------------ src/GObject.php | 4 ++-- 2 files changed, 16 insertions(+), 44 deletions(-) diff --git a/src/FFI.php b/src/FFI.php index e684aec..f6cb23d 100644 --- a/src/FFI.php +++ b/src/FFI.php @@ -178,6 +178,18 @@ public static function shutDown(): void self::vips()->vips_shutdown(); } + public static function newGClosure(): \FFI\CData + { + // GClosure measures 32-bit with the first few fields until marshal + // Marshal is a function pointer, thus platform-dependant. + // Data is a pointer, thus platform-dependant. + // Notifiers is an array-pointer, thus platform-dependant. + // All in all it's basically 4 (bytes) + 3 * POINTER_SIZE + // However, gobject wants 8 (bytes) + 3 * POINTER_SIZE. + // I'm not sure where that extra byte comes from. Padding on 64-bit machines? + return self::gobject()->g_closure_new_simple(8 + 3 * PHP_INT_SIZE, null); + } + private static function libraryName(string $name, int $abi): string { switch (PHP_OS_FAMILY) { @@ -433,7 +445,7 @@ private static function init(): void const char* g_param_spec_get_blurb (GParamSpec* psp); -typedef struct _GClosure GClosure; +typedef void *GClosure; typedef void (*marshaler)( struct GClosure* closure, GValue* return_value, @@ -442,47 +454,7 @@ private static function init(): void void* invocation_hint, void* marshal_data ); - -typedef struct _GClosureNotifyData GClosureNotifyData; -struct _GClosureNotifyData -{ - void* data; - GClosureNotify notify; -}; -struct _GClosure -{ - /*< private >*/ - int ref_count : 15; /* (atomic) */ - /* meta_marshal is not used anymore but must be zero for historical reasons - as it was exposed in the G_CLOSURE_N_NOTIFIERS macro */ - int meta_marshal_nouse : 1; /* (atomic) */ - int n_guards : 1; /* (atomic) */ - int n_fnotifiers : 2; /* finalization notifiers (atomic) */ - int n_inotifiers : 8; /* invalidation notifiers (atomic) */ - int in_inotify : 1; /* (atomic) */ - int floating : 1; /* (atomic) */ - /*< protected >*/ - int derivative_flag : 1; /* (atomic) */ - /*< public >*/ - int in_marshal : 1; /* (atomic) */ - int is_invalid : 1; /* (atomic) */ - - /*< private >*/ marshaler marshal; - /*< protected >*/ void* data; - - /*< private >*/ GClosureNotifyData *notifiers; - - /* invariants/constraints: - * - ->marshal and ->data are _invalid_ as soon as ->is_invalid==TRUE - * - invocation of all inotifiers occurs prior to fnotifiers - * - order of inotifiers is random - * inotifiers may _not_ free/invalidate parameter values (e.g. ->data) - * - order of fnotifiers is random - * - each notifier may only be removed before or during its invocation - * - reference counting may only happen prior to fnotify invocation - * (in that sense, fnotifiers are really finalization handlers) - */ -}; +void g_closure_set_marshal(GClosure* closure, marshaler marshal); long g_signal_connect_closure(GObject* object, const char* detailed_signal, GClosure *closure, bool after); GClosure* g_closure_new_simple (int sizeof_closure, void* data); EOS; diff --git a/src/GObject.php b/src/GObject.php index 88998d4..0047e20 100644 --- a/src/GObject.php +++ b/src/GObject.php @@ -109,8 +109,8 @@ public function signalConnect(string $name, callable $callback): void throw new Exception("unsupported signal $name"); } - $gc = FFI::gobject()->g_closure_new_simple(\FFI::sizeof(FFI::ctypes('GClosure')), null); - $gc->marshal = $marshaler; + $gc = FFI::newGClosure(); + FFI::gobject()->g_closure_set_marshal($gc, $marshaler); FFI::gobject()->g_signal_connect_closure($this->pointer, $name, $gc, 0); }