From fc8752108b03a15fce0220e82e95eb81a44b2677 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 4 Apr 2018 14:46:40 +0100 Subject: [PATCH 001/115] revise README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 14cdeae..28c4601 100644 --- a/README.md +++ b/README.md @@ -164,7 +164,7 @@ libvips properties as properties of the PHP `Vips\Image` class. ``` $ phpcs --standard=PSR2 src -$ php ~/packages/php/composer.phar install +$ composer install $ vendor/bin/phpunit $ vendor/bin/phpdoc ``` From 5ce14607fceaea476d82ca0856dd87de15357323 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 4 Apr 2018 15:14:13 +0100 Subject: [PATCH 002/115] remove an eol whitespace --- src/Image.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Image.php b/src/Image.php index 2889852..e3a6e0e 100644 --- a/src/Image.php +++ b/src/Image.php @@ -1978,7 +1978,7 @@ public function bandrank($other, array $options = []): Image /* bandrank will appear as a static class member, as * Image::bandrank([a, b, c]), but it's better as an instance * method. - * + * * We need to define this by hand. */ From 5b52e00dba1e221a1a233c03b3176a1d411ba842 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Sat, 9 Jun 2018 22:03:42 +0200 Subject: [PATCH 003/115] General improvements - Update dependencies. - Test with libvips 8.6.3 on Travis. - Use pyvips for the auto-generated docs/enums (fixes #56). - Regenerate docs/enums. - If statement can be merged with parent one. --- .travis.yml | 2 +- composer.json | 6 +- examples/generate_phpdoc.py | 331 +++++++++++++++++++++ examples/generate_phpdoc.rb | 347 ---------------------- src/DemandStyle.php | 1 - src/Image.php | 10 +- src/ImageAutodoc.php | 562 ++++++++++++++++++------------------ src/ImageType.php | 1 - src/Token.php | 1 - 9 files changed, 617 insertions(+), 644 deletions(-) create mode 100644 examples/generate_phpdoc.py delete mode 100755 examples/generate_phpdoc.rb diff --git a/.travis.yml b/.travis.yml index 582e779..c8d5c9b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ env: global: - VIPS_VERSION_MAJOR=8 - VIPS_VERSION_MINOR=6 - - VIPS_VERSION_MICRO=0 + - VIPS_VERSION_MICRO=3 - PATH=$HOME/vips/bin:$PATH - LD_LIBRARY_PATH=$HOME/vips/lib:$LD_LIBRARY_PATH - PKG_CONFIG_PATH=$HOME/vips/lib/pkgconfig:$PKG_CONFIG_PATH diff --git a/composer.json b/composer.json index e0fde80..ead46b0 100644 --- a/composer.json +++ b/composer.json @@ -19,12 +19,12 @@ "require": { "php": ">=7.0.11", "ext-vips": ">=0.1.2", - "psr/log": "^1.0.1" + "psr/log": "^1.0.2" }, "require-dev": { - "phpunit/phpunit": "^6.3", + "phpunit/phpunit": "^6.5", "phpdocumentor/phpdocumentor" : "^2.9", - "jakub-onderka/php-parallel-lint": "^0.9.2", + "jakub-onderka/php-parallel-lint": "^1.0.0", "squizlabs/php_codesniffer": "3.*" }, "autoload": { diff --git a/examples/generate_phpdoc.py b/examples/generate_phpdoc.py new file mode 100644 index 0000000..fd0f750 --- /dev/null +++ b/examples/generate_phpdoc.py @@ -0,0 +1,331 @@ +#!/usr/bin/env python + +from pyvips import Image, Operation, GValue, Error, \ + ffi, values_for_enum, vips_lib, gobject_lib, \ + type_map, type_name, type_from_name, nickname_find + +# This file generates the phpdoc comments for the magic methods and properties. +# It's in Python, since we use the whole of FFI, not just the +# small bit exposed by php-vips-ext. + +# Regenerate docs with something like: +# +# cd src +# python ../examples/generate_phpdoc.py + +# this needs pyvips +# +# pip install --user pyvips + +# map a Python gtype to PHP argument type names +gtype_to_php_arg = { + GValue.gbool_type: 'bool', + GValue.gint_type: 'integer', + GValue.gdouble_type: 'float', + GValue.gstr_type: 'string', + GValue.refstr_type: 'string', + GValue.genum_type: 'string', + GValue.gflags_type: 'integer', + GValue.gobject_type: 'string', + GValue.image_type: 'Image', + GValue.array_int_type: 'integer[]|integer', + GValue.array_double_type: 'float[]|float', + GValue.array_image_type: 'Image[]|Image', + GValue.blob_type: 'string' +} + +# php result type names are different, annoyingly, and very restricted +gtype_to_php_result = { + GValue.gbool_type: 'bool', + GValue.gint_type: 'integer', + GValue.gdouble_type: 'float', + GValue.gstr_type: 'string', + GValue.refstr_type: 'string', + GValue.genum_type: 'string', + GValue.gflags_type: 'integer', + GValue.gobject_type: 'string', + GValue.image_type: 'Image', + GValue.array_int_type: 'array', + GValue.array_double_type: 'array', + GValue.array_image_type: 'array', + GValue.blob_type: 'string' +} + +# values for VipsArgumentFlags +_REQUIRED = 1 +_INPUT = 16 +_OUTPUT = 32 +_DEPRECATED = 64 +_MODIFY = 128 + +# for VipsOperationFlags +_OPERATION_DEPRECATED = 8 + + +def gtype_to_php(gtype, result=False): + """Map a gtype to PHP type name we use to represent it. + """ + + fundamental = gobject_lib.g_type_fundamental(gtype) + + gtype_map = gtype_to_php_result if result else gtype_to_php_arg + + if gtype in gtype_map: + return gtype_map[gtype] + if fundamental in gtype_map: + return gtype_map[fundamental] + return '' + + +def remove_prefix(enum_str): + prefix = 'Vips' + + if enum_str.startswith(prefix): + return enum_str[len(prefix):] + + return enum_str + + +def generate_operation(operation_name): + op = Operation.new_from_name(operation_name) + + # we are only interested in non-deprecated args + args = [[name, flags] for name, flags in op.get_args() + if not flags & _DEPRECATED] + + # find the first required input image arg, if any ... that will be self + member_x = None + for name, flags in args: + if ((flags & _INPUT) != 0 and + (flags & _REQUIRED) != 0 and + op.get_typeof(name) == GValue.image_type): + member_x = name + break + + required_input = [name for name, flags in args + if (flags & _INPUT) != 0 and + (flags & _REQUIRED) != 0 and + name != member_x] + + required_output = [name for name, flags in args + if ((flags & _OUTPUT) != 0 and + (flags & _REQUIRED) != 0) or + ((flags & _INPUT) != 0 and + (flags & _REQUIRED) != 0 and + (flags & _MODIFY) != 0)] + + result = ' * @method ' + if member_x is None: + result += 'static ' + if len(required_output) == 0: + result += 'void ' + elif len(required_output) == 1: + result += '{0} '.format(gtype_to_php(op.get_typeof(required_output[0]), True)) + else: + # we generate a Returns: block for this case, see below + result += 'array ' + + result += '{0}('.format(operation_name) + for name in required_input: + gtype = op.get_typeof(name) + result += '{0} ${1}, '.format(gtype_to_php(gtype), name) + + result += 'array $options = []) ' + + description = op.get_description() + result += description[0].upper() + description[1:] + '.\n' + + # find any Enums we've referenced and output @see lines for them + for name in required_output + required_input: + gtype = op.get_typeof(name) + fundamental = gobject_lib.g_type_fundamental(gtype) + + if fundamental != GValue.genum_type: + continue + + result += ' * @see {0} for possible values for ${1}\n'.format(remove_prefix(type_name(gtype)), name) + + if len(required_output) > 1: + result += ' * Return array with: [\n' + for name in required_output: + gtype = op.get_typeof(name) + blurb = op.get_blurb(name) + result += ' * \'{0}\' => @type {1} {2}\n'.format(name, gtype_to_php(gtype), + blurb[0].upper() + blurb[1:]) + result += ' * ];\n' + + result += ' * @throws Exception\n' + + return result + + +preamble = """ + * @copyright 2016 John Cupitt + * @license https://opensource.org/licenses/MIT MIT + * @link https://github.com/jcupitt/php-vips + */ +""" + +class_header = """ * @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 +""" + + +def generate_auto_doc(filename): + all_nicknames = [] + + def add_nickname(gtype, a, b): + nickname = nickname_find(gtype) + try: + # can fail for abstract types + op = Operation.new_from_name(nickname) + + # we are only interested in non-deprecated operations + if (op.get_flags() & _OPERATION_DEPRECATED) == 0: + all_nicknames.append(nickname) + except Error: + pass + + type_map(gtype, add_nickname) + + return ffi.NULL + + type_map(type_from_name('VipsOperation'), add_nickname) + + # add 'missing' synonyms by hand + all_nicknames.append('crop') + + # make list unique and sort + all_nicknames = list(set(all_nicknames)) + all_nicknames.sort() + + # these have hand-written methods, don't autodoc them + no_generate = [ + 'bandjoin', + 'bandrank', + 'ifthenelse', + 'add', + 'subtract', + 'multiply', + 'divide', + 'remainder' + ] + all_nicknames = [x for x in all_nicknames if x not in no_generate] + + print('Generating {0} ...'.format(filename)) + + with open(filename, 'w') as f: + f.write(preamble) + f.write('\n') + f.write('namespace Jcupitt\\Vips;\n') + f.write('\n') + f.write('/**\n') + f.write(' * Autodocs for the Image class.\n') + f.write(class_header) + f.write(' *\n') + + for nickname in all_nicknames: + f.write(generate_operation(nickname)) + + f.write(' *\n') + + # all magic properties + tmp_file = Image.new_temp_file('%s.v') + all_properties = tmp_file.get_fields() + for name in all_properties: + php_name = name.replace('-', '_') + gtype = tmp_file.get_typeof(name) + fundamental = gobject_lib.g_type_fundamental(gtype) + + f.write(' * @property {0} ${1} {2}\n'.format(gtype_to_php(gtype), php_name, tmp_file.get_blurb(name))) + + if fundamental == GValue.genum_type: + f.write(' * @see {0} for possible values\n'.format(remove_prefix(type_name(gtype)))) + + f.write(' */\n') + f.write('abstract class ImageAutodoc\n') + f.write('{\n') + f.write('}\n') + + +def generate_enums(): + # otherwise we're missing some enums + vips_lib.vips_token_get_type() + vips_lib.vips_saveable_get_type() + vips_lib.vips_image_type_get_type() + + all_enums = [] + + def add_enum(gtype, a, b): + nickname = type_name(gtype) + all_enums.append(nickname) + + type_map(gtype, add_enum) + + return ffi.NULL + + type_map(type_from_name('GEnum'), add_enum) + + for name in all_enums: + gtype = type_from_name(name) + php_name = remove_prefix(name) + + print('Generating {0}.php ...'.format(php_name)) + + with open('{0}.php'.format(php_name), 'w') as f: + f.write(preamble) + f.write('\n') + f.write('namespace Jcupitt\\Vips;\n') + f.write('\n') + f.write('/**\n') + f.write(' * The {0} enum.\n'.format(php_name)) + f.write(class_header) + f.write(' */\n') + f.write('abstract class {0}\n'.format(php_name)) + f.write('{\n') + + for value in values_for_enum(gtype): + php_name = value.replace('-', '_').upper() + f.write(' const {0} = \'{1}\';\n'.format(php_name, value)) + + f.write('}\n') + + +generate_auto_doc('ImageAutodoc.php') +generate_enums() diff --git a/examples/generate_phpdoc.rb b/examples/generate_phpdoc.rb deleted file mode 100755 index 3d7fc16..0000000 --- a/examples/generate_phpdoc.rb +++ /dev/null @@ -1,347 +0,0 @@ -#!/usr/bin/env ruby - -require 'vips' - -# This file generates the phpdoc comments for the magic methods and properties. -# It's in Ruby, since we use the whole of gobject-introspection, not just the -# small bit exposed by php-vips-ext. - -# Regenerate docs with something like: -# -# cd src -# ../examples/generate_phpdoc.rb - -# this needs version 1.x of ruby-vips -# -# gem install ruby-vips -v 1.0.6 - -# gobject-introspection 3.0.7 crashes a lot if it GCs while doing -# something -GC.disable - -Vips::init("") - -# these have hand-written methods, don't autodoc them -$no_generate = %w( - bandjoin - bandrank - ifthenelse - add - subtract - multiply - divide - remainder - extract_area -) - -# Find all the vips enums -$enums = [] -Vips.constants.each do |name| - const = Vips.const_get name - if const.respond_to? :superclass and - const.superclass == GLib::Enum - $enums << name.to_s - end -end - -def enum?(name) - return true if $enums.include?(name) - - # is there a "Vips" at the front of the name? remove it and try the - # enums again - trim = name.to_s.tap{|s| s.slice!("Vips")} - return true if $enums.include?(trim) - - # is there a "::" at the front of the name? remove it and try the - # enums again - trim.slice! "::" - return true if $enums.include?(trim) - - return false -end - -# map Ruby type names to PHP argument type names -$to_php_map = { - "Vips::Image" => "Image", - "Image" => "Image", - "Array" => "integer[]|integer", - "Array" => "float[]|float", - "Array" => "Image[]|image", - "Integer" => "integer", - "gint" => "integer", - "guint64" => "integer", - "Double" => "float", - "gdouble" => "float", - "Float" => "float", - "String" => "string", - "Boolean" => "bool", - "gboolean" => "bool", - "Vips::Blob" => "string", - "gchararray" => "string", - "gpointer" => "string", -} - -# php result type names are different, annoyingly, and very restricted -$to_php_result_map = { - "Vips::Image" => "Image", - "Image" => "Image", - "Array" => "array", - "Array" => "array", - "Array" => "array", - "Integer" => "integer", - "gint" => "integer", - "guint64" => "integer", - "Double" => "float", - "gdouble" => "float", - "Float" => "float", - "String" => "string", - "Boolean" => "bool", - "gboolean" => "bool", - "Vips::Blob" => "string", - "gchararray" => "string", - "gpointer" => "string", -} - -def type_to_php(map, type) - return map[type] if map.include?(type) - return "string" if enum? type - - # no mapping found - puts "no mapping found for #{type}" - return "" -end - -class Vips::Argument - def to_php - type_to_php $to_php_map, type - end - - def to_php_result - type_to_php $to_php_result_map, type - end -end - -def generate_operation(file, op) - flags = op.flags - return if (flags & :deprecated) != 0 - nickname = Vips::nickname_find op.gtype - - return if $no_generate.include? nickname - - all_args = op.get_args.select {|arg| not arg.isset} - - # separate args into various categories - - required_input = all_args.select do |arg| - (arg.flags & :input) != 0 and - (arg.flags & :required) != 0 - end - - optional_input = all_args.select do |arg| - (arg.flags & :input) != 0 and - (arg.flags & :required) == 0 - end - - required_output = all_args.select do |arg| - (arg.flags & :output) != 0 and - (arg.flags & :required) != 0 - end - - # required input args with :modify are copied and appended to - # output - modified_required_input = required_input.select do |arg| - (arg.flags & :modify) != 0 - end - required_output += modified_required_input - - optional_output = all_args.select do |arg| - (arg.flags & :output) != 0 and - (arg.flags & :required) == 0 - end - - # optional input args with :modify are copied and appended to - # output - modified_optional_input = optional_input.select do |arg| - (arg.flags & :modify) != 0 - end - optional_output += modified_optional_input - - # find the first input image, if any ... we will be a method of this - # instance - member_x = required_input.find do |x| - x.gtype.type_is_a? GLib::Type["VipsImage"] - end - if member_x != nil - required_input.delete member_x - end - - file << " * @method " - file << "static " if not member_x - if required_output.length == 0 - file << "void " - elsif required_output.length == 1 - file << "#{required_output[0].to_php_result} " - else - # we generate a Returns: block for this case, see below - file << "array " - end - - file << "#{nickname}(" - - required_input.each do |arg| - file << "#{arg.to_php} $#{arg.name}, " - end - file << "array $options = []) " - - file << "#{op.description.capitalize}.\n" - - # find any Enums we've referenced and output @see lines for them - used_enums = [] - (required_output + required_input).each do |arg| - next if not enum? arg.type - file << " * @see #{arg.type} for possible values for $#{arg.name}\n" - end - - if required_output.length > 1 - file << " * Return array with: [\n" - required_output.each do |arg| - file << " * '#{arg.name}' => " - file << "@type #{arg.to_php} #{arg.blurb.capitalize}.\n" - end - file << " * ];\n" - end - - file << " * @throws Exception\n" -end - -def generate_class(file, gtype) - begin - # can fail for abstract types - # can't find a way to get to #abstract? from a gtype - op = Vips::Operation.new gtype.name - rescue - op = nil - end - - generate_operation(file, op) if op - - gtype.children.each {|x| generate_class file, x} -end - -preamble = < - * @copyright 2016 John Cupitt - * @license https://opensource.org/licenses/MIT MIT - * @link https://github.com/jcupitt/php-vips - */ -EOF - -class_header = < - * @copyright 2016 John Cupitt - * @license https://opensource.org/licenses/MIT MIT - * @link https://github.com/jcupitt/php-vips -EOF - -# The image class is the most complex -puts "Generating ImageAutodoc.php ..." -File.open("ImageAutodoc.php", "w") do |file| - file << preamble - file << "\n" - file << "namespace Jcupitt\\Vips;\n" - - file << "\n" - file << "/**\n" - file << " * Autodocs for the Image class.\n" - file << class_header - file << " *\n" - - generate_class file, GLib::Type["VipsOperation"] - -# extract_area is in there twice, once as "crop" ... do them by hand - file << < @type Image Sums of columns. - * 'rows' => @type Image Sums of rows. - * ]; + * @method Image boolean(Image $right, string $boolean, array $options = []) Boolean operation on two images. + * @see OperationBoolean for possible values for $boolean * @throws Exception - * @method array profile(array $options = []) Find image profiles. - * Return array with: [ - * 'columns' => @type Image First non-zero pixel in column. - * 'rows' => @type Image First non-zero pixel in row. - * ]; + * @method Image boolean_const(string $boolean, float[]|float $c, array $options = []) Boolean operations against a constant. + * @see OperationBoolean for possible values for $boolean * @throws Exception - * @method Image measure(integer $h, integer $v, array $options = []) Measure a set of patches on a colour chart. + * @method Image buildlut(array $options = []) Build a look-up table. * @throws Exception - * @method array getpoint(integer $x, integer $y, array $options = []) Read a point from an image. + * @method Image byteswap(array $options = []) Byteswap an image. * @throws Exception - * @method array find_trim(array $options = []) Search an image for non-edge areas. - * Return array with: [ - * 'left' => @type integer Left edge of image. - * 'top' => @type integer Top edge of extract area. - * 'width' => @type integer Width of extract area. - * 'height' => @type integer Height of extract area. - * ]; + * @method Image cache(array $options = []) Cache an image. * @throws Exception - * @method Image copy(array $options = []) Copy an image. + * @method Image cast(string $format, array $options = []) Cast an image. + * @see BandFormat for possible values for $format * @throws Exception - * @method Image tilecache(array $options = []) Cache an image as a set of tiles. + * @method Image colourspace(string $space, array $options = []) Convert to a new colorspace. + * @see Interpretation for possible values for $space * @throws Exception - * @method Image linecache(array $options = []) Cache an image as a set of lines. + * @method Image compass(Image $mask, array $options = []) Convolve with rotating mask. * @throws Exception - * @method Image sequential(array $options = []) Check sequential access. + * @method Image complex(string $cmplx, array $options = []) Perform a complex operation on an image. + * @see OperationComplex for possible values for $cmplx * @throws Exception - * @method Image cache(array $options = []) Cache an image. + * @method Image complex2(Image $right, string $cmplx, array $options = []) Complex binary operations on two images. + * @see OperationComplex2 for possible values for $cmplx * @throws Exception - * @method Image embed(integer $x, integer $y, integer $width, integer $height, array $options = []) Embed an image in a larger image. + * @method Image complexform(Image $right, array $options = []) Form a complex image from two real images. * @throws Exception - * @method Image gravity(string $direction, integer $width, integer $height, array $options = []) Place an image within a larger image with a certain gravity. - * @see CompassDirection for possible values for $direction + * @method Image complexget(string $get, array $options = []) Get a component from a complex image. + * @see OperationComplexget for possible values for $get * @throws Exception - * @method Image flip(string $direction, array $options = []) Flip an image. - * @see Direction for possible values for $direction + * @method static Image composite(Image[]|Image $in, integer[]|integer $mode, array $options = []) Blend an array of images with an array of blend modes. * @throws Exception - * @method Image insert(Image $sub, integer $x, integer $y, array $options = []) Insert image @sub into @main at @x, @y. + * @method Image composite2(Image $overlay, string $mode, array $options = []) Blend a pair of images with a blend mode. + * @see BlendMode for possible values for $mode * @throws Exception - * @method Image join(Image $in2, string $direction, array $options = []) Join a pair of images. - * @see Direction for possible values for $direction + * @method Image conv(Image $mask, array $options = []) Convolution operation. * @throws Exception - * @method static Image arrayjoin(Image[]|image $in, array $options = []) Join an array of images. + * @method Image conva(Image $mask, array $options = []) Approximate integer convolution. * @throws Exception - * @method Image smartcrop(integer $width, integer $height, array $options = []) Extract an area from an image. + * @method Image convasep(Image $mask, array $options = []) Approximate separable integer convolution. * @throws Exception - * @method Image extract_band(integer $band, array $options = []) Extract band from an image. + * @method Image convf(Image $mask, array $options = []) Float convolution operation. * @throws Exception - * @method Image bandjoin_const(float[]|float $c, array $options = []) Append a constant band to an image. + * @method Image convi(Image $mask, array $options = []) Int convolution operation. * @throws Exception - * @method Image bandmean(array $options = []) Band-wise average. + * @method Image convsep(Image $mask, array $options = []) Seperable convolution operation. * @throws Exception - * @method Image bandbool(string $boolean, array $options = []) Boolean operation across image bands. - * @see OperationBoolean for possible values for $boolean + * @method Image copy(array $options = []) Copy an image. * @throws Exception - * @method Image replicate(integer $across, integer $down, array $options = []) Replicate an image. + * @method float countlines(string $direction, array $options = []) Count lines in an image. + * @see Direction for possible values for $direction * @throws Exception - * @method Image cast(string $format, array $options = []) Cast an image. - * @see BandFormat for possible values for $format + * @method Image crop(integer $left, integer $top, integer $width, integer $height, array $options = []) Extract an area from an image. * @throws Exception - * @method Image rot(string $angle, array $options = []) Rotate an image. - * @see Angle for possible values for $angle + * @method static Image csvload(string $filename, array $options = []) Load csv from file. * @throws Exception - * @method Image rot45(array $options = []) Rotate an image. + * @method void csvsave(string $filename, array $options = []) Save image to csv file. * @throws Exception - * @method Image autorot(array $options = []) Autorotate image by exif tag. + * @method Image dE00(Image $right, array $options = []) Calculate dE00. * @throws Exception - * @method Image recomb(Image $m, array $options = []) Linear recombination with matrix. + * @method Image dE76(Image $right, array $options = []) Calculate dE76. * @throws Exception - * @method Image bandfold(array $options = []) Fold up x axis into bands. + * @method Image dECMC(Image $right, array $options = []) Calculate dECMC. * @throws Exception - * @method Image bandunfold(array $options = []) Unfold image bands into x axis. + * @method float deviate(array $options = []) Find image standard deviation. * @throws Exception - * @method Image flatten(array $options = []) Flatten alpha out of an image. + * @method Image draw_circle(float[]|float $ink, integer $cx, integer $cy, integer $radius, array $options = []) Draw a circle on an image. * @throws Exception - * @method Image premultiply(array $options = []) Premultiply image alpha. + * @method Image draw_flood(float[]|float $ink, integer $x, integer $y, array $options = []) Flood-fill an area. * @throws Exception - * @method Image unpremultiply(array $options = []) Unpremultiply image alpha. + * @method Image draw_image(Image $sub, integer $x, integer $y, array $options = []) Paint an image into another image. * @throws Exception - * @method Image grid(integer $tile_height, integer $across, integer $down, array $options = []) Grid an image. + * @method Image draw_line(float[]|float $ink, integer $x1, integer $y1, integer $x2, integer $y2, array $options = []) Draw a line on an image. * @throws Exception - * @method Image scale(array $options = []) Scale an image to uchar. + * @method Image draw_mask(float[]|float $ink, Image $mask, integer $x, integer $y, array $options = []) Draw a mask on an image. * @throws Exception - * @method Image wrap(array $options = []) Wrap image origin. + * @method Image draw_rect(float[]|float $ink, integer $left, integer $top, integer $width, integer $height, array $options = []) Paint a rectangle on an image. * @throws Exception - * @method Image zoom(integer $xfac, integer $yfac, array $options = []) Zoom an image. + * @method Image draw_smudge(integer $left, integer $top, integer $width, integer $height, array $options = []) Blur a rectangle on an image. * @throws Exception - * @method Image subsample(integer $xfac, integer $yfac, array $options = []) Subsample an image. + * @method void dzsave(string $filename, array $options = []) Save image to deepzoom file. * @throws Exception - * @method Image msb(array $options = []) Pick most-significant byte from an image. + * @method string dzsave_buffer(array $options = []) Save image to dz buffer. * @throws Exception - * @method Image byteswap(array $options = []) Byteswap an image. + * @method Image embed(integer $x, integer $y, integer $width, integer $height, array $options = []) Embed an image in a larger image. * @throws Exception - * @method Image falsecolour(array $options = []) False-colour an image. + * @method Image extract_area(integer $left, integer $top, integer $width, integer $height, array $options = []) Extract an area from an image. * @throws Exception - * @method Image gamma(array $options = []) Gamma an image. + * @method Image extract_band(integer $band, array $options = []) Extract band from an image. * @throws Exception - * @method static Image composite(Image[]|image $in, integer[]|integer $mode, array $options = []) Blend an array of images with an array of blend modes. + * @method static Image eye(integer $width, integer $height, array $options = []) Make an image showing the eye's spatial response. * @throws Exception - * @method Image composite2(Image $overlay, string $mode, array $options = []) Blend a pair of images with a blend mode. - * @see BlendMode for possible values for $mode + * @method Image falsecolour(array $options = []) False-color an image. * @throws Exception - * @method static Image black(integer $width, integer $height, array $options = []) Make a black image. + * @method Image fastcor(Image $ref, array $options = []) Fast correlation. * @throws Exception - * @method static Image gaussnoise(integer $width, integer $height, array $options = []) Make a gaussnoise image. + * @method Image fill_nearest(array $options = []) Fill image zeros with nearest non-zero pixel. * @throws Exception - * @method static Image text(string $text, array $options = []) Make a text image. + * @method array find_trim(array $options = []) Search an image for non-edge areas. + * Return array with: [ + * 'left' => @type integer Left edge of image + * 'top' => @type integer Top edge of extract area + * 'width' => @type integer Width of extract area + * 'height' => @type integer Height of extract area + * ]; * @throws Exception - * @method static Image xyz(integer $width, integer $height, array $options = []) Make an image where pixel values are coordinates. + * @method static Image fitsload(string $filename, array $options = []) Load a FITS image. * @throws Exception - * @method static Image gaussmat(float $sigma, float $min_ampl, array $options = []) Make a gaussian image. + * @method void fitssave(string $filename, array $options = []) Save image to fits file. * @throws Exception - * @method static Image logmat(float $sigma, float $min_ampl, array $options = []) Make a laplacian of gaussian image. + * @method Image flatten(array $options = []) Flatten alpha out of an image. * @throws Exception - * @method static Image eye(integer $width, integer $height, array $options = []) Make an image showing the eye's spatial response. + * @method Image flip(string $direction, array $options = []) Flip an image. + * @see Direction for possible values for $direction * @throws Exception - * @method static Image grey(integer $width, integer $height, array $options = []) Make a grey ramp image. + * @method Image float2rad(array $options = []) Transform float RGB to Radiance coding. * @throws Exception - * @method static Image zone(integer $width, integer $height, array $options = []) Make a zone plate. + * @method static Image fractsurf(integer $width, integer $height, float $fractal_dimension, array $options = []) Make a fractal surface. * @throws Exception - * @method static Image sines(integer $width, integer $height, array $options = []) Make a 2d sine wave. + * @method Image freqmult(Image $mask, array $options = []) Frequency-domain filtering. * @throws Exception - * @method static Image mask_ideal(integer $width, integer $height, float $frequency_cutoff, array $options = []) Make an ideal filter. + * @method Image fwfft(array $options = []) Forward FFT. * @throws Exception - * @method static Image mask_ideal_ring(integer $width, integer $height, float $frequency_cutoff, float $ringwidth, array $options = []) Make an ideal ring filter. + * @method Image gamma(array $options = []) Gamma an image. * @throws Exception - * @method static Image mask_ideal_band(integer $width, integer $height, float $frequency_cutoff_x, float $frequency_cutoff_y, float $radius, array $options = []) Make an ideal band filter. + * @method Image gaussblur(float $sigma, array $options = []) Gaussian blur. * @throws Exception - * @method static Image mask_butterworth(integer $width, integer $height, float $order, float $frequency_cutoff, float $amplitude_cutoff, array $options = []) Make a butterworth filter. + * @method static Image gaussmat(float $sigma, float $min_ampl, array $options = []) Make a gaussian image. * @throws Exception - * @method static Image mask_butterworth_ring(integer $width, integer $height, float $order, float $frequency_cutoff, float $amplitude_cutoff, float $ringwidth, array $options = []) Make a butterworth ring filter. + * @method static Image gaussnoise(integer $width, integer $height, array $options = []) Make a gaussnoise image. * @throws Exception - * @method static Image mask_butterworth_band(integer $width, integer $height, float $order, float $frequency_cutoff_x, float $frequency_cutoff_y, float $radius, float $amplitude_cutoff, array $options = []) Make a butterworth_band filter. + * @method array getpoint(integer $x, integer $y, array $options = []) Read a point from an image. * @throws Exception - * @method static Image mask_gaussian(integer $width, integer $height, float $frequency_cutoff, float $amplitude_cutoff, array $options = []) Make a gaussian filter. + * @method static Image gifload(string $filename, array $options = []) Load GIF with giflib. * @throws Exception - * @method static Image mask_gaussian_ring(integer $width, integer $height, float $frequency_cutoff, float $amplitude_cutoff, float $ringwidth, array $options = []) Make a gaussian ring filter. + * @method static Image gifload_buffer(string $buffer, array $options = []) Load GIF with giflib. * @throws Exception - * @method static Image mask_gaussian_band(integer $width, integer $height, float $frequency_cutoff_x, float $frequency_cutoff_y, float $radius, float $amplitude_cutoff, array $options = []) Make a gaussian filter. + * @method Image globalbalance(array $options = []) Global balance an image mosaic. * @throws Exception - * @method static Image mask_fractal(integer $width, integer $height, float $fractal_dimension, array $options = []) Make fractal filter. + * @method Image gravity(string $direction, integer $width, integer $height, array $options = []) Place an image within a larger image with a certain gravity. + * @see CompassDirection for possible values for $direction * @throws Exception - * @method Image buildlut(array $options = []) Build a look-up table. + * @method static Image grey(integer $width, integer $height, array $options = []) Make a grey ramp image. * @throws Exception - * @method Image invertlut(array $options = []) Build an inverted look-up table. + * @method Image grid(integer $tile_height, integer $across, integer $down, array $options = []) Grid an image. * @throws Exception - * @method static Image tonelut(array $options = []) Build a look-up table. + * @method Image hist_cum(array $options = []) Form cumulative histogram. * @throws Exception - * @method static Image identity(array $options = []) Make a 1d image where pixel values are indexes. + * @method float hist_entropy(array $options = []) Estimate image entropy. * @throws Exception - * @method static Image fractsurf(integer $width, integer $height, float $fractal_dimension, array $options = []) Make a fractal surface. + * @method Image hist_equal(array $options = []) Histogram equalisation. * @throws Exception - * @method static Image worley(integer $width, integer $height, array $options = []) Make a worley noise image. + * @method Image hist_find(array $options = []) Find image histogram. * @throws Exception - * @method static Image perlin(integer $width, integer $height, array $options = []) Make a perlin noise image. + * @method Image hist_find_indexed(Image $index, array $options = []) Find indexed image histogram. * @throws Exception - * @method static Image csvload(string $filename, array $options = []) Load csv from file. + * @method Image hist_find_ndim(array $options = []) Find n-dimensional image histogram. * @throws Exception - * @method static Image matrixload(string $filename, array $options = []) Load matrix from file. + * @method bool hist_ismonotonic(array $options = []) Test for monotonicity. * @throws Exception - * @method static Image rawload(string $filename, integer $width, integer $height, integer $bands, array $options = []) Load raw data from a file. + * @method Image hist_local(integer $width, integer $height, array $options = []) Local histogram equalisation. * @throws Exception - * @method static Image vipsload(string $filename, array $options = []) Load vips from file. + * @method Image hist_match(Image $ref, array $options = []) Match two histograms. * @throws Exception - * @method static Image analyzeload(string $filename, array $options = []) Load an analyze6 image. + * @method Image hist_norm(array $options = []) Normalise histogram. * @throws Exception - * @method static Image ppmload(string $filename, array $options = []) Load ppm from file. + * @method Image hist_plot(array $options = []) Plot histogram. * @throws Exception - * @method static Image radload(string $filename, array $options = []) Load a radiance image from a file. + * @method Image hough_circle(array $options = []) Find hough circle transform. * @throws Exception - * @method static Image pdfload(string $filename, array $options = []) Load pdf with libpoppler. + * @method Image hough_line(array $options = []) Find hough line transform. * @throws Exception - * @method static Image pdfload_buffer(string $buffer, array $options = []) Load pdf with libpoppler. + * @method Image icc_export(array $options = []) Output to device with ICC profile. * @throws Exception - * @method static Image svgload(string $filename, array $options = []) Load svg with rsvg. + * @method Image icc_import(array $options = []) Import from device with ICC profile. * @throws Exception - * @method static Image svgload_buffer(string $buffer, array $options = []) Load svg with rsvg. + * @method Image icc_transform(string $output_profile, array $options = []) Transform between devices with ICC profiles. * @throws Exception - * @method static Image gifload(string $filename, array $options = []) Load gif with giflib. + * @method static Image identity(array $options = []) Make a 1D image where pixel values are indexes. * @throws Exception - * @method static Image gifload_buffer(string $buffer, array $options = []) Load gif with giflib. + * @method Image insert(Image $sub, integer $x, integer $y, array $options = []) Insert image @sub into @main at @x, @y. * @throws Exception - * @method static Image pngload(string $filename, array $options = []) Load png from file. + * @method Image invert(array $options = []) Invert an image. * @throws Exception - * @method static Image pngload_buffer(string $buffer, array $options = []) Load png from buffer. + * @method Image invertlut(array $options = []) Build an inverted look-up table. * @throws Exception - * @method static Image matload(string $filename, array $options = []) Load mat from file. + * @method Image invfft(array $options = []) Inverse FFT. + * @throws Exception + * @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 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 webpload(string $filename, array $options = []) Load webp from file. + * @method void jpegsave(string $filename, array $options = []) Save image to jpeg file. * @throws Exception - * @method static Image webpload_buffer(string $buffer, array $options = []) Load webp from buffer. + * @method string jpegsave_buffer(array $options = []) Save image to jpeg buffer. * @throws Exception - * @method static Image tiffload(string $filename, array $options = []) Load tiff from file. + * @method void jpegsave_mime(array $options = []) Save image to jpeg mime. * @throws Exception - * @method static Image tiffload_buffer(string $buffer, array $options = []) Load tiff from buffer. + * @method Image labelregions(array $options = []) Label regions in an image. * @throws Exception - * @method static Image openslideload(string $filename, array $options = []) Load file with openslide. + * @method Image linear(float[]|float $a, float[]|float $b, array $options = []) Calculate (a * in + b). * @throws Exception - * @method static Image magickload(string $filename, array $options = []) Load file with imagemagick. + * @method Image linecache(array $options = []) Cache an image as a set of lines. * @throws Exception - * @method static Image magickload_buffer(string $buffer, array $options = []) Load buffer with imagemagick. + * @method static Image logmat(float $sigma, float $min_ampl, array $options = []) Make a laplacian of gaussian image. * @throws Exception - * @method static Image fitsload(string $filename, array $options = []) Load a fits image. + * @method static Image magickload(string $filename, array $options = []) Load file with ImageMagick. * @throws Exception - * @method static Image openexrload(string $filename, array $options = []) Load an openexr image. + * @method static Image magickload_buffer(string $buffer, array $options = []) Load buffer with ImageMagick. * @throws Exception - * @method void csvsave(string $filename, array $options = []) Save image to csv file. + * @method Image mapim(Image $index, array $options = []) Resample with an mapim image. * @throws Exception - * @method void matrixsave(string $filename, array $options = []) Save image to matrix file. + * @method Image maplut(Image $lut, array $options = []) Map an image though a lut. * @throws Exception - * @method void matrixprint(array $options = []) Print matrix. + * @method static Image mask_butterworth(integer $width, integer $height, float $order, float $frequency_cutoff, float $amplitude_cutoff, array $options = []) Make a butterworth filter. * @throws Exception - * @method void rawsave(string $filename, array $options = []) Save image to raw file. + * @method static Image mask_butterworth_band(integer $width, integer $height, float $order, float $frequency_cutoff_x, float $frequency_cutoff_y, float $radius, float $amplitude_cutoff, array $options = []) Make a butterworth_band filter. * @throws Exception - * @method void rawsave_fd(integer $fd, array $options = []) Write raw image to file descriptor. + * @method static Image mask_butterworth_ring(integer $width, integer $height, float $order, float $frequency_cutoff, float $amplitude_cutoff, float $ringwidth, array $options = []) Make a butterworth ring filter. * @throws Exception - * @method void vipssave(string $filename, array $options = []) Save image to vips file. + * @method static Image mask_fractal(integer $width, integer $height, float $fractal_dimension, array $options = []) Make fractal filter. * @throws Exception - * @method void ppmsave(string $filename, array $options = []) Save image to ppm file. + * @method static Image mask_gaussian(integer $width, integer $height, float $frequency_cutoff, float $amplitude_cutoff, array $options = []) Make a gaussian filter. * @throws Exception - * @method void radsave(string $filename, array $options = []) Save image to radiance file. + * @method static Image mask_gaussian_band(integer $width, integer $height, float $frequency_cutoff_x, float $frequency_cutoff_y, float $radius, float $amplitude_cutoff, array $options = []) Make a gaussian filter. * @throws Exception - * @method string radsave_buffer(array $options = []) Save image to radiance buffer. + * @method static Image mask_gaussian_ring(integer $width, integer $height, float $frequency_cutoff, float $amplitude_cutoff, float $ringwidth, array $options = []) Make a gaussian ring filter. * @throws Exception - * @method void dzsave(string $filename, array $options = []) Save image to deepzoom file. + * @method static Image mask_ideal(integer $width, integer $height, float $frequency_cutoff, array $options = []) Make an ideal filter. * @throws Exception - * @method string dzsave_buffer(array $options = []) Save image to dz buffer. + * @method static Image mask_ideal_band(integer $width, integer $height, float $frequency_cutoff_x, float $frequency_cutoff_y, float $radius, array $options = []) Make an ideal band filter. * @throws Exception - * @method void pngsave(string $filename, array $options = []) Save image to png file. + * @method static Image mask_ideal_ring(integer $width, integer $height, float $frequency_cutoff, float $ringwidth, array $options = []) Make an ideal ring filter. * @throws Exception - * @method string pngsave_buffer(array $options = []) Save image to png buffer. + * @method Image match(Image $sec, integer $xr1, integer $yr1, integer $xs1, integer $ys1, integer $xr2, integer $yr2, integer $xs2, integer $ys2, array $options = []) First-order match of two images. * @throws Exception - * @method void jpegsave(string $filename, array $options = []) Save image to jpeg file. + * @method Image math(string $math, array $options = []) Apply a math operation to an image. + * @see OperationMath for possible values for $math * @throws Exception - * @method string jpegsave_buffer(array $options = []) Save image to jpeg buffer. + * @method Image math2(Image $right, string $math2, array $options = []) Binary math operations. + * @see OperationMath2 for possible values for $math2 * @throws Exception - * @method void jpegsave_mime(array $options = []) Save image to jpeg mime. + * @method Image math2_const(string $math2, float[]|float $c, array $options = []) Binary math operations with a constant. + * @see OperationMath2 for possible values for $math2 * @throws Exception - * @method void webpsave(string $filename, array $options = []) Save image to webp file. + * @method static Image matload(string $filename, array $options = []) Load mat from file. * @throws Exception - * @method string webpsave_buffer(array $options = []) Save image to webp buffer. + * @method static Image matrixload(string $filename, array $options = []) Load matrix from file. * @throws Exception - * @method void tiffsave(string $filename, array $options = []) Save image to tiff file. + * @method void matrixprint(array $options = []) Print matrix. * @throws Exception - * @method string tiffsave_buffer(array $options = []) Save image to tiff buffer. + * @method void matrixsave(string $filename, array $options = []) Save image to matrix file. * @throws Exception - * @method void fitssave(string $filename, array $options = []) Save image to fits file. + * @method float max(array $options = []) Find image maximum. * @throws Exception - * @method static Image thumbnail(string $filename, integer $width, array $options = []) Generate thumbnail from file. + * @method Image measure(integer $h, integer $v, array $options = []) Measure a set of patches on a color chart. * @throws Exception - * @method static Image thumbnail_buffer(string $buffer, integer $width, array $options = []) Generate thumbnail from buffer. + * @method Image merge(Image $sec, string $direction, integer $dx, integer $dy, array $options = []) Merge two images. + * @see Direction for possible values for $direction * @throws Exception - * @method Image thumbnail_image(integer $width, array $options = []) Generate thumbnail from image. + * @method float min(array $options = []) Find image minimum. * @throws Exception - * @method Image mapim(Image $index, array $options = []) Resample with an mapim image. + * @method Image morph(Image $mask, string $morph, array $options = []) Morphology operation. + * @see OperationMorphology for possible values for $morph * @throws Exception - * @method Image shrink(float $hshrink, float $vshrink, array $options = []) Shrink an image. + * @method Image mosaic(Image $sec, string $direction, integer $xref, integer $yref, integer $xsec, integer $ysec, array $options = []) Mosaic two images. + * @see Direction for possible values for $direction * @throws Exception - * @method Image shrinkh(integer $hshrink, array $options = []) Shrink an image horizontally. + * @method Image mosaic1(Image $sec, string $direction, integer $xr1, integer $yr1, integer $xs1, integer $ys1, integer $xr2, integer $yr2, integer $xs2, integer $ys2, array $options = []) First-order mosaic of two images. + * @see Direction for possible values for $direction * @throws Exception - * @method Image shrinkv(integer $vshrink, array $options = []) Shrink an image vertically. + * @method Image msb(array $options = []) Pick most-significant byte from an image. * @throws Exception - * @method Image reduceh(float $hshrink, array $options = []) Shrink an image horizontally. + * @method static Image openexrload(string $filename, array $options = []) Load an OpenEXR image. * @throws Exception - * @method Image reducev(float $vshrink, array $options = []) Shrink an image vertically. + * @method static Image openslideload(string $filename, array $options = []) Load file with OpenSlide. * @throws Exception - * @method Image reduce(float $hshrink, float $vshrink, array $options = []) Reduce an image. + * @method static Image pdfload(string $filename, array $options = []) Load PDF with libpoppler. * @throws Exception - * @method Image quadratic(Image $coeff, array $options = []) Resample an image with a quadratic transform. + * @method static Image pdfload_buffer(string $buffer, array $options = []) Load PDF with libpoppler. * @throws Exception - * @method Image affine(float[]|float $matrix, array $options = []) Affine transform of an image. + * @method integer percent(float $percent, array $options = []) Find threshold for percent of pixels. * @throws Exception - * @method Image similarity(array $options = []) Similarity transform of an image. + * @method static Image perlin(integer $width, integer $height, array $options = []) Make a perlin noise image. * @throws Exception - * @method Image resize(float $scale, array $options = []) Resize an image. + * @method Image phasecor(Image $in2, array $options = []) Calculate phase correlation. * @throws Exception - * @method Image colourspace(string $space, array $options = []) Convert to a new colourspace. - * @see Interpretation for possible values for $space + * @method static Image pngload(string $filename, array $options = []) Load png from file. + * @throws Exception + * @method static Image pngload_buffer(string $buffer, array $options = []) Load png from buffer. * @throws Exception - * @method Image Lab2XYZ(array $options = []) Transform cielab to xyz. + * @method void pngsave(string $filename, array $options = []) Save image to png file. * @throws Exception - * @method Image XYZ2Lab(array $options = []) Transform xyz to lab. + * @method string pngsave_buffer(array $options = []) Save image to png buffer. * @throws Exception - * @method Image Lab2LCh(array $options = []) Transform lab to lch. + * @method static Image ppmload(string $filename, array $options = []) Load ppm from file. * @throws Exception - * @method Image LCh2Lab(array $options = []) Transform lch to lab. + * @method void ppmsave(string $filename, array $options = []) Save image to ppm file. * @throws Exception - * @method Image LCh2CMC(array $options = []) Transform lch to cmc. + * @method Image premultiply(array $options = []) Premultiply image alpha. * @throws Exception - * @method Image CMC2LCh(array $options = []) Transform lch to cmc. + * @method array profile(array $options = []) Find image profiles. + * Return array with: [ + * 'columns' => @type Image First non-zero pixel in column + * 'rows' => @type Image First non-zero pixel in row + * ]; * @throws Exception - * @method Image XYZ2Yxy(array $options = []) Transform xyz to yxy. + * @method array project(array $options = []) Find image projections. + * Return array with: [ + * 'columns' => @type Image Sums of columns + * 'rows' => @type Image Sums of rows + * ]; * @throws Exception - * @method Image Yxy2XYZ(array $options = []) Transform yxy to xyz. + * @method Image quadratic(Image $coeff, array $options = []) Resample an image with a quadratic transform. * @throws Exception - * @method Image scRGB2XYZ(array $options = []) Transform scrgb to xyz. + * @method Image rad2float(array $options = []) Unpack Radiance coding to float RGB. * @throws Exception - * @method Image XYZ2scRGB(array $options = []) Transform xyz to scrgb. + * @method static Image radload(string $filename, array $options = []) Load a Radiance image from a file. * @throws Exception - * @method Image LabQ2Lab(array $options = []) Unpack a labq image to float lab. + * @method void radsave(string $filename, array $options = []) Save image to Radiance file. * @throws Exception - * @method Image Lab2LabQ(array $options = []) Transform float lab to labq coding. + * @method string radsave_buffer(array $options = []) Save image to Radiance buffer. * @throws Exception - * @method Image LabQ2LabS(array $options = []) Unpack a labq image to short lab. + * @method Image rank(integer $width, integer $height, integer $index, array $options = []) Rank filter. * @throws Exception - * @method Image LabS2LabQ(array $options = []) Transform short lab to labq coding. + * @method static Image rawload(string $filename, integer $width, integer $height, integer $bands, array $options = []) Load raw data from a file. * @throws Exception - * @method Image LabS2Lab(array $options = []) Transform signed short lab to float. + * @method void rawsave(string $filename, array $options = []) Save image to raw file. * @throws Exception - * @method Image Lab2LabS(array $options = []) Transform float lab to signed short. + * @method void rawsave_fd(integer $fd, array $options = []) Write raw image to file descriptor. * @throws Exception - * @method Image rad2float(array $options = []) Unpack radiance coding to float rgb. + * @method Image recomb(Image $m, array $options = []) Linear recombination with matrix. * @throws Exception - * @method Image float2rad(array $options = []) Transform float rgb to radiance coding. + * @method Image reduce(float $hshrink, float $vshrink, array $options = []) Reduce an image. * @throws Exception - * @method Image LabQ2sRGB(array $options = []) Convert a labq image to srgb. + * @method Image reduceh(float $hshrink, array $options = []) Shrink an image horizontally. * @throws Exception - * @method Image sRGB2HSV(array $options = []) Transform srgb to hsv. + * @method Image reducev(float $vshrink, array $options = []) Shrink an image vertically. * @throws Exception - * @method Image HSV2sRGB(array $options = []) Transform hsv to srgb. + * @method Image relational(Image $right, string $relational, array $options = []) Relational operation on two images. + * @see OperationRelational for possible values for $relational * @throws Exception - * @method Image icc_import(array $options = []) Import from device with icc profile. + * @method Image relational_const(string $relational, float[]|float $c, array $options = []) Relational operations against a constant. + * @see OperationRelational for possible values for $relational * @throws Exception - * @method Image icc_export(array $options = []) Output to device with icc profile. + * @method Image remainder_const(float[]|float $c, array $options = []) Remainder after integer division of an image and a constant. * @throws Exception - * @method Image icc_transform(string $output_profile, array $options = []) Transform between devices with icc profiles. + * @method Image replicate(integer $across, integer $down, array $options = []) Replicate an image. * @throws Exception - * @method Image dE76(Image $right, array $options = []) Calculate de76. + * @method Image resize(float $scale, array $options = []) Resize an image. * @throws Exception - * @method Image dE00(Image $right, array $options = []) Calculate de00. + * @method Image rot(string $angle, array $options = []) Rotate an image. + * @see Angle for possible values for $angle * @throws Exception - * @method Image dECMC(Image $right, array $options = []) Calculate decmc. + * @method Image rot45(array $options = []) Rotate an image. * @throws Exception - * @method Image sRGB2scRGB(array $options = []) Convert an srgb image to scrgb. + * @method Image round(string $round, array $options = []) Perform a round function on an image. + * @see OperationRound for possible values for $round * @throws Exception - * @method Image scRGB2BW(array $options = []) Convert scrgb to bw. + * @method Image sRGB2HSV(array $options = []) Transform sRGB to HSV. * @throws Exception - * @method Image scRGB2sRGB(array $options = []) Convert an scrgb image to srgb. + * @method Image sRGB2scRGB(array $options = []) Convert an sRGB image to scRGB. * @throws Exception - * @method Image maplut(Image $lut, array $options = []) Map an image though a lut. + * @method Image scRGB2BW(array $options = []) Convert scRGB to BW. * @throws Exception - * @method integer percent(float $percent, array $options = []) Find threshold for percent of pixels. + * @method Image scRGB2XYZ(array $options = []) Transform scRGB to XYZ. * @throws Exception - * @method Image stdif(integer $width, integer $height, array $options = []) Statistical difference. + * @method Image scRGB2sRGB(array $options = []) Convert an scRGB image to sRGB. * @throws Exception - * @method Image hist_cum(array $options = []) Form cumulative histogram. + * @method Image scale(array $options = []) Scale an image to uchar. * @throws Exception - * @method Image hist_match(Image $ref, array $options = []) Match two histograms. + * @method Image sequential(array $options = []) Check sequential access. * @throws Exception - * @method Image hist_norm(array $options = []) Normalise histogram. + * @method Image sharpen(array $options = []) Unsharp masking for print. * @throws Exception - * @method Image hist_equal(array $options = []) Histogram equalisation. + * @method Image shrink(float $hshrink, float $vshrink, array $options = []) Shrink an image. * @throws Exception - * @method Image hist_plot(array $options = []) Plot histogram. + * @method Image shrinkh(integer $hshrink, array $options = []) Shrink an image horizontally. * @throws Exception - * @method Image hist_local(integer $width, integer $height, array $options = []) Local histogram equalisation. + * @method Image shrinkv(integer $vshrink, array $options = []) Shrink an image vertically. * @throws Exception - * @method bool hist_ismonotonic(array $options = []) Test for monotonicity. + * @method Image sign(array $options = []) Unit vector of pixel. * @throws Exception - * @method float hist_entropy(array $options = []) Estimate image entropy. + * @method Image similarity(array $options = []) Similarity transform of an image. * @throws Exception - * @method Image conv(Image $mask, array $options = []) Convolution operation. + * @method static Image sines(integer $width, integer $height, array $options = []) Make a 2D sine wave. * @throws Exception - * @method Image conva(Image $mask, array $options = []) Approximate integer convolution. + * @method Image smartcrop(integer $width, integer $height, array $options = []) Extract an area from an image. * @throws Exception - * @method Image convf(Image $mask, array $options = []) Float convolution operation. + * @method Image spcor(Image $ref, array $options = []) Spatial correlation. * @throws Exception - * @method Image convi(Image $mask, array $options = []) Int convolution operation. + * @method Image spectrum(array $options = []) Make displayable power spectrum. * @throws Exception - * @method Image compass(Image $mask, array $options = []) Convolve with rotating mask. + * @method Image stats(array $options = []) Find image average. * @throws Exception - * @method Image convsep(Image $mask, array $options = []) Seperable convolution operation. + * @method Image stdif(integer $width, integer $height, array $options = []) Statistical difference. * @throws Exception - * @method Image convasep(Image $mask, array $options = []) Approximate separable integer convolution. + * @method Image subsample(integer $xfac, integer $yfac, array $options = []) Subsample an image. * @throws Exception - * @method Image fastcor(Image $ref, array $options = []) Fast correlation. + * @method static Image sum(Image[]|Image $in, array $options = []) Sum an array of images. * @throws Exception - * @method Image spcor(Image $ref, array $options = []) Spatial correlation. + * @method static Image svgload(string $filename, array $options = []) Load SVG with rsvg. * @throws Exception - * @method Image sharpen(array $options = []) Unsharp masking for print. + * @method static Image svgload_buffer(string $buffer, array $options = []) Load SVG with rsvg. * @throws Exception - * @method Image gaussblur(float $sigma, array $options = []) Gaussian blur. + * @method static void system(string $cmd_format, array $options = []) Run an external command. * @throws Exception - * @method Image fwfft(array $options = []) Forward fft. + * @method static Image text(string $text, array $options = []) Make a text image. * @throws Exception - * @method Image invfft(array $options = []) Inverse fft. + * @method static Image thumbnail(string $filename, integer $width, array $options = []) Generate thumbnail from file. * @throws Exception - * @method Image freqmult(Image $mask, array $options = []) Frequency-domain filtering. + * @method static Image thumbnail_buffer(string $buffer, integer $width, array $options = []) Generate thumbnail from buffer. * @throws Exception - * @method Image spectrum(array $options = []) Make displayable power spectrum. + * @method Image thumbnail_image(integer $width, array $options = []) Generate thumbnail from image. * @throws Exception - * @method Image phasecor(Image $in2, array $options = []) Calculate phase correlation. + * @method static Image tiffload(string $filename, array $options = []) Load tiff from file. * @throws Exception - * @method Image morph(Image $mask, string $morph, array $options = []) Morphology operation. - * @see OperationMorphology for possible values for $morph + * @method static Image tiffload_buffer(string $buffer, array $options = []) Load tiff from buffer. * @throws Exception - * @method Image rank(integer $width, integer $height, integer $index, array $options = []) Rank filter. + * @method void tiffsave(string $filename, array $options = []) Save image to tiff file. * @throws Exception - * @method float countlines(string $direction, array $options = []) Count lines in an image. - * @see Direction for possible values for $direction + * @method string tiffsave_buffer(array $options = []) Save image to tiff buffer. * @throws Exception - * @method Image labelregions(array $options = []) Label regions in an image. + * @method Image tilecache(array $options = []) Cache an image as a set of tiles. * @throws Exception - * @method Image fill_nearest(array $options = []) Fill image zeros with nearest non-zero pixel. + * @method static Image tonelut(array $options = []) Build a look-up table. * @throws Exception - * @method Image draw_rect(float[]|float $ink, integer $left, integer $top, integer $width, integer $height, array $options = []) Paint a rectangle on an image. + * @method Image unpremultiply(array $options = []) Unpremultiply image alpha. * @throws Exception - * @method Image draw_mask(float[]|float $ink, Image $mask, integer $x, integer $y, array $options = []) Draw a mask on an image. + * @method static Image vipsload(string $filename, array $options = []) Load vips from file. * @throws Exception - * @method Image draw_line(float[]|float $ink, integer $x1, integer $y1, integer $x2, integer $y2, array $options = []) Draw a line on an image. + * @method void vipssave(string $filename, array $options = []) Save image to vips file. * @throws Exception - * @method Image draw_circle(float[]|float $ink, integer $cx, integer $cy, integer $radius, array $options = []) Draw a circle on an image. + * @method static Image webpload(string $filename, array $options = []) Load webp from file. * @throws Exception - * @method Image draw_flood(float[]|float $ink, integer $x, integer $y, array $options = []) Flood-fill an area. + * @method static Image webpload_buffer(string $buffer, array $options = []) Load webp from buffer. * @throws Exception - * @method Image draw_image(Image $sub, integer $x, integer $y, array $options = []) Paint an image into another image. + * @method void webpsave(string $filename, array $options = []) Save image to webp file. * @throws Exception - * @method Image draw_smudge(integer $left, integer $top, integer $width, integer $height, array $options = []) Blur a rectangle on an image. + * @method string webpsave_buffer(array $options = []) Save image to webp buffer. * @throws Exception - * @method Image merge(Image $sec, string $direction, integer $dx, integer $dy, array $options = []) Merge two images. - * @see Direction for possible values for $direction + * @method static Image worley(integer $width, integer $height, array $options = []) Make a worley noise image. * @throws Exception - * @method Image mosaic(Image $sec, string $direction, integer $xref, integer $yref, integer $xsec, integer $ysec, array $options = []) Mosaic two images. - * @see Direction for possible values for $direction + * @method Image wrap(array $options = []) Wrap image origin. * @throws Exception - * @method Image mosaic1(Image $sec, string $direction, integer $xr1, integer $yr1, integer $xs1, integer $ys1, integer $xr2, integer $yr2, integer $xs2, integer $ys2, array $options = []) First-order mosaic of two images. - * @see Direction for possible values for $direction + * @method static Image xyz(integer $width, integer $height, array $options = []) Make an image where pixel values are coordinates. * @throws Exception - * @method Image match(Image $sec, integer $xr1, integer $yr1, integer $xs1, integer $ys1, integer $xr2, integer $yr2, integer $xs2, integer $ys2, array $options = []) First-order match of two images. + * @method static Image zone(integer $width, integer $height, array $options = []) Make a zone plate. * @throws Exception - * @method Image globalbalance(array $options = []) Global balance an image mosaic. + * @method Image zoom(integer $xfac, integer $yfac, array $options = []) Zoom an image. * @throws Exception - * @method Image extract_area(integer $left, integer $top, integer $width, integer $height, array $options = []) Extract an area from an image. - * @method Image crop(integer $left, integer $top, integer $width, integer $height, array $options = []) Extract an area from an image. * - * @property string $nickname Class nickname - * @property string $description Class description * @property integer $width Image width in pixels * @property integer $height Image height in pixels * @property integer $bands Number of bands in image @@ -571,17 +571,11 @@ * @see Coding for possible values * @property string $interpretation Pixel interpretation * @see Interpretation for possible values - * @property float $xres Horizontal resolution in pixels/mm - * @property float $yres Vertical resolution in pixels/mm * @property integer $xoffset Horizontal offset of origin * @property integer $yoffset Vertical offset of origin + * @property float $xres Horizontal resolution in pixels/mm + * @property float $yres Vertical resolution in pixels/mm * @property string $filename Image filename - * @property string $mode Open mode - * @property bool $kill Block evaluation on this image - * @property string $demand Preferred demand style for this image - * @see DemandStyle for possible values - * @property integer $sizeof_header Offset in bytes from start of file - * @property string $foreign_buffer Pointer to foreign pixels */ abstract class ImageAutodoc { diff --git a/src/ImageType.php b/src/ImageType.php index 83648f5..3678d22 100644 --- a/src/ImageType.php +++ b/src/ImageType.php @@ -57,5 +57,4 @@ abstract class ImageType const MMAPIN = 'mmapin'; const MMAPINRW = 'mmapinrw'; const OPENOUT = 'openout'; - const PARTIAL = 'partial'; } diff --git a/src/Token.php b/src/Token.php index b48aa2c..40df593 100644 --- a/src/Token.php +++ b/src/Token.php @@ -53,5 +53,4 @@ abstract class Token const RIGHT = 'right'; const STRING = 'string'; const EQUALS = 'equals'; - const COMMA = 'comma'; } From b3b3c74cad50a9a2f031e0354e72146bb4be5985 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Sat, 9 Jun 2018 22:34:51 +0200 Subject: [PATCH 004/115] Use elseif instead --- src/Image.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Image.php b/src/Image.php index f7f52a2..3c6132c 100644 --- a/src/Image.php +++ b/src/Image.php @@ -612,7 +612,7 @@ private static function mapNumeric($value, \Closure $func) { if (is_numeric($value)) { $value = $func($value); - } else if (is_array($value)) { + } elseif (is_array($value)) { array_walk_recursive($value, function (&$value) use ($func) { $value = self::mapNumeric($value, $func); }); From af3edb077271674d2218a293e68996aaa39c8eaf Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 4 Oct 2018 16:01:41 +0100 Subject: [PATCH 005/115] update links for new home and update autodocs for 8.7 --- CONTRIBUTING.md | 2 +- README.md | 16 +++++------ composer.json | 2 +- examples/generate_phpdoc.py | 0 install-vips.sh | 2 +- src/ImageAutodoc.php | 18 +++++++++++- src/Interesting.php | 2 ++ src/Kernel.php | 1 + src/RegionShrink.php | 55 +++++++++++++++++++++++++++++++++++++ 9 files changed, 86 insertions(+), 12 deletions(-) mode change 100644 => 100755 examples/generate_phpdoc.py create mode 100644 src/RegionShrink.php diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 724f6ac..b092559 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ Contributions are **welcome** and will be fully **credited**. -We accept contributions via Pull Requests on [Github](https://github.com/jcupitt/php-vips). +We accept contributions via Pull Requests on [Github](https://github.com/libvips/php-vips). ## Pull Requests diff --git a/README.md b/README.md index 28c4601..1a57513 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # PHP binding for libvips -[![Build Status](https://travis-ci.org/jcupitt/php-vips.svg?branch=master)](https://travis-ci.org/jcupitt/php-vips) +[![Build Status](https://travis-ci.org/libvips/php-vips.svg?branch=master)](https://travis-ci.org/libvips/php-vips) -`php-vips` is a binding for [libvips](https://github.com/jcupitt/libvips) for +`php-vips` is a binding for [libvips](https://github.com/libvips/libvips) for PHP 7. libvips is fast and needs little memory. The @@ -19,7 +19,7 @@ destination in a set of small fragments. This module builds upon the `vips` PHP extension: -https://github.com/jcupitt/php-vips-ext +https://github.com/libvips/php-vips-ext You'll need to install that first. It's tested on Linux and macOS --- Windows would need some work, but should be possible. @@ -27,7 +27,7 @@ Windows would need some work, but should be possible. See the README there, but briefly: 1. [Install the libvips library and - headers](https://jcupitt.github.io/libvips/install.html). It's in + headers](https://libvips.github.io/libvips/install.html). It's in the linux package managers, homebrew and MacPorts, and there are Windows binaries on the vips website. For example, on Debian: @@ -85,7 +85,7 @@ $ ./try1.php ~/pics/k2.jpg x.tif ``` See `examples/`. We have a [complete set of formatted API -docs](https://jcupitt.github.io/php-vips/docs/classes/Jcupitt.Vips.Image.html). +docs](https://libvips.github.io/php-vips/docs/classes/Jcupitt.Vips.Image.html). ### Introduction to the API @@ -135,7 +135,7 @@ For example: $image->writeToFile("fred.jpg", ["Q" => 90]); ``` -`php-vips` comes [with full API docs](https://jcupitt.github.io/php-vips/docs/classes/Jcupitt.Vips.Image.html). To regenerate these from your sources, type: +`php-vips` comes [with full API docs](https://libvips.github.io/php-vips/docs/classes/Jcupitt.Vips.Image.html). To regenerate these from your sources, type: ``` $ vendor/bin/phpdoc @@ -146,7 +146,7 @@ And look in `docs/`. There are around 300 operations in the library, see the vips docs for an introduction: -https://jcupitt.github.io/libvips/API/current +https://libvips.github.io/libvips/API/current ### How it works @@ -173,6 +173,6 @@ $ vendor/bin/phpdoc ``` $ cd src -$ ../examples/generate_phpdoc.rb +$ ../examples/generate_phpdoc.py ``` diff --git a/composer.json b/composer.json index ead46b0..cc95a2f 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,7 @@ "processing", "libvips" ], - "homepage": "https://github.com/jcupitt/php-vips", + "homepage": "https://github.com/libvips/php-vips", "license": "MIT", "authors": [ { diff --git a/examples/generate_phpdoc.py b/examples/generate_phpdoc.py old mode 100644 new mode 100755 diff --git a/install-vips.sh b/install-vips.sh index edf02ea..d09dc89 100755 --- a/install-vips.sh +++ b/install-vips.sh @@ -1,6 +1,6 @@ #!/bin/bash -vips_site=https://github.com/jcupitt/libvips/releases/download +vips_site=https://github.com/libvips/libvips/releases/download version=$VIPS_VERSION_MAJOR.$VIPS_VERSION_MINOR.$VIPS_VERSION_MICRO set -e diff --git a/src/ImageAutodoc.php b/src/ImageAutodoc.php index c1b974d..7617c1e 100644 --- a/src/ImageAutodoc.php +++ b/src/ImageAutodoc.php @@ -118,6 +118,8 @@ * @throws Exception * @method Image cache(array $options = []) Cache an image. * @throws Exception + * @method Image canny(array $options = []) Canny edge detector. + * @throws Exception * @method Image cast(string $format, array $options = []) Cast an image. * @see BandFormat for possible values for $format * @throws Exception @@ -320,6 +322,10 @@ * @throws Exception * @method static Image magickload_buffer(string $buffer, array $options = []) Load buffer with ImageMagick. * @throws Exception + * @method void magicksave(string $filename, array $options = []) Save file with ImageMagick. + * @throws Exception + * @method string magicksave_buffer(array $options = []) Save image to magick buffer. + * @throws Exception * @method Image mapim(Image $index, array $options = []) Resample with an mapim image. * @throws Exception * @method Image maplut(Image $lut, array $options = []) Map an image though a lut. @@ -383,6 +389,10 @@ * @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 a NIFTI image. + * @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. @@ -466,6 +476,8 @@ * @throws Exception * @method Image rot45(array $options = []) Rotate an image. * @throws Exception + * @method Image rotate(float $angle, array $options = []) Rotate an image by a number of degrees. + * @throws Exception * @method Image round(string $round, array $options = []) Perform a round function on an image. * @see OperationRound for possible values for $round * @throws Exception @@ -499,11 +511,13 @@ * @throws Exception * @method Image smartcrop(integer $width, integer $height, array $options = []) Extract an area from an image. * @throws Exception + * @method Image sobel(array $options = []) Sobel edge detector. + * @throws Exception * @method Image spcor(Image $ref, array $options = []) Spatial correlation. * @throws Exception * @method Image spectrum(array $options = []) Make displayable power spectrum. * @throws Exception - * @method Image stats(array $options = []) Find image average. + * @method Image stats(array $options = []) Find many image stats. * @throws Exception * @method Image stdif(integer $width, integer $height, array $options = []) Statistical difference. * @throws Exception @@ -537,6 +551,8 @@ * @throws Exception * @method static Image tonelut(array $options = []) Build a look-up table. * @throws Exception + * @method Image transpose3d(array $options = []) Transpose3d an image. + * @throws Exception * @method Image unpremultiply(array $options = []) Unpremultiply image alpha. * @throws Exception * @method static Image vipsload(string $filename, array $options = []) Load vips from file. diff --git a/src/Interesting.php b/src/Interesting.php index 2d64c21..80a03b2 100644 --- a/src/Interesting.php +++ b/src/Interesting.php @@ -53,4 +53,6 @@ abstract class Interesting const CENTRE = 'centre'; const ENTROPY = 'entropy'; const ATTENTION = 'attention'; + const LOW = 'low'; + const HIGH = 'high'; } diff --git a/src/Kernel.php b/src/Kernel.php index 43bb236..d5b76dc 100644 --- a/src/Kernel.php +++ b/src/Kernel.php @@ -52,6 +52,7 @@ abstract class Kernel const NEAREST = 'nearest'; const LINEAR = 'linear'; const CUBIC = 'cubic'; + const MITCHELL = 'mitchell'; const LANCZOS2 = 'lanczos2'; const LANCZOS3 = 'lanczos3'; } diff --git a/src/RegionShrink.php b/src/RegionShrink.php new file mode 100644 index 0000000..ebf1d50 --- /dev/null +++ b/src/RegionShrink.php @@ -0,0 +1,55 @@ + + * @copyright 2016 John Cupitt + * @license https://opensource.org/licenses/MIT MIT + * @link https://github.com/jcupitt/php-vips + */ + +namespace Jcupitt\Vips; + +/** + * The RegionShrink 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 RegionShrink +{ + const MEAN = 'mean'; + const MEDIAN = 'median'; + const MODE = 'mode'; +} From 3678b8a4b1c301c9e6f6e136746f5894e85b9dbe Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 4 Oct 2018 16:25:12 +0100 Subject: [PATCH 006/115] version bump --- RELEASE-1.0.2 => RELEASE-1.0.3 | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename RELEASE-1.0.2 => RELEASE-1.0.3 (100%) diff --git a/RELEASE-1.0.2 b/RELEASE-1.0.3 similarity index 100% rename from RELEASE-1.0.2 rename to RELEASE-1.0.3 From 31b29b8f0a4604c268f987293c4526eb4703c92b Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 29 Nov 2018 17:31:07 +0000 Subject: [PATCH 007/115] fix phpdoc we need to stop it using "jms/serializer" 1.8.0 or later, there's an issue with paths add a "1" to the end of some autogenerated enum names to stop phpdoc falling over --- composer.json | 1 + examples/generate_phpdoc.py | 10 ++++++++++ src/BlendMode.php | 2 +- src/ForeignWebpPreset.php | 2 +- src/Image.php | 2 +- src/OperationBoolean.php | 4 ++-- 6 files changed, 16 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index cc95a2f..ee764e4 100644 --- a/composer.json +++ b/composer.json @@ -22,6 +22,7 @@ "psr/log": "^1.0.2" }, "require-dev": { + "jms/serializer" : ">=0.12 < 1.8.0", "phpunit/phpunit": "^6.5", "phpdocumentor/phpdocumentor" : "^2.9", "jakub-onderka/php-parallel-lint": "^1.0.0", diff --git a/examples/generate_phpdoc.py b/examples/generate_phpdoc.py index fd0f750..11c7b05 100755 --- a/examples/generate_phpdoc.py +++ b/examples/generate_phpdoc.py @@ -61,6 +61,14 @@ # for VipsOperationFlags _OPERATION_DEPRECATED = 8 +# some names we might generate are reserved PHP names ... just append a "1". +reserved_php_names = { + 'DEFAULT': 'DEFAULT1', + 'XOR': 'XOR1', + 'AND': 'AND1', + 'OR': 'OR1' +} + def gtype_to_php(gtype, result=False): """Map a gtype to PHP type name we use to represent it. @@ -322,6 +330,8 @@ def add_enum(gtype, a, b): for value in values_for_enum(gtype): php_name = value.replace('-', '_').upper() + if php_name in reserved_php_names: + php_name = reserved_php_names[php_name] f.write(' const {0} = \'{1}\';\n'.format(php_name, value)) f.write('}\n') diff --git a/src/BlendMode.php b/src/BlendMode.php index 833ee1b..8f0df6d 100644 --- a/src/BlendMode.php +++ b/src/BlendMode.php @@ -60,7 +60,7 @@ abstract class BlendMode const DEST_IN = 'dest-in'; const DEST_OUT = 'dest-out'; const DEST_ATOP = 'dest-atop'; - const XOR = 'xor'; + const XOR1 = 'xor'; const ADD = 'add'; const SATURATE = 'saturate'; const MULTIPLY = 'multiply'; diff --git a/src/ForeignWebpPreset.php b/src/ForeignWebpPreset.php index 44f84ef..cfbf0b0 100644 --- a/src/ForeignWebpPreset.php +++ b/src/ForeignWebpPreset.php @@ -49,7 +49,7 @@ */ abstract class ForeignWebpPreset { - const DEFAULT = 'default'; + const DEFAULT1 = 'default'; const PICTURE = 'picture'; const PHOTO = 'photo'; const DRAWING = 'drawing'; diff --git a/src/Image.php b/src/Image.php index 3c6132c..3ae0dd6 100644 --- a/src/Image.php +++ b/src/Image.php @@ -558,7 +558,7 @@ class Image extends ImageAutodoc implements \ArrayAccess BlendMode::DEST_IN => 8, BlendMode::DEST_OUT => 9, BlendMode::DEST_ATOP => 10, - BlendMode::XOR => 11, + BlendMode::XOR1 => 11, BlendMode::ADD => 12, BlendMode::SATURATE => 13, BlendMode::MULTIPLY => 14, diff --git a/src/OperationBoolean.php b/src/OperationBoolean.php index 78f6f40..5b69509 100644 --- a/src/OperationBoolean.php +++ b/src/OperationBoolean.php @@ -49,8 +49,8 @@ */ abstract class OperationBoolean { - const AND = 'and'; - const OR = 'or'; + const AND1 = 'and'; + const OR1 = 'or'; const EOR = 'eor'; const LSHIFT = 'lshift'; const RSHIFT = 'rshift'; From e958d35798b8976be2553374be2f939f9e298888 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 22 Dec 2018 12:19:13 +0000 Subject: [PATCH 008/115] polar() and rect() work on non-complex make polar() and recct() work on non-complex images, like Python see https://github.com/libvips/php-vips/issues/81 --- CHANGELOG.md | 17 ++++++++++ RELEASE-1.0.3 => RELEASE-1.0.4 | 0 src/Image.php | 61 ++++++++++++++++++++++++++++++++-- 3 files changed, 76 insertions(+), 2 deletions(-) rename RELEASE-1.0.3 => RELEASE-1.0.4 (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f1ab2f..81b68fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,23 @@ # Changelog All notable changes to `:vips` will be documented in this file. +## 1.0.4 - 2018-12-22 + +### Added +- polar() and rect() now work onadd cross_phase() [John Cupitt] + +### Deprecated +- Nothing + +### Fixed +- Nothing + +### Remove +- Nothing + +### Security +- Nothing + ## 1.0.3 - 2017-06-06 ### Added diff --git a/RELEASE-1.0.3 b/RELEASE-1.0.4 similarity index 100% rename from RELEASE-1.0.3 rename to RELEASE-1.0.4 diff --git a/src/Image.php b/src/Image.php index 3ae0dd6..37deefc 100644 --- a/src/Image.php +++ b/src/Image.php @@ -801,6 +801,59 @@ private static function errorIsArray($result) } } + /** + * Run a function expecting a complex image. If the image is not in complex + * format, try to make it complex by joining adjacant bands as real and + * imaginary. + * + * @param function $filename The function to run. + * @param Image $image The image to run the function on. + * + * @throws Exception + * + * @return Image A new Image. + * + * @internal + */ + private static function runCmplx($func, Image $image): Image + { + $original_format = $image->format; + + if ($image->format != 'complex' && $image->format != 'dpcomplex') { + if ($image->bands % 2 != 0) { + throw new Exception('not an even number of bands'); + } + + if ($image->format != 'float' && $image->format != 'double') { + $image = $image->cast('float'); + } + + if ($image->format == 'double') { + $new_format = 'dpcomplex'; + } else { + $new_format = 'complex'; + } + + $image = $image->copy(['format' => $new_format, + 'bands' => $image->bands / 2]); + } + + $image = $func($image); + + if ($original_format != 'complex' && $original_format != 'dpcomplex') { + if ($image->format == 'dpcomplex') { + $new_format = 'double'; + } else { + $new_format = 'float'; + } + + $image = $image->copy(['format' => $new_format, + 'bands' => $image->bands * 2]); + } + + return $image; + } + /** * Create a new Image from a file on disc. * @@ -2207,7 +2260,9 @@ public function imag(): Image */ public function polar(): Image { - return $this->complex(OperationComplex::POLAR); + return self::runCmplx(function ($image) { + return $image->complex(OperationComplex::POLAR); + }, $this); } /** @@ -2219,7 +2274,9 @@ public function polar(): Image */ public function rect(): Image { - return $this->complex(OperationComplex::RECT); + return self::runCmplx(function ($image) { + return $image->complex(OperationComplex::RECT); + }, $this); } /** From 03cc7b00e15daab8fd9f9426a2e458ec83983ea0 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 22 Dec 2018 12:34:48 +0000 Subject: [PATCH 009/115] add crossPhase() an enum expansion we'd forgotten --- CHANGELOG.md | 4 +++- src/Image.php | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81b68fb..047d22a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,9 @@ All notable changes to `:vips` will be documented in this file. ## 1.0.4 - 2018-12-22 ### Added -- polar() and rect() now work onadd cross_phase() [John Cupitt] +- polar() and rect() now work on non-complex images [John Cupitt] +- add crossPhase() [John Cupitt] +- update autodocs [John Cupitt] ### Deprecated - Nothing diff --git a/src/Image.php b/src/Image.php index 37deefc..f8a121b 100644 --- a/src/Image.php +++ b/src/Image.php @@ -2291,6 +2291,20 @@ public function conj(): Image return $this->complex(OperationComplex::CONJ); } + /** + * Find the cross-phase of this image with $other. + * + * @param mixed $other The thing to cross-phase by. + * + * @throws Exception + * + * @return Image A new image. + */ + public function crossPhase($other): Image + { + return $this->complex2($other, OperationComplex2::CROSS_PHASE); + } + /** * Return the sine of an image in degrees. * From d6cd69307b086f43bc099471a4653cbf5ecc40d2 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 22 Dec 2018 12:44:40 +0000 Subject: [PATCH 010/115] fix a vips8.8-ism in docs --- src/Interesting.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Interesting.php b/src/Interesting.php index 80a03b2..2d64c21 100644 --- a/src/Interesting.php +++ b/src/Interesting.php @@ -53,6 +53,4 @@ abstract class Interesting const CENTRE = 'centre'; const ENTROPY = 'entropy'; const ATTENTION = 'attention'; - const LOW = 'low'; - const HIGH = 'high'; } From 0338997bf358d1ac87f75ef7b814933ec40caa21 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 22 Feb 2019 11:12:23 +0000 Subject: [PATCH 011/115] update version number in README example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1a57513..4641e17 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ See the README there, but briefly: ``` "require": { - "jcupitt/vips" : "1.0.2" + "jcupitt/vips" : "1.0.4" } ``` From e0c3d3fb9727c6bd96bd1ff8622d1afae0791935 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 26 Sep 2019 16:34:52 +0100 Subject: [PATCH 012/115] add writeToArray and bump v to 1.0.5 --- CHANGELOG.md | 17 ++++++++++++++ README.md | 2 +- RELEASE-1.0.4 => RELEASE-1.0.5 | 0 src/Image.php | 43 +++++++++++++++++++++++++++++++++- 4 files changed, 60 insertions(+), 2 deletions(-) rename RELEASE-1.0.4 => RELEASE-1.0.5 (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 047d22a..797e22d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,23 @@ # Changelog All notable changes to `:vips` will be documented in this file. +## 1.0.5 - 2019-09-26 + +### Added +- writeToArray() [John Cupitt] + +### Deprecated +- Nothing + +### Fixed +- Nothing + +### Remove +- Nothing + +### Security +- Nothing + ## 1.0.4 - 2018-12-22 ### Added diff --git a/README.md b/README.md index 4641e17..cf5b4bc 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ See the README there, but briefly: ``` "require": { - "jcupitt/vips" : "1.0.4" + "jcupitt/vips" : "1.0.5" } ``` diff --git a/RELEASE-1.0.4 b/RELEASE-1.0.5 similarity index 100% rename from RELEASE-1.0.4 rename to RELEASE-1.0.5 diff --git a/src/Image.php b/src/Image.php index f8a121b..357a981 100644 --- a/src/Image.php +++ b/src/Image.php @@ -137,7 +137,8 @@ * * `Image::writeToFile` writes an image back to the filesystem. It can * write any format supported by vips: the file type is set from the filename - * suffix. You can also write formatted images to strings. + * suffix. You can write formatted images to strings, and pixel values to + * arrays. * * # Getting more help * @@ -1212,6 +1213,46 @@ public function writeToMemory(): string return $result; } + /** + * Write an image to a PHP array. + * + * Pixels are written as a simple one-dimensional array, for example, if + * you write: + * + * ```php + * $result = $image->crop(100, 100, 10, 1)->writeToArray(); + * ``` + * + * This will crop out 10 pixels and write them to the array. If `$image` + * is an RGB image, then `$array` will contain 30 numbers, with the first + * three being R, G and B for the first pixel. + * + * You'll need to slice and repack the array if you want more dimensions. + * + * This method is much faster than repeatedly calling `getpoint()`. It + * will use a lot of memory. + * + * @throws Exception + * + * @return array The pixel values as a PHP array. + */ + public function writeToArray(): array + { + Utils::debugLog('writeToArray', [ + 'instance' => $this, + 'arguments' => [] + ]); + + $result = vips_image_write_to_array($this->image); + if ($result === -1) { + self::errorVips(); + } + + Utils::debugLog('writeToArray', ['result' => $result]); + + return $result; + } + /** * Copy to memory. * From 3a812dcac5feab9554380678d15e91ef3bce3b17 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 28 Aug 2020 02:43:51 +0100 Subject: [PATCH 013/115] add setType and typeFromName so we can set arbitrary metadata --- CHANGELOG.md | 18 ++++++++++++++++++ RELEASE-1.0.5 => RELEASE-1.0.6 | 0 examples/generate_phpdoc.py | 2 +- src/Image.php | 24 ++++++++++++++++++++++++ src/Utils.php | 15 ++++++++++++++- 5 files changed, 57 insertions(+), 2 deletions(-) rename RELEASE-1.0.5 => RELEASE-1.0.6 (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 797e22d..acbfda6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,24 @@ # Changelog All notable changes to `:vips` will be documented in this file. +## 1.0.6 - 2020-08-28 + +### Added +- Image::setType() +- Utils::typeFromName() + +### Deprecated +- Nothing + +### Fixed +- Nothing + +### Remove +- Nothing + +### Security +- Nothing + ## 1.0.5 - 2019-09-26 ### Added diff --git a/RELEASE-1.0.5 b/RELEASE-1.0.6 similarity index 100% rename from RELEASE-1.0.5 rename to RELEASE-1.0.6 diff --git a/examples/generate_phpdoc.py b/examples/generate_phpdoc.py index 11c7b05..d822f58 100755 --- a/examples/generate_phpdoc.py +++ b/examples/generate_phpdoc.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/python3 from pyvips import Image, Operation, GValue, Error, \ ffi, values_for_enum, vips_lib, gobject_lib, \ diff --git a/src/Image.php b/src/Image.php index 357a981..4fa7c8e 100644 --- a/src/Image.php +++ b/src/Image.php @@ -1383,6 +1383,30 @@ public function set(string $name, $value) } } + /** + * Set the type and value for any property on the underlying image. + * + * This is useful if the type of the property cannot be determined from the + * php type of the value. + * + * Use Utils::typefromName() to look up types by name. + * + * @param int $type The type of the property. + * @param string $name The property name. + * @param mixed $value The value to set for this property. + * + * @throws Exception + * + * @return void + */ + public function setType(int $type, string $name, $value) + { + $result = vips_image_set_type($this->image, $type, $name, $value); + if ($result === -1) { + self::errorVips(); + } + } + /** * Remove a field from the underlying image. * diff --git a/src/Utils.php b/src/Utils.php index 0e1d20b..a122e31 100644 --- a/src/Utils.php +++ b/src/Utils.php @@ -41,7 +41,7 @@ use Psr\Log\LoggerInterface; /** - * Various utilities. For now, just loggers. + * Various utilities. * * @category Images * @package Jcupitt\Vips @@ -83,6 +83,19 @@ public static function errorLog(string $message, \Exception $exception) $logger->error($message, ['exception' => $exception]); } } + + /** + * Look up the GTyoe from a type name. If the type does not exist, + * return 0. + * + * @param string $name The type name. + * + * @return int + */ + public static function typeFromName(string $name) + { + return vips_type_from_name($name); + } } /* From 1ea7d0e66c29557f4e9b9fb03af412d8d4fd02bd Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 28 Aug 2020 03:04:31 +0100 Subject: [PATCH 014/115] update autodocs --- CHANGELOG.md | 1 + examples/generate_phpdoc.py | 70 ++--- src/ForeignDzContainer.php | 1 + src/ForeignDzLayout.php | 1 + src/ForeignHeifCompression.php | 56 ++++ src/ForeignJpegSubsample.php | 55 ++++ src/ForeignTiffCompression.php | 2 + src/GsfOutputCsvQuotingMode.php | 54 ++++ src/ImageAutodoc.php | 446 ++++++++++++++++++-------------- src/Interesting.php | 3 + src/RegionShrink.php | 3 + 11 files changed, 454 insertions(+), 238 deletions(-) create mode 100644 src/ForeignHeifCompression.php create mode 100644 src/ForeignJpegSubsample.php create mode 100644 src/GsfOutputCsvQuotingMode.php diff --git a/CHANGELOG.md b/CHANGELOG.md index acbfda6..4dc6cbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to `:vips` will be documented in this file. ### Added - Image::setType() - Utils::typeFromName() +- Updated autodocs for libvips 8.10 ### Deprecated - Nothing diff --git a/examples/generate_phpdoc.py b/examples/generate_phpdoc.py index d822f58..d617893 100755 --- a/examples/generate_phpdoc.py +++ b/examples/generate_phpdoc.py @@ -1,6 +1,6 @@ #!/usr/bin/python3 -from pyvips import Image, Operation, GValue, Error, \ +from pyvips import Image, Introspect, GValue, Error, \ ffi, values_for_enum, vips_lib, gobject_lib, \ type_map, type_name, type_from_name, nickname_find @@ -95,71 +95,47 @@ def remove_prefix(enum_str): def generate_operation(operation_name): - op = Operation.new_from_name(operation_name) - - # we are only interested in non-deprecated args - args = [[name, flags] for name, flags in op.get_args() - if not flags & _DEPRECATED] - - # find the first required input image arg, if any ... that will be self - member_x = None - for name, flags in args: - if ((flags & _INPUT) != 0 and - (flags & _REQUIRED) != 0 and - op.get_typeof(name) == GValue.image_type): - member_x = name - break - - required_input = [name for name, flags in args - if (flags & _INPUT) != 0 and - (flags & _REQUIRED) != 0 and - name != member_x] - - required_output = [name for name, flags in args - if ((flags & _OUTPUT) != 0 and - (flags & _REQUIRED) != 0) or - ((flags & _INPUT) != 0 and - (flags & _REQUIRED) != 0 and - (flags & _MODIFY) != 0)] + intro = Introspect.get(operation_name) result = ' * @method ' - if member_x is None: + if intro.member_x is None: result += 'static ' - if len(required_output) == 0: + if len(intro.required_output) == 0: result += 'void ' - elif len(required_output) == 1: - result += '{0} '.format(gtype_to_php(op.get_typeof(required_output[0]), True)) + elif len(intro.required_output) == 1: + arg = intro.required_output[0] + details = intro.details[arg] + result += '{0} '.format(gtype_to_php(details['type'], True)) else: # we generate a Returns: block for this case, see below result += 'array ' result += '{0}('.format(operation_name) - for name in required_input: - gtype = op.get_typeof(name) - result += '{0} ${1}, '.format(gtype_to_php(gtype), name) + for name in intro.required_input: + details = intro.details[name] + result += '{0} ${1}, '.format(gtype_to_php(details['type']), name) result += 'array $options = []) ' - description = op.get_description() + description = intro.description result += description[0].upper() + description[1:] + '.\n' # find any Enums we've referenced and output @see lines for them - for name in required_output + required_input: - gtype = op.get_typeof(name) - fundamental = gobject_lib.g_type_fundamental(gtype) + for name in intro.required_output + intro.required_input: + details = intro.details[name] + fundamental = gobject_lib.g_type_fundamental(details['type']) if fundamental != GValue.genum_type: continue - result += ' * @see {0} for possible values for ${1}\n'.format(remove_prefix(type_name(gtype)), name) + result += ' * @see {0} for possible values for ${1}\n'.format(remove_prefix(type_name(details['type'])), name) - if len(required_output) > 1: + if len(intro.required_output) > 1: result += ' * Return array with: [\n' - for name in required_output: - gtype = op.get_typeof(name) - blurb = op.get_blurb(name) - result += ' * \'{0}\' => @type {1} {2}\n'.format(name, gtype_to_php(gtype), - blurb[0].upper() + blurb[1:]) + for name in intro.required_output: + details = intro.details[name] + result += ' * \'{0}\' => @type {1} {2}\n'.format(name, gtype_to_php(details['type']), + details['blurb'][0].upper() + details['blurb'][1:]) result += ' * ];\n' result += ' * @throws Exception\n' @@ -222,10 +198,10 @@ def add_nickname(gtype, a, b): nickname = nickname_find(gtype) try: # can fail for abstract types - op = Operation.new_from_name(nickname) + intro = Introspect.get(nickname) # we are only interested in non-deprecated operations - if (op.get_flags() & _OPERATION_DEPRECATED) == 0: + if (intro.flags & _OPERATION_DEPRECATED) == 0: all_nicknames.append(nickname) except Error: pass diff --git a/src/ForeignDzContainer.php b/src/ForeignDzContainer.php index 5ba8d34..054b853 100644 --- a/src/ForeignDzContainer.php +++ b/src/ForeignDzContainer.php @@ -51,4 +51,5 @@ abstract class ForeignDzContainer { const FS = 'fs'; const ZIP = 'zip'; + const SZI = 'szi'; } diff --git a/src/ForeignDzLayout.php b/src/ForeignDzLayout.php index c06e3c7..021d75f 100644 --- a/src/ForeignDzLayout.php +++ b/src/ForeignDzLayout.php @@ -52,4 +52,5 @@ abstract class ForeignDzLayout const DZ = 'dz'; const ZOOMIFY = 'zoomify'; const GOOGLE = 'google'; + const IIIF = 'iiif'; } diff --git a/src/ForeignHeifCompression.php b/src/ForeignHeifCompression.php new file mode 100644 index 0000000..926b1ab --- /dev/null +++ b/src/ForeignHeifCompression.php @@ -0,0 +1,56 @@ + + * @copyright 2016 John Cupitt + * @license https://opensource.org/licenses/MIT MIT + * @link https://github.com/jcupitt/php-vips + */ + +namespace Jcupitt\Vips; + +/** + * The ForeignHeifCompression 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 ForeignHeifCompression +{ + const HEVC = 'hevc'; + const AVC = 'avc'; + const JPEG = 'jpeg'; + const AV1 = 'av1'; +} diff --git a/src/ForeignJpegSubsample.php b/src/ForeignJpegSubsample.php new file mode 100644 index 0000000..f87bfa7 --- /dev/null +++ b/src/ForeignJpegSubsample.php @@ -0,0 +1,55 @@ + + * @copyright 2016 John Cupitt + * @license https://opensource.org/licenses/MIT MIT + * @link https://github.com/jcupitt/php-vips + */ + +namespace Jcupitt\Vips; + +/** + * The ForeignJpegSubsample 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 ForeignJpegSubsample +{ + const AUTO = 'auto'; + const ON = 'on'; + const OFF = 'off'; +} diff --git a/src/ForeignTiffCompression.php b/src/ForeignTiffCompression.php index 6354e6e..0d5a6b0 100644 --- a/src/ForeignTiffCompression.php +++ b/src/ForeignTiffCompression.php @@ -55,4 +55,6 @@ abstract class ForeignTiffCompression const PACKBITS = 'packbits'; const CCITTFAX4 = 'ccittfax4'; const LZW = 'lzw'; + const WEBP = 'webp'; + const ZSTD = 'zstd'; } 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 7617c1e..0aef9c1 100644 --- a/src/ImageAutodoc.php +++ b/src/ImageAutodoc.php @@ -47,167 +47,177 @@ * @license https://opensource.org/licenses/MIT MIT * @link https://github.com/jcupitt/php-vips * - * @method Image CMC2LCh(array $options = []) Transform LCh to CMC. + * @method Image CMC2LCh(Image $in, array $options = []) Transform LCh to CMC. * @throws Exception - * @method Image HSV2sRGB(array $options = []) Transform HSV to sRGB. + * @method Image CMYK2XYZ(Image $in, array $options = []) Transform CMYK to XYZ. * @throws Exception - * @method Image LCh2CMC(array $options = []) Transform LCh to CMC. + * @method Image HSV2sRGB(Image $in, array $options = []) Transform HSV to sRGB. * @throws Exception - * @method Image LCh2Lab(array $options = []) Transform LCh to Lab. + * @method Image LCh2CMC(Image $in, array $options = []) Transform LCh to CMC. * @throws Exception - * @method Image Lab2LCh(array $options = []) Transform Lab to LCh. + * @method Image LCh2Lab(Image $in, array $options = []) Transform LCh to Lab. * @throws Exception - * @method Image Lab2LabQ(array $options = []) Transform float Lab to LabQ coding. + * @method Image Lab2LCh(Image $in, array $options = []) Transform Lab to LCh. * @throws Exception - * @method Image Lab2LabS(array $options = []) Transform float Lab to signed short. + * @method Image Lab2LabQ(Image $in, array $options = []) Transform float Lab to LabQ coding. * @throws Exception - * @method Image Lab2XYZ(array $options = []) Transform CIELAB to XYZ. + * @method Image Lab2LabS(Image $in, array $options = []) Transform float Lab to signed short. * @throws Exception - * @method Image LabQ2Lab(array $options = []) Unpack a LabQ image to float Lab. + * @method Image Lab2XYZ(Image $in, array $options = []) Transform CIELAB to XYZ. * @throws Exception - * @method Image LabQ2LabS(array $options = []) Unpack a LabQ image to short Lab. + * @method Image LabQ2Lab(Image $in, array $options = []) Unpack a LabQ image to float Lab. * @throws Exception - * @method Image LabQ2sRGB(array $options = []) Convert a LabQ image to sRGB. + * @method Image LabQ2LabS(Image $in, array $options = []) Unpack a LabQ image to short Lab. * @throws Exception - * @method Image LabS2Lab(array $options = []) Transform signed short Lab to float. + * @method Image LabQ2sRGB(Image $in, array $options = []) Convert a LabQ image to sRGB. * @throws Exception - * @method Image LabS2LabQ(array $options = []) Transform short Lab to LabQ coding. + * @method Image LabS2Lab(Image $in, array $options = []) Transform signed short Lab to float. * @throws Exception - * @method Image XYZ2Lab(array $options = []) Transform XYZ to Lab. + * @method Image LabS2LabQ(Image $in, array $options = []) Transform short Lab to LabQ coding. * @throws Exception - * @method Image XYZ2Yxy(array $options = []) Transform XYZ to Yxy. + * @method Image XYZ2CMYK(Image $in, array $options = []) Transform XYZ to CMYK. * @throws Exception - * @method Image XYZ2scRGB(array $options = []) Transform XYZ to scRGB. + * @method Image XYZ2Lab(Image $in, array $options = []) Transform XYZ to Lab. * @throws Exception - * @method Image Yxy2XYZ(array $options = []) Transform Yxy to XYZ. + * @method Image XYZ2Yxy(Image $in, array $options = []) Transform XYZ to Yxy. * @throws Exception - * @method Image abs(array $options = []) Absolute value of an image. + * @method Image XYZ2scRGB(Image $in, array $options = []) Transform XYZ to scRGB. * @throws Exception - * @method Image affine(float[]|float $matrix, array $options = []) Affine transform of an image. + * @method Image Yxy2XYZ(Image $in, array $options = []) Transform Yxy to XYZ. + * @throws Exception + * @method Image abs(Image $in, array $options = []) Absolute value of an image. + * @throws Exception + * @method Image affine(Image $in, float[]|float $matrix, array $options = []) Affine transform of an image. * @throws Exception * @method static Image analyzeload(string $filename, array $options = []) Load an Analyze6 image. * @throws Exception * @method static Image arrayjoin(Image[]|Image $in, array $options = []) Join an array of images. * @throws Exception - * @method Image autorot(array $options = []) Autorotate image by exif tag. + * @method Image autorot(Image $in, array $options = []) Autorotate image by exif tag. * @throws Exception - * @method float avg(array $options = []) Find image average. + * @method float avg(Image $in, array $options = []) Find image average. * @throws Exception - * @method Image bandbool(string $boolean, array $options = []) Boolean operation across image bands. + * @method Image bandbool(Image $in, string $boolean, array $options = []) Boolean operation across image bands. * @see OperationBoolean for possible values for $boolean * @throws Exception - * @method Image bandfold(array $options = []) Fold up x axis into bands. + * @method Image bandfold(Image $in, array $options = []) Fold up x axis into bands. * @throws Exception - * @method Image bandjoin_const(float[]|float $c, array $options = []) Append a constant band to an image. + * @method Image bandjoin_const(Image $in, float[]|float $c, array $options = []) Append a constant band to an image. * @throws Exception - * @method Image bandmean(array $options = []) Band-wise average. + * @method Image bandmean(Image $in, array $options = []) Band-wise average. * @throws Exception - * @method Image bandunfold(array $options = []) Unfold image bands into x axis. + * @method Image bandunfold(Image $in, array $options = []) Unfold image bands into x axis. * @throws Exception * @method static Image black(integer $width, integer $height, array $options = []) Make a black image. * @throws Exception - * @method Image boolean(Image $right, string $boolean, array $options = []) Boolean operation on two images. + * @method Image boolean(Image $left, Image $right, string $boolean, array $options = []) Boolean operation on two images. * @see OperationBoolean for possible values for $boolean * @throws Exception - * @method Image boolean_const(string $boolean, float[]|float $c, array $options = []) Boolean operations against a constant. + * @method Image boolean_const(Image $in, string $boolean, float[]|float $c, array $options = []) Boolean operations against a constant. * @see OperationBoolean for possible values for $boolean * @throws Exception - * @method Image buildlut(array $options = []) Build a look-up table. + * @method Image buildlut(Image $in, array $options = []) Build a look-up table. * @throws Exception - * @method Image byteswap(array $options = []) Byteswap an image. + * @method Image byteswap(Image $in, array $options = []) Byteswap an image. * @throws Exception - * @method Image cache(array $options = []) Cache an image. + * @method Image cache(Image $in, array $options = []) Cache an image. * @throws Exception - * @method Image canny(array $options = []) Canny edge detector. + * @method Image canny(Image $in, array $options = []) Canny edge detector. * @throws Exception - * @method Image cast(string $format, array $options = []) Cast an image. + * @method Image case(Image $index, Image[]|Image $cases, array $options = []) Use pixel values to pick cases from an array of images. + * @throws Exception + * @method Image cast(Image $in, string $format, array $options = []) Cast an image. * @see BandFormat for possible values for $format * @throws Exception - * @method Image colourspace(string $space, array $options = []) Convert to a new colorspace. + * @method Image colourspace(Image $in, string $space, array $options = []) Convert to a new colorspace. * @see Interpretation for possible values for $space * @throws Exception - * @method Image compass(Image $mask, array $options = []) Convolve with rotating mask. + * @method Image compass(Image $in, Image $mask, array $options = []) Convolve with rotating mask. * @throws Exception - * @method Image complex(string $cmplx, array $options = []) Perform a complex operation on an image. + * @method Image complex(Image $in, string $cmplx, array $options = []) Perform a complex operation on an image. * @see OperationComplex for possible values for $cmplx * @throws Exception - * @method Image complex2(Image $right, string $cmplx, array $options = []) Complex binary operations on two images. + * @method Image complex2(Image $left, Image $right, string $cmplx, array $options = []) Complex binary operations on two images. * @see OperationComplex2 for possible values for $cmplx * @throws Exception - * @method Image complexform(Image $right, array $options = []) Form a complex image from two real images. + * @method Image complexform(Image $left, Image $right, array $options = []) Form a complex image from two real images. * @throws Exception - * @method Image complexget(string $get, array $options = []) Get a component from a complex image. + * @method Image complexget(Image $in, string $get, array $options = []) Get a component from a complex image. * @see OperationComplexget for possible values for $get * @throws Exception * @method static Image composite(Image[]|Image $in, integer[]|integer $mode, array $options = []) Blend an array of images with an array of blend modes. * @throws Exception - * @method Image composite2(Image $overlay, string $mode, array $options = []) Blend a pair of images with a blend mode. + * @method Image composite2(Image $base, Image $overlay, string $mode, array $options = []) Blend a pair of images with a blend mode. * @see BlendMode for possible values for $mode * @throws Exception - * @method Image conv(Image $mask, array $options = []) Convolution operation. + * @method Image conv(Image $in, Image $mask, array $options = []) Convolution operation. * @throws Exception - * @method Image conva(Image $mask, array $options = []) Approximate integer convolution. + * @method Image conva(Image $in, Image $mask, array $options = []) Approximate integer convolution. * @throws Exception - * @method Image convasep(Image $mask, array $options = []) Approximate separable integer convolution. + * @method Image convasep(Image $in, Image $mask, array $options = []) Approximate separable integer convolution. * @throws Exception - * @method Image convf(Image $mask, array $options = []) Float convolution operation. + * @method Image convf(Image $in, Image $mask, array $options = []) Float convolution operation. * @throws Exception - * @method Image convi(Image $mask, array $options = []) Int convolution operation. + * @method Image convi(Image $in, Image $mask, array $options = []) Int convolution operation. * @throws Exception - * @method Image convsep(Image $mask, array $options = []) Seperable convolution operation. + * @method Image convsep(Image $in, Image $mask, array $options = []) Seperable convolution operation. * @throws Exception - * @method Image copy(array $options = []) Copy an image. + * @method Image copy(Image $in, array $options = []) Copy an image. * @throws Exception - * @method float countlines(string $direction, array $options = []) Count lines in an image. + * @method float countlines(Image $in, string $direction, array $options = []) Count lines in an image. * @see Direction for possible values for $direction * @throws Exception - * @method Image crop(integer $left, integer $top, integer $width, integer $height, array $options = []) Extract an area from an image. + * @method Image crop(Image $input, integer $left, integer $top, integer $width, integer $height, array $options = []) Extract an area from an image. + * @throws Exception + * @method static Image csvload(string $filename, array $options = []) Load csv. * @throws Exception - * @method static Image csvload(string $filename, array $options = []) Load csv from file. + * @method static Image csvload_source(string $source, array $options = []) Load csv. * @throws Exception - * @method void csvsave(string $filename, array $options = []) Save image to csv file. + * @method void csvsave(Image $in, string $filename, array $options = []) Save image to csv. * @throws Exception - * @method Image dE00(Image $right, array $options = []) Calculate dE00. + * @method void csvsave_target(Image $in, string $target, array $options = []) Save image to csv. * @throws Exception - * @method Image dE76(Image $right, array $options = []) Calculate dE76. + * @method Image dE00(Image $left, Image $right, array $options = []) Calculate dE00. * @throws Exception - * @method Image dECMC(Image $right, array $options = []) Calculate dECMC. + * @method Image dE76(Image $left, Image $right, array $options = []) Calculate dE76. * @throws Exception - * @method float deviate(array $options = []) Find image standard deviation. + * @method Image dECMC(Image $left, Image $right, array $options = []) Calculate dECMC. * @throws Exception - * @method Image draw_circle(float[]|float $ink, integer $cx, integer $cy, integer $radius, array $options = []) Draw a circle on an image. + * @method float deviate(Image $in, array $options = []) Find image standard deviation. * @throws Exception - * @method Image draw_flood(float[]|float $ink, integer $x, integer $y, array $options = []) Flood-fill an area. + * @method Image draw_circle(Image $image, float[]|float $ink, integer $cx, integer $cy, integer $radius, array $options = []) Draw a circle on an image. * @throws Exception - * @method Image draw_image(Image $sub, integer $x, integer $y, array $options = []) Paint an image into another image. + * @method Image draw_flood(Image $image, float[]|float $ink, integer $x, integer $y, array $options = []) Flood-fill an area. * @throws Exception - * @method Image draw_line(float[]|float $ink, integer $x1, integer $y1, integer $x2, integer $y2, array $options = []) Draw a line on an image. + * @method Image draw_image(Image $image, Image $sub, integer $x, integer $y, array $options = []) Paint an image into another image. * @throws Exception - * @method Image draw_mask(float[]|float $ink, Image $mask, integer $x, integer $y, array $options = []) Draw a mask on an image. + * @method Image draw_line(Image $image, float[]|float $ink, integer $x1, integer $y1, integer $x2, integer $y2, array $options = []) Draw a line on an image. * @throws Exception - * @method Image draw_rect(float[]|float $ink, integer $left, integer $top, integer $width, integer $height, array $options = []) Paint a rectangle on an image. + * @method Image draw_mask(Image $image, float[]|float $ink, Image $mask, integer $x, integer $y, array $options = []) Draw a mask on an image. * @throws Exception - * @method Image draw_smudge(integer $left, integer $top, integer $width, integer $height, array $options = []) Blur a rectangle on an image. + * @method Image draw_rect(Image $image, float[]|float $ink, integer $left, integer $top, integer $width, integer $height, array $options = []) Paint a rectangle on an image. * @throws Exception - * @method void dzsave(string $filename, array $options = []) Save image to deepzoom file. + * @method Image draw_smudge(Image $image, integer $left, integer $top, integer $width, integer $height, array $options = []) Blur a rectangle on an image. * @throws Exception - * @method string dzsave_buffer(array $options = []) Save image to dz buffer. + * @method void dzsave(Image $in, string $filename, array $options = []) Save image to deepzoom file. * @throws Exception - * @method Image embed(integer $x, integer $y, integer $width, integer $height, array $options = []) Embed an image in a larger image. + * @method string dzsave_buffer(Image $in, array $options = []) Save image to dz buffer. * @throws Exception - * @method Image extract_area(integer $left, integer $top, integer $width, integer $height, array $options = []) Extract an area from an image. + * @method Image embed(Image $in, integer $x, integer $y, integer $width, integer $height, array $options = []) Embed an image in a larger image. * @throws Exception - * @method Image extract_band(integer $band, array $options = []) Extract band from an image. + * @method Image extract_area(Image $input, integer $left, integer $top, integer $width, integer $height, array $options = []) Extract an area from an image. + * @throws Exception + * @method Image extract_band(Image $in, integer $band, array $options = []) Extract band from an image. * @throws Exception * @method static Image eye(integer $width, integer $height, array $options = []) Make an image showing the eye's spatial response. * @throws Exception - * @method Image falsecolour(array $options = []) False-color an image. + * @method Image falsecolour(Image $in, array $options = []) False-color an image. * @throws Exception - * @method Image fastcor(Image $ref, array $options = []) Fast correlation. + * @method Image fastcor(Image $in, Image $ref, array $options = []) Fast correlation. * @throws Exception - * @method Image fill_nearest(array $options = []) Fill image zeros with nearest non-zero pixel. + * @method Image fill_nearest(Image $in, array $options = []) Fill image zeros with nearest non-zero pixel. * @throws Exception - * @method array find_trim(array $options = []) Search an image for non-edge areas. + * @method array find_trim(Image $in, array $options = []) Search an image for non-edge areas. * Return array with: [ * 'left' => @type integer Left edge of image * 'top' => @type integer Top edge of extract area @@ -217,104 +227,122 @@ * @throws Exception * @method static Image fitsload(string $filename, array $options = []) Load a FITS image. * @throws Exception - * @method void fitssave(string $filename, array $options = []) Save image to fits file. + * @method void fitssave(Image $in, string $filename, array $options = []) Save image to fits file. * @throws Exception - * @method Image flatten(array $options = []) Flatten alpha out of an image. + * @method Image flatten(Image $in, array $options = []) Flatten alpha out of an image. * @throws Exception - * @method Image flip(string $direction, array $options = []) Flip an image. + * @method Image flip(Image $in, string $direction, array $options = []) Flip an image. * @see Direction for possible values for $direction * @throws Exception - * @method Image float2rad(array $options = []) Transform float RGB to Radiance coding. + * @method Image float2rad(Image $in, array $options = []) Transform float RGB to Radiance coding. * @throws Exception * @method static Image fractsurf(integer $width, integer $height, float $fractal_dimension, array $options = []) Make a fractal surface. * @throws Exception - * @method Image freqmult(Image $mask, array $options = []) Frequency-domain filtering. + * @method Image freqmult(Image $in, Image $mask, array $options = []) Frequency-domain filtering. * @throws Exception - * @method Image fwfft(array $options = []) Forward FFT. + * @method Image fwfft(Image $in, array $options = []) Forward FFT. * @throws Exception - * @method Image gamma(array $options = []) Gamma an image. + * @method Image gamma(Image $in, array $options = []) Gamma an image. * @throws Exception - * @method Image gaussblur(float $sigma, array $options = []) Gaussian blur. + * @method Image gaussblur(Image $in, float $sigma, array $options = []) Gaussian blur. * @throws Exception * @method static Image gaussmat(float $sigma, float $min_ampl, array $options = []) Make a gaussian image. * @throws Exception * @method static Image gaussnoise(integer $width, integer $height, array $options = []) Make a gaussnoise image. * @throws Exception - * @method array getpoint(integer $x, integer $y, array $options = []) Read a point from an image. + * @method array getpoint(Image $in, integer $x, integer $y, array $options = []) Read a point from an image. * @throws Exception * @method static Image gifload(string $filename, array $options = []) Load GIF with giflib. * @throws Exception * @method static Image gifload_buffer(string $buffer, array $options = []) Load GIF with giflib. * @throws Exception - * @method Image globalbalance(array $options = []) Global balance an image mosaic. + * @method static Image gifload_source(string $source, array $options = []) Load GIF with giflib. + * @throws Exception + * @method Image globalbalance(Image $in, array $options = []) Global balance an image mosaic. * @throws Exception - * @method Image gravity(string $direction, integer $width, integer $height, array $options = []) Place an image within a larger image with a certain gravity. + * @method Image gravity(Image $in, string $direction, integer $width, integer $height, array $options = []) Place an image within a larger image with a certain gravity. * @see CompassDirection for possible values for $direction * @throws Exception * @method static Image grey(integer $width, integer $height, array $options = []) Make a grey ramp image. * @throws Exception - * @method Image grid(integer $tile_height, integer $across, integer $down, array $options = []) Grid an image. + * @method Image grid(Image $in, integer $tile_height, integer $across, integer $down, array $options = []) Grid an image. + * @throws Exception + * @method static Image heifload(string $filename, array $options = []) Load a HEIF image. + * @throws Exception + * @method static Image heifload_buffer(string $buffer, array $options = []) Load a HEIF image. * @throws Exception - * @method Image hist_cum(array $options = []) Form cumulative histogram. + * @method static Image heifload_source(string $source, array $options = []) Load a HEIF image. * @throws Exception - * @method float hist_entropy(array $options = []) Estimate image entropy. + * @method void heifsave(Image $in, string $filename, array $options = []) Save image in HEIF format. * @throws Exception - * @method Image hist_equal(array $options = []) Histogram equalisation. + * @method string heifsave_buffer(Image $in, array $options = []) Save image in HEIF format. * @throws Exception - * @method Image hist_find(array $options = []) Find image histogram. + * @method void heifsave_target(Image $in, string $target, array $options = []) Save image in HEIF format. * @throws Exception - * @method Image hist_find_indexed(Image $index, array $options = []) Find indexed image histogram. + * @method Image hist_cum(Image $in, array $options = []) Form cumulative histogram. * @throws Exception - * @method Image hist_find_ndim(array $options = []) Find n-dimensional image histogram. + * @method float hist_entropy(Image $in, array $options = []) Estimate image entropy. * @throws Exception - * @method bool hist_ismonotonic(array $options = []) Test for monotonicity. + * @method Image hist_equal(Image $in, array $options = []) Histogram equalisation. * @throws Exception - * @method Image hist_local(integer $width, integer $height, array $options = []) Local histogram equalisation. + * @method Image hist_find(Image $in, array $options = []) Find image histogram. * @throws Exception - * @method Image hist_match(Image $ref, array $options = []) Match two histograms. + * @method Image hist_find_indexed(Image $in, Image $index, array $options = []) Find indexed image histogram. * @throws Exception - * @method Image hist_norm(array $options = []) Normalise histogram. + * @method Image hist_find_ndim(Image $in, array $options = []) Find n-dimensional image histogram. * @throws Exception - * @method Image hist_plot(array $options = []) Plot histogram. + * @method bool hist_ismonotonic(Image $in, array $options = []) Test for monotonicity. * @throws Exception - * @method Image hough_circle(array $options = []) Find hough circle transform. + * @method Image hist_local(Image $in, integer $width, integer $height, array $options = []) Local histogram equalisation. * @throws Exception - * @method Image hough_line(array $options = []) Find hough line transform. + * @method Image hist_match(Image $in, Image $ref, array $options = []) Match two histograms. * @throws Exception - * @method Image icc_export(array $options = []) Output to device with ICC profile. + * @method Image hist_norm(Image $in, array $options = []) Normalise histogram. * @throws Exception - * @method Image icc_import(array $options = []) Import from device with ICC profile. + * @method Image hist_plot(Image $in, array $options = []) Plot histogram. * @throws Exception - * @method Image icc_transform(string $output_profile, array $options = []) Transform between devices with ICC profiles. + * @method Image hough_circle(Image $in, array $options = []) Find hough circle transform. + * @throws Exception + * @method Image hough_line(Image $in, array $options = []) Find hough line transform. + * @throws Exception + * @method Image icc_export(Image $in, array $options = []) Output to device with ICC profile. + * @throws Exception + * @method Image icc_import(Image $in, array $options = []) Import from device with ICC profile. + * @throws Exception + * @method Image icc_transform(Image $in, string $output_profile, array $options = []) Transform between devices with ICC profiles. * @throws Exception * @method static Image identity(array $options = []) Make a 1D image where pixel values are indexes. * @throws Exception - * @method Image insert(Image $sub, integer $x, integer $y, array $options = []) Insert image @sub into @main at @x, @y. + * @method Image insert(Image $main, Image $sub, integer $x, integer $y, array $options = []) Insert image @sub into @main at @x, @y. * @throws Exception - * @method Image invert(array $options = []) Invert an image. + * @method Image invert(Image $in, array $options = []) Invert an image. * @throws Exception - * @method Image invertlut(array $options = []) Build an inverted look-up table. + * @method Image invertlut(Image $in, array $options = []) Build an inverted look-up table. * @throws Exception - * @method Image invfft(array $options = []) Inverse FFT. + * @method Image invfft(Image $in, array $options = []) Inverse FFT. * @throws Exception - * @method Image join(Image $in2, string $direction, array $options = []) Join a pair of images. + * @method Image join(Image $in1, Image $in2, string $direction, array $options = []) Join a pair of images. * @see Direction for possible values for $direction * @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 void jpegsave(string $filename, array $options = []) Save image to jpeg file. + * @method static Image jpegload_source(string $source, array $options = []) Load image from jpeg source. * @throws Exception - * @method string jpegsave_buffer(array $options = []) Save image to jpeg buffer. + * @method void jpegsave(Image $in, string $filename, array $options = []) Save image to jpeg file. * @throws Exception - * @method void jpegsave_mime(array $options = []) Save image to jpeg mime. + * @method string jpegsave_buffer(Image $in, array $options = []) Save image to jpeg buffer. * @throws Exception - * @method Image labelregions(array $options = []) Label regions in an image. + * @method void jpegsave_mime(Image $in, array $options = []) Save image to jpeg mime. * @throws Exception - * @method Image linear(float[]|float $a, float[]|float $b, array $options = []) Calculate (a * in + b). + * @method void jpegsave_target(Image $in, string $target, array $options = []) Save image to jpeg target. * @throws Exception - * @method Image linecache(array $options = []) Cache an image as a set of lines. + * @method Image labelregions(Image $in, array $options = []) Label regions in an image. + * @throws Exception + * @method Image linear(Image $in, float[]|float $a, float[]|float $b, array $options = []) Calculate (a * in + b). + * @throws Exception + * @method Image linecache(Image $in, 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. * @throws Exception @@ -322,13 +350,13 @@ * @throws Exception * @method static Image magickload_buffer(string $buffer, array $options = []) Load buffer with ImageMagick. * @throws Exception - * @method void magicksave(string $filename, array $options = []) Save file with ImageMagick. + * @method void magicksave(Image $in, string $filename, array $options = []) Save file with ImageMagick. * @throws Exception - * @method string magicksave_buffer(array $options = []) Save image to magick buffer. + * @method string magicksave_buffer(Image $in, array $options = []) Save image to magick buffer. * @throws Exception - * @method Image mapim(Image $index, array $options = []) Resample with an mapim image. + * @method Image mapim(Image $in, Image $index, array $options = []) Resample with a map image. * @throws Exception - * @method Image maplut(Image $lut, array $options = []) Map an image though a lut. + * @method Image maplut(Image $in, Image $lut, array $options = []) Map an image though a lut. * @throws Exception * @method static Image mask_butterworth(integer $width, integer $height, float $order, float $frequency_cutoff, float $amplitude_cutoff, array $options = []) Make a butterworth filter. * @throws Exception @@ -350,178 +378,202 @@ * @throws Exception * @method static Image mask_ideal_ring(integer $width, integer $height, float $frequency_cutoff, float $ringwidth, array $options = []) Make an ideal ring filter. * @throws Exception - * @method Image match(Image $sec, integer $xr1, integer $yr1, integer $xs1, integer $ys1, integer $xr2, integer $yr2, integer $xs2, integer $ys2, array $options = []) First-order match of two images. + * @method Image match(Image $ref, Image $sec, integer $xr1, integer $yr1, integer $xs1, integer $ys1, integer $xr2, integer $yr2, integer $xs2, integer $ys2, array $options = []) First-order match of two images. * @throws Exception - * @method Image math(string $math, array $options = []) Apply a math operation to an image. + * @method Image math(Image $in, string $math, array $options = []) Apply a math operation to an image. * @see OperationMath for possible values for $math * @throws Exception - * @method Image math2(Image $right, string $math2, array $options = []) Binary math operations. + * @method Image math2(Image $left, Image $right, string $math2, array $options = []) Binary math operations. * @see OperationMath2 for possible values for $math2 * @throws Exception - * @method Image math2_const(string $math2, float[]|float $c, array $options = []) Binary math operations with a constant. + * @method Image math2_const(Image $in, string $math2, float[]|float $c, array $options = []) Binary math operations with a constant. * @see OperationMath2 for possible values for $math2 * @throws Exception * @method static Image matload(string $filename, array $options = []) Load mat from file. * @throws Exception - * @method static Image matrixload(string $filename, array $options = []) Load matrix from file. + * @method Image matrixinvert(Image $in, array $options = []) Invert an matrix. + * @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. * @throws Exception - * @method void matrixprint(array $options = []) Print matrix. + * @method void matrixprint(Image $in, array $options = []) Print matrix. * @throws Exception - * @method void matrixsave(string $filename, array $options = []) Save image to matrix file. + * @method void matrixsave(Image $in, string $filename, array $options = []) Save image to matrix. * @throws Exception - * @method float max(array $options = []) Find image maximum. + * @method void matrixsave_target(Image $in, string $target, array $options = []) Save image to matrix. * @throws Exception - * @method Image measure(integer $h, integer $v, array $options = []) Measure a set of patches on a color chart. + * @method float max(Image $in, array $options = []) Find image maximum. * @throws Exception - * @method Image merge(Image $sec, string $direction, integer $dx, integer $dy, array $options = []) Merge two images. + * @method Image measure(Image $in, integer $h, integer $v, array $options = []) Measure a set of patches on a color chart. + * @throws Exception + * @method Image merge(Image $ref, Image $sec, string $direction, integer $dx, integer $dy, array $options = []) Merge two images. * @see Direction for possible values for $direction * @throws Exception - * @method float min(array $options = []) Find image minimum. + * @method float min(Image $in, array $options = []) Find image minimum. * @throws Exception - * @method Image morph(Image $mask, string $morph, array $options = []) Morphology operation. + * @method Image morph(Image $in, Image $mask, string $morph, array $options = []) Morphology operation. * @see OperationMorphology for possible values for $morph * @throws Exception - * @method Image mosaic(Image $sec, string $direction, integer $xref, integer $yref, integer $xsec, integer $ysec, array $options = []) Mosaic two images. + * @method Image mosaic(Image $ref, Image $sec, string $direction, integer $xref, integer $yref, integer $xsec, integer $ysec, array $options = []) Mosaic two images. * @see Direction for possible values for $direction * @throws Exception - * @method Image mosaic1(Image $sec, string $direction, integer $xr1, integer $yr1, integer $xs1, integer $ys1, integer $xr2, integer $yr2, integer $xs2, integer $ys2, array $options = []) First-order mosaic of two images. + * @method Image mosaic1(Image $ref, Image $sec, string $direction, integer $xr1, integer $yr1, integer $xs1, integer $ys1, integer $xr2, integer $yr2, integer $xs2, integer $ys2, array $options = []) First-order mosaic of two images. * @see Direction for possible values for $direction * @throws Exception - * @method Image msb(array $options = []) Pick most-significant byte from an image. + * @method Image msb(Image $in, array $options = []) Pick most-significant byte from an image. * @throws Exception * @method static Image niftiload(string $filename, array $options = []) Load a NIFTI image. * @throws Exception - * @method void niftisave(string $filename, array $options = []) Save image to nifti file. + * @method void niftisave(Image $in, 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 pdfload(string $filename, array $options = []) Load PDF with libpoppler. + * @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_buffer(string $buffer, array $options = []) Load PDF with libpoppler. + * @method static Image pdfload_source(string $source, array $options = []) Load PDF from source. * @throws Exception - * @method integer percent(float $percent, array $options = []) Find threshold for percent of pixels. + * @method integer percent(Image $in, float $percent, array $options = []) Find threshold for percent of pixels. * @throws Exception * @method static Image perlin(integer $width, integer $height, array $options = []) Make a perlin noise image. * @throws Exception - * @method Image phasecor(Image $in2, array $options = []) Calculate phase correlation. + * @method Image phasecor(Image $in, Image $in2, array $options = []) Calculate phase correlation. * @throws Exception * @method static Image pngload(string $filename, array $options = []) Load png from file. * @throws Exception * @method static Image pngload_buffer(string $buffer, array $options = []) Load png from buffer. * @throws Exception - * @method void pngsave(string $filename, array $options = []) Save image to png file. + * @method static Image pngload_source(string $source, array $options = []) Load png from source. + * @throws Exception + * @method void pngsave(Image $in, string $filename, array $options = []) Save image to png file. + * @throws Exception + * @method string pngsave_buffer(Image $in, array $options = []) Save image to png buffer. * @throws Exception - * @method string pngsave_buffer(array $options = []) Save image to png buffer. + * @method void pngsave_target(Image $in, string $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 void ppmsave(string $filename, array $options = []) Save image to ppm file. + * @method static Image ppmload_source(string $source, array $options = []) Load ppm base class. * @throws Exception - * @method Image premultiply(array $options = []) Premultiply image alpha. + * @method void ppmsave(Image $in, string $filename, array $options = []) Save image to ppm file. * @throws Exception - * @method array profile(array $options = []) Find image profiles. + * @method void ppmsave_target(Image $in, string $target, array $options = []) Save to ppm. + * @throws Exception + * @method Image premultiply(Image $in, array $options = []) Premultiply image alpha. + * @throws Exception + * @method array profile(Image $in, array $options = []) Find image profiles. * Return array with: [ * 'columns' => @type Image First non-zero pixel in column * 'rows' => @type Image First non-zero pixel in row * ]; * @throws Exception - * @method array project(array $options = []) Find image projections. + * @method static string profile_load(string $name, array $options = []) Load named ICC profile. + * @throws Exception + * @method array project(Image $in, array $options = []) Find image projections. * Return array with: [ * 'columns' => @type Image Sums of columns * 'rows' => @type Image Sums of rows * ]; * @throws Exception - * @method Image quadratic(Image $coeff, array $options = []) Resample an image with a quadratic transform. + * @method Image quadratic(Image $in, Image $coeff, array $options = []) Resample an image with a quadratic transform. * @throws Exception - * @method Image rad2float(array $options = []) Unpack Radiance coding to float RGB. + * @method Image rad2float(Image $in, array $options = []) Unpack Radiance coding to float RGB. * @throws Exception * @method static Image radload(string $filename, array $options = []) Load a Radiance image from a file. * @throws Exception - * @method void radsave(string $filename, array $options = []) Save image to Radiance file. + * @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. * @throws Exception - * @method string radsave_buffer(array $options = []) Save image to Radiance buffer. + * @method void radsave(Image $in, string $filename, array $options = []) Save image to Radiance file. * @throws Exception - * @method Image rank(integer $width, integer $height, integer $index, array $options = []) Rank filter. + * @method string radsave_buffer(Image $in, array $options = []) Save image to Radiance buffer. + * @throws Exception + * @method void radsave_target(Image $in, string $target, array $options = []) Save image to Radiance target. + * @throws Exception + * @method Image rank(Image $in, integer $width, integer $height, integer $index, array $options = []) Rank filter. * @throws Exception * @method static Image rawload(string $filename, integer $width, integer $height, integer $bands, array $options = []) Load raw data from a file. * @throws Exception - * @method void rawsave(string $filename, array $options = []) Save image to raw file. + * @method void rawsave(Image $in, string $filename, array $options = []) Save image to raw file. * @throws Exception - * @method void rawsave_fd(integer $fd, array $options = []) Write raw image to file descriptor. + * @method void rawsave_fd(Image $in, integer $fd, array $options = []) Write raw image to file descriptor. * @throws Exception - * @method Image recomb(Image $m, array $options = []) Linear recombination with matrix. + * @method Image recomb(Image $in, Image $m, array $options = []) Linear recombination with matrix. * @throws Exception - * @method Image reduce(float $hshrink, float $vshrink, array $options = []) Reduce an image. + * @method Image reduce(Image $in, float $hshrink, float $vshrink, array $options = []) Reduce an image. * @throws Exception - * @method Image reduceh(float $hshrink, array $options = []) Shrink an image horizontally. + * @method Image reduceh(Image $in, float $hshrink, array $options = []) Shrink an image horizontally. * @throws Exception - * @method Image reducev(float $vshrink, array $options = []) Shrink an image vertically. + * @method Image reducev(Image $in, float $vshrink, array $options = []) Shrink an image vertically. * @throws Exception - * @method Image relational(Image $right, string $relational, array $options = []) Relational operation on two images. + * @method Image relational(Image $left, Image $right, string $relational, array $options = []) Relational operation on two images. * @see OperationRelational for possible values for $relational * @throws Exception - * @method Image relational_const(string $relational, float[]|float $c, array $options = []) Relational operations against a constant. + * @method Image relational_const(Image $in, string $relational, float[]|float $c, array $options = []) Relational operations against a constant. * @see OperationRelational for possible values for $relational * @throws Exception - * @method Image remainder_const(float[]|float $c, array $options = []) Remainder after integer division of an image and a constant. + * @method Image remainder_const(Image $in, float[]|float $c, array $options = []) Remainder after integer division of an image and a constant. * @throws Exception - * @method Image replicate(integer $across, integer $down, array $options = []) Replicate an image. + * @method Image replicate(Image $in, integer $across, integer $down, array $options = []) Replicate an image. * @throws Exception - * @method Image resize(float $scale, array $options = []) Resize an image. + * @method Image resize(Image $in, float $scale, array $options = []) Resize an image. * @throws Exception - * @method Image rot(string $angle, array $options = []) Rotate an image. + * @method Image rot(Image $in, string $angle, array $options = []) Rotate an image. * @see Angle for possible values for $angle * @throws Exception - * @method Image rot45(array $options = []) Rotate an image. + * @method Image rot45(Image $in, array $options = []) Rotate an image. * @throws Exception - * @method Image rotate(float $angle, array $options = []) Rotate an image by a number of degrees. + * @method Image rotate(Image $in, float $angle, array $options = []) Rotate an image by a number of degrees. * @throws Exception - * @method Image round(string $round, array $options = []) Perform a round function on an image. + * @method Image round(Image $in, string $round, array $options = []) Perform a round function on an image. * @see OperationRound for possible values for $round * @throws Exception - * @method Image sRGB2HSV(array $options = []) Transform sRGB to HSV. + * @method Image sRGB2HSV(Image $in, array $options = []) Transform sRGB to HSV. * @throws Exception - * @method Image sRGB2scRGB(array $options = []) Convert an sRGB image to scRGB. + * @method Image sRGB2scRGB(Image $in, array $options = []) Convert an sRGB image to scRGB. * @throws Exception - * @method Image scRGB2BW(array $options = []) Convert scRGB to BW. + * @method Image scRGB2BW(Image $in, array $options = []) Convert scRGB to BW. * @throws Exception - * @method Image scRGB2XYZ(array $options = []) Transform scRGB to XYZ. + * @method Image scRGB2XYZ(Image $in, array $options = []) Transform scRGB to XYZ. * @throws Exception - * @method Image scRGB2sRGB(array $options = []) Convert an scRGB image to sRGB. + * @method Image scRGB2sRGB(Image $in, array $options = []) Convert an scRGB image to sRGB. * @throws Exception - * @method Image scale(array $options = []) Scale an image to uchar. + * @method Image scale(Image $in, array $options = []) Scale an image to uchar. * @throws Exception - * @method Image sequential(array $options = []) Check sequential access. + * @method Image sequential(Image $in, array $options = []) Check sequential access. * @throws Exception - * @method Image sharpen(array $options = []) Unsharp masking for print. + * @method Image sharpen(Image $in, array $options = []) Unsharp masking for print. * @throws Exception - * @method Image shrink(float $hshrink, float $vshrink, array $options = []) Shrink an image. + * @method Image shrink(Image $in, float $hshrink, float $vshrink, array $options = []) Shrink an image. * @throws Exception - * @method Image shrinkh(integer $hshrink, array $options = []) Shrink an image horizontally. + * @method Image shrinkh(Image $in, integer $hshrink, array $options = []) Shrink an image horizontally. * @throws Exception - * @method Image shrinkv(integer $vshrink, array $options = []) Shrink an image vertically. + * @method Image shrinkv(Image $in, integer $vshrink, array $options = []) Shrink an image vertically. * @throws Exception - * @method Image sign(array $options = []) Unit vector of pixel. + * @method Image sign(Image $in, array $options = []) Unit vector of pixel. * @throws Exception - * @method Image similarity(array $options = []) Similarity transform of an image. + * @method Image similarity(Image $in, array $options = []) Similarity transform of an image. * @throws Exception * @method static Image sines(integer $width, integer $height, array $options = []) Make a 2D sine wave. * @throws Exception - * @method Image smartcrop(integer $width, integer $height, array $options = []) Extract an area from an image. + * @method Image smartcrop(Image $input, integer $width, integer $height, array $options = []) Extract an area from an image. * @throws Exception - * @method Image sobel(array $options = []) Sobel edge detector. + * @method Image sobel(Image $in, array $options = []) Sobel edge detector. * @throws Exception - * @method Image spcor(Image $ref, array $options = []) Spatial correlation. + * @method Image spcor(Image $in, Image $ref, array $options = []) Spatial correlation. * @throws Exception - * @method Image spectrum(array $options = []) Make displayable power spectrum. + * @method Image spectrum(Image $in, array $options = []) Make displayable power spectrum. * @throws Exception - * @method Image stats(array $options = []) Find many image stats. + * @method Image stats(Image $in, array $options = []) Find many image stats. * @throws Exception - * @method Image stdif(integer $width, integer $height, array $options = []) Statistical difference. + * @method Image stdif(Image $in, integer $width, integer $height, array $options = []) Statistical difference. * @throws Exception - * @method Image subsample(integer $xfac, integer $yfac, array $options = []) Subsample an image. + * @method Image subsample(Image $input, integer $xfac, integer $yfac, array $options = []) Subsample an image. * @throws Exception * @method static Image sum(Image[]|Image $in, array $options = []) Sum an array of images. * @throws Exception @@ -529,6 +581,10 @@ * @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. + * @throws Exception + * @method static Image switch(Image[]|Image $tests, array $options = []) Find the index of the first non-zero pixel in tests. + * @throws Exception * @method static void system(string $cmd_format, array $options = []) Run an external command. * @throws Exception * @method static Image text(string $text, array $options = []) Make a text image. @@ -537,45 +593,53 @@ * @throws Exception * @method static Image thumbnail_buffer(string $buffer, integer $width, array $options = []) Generate thumbnail from buffer. * @throws Exception - * @method Image thumbnail_image(integer $width, array $options = []) Generate thumbnail from image. + * @method Image thumbnail_image(Image $in, 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. * @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 void tiffsave(string $filename, array $options = []) Save image to tiff file. + * @method static Image tiffload_source(string $source, array $options = []) Load tiff from source. + * @throws Exception + * @method void tiffsave(Image $in, string $filename, array $options = []) Save image to tiff file. * @throws Exception - * @method string tiffsave_buffer(array $options = []) Save image to tiff buffer. + * @method string tiffsave_buffer(Image $in, array $options = []) Save image to tiff buffer. * @throws Exception - * @method Image tilecache(array $options = []) Cache an image as a set of tiles. + * @method Image tilecache(Image $in, array $options = []) Cache an image as a set of tiles. * @throws Exception * @method static Image tonelut(array $options = []) Build a look-up table. * @throws Exception - * @method Image transpose3d(array $options = []) Transpose3d an image. + * @method Image transpose3d(Image $in, array $options = []) Transpose3d an image. * @throws Exception - * @method Image unpremultiply(array $options = []) Unpremultiply image alpha. + * @method Image unpremultiply(Image $in, array $options = []) Unpremultiply image alpha. * @throws Exception * @method static Image vipsload(string $filename, array $options = []) Load vips from file. * @throws Exception - * @method void vipssave(string $filename, array $options = []) Save image to vips file. + * @method void vipssave(Image $in, string $filename, array $options = []) Save image to vips file. * @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 void webpsave(string $filename, array $options = []) Save image to webp file. + * @method static Image webpload_source(string $source, array $options = []) Load webp from source. + * @throws Exception + * @method void webpsave(Image $in, string $filename, array $options = []) Save image to webp file. + * @throws Exception + * @method string webpsave_buffer(Image $in, array $options = []) Save image to webp buffer. * @throws Exception - * @method string webpsave_buffer(array $options = []) Save image to webp buffer. + * @method void webpsave_target(Image $in, string $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 - * @method Image wrap(array $options = []) Wrap image origin. + * @method Image wrap(Image $in, array $options = []) Wrap image origin. * @throws Exception * @method static Image xyz(integer $width, integer $height, array $options = []) Make an image where pixel values are coordinates. * @throws Exception * @method static Image zone(integer $width, integer $height, array $options = []) Make a zone plate. * @throws Exception - * @method Image zoom(integer $xfac, integer $yfac, array $options = []) Zoom an image. + * @method Image zoom(Image $input, integer $xfac, integer $yfac, array $options = []) Zoom an image. * @throws Exception * * @property integer $width Image width in pixels diff --git a/src/Interesting.php b/src/Interesting.php index 2d64c21..b7166cf 100644 --- a/src/Interesting.php +++ b/src/Interesting.php @@ -53,4 +53,7 @@ abstract class Interesting const CENTRE = 'centre'; const ENTROPY = 'entropy'; const ATTENTION = 'attention'; + const LOW = 'low'; + const HIGH = 'high'; + const ALL = 'all'; } diff --git a/src/RegionShrink.php b/src/RegionShrink.php index ebf1d50..48773d0 100644 --- a/src/RegionShrink.php +++ b/src/RegionShrink.php @@ -52,4 +52,7 @@ abstract class RegionShrink const MEAN = 'mean'; const MEDIAN = 'median'; const MODE = 'mode'; + const MAX = 'max'; + const MIN = 'min'; + const NEAREST = 'nearest'; } From 3a6fba8b1f203550c5df43dbaf9146d7b650f0d3 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Fri, 28 Aug 2020 13:30:43 +0200 Subject: [PATCH 015/115] Take advantage of PHP >= 7.1 functionality - Use nullable types and void return type were possible. - Fix autodocs for non-static methods. - Update Travis configuration (use bionic and update to vips 8.10). - Update dependencies. - Improve install-vips.sh script. --- .gitignore | 1 + .travis.yml | 46 +++-- CHANGELOG.md | 17 ++ README.md | 3 +- composer.json | 17 +- examples/generate_phpdoc.py | 4 +- install-vips.sh | 27 ++- phpcs-ruleset.xml | 3 + phpunit.xml | 3 +- src/Config.php | 18 +- src/Image.php | 40 ++-- src/ImageAutodoc.php | 402 ++++++++++++++++++------------------ src/Utils.php | 10 +- tests/ConvenienceTest.php | 2 +- tests/ExceptionTest.php | 2 +- tests/MetaTest.php | 2 +- tests/ShortcutTest.php | 2 +- tests/WriteTest.php | 4 +- 18 files changed, 311 insertions(+), 292 deletions(-) diff --git a/.gitignore b/.gitignore index a66d4dd..1cd2994 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ vendor composer.lock /docs *.swp +.phpunit.result.cache diff --git a/.travis.yml b/.travis.yml index c8d5c9b..914efa1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,23 +1,17 @@ -sudo: false - language: php -dist: trusty +dist: bionic php: - - 7.0.13 - 7.1 + - 7.4 env: global: - - VIPS_VERSION_MAJOR=8 - - VIPS_VERSION_MINOR=6 - - VIPS_VERSION_MICRO=3 + - VIPS_VERSION=8.10.0 - PATH=$HOME/vips/bin:$PATH - LD_LIBRARY_PATH=$HOME/vips/lib:$LD_LIBRARY_PATH - PKG_CONFIG_PATH=$HOME/vips/lib/pkgconfig:$PKG_CONFIG_PATH - - PYTHONPATH=$HOME/vips/lib/python2.7/site-packages:$PYTHONPATH - - GI_TYPELIB_PATH=$HOME/vips/lib/girepository-1.0:$GI_TYPELIB_PATH cache: apt: true @@ -28,30 +22,38 @@ cache: addons: apt: packages: - - gobject-introspection - - libcfitsio3-dev + # main dependencies + - libcfitsio-dev + - libexif-dev + - libexpat1-dev - libfftw3-dev - libgif-dev - - libgs-dev - libgsf-1-dev + - libgsl-dev + - libheif-dev + - liblcms2-dev + - libmagickwand-dev - libmatio-dev + - libnifti-dev + - libopenexr-dev - libopenslide-dev - liborc-0.4-dev - libpango1.0-dev + - libpng-dev - libpoppler-glib-dev + - librsvg2-dev + - libtiff5-dev - libwebp-dev + # needed for building libvips from source + - gtk-doc-tools + - gobject-introspection before_install: - - bash install-vips.sh - --disable-debug - --disable-dependency-tracking - --disable-introspection - --disable-static - --enable-gtk-doc-html=no - --enable-gtk-doc=no - --enable-pyvips8=no - --without-orc - --without-python + - bash install-vips.sh + --disable-dependency-tracking + --disable-introspection + --disable-gtk-doc-html + --disable-gtk-doc - yes '' | pecl install vips install: composer install --prefer-dist diff --git a/CHANGELOG.md b/CHANGELOG.md index 4dc6cbc..f03a6fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,23 @@ # Changelog All notable changes to `:vips` will be documented in this file. +### 1.0.7 - 2020-08-28 + +### Added +- use nullable types and void return type were possible + +### Deprecated +- requires php >= 7.1 + +### Fixed +- fix autodocs for non-static methods + +### Remove +- Nothing + +### Security +- Nothing + ## 1.0.6 - 2020-08-28 ### Added diff --git a/README.md b/README.md index cf5b4bc..e3b067f 100644 --- a/README.md +++ b/README.md @@ -163,9 +163,8 @@ libvips properties as properties of the PHP `Vips\Image` class. ### Test and install ``` -$ phpcs --standard=PSR2 src $ composer install -$ vendor/bin/phpunit +$ composer test $ vendor/bin/phpdoc ``` diff --git a/composer.json b/composer.json index ee764e4..d472bf0 100644 --- a/composer.json +++ b/composer.json @@ -17,16 +17,15 @@ } ], "require": { - "php": ">=7.0.11", + "php": ">=7.1", "ext-vips": ">=0.1.2", - "psr/log": "^1.0.2" + "psr/log": "^1.1.3" }, "require-dev": { - "jms/serializer" : ">=0.12 < 1.8.0", - "phpunit/phpunit": "^6.5", - "phpdocumentor/phpdocumentor" : "^2.9", - "jakub-onderka/php-parallel-lint": "^1.0.0", - "squizlabs/php_codesniffer": "3.*" + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpdocumentor/phpdocumentor": "3.0.0-rc", + "phpunit/phpunit": "^9.3", + "squizlabs/php_codesniffer": "^3.5" }, "autoload": { "psr-4": { @@ -49,5 +48,7 @@ "phpunit", "phpcs --standard=phpcs-ruleset.xml ." ] - } + }, + "minimum-stability": "dev", + "prefer-stable": true } diff --git a/examples/generate_phpdoc.py b/examples/generate_phpdoc.py index d617893..7ad9eda 100755 --- a/examples/generate_phpdoc.py +++ b/examples/generate_phpdoc.py @@ -111,7 +111,7 @@ def generate_operation(operation_name): result += 'array ' result += '{0}('.format(operation_name) - for name in intro.required_input: + for name in intro.method_args: details = intro.details[name] result += '{0} ${1}, '.format(gtype_to_php(details['type']), name) @@ -121,7 +121,7 @@ def generate_operation(operation_name): result += description[0].upper() + description[1:] + '.\n' # find any Enums we've referenced and output @see lines for them - for name in intro.required_output + intro.required_input: + for name in intro.required_output + intro.method_args: details = intro.details[name] fundamental = gobject_lib.g_type_fundamental(details['type']) diff --git a/install-vips.sh b/install-vips.sh index d09dc89..db7c4b6 100755 --- a/install-vips.sh +++ b/install-vips.sh @@ -1,26 +1,25 @@ #!/bin/bash -vips_site=https://github.com/libvips/libvips/releases/download -version=$VIPS_VERSION_MAJOR.$VIPS_VERSION_MINOR.$VIPS_VERSION_MICRO +version=$VIPS_VERSION +vips_tarball=https://github.com/libvips/libvips/releases/download/v$version/vips-$version.tar.gz set -e # do we already have the correct vips built? early exit if yes # we could check the configure params as well I guess if [ -d "$HOME/vips/bin" ]; then - installed_version=$($HOME/vips/bin/vips --version) - escaped_version="$VIPS_VERSION_MAJOR\.$VIPS_VERSION_MINOR\.$VIPS_VERSION_MICRO" - echo "Need vips-$version" - echo "Found $installed_version" - if [[ "$installed_version" =~ ^vips-$escaped_version ]]; then - echo "Using cached directory" - exit 0 - fi + installed_version=$($HOME/vips/bin/vips --version | awk -F- '{print $2}') + echo "Need vips $version" + echo "Found vips $installed_version" + + if [ "$installed_version" == "$version" ]; then + echo "Using cached vips directory" + exit 0 + fi fi rm -rf $HOME/vips -wget $vips_site/v$version/vips-$version.tar.gz -tar xf vips-$version.tar.gz +curl -Ls $vips_tarball | tar xz cd vips-$version -CXXFLAGS=-D_GLIBCXX_USE_CXX11_ABI=0 ./configure --prefix=$HOME/vips $* -make && make install +CXXFLAGS=-D_GLIBCXX_USE_CXX11_ABI=0 ./configure --prefix=$HOME/vips "$@" +make -j`nproc` && make install diff --git a/phpcs-ruleset.xml b/phpcs-ruleset.xml index fd567e0..f2bc93b 100644 --- a/phpcs-ruleset.xml +++ b/phpcs-ruleset.xml @@ -9,6 +9,9 @@ vendor/ + + docs/ + diff --git a/phpunit.xml b/phpunit.xml index 0bc9183..cd2dbad 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -8,8 +8,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="false" - syntaxCheck="false"> + stopOnFailure="false"> ./tests/ diff --git a/src/Config.php b/src/Config.php index 1c78b0c..b546870 100644 --- a/src/Config.php +++ b/src/Config.php @@ -71,7 +71,7 @@ class Config * * @return void */ - public static function setLogger(LoggerInterface $logger) + public static function setLogger(LoggerInterface $logger): void { self::$logger = $logger; } @@ -79,9 +79,9 @@ public static function setLogger(LoggerInterface $logger) /** * Gets a logger. * - * @return LoggerInterface $logger|null + * @return LoggerInterface|null The logger or null. */ - public static function getLogger() + public static function getLogger(): ?LoggerInterface { return self::$logger; } @@ -94,7 +94,7 @@ public static function getLogger() * * @return void */ - public static function cacheSetMax($value) + public static function cacheSetMax(int $value): void { vips_cache_set_max($value); } @@ -103,12 +103,12 @@ public static function cacheSetMax($value) * Set the maximum amount of memory to allow cached operations to use, in * bytes. * - * @param integer $value The maximum amount of memory cached opertations can + * @param integer $value The maximum amount of memory cached operations can * hold, in bytes. * * @return void */ - public static function cacheSetMaxMem($value) + public static function cacheSetMaxMem(int $value): void { vips_cache_set_max_mem($value); } @@ -121,7 +121,7 @@ public static function cacheSetMaxMem($value) * * @return void */ - public static function cacheSetMaxFiles($value) + public static function cacheSetMaxFiles(int $value): void { vips_cache_set_max_files($value); } @@ -135,13 +135,13 @@ public static function cacheSetMaxFiles($value) * * @return void */ - public static function concurrencySet($value) + public static function concurrencySet(int $value): void { vips_concurrency_set($value); } /** - * Gets the libvips version number as a atring of the form + * Gets the libvips version number as a string of the form * MAJOR.MINOR.MICRO, for example "8.6.1". * * @return string diff --git a/src/Image.php b/src/Image.php index 4fa7c8e..4d26810 100644 --- a/src/Image.php +++ b/src/Image.php @@ -772,7 +772,7 @@ private static function wrapResult($result) * * @internal */ - private static function errorVips() + private static function errorVips(): void { $message = vips_error_buffer(); $exception = new Exception($message); @@ -795,7 +795,7 @@ private static function errorVips() * * @internal */ - private static function errorIsArray($result) + private static function errorIsArray($result): void { if (!is_array($result)) { self::errorVips(); @@ -807,8 +807,8 @@ private static function errorIsArray($result) * format, try to make it complex by joining adjacant bands as real and * imaginary. * - * @param function $filename The function to run. - * @param Image $image The image to run the function on. + * @param \Closure $func The function to run. + * @param Image $image The image to run the function on. * * @throws Exception * @@ -816,7 +816,7 @@ private static function errorIsArray($result) * * @internal */ - private static function runCmplx($func, Image $image): Image + private static function runCmplx(\Closure $func, Image $image): Image { $original_format = $image->format; @@ -893,7 +893,7 @@ public static function newFromFile( * * @return string|null The name of the load operation, or null. */ - public static function findLoad(string $filename) + public static function findLoad(string $filename): ?string { Utils::debugLog('findLoad', [ 'instance' => null, @@ -963,7 +963,7 @@ public static function newFromBuffer( * * @return string|null The name of the load operation, or null. */ - public static function findLoadBuffer(string $buffer) + public static function findLoadBuffer(string $buffer): ?string { Utils::debugLog('findLoadBuffer', [ 'instance' => null, @@ -1145,7 +1145,7 @@ public function newFromImage($value): Image * * @return void */ - public function writeToFile(string $filename, array $options = []) + public function writeToFile(string $filename, array $options = []): void { Utils::debugLog('writeToFile', [ 'instance' => $this, @@ -1309,7 +1309,7 @@ public function __get(string $name) * * @return void */ - public function __set(string $name, $value) + public function __set(string $name, $value): void { vips_image_set($this->image, $name, $value); } @@ -1321,7 +1321,7 @@ public function __set(string $name, $value) * * @return bool */ - public function __isset(string $name) + public function __isset(string $name): bool { return $this->typeof($name) !== 0; } @@ -1375,7 +1375,7 @@ public function typeof(string $name): int * * @return void */ - public function set(string $name, $value) + public function set(string $name, $value): void { $result = vips_image_set($this->image, $name, $value); if ($result === -1) { @@ -1389,7 +1389,7 @@ public function set(string $name, $value) * This is useful if the type of the property cannot be determined from the * php type of the value. * - * Use Utils::typefromName() to look up types by name. + * Use Utils::typeFromName() to look up types by name. * * @param int $type The type of the property. * @param string $name The property name. @@ -1399,7 +1399,7 @@ public function set(string $name, $value) * * @return void */ - public function setType(int $type, string $name, $value) + public function setType(int $type, string $name, $value): void { $result = vips_image_set_type($this->image, $type, $name, $value); if ($result === -1) { @@ -1416,7 +1416,7 @@ public function setType(int $type, string $name, $value) * * @return void */ - public function remove(string $name) + public function remove(string $name): void { $result = vips_image_remove($this->image, $name); if ($result === -1) { @@ -1462,7 +1462,7 @@ public function __toString() */ public static function callBase( string $name, - $instance, + ?Image $instance, array $arguments ) { Utils::debugLog($name, [ @@ -1500,7 +1500,7 @@ public static function callBase( */ public static function call( string $name, - $instance, + ?Image $instance, array $arguments, array $options = [] ) { @@ -1611,9 +1611,9 @@ public function offsetExists($offset): bool * * @throws Exception * - * @return Image the extracted band. + * @return Image|null the extracted band or null. */ - public function offsetGet($offset): Image + public function offsetGet($offset): ?Image { return $this->offsetExists($offset) ? $this->extract_band($offset) : null; } @@ -1642,7 +1642,7 @@ public function offsetGet($offset): Image * * @return void */ - public function offsetSet($offset, $value) + public function offsetSet($offset, $value): void { // no offset means append if ($offset === null) { @@ -1688,7 +1688,7 @@ public function offsetSet($offset, $value) * * @return void */ - public function offsetUnset($offset) + public function offsetUnset($offset): void { if (is_int($offset) && $offset >= 0 && $offset < $this->bands) { if ($this->bands === 1) { diff --git a/src/ImageAutodoc.php b/src/ImageAutodoc.php index 0aef9c1..79abaf8 100644 --- a/src/ImageAutodoc.php +++ b/src/ImageAutodoc.php @@ -47,177 +47,177 @@ * @license https://opensource.org/licenses/MIT MIT * @link https://github.com/jcupitt/php-vips * - * @method Image CMC2LCh(Image $in, array $options = []) Transform LCh to CMC. + * @method Image CMC2LCh(array $options = []) Transform LCh to CMC. * @throws Exception - * @method Image CMYK2XYZ(Image $in, array $options = []) Transform CMYK to XYZ. + * @method Image CMYK2XYZ(array $options = []) Transform CMYK to XYZ. * @throws Exception - * @method Image HSV2sRGB(Image $in, array $options = []) Transform HSV to sRGB. + * @method Image HSV2sRGB(array $options = []) Transform HSV to sRGB. * @throws Exception - * @method Image LCh2CMC(Image $in, array $options = []) Transform LCh to CMC. + * @method Image LCh2CMC(array $options = []) Transform LCh to CMC. * @throws Exception - * @method Image LCh2Lab(Image $in, array $options = []) Transform LCh to Lab. + * @method Image LCh2Lab(array $options = []) Transform LCh to Lab. * @throws Exception - * @method Image Lab2LCh(Image $in, array $options = []) Transform Lab to LCh. + * @method Image Lab2LCh(array $options = []) Transform Lab to LCh. * @throws Exception - * @method Image Lab2LabQ(Image $in, array $options = []) Transform float Lab to LabQ coding. + * @method Image Lab2LabQ(array $options = []) Transform float Lab to LabQ coding. * @throws Exception - * @method Image Lab2LabS(Image $in, array $options = []) Transform float Lab to signed short. + * @method Image Lab2LabS(array $options = []) Transform float Lab to signed short. * @throws Exception - * @method Image Lab2XYZ(Image $in, array $options = []) Transform CIELAB to XYZ. + * @method Image Lab2XYZ(array $options = []) Transform CIELAB to XYZ. * @throws Exception - * @method Image LabQ2Lab(Image $in, array $options = []) Unpack a LabQ image to float Lab. + * @method Image LabQ2Lab(array $options = []) Unpack a LabQ image to float Lab. * @throws Exception - * @method Image LabQ2LabS(Image $in, array $options = []) Unpack a LabQ image to short Lab. + * @method Image LabQ2LabS(array $options = []) Unpack a LabQ image to short Lab. * @throws Exception - * @method Image LabQ2sRGB(Image $in, array $options = []) Convert a LabQ image to sRGB. + * @method Image LabQ2sRGB(array $options = []) Convert a LabQ image to sRGB. * @throws Exception - * @method Image LabS2Lab(Image $in, array $options = []) Transform signed short Lab to float. + * @method Image LabS2Lab(array $options = []) Transform signed short Lab to float. * @throws Exception - * @method Image LabS2LabQ(Image $in, array $options = []) Transform short Lab to LabQ coding. + * @method Image LabS2LabQ(array $options = []) Transform short Lab to LabQ coding. * @throws Exception - * @method Image XYZ2CMYK(Image $in, array $options = []) Transform XYZ to CMYK. + * @method Image XYZ2CMYK(array $options = []) Transform XYZ to CMYK. * @throws Exception - * @method Image XYZ2Lab(Image $in, array $options = []) Transform XYZ to Lab. + * @method Image XYZ2Lab(array $options = []) Transform XYZ to Lab. * @throws Exception - * @method Image XYZ2Yxy(Image $in, array $options = []) Transform XYZ to Yxy. + * @method Image XYZ2Yxy(array $options = []) Transform XYZ to Yxy. * @throws Exception - * @method Image XYZ2scRGB(Image $in, array $options = []) Transform XYZ to scRGB. + * @method Image XYZ2scRGB(array $options = []) Transform XYZ to scRGB. * @throws Exception - * @method Image Yxy2XYZ(Image $in, array $options = []) Transform Yxy to XYZ. + * @method Image Yxy2XYZ(array $options = []) Transform Yxy to XYZ. * @throws Exception - * @method Image abs(Image $in, array $options = []) Absolute value of an image. + * @method Image abs(array $options = []) Absolute value of an image. * @throws Exception - * @method Image affine(Image $in, float[]|float $matrix, array $options = []) Affine transform of an image. + * @method Image affine(float[]|float $matrix, array $options = []) Affine transform of an image. * @throws Exception * @method static Image analyzeload(string $filename, array $options = []) Load an Analyze6 image. * @throws Exception * @method static Image arrayjoin(Image[]|Image $in, array $options = []) Join an array of images. * @throws Exception - * @method Image autorot(Image $in, array $options = []) Autorotate image by exif tag. + * @method Image autorot(array $options = []) Autorotate image by exif tag. * @throws Exception - * @method float avg(Image $in, array $options = []) Find image average. + * @method float avg(array $options = []) Find image average. * @throws Exception - * @method Image bandbool(Image $in, string $boolean, array $options = []) Boolean operation across image bands. + * @method Image bandbool(string $boolean, array $options = []) Boolean operation across image bands. * @see OperationBoolean for possible values for $boolean * @throws Exception - * @method Image bandfold(Image $in, array $options = []) Fold up x axis into bands. + * @method Image bandfold(array $options = []) Fold up x axis into bands. * @throws Exception - * @method Image bandjoin_const(Image $in, float[]|float $c, array $options = []) Append a constant band to an image. + * @method Image bandjoin_const(float[]|float $c, array $options = []) Append a constant band to an image. * @throws Exception - * @method Image bandmean(Image $in, array $options = []) Band-wise average. + * @method Image bandmean(array $options = []) Band-wise average. * @throws Exception - * @method Image bandunfold(Image $in, array $options = []) Unfold image bands into x axis. + * @method Image bandunfold(array $options = []) Unfold image bands into x axis. * @throws Exception * @method static Image black(integer $width, integer $height, array $options = []) Make a black image. * @throws Exception - * @method Image boolean(Image $left, Image $right, string $boolean, array $options = []) Boolean operation on two images. + * @method Image boolean(Image $right, string $boolean, array $options = []) Boolean operation on two images. * @see OperationBoolean for possible values for $boolean * @throws Exception - * @method Image boolean_const(Image $in, string $boolean, float[]|float $c, array $options = []) Boolean operations against a constant. + * @method Image boolean_const(string $boolean, float[]|float $c, array $options = []) Boolean operations against a constant. * @see OperationBoolean for possible values for $boolean * @throws Exception - * @method Image buildlut(Image $in, array $options = []) Build a look-up table. + * @method Image buildlut(array $options = []) Build a look-up table. * @throws Exception - * @method Image byteswap(Image $in, array $options = []) Byteswap an image. + * @method Image byteswap(array $options = []) Byteswap an image. * @throws Exception - * @method Image cache(Image $in, array $options = []) Cache an image. + * @method Image cache(array $options = []) Cache an image. * @throws Exception - * @method Image canny(Image $in, array $options = []) Canny edge detector. + * @method Image canny(array $options = []) Canny edge detector. * @throws Exception - * @method Image case(Image $index, Image[]|Image $cases, array $options = []) Use pixel values to pick cases from an array of images. + * @method Image case(Image[]|Image $cases, array $options = []) Use pixel values to pick cases from an array of images. * @throws Exception - * @method Image cast(Image $in, string $format, array $options = []) Cast an image. + * @method Image cast(string $format, array $options = []) Cast an image. * @see BandFormat for possible values for $format * @throws Exception - * @method Image colourspace(Image $in, string $space, array $options = []) Convert to a new colorspace. + * @method Image colourspace(string $space, array $options = []) Convert to a new colorspace. * @see Interpretation for possible values for $space * @throws Exception - * @method Image compass(Image $in, Image $mask, array $options = []) Convolve with rotating mask. + * @method Image compass(Image $mask, array $options = []) Convolve with rotating mask. * @throws Exception - * @method Image complex(Image $in, string $cmplx, array $options = []) Perform a complex operation on an image. + * @method Image complex(string $cmplx, array $options = []) Perform a complex operation on an image. * @see OperationComplex for possible values for $cmplx * @throws Exception - * @method Image complex2(Image $left, Image $right, string $cmplx, array $options = []) Complex binary operations on two images. + * @method Image complex2(Image $right, string $cmplx, array $options = []) Complex binary operations on two images. * @see OperationComplex2 for possible values for $cmplx * @throws Exception - * @method Image complexform(Image $left, Image $right, array $options = []) Form a complex image from two real images. + * @method Image complexform(Image $right, array $options = []) Form a complex image from two real images. * @throws Exception - * @method Image complexget(Image $in, string $get, array $options = []) Get a component from a complex image. + * @method Image complexget(string $get, array $options = []) Get a component from a complex image. * @see OperationComplexget for possible values for $get * @throws Exception * @method static Image composite(Image[]|Image $in, integer[]|integer $mode, array $options = []) Blend an array of images with an array of blend modes. * @throws Exception - * @method Image composite2(Image $base, Image $overlay, string $mode, array $options = []) Blend a pair of images with a blend mode. + * @method Image composite2(Image $overlay, string $mode, array $options = []) Blend a pair of images with a blend mode. * @see BlendMode for possible values for $mode * @throws Exception - * @method Image conv(Image $in, Image $mask, array $options = []) Convolution operation. + * @method Image conv(Image $mask, array $options = []) Convolution operation. * @throws Exception - * @method Image conva(Image $in, Image $mask, array $options = []) Approximate integer convolution. + * @method Image conva(Image $mask, array $options = []) Approximate integer convolution. * @throws Exception - * @method Image convasep(Image $in, Image $mask, array $options = []) Approximate separable integer convolution. + * @method Image convasep(Image $mask, array $options = []) Approximate separable integer convolution. * @throws Exception - * @method Image convf(Image $in, Image $mask, array $options = []) Float convolution operation. + * @method Image convf(Image $mask, array $options = []) Float convolution operation. * @throws Exception - * @method Image convi(Image $in, Image $mask, array $options = []) Int convolution operation. + * @method Image convi(Image $mask, array $options = []) Int convolution operation. * @throws Exception - * @method Image convsep(Image $in, Image $mask, array $options = []) Seperable convolution operation. + * @method Image convsep(Image $mask, array $options = []) Seperable convolution operation. * @throws Exception - * @method Image copy(Image $in, array $options = []) Copy an image. + * @method Image copy(array $options = []) Copy an image. * @throws Exception - * @method float countlines(Image $in, string $direction, array $options = []) Count lines in an image. + * @method float countlines(string $direction, array $options = []) Count lines in an image. * @see Direction for possible values for $direction * @throws Exception - * @method Image crop(Image $input, integer $left, integer $top, integer $width, integer $height, array $options = []) Extract an area from an image. + * @method Image crop(integer $left, integer $top, integer $width, integer $height, array $options = []) Extract an area from an image. * @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. * @throws Exception - * @method void csvsave(Image $in, string $filename, array $options = []) Save image to csv. + * @method void csvsave(string $filename, array $options = []) Save image to csv. * @throws Exception - * @method void csvsave_target(Image $in, string $target, array $options = []) Save image to csv. + * @method void csvsave_target(string $target, array $options = []) Save image to csv. * @throws Exception - * @method Image dE00(Image $left, Image $right, array $options = []) Calculate dE00. + * @method Image dE00(Image $right, array $options = []) Calculate dE00. * @throws Exception - * @method Image dE76(Image $left, Image $right, array $options = []) Calculate dE76. + * @method Image dE76(Image $right, array $options = []) Calculate dE76. * @throws Exception - * @method Image dECMC(Image $left, Image $right, array $options = []) Calculate dECMC. + * @method Image dECMC(Image $right, array $options = []) Calculate dECMC. * @throws Exception - * @method float deviate(Image $in, array $options = []) Find image standard deviation. + * @method float deviate(array $options = []) Find image standard deviation. * @throws Exception - * @method Image draw_circle(Image $image, float[]|float $ink, integer $cx, integer $cy, integer $radius, array $options = []) Draw a circle on an image. + * @method Image draw_circle(float[]|float $ink, integer $cx, integer $cy, integer $radius, array $options = []) Draw a circle on an image. * @throws Exception - * @method Image draw_flood(Image $image, float[]|float $ink, integer $x, integer $y, array $options = []) Flood-fill an area. + * @method Image draw_flood(float[]|float $ink, integer $x, integer $y, array $options = []) Flood-fill an area. * @throws Exception - * @method Image draw_image(Image $image, Image $sub, integer $x, integer $y, array $options = []) Paint an image into another image. + * @method Image draw_image(Image $sub, integer $x, integer $y, array $options = []) Paint an image into another image. * @throws Exception - * @method Image draw_line(Image $image, float[]|float $ink, integer $x1, integer $y1, integer $x2, integer $y2, array $options = []) Draw a line on an image. + * @method Image draw_line(float[]|float $ink, integer $x1, integer $y1, integer $x2, integer $y2, array $options = []) Draw a line on an image. * @throws Exception - * @method Image draw_mask(Image $image, float[]|float $ink, Image $mask, integer $x, integer $y, array $options = []) Draw a mask on an image. + * @method Image draw_mask(float[]|float $ink, Image $mask, integer $x, integer $y, array $options = []) Draw a mask on an image. * @throws Exception - * @method Image draw_rect(Image $image, float[]|float $ink, integer $left, integer $top, integer $width, integer $height, array $options = []) Paint a rectangle on an image. + * @method Image draw_rect(float[]|float $ink, integer $left, integer $top, integer $width, integer $height, array $options = []) Paint a rectangle on an image. * @throws Exception - * @method Image draw_smudge(Image $image, integer $left, integer $top, integer $width, integer $height, array $options = []) Blur a rectangle on an image. + * @method Image draw_smudge(integer $left, integer $top, integer $width, integer $height, array $options = []) Blur a rectangle on an image. * @throws Exception - * @method void dzsave(Image $in, string $filename, array $options = []) Save image to deepzoom file. + * @method void dzsave(string $filename, array $options = []) Save image to deepzoom file. * @throws Exception - * @method string dzsave_buffer(Image $in, array $options = []) Save image to dz buffer. + * @method string dzsave_buffer(array $options = []) Save image to dz buffer. * @throws Exception - * @method Image embed(Image $in, integer $x, integer $y, integer $width, integer $height, array $options = []) Embed an image in a larger image. + * @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(Image $input, integer $left, integer $top, integer $width, integer $height, array $options = []) Extract an area from an image. + * @method Image extract_area(integer $left, integer $top, integer $width, integer $height, array $options = []) Extract an area from an image. * @throws Exception - * @method Image extract_band(Image $in, integer $band, array $options = []) Extract band from an image. + * @method Image extract_band(integer $band, array $options = []) Extract band from an image. * @throws Exception * @method static Image eye(integer $width, integer $height, array $options = []) Make an image showing the eye's spatial response. * @throws Exception - * @method Image falsecolour(Image $in, array $options = []) False-color an image. + * @method Image falsecolour(array $options = []) False-color an image. * @throws Exception - * @method Image fastcor(Image $in, Image $ref, array $options = []) Fast correlation. + * @method Image fastcor(Image $ref, array $options = []) Fast correlation. * @throws Exception - * @method Image fill_nearest(Image $in, array $options = []) Fill image zeros with nearest non-zero pixel. + * @method Image fill_nearest(array $options = []) Fill image zeros with nearest non-zero pixel. * @throws Exception - * @method array find_trim(Image $in, array $options = []) Search an image for non-edge areas. + * @method array find_trim(array $options = []) Search an image for non-edge areas. * Return array with: [ * 'left' => @type integer Left edge of image * 'top' => @type integer Top edge of extract area @@ -227,30 +227,30 @@ * @throws Exception * @method static Image fitsload(string $filename, array $options = []) Load a FITS image. * @throws Exception - * @method void fitssave(Image $in, string $filename, array $options = []) Save image to fits file. + * @method void fitssave(string $filename, array $options = []) Save image to fits file. * @throws Exception - * @method Image flatten(Image $in, array $options = []) Flatten alpha out of an image. + * @method Image flatten(array $options = []) Flatten alpha out of an image. * @throws Exception - * @method Image flip(Image $in, string $direction, array $options = []) Flip an image. + * @method Image flip(string $direction, array $options = []) Flip an image. * @see Direction for possible values for $direction * @throws Exception - * @method Image float2rad(Image $in, array $options = []) Transform float RGB to Radiance coding. + * @method Image float2rad(array $options = []) Transform float RGB to Radiance coding. * @throws Exception * @method static Image fractsurf(integer $width, integer $height, float $fractal_dimension, array $options = []) Make a fractal surface. * @throws Exception - * @method Image freqmult(Image $in, Image $mask, array $options = []) Frequency-domain filtering. + * @method Image freqmult(Image $mask, array $options = []) Frequency-domain filtering. * @throws Exception - * @method Image fwfft(Image $in, array $options = []) Forward FFT. + * @method Image fwfft(array $options = []) Forward FFT. * @throws Exception - * @method Image gamma(Image $in, array $options = []) Gamma an image. + * @method Image gamma(array $options = []) Gamma an image. * @throws Exception - * @method Image gaussblur(Image $in, float $sigma, array $options = []) Gaussian blur. + * @method Image gaussblur(float $sigma, array $options = []) Gaussian blur. * @throws Exception * @method static Image gaussmat(float $sigma, float $min_ampl, array $options = []) Make a gaussian image. * @throws Exception * @method static Image gaussnoise(integer $width, integer $height, array $options = []) Make a gaussnoise image. * @throws Exception - * @method array getpoint(Image $in, integer $x, integer $y, array $options = []) Read a point from an image. + * @method array getpoint(integer $x, integer $y, array $options = []) Read a point from an image. * @throws Exception * @method static Image gifload(string $filename, array $options = []) Load GIF with giflib. * @throws Exception @@ -258,14 +258,14 @@ * @throws Exception * @method static Image gifload_source(string $source, array $options = []) Load GIF with giflib. * @throws Exception - * @method Image globalbalance(Image $in, array $options = []) Global balance an image mosaic. + * @method Image globalbalance(array $options = []) Global balance an image mosaic. * @throws Exception - * @method Image gravity(Image $in, string $direction, integer $width, integer $height, array $options = []) Place an image within a larger image with a certain gravity. + * @method Image gravity(string $direction, integer $width, integer $height, array $options = []) Place an image within a larger image with a certain gravity. * @see CompassDirection for possible values for $direction * @throws Exception * @method static Image grey(integer $width, integer $height, array $options = []) Make a grey ramp image. * @throws Exception - * @method Image grid(Image $in, integer $tile_height, integer $across, integer $down, array $options = []) Grid an image. + * @method Image grid(integer $tile_height, integer $across, integer $down, array $options = []) Grid an image. * @throws Exception * @method static Image heifload(string $filename, array $options = []) Load a HEIF image. * @throws Exception @@ -273,55 +273,55 @@ * @throws Exception * @method static Image heifload_source(string $source, array $options = []) Load a HEIF image. * @throws Exception - * @method void heifsave(Image $in, string $filename, array $options = []) Save image in HEIF format. + * @method void heifsave(string $filename, array $options = []) Save image in HEIF format. * @throws Exception - * @method string heifsave_buffer(Image $in, array $options = []) Save image in HEIF format. + * @method string heifsave_buffer(array $options = []) Save image in HEIF format. * @throws Exception - * @method void heifsave_target(Image $in, string $target, array $options = []) Save image in HEIF format. + * @method void heifsave_target(string $target, array $options = []) Save image in HEIF format. * @throws Exception - * @method Image hist_cum(Image $in, array $options = []) Form cumulative histogram. + * @method Image hist_cum(array $options = []) Form cumulative histogram. * @throws Exception - * @method float hist_entropy(Image $in, array $options = []) Estimate image entropy. + * @method float hist_entropy(array $options = []) Estimate image entropy. * @throws Exception - * @method Image hist_equal(Image $in, array $options = []) Histogram equalisation. + * @method Image hist_equal(array $options = []) Histogram equalisation. * @throws Exception - * @method Image hist_find(Image $in, array $options = []) Find image histogram. + * @method Image hist_find(array $options = []) Find image histogram. * @throws Exception - * @method Image hist_find_indexed(Image $in, Image $index, array $options = []) Find indexed image histogram. + * @method Image hist_find_indexed(Image $index, array $options = []) Find indexed image histogram. * @throws Exception - * @method Image hist_find_ndim(Image $in, array $options = []) Find n-dimensional image histogram. + * @method Image hist_find_ndim(array $options = []) Find n-dimensional image histogram. * @throws Exception - * @method bool hist_ismonotonic(Image $in, array $options = []) Test for monotonicity. + * @method bool hist_ismonotonic(array $options = []) Test for monotonicity. * @throws Exception - * @method Image hist_local(Image $in, integer $width, integer $height, array $options = []) Local histogram equalisation. + * @method Image hist_local(integer $width, integer $height, array $options = []) Local histogram equalisation. * @throws Exception - * @method Image hist_match(Image $in, Image $ref, array $options = []) Match two histograms. + * @method Image hist_match(Image $ref, array $options = []) Match two histograms. * @throws Exception - * @method Image hist_norm(Image $in, array $options = []) Normalise histogram. + * @method Image hist_norm(array $options = []) Normalise histogram. * @throws Exception - * @method Image hist_plot(Image $in, array $options = []) Plot histogram. + * @method Image hist_plot(array $options = []) Plot histogram. * @throws Exception - * @method Image hough_circle(Image $in, array $options = []) Find hough circle transform. + * @method Image hough_circle(array $options = []) Find hough circle transform. * @throws Exception - * @method Image hough_line(Image $in, array $options = []) Find hough line transform. + * @method Image hough_line(array $options = []) Find hough line transform. * @throws Exception - * @method Image icc_export(Image $in, array $options = []) Output to device with ICC profile. + * @method Image icc_export(array $options = []) Output to device with ICC profile. * @throws Exception - * @method Image icc_import(Image $in, array $options = []) Import from device with ICC profile. + * @method Image icc_import(array $options = []) Import from device with ICC profile. * @throws Exception - * @method Image icc_transform(Image $in, string $output_profile, array $options = []) Transform between devices with ICC profiles. + * @method Image icc_transform(string $output_profile, array $options = []) Transform between devices with ICC profiles. * @throws Exception * @method static Image identity(array $options = []) Make a 1D image where pixel values are indexes. * @throws Exception - * @method Image insert(Image $main, Image $sub, integer $x, integer $y, array $options = []) Insert image @sub into @main at @x, @y. + * @method Image insert(Image $sub, integer $x, integer $y, array $options = []) Insert image @sub into @main at @x, @y. * @throws Exception - * @method Image invert(Image $in, array $options = []) Invert an image. + * @method Image invert(array $options = []) Invert an image. * @throws Exception - * @method Image invertlut(Image $in, array $options = []) Build an inverted look-up table. + * @method Image invertlut(array $options = []) Build an inverted look-up table. * @throws Exception - * @method Image invfft(Image $in, array $options = []) Inverse FFT. + * @method Image invfft(array $options = []) Inverse FFT. * @throws Exception - * @method Image join(Image $in1, Image $in2, string $direction, array $options = []) Join a pair of images. + * @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 jpegload(string $filename, array $options = []) Load jpeg from file. @@ -330,19 +330,19 @@ * @throws Exception * @method static Image jpegload_source(string $source, array $options = []) Load image from jpeg source. * @throws Exception - * @method void jpegsave(Image $in, string $filename, array $options = []) Save image to jpeg file. + * @method void jpegsave(string $filename, array $options = []) Save image to jpeg file. * @throws Exception - * @method string jpegsave_buffer(Image $in, array $options = []) Save image to jpeg buffer. + * @method string jpegsave_buffer(array $options = []) Save image to jpeg buffer. * @throws Exception - * @method void jpegsave_mime(Image $in, array $options = []) Save image to jpeg mime. + * @method void jpegsave_mime(array $options = []) Save image to jpeg mime. * @throws Exception - * @method void jpegsave_target(Image $in, string $target, array $options = []) Save image to jpeg target. + * @method void jpegsave_target(string $target, array $options = []) Save image to jpeg target. * @throws Exception - * @method Image labelregions(Image $in, array $options = []) Label regions in an image. + * @method Image labelregions(array $options = []) Label regions in an image. * @throws Exception - * @method Image linear(Image $in, float[]|float $a, float[]|float $b, array $options = []) Calculate (a * in + b). + * @method Image linear(float[]|float $a, float[]|float $b, array $options = []) Calculate (a * in + b). * @throws Exception - * @method Image linecache(Image $in, array $options = []) Cache an image as a set of lines. + * @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. * @throws Exception @@ -350,13 +350,13 @@ * @throws Exception * @method static Image magickload_buffer(string $buffer, array $options = []) Load buffer with ImageMagick. * @throws Exception - * @method void magicksave(Image $in, string $filename, array $options = []) Save file with ImageMagick. + * @method void magicksave(string $filename, array $options = []) Save file with ImageMagick. * @throws Exception - * @method string magicksave_buffer(Image $in, array $options = []) Save image to magick buffer. + * @method string magicksave_buffer(array $options = []) Save image to magick buffer. * @throws Exception - * @method Image mapim(Image $in, Image $index, array $options = []) Resample with a map image. + * @method Image mapim(Image $index, array $options = []) Resample with a map image. * @throws Exception - * @method Image maplut(Image $in, Image $lut, array $options = []) Map an image though a lut. + * @method Image maplut(Image $lut, array $options = []) Map an image though a lut. * @throws Exception * @method static Image mask_butterworth(integer $width, integer $height, float $order, float $frequency_cutoff, float $amplitude_cutoff, array $options = []) Make a butterworth filter. * @throws Exception @@ -378,54 +378,54 @@ * @throws Exception * @method static Image mask_ideal_ring(integer $width, integer $height, float $frequency_cutoff, float $ringwidth, array $options = []) Make an ideal ring filter. * @throws Exception - * @method Image match(Image $ref, Image $sec, integer $xr1, integer $yr1, integer $xs1, integer $ys1, integer $xr2, integer $yr2, integer $xs2, integer $ys2, array $options = []) First-order match of two images. + * @method Image match(Image $sec, integer $xr1, integer $yr1, integer $xs1, integer $ys1, integer $xr2, integer $yr2, integer $xs2, integer $ys2, array $options = []) First-order match of two images. * @throws Exception - * @method Image math(Image $in, string $math, array $options = []) Apply a math operation to an image. + * @method Image math(string $math, array $options = []) Apply a math operation to an image. * @see OperationMath for possible values for $math * @throws Exception - * @method Image math2(Image $left, Image $right, string $math2, array $options = []) Binary math operations. + * @method Image math2(Image $right, string $math2, array $options = []) Binary math operations. * @see OperationMath2 for possible values for $math2 * @throws Exception - * @method Image math2_const(Image $in, string $math2, float[]|float $c, array $options = []) Binary math operations with a constant. + * @method Image math2_const(string $math2, float[]|float $c, array $options = []) Binary math operations with a constant. * @see OperationMath2 for possible values for $math2 * @throws Exception * @method static Image matload(string $filename, array $options = []) Load mat from file. * @throws Exception - * @method Image matrixinvert(Image $in, array $options = []) Invert an matrix. + * @method Image matrixinvert(array $options = []) Invert an matrix. * @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. * @throws Exception - * @method void matrixprint(Image $in, array $options = []) Print matrix. + * @method void matrixprint(array $options = []) Print matrix. * @throws Exception - * @method void matrixsave(Image $in, string $filename, array $options = []) Save image to matrix. + * @method void matrixsave(string $filename, array $options = []) Save image to matrix. * @throws Exception - * @method void matrixsave_target(Image $in, string $target, array $options = []) Save image to matrix. + * @method void matrixsave_target(string $target, array $options = []) Save image to matrix. * @throws Exception - * @method float max(Image $in, array $options = []) Find image maximum. + * @method float max(array $options = []) Find image maximum. * @throws Exception - * @method Image measure(Image $in, integer $h, integer $v, array $options = []) Measure a set of patches on a color chart. + * @method Image measure(integer $h, integer $v, array $options = []) Measure a set of patches on a color chart. * @throws Exception - * @method Image merge(Image $ref, Image $sec, string $direction, integer $dx, integer $dy, array $options = []) Merge two images. + * @method Image merge(Image $sec, string $direction, integer $dx, integer $dy, array $options = []) Merge two images. * @see Direction for possible values for $direction * @throws Exception - * @method float min(Image $in, array $options = []) Find image minimum. + * @method float min(array $options = []) Find image minimum. * @throws Exception - * @method Image morph(Image $in, Image $mask, string $morph, array $options = []) Morphology operation. + * @method Image morph(Image $mask, string $morph, array $options = []) Morphology operation. * @see OperationMorphology for possible values for $morph * @throws Exception - * @method Image mosaic(Image $ref, Image $sec, string $direction, integer $xref, integer $yref, integer $xsec, integer $ysec, array $options = []) Mosaic two images. + * @method Image mosaic(Image $sec, string $direction, integer $xref, integer $yref, integer $xsec, integer $ysec, array $options = []) Mosaic two images. * @see Direction for possible values for $direction * @throws Exception - * @method Image mosaic1(Image $ref, Image $sec, string $direction, integer $xr1, integer $yr1, integer $xs1, integer $ys1, integer $xr2, integer $yr2, integer $xs2, integer $ys2, array $options = []) First-order mosaic of two images. + * @method Image mosaic1(Image $sec, string $direction, integer $xr1, integer $yr1, integer $xs1, integer $ys1, integer $xr2, integer $yr2, integer $xs2, integer $ys2, array $options = []) First-order mosaic of two images. * @see Direction for possible values for $direction * @throws Exception - * @method Image msb(Image $in, array $options = []) Pick most-significant byte from an image. + * @method Image msb(array $options = []) Pick most-significant byte from an image. * @throws Exception * @method static Image niftiload(string $filename, array $options = []) Load a NIFTI image. * @throws Exception - * @method void niftisave(Image $in, string $filename, array $options = []) Save image to nifti file. + * @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 @@ -437,11 +437,11 @@ * @throws Exception * @method static Image pdfload_source(string $source, array $options = []) Load PDF from source. * @throws Exception - * @method integer percent(Image $in, float $percent, array $options = []) Find threshold for percent of pixels. + * @method integer percent(float $percent, array $options = []) Find threshold for percent of pixels. * @throws Exception * @method static Image perlin(integer $width, integer $height, array $options = []) Make a perlin noise image. * @throws Exception - * @method Image phasecor(Image $in, Image $in2, array $options = []) Calculate phase correlation. + * @method Image phasecor(Image $in2, array $options = []) Calculate phase correlation. * @throws Exception * @method static Image pngload(string $filename, array $options = []) Load png from file. * @throws Exception @@ -449,23 +449,23 @@ * @throws Exception * @method static Image pngload_source(string $source, array $options = []) Load png from source. * @throws Exception - * @method void pngsave(Image $in, string $filename, array $options = []) Save image to png file. + * @method void pngsave(string $filename, array $options = []) Save image to png file. * @throws Exception - * @method string pngsave_buffer(Image $in, array $options = []) Save image to png buffer. + * @method string pngsave_buffer(array $options = []) Save image to png buffer. * @throws Exception - * @method void pngsave_target(Image $in, string $target, array $options = []) Save image to target as PNG. + * @method void pngsave_target(string $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. * @throws Exception - * @method void ppmsave(Image $in, string $filename, array $options = []) Save image to ppm file. + * @method void ppmsave(string $filename, array $options = []) Save image to ppm file. * @throws Exception - * @method void ppmsave_target(Image $in, string $target, array $options = []) Save to ppm. + * @method void ppmsave_target(string $target, array $options = []) Save to ppm. * @throws Exception - * @method Image premultiply(Image $in, array $options = []) Premultiply image alpha. + * @method Image premultiply(array $options = []) Premultiply image alpha. * @throws Exception - * @method array profile(Image $in, array $options = []) Find image profiles. + * @method array profile(array $options = []) Find image profiles. * Return array with: [ * 'columns' => @type Image First non-zero pixel in column * 'rows' => @type Image First non-zero pixel in row @@ -473,15 +473,15 @@ * @throws Exception * @method static string profile_load(string $name, array $options = []) Load named ICC profile. * @throws Exception - * @method array project(Image $in, array $options = []) Find image projections. + * @method array project(array $options = []) Find image projections. * Return array with: [ * 'columns' => @type Image Sums of columns * 'rows' => @type Image Sums of rows * ]; * @throws Exception - * @method Image quadratic(Image $in, Image $coeff, array $options = []) Resample an image with a quadratic transform. + * @method Image quadratic(Image $coeff, array $options = []) Resample an image with a quadratic transform. * @throws Exception - * @method Image rad2float(Image $in, array $options = []) Unpack Radiance coding to float RGB. + * @method Image rad2float(array $options = []) Unpack Radiance coding to float RGB. * @throws Exception * @method static Image radload(string $filename, array $options = []) Load a Radiance image from a file. * @throws Exception @@ -489,91 +489,91 @@ * @throws Exception * @method static Image radload_source(string $source, array $options = []) Load rad from source. * @throws Exception - * @method void radsave(Image $in, string $filename, array $options = []) Save image to Radiance file. + * @method void radsave(string $filename, array $options = []) Save image to Radiance file. * @throws Exception - * @method string radsave_buffer(Image $in, array $options = []) Save image to Radiance buffer. + * @method string radsave_buffer(array $options = []) Save image to Radiance buffer. * @throws Exception - * @method void radsave_target(Image $in, string $target, array $options = []) Save image to Radiance target. + * @method void radsave_target(string $target, array $options = []) Save image to Radiance target. * @throws Exception - * @method Image rank(Image $in, integer $width, integer $height, integer $index, array $options = []) Rank filter. + * @method Image rank(integer $width, integer $height, integer $index, array $options = []) Rank filter. * @throws Exception * @method static Image rawload(string $filename, integer $width, integer $height, integer $bands, array $options = []) Load raw data from a file. * @throws Exception - * @method void rawsave(Image $in, string $filename, array $options = []) Save image to raw file. + * @method void rawsave(string $filename, array $options = []) Save image to raw file. * @throws Exception - * @method void rawsave_fd(Image $in, integer $fd, array $options = []) Write raw image to file descriptor. + * @method void rawsave_fd(integer $fd, array $options = []) Write raw image to file descriptor. * @throws Exception - * @method Image recomb(Image $in, Image $m, array $options = []) Linear recombination with matrix. + * @method Image recomb(Image $m, array $options = []) Linear recombination with matrix. * @throws Exception - * @method Image reduce(Image $in, float $hshrink, float $vshrink, array $options = []) Reduce an image. + * @method Image reduce(float $hshrink, float $vshrink, array $options = []) Reduce an image. * @throws Exception - * @method Image reduceh(Image $in, float $hshrink, array $options = []) Shrink an image horizontally. + * @method Image reduceh(float $hshrink, array $options = []) Shrink an image horizontally. * @throws Exception - * @method Image reducev(Image $in, float $vshrink, array $options = []) Shrink an image vertically. + * @method Image reducev(float $vshrink, array $options = []) Shrink an image vertically. * @throws Exception - * @method Image relational(Image $left, Image $right, string $relational, array $options = []) Relational operation on two images. + * @method Image relational(Image $right, string $relational, array $options = []) Relational operation on two images. * @see OperationRelational for possible values for $relational * @throws Exception - * @method Image relational_const(Image $in, string $relational, float[]|float $c, array $options = []) Relational operations against a constant. + * @method Image relational_const(string $relational, float[]|float $c, array $options = []) Relational operations against a constant. * @see OperationRelational for possible values for $relational * @throws Exception - * @method Image remainder_const(Image $in, float[]|float $c, array $options = []) Remainder after integer division of an image and a constant. + * @method Image remainder_const(float[]|float $c, array $options = []) Remainder after integer division of an image and a constant. * @throws Exception - * @method Image replicate(Image $in, integer $across, integer $down, array $options = []) Replicate an image. + * @method Image replicate(integer $across, integer $down, array $options = []) Replicate an image. * @throws Exception - * @method Image resize(Image $in, float $scale, array $options = []) Resize an image. + * @method Image resize(float $scale, array $options = []) Resize an image. * @throws Exception - * @method Image rot(Image $in, string $angle, array $options = []) Rotate an image. + * @method Image rot(string $angle, array $options = []) Rotate an image. * @see Angle for possible values for $angle * @throws Exception - * @method Image rot45(Image $in, array $options = []) Rotate an image. + * @method Image rot45(array $options = []) Rotate an image. * @throws Exception - * @method Image rotate(Image $in, float $angle, array $options = []) Rotate an image by a number of degrees. + * @method Image rotate(float $angle, array $options = []) Rotate an image by a number of degrees. * @throws Exception - * @method Image round(Image $in, string $round, array $options = []) Perform a round function on an image. + * @method Image round(string $round, array $options = []) Perform a round function on an image. * @see OperationRound for possible values for $round * @throws Exception - * @method Image sRGB2HSV(Image $in, array $options = []) Transform sRGB to HSV. + * @method Image sRGB2HSV(array $options = []) Transform sRGB to HSV. * @throws Exception - * @method Image sRGB2scRGB(Image $in, array $options = []) Convert an sRGB image to scRGB. + * @method Image sRGB2scRGB(array $options = []) Convert an sRGB image to scRGB. * @throws Exception - * @method Image scRGB2BW(Image $in, array $options = []) Convert scRGB to BW. + * @method Image scRGB2BW(array $options = []) Convert scRGB to BW. * @throws Exception - * @method Image scRGB2XYZ(Image $in, array $options = []) Transform scRGB to XYZ. + * @method Image scRGB2XYZ(array $options = []) Transform scRGB to XYZ. * @throws Exception - * @method Image scRGB2sRGB(Image $in, array $options = []) Convert an scRGB image to sRGB. + * @method Image scRGB2sRGB(array $options = []) Convert an scRGB image to sRGB. * @throws Exception - * @method Image scale(Image $in, array $options = []) Scale an image to uchar. + * @method Image scale(array $options = []) Scale an image to uchar. * @throws Exception - * @method Image sequential(Image $in, array $options = []) Check sequential access. + * @method Image sequential(array $options = []) Check sequential access. * @throws Exception - * @method Image sharpen(Image $in, array $options = []) Unsharp masking for print. + * @method Image sharpen(array $options = []) Unsharp masking for print. * @throws Exception - * @method Image shrink(Image $in, float $hshrink, float $vshrink, array $options = []) Shrink an image. + * @method Image shrink(float $hshrink, float $vshrink, array $options = []) Shrink an image. * @throws Exception - * @method Image shrinkh(Image $in, integer $hshrink, array $options = []) Shrink an image horizontally. + * @method Image shrinkh(integer $hshrink, array $options = []) Shrink an image horizontally. * @throws Exception - * @method Image shrinkv(Image $in, integer $vshrink, array $options = []) Shrink an image vertically. + * @method Image shrinkv(integer $vshrink, array $options = []) Shrink an image vertically. * @throws Exception - * @method Image sign(Image $in, array $options = []) Unit vector of pixel. + * @method Image sign(array $options = []) Unit vector of pixel. * @throws Exception - * @method Image similarity(Image $in, array $options = []) Similarity transform of an image. + * @method Image similarity(array $options = []) Similarity transform of an image. * @throws Exception * @method static Image sines(integer $width, integer $height, array $options = []) Make a 2D sine wave. * @throws Exception - * @method Image smartcrop(Image $input, integer $width, integer $height, array $options = []) Extract an area from an image. + * @method Image smartcrop(integer $width, integer $height, array $options = []) Extract an area from an image. * @throws Exception - * @method Image sobel(Image $in, array $options = []) Sobel edge detector. + * @method Image sobel(array $options = []) Sobel edge detector. * @throws Exception - * @method Image spcor(Image $in, Image $ref, array $options = []) Spatial correlation. + * @method Image spcor(Image $ref, array $options = []) Spatial correlation. * @throws Exception - * @method Image spectrum(Image $in, array $options = []) Make displayable power spectrum. + * @method Image spectrum(array $options = []) Make displayable power spectrum. * @throws Exception - * @method Image stats(Image $in, array $options = []) Find many image stats. + * @method Image stats(array $options = []) Find many image stats. * @throws Exception - * @method Image stdif(Image $in, integer $width, integer $height, array $options = []) Statistical difference. + * @method Image stdif(integer $width, integer $height, array $options = []) Statistical difference. * @throws Exception - * @method Image subsample(Image $input, integer $xfac, integer $yfac, array $options = []) Subsample an image. + * @method Image subsample(integer $xfac, integer $yfac, array $options = []) Subsample an image. * @throws Exception * @method static Image sum(Image[]|Image $in, array $options = []) Sum an array of images. * @throws Exception @@ -593,7 +593,7 @@ * @throws Exception * @method static Image thumbnail_buffer(string $buffer, integer $width, array $options = []) Generate thumbnail from buffer. * @throws Exception - * @method Image thumbnail_image(Image $in, integer $width, array $options = []) Generate thumbnail from image. + * @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. * @throws Exception @@ -603,21 +603,21 @@ * @throws Exception * @method static Image tiffload_source(string $source, array $options = []) Load tiff from source. * @throws Exception - * @method void tiffsave(Image $in, string $filename, array $options = []) Save image to tiff file. + * @method void tiffsave(string $filename, array $options = []) Save image to tiff file. * @throws Exception - * @method string tiffsave_buffer(Image $in, array $options = []) Save image to tiff buffer. + * @method string tiffsave_buffer(array $options = []) Save image to tiff buffer. * @throws Exception - * @method Image tilecache(Image $in, array $options = []) Cache an image as a set of tiles. + * @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. * @throws Exception - * @method Image transpose3d(Image $in, array $options = []) Transpose3d an image. + * @method Image transpose3d(array $options = []) Transpose3d an image. * @throws Exception - * @method Image unpremultiply(Image $in, array $options = []) Unpremultiply image alpha. + * @method Image unpremultiply(array $options = []) Unpremultiply image alpha. * @throws Exception * @method static Image vipsload(string $filename, array $options = []) Load vips from file. * @throws Exception - * @method void vipssave(Image $in, string $filename, array $options = []) Save image to vips file. + * @method void vipssave(string $filename, array $options = []) Save image to vips file. * @throws Exception * @method static Image webpload(string $filename, array $options = []) Load webp from file. * @throws Exception @@ -625,21 +625,21 @@ * @throws Exception * @method static Image webpload_source(string $source, array $options = []) Load webp from source. * @throws Exception - * @method void webpsave(Image $in, string $filename, array $options = []) Save image to webp file. + * @method void webpsave(string $filename, array $options = []) Save image to webp file. * @throws Exception - * @method string webpsave_buffer(Image $in, array $options = []) Save image to webp buffer. + * @method string webpsave_buffer(array $options = []) Save image to webp buffer. * @throws Exception - * @method void webpsave_target(Image $in, string $target, array $options = []) Save image to webp target. + * @method void webpsave_target(string $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 - * @method Image wrap(Image $in, array $options = []) Wrap image origin. + * @method Image wrap(array $options = []) Wrap image origin. * @throws Exception * @method static Image xyz(integer $width, integer $height, array $options = []) Make an image where pixel values are coordinates. * @throws Exception * @method static Image zone(integer $width, integer $height, array $options = []) Make a zone plate. * @throws Exception - * @method Image zoom(Image $input, integer $xfac, integer $yfac, array $options = []) Zoom an image. + * @method Image zoom(integer $xfac, integer $yfac, array $options = []) Zoom an image. * @throws Exception * * @property integer $width Image width in pixels diff --git a/src/Utils.php b/src/Utils.php index a122e31..2f4fe53 100644 --- a/src/Utils.php +++ b/src/Utils.php @@ -38,8 +38,6 @@ namespace Jcupitt\Vips; -use Psr\Log\LoggerInterface; - /** * Various utilities. * @@ -60,7 +58,7 @@ class Utils * * @return void */ - public static function debugLog(string $name, array $arguments) + public static function debugLog(string $name, array $arguments): void { $logger = Config::getLogger(); if ($logger) { @@ -76,7 +74,7 @@ public static function debugLog(string $name, array $arguments) * * @return void */ - public static function errorLog(string $message, \Exception $exception) + public static function errorLog(string $message, \Exception $exception): void { $logger = Config::getLogger(); if ($logger) { @@ -88,11 +86,11 @@ public static function errorLog(string $message, \Exception $exception) * Look up the GTyoe from a type name. If the type does not exist, * return 0. * - * @param string $name The type name. + * @param string $name The type name. * * @return int */ - public static function typeFromName(string $name) + public static function typeFromName(string $name): int { return vips_type_from_name($name); } diff --git a/tests/ConvenienceTest.php b/tests/ConvenienceTest.php index 49492ef..34fa4f9 100644 --- a/tests/ConvenienceTest.php +++ b/tests/ConvenienceTest.php @@ -17,7 +17,7 @@ class ConvenienceTest extends TestCase */ private $pixel; - protected function setUp() + protected function setUp(): void { $filename = __DIR__ . '/images/img_0076.jpg'; $this->image = Vips\Image::newFromFile($filename); diff --git a/tests/ExceptionTest.php b/tests/ExceptionTest.php index b38c43b..262e519 100644 --- a/tests/ExceptionTest.php +++ b/tests/ExceptionTest.php @@ -17,7 +17,7 @@ class ExceptionTest extends TestCase */ private $pixel; - protected function setUp() + protected function setUp(): void { $filename = __DIR__ . '/images/img_0076.jpg'; $this->image = Vips\Image::newFromFile($filename); diff --git a/tests/MetaTest.php b/tests/MetaTest.php index 9fd3cb5..9447fe3 100644 --- a/tests/MetaTest.php +++ b/tests/MetaTest.php @@ -17,7 +17,7 @@ class MetaTest extends TestCase */ private $png_image; - protected function setUp() + protected function setUp(): void { $filename = __DIR__ . '/images/img_0076.jpg'; $this->image = Vips\Image::newFromFile($filename); diff --git a/tests/ShortcutTest.php b/tests/ShortcutTest.php index d66798c..da251a2 100644 --- a/tests/ShortcutTest.php +++ b/tests/ShortcutTest.php @@ -30,7 +30,7 @@ public static function mapNumeric($value, $func) return $value; } - protected function setUp() + protected function setUp(): void { $filename = __DIR__ . '/images/img_0076.jpg'; $this->image = Vips\Image::newFromFile($filename, ['shrink' => 8]); diff --git a/tests/WriteTest.php b/tests/WriteTest.php index 6fb5dc4..cc27b81 100644 --- a/tests/WriteTest.php +++ b/tests/WriteTest.php @@ -12,12 +12,12 @@ class WriteTest extends TestCase */ private $tmps; - protected function setUp() + protected function setUp(): void { $this->tmps = []; } - protected function tearDown() + protected function tearDown(): void { foreach ($this->tmps as $tmp) { @unlink($tmp); From 5adde1b76fd59db27b0890e4ef8f6c356e944225 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Fri, 28 Aug 2020 13:36:02 +0200 Subject: [PATCH 016/115] Skip installing libheif --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 914efa1..90b3cd3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,7 +30,6 @@ addons: - libgif-dev - libgsf-1-dev - libgsl-dev - - libheif-dev - liblcms2-dev - libmagickwand-dev - libmatio-dev From 315ede6b0dc8268d47846d18654f22c83105df54 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Fri, 28 Aug 2020 13:44:03 +0200 Subject: [PATCH 017/115] Do not test PHP 7.1 on Travis Our development dependencies require newer versions of PHP. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 90b3cd3..fb20029 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ language: php dist: bionic php: - - 7.1 + - 7.3 - 7.4 env: From a2cd8b6d3408ca0f3317d0a1affd92c39533ecb1 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 28 Aug 2020 18:35:19 +0100 Subject: [PATCH 018/115] update README for new docs url --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e3b067f..f0b077f 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ See the README there, but briefly: ``` "require": { - "jcupitt/vips" : "1.0.5" + "jcupitt/vips" : "1.0.7" } ``` @@ -85,7 +85,7 @@ $ ./try1.php ~/pics/k2.jpg x.tif ``` See `examples/`. We have a [complete set of formatted API -docs](https://libvips.github.io/php-vips/docs/classes/Jcupitt.Vips.Image.html). +docs](https://libvips.github.io/php-vips/docs/classes/Jcupitt-Vips-Image.html). ### Introduction to the API From d3cf61bd087bd9c00cfc5ae32f6ded7165963058 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 29 Aug 2020 14:10:03 +0100 Subject: [PATCH 019/115] allow type names to setType see https://github.com/libvips/php-vips-ext/issues/38 --- CHANGELOG.md | 20 +++++++++++++++++++- src/Image.php | 11 ++++++----- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f03a6fa..aecf06d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,28 @@ # Changelog All notable changes to `:vips` will be documented in this file. +### 1.0.8 - 2020-08-29 + +### Added +- allow type names as type params to Image::setType() -- fixes issue with GType + on 32-bit platforms + +### Deprecated +- Nothing + +### Fixed +- Nothing + +### Remove +- Nothing + +### Security +- Nothing + ### 1.0.7 - 2020-08-28 ### Added -- use nullable types and void return type were possible +- use nullable types and void return type where possible ### Deprecated - requires php >= 7.1 diff --git a/src/Image.php b/src/Image.php index 4d26810..1f364bc 100644 --- a/src/Image.php +++ b/src/Image.php @@ -1389,17 +1389,18 @@ public function set(string $name, $value): void * This is useful if the type of the property cannot be determined from the * php type of the value. * - * Use Utils::typeFromName() to look up types by name. + * Pass the type name directly, or use Utils::typeFromName() to look up + * types by name. * - * @param int $type The type of the property. - * @param string $name The property name. - * @param mixed $value The value to set for this property. + * @param string|int $type The type of the property. + * @param string $name The property name. + * @param mixed $value The value to set for this property. * * @throws Exception * * @return void */ - public function setType(int $type, string $name, $value): void + public function setType($type, string $name, $value): void { $result = vips_image_set_type($this->image, $type, $name, $value); if ($result === -1) { From 52d492c50e0475a55c552b19dc69b24a9bf0f4e2 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 24 Feb 2021 09:15:55 +0000 Subject: [PATCH 020/115] add watermark-text example renders text in a box on every page --- examples/watermark-text.php | 55 +++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100755 examples/watermark-text.php diff --git a/examples/watermark-text.php b/examples/watermark-text.php new file mode 100755 index 0000000..2c403fd --- /dev/null +++ b/examples/watermark-text.php @@ -0,0 +1,55 @@ +#!/usr/bin/env php + 'sequential', + 'n' => -1 +]); + +$output_filename = $argv[2]; +$text = $argv[3]; + +// the size of each frame +$page_height = $image->get('page-height'); + +$text_mask = Vips\Image::text($text, ['width' => $image->width, 'dpi' => 150]); + +// semi-transparent white text on a blue background +$foreground = [255, 255, 255, 50]; +$background = [0, 0, 255, 50]; + +// and a 10-pixel marghin +$margin = 10; + +$overlay = $text_mask->ifthenelse($foreground, $background, ['blend' => TRUE ]); + +// add a margin, with the same background +$overlay = $overlay->embed($margin, $margin, + $overlay->width + 2 * $margin, $overlay->height + 2 * $margin, + ['extend' => 'background', 'background' => $background]); + +// tag as srgb +$overlay = $overlay->copy(['interpretation' => 'srgb']); + +// expand to the size of a frame, transparent background, place at the bottom +// left +$overlay = $overlay->embed($margin, $page_height - $overlay->height - $margin, + $image->width, $page_height); + +// expand to the full size of the gif roll +$overlay = $overlay->replicate(1, $image->height / $page_height); + +// composite on top of the gif +$image = $image->composite2($overlay, 'over'); + +// and write back +$image->writeToFile($output_filename); From 96dde84bc6e9ebad0f8bebe1a742cca4c5e123ee Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 24 Feb 2021 09:36:13 +0000 Subject: [PATCH 021/115] watermark formatting --- examples/watermark-text.php | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/examples/watermark-text.php b/examples/watermark-text.php index 2c403fd..3a0377d 100755 --- a/examples/watermark-text.php +++ b/examples/watermark-text.php @@ -4,14 +4,14 @@ require __DIR__ . '/vendor/autoload.php'; use Jcupitt\Vips; -if(count($argv) != 4) { - echo("usage: ./watermark-text.php input-image output-image \"some text\"\n"); +if (count($argv) != 4) { + echo("usage: ./watermark-text.php input output \"some text\"\n"); exit(1); } // we can stream the main image, and we want all frames $image = Vips\Image::newFromFile($argv[1], [ - 'access' => 'sequential', + 'access' => 'sequential', 'n' => -1 ]); @@ -21,7 +21,10 @@ // the size of each frame $page_height = $image->get('page-height'); -$text_mask = Vips\Image::text($text, ['width' => $image->width, 'dpi' => 150]); +$text_mask = Vips\Image::text($text, [ + 'width' => $image->width, + 'dpi' => 150 +]); // semi-transparent white text on a blue background $foreground = [255, 255, 255, 50]; @@ -30,20 +33,33 @@ // and a 10-pixel marghin $margin = 10; -$overlay = $text_mask->ifthenelse($foreground, $background, ['blend' => TRUE ]); +$overlay = $text_mask->ifthenelse($foreground, $background, [ + 'blend' => true +]); // add a margin, with the same background -$overlay = $overlay->embed($margin, $margin, - $overlay->width + 2 * $margin, $overlay->height + 2 * $margin, - ['extend' => 'background', 'background' => $background]); +$overlay = $overlay->embed( + $margin, + $margin, + $overlay->width + 2 * $margin, + $overlay->height + 2 * $margin, + [ + 'extend' => 'background', + 'background' => $background + ] +); // tag as srgb $overlay = $overlay->copy(['interpretation' => 'srgb']); // expand to the size of a frame, transparent background, place at the bottom // left -$overlay = $overlay->embed($margin, $page_height - $overlay->height - $margin, - $image->width, $page_height); +$overlay = $overlay->embed( + $margin, + $page_height - $overlay->height - $margin, + $image->width, + $page_height +); // expand to the full size of the gif roll $overlay = $overlay->replicate(1, $image->height / $page_height); From 4d995296b8362e8bc042ef7be5890b2e9c9c4c7e Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 4 Mar 2021 10:37:12 +0100 Subject: [PATCH 022/115] Add missing pear lib ```pecl install vips Command 'pecl' not found, but can be installed with: apt install php-pear ``` after adding pear all works fine --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f0b077f..645976d 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ See the README there, but briefly: binaries on the vips website. For example, on Debian: ``` - sudo apt-get install libvips-dev + sudo apt-get install libvips-dev php-pear ``` Or macOS: From a5fcbd7d6f5fde27d749fb2bdf59749e31d42910 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 4 Mar 2021 09:49:11 +0000 Subject: [PATCH 023/115] clarify need for PHOP dev envirionment see https://github.com/libvips/php-vips/pull/114 --- README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 645976d..447f4ef 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ See the README there, but briefly: binaries on the vips website. For example, on Debian: ``` - sudo apt-get install libvips-dev php-pear + sudo apt-get install libvips-dev ``` Or macOS: @@ -41,7 +41,15 @@ See the README there, but briefly: brew install vips ``` -2. Install the binary PHP extension: +2. Install the binary PHP extension. You'll need a PHP development environment + for this, since it will download and build the sources for the extension. + For example, on Debian: + + ``` + sudo apt-get install php-pear + ``` + + Then to download and build the extension it's: ``` pecl install vips From 1f83dd0058854f0212938850c6894a1d5429a735 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 10 Sep 2021 16:30:48 +0100 Subject: [PATCH 024/115] update php-doc --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index d472bf0..b945a16 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ }, "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.2", - "phpdocumentor/phpdocumentor": "3.0.0-rc", + "phpdocumentor/phpdocumentor": "3.0.0", "phpunit/phpunit": "^9.3", "squizlabs/php_codesniffer": "^3.5" }, From 5015e7fb823e68ff914cd421b1a2e2674c9a1424 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 20 Nov 2021 12:28:59 +0000 Subject: [PATCH 025/115] update docs for libvips 8.12 --- CHANGELOG.md | 17 +++++++++++ RELEASE-1.0.6 => RELEASE-1.0.8 | 0 src/FailOn.php | 56 ++++++++++++++++++++++++++++++++++ src/ForeignDzLayout.php | 1 + src/ForeignPpmFormat.php | 56 ++++++++++++++++++++++++++++++++++ src/ForeignSubsample.php | 55 +++++++++++++++++++++++++++++++++ src/ForeignTiffCompression.php | 1 + src/ImageAutodoc.php | 52 +++++++++++++++++++++++++++---- src/OperationMath.php | 6 ++++ src/OperationMath2.php | 1 + 10 files changed, 239 insertions(+), 6 deletions(-) rename RELEASE-1.0.6 => RELEASE-1.0.8 (100%) create mode 100644 src/FailOn.php create mode 100644 src/ForeignPpmFormat.php create mode 100644 src/ForeignSubsample.php diff --git a/CHANGELOG.md b/CHANGELOG.md index aecf06d..b798b57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,23 @@ # Changelog All notable changes to `:vips` will be documented in this file. +### 1.0.9 - 2021-11-20 + +### Added +- update docs for libvips 8.12 + +### Deprecated +- Nothing + +### Fixed +- Nothing + +### Remove +- Nothing + +### Security +- Nothing + ### 1.0.8 - 2020-08-29 ### Added diff --git a/RELEASE-1.0.6 b/RELEASE-1.0.8 similarity index 100% rename from RELEASE-1.0.6 rename to RELEASE-1.0.8 diff --git a/src/FailOn.php b/src/FailOn.php new file mode 100644 index 0000000..eaa4d9c --- /dev/null +++ b/src/FailOn.php @@ -0,0 +1,56 @@ + + * @copyright 2016 John Cupitt + * @license https://opensource.org/licenses/MIT MIT + * @link https://github.com/jcupitt/php-vips + */ + +namespace Jcupitt\Vips; + +/** + * The FailOn 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 FailOn +{ + const NONE = 'none'; + const TRUNCATED = 'truncated'; + const ERROR = 'error'; + const WARNING = 'warning'; +} diff --git a/src/ForeignDzLayout.php b/src/ForeignDzLayout.php index 021d75f..ae36259 100644 --- a/src/ForeignDzLayout.php +++ b/src/ForeignDzLayout.php @@ -53,4 +53,5 @@ abstract class ForeignDzLayout const ZOOMIFY = 'zoomify'; const GOOGLE = 'google'; const IIIF = 'iiif'; + const IIIF3 = 'iiif3'; } diff --git a/src/ForeignPpmFormat.php b/src/ForeignPpmFormat.php new file mode 100644 index 0000000..ed28dc8 --- /dev/null +++ b/src/ForeignPpmFormat.php @@ -0,0 +1,56 @@ + + * @copyright 2016 John Cupitt + * @license https://opensource.org/licenses/MIT MIT + * @link https://github.com/jcupitt/php-vips + */ + +namespace Jcupitt\Vips; + +/** + * The ForeignPpmFormat 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 ForeignPpmFormat +{ + const PBM = 'pbm'; + const PGM = 'pgm'; + const PPM = 'ppm'; + const PFM = 'pfm'; +} diff --git a/src/ForeignSubsample.php b/src/ForeignSubsample.php new file mode 100644 index 0000000..c46f73f --- /dev/null +++ b/src/ForeignSubsample.php @@ -0,0 +1,55 @@ + + * @copyright 2016 John Cupitt + * @license https://opensource.org/licenses/MIT MIT + * @link https://github.com/jcupitt/php-vips + */ + +namespace Jcupitt\Vips; + +/** + * The ForeignSubsample 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 ForeignSubsample +{ + const AUTO = 'auto'; + const ON = 'on'; + const OFF = 'off'; +} diff --git a/src/ForeignTiffCompression.php b/src/ForeignTiffCompression.php index 0d5a6b0..3c459f1 100644 --- a/src/ForeignTiffCompression.php +++ b/src/ForeignTiffCompression.php @@ -57,4 +57,5 @@ abstract class ForeignTiffCompression const LZW = 'lzw'; const WEBP = 'webp'; const ZSTD = 'zstd'; + const JP2K = 'jp2k'; } diff --git a/src/ImageAutodoc.php b/src/ImageAutodoc.php index 79abaf8..e917baf 100644 --- a/src/ImageAutodoc.php +++ b/src/ImageAutodoc.php @@ -227,6 +227,8 @@ * @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. + * @throws Exception * @method void fitssave(string $filename, array $options = []) Save image to fits file. * @throws Exception * @method Image flatten(array $options = []) Flatten alpha out of an image. @@ -252,11 +254,17 @@ * @throws Exception * @method array getpoint(integer $x, integer $y, array $options = []) Read a point from an image. * @throws Exception - * @method static Image gifload(string $filename, array $options = []) Load GIF with giflib. + * @method static Image gifload(string $filename, array $options = []) Load GIF with libnsgif. + * @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 static Image gifload_buffer(string $buffer, array $options = []) Load GIF with giflib. + * @method void gifsave(string $filename, array $options = []) Save as gif. * @throws Exception - * @method static Image gifload_source(string $source, array $options = []) Load GIF with giflib. + * @method string gifsave_buffer(array $options = []) Save as gif. + * @throws Exception + * @method void gifsave_target(string $target, array $options = []) Save as gif. * @throws Exception * @method Image globalbalance(array $options = []) Global balance an image mosaic. * @throws Exception @@ -324,6 +332,18 @@ * @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. @@ -338,13 +358,25 @@ * @throws Exception * @method void jpegsave_target(string $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. + * @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. + * @throws Exception * @method Image labelregions(array $options = []) Label regions in an image. * @throws Exception * @method Image linear(float[]|float $a, float[]|float $b, array $options = []) Calculate (a * in + b). * @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 @@ -423,7 +455,9 @@ * @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 a NIFTI image. + * @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 @@ -431,6 +465,8 @@ * @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. + * @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. @@ -617,7 +653,11 @@ * @throws Exception * @method static Image vipsload(string $filename, array $options = []) Load vips from file. * @throws Exception - * @method void vipssave(string $filename, array $options = []) Save image to vips file. + * @method static Image vipsload_source(string $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. * @throws Exception * @method static Image webpload(string $filename, array $options = []) Load webp from file. * @throws Exception diff --git a/src/OperationMath.php b/src/OperationMath.php index 52ffef1..857b488 100644 --- a/src/OperationMath.php +++ b/src/OperationMath.php @@ -59,4 +59,10 @@ 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 9e86093..b1fc69e 100644 --- a/src/OperationMath2.php +++ b/src/OperationMath2.php @@ -51,4 +51,5 @@ abstract class OperationMath2 { const POW = 'pow'; const WOP = 'wop'; + const ATAN2 = 'atan2'; } From 9080e310919285e4509d0564954c8c7468f84f06 Mon Sep 17 00:00:00 2001 From: Boris Glumpler Date: Thu, 3 Feb 2022 23:33:01 +0100 Subject: [PATCH 026/115] Allowed for psr/log 2.0 and 3.0 to be installed as well --- composer.json | 4 ++-- src/DebugLogger.php | 2 +- tests/LoggerTest.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index b945a16..fa0b05c 100644 --- a/composer.json +++ b/composer.json @@ -17,9 +17,9 @@ } ], "require": { - "php": ">=7.1", + "php": ">=7.2", "ext-vips": ">=0.1.2", - "psr/log": "^1.1.3" + "psr/log": "^1.1.3|^2.0|^3.0" }, "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.2", diff --git a/src/DebugLogger.php b/src/DebugLogger.php index 4cfa6ee..ddf8373 100644 --- a/src/DebugLogger.php +++ b/src/DebugLogger.php @@ -69,7 +69,7 @@ class DebugLogger implements LoggerInterface * * @return void */ - public function log($level, $message, array $context = []) + public function log($level, $message, array $context = []): void { // `Vips\Image` to string convert array_walk_recursive($context, function (&$value) { diff --git a/tests/LoggerTest.php b/tests/LoggerTest.php index ea79e67..c7f1d37 100644 --- a/tests/LoggerTest.php +++ b/tests/LoggerTest.php @@ -33,7 +33,7 @@ public function testSetLoggerCall() * * @return void */ - public function log($level, $message, array $context = array()) + public function log($level, $message, array $context = array()): void { // Do logging logic here. } From fc7516c58e58c4e577e6f1797ef0054c65b0215f Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 10 Mar 2022 08:15:14 +0000 Subject: [PATCH 027/115] Switch to php ffi (#133) Rewrite for php-ffi --- CHANGELOG.md | 25 +- README.md | 82 +-- RELEASE-1.0.8 => RELEASE-2.0.0 | 0 composer.json | 14 +- examples/composer.json | 5 + examples/watermark-image.php | 44 ++ examples/watermark-text.php | 4 +- src/ArgumentFlags.php | 69 +++ src/Config.php | 689 +++++++++++++++++++++++- src/GObject.php | 107 ++++ src/GValue.php | 342 ++++++++++++ src/Image.php | 936 +++++++++++++-------------------- src/ImageAutodoc.php | 2 +- src/Interpolate.php | 104 ++++ src/Introspect.php | 252 +++++++++ src/Utils.php | 2 +- src/VipsObject.php | 187 +++++++ src/VipsOperation.php | 413 +++++++++++++++ tests/NewTest.php | 17 - tests/ShortcutTest.php | 2 + tests/WriteTest.php | 14 +- 21 files changed, 2656 insertions(+), 654 deletions(-) rename RELEASE-1.0.8 => RELEASE-2.0.0 (100%) create mode 100644 examples/composer.json create mode 100755 examples/watermark-image.php create mode 100644 src/ArgumentFlags.php create mode 100644 src/GObject.php create mode 100644 src/GValue.php create mode 100644 src/Interpolate.php create mode 100644 src/Introspect.php create mode 100644 src/VipsObject.php create mode 100644 src/VipsOperation.php diff --git a/CHANGELOG.md b/CHANGELOG.md index b798b57..9139f34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,27 @@ # Changelog + All notable changes to `:vips` will be documented in this file. +## 2.0.0 - 2022-1-20 + +Rewritten to use PHP FFI to call into the libvips library rather than a binary +extension. This means php-vips now requires php 7.4 or later. + +### Added +- `Interpolate` class + +### Deprecated +- Nothing + +### Fixed +- Nothing + +### Remove +- Nothing + +### Security +- Nothing + ### 1.0.9 - 2021-11-20 ### Added @@ -18,7 +39,7 @@ All notable changes to `:vips` will be documented in this file. ### Security - Nothing -### 1.0.8 - 2020-08-29 +## 1.0.8 - 2020-08-29 ### Added - allow type names as type params to Image::setType() -- fixes issue with GType @@ -36,7 +57,7 @@ All notable changes to `:vips` will be documented in this file. ### Security - Nothing -### 1.0.7 - 2020-08-28 +## 1.0.7 - 2020-08-28 ### Added - use nullable types and void return type where possible diff --git a/README.md b/README.md index 447f4ef..592ef6b 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ [![Build Status](https://travis-ci.org/libvips/php-vips.svg?branch=master)](https://travis-ci.org/libvips/php-vips) -`php-vips` is a binding for [libvips](https://github.com/libvips/libvips) for -PHP 7. +`php-vips` is a binding for [libvips](https://github.com/libvips/libvips) 8.7 +and later for PHP 7.4 and later. libvips is fast and needs little memory. The [`vips-php-bench`](https://github.com/jcupitt/php-vips-bench) repository @@ -17,54 +17,30 @@ image. When the pipe is connected to a destination, the whole pipeline executes at once and in parallel, streaming the image from source to destination in a set of small fragments. -This module builds upon the `vips` PHP extension: +### Install -https://github.com/libvips/php-vips-ext +You need to [install the libvips +library](https://libvips.github.io/libvips/install.html). It's in the linux +package managers, homebrew and MacPorts, and there are Windows binaries on +the vips website. For example, on Debian: -You'll need to install that first. It's tested on Linux and macOS --- -Windows would need some work, but should be possible. - -See the README there, but briefly: - -1. [Install the libvips library and - headers](https://libvips.github.io/libvips/install.html). It's in - the linux package managers, homebrew and MacPorts, and there are Windows - binaries on the vips website. For example, on Debian: - - ``` - sudo apt-get install libvips-dev - ``` - - Or macOS: - - ``` - brew install vips - ``` - -2. Install the binary PHP extension. You'll need a PHP development environment - for this, since it will download and build the sources for the extension. - For example, on Debian: - - ``` - sudo apt-get install php-pear - ``` - - Then to download and build the extension it's: +``` +sudo apt-get install libvips-dev +``` - ``` - pecl install vips - ``` +Or macOS: - You may need to add `extension=vips.so` or equivalent to `php.ini`, see the - output of pecl. +``` +brew install vips +``` -3. Add vips to your `composer.json`: +Then add vips to your `composer.json`: - ``` - "require": { - "jcupitt/vips" : "1.0.7" - } - ``` +``` +"require": { + "jcupitt/vips" : "2.0.0" +} +``` ### Example @@ -156,17 +132,17 @@ introduction: https://libvips.github.io/libvips/API/current -### How it works +### TODO after merge + +- Support preloading, see https://www.php.net/manual/en/class.ffi.php + +- Rewrite the enum and doc generator in php. + +- Add source/target API -The `vips` extension defines a simple but ugly way to call any libvips -operation from PHP. It uses libvips' own introspection facilities -and does not depend on anything else (so no gobject-introspection, -for example). It's a fairly short 1,600 lines of C. +- Add progress callbacks etc. -This module is a PHP layer over the ugly `vips` extension that -tries to make a nice interface for programmers. It uses `__call()` and -`__get()` to make all libvips operations appear as methods, and all -libvips properties as properties of the PHP `Vips\Image` class. +- Add mutable. ### Test and install diff --git a/RELEASE-1.0.8 b/RELEASE-2.0.0 similarity index 100% rename from RELEASE-1.0.8 rename to RELEASE-2.0.0 diff --git a/composer.json b/composer.json index fa0b05c..2c61251 100644 --- a/composer.json +++ b/composer.json @@ -17,15 +17,15 @@ } ], "require": { - "php": ">=7.2", - "ext-vips": ">=0.1.2", + "php": ">=7.4", + "ext-ffi": "*", "psr/log": "^1.1.3|^2.0|^3.0" }, "require-dev": { - "php-parallel-lint/php-parallel-lint": "^1.2", - "phpdocumentor/phpdocumentor": "3.0.0", - "phpunit/phpunit": "^9.3", - "squizlabs/php_codesniffer": "^3.5" + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpdocumentor/shim": "^3.3", + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "^3.6" }, "autoload": { "psr-4": { @@ -39,7 +39,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0.x-dev" } }, "scripts": { diff --git a/examples/composer.json b/examples/composer.json new file mode 100644 index 0000000..2de9b0e --- /dev/null +++ b/examples/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "jcupitt/vips": "2.0.0" + } +} diff --git a/examples/watermark-image.php b/examples/watermark-image.php new file mode 100755 index 0000000..195343f --- /dev/null +++ b/examples/watermark-image.php @@ -0,0 +1,44 @@ +#!/usr/bin/env php + 'sequential']); + +// we'll read the watermark image many times, so we need random access for this +$watermark = Vips\Image::newFromFile($argv[3]); + +// the watermark image needs to have an alpha channel +if (!$watermark->hasAlpha() || $watermark->bands != 4) { + echo("watermark image is not RGBA\n"); + exit(1); +} + +// make the watermark semi-transparent +$watermark = $watermark->multiply([1, 1, 1, 0.3])->cast("uchar"); + +// repeat the watermark to the size of the image +$watermark = $watermark->replicate( + 1 + $image->width / $watermark->width, + 1 + $image->height / $watermark->height +); +$watermark = $watermark->crop(0, 0, $image->width, $image->height); + +// composite the watermark over the main image +$image = $image->composite2($watermark, 'over'); + +$image->writeToFile($argv[2]); + +$image = null; +$watermark = null; + +Vips\Config::shutDown(); diff --git a/examples/watermark-text.php b/examples/watermark-text.php index 3a0377d..654cada 100755 --- a/examples/watermark-text.php +++ b/examples/watermark-text.php @@ -4,6 +4,8 @@ require __DIR__ . '/vendor/autoload.php'; use Jcupitt\Vips; +#Vips\Config::setLogger(new Vips\DebugLogger()); + if (count($argv) != 4) { echo("usage: ./watermark-text.php input output \"some text\"\n"); exit(1); @@ -30,7 +32,7 @@ $foreground = [255, 255, 255, 50]; $background = [0, 0, 255, 50]; -// and a 10-pixel marghin +// and a 10-pixel margin $margin = 10; $overlay = $text_mask->ifthenelse($foreground, $background, [ diff --git a/src/ArgumentFlags.php b/src/ArgumentFlags.php new file mode 100644 index 0000000..cb54e69 --- /dev/null +++ b/src/ArgumentFlags.php @@ -0,0 +1,69 @@ + + * @copyright 2016 John Cupitt + * @license https://opensource.org/licenses/MIT MIT + * @link https://github.com/jcupitt/php-vips + */ + +namespace Jcupitt\Vips; + +/** + * The ArgumentFlags 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 ArgumentFlags +{ + const REQUIRED = 1; + const CONSTRUCT = 2; + const SET_ONCE = 4; + const SET_ALWAYS = 8; + const INPUT = 16; + const OUTPUT = 32; + const DEPRECATED = 64; + const MODIFY = 128; + + const NAMES = [ + "REQUIRED" => self::REQUIRED, + "CONSTRUCT" => self::CONSTRUCT, + "SET_ONCE" => self::SET_ONCE, + "SET_ALWAYS" => self::SET_ALWAYS, + "INPUT" => self::INPUT, + "OUTPUT" => self::OUTPUT, + "DEPRECATED" => self::DEPRECATED, + "MODIFY" => self::MODIFY, + ]; +} diff --git a/src/Config.php b/src/Config.php index b546870..ed720df 100644 --- a/src/Config.php +++ b/src/Config.php @@ -60,6 +60,32 @@ class Config */ private static $logger; + /** + * The FFI handle we use for the libvips binary. + * + * @internal + */ + private static \FFI $ffi; + private static $ffi_inited = false; + + /** + * The library version number we detect. + * + * @internal + */ + private static int $library_major; + private static int $library_minor; + private static int $library_micro; + + /** + * Look up these once. + * + * @internal + */ + private static array $ctypes; + private static array $gtypes; + private static array $ftypes; + /** * Sets a logger. This can be handy for debugging. For example: * @@ -96,7 +122,7 @@ public static function getLogger(): ?LoggerInterface */ public static function cacheSetMax(int $value): void { - vips_cache_set_max($value); + Config::ffi()->vips_cache_set_max($value); } /** @@ -110,7 +136,7 @@ public static function cacheSetMax(int $value): void */ public static function cacheSetMaxMem(int $value): void { - vips_cache_set_max_mem($value); + Config::ffi()->vips_cache_set_max_mem($value); } /** @@ -123,7 +149,7 @@ public static function cacheSetMaxMem(int $value): void */ public static function cacheSetMaxFiles(int $value): void { - vips_cache_set_max_files($value); + Config::ffi()->vips_cache_set_max_files($value); } /** @@ -137,7 +163,7 @@ public static function cacheSetMaxFiles(int $value): void */ public static function concurrencySet(int $value): void { - vips_concurrency_set($value); + Config::ffi()->vips_concurrency_set($value); } /** @@ -148,7 +174,660 @@ public static function concurrencySet(int $value): void */ public static function version(): string { - return vips_version(); + Config::ffi(); + + return self::$library_major . "." . + self::$library_minor . ".". + self::$library_micro; + } + + /** + * Handy for debugging. + */ + public static function printAll() + { + Config::ffi()->vips_object_print_all(); + } + + public static function ffi() + { + if (!self::$ffi_inited) { + self::init(); + self::$ffi_inited = true; + } + + return self::$ffi; + } + + public static function ctypes(string $name) + { + Config::ffi(); + + return self::$ctypes[$name]; + } + + public static function gtypes(string $name) + { + Config::ffi(); + + return self::$gtypes[$name]; + } + + public static function ftypes(string $name) + { + Config::ffi(); + + return self::$ftypes[$name]; + } + + /** + * Throw a vips error as an exception. + * + * @throws Exception + * + * @return void + * + * @internal + */ + public static function error(string $message = "") + { + if ($message == "") { + $message = Config::ffi()->vips_error_buffer(); + Config::ffi()->vips_error_clear(); + } + $exception = new Exception($message); + Utils::errorLog($message, $exception); + throw $exception; + } + + /** + * Shut down libvips. Call this just before process exit. + * + * @throws Exception + * + * @return void + * + * @internal + */ + public static function shutDown() + { + Config::ffi()->vips_shutdown(); + } + + public static function filenameGetFilename($name) + { + $pointer = Config::ffi()->vips_filename_get_filename($name); + $filename = \FFI::string($pointer); + Config::ffi()->g_free($pointer); + + return $filename; + } + + public static function filenameGetOptions($name) + { + $pointer = Config::ffi()->vips_filename_get_options($name); + $options = \FFI::string($pointer); + Config::ffi()->g_free($pointer); + + return $options; + } + + public static function atLeast($need_major, $need_minor) + { + return $need_major < self::$library_major || + ($need_major == self::$library_major && + $need_minor <= self::$library_minor); + } + + private static function libraryName($name, $abi) + { + switch (PHP_OS_FAMILY) { + case "Windows": + return "$name-$abi.dll"; + + case "OSX": + return "$name.$abi.dylib"; + + default: + // most *nix + return "$name.so.$abi"; + } + } + + private static function init() + { + $library = self::libraryName("libvips", 42); + + Utils::debugLog("init", ["libray" => $library]); + + /* FIXME ... maybe display a helpful message on failure? This will + * probably be the main point of failure. + */ + $ffi = \FFI::cdef(<<vips_init(""); + if ($result != 0) { + $msg = $ffi->vips_error_buffer(); + throw new Vips\Exception("libvips error: $msg"); + } + Utils::debugLog("init", ["vips_init" => $result]); + + # get the library version number, then we can build the API + self::$library_major = $ffi->vips_version(0); + self::$library_minor = $ffi->vips_version(1); + self::$library_micro = $ffi->vips_version(2); + Utils::debugLog("init", [ + "libvips version" => [ + self::$library_major, + self::$library_minor, + self::$library_micro + ] + ]); + + if (!self::atLeast(8, 7)) { + throw new Vips\Exception("your libvips is too old -- " . + "8.7 or later required"); + } + + if (PHP_INT_SIZE != 8) { + # we could maybe fix this if it's important ... it's mostly + # necessary since GType is the size of a pointer, and there's no + # easy way to discover if php is running on a 32 or 64-bit + # systems (as far as I can see) + throw new Vips\Exception("your php only supports 32-bit ints -- " . + "64 bit ints required"); + } + + # the whole libvips API, mostly adapted from pyvips + $header = <<vips_blend_mode_get_type(); + self::$ffi->vips_interpretation_get_type(); + self::$ffi->vips_operation_flags_get_type(); + self::$ffi->vips_band_format_get_type(); + self::$ffi->vips_token_get_type(); + self::$ffi->vips_saveable_get_type(); + self::$ffi->vips_image_type_get_type(); + + // look these up in advance + self::$ctypes = [ + "GObject" => self::$ffi->type("GObject*"), + "VipsObject" => self::$ffi->type("VipsObject*"), + "VipsOperation" => self::$ffi->type("VipsOperation*"), + "VipsImage" => self::$ffi->type("VipsImage*"), + "VipsInterpolate" => self::$ffi->type("VipsInterpolate*"), + ]; + + self::$gtypes = [ + "gboolean" => self::$ffi->g_type_from_name("gboolean"), + "gint" => self::$ffi->g_type_from_name("gint"), + "gint64" => self::$ffi->g_type_from_name("gint64"), + "guint64" => self::$ffi->g_type_from_name("guint64"), + "gdouble" => self::$ffi->g_type_from_name("gdouble"), + "gchararray" => self::$ffi->g_type_from_name("gchararray"), + "VipsRefString" => self::$ffi->g_type_from_name("VipsRefString"), + + "GEnum" => self::$ffi->g_type_from_name("GEnum"), + "GFlags" => self::$ffi->g_type_from_name("GFlags"), + "VipsBandFormat" => self::$ffi->g_type_from_name("VipsBandFormat"), + "VipsBlendMode" => self::$ffi->g_type_from_name("VipsBlendMode"), + "VipsArrayInt" => self::$ffi->g_type_from_name("VipsArrayInt"), + "VipsArrayDouble" => + self::$ffi->g_type_from_name("VipsArrayDouble"), + "VipsArrayImage" => self::$ffi->g_type_from_name("VipsArrayImage"), + "VipsBlob" => self::$ffi->g_type_from_name("VipsBlob"), + + "GObject" => self::$ffi->g_type_from_name("GObject"), + "VipsImage" => self::$ffi->g_type_from_name("VipsImage"), + ]; + + // map vips format names to c type names + self::$ftypes = [ + "char" => "char", + "uchar" => "unsigned char", + "short" => "short", + "ushort" => "unsigned short", + "int" => "int", + "uint" => "unsigned int", + "float" => "float", + "double" => "double", + "complex" => "float", + "dpcomplex" => "double", + ]; + + Utils::debugLog("init", ["done"]); } } diff --git a/src/GObject.php b/src/GObject.php new file mode 100644 index 0000000..74c35ef --- /dev/null +++ b/src/GObject.php @@ -0,0 +1,107 @@ + + * @copyright 2016 John Cupitt + * @license https://opensource.org/licenses/MIT MIT + * @link https://github.com/libvips/php-vips + */ + +namespace Jcupitt\Vips; + +/** + * This class holds a pointer to a GObject and manages object lifetime. + * + * @category Images + * @package Jcupitt\Vips + * @author John Cupitt + * @copyright 2016 John Cupitt + * @license https://opensource.org/licenses/MIT MIT + * @link https://github.com/libvips/php-vips + */ +abstract class GObject +{ + /** + * A pointer to the underlying GObject. + * + * @internal + */ + private \FFI\CData $pointer; + + /** + * Wrap a GObject around an underlying vips resource. The GObject takes + * ownership of the pointer and will unref it on finalize. + * + * Don't call this yourself, users should stick to (for example) + * Image::newFromFile(). + * + * @param FFI\CData $pointer The underlying pointer that this + * object should wrap. + * + * @internal + */ + public function __construct($pointer) + { + $this->pointer = \FFI::cast(Config::ctypes("GObject"), $pointer); + } + + public function __destruct() + { + $this->unref(); + } + + public function __clone() + { + $this->ref(); + } + + public function ref() + { + Config::ffi()->g_object_ref($this->pointer); + } + + public function unref() + { + Config::ffi()->g_object_unref($this->pointer); + } + + // TODO signal marshalling to go in +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: expandtab sw=4 ts=4 fdm=marker + * vim<600: expandtab sw=4 ts=4 + */ diff --git a/src/GValue.php b/src/GValue.php new file mode 100644 index 0000000..c0ce60d --- /dev/null +++ b/src/GValue.php @@ -0,0 +1,342 @@ + + * @copyright 2016 John Cupitt + * @license https://opensource.org/licenses/MIT MIT + * @link https://github.com/libvips/php-vips + */ + +namespace Jcupitt\Vips; + +class GValue +{ + private \FFI\CData $struct; + public \FFI\CData $pointer; + + public function __construct() + { + # allocate a gvalue on the heap, and make it persistent between requests + $this->struct = Config::ffi()->new("GValue", true, true); + $this->pointer = \FFI::addr($this->struct); + + # GValue needs to be inited to all zero + \FFI::memset($this->pointer, 0, \FFI::sizeof($this->struct)); + } + + /* Turn a string into an enum value, if possible + */ + public static function toEnum($gtype, $value) + { + if (is_string($value)) { + $enum_value = Config::ffi()-> + vips_enum_from_nick("php-vips", $gtype, $value); + if ($enum_value < 0) { + echo "gtype = " . $gtype . "\n"; + echo "value = " . $value . "\n"; + Config::error(); + } + } else { + $enum_value = $value; + } + + return $enum_value; + } + + public static function fromEnum($gtype, $value) + { + $result = Config::ffi()->vips_enum_nick($gtype, $value); + if ($result === null) { + Config::error("value not in enum"); + } + + return $result; + } + + public function __destruct() + { + Config::ffi()->g_value_unset($this->pointer); + } + + public function setType(int $gtype) + { + Config::ffi()->g_value_init($this->pointer, $gtype); + } + + public function getType(): int + { + return $this->pointer->g_type; + } + + public function set($value) + { + $gtype = $this->getType(); + + switch ($gtype) { + case Config::gtypes("gboolean"): + Config::ffi()->g_value_set_boolean($this->pointer, $value); + break; + + case Config::gtypes("gint"): + Config::ffi()->g_value_set_int($this->pointer, $value); + break; + + case Config::gtypes("gint64"): + Config::ffi()->g_value_set_int64($this->pointer, $value); + break; + + case Config::gtypes("guint64"): + Config::ffi()->g_value_set_uint64($this->pointer, $value); + break; + + case Config::gtypes("gdouble"): + Config::ffi()->g_value_set_double($this->pointer, $value); + break; + + case Config::gtypes("gchararray"): + Config::ffi()->g_value_set_string($this->pointer, $value); + break; + + case Config::gtypes("VipsRefString"): + Config::ffi()-> + vips_value_set_ref_string($this->pointer, $value); + break; + + case Config::gtypes("VipsArrayInt"): + if (!is_array($value)) { + $value = [$value]; + } + $n = count($value); + $ctype = \FFI::arrayType(\FFI::type("int"), [$n]); + $array = \FFI::new($ctype); + for ($i = 0; $i < $n; $i++) { + $array[$i] = $value[$i]; + } + Config::ffi()-> + vips_value_set_array_int($this->pointer, $array, $n); + break; + + case Config::gtypes("VipsArrayDouble"): + if (!is_array($value)) { + $value = [$value]; + } + $n = count($value); + $ctype = \FFI::arrayType(\FFI::type("double"), [$n]); + $array = \FFI::new($ctype); + for ($i = 0; $i < $n; $i++) { + $array[$i] = $value[$i]; + } + Config::ffi()-> + vips_value_set_array_double($this->pointer, $array, $n); + break; + + case Config::gtypes("VipsArrayImage"): + if (!is_array($value)) { + $value = [$value]; + } + $n = count($value); + Config::ffi()->vips_value_set_array_image($this->pointer, $n); + $array = Config::ffi()-> + vips_value_get_array_image($this->pointer, null); + for ($i = 0; $i < $n; $i++) { + $image = $value[$i]; + $array[$i] = $image->pointer; + $image->ref(); + } + break; + + case Config::gtypes("VipsBlob"): + # we need to set the blob to a copy of the data that vips_lib + # can own and free + $n = strlen($value); + $ctype = \FFI::arrayType(\FFI::type("char"), [$n]); + $memory = \FFI::new($ctype, false, true); + for ($i = 0; $i < $n; $i++) { + $memory[$i] = $value[$i]; + } + Config::ffi()-> + vips_value_set_blob_free($this->pointer, $memory, $n); + break; + + default: + $fundamental = Config::ffi()->g_type_fundamental($gtype); + switch ($fundamental) { + case Config::gtypes("GObject"): + Config::ffi()-> + g_value_set_object($this->pointer, $value->pointer); + break; + + case Config::gtypes("GEnum"): + Config::ffi()->g_value_set_enum( + $this->pointer, + self::toEnum($gtype, $value) + ); + break; + + case Config::gtypes("GFlags"): + /* Just set as int. + */ + Config::ffi()-> + g_value_set_flags($this->pointer, $value); + break; + + default: + $typeName = Config::ffi()->g_type_name($gtype); + throw new \BadMethodCallException( + "gtype $typeName ($gtype) not implemented" + ); + break; + } + } + } + + public function get() + { + $gtype = $this->getType(); + $result = null; + + switch ($gtype) { + case Config::gtypes("gboolean"): + $result = Config::ffi()->g_value_get_boolean($this->pointer); + break; + + case Config::gtypes("gint"): + $result = Config::ffi()->g_value_get_int($this->pointer); + break; + + case Config::gtypes("gint64"): + $result = Config::ffi()->g_value_get_int64($this->pointer); + break; + + case Config::gtypes("guint64"): + $result = Config::ffi()->g_value_get_uint64($this->pointer); + break; + + case Config::gtypes("gdouble"): + $result = Config::ffi()->g_value_get_double($this->pointer); + break; + + case Config::gtypes("gchararray"): + $result = Config::ffi()->g_value_get_string($this->pointer); + break; + + case Config::gtypes("VipsRefString"): + $p_size = Config::ffi()->new("size_t[1]"); + $result = Config::ffi()-> + vips_value_get_ref_string($this->pointer, $p_size); + # $p_size[0] will be the string length, but assume it's null + # terminated + break; + + case Config::gtypes("VipsImage"): + $pointer = Config::ffi()->g_value_get_object($this->pointer); + $result = new Image($pointer); + // get_object does not increment the ref count + $result->ref(); + break; + + case Config::gtypes("VipsArrayInt"): + $p_len = Config::ffi()->new("int[1]"); + $pointer = Config::ffi()-> + vips_value_get_array_int($this->pointer, $p_len); + $result = []; + for ($i = 0; $i < $p_len[0]; $i++) { + $result[] = $pointer[$i]; + } + break; + + case Config::gtypes("VipsArrayDouble"): + $p_len = Config::ffi()->new("int[1]"); + $pointer = Config::ffi()-> + vips_value_get_array_double($this->pointer, $p_len); + $result = []; + for ($i = 0; $i < $p_len[0]; $i++) { + $result[] = $pointer[$i]; + } + break; + + case Config::gtypes("VipsArrayImage"): + $p_len = Config::ffi()->new("int[1]"); + $pointer = Config::ffi()-> + vips_value_get_array_image($this->pointer, $p_len); + $result = []; + for ($i = 0; $i < $p_len[0]; $i++) { + $image = new Image($pointer[$i]); + $image->ref(); + $result[] = $image; + } + break; + + case Config::gtypes("VipsBlob"): + $p_len = Config::ffi()->new("size_t[1]"); + $pointer = Config::ffi()-> + vips_value_get_blob($this->pointer, $p_len); + $result = \FFI::string($pointer, $p_len[0]); + break; + + default: + $fundamental = Config::ffi()->g_type_fundamental($gtype); + switch ($fundamental) { + case Config::gtypes("GEnum"): + $result = Config::ffi()-> + g_value_get_enum($this->pointer); + $result = self::fromEnum($gtype, $result); + break; + + case Config::gtypes("GFlags"): + /* Just get as int. + */ + $result = Config::ffi()-> + g_value_get_flags($this->pointer); + break; + + default: + $typeName = Config::ffi()->g_type_name($gtype); + throw new \BadMethodCallException( + "gtype $typeName ($gtype) not implemented" + ); + break; + } + } + + return $result; + } +} + +/* +* Local variables: +* tab-width: 4 +* c-basic-offset: 4 +* End: +* vim600: expandtab sw=4 ts=4 fdm=marker +* vim<600: expandtab sw=4 ts=4 +*/ diff --git a/src/Image.php b/src/Image.php index 1f364bc..c6de83a 100644 --- a/src/Image.php +++ b/src/Image.php @@ -42,11 +42,8 @@ * This class represents a Vips image object. * * This module provides a binding for the [vips image processing - * library](https://jcupitt.github.io/libvips/). - * - * It needs libvips 8.0 or later to be installed, and it needs the binary - * [`vips` extension](https://github.com/jcupitt/php-vips-ext) to be added to - * your PHP. + * library](https://libvips.org) version 8.7 and later, and required PHP 7.4 + * and later. * * # Example * @@ -213,26 +210,6 @@ * Use `$image->get('ipct-data')` for property names which are not valid under * PHP syntax. * - * # How it works - * - * The binary - * [`vips` extension](https://github.com/jcupitt/php-vips-ext) adds a few extra - * functions to PHP to let you call anything in the libvips library. The API - * it provides is simple, but horrible. - * - * This module is pure PHP and builds on the binary extension to provide a - * convenient interface for programmers. It uses the PHP magic methods - * `__call()`, `__callStatic()`, `__get()` and `__set()` to make vips operators - * appear as methods on the `Image` class, and vips properties as PHP - * properties. - * - * The API you end up with is a object-oriented version of the [VIPS C - * API](https://jcupitt.github.io/libvips/API/current). - * Full documentation - * on the operations and what they do is there, you can use it directly. This - * document explains the extra features of the PHP API and lists the available - * operations very briefly. - * * # Automatic wrapping * * This binding has a `__call()` method and uses @@ -249,8 +226,8 @@ * produces several results. * * For example, `Image::min`, the vips operation that searches an image for - * the minimum value, has a large number of optional arguments. You can use it to - * find the minimum value like this: + * the minimum value, has a large number of optional arguments. You can use it + * to find the minimum value like this: * * ```php * $min_value = $image->min(); @@ -500,102 +477,26 @@ class Image extends ImageAutodoc implements \ArrayAccess { /** - * Map load nicknames to canonical names. Regenerate this table with - * something like: - * - * $ vips -l foreign | grep -i load | awk '{ print $2, $1; }' - * - * Plus a bit of editing. - * - * @internal - */ - private static $nicknameToCanonical = [ - 'csvload' => 'VipsForeignLoadCsv', - 'matrixload' => 'VipsForeignLoadMatrix', - 'rawload' => 'VipsForeignLoadRaw', - 'vipsload' => 'VipsForeignLoadVips', - 'analyzeload' => 'VipsForeignLoadAnalyze', - 'ppmload' => 'VipsForeignLoadPpm', - 'radload' => 'VipsForeignLoadRad', - 'pdfload' => 'VipsForeignLoadPdfFile', - 'pdfload_buffer' => 'VipsForeignLoadPdfBuffer', - 'svgload' => 'VipsForeignLoadSvgFile', - 'svgload_buffer' => 'VipsForeignLoadSvgBuffer', - 'gifload' => 'VipsForeignLoadGifFile', - 'gifload_buffer' => 'VipsForeignLoadGifBuffer', - 'pngload' => 'VipsForeignLoadPng', - 'pngload_buffer' => 'VipsForeignLoadPngBuffer', - 'matload' => 'VipsForeignLoadMat', - 'jpegload' => 'VipsForeignLoadJpegFile', - 'jpegload_buffer' => 'VipsForeignLoadJpegBuffer', - 'webpload' => 'VipsForeignLoadWebpFile', - 'webpload_buffer' => 'VipsForeignLoadWebpBuffer', - 'tiffload' => 'VipsForeignLoadTiffFile', - 'tiffload_buffer' => 'VipsForeignLoadTiffBuffer', - 'magickload' => 'VipsForeignLoadMagickFile', - 'magickload_buffer' => 'VipsForeignLoadMagickBuffer', - 'fitsload' => 'VipsForeignLoadFits', - 'openexrload' => 'VipsForeignLoadOpenexr' - ]; - - /** - * Combine takes an array of blend modes, passed to libvips as an array of - * int. Because libvips does now know they should be enums, we have to do - * the string->int conversion ourselves. We ought to introspect to find the - * mapping, but until we have the machinery for that, we just hardwire the - * mapping here. + * A pointer to the underlying VipsImage. This is the same as the + * GObject, just cast to VipsImage to help FFI. * * @internal */ - private static $blendModeToInt = [ - BlendMode::CLEAR => 0, - BlendMode::SOURCE => 1, - BlendMode::OVER => 2, - BlendMode::IN => 3, - BlendMode::OUT => 4, - BlendMode::ATOP => 5, - BlendMode::DEST => 6, - BlendMode::DEST_OVER => 7, - BlendMode::DEST_IN => 8, - BlendMode::DEST_OUT => 9, - BlendMode::DEST_ATOP => 10, - BlendMode::XOR1 => 11, - BlendMode::ADD => 12, - BlendMode::SATURATE => 13, - BlendMode::MULTIPLY => 14, - BlendMode::SCREEN => 15, - BlendMode::OVERLAY => 16, - BlendMode::DARKEN => 17, - BlendMode::LIGHTEN => 18, - BlendMode::COLOUR_DODGE => 19, - BlendMode::COLOUR_BURN => 20, - BlendMode::HARD_LIGHT => 21, - BlendMode::SOFT_LIGHT => 22, - BlendMode::DIFFERENCE => 23, - BlendMode::EXCLUSION => 24 - ]; - - /** - * The resource for the underlying VipsImage. - * - * @internal - */ - private $image; + public \FFI\CData $pointer; /** - * Wrap a Image around an underlying vips resource. + * Wrap an Image around an underlying CData pointer. * * Don't call this yourself, users should stick to (for example) * Image::newFromFile(). * - * @param resource $image The underlying vips image resource that this - * class should wrap. - * * @internal */ - public function __construct($image) + public function __construct($pointer) { - $this->image = $image; + $this->pointer = Config::ffi()-> + cast(Config::ctypes("VipsImage"), $pointer); + parent::__construct($pointer); } /** @@ -663,16 +564,15 @@ private static function is2D($value): bool * * @internal */ - private static function isImageish($value): bool + public static function isImageish($value): bool { return self::is2D($value) || $value instanceof Image; } /** * Turn a constant (eg. 1, '12', [1, 2, 3], [[1]]) into an image using - * match_image as a guide. + * this as a guide. * - * @param Image $match_image Use this image as a guide. * @param mixed $value Turn this into an image. * * @throws Exception @@ -681,124 +581,14 @@ private static function isImageish($value): bool * * @internal */ - private static function imageize(Image $match_image, $value): Image + public function imageize($value): Image { - if (self::is2D($value)) { - $result = self::newFromArray($value); + if ($value instanceof Image) { + return $value; + } elseif (self::is2D($value)) { + return self::newFromArray($value); } else { - $result = $match_image->newFromImage($value); - } - - return $result; - } - - /** - * Unwrap an array of stuff ready to pass down to the vips_ layer. We - * swap instances of the Image for the plain resource. - * - * @param array $result Unwrap this. - * - * @return array $result unwrapped, ready for vips. - * - * @internal - */ - private static function unwrap(array $result): array - { - array_walk_recursive($result, function (&$value) { - if ($value instanceof Image) { - $value = $value->image; - } - }); - - return $result; - } - - /** - * Is $value a VipsImage. - * - * @param mixed $value The thing to test. - * - * @return bool true if this is a vips image resource. - * - * @internal - */ - private static function isImage($value): bool - { - return is_resource($value) && - get_resource_type($value) === 'GObject'; - } - - /** - * Wrap up the result of a vips_ call ready to return it to PHP. We do - * two things: - * - * - If the array is a singleton, we strip it off. For example, many - * operations return a single result and there's no sense handling - * this as an array of values, so we transform ['out' => x] -> x. - * - * - Any VipsImage resources are rewrapped as instances of Image. - * - * @param mixed $result Wrap this up. - * - * @return mixed $result, but wrapped up as a php class. - * - * @internal - */ - private static function wrapResult($result) - { - if (!is_array($result)) { - $result = ['x' => $result]; - } - - array_walk_recursive($result, function (&$item) { - if (self::isImage($item)) { - $item = new self($item); - } - }); - - if (count($result) === 1) { - $result = array_shift($result); - } - - return $result; - } - - /** - * Throw a vips error as an exception. - * - * @throws Exception - * - * @return void - * - * @internal - */ - private static function errorVips(): void - { - $message = vips_error_buffer(); - $exception = new Exception($message); - Utils::errorLog($message, $exception); - throw $exception; - } - - /** - * Check the result of a vips_ call for an error, and throw an exception - * if we see one. - * - * This won't work for things like __get where a non-array return can be - * a valid return. - * - * @param mixed $result Test this. - * - * @throws Exception - * - * @return void - * - * @internal - */ - private static function errorIsArray($result): void - { - if (!is_array($result)) { - self::errorVips(); + return $this->newFromImage($value); } } @@ -856,32 +646,37 @@ private static function runCmplx(\Closure $func, Image $image): Image } /** - * Create a new Image from a file on disc. + * Handy for things like self::more. Call a 2-ary vips operator like + * 'more', but if the arg is not an image (ie. it's a constant), call + * 'more_const' instead. * - * @param string $filename The file to open. - * @param array $options Any options to pass on to the load operation. + * @param mixed $other The right-hand argument. + * @param string $base The base part of the operation name. + * @param string $op The action to invoke. + * @param array $options An array of options to pass to the operation. * * @throws Exception * - * @return Image A new Image. + * @return mixed The operation result. + * + * @internal */ - public static function newFromFile( - string $filename, + private function callEnum( + $other, + string $base, + string $op, array $options = [] - ): Image { - Utils::debugLog('newFromFile', [ - 'instance' => null, - 'arguments' => [$filename, $options] - ]); - - $options = self::unwrap($options); - $result = vips_image_new_from_file($filename, $options); - self::errorIsArray($result); - $result = self::wrapResult($result); - - Utils::debugLog('newFromFile', ['result' => $result]); - - return $result; + ) { + if (self::isImageish($other)) { + return VipsOperation::call($base, $this, [$other, $op], $options); + } else { + return VipsOperation::call( + $base . '_const', + $this, + [$op, $other], + $options + ); + } } /** @@ -895,61 +690,40 @@ public static function newFromFile( */ public static function findLoad(string $filename): ?string { - Utils::debugLog('findLoad', [ - 'instance' => null, - 'arguments' => [$filename] - ]); - - // added in 1.0.5 of the binary module - if (function_exists('vips_foreign_find_load')) { - $result = vips_foreign_find_load($filename); - } else { - $result = null; - - // fallback: use the vips-loader property ... this can be much slower - try { - $image = self::newFromFile($filename); - // Unfortunately, vips-loader is the operation nickname, rather - // than the canonical name returned by vips_foreign_find_load(). - $loader = $image->get('vips-loader'); - $result = self::$nicknameToCanonical[$loader]; - } catch (Exception $ignored) { - } - } - - Utils::debugLog('findLoad', ['result' => [$result]]); + $result = Config::ffi()->vips_foreign_find_load($filename); return $result; } /** - * Create a new Image from a compressed image held as a string. + * Create a new Image from a file on disc. * - * @param string $buffer The formatted image to open. - * @param string $option_string Any text-style options to pass to the - * selected loader. - * @param array $options Any options to pass on to the load operation. + * @param string $filename The file to open. + * @param array $options Any options to pass on to the load operation. * * @throws Exception * * @return Image A new Image. */ - public static function newFromBuffer( - string $buffer, - string $option_string = '', + public static function newFromFile( + string $name, array $options = [] ): Image { - Utils::debugLog('newFromBuffer', [ - 'instance' => null, - 'arguments' => [$buffer, $option_string, $options] - ]); + $filename = Config::filenameGetFilename($name); + $string_options = Config::filenameGetOptions($name); - $options = self::unwrap($options); - $result = vips_image_new_from_buffer($buffer, $option_string, $options); - self::errorIsArray($result); - $result = self::wrapResult($result); + $loader = self::findLoad($filename); + if ($loader == null) { + Config::error(); + } + + if (strlen($string_options) != 0) { + $options = array_merge([ + "string_options" => $string_options, + ], $options); + } - Utils::debugLog('newFromBuffer', ['result' => $result]); + $result = VipsOperation::call($loader, null, [$filename], $options); return $result; } @@ -965,30 +739,41 @@ public static function newFromBuffer( */ public static function findLoadBuffer(string $buffer): ?string { - Utils::debugLog('findLoadBuffer', [ - 'instance' => null, - 'arguments' => [$buffer] - ]); + $result = Config::ffi()-> + vips_foreign_find_load_buffer($buffer, strlen($buffer)); - // added in 1.0.5 of the binary module - if (function_exists('vips_foreign_find_load_buffer')) { - $result = vips_foreign_find_load_buffer($buffer); - } else { - $result = null; - - // fallback: use the vips-loader property ... this can be much slower - try { - $image = self::newFromBuffer($buffer); - // Unfortunately, vips-loader is the operation nickname, rather - // than the canonical name returned by - // vips_foreign_find_load_buffer(). - $loader = $image->get('vips-loader'); - $result = self::$nicknameToCanonical[$loader]; - } catch (Exception $ignored) { - } + return $result; + } + + /** + * Create a new Image from a compressed image held as a string. + * + * @param string $buffer The formatted image to open. + * @param string $string_options Any text-style options to pass to the + * selected loader. + * @param array $options Options to pass on to the load operation. + * + * @throws Exception + * + * @return Image A new Image. + */ + public static function newFromBuffer( + string $buffer, + string $string_options = '', + array $options = [] + ): Image { + $loader = self::findLoadBuffer($buffer); + if ($loader == null) { + Config::error(); + } + + if (strlen($string_options) != 0) { + $options = array_merge([ + "string_options" => $string_options, + ], $options); } - Utils::debugLog('findLoadBuffer', ['result' => [$result]]); + $result = VipsOperation::call($loader, null, [$buffer], $options); return $result; } @@ -1013,18 +798,31 @@ public static function newFromArray( float $scale = 1.0, float $offset = 0.0 ): Image { - Utils::debugLog('newFromArray', [ - 'instance' => null, - 'arguments' => [$array, $scale, $offset] - ]); + if (!self::is2D($array)) { + $array = [$array]; + } - $result = vips_image_new_from_array($array, $scale, $offset); - if ($result === -1) { - self::errorVips(); + $height = count($array); + $width = count($array[0]); + + $n = $width * $height; + $ctype = \FFI::arrayType(\FFI::type("double"), [$n]); + $a = \FFI::new($ctype, true, true); + for ($y = 0; $y < $height; $y++) { + for ($x = 0; $x < $width; $x++) { + $a[$x + $y * $width] = $array[$y][$x]; + } + } + + $pointer = Config::ffi()-> + vips_image_new_matrix_from_array($width, $height, $a, $n); + if ($pointer == null) { + Config::error(); } - $result = self::wrapResult($result); + $result = new Image($pointer); - Utils::debugLog('newFromArray', ['result' => $result]); + $result->setType(Config::gtypes("gdouble"), 'scale', $scale); + $result->setType(Config::gtypes("gdouble"), 'offset', $offset); return $result; } @@ -1032,7 +830,7 @@ public static function newFromArray( /** * Wraps an Image around an area of memory containing a C-style array. * - * @param string $data C-style array. + * @param mixed $data C-style array. * @param int $width Image width in pixels. * @param int $height Image height in pixels. * @param int $bands Number of bands. @@ -1043,51 +841,41 @@ public static function newFromArray( * @return Image A new Image. */ public static function newFromMemory( - string $data, + mixed $data, int $width, int $height, int $bands, string $format ): Image { - Utils::debugLog('newFromMemory', [ - 'instance' => null, - 'arguments' => [$data, $width, $height, $bands, $format] - ]); - - $result = vips_image_new_from_memory($data, $width, $height, $bands, $format); - if ($result === -1) { - self::errorVips(); + /* Take a copy of the memory area to avoid lifetime issues. + * + * TODO add a references system instead, see pyvips. + */ + $pointer = Config::ffi()->vips_image_new_from_memory_copy( + $data, + strlen($data), + $width, + $height, + $bands, + $format + ); + if ($pointer == null) { + Config::error(); } - $result = self::wrapResult($result); - Utils::debugLog('newFromMemory', ['result' => $result]); + $result = new Image($pointer); return $result; } /** - * Make an interpolator from a name. - * - * @param string $name Name of the interpolator. - * Possible interpolators are: - * - `'nearest'`: Use nearest neighbour interpolation. - * - `'bicubic'`: Use bicubic interpolation. - * - `'bilinear'`: Use bilinear interpolation (the default). - * - `'nohalo'`: Use Nohalo interpolation. - * - `'lbb'`: Use LBB interpolation. - * - `'vsqbs'`: Use the VSQBS interpolation. + * Deprecated thing to make an interpolator. * - * @return resource|null The interpolator, or null on error. + * See Interpolator::newFromName() for the new thing. */ public static function newInterpolator(string $name) { - Utils::debugLog('newInterpolator', [ - 'instance' => null, - 'arguments' => [$name] - ]); - - // added in 1.0.7 of the binary module - return vips_interpolate_new($name); + return Interpolate::newFromName($name); } /** @@ -1108,12 +896,7 @@ public static function newInterpolator(string $name) */ public function newFromImage($value): Image { - Utils::debugLog('newFromImage', [ - 'instance' => $this, - 'arguments' => [$value] - ]); - - $pixel = self::black(1, 1)->add($value)->cast($this->format); + $pixel = static::black(1, 1)->add($value)->cast($this->format); $image = $pixel->embed( 0, 0, @@ -1129,8 +912,6 @@ public function newFromImage($value): Image 'yoffset' => $this->yoffset ]); - Utils::debugLog('newFromImage', ['result' => $image]); - return $image; } @@ -1145,17 +926,26 @@ public function newFromImage($value): Image * * @return void */ - public function writeToFile(string $filename, array $options = []): void + public function writeToFile(string $name, array $options = []): void { - Utils::debugLog('writeToFile', [ - 'instance' => $this, - 'arguments' => [$filename, $options] - ]); + $filename = Config::filenameGetFilename($name); + $string_options = Config::filenameGetOptions($name); + + $saver = Config::ffi()->vips_foreign_find_save($filename); + if ($saver == "") { + Config::error(); + } + + if (strlen($string_options) != 0) { + $options = array_merge([ + "string_options" => $string_options, + ], $options); + } + + $result = VipsOperation::call($saver, $this, [$filename], $options); - $options = self::unwrap($options); - $result = vips_image_write_to_file($this->image, $filename, $options); if ($result === -1) { - self::errorVips(); + Config::error(); } } @@ -1172,19 +962,21 @@ public function writeToFile(string $filename, array $options = []): void */ public function writeToBuffer(string $suffix, array $options = []): string { - Utils::debugLog('writeToBuffer', [ - 'instance' => $this, - 'arguments' => [$suffix, $options] - ]); + $filename = Config::filenameGetFilename($suffix); + $string_options = Config::filenameGetOptions($suffix); - $options = self::unwrap($options); - $result = vips_image_write_to_buffer($this->image, $suffix, $options); - if ($result === -1) { - self::errorVips(); + $saver = Config::ffi()->vips_foreign_find_save_buffer($filename); + if ($saver == "") { + Config::error(); + } + + if (strlen($string_options) != 0) { + $options = array_merge([ + "string_options" => $string_options, + ], $options); } - $result = self::wrapResult($result); - Utils::debugLog('writeToBuffer', ['result' => $result]); + $result = VipsOperation::call($saver, $this, [], $options); return $result; } @@ -1198,17 +990,19 @@ public function writeToBuffer(string $suffix, array $options = []): string */ public function writeToMemory(): string { - Utils::debugLog('writeToMemory', [ - 'instance' => $this, - 'arguments' => [] - ]); + $ctype = \FFI::arrayType(\FFI::type("size_t"), [1]); + $p_size = \FFI::new($ctype); - $result = vips_image_write_to_memory($this->image); - if ($result === -1) { - self::errorVips(); + $pointer = Config::ffi()-> + vips_image_write_to_memory($this->pointer, $p_size); + if ($pointer == null) { + Config::error(); } - Utils::debugLog('writeToMemory', ['result' => $result]); + // string() takes a copy + $result = \FFI::string($pointer, $p_size[0]); + + Config::ffi()->g_free($pointer); return $result; } @@ -1238,17 +1032,29 @@ public function writeToMemory(): string */ public function writeToArray(): array { - Utils::debugLog('writeToArray', [ - 'instance' => $this, - 'arguments' => [] - ]); + $ctype = \FFI::arrayType(\FFI::type("size_t"), [1]); + $p_size = \FFI::new($ctype); - $result = vips_image_write_to_array($this->image); - if ($result === -1) { - self::errorVips(); + $pointer = Config::ffi()-> + vips_image_write_to_memory($this->pointer, $p_size); + if ($pointer == null) { + Config::error(); } - Utils::debugLog('writeToArray', ['result' => $result]); + // wrap pointer up as a C array of the right type + $n = $this->width * $this->height * $this->bands; + $type_name = Config::ftypes($this->format); + $ctype = \FFI::arrayType(\FFI::type($type_name), [$n]); + $array = \FFI::cast($ctype, $pointer); + + // copy to PHP memory as a flat array + $result = []; + for ($i = 0; $i < $n; $i++) { + $result[] = $array[$i]; + } + + // the vips result is not PHP memory, so we must free it + Config::ffi()->g_free($pointer); return $result; } @@ -1269,18 +1075,11 @@ public function writeToArray(): array */ public function copyMemory(): Image { - Utils::debugLog('copyMemory', [ - 'instance' => $this, - 'arguments' => [] - ]); - - $result = vips_image_copy_memory($this->image); - if ($result === -1) { - self::errorVips(); + $pointer = Config::ffi()->vips_image_copy_memory($this->pointer); + if ($pointer == null) { + Config::error(); } - $result = self::wrapResult($result); - - Utils::debugLog('copyMemory', ['result' => $result]); + $result = new Image($pointer); return $result; } @@ -1296,9 +1095,7 @@ public function copyMemory(): Image */ public function __get(string $name) { - $result = vips_image_get($this->image, $name); - self::errorIsArray($result); - return self::wrapResult($result); + return $this->get($name); } /** @@ -1311,7 +1108,7 @@ public function __get(string $name) */ public function __set(string $name, $value): void { - vips_image_set($this->image, $name, $value); + $this->set($name, $value); } /** @@ -1323,7 +1120,7 @@ public function __set(string $name, $value): void */ public function __isset(string $name): bool { - return $this->typeof($name) !== 0; + return $this->getType($name) != 0; } /** @@ -1332,7 +1129,7 @@ public function __isset(string $name): bool * This is handy for fields whose name * does not match PHP's variable naming conventions, like `'exif-data'`. * - * It will throw an exception if $name does not exist. Use Image::typeof() + * It will throw an exception if $name does not exist. Use Image::getType() * to test for the existence of a field. * * @param string $name The property name. @@ -1343,9 +1140,13 @@ public function __isset(string $name): bool */ public function get(string $name) { - $result = vips_image_get($this->image, $name); - self::errorIsArray($result); - return self::wrapResult($result); + $gvalue = new GValue(); + if (Config::ffi()-> + vips_image_get($this->pointer, $name, $gvalue->pointer) != 0) { + Config::error(); + } + + return $gvalue->get(); } /** @@ -1357,9 +1158,21 @@ public function get(string $name) * * @return integer */ - public function typeof(string $name): int + public function getType(string $name): int { - return vips_image_get_typeof($this->image, $name); + return Config::ffi()->vips_image_get_typeof($this->pointer, $name); + } + + /** + * A deprecated synonym for getType(). + * + * @param string $name The property name. + * + * @return integer + */ + public function typeOf(string $name): int + { + return $this->getType($name); } /** @@ -1377,10 +1190,35 @@ public function typeof(string $name): int */ public function set(string $name, $value): void { - $result = vips_image_set($this->image, $name, $value); - if ($result === -1) { - self::errorVips(); + $gvalue = new GValue(); + $gtype = $this->getType($name); + + /* If this is not a known field, guess a sensible type from the value. + */ + if ($gtype == 0) { + if (is_array($value)) { + if (is_int($value[0])) { + $gtype = Config::gtypes("VipsArrayInt"); + } elseif (is_float($value[0])) { + $gtype = Config::gtypes("VipsArrayDouble"); + } else { + $gtype = Config::gtypes("VipsArrayImage"); + } + } elseif (is_int($value)) { + $gtype = Config::gtypes("gint"); + } elseif (is_float($value)) { + $gtype = Config::gtypes("gdouble"); + } elseif (is_string($value)) { + $gtype = Config::gtypes("VipsRefString"); + } else { + $gtype = Config::gtypes("VipsImage"); + } } + + $gvalue->setType($gtype); + $gvalue->set($value); + + Config::ffi()->vips_image_set($this->pointer, $name, $gvalue->pointer); } /** @@ -1402,10 +1240,10 @@ public function set(string $name, $value): void */ public function setType($type, string $name, $value): void { - $result = vips_image_set_type($this->image, $type, $name, $value); - if ($result === -1) { - self::errorVips(); - } + $gvalue = new GValue(); + $gvalue->setType($type); + $gvalue->set($value); + Config::ffi()->vips_image_set($this->pointer, $name, $gvalue->pointer); } /** @@ -1419,9 +1257,8 @@ public function setType($type, string $name, $value): void */ public function remove(string $name): void { - $result = vips_image_remove($this->image, $name); - if ($result === -1) { - self::errorVips(); + if (!Config::ffi()->vips_image_remove($this->pointer, $name)) { + Config::error(); } } @@ -1443,110 +1280,6 @@ public function __toString() return json_encode($array); } - /** - * Call any vips operation. The final element of $arguments can be - * (but doesn't have to be) an array of options to pass to the operation. - * - * We can't have a separate arg for the options since this will be run from - * __call(), which cannot know which args are required and which are - * optional. See call() below for a version with the options broken out. - * - * @param string $name The operation name. - * @param Image|null $instance The instance this operation is being invoked - * from. - * @param array $arguments An array of arguments to pass to the - * operation. - * - * @throws Exception - * - * @return mixed The result(s) of the operation. - */ - public static function callBase( - string $name, - ?Image $instance, - array $arguments - ) { - Utils::debugLog($name, [ - 'instance' => $instance, - 'arguments' => $arguments - ]); - - $arguments = array_merge([$name, $instance], $arguments); - - $arguments = array_values(self::unwrap($arguments)); - $result = vips_call(...$arguments); - self::errorIsArray($result); - $result = self::wrapResult($result); - - Utils::debugLog($name, ['result' => $result]); - - return $result; - } - - /** - * Call any vips operation, with an explicit set of options. This is more - * convenient than callBase() if you have a set of known options. - * - * @param string $name The operation name. - * @param Image|null $instance The instance this operation is being invoked - * from. - * @param array $arguments An array of arguments to pass to the - * operation. - * @param array $options An array of optional arguments to pass to - * the operation. - * - * @throws Exception - * - * @return mixed The result(s) of the operation. - */ - public static function call( - string $name, - ?Image $instance, - array $arguments, - array $options = [] - ) { - /* - echo "call: $name \n"; - echo "instance = \n"; - var_dump($instance); - echo "arguments = \n"; - var_dump($arguments); - echo "options = \n"; - var_dump($options); - */ - - return self::callBase($name, $instance, array_merge($arguments, [$options])); - } - - /** - * Handy for things like self::more. Call a 2-ary vips operator like - * 'more', but if the arg is not an image (ie. it's a constant), call - * 'more_const' instead. - * - * @param mixed $other The right-hand argument. - * @param string $base The base part of the operation name. - * @param string $op The action to invoke. - * @param array $options An array of options to pass to the operation. - * - * @throws Exception - * - * @return mixed The operation result. - * - * @internal - */ - private function callEnum( - $other, - string $base, - string $op, - array $options = [] - ) { - if (self::isImageish($other)) { - return self::call($base, $this, [$other, $op], $options); - } else { - return self::call($base . '_const', $this, [$op, $other], $options); - } - } - /** * Call any vips operation as an instance method. * @@ -1559,7 +1292,7 @@ private function callEnum( */ public function __call(string $name, array $arguments) { - return self::callBase($name, $this, $arguments); + return VipsOperation::callBase($name, $this, $arguments); } /** @@ -1574,7 +1307,7 @@ public function __call(string $name, array $arguments) */ public static function __callStatic(string $name, array $arguments) { - return self::callBase($name, null, $arguments); + return VipsOperation::callBase($name, null, $arguments); } /** @@ -1616,7 +1349,8 @@ public function offsetExists($offset): bool */ public function offsetGet($offset): ?Image { - return $this->offsetExists($offset) ? $this->extract_band($offset) : null; + return $this->offsetExists($offset) ? + $this->extract_band($offset) : null; } /** @@ -1651,7 +1385,8 @@ public function offsetSet($offset, $value): void } if (!is_int($offset)) { - throw new \BadMethodCallException('Image::offsetSet: offset is not integer or null'); + throw new \BadMethodCallException('Image::offsetSet: ' . + 'offset is not integer or null'); } // number of bands to the left and right of $value @@ -1662,7 +1397,7 @@ public function offsetSet($offset, $value): void // if we are setting a constant as the first element, we must expand it // to an image, since bandjoin must have an image as the first argument if ($n_left === 0 && !($value instanceof Image)) { - $value = self::imageize($this, $value); + $value = $this->imageize($value); } $components = []; @@ -1675,7 +1410,14 @@ public function offsetSet($offset, $value): void } $head = array_shift($components); - $this->image = $head->bandjoin($components)->image; + $joined = $head->bandjoin($components); + + /* Overwrite our pointer with the pointer from the new, joined object. + * We have to adjust the refs, yuk! + */ + $joined->ref(); + $this->unref(); + $this->pointer = $joined->pointer; } /** @@ -1693,7 +1435,8 @@ public function offsetUnset($offset): void { if (is_int($offset) && $offset >= 0 && $offset < $this->bands) { if ($this->bands === 1) { - throw new \BadMethodCallException('Image::offsetUnset: cannot delete final band'); + throw new \BadMethodCallException('Image::offsetUnset: ' . + 'cannot delete final band'); } $components = []; @@ -1709,9 +1452,14 @@ public function offsetUnset($offset): void $head = array_shift($components); if (empty($components)) { - $this->image = $head->image; + $head->ref(); + $this->unref(); + $this->pointer = $head->pointer; } else { - $this->image = $head->bandjoin($components)->image; + $new_image = $head->bandjoin($components); + $new_image->ref(); + $this->unref(); + $this->pointer = $new_image->pointer; } } } @@ -1729,7 +1477,7 @@ public function offsetUnset($offset): void public function add($other, array $options = []): Image { if (self::isImageish($other)) { - return self::call('add', $this, [$other], $options); + return VipsOperation::call('add', $this, [$other], $options); } else { return $this->linear(1, $other, $options); } @@ -1748,7 +1496,7 @@ public function add($other, array $options = []): Image public function subtract($other, array $options = []): Image { if (self::isImageish($other)) { - return self::call('subtract', $this, [$other], $options); + return VipsOperation::call('subtract', $this, [$other], $options); } else { $other = self::mapNumeric($other, function ($value) { return -1 * $value; @@ -1770,7 +1518,7 @@ public function subtract($other, array $options = []): Image public function multiply($other, array $options = []): Image { if (self::isImageish($other)) { - return self::call('multiply', $this, [$other], $options); + return VipsOperation::call('multiply', $this, [$other], $options); } else { return $this->linear($other, 0, $options); } @@ -1789,7 +1537,7 @@ public function multiply($other, array $options = []): Image public function divide($other, array $options = []): Image { if (self::isImageish($other)) { - return self::call('divide', $this, [$other], $options); + return VipsOperation::call('divide', $this, [$other], $options); } else { $other = self::mapNumeric($other, function ($value) { return $value ** -1; @@ -1811,9 +1559,14 @@ public function divide($other, array $options = []): Image public function remainder($other, array $options = []): Image { if (self::isImageish($other)) { - return self::call('remainder', $this, [$other], $options); + return VipsOperation::call('remainder', $this, [$other], $options); } else { - return self::call('remainder_const', $this, [$other], $options); + return VipsOperation::call( + 'remainder_const', + $this, + [$other], + $options + ); } } @@ -1829,7 +1582,7 @@ public function remainder($other, array $options = []): Image */ public function pow($other, array $options = []): Image { - return $this->callEnum($other, 'math2', OperationMath2::POW, $options); + return self::callEnum($other, 'math2', OperationMath2::POW, $options); } /** @@ -1844,7 +1597,7 @@ public function pow($other, array $options = []): Image */ public function wop($other, array $options = []): Image { - return $this->callEnum($other, 'math2', OperationMath2::WOP, $options); + return self::callEnum($other, 'math2', OperationMath2::WOP, $options); } /** @@ -1859,7 +1612,7 @@ public function wop($other, array $options = []): Image */ public function lshift($other, array $options = []): Image { - return $this->callEnum($other, 'boolean', OperationBoolean::LSHIFT, $options); + return self::callEnum($other, 'boolean', OperationBoolean::LSHIFT, $options); } /** @@ -1874,7 +1627,12 @@ public function lshift($other, array $options = []): Image */ public function rshift($other, array $options = []): Image { - return $this->callEnum($other, 'boolean', OperationBoolean::RSHIFT, $options); + return self::callEnum( + $other, + 'boolean', + OperationBoolean::RSHIFT, + $options + ); } /** @@ -1891,7 +1649,7 @@ public function rshift($other, array $options = []): Image public function andimage($other, array $options = []): Image { // phpdoc hates OperationBoolean::AND, so use the string form here - return $this->callEnum($other, 'boolean', 'and', $options); + return self::callEnum($other, 'boolean', 'and', $options); } /** @@ -1907,7 +1665,7 @@ public function andimage($other, array $options = []): Image public function orimage($other, array $options = []): Image { // phpdoc hates OperationBoolean::OR, so use the string form here - return $this->callEnum($other, 'boolean', 'or', $options); + return self::callEnum($other, 'boolean', 'or', $options); } /** @@ -1922,7 +1680,12 @@ public function orimage($other, array $options = []): Image */ public function eorimage($other, array $options = []): Image { - return $this->callEnum($other, 'boolean', OperationBoolean::EOR, $options); + return self::callEnum( + $other, + 'boolean', + OperationBoolean::EOR, + $options + ); } /** @@ -1937,7 +1700,12 @@ public function eorimage($other, array $options = []): Image */ public function more($other, array $options = []): Image { - return $this->callEnum($other, 'relational', OperationRelational::MORE, $options); + return self::callEnum( + $other, + 'relational', + OperationRelational::MORE, + $options + ); } /** @@ -1952,7 +1720,12 @@ public function more($other, array $options = []): Image */ public function moreEq($other, array $options = []): Image { - return $this->callEnum($other, 'relational', OperationRelational::MOREEQ, $options); + return self::callEnum( + $other, + 'relational', + OperationRelational::MOREEQ, + $options + ); } /** @@ -1967,7 +1740,12 @@ public function moreEq($other, array $options = []): Image */ public function less($other, array $options = []): Image { - return $this->callEnum($other, 'relational', OperationRelational::LESS, $options); + return self::callEnum( + $other, + 'relational', + OperationRelational::LESS, + $options + ); } /** @@ -1982,7 +1760,12 @@ public function less($other, array $options = []): Image */ public function lessEq($other, array $options = []): Image { - return $this->callEnum($other, 'relational', OperationRelational::LESSEQ, $options); + return self::callEnum( + $other, + 'relational', + OperationRelational::LESSEQ, + $options + ); } /** @@ -1997,7 +1780,12 @@ public function lessEq($other, array $options = []): Image */ public function equal($other, array $options = []): Image { - return $this->callEnum($other, 'relational', OperationRelational::EQUAL, $options); + return self::callEnum( + $other, + 'relational', + OperationRelational::EQUAL, + $options + ); } /** @@ -2012,7 +1800,12 @@ public function equal($other, array $options = []): Image */ public function notEq($other, array $options = []): Image { - return $this->callEnum($other, 'relational', OperationRelational::NOTEQ, $options); + return self::callEnum( + $other, + 'relational', + OperationRelational::NOTEQ, + $options + ); } /** @@ -2048,9 +1841,14 @@ public function bandjoin($other, array $options = []): Image /* We can't use self::bandjoin(), that would just recurse. */ if ($is_const) { - return self::call('bandjoin_const', $this, [$other], $options); + return VipsOperation::call( + 'bandjoin_const', + $this, + [$other], + $options + ); } else { - return self::call( + return VipsOperation::call( 'bandjoin', null, [array_merge([$this], $other)], @@ -2107,7 +1905,7 @@ public function bandrank($other, array $options = []): Image $other = (array) $other; } - return self::call('bandrank', $this, $other, $options); + return VipsOperation::call('bandrank', $this, $other, $options); } /** @@ -2130,16 +1928,18 @@ public function composite($other, $mode, array $options = []): Image } else { $other = (array) $other; } + if (!is_array($mode)) { $mode = [$mode]; } + # composite takes an arrayint, but it's really an array of blend modes + # gvalue doesn't know this, so we must do name -> enum value mapping $mode = array_map(function ($x) { - // Use BlendMode::OVER if a non-existent value is given. - return self::$blendModeToInt[$x] ?? BlendMode::OVER; + return GValue::toEnum(Config::gtypes("VipsBlendMode"), $x); }, $mode); - return self::call( + return VipsOperation::call( 'composite', null, [array_merge([$this], $other), $mode], @@ -2199,7 +1999,6 @@ public function ifthenelse($then, $else, array $options = []): Image * match each other first, and only if they are both constants do we * match to $this. */ - $match_image = null; foreach ([$then, $else, $this] as $item) { if ($item instanceof Image) { @@ -2209,14 +2008,19 @@ public function ifthenelse($then, $else, array $options = []): Image } if (!($then instanceof Image)) { - $then = self::imageize($match_image, $then); + $then = $match_image->imageize($then); } if (!($else instanceof Image)) { - $else = self::imageize($match_image, $else); + $else = $match_image->imageize($else); } - return self::call('ifthenelse', $this, [$then, $else], $options); + return VipsOperation::call( + 'ifthenelse', + $this, + [$then, $else], + $options + ); } /** diff --git a/src/ImageAutodoc.php b/src/ImageAutodoc.php index e917baf..f9537ec 100644 --- a/src/ImageAutodoc.php +++ b/src/ImageAutodoc.php @@ -697,6 +697,6 @@ * @property float $yres Vertical resolution in pixels/mm * @property string $filename Image filename */ -abstract class ImageAutodoc +abstract class ImageAutodoc extends VipsObject { } diff --git a/src/Interpolate.php b/src/Interpolate.php new file mode 100644 index 0000000..c70b327 --- /dev/null +++ b/src/Interpolate.php @@ -0,0 +1,104 @@ + + * @copyright 2016 John Cupitt + * @license https://opensource.org/licenses/MIT MIT + * @link https://github.com/libvips/php-vips + */ + +namespace Jcupitt\Vips; + +/** + * This class holds a pointer to a VipsInterpolate (the libvips + * base class for interpolators) and manages argument introspection and + * operation call. + * + * @category Images + * @package Jcupitt\Vips + * @author John Cupitt + * @copyright 2016 John Cupitt + * @license https://opensource.org/licenses/MIT MIT + * @link https://github.com/libvips/php-vips + */ +class Interpolate extends VipsObject +{ + /** + * A pointer to the underlying Interpolate. This is the same as the + * GObject, just cast to Interpolate to help FFI. + * + * @internal + */ + public \FFI\CData $pointer; + + public function __construct($pointer) + { + $this->pointer = Config::ffi()-> + cast(Config::ctypes("VipsInterpolate"), $pointer); + + parent::__construct($pointer); + } + + /** + * Make an interpolator from a name. + * + * @param string $name Name of the interpolator. + * + * Possible interpolators are: + * - `'nearest'`: Use nearest neighbour interpolation. + * - `'bicubic'`: Use bicubic interpolation. + * - `'bilinear'`: Use bilinear interpolation (the default). + * - `'nohalo'`: Use Nohalo interpolation. + * - `'lbb'`: Use LBB interpolation. + * - `'vsqbs'`: Use the VSQBS interpolation. + * + * @return resource|null The interpolator, or null on error. + */ + public static function newFromName($name) + { + $pointer = Config::ffi()->vips_interpolate_new($name); + if ($pointer == null) { + Config::error(); + } + + return new Interpolate($pointer); + } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: expandtab sw=4 ts=4 fdm=marker + * vim<600: expandtab sw=4 ts=4 + */ diff --git a/src/Introspect.php b/src/Introspect.php new file mode 100644 index 0000000..b4aca43 --- /dev/null +++ b/src/Introspect.php @@ -0,0 +1,252 @@ + + * @copyright 2016 John Cupitt + * @license https://opensource.org/licenses/MIT MIT + * @link https://github.com/libvips/php-vips + */ + +namespace Jcupitt\Vips; + +/** + * Introspect a VipsOperation and discover everything we can. This is called + * on demand once per operation and the results held in a cache. + * + * @category Images + * @package Jcupitt\Vips + * @author John Cupitt + * @copyright 2016 John Cupitt + * @license https://opensource.org/licenses/MIT MIT + * @link https://github.com/libvips/php-vips + */ +class Introspect +{ + /** + * The operation nickname (eg. "add"). + */ + public string $name; + + /** + * The operation description (eg. "add two images"). + */ + public string $description; + + /** + * The operation flags (eg. SEQUENTIAL | DEPRECATED). + */ + public int $flags; + + /** + * A hash from arg name to a hash of details. + */ + public array $arguments; + + /** + * Arrays of arg names, in order and by category, eg. $this->required_input + * = ["filename"]. + */ + public array $required_input; + public array $optional_input; + public array $required_output; + public array $optional_output; + + /** + * The name of the arg this operation uses as "this". + */ + public string $member_this; + + /** + * And the required input args, without the "this". + */ + public array $method_args; + + public function __construct($name) + { + $this->name = $name; + + $operation = VipsOperation::newFromName($name); + + $this->description = $operation->getDescription(); + $flags = Config::ffi()->vips_operation_get_flags($operation->pointer); + + $p_names = Config::ffi()->new("char**[1]"); + $p_flags = Config::ffi()->new("int*[1]"); + $p_n_args = Config::ffi()->new("int[1]"); + $result = Config::ffi()->vips_object_get_args( + \FFI::cast(Config::ctypes("VipsObject"), $operation->pointer), + $p_names, + $p_flags, + $p_n_args + ); + if ($result != 0) { + error(); + } + $p_names = $p_names[0]; + $p_flags = $p_flags[0]; + $n_args = $p_n_args[0]; + + # make a hash from arg name to flags + $argumentFlags = []; + for ($i = 0; $i < $n_args; $i++) { + if (($p_flags[$i] & ArgumentFlags::CONSTRUCT) != 0) { + # libvips uses '-' to separate parts of arg names, but we + # need '_' for php + $name = \FFI::string($p_names[$i]); + $name = str_replace("-", "_", $name); + $argumentFlags[$name] = $p_flags[$i]; + } + } + + # make a hash from arg name to detailed arg info + $this->arguments = []; + foreach ($argumentFlags as $name => $flags) { + $this->arguments[$name] = [ + "name" => $name, + "flags" => $flags, + "blurb" => $operation->getBlurb($name), + "type" => $operation->getType($name) + ]; + } + + # split args into categories + $this->required_input = []; + $this->optional_input = []; + $this->required_output = []; + $this->optional_output = []; + + foreach ($this->arguments as $name => $details) { + $flags = $details["flags"]; + $blurb = $details["blurb"]; + $type = $details["type"]; + $typeName = Config::ffi()->g_type_name($type); + + if (($flags & ArgumentFlags::INPUT) && + ($flags & ArgumentFlags::REQUIRED) && + !($flags & ArgumentFlags::DEPRECATED)) { + $this->required_input[] = $name; + + # required inputs which we MODIFY are also required outputs + if ($flags & ArgumentFlags::MODIFY) { + $this->required_output[] = $name; + } + } + + if (($flags & ArgumentFlags::OUTPUT) && + ($flags & ArgumentFlags::REQUIRED) && + !($flags & ArgumentFlags::DEPRECATED)) { + $this->required_output[] = $name; + } + + # we let deprecated optional args through, but warn about them + # if they get used, see below + if (($flags & ArgumentFlags::INPUT) && + !($flags & ArgumentFlags::REQUIRED)) { + $this->optional_input[] = $name; + } + + if (($flags & ArgumentFlags::OUTPUT) && + !($flags & ArgumentFlags::REQUIRED)) { + $this->optional_output[] = $name; + } + } + + # find the first required input image arg, if any ... that will be self + $this->member_this = ""; + foreach ($this->required_input as $name) { + $type = $this->arguments[$name]["type"]; + if ($type == Config::gtypes("VipsImage")) { + $this->member_this = $name; + break; + } + } + + # method args are required args, but without the image they are a + # method on + $this->method_args = $this->required_input; + if ($this->member_this != "") { + $index = array_search($this->member_this, $this->method_args); + array_splice($this->method_args, $index); + } + + Utils::debugLog($name, ['introspect' => strval($this)]); + } + + public function __toString() + { + $result = ""; + + $result .= "$this->name:\n"; + + foreach ($this->arguments as $name => $details) { + $flags = $details["flags"]; + $blurb = $details["blurb"]; + $type = $details["type"]; + $typeName = Config::ffi()->g_type_name($type); + + $result .= " $name:\n"; + + $result .= " flags: $flags\n"; + foreach (ArgumentFlags::NAMES as $name => $flag) { + if ($flags & $flag) { + $result .= " $name\n"; + } + } + + $result .= " blurb: $blurb\n"; + $result .= " type: $typeName\n"; + } + + $info = implode(", ", $this->required_input); + $result .= "required input: $info\n"; + $info = implode(", ", $this->required_output); + $result .= "required output: $info\n"; + $info = implode(", ", $this->optional_input); + $result .= "optional input: $info\n"; + $info = implode(", ", $this->optional_output); + $result .= "optional output: $info\n"; + $result .= "member_this: $this->member_this\n"; + $info = implode(", ", $this->method_args); + $result .= "method args: $info\n"; + + return $result; + } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: expandtab sw=4 ts=4 fdm=marker + * vim<600: expandtab sw=4 ts=4 + */ diff --git a/src/Utils.php b/src/Utils.php index 2f4fe53..d5ee068 100644 --- a/src/Utils.php +++ b/src/Utils.php @@ -92,7 +92,7 @@ public static function errorLog(string $message, \Exception $exception): void */ public static function typeFromName(string $name): int { - return vips_type_from_name($name); + return Config::ffi()->g_type_from_name($name); } } diff --git a/src/VipsObject.php b/src/VipsObject.php new file mode 100644 index 0000000..2a027cf --- /dev/null +++ b/src/VipsObject.php @@ -0,0 +1,187 @@ + + * @copyright 2016 John Cupitt + * @license https://opensource.org/licenses/MIT MIT + * @link https://github.com/libvips/php-vips + */ + +namespace Jcupitt\Vips; + +/** + * This class holds a pointer to a VipsObject (the libvips base class) and + * manages properties. + * + * @category Images + * @package Jcupitt\Vips + * @author John Cupitt + * @copyright 2016 John Cupitt + * @license https://opensource.org/licenses/MIT MIT + * @link https://github.com/libvips/php-vips + */ +abstract class VipsObject extends GObject +{ + /** + * A pointer to the underlying VipsObject. + * + * @internal + */ + private \FFI\CData $pointer; + + /** + * A pointer to the underlying GObject. This is the same as the + * VipsObject, just cast. + * + * @internal + */ + private \FFI\CData $gObject; + + public function __construct($pointer) + { + $this->pointer = Config::ffi()-> + cast(Config::ctypes("VipsObject"), $pointer); + $this->gObject = Config::ffi()-> + cast(Config::ctypes("GObject"), $pointer); + + parent::__construct($pointer); + } + + // print a table of all active vipsobjects ... handy for debugging + public static function printAll() + { + Config::ffi()->vips_object_print_all(); + } + + public function getDescription() + { + return Config::ffi()->vips_object_get_description($this->pointer); + } + + // get the pspec for a property + // NULL for no such name + // very slow! avoid if possible + // FIXME add a cache for this thing + public function getPspec(string $name) + { + $pspec = Config::ffi()->new("GParamSpec*[1]"); + $argument_class = Config::ffi()->new("VipsArgumentClass*[1]"); + $argument_instance = Config::ffi()->new("VipsArgumentInstance*[1]"); + $result = Config::ffi()->vips_object_get_argument( + $this->pointer, + $name, + $pspec, + $argument_class, + $argument_instance + ); + + if ($result != 0) { + return null; + } else { + return $pspec[0]; + } + } + + // get the type of a property from a VipsObject + // 0 if no such property + public function getType(string $name) + { + $pspec = $this->getPspec($name); + if (\FFI::isNull($pspec)) { + # need to clear any error, this is horrible + Config::ffi()->vips_error_clear(); + return 0; + } else { + return $pspec->value_type; + } + } + + public function getBlurb(string $name): string + { + $pspec = $this->getPspec($name); + return Config::ffi()->g_param_spec_get_blurb($pspec); + } + + public function getArgumentDescription(string $name): string + { + $pspec = $this->getPspec($name); + return Config::ffi()->g_param_spec_get_description($pspec); + } + + public function get(string $name) + { + $gvalue = new GValue(); + $gvalue->setType($this->getType($name)); + + Config::ffi()-> + g_object_get_property($this->gObject, $name, $gvalue->pointer); + $value = $gvalue->get(); + + Utils::debugLog("get", [$name => var_export($value, true)]); + + return $value; + } + + public function set(string $name, $value) + { + Utils::debugLog("set", [$name => $value]); + + $gvalue = new GValue(); + $gvalue->setType($this->getType($name)); + $gvalue->set($value); + + Config::ffi()-> + g_object_set_property($this->gObject, $name, $gvalue->pointer); + } + + public function setString(string $string_options) + { + $result = Config::ffi()-> + vips_object_set_from_string($this->pointer, $string_options); + + return $result == 0; + } + + public function unrefOutputs() + { + Config::ffi()->vips_object_unref_outputs($this->pointer); + } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: expandtab sw=4 ts=4 fdm=marker + * vim<600: expandtab sw=4 ts=4 + */ diff --git a/src/VipsOperation.php b/src/VipsOperation.php new file mode 100644 index 0000000..606792d --- /dev/null +++ b/src/VipsOperation.php @@ -0,0 +1,413 @@ + + * @copyright 2016 John Cupitt + * @license https://opensource.org/licenses/MIT MIT + * @link https://github.com/libvips/php-vips + */ + +namespace Jcupitt\Vips; + +/** + * This class holds a pointer to a VipsOperation (the libvips operation base + * class) and manages argument introspection and operation call. + * + * @category Images + * @package Jcupitt\Vips + * @author John Cupitt + * @copyright 2016 John Cupitt + * @license https://opensource.org/licenses/MIT MIT + * @link https://github.com/libvips/php-vips + */ +class VipsOperation extends VipsObject +{ + /** + * A pointer to the underlying VipsOperation. This is the same as the + * GObject, just cast to VipsOperation to help FFI. + * + * @internal + */ + public \FFI\CData $pointer; + + /** + * Introspection data for this operation. + */ + public Introspect $introspect; + + public function __construct($pointer) + { + $this->pointer = Config::ffi()-> + cast(Config::ctypes("VipsOperation"), $pointer); + + parent::__construct($pointer); + } + + public static function newFromName($name) + { + $pointer = Config::ffi()->vips_operation_new($name); + if ($pointer == null) { + Config::error(); + } + + return new VipsOperation($pointer); + } + + public function setMatch($name, $match_image, $value) + { + $flags = $this->introspect->arguments[$name]["flags"]; + $gtype = $this->introspect->arguments[$name]["type"]; + + if ($match_image != null) { + if ($gtype == Config::gtypes("VipsImage")) { + $value = $match_image->imageize($value); + } elseif ($gtype == Config::gtypes("VipsArrayImage") && + is_array($value)) { + $new_value = []; + foreach ($value as $x) { + $new_value[] = $match_image->imageize($x); + } + $value = $new_value; + } + } + + # MODIFY args need to be copied before they are set + if (($flags & ArgumentFlags::MODIFY) != 0) { + # logger.debug('copying MODIFY arg %s', name) + # make sure we have a unique copy + $value = $value->copyMemory(); + } + + parent::set($name, $value); + } + + private static function introspect($name): Introspect + { + static $cache = []; + + if (!array_key_exists($name, $cache)) { + $cache[$name] = new Introspect($name); + } + + return $cache[$name]; + } + + private static function findInside($predicate, $x) + { + if ($predicate($x)) { + return $x; + } + + if (is_array($x)) { + foreach ($x as $y) { + $result = self::findInside($predicate, $y); + + if ($result != null) { + return $result; + } + } + } + + return null; + } + + /** + * Unwrap an array of stuff ready to pass down to the vips_ layer. We + * swap instances of Image for the ffi pointer. + * + * @param array $result Unwrap this. + * + * @return array $result unwrapped, ready for vips. + * + * @internal + */ + private static function unwrap(array $result): array + { + array_walk_recursive($result, function (&$value) { + if ($value instanceof Image) { + $value = $value->image; + } + }); + + return $result; + } + + /** + * Is $value a VipsImage. + * + * @param mixed $value The thing to test. + * + * @return bool true if this is a ffi VipsImage*. + * + * @internal + */ + private static function isImagePointer($value): bool + { + return $value instanceof \FFI\CData && + \FFI::typeof($value) == Config::ctypes("VipsImage"); + } + + /** + * Wrap up the result of a vips_ call ready to return it to PHP. We do + * two things: + * + * - If the array is a singleton, we strip it off. For example, many + * operations return a single result and there's no sense handling + * this as an array of values, so we transform ['out' => x] -> x. + * + * - Any VipsImage resources are rewrapped as instances of Image. + * + * @param mixed $result Wrap this up. + * + * @return mixed $result, but wrapped up as a php class. + * + * @internal + */ + private static function wrapResult($result) + { + if (!is_array($result)) { + $result = ['x' => $result]; + } + + array_walk_recursive($result, function (&$item) { + if (self::isImagePointer($item)) { + $item = new Image($item); + } + }); + + if (count($result) === 1) { + $result = array_shift($result); + } + + return $result; + } + + /** + * Check the result of a vips_ call for an error, and throw an exception + * if we see one. + * + * This won't work for things like __get where a non-array return can be + * a valid return. + * + * @param mixed $result Test this. + * + * @throws Exception + * + * @return void + * + * @internal + */ + private static function errorIsArray($result): void + { + if (!is_array($result)) { + Config::error(); + } + } + + /** + * Call any vips operation. The final element of $arguments can be + * (but doesn't have to be) an array of options to pass to the operation. + * + * We can't have a separate arg for the options since this will be run from + * __call(), which cannot know which args are required and which are + * optional. See call() below for a version with the options broken out. + * + * @param string $operation_name The operation name. + * @param Image|null $instance The instance this operation is being invoked + * from. + * @param array $arguments An array of arguments to pass to the + * operation. + * + * @throws Exception + * + * @return mixed The result(s) of the operation. + */ + public static function callBase( + string $operation_name, + ?Image $instance, + array $arguments + ) { + Utils::debugLog($operation_name, [ + 'instance' => $instance, + 'arguments' => $arguments + ]); + + $operation = self::newFromName($operation_name); + $operation->introspect = self::introspect($operation_name); + + /* the first image argument is the thing we expand constants to + * match ... look inside tables for images, since we may be passing + * an array of images as a single param. + */ + if ($instance != null) { + $match_image = $instance; + } else { + $match_image = self::findInside( + fn($x) => $x instanceof Image, + $arguments + ); + } + + /* Because of the way php callStatic works, we can sometimes be given + * an instance even when no instance was given. + * + * We must loop over the required args and set them from the supplied + * args, using instance if required, and only check the nargs after + * this pass. + */ + $n_required = count($operation->introspect->required_input); + $n_supplied = count($arguments); + $used_instance = false; + $n_used = 0; + foreach ($operation->introspect->required_input as $name) { + if ($name == $operation->introspect->member_this) { + if (!$instance) { + $operation->unrefOutputs(); + Config::error("instance argument not supplied"); + } + $operation->setMatch($name, $match_image, $instance); + $used_instance = true; + } elseif ($n_used < $n_supplied) { + $operation->setMatch($name, $match_image, $arguments[$n_used]); + $n_used += 1; + } else { + $operation->unrefOutputs(); + Config::error("$n_required arguments required, " . + "but $n_supplied supplied"); + } + } + + /* If there's one extra arg and it's an array, use it as our options. + */ + $options = []; + if ($n_supplied == $n_used + 1 && is_array($arguments[$n_used])) { + $options = array_pop($arguments); + $n_supplied -= 1; + } + + if ($n_supplied != $n_used) { + $operation->unrefOutputs(); + Config::error("$n_required arguments required, " . + "but $n_supplied supplied"); + } + + /* Set optional. + */ + foreach ($options as $name => $value) { + if (!in_array($name, $operation->introspect->optional_input) && + !in_array($name, $operation->introspect->optional_output)) { + $operation->unrefOutputs(); + Config::error("optional argument '$name' does not exist"); + } + + $operation->setMatch($name, $match_image, $value); + } + + /* Build the operation + */ + $pointer = Config::ffi()-> + vips_cache_operation_build($operation->pointer); + if ($pointer == null) { + $operation->unrefOutputs(); + Config::error(); + } + $operation = new VipsOperation($pointer); + $operation->introspect = self::introspect($operation_name); + + # TODO .. need to attach input refs to output, see _find_inside in + # pyvips + + /* Fetch required output args (and modified input args). + */ + $result = []; + foreach ($operation->introspect->required_output as $name) { + $result[$name] = $operation->get($name); + } + + /* Any optional output args. + */ + $option_keys = array_keys($options); + foreach ($operation->introspect->optional_output as $name) { + if (in_array($name, $option_keys)) { + $result[$name] = $operation->get($name); + } + } + + /* Free any outputs we've not used. + */ + $operation->unrefOutputs(); + + $result = self::wrapResult($result); + + Utils::debugLog($name, ['result' => var_export($result, true)]); + + return $result; + } + + /** + * Call any vips operation, with an explicit set of options. This is more + * convenient than callBase() if you have a set of known options. + * + * @param string $name The operation name. + * @param Image|null $instance The instance this operation is being invoked + * from. + * @param array $arguments An array of arguments to pass to the + * operation. + * @param array $options An array of optional arguments to pass to + * the operation. + * + * @throws Exception + * + * @return mixed The result(s) of the operation. + */ + public static function call( + string $name, + ?Image $instance, + array $arguments, + array $options = [] + ) { + return self::callBase( + $name, + $instance, + array_merge($arguments, [$options]) + ); + } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: expandtab sw=4 ts=4 fdm=marker + * vim<600: expandtab sw=4 ts=4 + */ diff --git a/tests/NewTest.php b/tests/NewTest.php index 3a97c6b..bf72426 100644 --- a/tests/NewTest.php +++ b/tests/NewTest.php @@ -57,14 +57,6 @@ public function testVipsNewFromImage() $this->assertEquals($image2->avg(), 2); } - public function testVipsFindLoad() - { - $filename = __DIR__ . '/images/img_0076.jpg'; - $loader = Vips\Image::findLoad($filename); - - $this->assertEquals($loader, 'VipsForeignLoadJpegFile'); - } - public function testVipsNewFromBuffer() { $filename = __DIR__ . '/images/img_0076.jpg'; @@ -76,15 +68,6 @@ public function testVipsNewFromBuffer() $this->assertEquals($image->bands, 3); } - public function testVipsFindLoadBuffer() - { - $filename = __DIR__ . '/images/img_0076.jpg'; - $buffer = file_get_contents($filename); - $loader = Vips\Image::findLoadBuffer($buffer); - - $this->assertEquals($loader, 'VipsForeignLoadJpegBuffer'); - } - public function testVipsCopyMemory() { $filename = __DIR__ . '/images/img_0076.jpg'; diff --git a/tests/ShortcutTest.php b/tests/ShortcutTest.php index da251a2..a4cc80c 100644 --- a/tests/ShortcutTest.php +++ b/tests/ShortcutTest.php @@ -207,6 +207,8 @@ public function testOffsetSet() // replace band with image $test = $image->copy(); $test[1] = $base; + $avg = $test->avg(); + $this->assertTrue(abs($avg - 2.666) < 0.001); $this->assertEquals($test->bands, 3); $this->assertEquals($test[0]->avg(), 2); $this->assertEquals($test[1]->avg(), 2); diff --git a/tests/WriteTest.php b/tests/WriteTest.php index cc27b81..ed592f7 100644 --- a/tests/WriteTest.php +++ b/tests/WriteTest.php @@ -52,7 +52,7 @@ public function testVipsWriteToBuffer() $filename = __DIR__ . '/images/img_0076.jpg'; $image = Vips\Image::newFromFile($filename, ['shrink' => 8]); - $buffer1 = $image->writeToBuffer('.jpg'); + $buffer1 = $image->writeToBuffer('.jpg', ['Q' => 75]); $output_filename = $this->tmp('.jpg'); $image->writeToFile($output_filename); $buffer2 = file_get_contents($output_filename); @@ -68,6 +68,18 @@ public function testVipsWriteToMemory() $this->assertEquals($binaryStr, $memStr); } + + public function testVipsWriteToArray() + { + $filename = __DIR__ . '/images/img_0076.jpg'; + $image = Vips\Image::newFromFile($filename, ['shrink' => 8]); + $array = $image->crop(0, 0, 2, 2)->writeToArray(); + + $this->assertEquals( + $array, + [34, 39, 35, 44, 49, 45, 67, 52, 49, 120, 105, 102] + ); + } } /* From 3a74231a91a38644345fa56ecf6940fdfd5750bd Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 10 Mar 2022 08:51:08 +0000 Subject: [PATCH 028/115] revise README for 2.x --- README.md | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 592ef6b..1aa3614 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Build Status](https://travis-ci.org/libvips/php-vips.svg?branch=master)](https://travis-ci.org/libvips/php-vips) `php-vips` is a binding for [libvips](https://github.com/libvips/libvips) 8.7 -and later for PHP 7.4 and later. +and later that runs on PHP 7.4 and later. libvips is fast and needs little memory. The [`vips-php-bench`](https://github.com/jcupitt/php-vips-bench) repository @@ -20,7 +20,7 @@ destination in a set of small fragments. ### Install You need to [install the libvips -library](https://libvips.github.io/libvips/install.html). It's in the linux +library](https://www.libvips.org/install.html). It's in the linux package managers, homebrew and MacPorts, and there are Windows binaries on the vips website. For example, on Debian: @@ -34,7 +34,9 @@ Or macOS: brew install vips ``` -Then add vips to your `composer.json`: +You'll need to [enable FFI in your +PHP](https://www.php.net/manual/en/ffi.configuration.php), then add vips +to your `composer.json`: ``` "require": { @@ -71,6 +73,21 @@ $ ./try1.php ~/pics/k2.jpg x.tif See `examples/`. We have a [complete set of formatted API docs](https://libvips.github.io/php-vips/docs/classes/Jcupitt-Vips-Image.html). + +### How it works + +php-vips uses [php-ffi](https://www.php.net/manual/en/book.ffi.php) to +call directly into the libvips binary. It introspects the library binary +and presents the methods it finds as members of the `Image` class. + +This means that the API you see depends on the version of libvips that +php-vips finds at runtime, and not on php-vips. php-vips documentation assumes +you are using the latest stable version of the libvips library. + +The previous php-vips version that relied on a binary extension +and not on php-ffi is still available and supported in [the 1.x +branch](https://github.com/libvips/php-vips/tree/1.x). + ### Introduction to the API Almost all methods return a new image as the result, so you can chain them. @@ -130,19 +147,7 @@ And look in `docs/`. There are around 300 operations in the library, see the vips docs for an introduction: -https://libvips.github.io/libvips/API/current - -### TODO after merge - -- Support preloading, see https://www.php.net/manual/en/class.ffi.php - -- Rewrite the enum and doc generator in php. - -- Add source/target API - -- Add progress callbacks etc. - -- Add mutable. +https://libvips.org/API/current ### Test and install From b3f4813609f53c9a57b9805f1fab5b6cac319e65 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 10 Mar 2022 08:56:09 +0000 Subject: [PATCH 029/115] minor doc revision --- src/Image.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Image.php b/src/Image.php index c6de83a..5a77d99 100644 --- a/src/Image.php +++ b/src/Image.php @@ -42,7 +42,7 @@ * This class represents a Vips image object. * * This module provides a binding for the [vips image processing - * library](https://libvips.org) version 8.7 and later, and required PHP 7.4 + * library](https://libvips.org) version 8.7 and later, and requires PHP 7.4 * and later. * * # Example @@ -66,7 +66,7 @@ * * ``` * "require": { - * "jcupitt/vips" : "@dev" + * "jcupitt/vips" : "2.0.0" * } * ``` * From fb68c96893fdbc56d9c5e6b8f327db3786519295 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 11 Mar 2022 10:51:44 +0000 Subject: [PATCH 030/115] remove dbg from an example --- examples/watermark-image.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/watermark-image.php b/examples/watermark-image.php index 195343f..4e1ee83 100755 --- a/examples/watermark-image.php +++ b/examples/watermark-image.php @@ -4,7 +4,7 @@ require __DIR__ . '/vendor/autoload.php'; use Jcupitt\Vips; -Vips\Config::setLogger(new Vips\DebugLogger()); +#Vips\Config::setLogger(new Vips\DebugLogger()); if (count($argv) != 4) { echo("usage: ./watermark.php input-image output-image watermark-image\n"); From 16451e8f4d4f042279296eea2ffa68dd990fbb2a Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 19 Mar 2022 10:38:15 +0000 Subject: [PATCH 031/115] make watermark-text work for still images --- examples/watermark-text.php | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/examples/watermark-text.php b/examples/watermark-text.php index 654cada..9b57f26 100755 --- a/examples/watermark-text.php +++ b/examples/watermark-text.php @@ -11,18 +11,25 @@ exit(1); } -// we can stream the main image, and we want all frames $image = Vips\Image::newFromFile($argv[1], [ 'access' => 'sequential', - 'n' => -1 ]); +$page_height = $image->height; + +// is this an animated image? open all pages +if ($image->getType("n-pages") != 0) { + $image = Vips\Image::newFromFile($argv[1], [ + 'access' => 'sequential', + 'n' => -1 + ]); + + // the size of each frame + $page_height = $image->get('page-height'); +} $output_filename = $argv[2]; $text = $argv[3]; -// the size of each frame -$page_height = $image->get('page-height'); - $text_mask = Vips\Image::text($text, [ 'width' => $image->width, 'dpi' => 150 From d517e8526e0cc033ffab3b1fe3325a587b517236 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 24 Mar 2022 21:24:44 +0000 Subject: [PATCH 032/115] update links --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1aa3614..908cebf 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ $ ./try1.php ~/pics/k2.jpg x.tif ``` See `examples/`. We have a [complete set of formatted API -docs](https://libvips.github.io/php-vips/docs/classes/Jcupitt-Vips-Image.html). +docs](https://libvips.github.io/php-vips/classes/Jcupitt-Vips-Image.html). ### How it works @@ -136,7 +136,9 @@ For example: $image->writeToFile("fred.jpg", ["Q" => 90]); ``` -`php-vips` comes [with full API docs](https://libvips.github.io/php-vips/docs/classes/Jcupitt.Vips.Image.html). To regenerate these from your sources, type: +`php-vips` comes [with full API +docs](https://libvips.github.io/php-vips/classes/Jcupitt-Vips-Image.html). +To regenerate these from your sources, type: ``` $ vendor/bin/phpdoc From ee6e1cf8b5acf1245c4acf5bd970bc8ae2d003da Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 28 Mar 2022 18:06:03 +0100 Subject: [PATCH 033/115] fix libname on macos The OS name was not right for macOS. Released as 2.0.1. Thanks andrefelipe. See https://github.com/libvips/php-vips/issues/140 --- CHANGELOG.md | 4 ++++ src/Config.php | 1 + 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9139f34..d990973 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to `:vips` will be documented in this file. +## 2.0.1 - 2022-1-20 + +- Fix library name on macOS [andrefelipe] + ## 2.0.0 - 2022-1-20 Rewritten to use PHP FFI to call into the libvips library rather than a binary diff --git a/src/Config.php b/src/Config.php index ed720df..b523038 100644 --- a/src/Config.php +++ b/src/Config.php @@ -286,6 +286,7 @@ private static function libraryName($name, $abi) return "$name-$abi.dll"; case "OSX": + case "Darwin": return "$name.$abi.dylib"; default: From 446c0b58a53de654bac5aaad6b62afac5ae21a00 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 6 Apr 2022 17:59:29 +0100 Subject: [PATCH 034/115] note --no-install-recommends for debian See https://github.com/libvips/php-vips/issues/141 --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 908cebf..f3a4d64 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,11 @@ package managers, homebrew and MacPorts, and there are Windows binaries on the vips website. For example, on Debian: ``` -sudo apt-get install libvips-dev +sudo apt-get install --no-install-recommends libvips42 ``` +(`--no-install-recommends` stops Debian installing a *lot* of extra packages) + Or macOS: ``` From bc3061bee8549cf232220e251596048bb8ad87da Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 14 Apr 2022 11:19:54 +0100 Subject: [PATCH 035/115] fix string_options code Ooops, it was only half-implemented. Fixes eg. `newFromFile("x.tif[page=2]");`. --- CHANGELOG.md | 4 ++++ src/VipsOperation.php | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d990973..3a3a719 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to `:vips` will be documented in this file. +## 2.0.2 - 2022-4-14 + +- Fix extra optional string args on file open + ## 2.0.1 - 2022-1-20 - Fix library name on macOS [andrefelipe] diff --git a/src/VipsOperation.php b/src/VipsOperation.php index 606792d..5148e42 100644 --- a/src/VipsOperation.php +++ b/src/VipsOperation.php @@ -320,6 +320,15 @@ public static function callBase( "but $n_supplied supplied"); } + /* set any string options before any args so they can't be + * overridden. + */ + if (array_key_exists("string_options", $options)) { + $string_options = $options["string_options"]; + unset($options["string_options"]); + $operation->setString($string_options); + } + /* Set optional. */ foreach ($options as $name => $value) { From 3c178b30521736136e0368d2858848bf4e6e5f01 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 21 Apr 2022 09:25:07 +0100 Subject: [PATCH 036/115] note preload and ffi implications in README thanks @talisto see https://github.com/libvips/php-vips/issues/123 --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index f3a4d64..6ada06b 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,13 @@ to your `composer.json`: } ``` +php-vips does not yet support preloading, so you need to enable FFI globally. +This has some security implications, since anyone who can run php on your +server can use it to make calls off into any native library they can find. + +Of course if attackers are running their own PHP code on your webserver you +are probably already toast, unfortunately. + ### Example ```php From d8d0d6336a9b7c2de4d7b379eef440c9c918cc90 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Mon, 4 Jul 2022 16:50:23 +0200 Subject: [PATCH 037/115] Fix Windows and 32-bit support (#146) * Fix Windows and 32-bit support - Move FFI functions to a separate class. - Split the GLib and GObject symbols into a separate string. - Ensure `g_*` functions are called via `libglib-2.0-0.dll` or `libgobject-2.0-0.dll` on Windows. - Fix 32-bit support, use `PHP_INT_SIZE` to determine `GType`. - Remove debug code in `GValue::toEnum`. - Prefer to call `cast` on the static `\FFI` class. - Remove `Config::error()` in favor of `throw new Exception()`, move error buffer logic to `Vips\Exception`. - Remove `GsfOutputCsvQuotingMode` class. - Remove `Config::printAll`, already exists as `VipsObject::printAll`. - Move `Config::filenameGetFilename` and `Config::filenameGetOptions` to Utils class. * Cleanup * Fix `FFI::libraryName` * Add missing type declarations * Revert "Move FFI functions to a separate class" To make reviewing easier. * Ensure exception is thrown with the error buffer --- README.md | 1 - composer.json | 9 +- examples/generate_phpdoc.py | 2 +- phpdoc.xml | 6 +- src/Config.php | 458 ++++++++++++++++---------------- src/Exception.php | 11 + src/GObject.php | 10 +- src/GValue.php | 124 +++++---- src/GsfOutputCsvQuotingMode.php | 54 ---- src/Image.php | 112 ++++---- src/ImageAutodoc.php | 8 +- src/Interpolate.php | 14 +- src/Introspect.php | 41 ++- src/Utils.php | 28 +- src/VipsObject.php | 54 ++-- src/VipsOperation.php | 74 ++---- 16 files changed, 478 insertions(+), 528 deletions(-) delete mode 100644 src/GsfOutputCsvQuotingMode.php diff --git a/README.md b/README.md index 6ada06b..93ecce9 100644 --- a/README.md +++ b/README.md @@ -174,4 +174,3 @@ $ vendor/bin/phpdoc $ cd src $ ../examples/generate_phpdoc.py ``` - diff --git a/composer.json b/composer.json index 2c61251..f007871 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "php-parallel-lint/php-parallel-lint": "^1.3", "phpdocumentor/shim": "^3.3", "phpunit/phpunit": "^9.5", - "squizlabs/php_codesniffer": "^3.6" + "squizlabs/php_codesniffer": "^3.7" }, "autoload": { "psr-4": { @@ -50,5 +50,10 @@ ] }, "minimum-stability": "dev", - "prefer-stable": true + "prefer-stable": true, + "config": { + "allow-plugins": { + "phpdocumentor/shim": true + } + } } diff --git a/examples/generate_phpdoc.py b/examples/generate_phpdoc.py index 7ad9eda..9676c8e 100755 --- a/examples/generate_phpdoc.py +++ b/examples/generate_phpdoc.py @@ -263,7 +263,7 @@ def add_nickname(gtype, a, b): f.write(' * @see {0} for possible values\n'.format(remove_prefix(type_name(gtype)))) f.write(' */\n') - f.write('abstract class ImageAutodoc\n') + f.write('abstract class ImageAutodoc extends VipsObject\n') f.write('{\n') f.write('}\n') diff --git a/phpdoc.xml b/phpdoc.xml index a37402d..131e964 100644 --- a/phpdoc.xml +++ b/phpdoc.xml @@ -1,16 +1,14 @@ - + php-vips + php-vips docs - docs - src - diff --git a/src/Config.php b/src/Config.php index b523038..5a9294f 100644 --- a/src/Config.php +++ b/src/Config.php @@ -55,18 +55,37 @@ class Config /** * The logger instance. + */ + private static ?LoggerInterface $logger = null; + + /** + * The FFI handle we use for the glib binary. * - * @var LoggerInterface + * @internal */ - private static $logger; + private static \FFI $glib; + + + /** + * The FFI handle we use for the gobject binary. + * + * @internal + */ + private static \FFI $gobject; /** * The FFI handle we use for the libvips binary. * * @internal */ - private static \FFI $ffi; - private static $ffi_inited = false; + private static \FFI $vips; + + /** + * Are the above FFI handles initialized? + * + * @internal + */ + private static bool $ffi_inited = false; /** * The library version number we detect. @@ -122,7 +141,7 @@ public static function getLogger(): ?LoggerInterface */ public static function cacheSetMax(int $value): void { - Config::ffi()->vips_cache_set_max($value); + self::vips()->vips_cache_set_max($value); } /** @@ -136,7 +155,7 @@ public static function cacheSetMax(int $value): void */ public static function cacheSetMaxMem(int $value): void { - Config::ffi()->vips_cache_set_max_mem($value); + self::vips()->vips_cache_set_max_mem($value); } /** @@ -149,7 +168,7 @@ public static function cacheSetMaxMem(int $value): void */ public static function cacheSetMaxFiles(int $value): void { - Config::ffi()->vips_cache_set_max_files($value); + self::vips()->vips_cache_set_max_files($value); } /** @@ -163,123 +182,91 @@ public static function cacheSetMaxFiles(int $value): void */ public static function concurrencySet(int $value): void { - Config::ffi()->vips_concurrency_set($value); + self::vips()->vips_concurrency_set($value); } - /** - * Gets the libvips version number as a string of the form - * MAJOR.MINOR.MICRO, for example "8.6.1". - * - * @return string - */ - public static function version(): string + public static function glib(): \FFI { - Config::ffi(); + self::init(); - return self::$library_major . "." . - self::$library_minor . ".". - self::$library_micro; + return self::$glib; } - /** - * Handy for debugging. - */ - public static function printAll() + public static function gobject(): \FFI { - Config::ffi()->vips_object_print_all(); + self::init(); + + return self::$gobject; } - public static function ffi() + public static function vips(): \FFI { - if (!self::$ffi_inited) { - self::init(); - self::$ffi_inited = true; - } + self::init(); - return self::$ffi; + return self::$vips; } - public static function ctypes(string $name) + public static function ctypes(string $name): \FFI\CType { - Config::ffi(); + self::init(); return self::$ctypes[$name]; } - public static function gtypes(string $name) + public static function gtypes(string $name): int { - Config::ffi(); + self::init(); return self::$gtypes[$name]; } - public static function ftypes(string $name) + public static function ftypes(string $name): string { - Config::ffi(); + self::init(); return self::$ftypes[$name]; } /** - * Throw a vips error as an exception. - * - * @throws Exception - * - * @return void + * Gets the libvips version number as a string of the form + * MAJOR.MINOR.MICRO, for example "8.6.1". * - * @internal + * @return string */ - public static function error(string $message = "") + public static function version(): string { - if ($message == "") { - $message = Config::ffi()->vips_error_buffer(); - Config::ffi()->vips_error_clear(); - } - $exception = new Exception($message); - Utils::errorLog($message, $exception); - throw $exception; + self::init(); + + return self::$library_major . "." . + self::$library_minor . "." . + self::$library_micro; } /** * Shut down libvips. Call this just before process exit. * - * @throws Exception - * * @return void - * - * @internal */ - public static function shutDown() + public static function shutDown(): void { - Config::ffi()->vips_shutdown(); + self::vips()->vips_shutdown(); } - public static function filenameGetFilename($name) - { - $pointer = Config::ffi()->vips_filename_get_filename($name); - $filename = \FFI::string($pointer); - Config::ffi()->g_free($pointer); - - return $filename; - } - - public static function filenameGetOptions($name) - { - $pointer = Config::ffi()->vips_filename_get_options($name); - $options = \FFI::string($pointer); - Config::ffi()->g_free($pointer); - - return $options; - } - - public static function atLeast($need_major, $need_minor) + /** + * Is this at least libvips major.minor[.patch]? + * @param int $x Major component. + * @param int $y Minor component. + * @param int $z Patch component. + * @return bool `true` if at least libvips major.minor[.patch]; otherwise, `false`. + */ + public static function atLeast(int $x, int $y, int $z = 0): bool { - return $need_major < self::$library_major || - ($need_major == self::$library_major && - $need_minor <= self::$library_minor); + return self::$library_major > $x || + self::$library_major == $x && self::$library_minor > $y || + self::$library_major == $x && self::$library_minor == $y && self::$library_micro >= $z; } - private static function libraryName($name, $abi) + private static function libraryName(string $name, int $abi): string { switch (PHP_OS_FAMILY) { case "Windows": @@ -295,32 +282,43 @@ private static function libraryName($name, $abi) } } - private static function init() + private static function init(): void { - $library = self::libraryName("libvips", 42); + // Already initialized. + if (self::$ffi_inited) { + return; + } + + $vips_libname = self::libraryName("libvips", 42); + if (PHP_OS_FAMILY === "Windows") { + $glib_libname = self::libraryName("libglib-2.0", 0); + $gobject_libname = self::libraryName("libgobject-2.0", 0); + } else { + $glib_libname = $vips_libname; + $gobject_libname = $vips_libname; + } - Utils::debugLog("init", ["libray" => $library]); + Utils::debugLog("init", ["library" => $vips_libname]); /* FIXME ... maybe display a helpful message on failure? This will * probably be the main point of failure. */ - $ffi = \FFI::cdef(<<vips_init(""); + $result = $vips->vips_init(""); if ($result != 0) { - $msg = $ffi->vips_error_buffer(); - throw new Vips\Exception("libvips error: $msg"); + throw new Exception("libvips error: " . $vips->vips_error_buffer()); } Utils::debugLog("init", ["vips_init" => $result]); # get the library version number, then we can build the API - self::$library_major = $ffi->vips_version(0); - self::$library_minor = $ffi->vips_version(1); - self::$library_micro = $ffi->vips_version(2); + self::$library_major = $vips->vips_version(0); + self::$library_minor = $vips->vips_version(1); + self::$library_micro = $vips->vips_version(2); Utils::debugLog("init", [ "libvips version" => [ self::$library_major, @@ -330,73 +328,77 @@ private static function init() ]); if (!self::atLeast(8, 7)) { - throw new Vips\Exception("your libvips is too old -- " . + throw new Exception("your libvips is too old -- " . "8.7 or later required"); } - if (PHP_INT_SIZE != 8) { - # we could maybe fix this if it's important ... it's mostly - # necessary since GType is the size of a pointer, and there's no - # easy way to discover if php is running on a 32 or 64-bit - # systems (as far as I can see) - throw new Vips\Exception("your php only supports 32-bit ints -- " . - "64 bit ints required"); - } + $is_64bits = PHP_INT_SIZE === 8; - # the whole libvips API, mostly adapted from pyvips - $header = <<vips_blend_mode_get_type(); - self::$ffi->vips_interpretation_get_type(); - self::$ffi->vips_operation_flags_get_type(); - self::$ffi->vips_band_format_get_type(); - self::$ffi->vips_token_get_type(); - self::$ffi->vips_saveable_get_type(); - self::$ffi->vips_image_type_get_type(); + self::$vips->vips_blend_mode_get_type(); + self::$vips->vips_interpretation_get_type(); + self::$vips->vips_operation_flags_get_type(); + self::$vips->vips_band_format_get_type(); + self::$vips->vips_token_get_type(); + self::$vips->vips_saveable_get_type(); + self::$vips->vips_image_type_get_type(); // look these up in advance self::$ctypes = [ - "GObject" => self::$ffi->type("GObject*"), - "VipsObject" => self::$ffi->type("VipsObject*"), - "VipsOperation" => self::$ffi->type("VipsOperation*"), - "VipsImage" => self::$ffi->type("VipsImage*"), - "VipsInterpolate" => self::$ffi->type("VipsInterpolate*"), + "GObject" => self::$gobject->type("GObject*"), + "GParamSpec" => self::$gobject->type("GParamSpec*"), + "VipsObject" => self::$vips->type("VipsObject*"), + "VipsOperation" => self::$vips->type("VipsOperation*"), + "VipsImage" => self::$vips->type("VipsImage*"), + "VipsInterpolate" => self::$vips->type("VipsInterpolate*"), ]; self::$gtypes = [ - "gboolean" => self::$ffi->g_type_from_name("gboolean"), - "gint" => self::$ffi->g_type_from_name("gint"), - "gint64" => self::$ffi->g_type_from_name("gint64"), - "guint64" => self::$ffi->g_type_from_name("guint64"), - "gdouble" => self::$ffi->g_type_from_name("gdouble"), - "gchararray" => self::$ffi->g_type_from_name("gchararray"), - "VipsRefString" => self::$ffi->g_type_from_name("VipsRefString"), - - "GEnum" => self::$ffi->g_type_from_name("GEnum"), - "GFlags" => self::$ffi->g_type_from_name("GFlags"), - "VipsBandFormat" => self::$ffi->g_type_from_name("VipsBandFormat"), - "VipsBlendMode" => self::$ffi->g_type_from_name("VipsBlendMode"), - "VipsArrayInt" => self::$ffi->g_type_from_name("VipsArrayInt"), + "gboolean" => self::$gobject->g_type_from_name("gboolean"), + "gint" => self::$gobject->g_type_from_name("gint"), + "gint64" => self::$gobject->g_type_from_name("gint64"), + "guint64" => self::$gobject->g_type_from_name("guint64"), + "gdouble" => self::$gobject->g_type_from_name("gdouble"), + "gchararray" => self::$gobject->g_type_from_name("gchararray"), + "VipsRefString" => self::$gobject->g_type_from_name("VipsRefString"), + + "GEnum" => self::$gobject->g_type_from_name("GEnum"), + "GFlags" => self::$gobject->g_type_from_name("GFlags"), + "VipsBandFormat" => self::$gobject->g_type_from_name("VipsBandFormat"), + "VipsBlendMode" => self::$gobject->g_type_from_name("VipsBlendMode"), + "VipsArrayInt" => self::$gobject->g_type_from_name("VipsArrayInt"), "VipsArrayDouble" => - self::$ffi->g_type_from_name("VipsArrayDouble"), - "VipsArrayImage" => self::$ffi->g_type_from_name("VipsArrayImage"), - "VipsBlob" => self::$ffi->g_type_from_name("VipsBlob"), + self::$gobject->g_type_from_name("VipsArrayDouble"), + "VipsArrayImage" => self::$gobject->g_type_from_name("VipsArrayImage"), + "VipsBlob" => self::$gobject->g_type_from_name("VipsBlob"), - "GObject" => self::$ffi->g_type_from_name("GObject"), - "VipsImage" => self::$ffi->g_type_from_name("VipsImage"), + "GObject" => self::$gobject->g_type_from_name("GObject"), + "VipsImage" => self::$gobject->g_type_from_name("VipsImage"), ]; // map vips format names to c type names @@ -829,6 +838,7 @@ private static function init() ]; Utils::debugLog("init", ["done"]); + self::$ffi_inited = true; } } diff --git a/src/Exception.php b/src/Exception.php index d4c4672..f8279b0 100644 --- a/src/Exception.php +++ b/src/Exception.php @@ -51,6 +51,17 @@ */ class Exception extends \Exception { + public function __construct($message = "", $code = 0, \Throwable $previous = null) + { + if ($message == "") { + $message = "libvips error: " . Config::vips()->vips_error_buffer(); + Config::vips()->vips_error_clear(); + } + + Utils::errorLog($message); + + parent::__construct($message, $code, $previous); + } } /* diff --git a/src/GObject.php b/src/GObject.php index 74c35ef..46620b3 100644 --- a/src/GObject.php +++ b/src/GObject.php @@ -69,7 +69,7 @@ abstract class GObject * * @internal */ - public function __construct($pointer) + public function __construct(\FFI\CData $pointer) { $this->pointer = \FFI::cast(Config::ctypes("GObject"), $pointer); } @@ -84,14 +84,14 @@ public function __clone() $this->ref(); } - public function ref() + public function ref(): void { - Config::ffi()->g_object_ref($this->pointer); + Config::gobject()->g_object_ref($this->pointer); } - public function unref() + public function unref(): void { - Config::ffi()->g_object_unref($this->pointer); + Config::gobject()->g_object_unref($this->pointer); } // TODO signal marshalling to go in diff --git a/src/GValue.php b/src/GValue.php index c0ce60d..32d28d1 100644 --- a/src/GValue.php +++ b/src/GValue.php @@ -46,24 +46,24 @@ class GValue public function __construct() { # allocate a gvalue on the heap, and make it persistent between requests - $this->struct = Config::ffi()->new("GValue", true, true); + $this->struct = Config::gobject()->new("GValue", true, true); $this->pointer = \FFI::addr($this->struct); # GValue needs to be inited to all zero \FFI::memset($this->pointer, 0, \FFI::sizeof($this->struct)); } - /* Turn a string into an enum value, if possible + /** + * Turn a string into an enum value, if possible + * @throws Exception */ - public static function toEnum($gtype, $value) + public static function toEnum(int $gtype, $value): int { if (is_string($value)) { - $enum_value = Config::ffi()-> + $enum_value = Config::vips()-> vips_enum_from_nick("php-vips", $gtype, $value); if ($enum_value < 0) { - echo "gtype = " . $gtype . "\n"; - echo "value = " . $value . "\n"; - Config::error(); + throw new Exception(); } } else { $enum_value = $value; @@ -72,11 +72,15 @@ public static function toEnum($gtype, $value) return $enum_value; } - public static function fromEnum($gtype, $value) + /** + * Turn an enum into a string, if possible + * @throws Exception + */ + public static function fromEnum(int $gtype, int $value): string { - $result = Config::ffi()->vips_enum_nick($gtype, $value); + $result = Config::vips()->vips_enum_nick($gtype, $value); if ($result === null) { - Config::error("value not in enum"); + throw new Exception("value not in enum"); } return $result; @@ -84,12 +88,12 @@ public static function fromEnum($gtype, $value) public function __destruct() { - Config::ffi()->g_value_unset($this->pointer); + Config::gobject()->g_value_unset($this->pointer); } - public function setType(int $gtype) + public function setType(int $gtype): void { - Config::ffi()->g_value_init($this->pointer, $gtype); + Config::gobject()->g_value_init($this->pointer, $gtype); } public function getType(): int @@ -97,37 +101,44 @@ public function getType(): int return $this->pointer->g_type; } - public function set($value) + /** + * Set a GValue. + * + * @param mixed $value Value to be set. + * + * @throws Exception + */ + public function set($value): void { $gtype = $this->getType(); switch ($gtype) { case Config::gtypes("gboolean"): - Config::ffi()->g_value_set_boolean($this->pointer, $value); + Config::gobject()->g_value_set_boolean($this->pointer, $value); break; case Config::gtypes("gint"): - Config::ffi()->g_value_set_int($this->pointer, $value); + Config::gobject()->g_value_set_int($this->pointer, $value); break; case Config::gtypes("gint64"): - Config::ffi()->g_value_set_int64($this->pointer, $value); + Config::gobject()->g_value_set_int64($this->pointer, $value); break; case Config::gtypes("guint64"): - Config::ffi()->g_value_set_uint64($this->pointer, $value); + Config::gobject()->g_value_set_uint64($this->pointer, $value); break; case Config::gtypes("gdouble"): - Config::ffi()->g_value_set_double($this->pointer, $value); + Config::gobject()->g_value_set_double($this->pointer, $value); break; case Config::gtypes("gchararray"): - Config::ffi()->g_value_set_string($this->pointer, $value); + Config::gobject()->g_value_set_string($this->pointer, $value); break; case Config::gtypes("VipsRefString"): - Config::ffi()-> + Config::vips()-> vips_value_set_ref_string($this->pointer, $value); break; @@ -141,7 +152,7 @@ public function set($value) for ($i = 0; $i < $n; $i++) { $array[$i] = $value[$i]; } - Config::ffi()-> + Config::vips()-> vips_value_set_array_int($this->pointer, $array, $n); break; @@ -155,7 +166,7 @@ public function set($value) for ($i = 0; $i < $n; $i++) { $array[$i] = $value[$i]; } - Config::ffi()-> + Config::vips()-> vips_value_set_array_double($this->pointer, $array, $n); break; @@ -164,8 +175,8 @@ public function set($value) $value = [$value]; } $n = count($value); - Config::ffi()->vips_value_set_array_image($this->pointer, $n); - $array = Config::ffi()-> + Config::vips()->vips_value_set_array_image($this->pointer, $n); + $array = Config::vips()-> vips_value_get_array_image($this->pointer, null); for ($i = 0; $i < $n; $i++) { $image = $value[$i]; @@ -183,20 +194,20 @@ public function set($value) for ($i = 0; $i < $n; $i++) { $memory[$i] = $value[$i]; } - Config::ffi()-> + Config::vips()-> vips_value_set_blob_free($this->pointer, $memory, $n); break; default: - $fundamental = Config::ffi()->g_type_fundamental($gtype); + $fundamental = Config::gobject()->g_type_fundamental($gtype); switch ($fundamental) { case Config::gtypes("GObject"): - Config::ffi()-> + Config::gobject()-> g_value_set_object($this->pointer, $value->pointer); break; case Config::gtypes("GEnum"): - Config::ffi()->g_value_set_enum( + Config::gobject()->g_value_set_enum( $this->pointer, self::toEnum($gtype, $value) ); @@ -205,20 +216,26 @@ public function set($value) case Config::gtypes("GFlags"): /* Just set as int. */ - Config::ffi()-> + Config::gobject()-> g_value_set_flags($this->pointer, $value); break; default: - $typeName = Config::ffi()->g_type_name($gtype); + $typeName = Config::gobject()->g_type_name($gtype); throw new \BadMethodCallException( "gtype $typeName ($gtype) not implemented" ); - break; } } } + /** + * Get the contents of a GValue. + * + * @return mixed The contents of this GValue. + * + * @throws Exception + */ public function get() { $gtype = $this->getType(); @@ -226,47 +243,47 @@ public function get() switch ($gtype) { case Config::gtypes("gboolean"): - $result = Config::ffi()->g_value_get_boolean($this->pointer); + $result = Config::gobject()->g_value_get_boolean($this->pointer); break; case Config::gtypes("gint"): - $result = Config::ffi()->g_value_get_int($this->pointer); + $result = Config::gobject()->g_value_get_int($this->pointer); break; case Config::gtypes("gint64"): - $result = Config::ffi()->g_value_get_int64($this->pointer); + $result = Config::gobject()->g_value_get_int64($this->pointer); break; case Config::gtypes("guint64"): - $result = Config::ffi()->g_value_get_uint64($this->pointer); + $result = Config::gobject()->g_value_get_uint64($this->pointer); break; case Config::gtypes("gdouble"): - $result = Config::ffi()->g_value_get_double($this->pointer); + $result = Config::gobject()->g_value_get_double($this->pointer); break; case Config::gtypes("gchararray"): - $result = Config::ffi()->g_value_get_string($this->pointer); + $result = Config::gobject()->g_value_get_string($this->pointer); break; case Config::gtypes("VipsRefString"): - $p_size = Config::ffi()->new("size_t[1]"); - $result = Config::ffi()-> + $p_size = Config::vips()->new("size_t[1]"); + $result = Config::vips()-> vips_value_get_ref_string($this->pointer, $p_size); # $p_size[0] will be the string length, but assume it's null # terminated break; case Config::gtypes("VipsImage"): - $pointer = Config::ffi()->g_value_get_object($this->pointer); + $pointer = Config::gobject()->g_value_get_object($this->pointer); $result = new Image($pointer); // get_object does not increment the ref count $result->ref(); break; case Config::gtypes("VipsArrayInt"): - $p_len = Config::ffi()->new("int[1]"); - $pointer = Config::ffi()-> + $p_len = Config::vips()->new("int[1]"); + $pointer = Config::vips()-> vips_value_get_array_int($this->pointer, $p_len); $result = []; for ($i = 0; $i < $p_len[0]; $i++) { @@ -275,8 +292,8 @@ public function get() break; case Config::gtypes("VipsArrayDouble"): - $p_len = Config::ffi()->new("int[1]"); - $pointer = Config::ffi()-> + $p_len = Config::vips()->new("int[1]"); + $pointer = Config::vips()-> vips_value_get_array_double($this->pointer, $p_len); $result = []; for ($i = 0; $i < $p_len[0]; $i++) { @@ -285,8 +302,8 @@ public function get() break; case Config::gtypes("VipsArrayImage"): - $p_len = Config::ffi()->new("int[1]"); - $pointer = Config::ffi()-> + $p_len = Config::vips()->new("int[1]"); + $pointer = Config::vips()-> vips_value_get_array_image($this->pointer, $p_len); $result = []; for ($i = 0; $i < $p_len[0]; $i++) { @@ -297,17 +314,17 @@ public function get() break; case Config::gtypes("VipsBlob"): - $p_len = Config::ffi()->new("size_t[1]"); - $pointer = Config::ffi()-> + $p_len = Config::vips()->new("size_t[1]"); + $pointer = Config::vips()-> vips_value_get_blob($this->pointer, $p_len); $result = \FFI::string($pointer, $p_len[0]); break; default: - $fundamental = Config::ffi()->g_type_fundamental($gtype); + $fundamental = Config::gobject()->g_type_fundamental($gtype); switch ($fundamental) { case Config::gtypes("GEnum"): - $result = Config::ffi()-> + $result = Config::gobject()-> g_value_get_enum($this->pointer); $result = self::fromEnum($gtype, $result); break; @@ -315,16 +332,15 @@ public function get() case Config::gtypes("GFlags"): /* Just get as int. */ - $result = Config::ffi()-> + $result = Config::gobject()-> g_value_get_flags($this->pointer); break; default: - $typeName = Config::ffi()->g_type_name($gtype); + $typeName = Config::gobject()->g_type_name($gtype); throw new \BadMethodCallException( "gtype $typeName ($gtype) not implemented" ); - break; } } diff --git a/src/GsfOutputCsvQuotingMode.php b/src/GsfOutputCsvQuotingMode.php deleted file mode 100644 index a8a08f0..0000000 --- a/src/GsfOutputCsvQuotingMode.php +++ /dev/null @@ -1,54 +0,0 @@ - - * @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/Image.php b/src/Image.php index 5a77d99..414198f 100644 --- a/src/Image.php +++ b/src/Image.php @@ -492,10 +492,9 @@ class Image extends ImageAutodoc implements \ArrayAccess * * @internal */ - public function __construct($pointer) + public function __construct(\FFI\CData $pointer) { - $this->pointer = Config::ffi()-> - cast(Config::ctypes("VipsImage"), $pointer); + $this->pointer = \FFI::cast(Config::ctypes("VipsImage"), $pointer); parent::__construct($pointer); } @@ -647,7 +646,7 @@ private static function runCmplx(\Closure $func, Image $image): Image /** * Handy for things like self::more. Call a 2-ary vips operator like - * 'more', but if the arg is not an image (ie. it's a constant), call + * 'more', but if the arg is not an image (i.e. it's a constant), call * 'more_const' instead. * * @param mixed $other The right-hand argument. @@ -690,15 +689,13 @@ private function callEnum( */ public static function findLoad(string $filename): ?string { - $result = Config::ffi()->vips_foreign_find_load($filename); - - return $result; + return Config::vips()->vips_foreign_find_load($filename); } /** * Create a new Image from a file on disc. * - * @param string $filename The file to open. + * @param string $name The file to open. * @param array $options Any options to pass on to the load operation. * * @throws Exception @@ -709,12 +706,12 @@ public static function newFromFile( string $name, array $options = [] ): Image { - $filename = Config::filenameGetFilename($name); - $string_options = Config::filenameGetOptions($name); + $filename = Utils::filenameGetFilename($name); + $string_options = Utils::filenameGetOptions($name); $loader = self::findLoad($filename); if ($loader == null) { - Config::error(); + throw new Exception(); } if (strlen($string_options) != 0) { @@ -723,9 +720,7 @@ public static function newFromFile( ], $options); } - $result = VipsOperation::call($loader, null, [$filename], $options); - - return $result; + return VipsOperation::call($loader, null, [$filename], $options); } /** @@ -739,10 +734,8 @@ public static function newFromFile( */ public static function findLoadBuffer(string $buffer): ?string { - $result = Config::ffi()-> + return Config::vips()-> vips_foreign_find_load_buffer($buffer, strlen($buffer)); - - return $result; } /** @@ -764,7 +757,7 @@ public static function newFromBuffer( ): Image { $loader = self::findLoadBuffer($buffer); if ($loader == null) { - Config::error(); + throw new Exception(); } if (strlen($string_options) != 0) { @@ -773,9 +766,7 @@ public static function newFromBuffer( ], $options); } - $result = VipsOperation::call($loader, null, [$buffer], $options); - - return $result; + return VipsOperation::call($loader, null, [$buffer], $options); } /** @@ -814,10 +805,10 @@ public static function newFromArray( } } - $pointer = Config::ffi()-> + $pointer = Config::vips()-> vips_image_new_matrix_from_array($width, $height, $a, $n); if ($pointer == null) { - Config::error(); + throw new Exception(); } $result = new Image($pointer); @@ -851,7 +842,7 @@ public static function newFromMemory( * * TODO add a references system instead, see pyvips. */ - $pointer = Config::ffi()->vips_image_new_from_memory_copy( + $pointer = Config::vips()->vips_image_new_from_memory_copy( $data, strlen($data), $width, @@ -860,12 +851,10 @@ public static function newFromMemory( $format ); if ($pointer == null) { - Config::error(); + throw new Exception(); } - $result = new Image($pointer); - - return $result; + return new Image($pointer); } /** @@ -873,7 +862,7 @@ public static function newFromMemory( * * See Interpolator::newFromName() for the new thing. */ - public static function newInterpolator(string $name) + public static function newInterpolator(string $name): Interpolate { return Interpolate::newFromName($name); } @@ -904,21 +893,19 @@ public function newFromImage($value): Image $this->height, ['extend' => Extend::COPY] ); - $image = $image->copy([ + return $image->copy([ 'interpretation' => $this->interpretation, 'xres' => $this->xres, 'yres' => $this->yres, 'xoffset' => $this->xoffset, 'yoffset' => $this->yoffset ]); - - return $image; } /** * Write an image to a file. * - * @param string $filename The file to write the image to. + * @param string $name The file to write the image to. * @param array $options Any options to pass on to the selected save * operation. * @@ -928,12 +915,12 @@ public function newFromImage($value): Image */ public function writeToFile(string $name, array $options = []): void { - $filename = Config::filenameGetFilename($name); - $string_options = Config::filenameGetOptions($name); + $filename = Utils::filenameGetFilename($name); + $string_options = Utils::filenameGetOptions($name); - $saver = Config::ffi()->vips_foreign_find_save($filename); + $saver = Config::vips()->vips_foreign_find_save($filename); if ($saver == "") { - Config::error(); + throw new Exception(); } if (strlen($string_options) != 0) { @@ -945,7 +932,7 @@ public function writeToFile(string $name, array $options = []): void $result = VipsOperation::call($saver, $this, [$filename], $options); if ($result === -1) { - Config::error(); + throw new Exception(); } } @@ -962,12 +949,12 @@ public function writeToFile(string $name, array $options = []): void */ public function writeToBuffer(string $suffix, array $options = []): string { - $filename = Config::filenameGetFilename($suffix); - $string_options = Config::filenameGetOptions($suffix); + $filename = Utils::filenameGetFilename($suffix); + $string_options = Utils::filenameGetOptions($suffix); - $saver = Config::ffi()->vips_foreign_find_save_buffer($filename); + $saver = Config::vips()->vips_foreign_find_save_buffer($filename); if ($saver == "") { - Config::error(); + throw new Exception(); } if (strlen($string_options) != 0) { @@ -976,9 +963,7 @@ public function writeToBuffer(string $suffix, array $options = []): string ], $options); } - $result = VipsOperation::call($saver, $this, [], $options); - - return $result; + return VipsOperation::call($saver, $this, [], $options); } /** @@ -993,16 +978,16 @@ public function writeToMemory(): string $ctype = \FFI::arrayType(\FFI::type("size_t"), [1]); $p_size = \FFI::new($ctype); - $pointer = Config::ffi()-> + $pointer = Config::vips()-> vips_image_write_to_memory($this->pointer, $p_size); if ($pointer == null) { - Config::error(); + throw new Exception(); } // string() takes a copy $result = \FFI::string($pointer, $p_size[0]); - Config::ffi()->g_free($pointer); + Config::glib()->g_free($pointer); return $result; } @@ -1035,10 +1020,10 @@ public function writeToArray(): array $ctype = \FFI::arrayType(\FFI::type("size_t"), [1]); $p_size = \FFI::new($ctype); - $pointer = Config::ffi()-> + $pointer = Config::vips()-> vips_image_write_to_memory($this->pointer, $p_size); if ($pointer == null) { - Config::error(); + throw new Exception(); } // wrap pointer up as a C array of the right type @@ -1054,7 +1039,7 @@ public function writeToArray(): array } // the vips result is not PHP memory, so we must free it - Config::ffi()->g_free($pointer); + Config::glib()->g_free($pointer); return $result; } @@ -1075,13 +1060,11 @@ public function writeToArray(): array */ public function copyMemory(): Image { - $pointer = Config::ffi()->vips_image_copy_memory($this->pointer); + $pointer = Config::vips()->vips_image_copy_memory($this->pointer); if ($pointer == null) { - Config::error(); + throw new Exception(); } - $result = new Image($pointer); - - return $result; + return new Image($pointer); } /** @@ -1105,6 +1088,7 @@ public function __get(string $name) * @param mixed $value The value to set for this property. * * @return void + * @throws Exception */ public function __set(string $name, $value): void { @@ -1141,9 +1125,9 @@ public function __isset(string $name): bool public function get(string $name) { $gvalue = new GValue(); - if (Config::ffi()-> + if (Config::vips()-> vips_image_get($this->pointer, $name, $gvalue->pointer) != 0) { - Config::error(); + throw new Exception(); } return $gvalue->get(); @@ -1160,7 +1144,7 @@ public function get(string $name) */ public function getType(string $name): int { - return Config::ffi()->vips_image_get_typeof($this->pointer, $name); + return Config::vips()->vips_image_get_typeof($this->pointer, $name); } /** @@ -1218,7 +1202,7 @@ public function set(string $name, $value): void $gvalue->setType($gtype); $gvalue->set($value); - Config::ffi()->vips_image_set($this->pointer, $name, $gvalue->pointer); + Config::vips()->vips_image_set($this->pointer, $name, $gvalue->pointer); } /** @@ -1243,7 +1227,7 @@ public function setType($type, string $name, $value): void $gvalue = new GValue(); $gvalue->setType($type); $gvalue->set($value); - Config::ffi()->vips_image_set($this->pointer, $name, $gvalue->pointer); + Config::vips()->vips_image_set($this->pointer, $name, $gvalue->pointer); } /** @@ -1257,8 +1241,8 @@ public function setType($type, string $name, $value): void */ public function remove(string $name): void { - if (!Config::ffi()->vips_image_remove($this->pointer, $name)) { - Config::error(); + if (!Config::vips()->vips_image_remove($this->pointer, $name)) { + throw new Exception(); } } @@ -1267,7 +1251,7 @@ public function remove(string $name): void * * @return string */ - public function __toString() + public function __toString(): string { $array = [ 'width' => $this->width, diff --git a/src/ImageAutodoc.php b/src/ImageAutodoc.php index f9537ec..531084a 100644 --- a/src/ImageAutodoc.php +++ b/src/ImageAutodoc.php @@ -203,6 +203,8 @@ * @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. @@ -485,9 +487,9 @@ * @throws Exception * @method static Image pngload_source(string $source, array $options = []) Load png from source. * @throws Exception - * @method void pngsave(string $filename, array $options = []) Save image to png file. + * @method void pngsave(string $filename, array $options = []) Save image to file as PNG. * @throws Exception - * @method string pngsave_buffer(array $options = []) Save image to png buffer. + * @method string pngsave_buffer(array $options = []) Save image to buffer as PNG. * @throws Exception * @method void pngsave_target(string $target, array $options = []) Save image to target as PNG. * @throws Exception @@ -643,6 +645,8 @@ * @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. diff --git a/src/Interpolate.php b/src/Interpolate.php index c70b327..9276b90 100644 --- a/src/Interpolate.php +++ b/src/Interpolate.php @@ -60,10 +60,9 @@ class Interpolate extends VipsObject */ public \FFI\CData $pointer; - public function __construct($pointer) + public function __construct(\FFI\CData $pointer) { - $this->pointer = Config::ffi()-> - cast(Config::ctypes("VipsInterpolate"), $pointer); + $this->pointer = \FFI::cast(Config::ctypes("VipsInterpolate"), $pointer); parent::__construct($pointer); } @@ -81,13 +80,14 @@ public function __construct($pointer) * - `'lbb'`: Use LBB interpolation. * - `'vsqbs'`: Use the VSQBS interpolation. * - * @return resource|null The interpolator, or null on error. + * @return Interpolate The interpolator. + * @throws Exception If unable to make a new interpolator from $name. */ - public static function newFromName($name) + public static function newFromName(string $name): Interpolate { - $pointer = Config::ffi()->vips_interpolate_new($name); + $pointer = Config::vips()->vips_interpolate_new($name); if ($pointer == null) { - Config::error(); + throw new Exception(); } return new Interpolate($pointer); diff --git a/src/Introspect.php b/src/Introspect.php index b4aca43..61a711d 100644 --- a/src/Introspect.php +++ b/src/Introspect.php @@ -57,12 +57,12 @@ class Introspect public string $name; /** - * The operation description (eg. "add two images"). + * The operation description (e.g. "add two images"). */ public string $description; /** - * The operation flags (eg. SEQUENTIAL | DEPRECATED). + * The operation flags (e.g. SEQUENTIAL | DEPRECATED). */ public int $flags; @@ -90,26 +90,28 @@ class Introspect */ public array $method_args; - public function __construct($name) + /** + * @throws Exception + */ + public function __construct($operation_name) { - $this->name = $name; + $this->name = $operation_name; - $operation = VipsOperation::newFromName($name); + $operation = VipsOperation::newFromName($operation_name); $this->description = $operation->getDescription(); - $flags = Config::ffi()->vips_operation_get_flags($operation->pointer); - $p_names = Config::ffi()->new("char**[1]"); - $p_flags = Config::ffi()->new("int*[1]"); - $p_n_args = Config::ffi()->new("int[1]"); - $result = Config::ffi()->vips_object_get_args( + $p_names = Config::vips()->new("char**[1]"); + $p_flags = Config::vips()->new("int*[1]"); + $p_n_args = Config::vips()->new("int[1]"); + $result = Config::vips()->vips_object_get_args( \FFI::cast(Config::ctypes("VipsObject"), $operation->pointer), $p_names, $p_flags, $p_n_args ); if ($result != 0) { - error(); + throw new Exception(); } $p_names = $p_names[0]; $p_flags = $p_flags[0]; @@ -146,9 +148,6 @@ public function __construct($name) foreach ($this->arguments as $name => $details) { $flags = $details["flags"]; - $blurb = $details["blurb"]; - $type = $details["type"]; - $typeName = Config::ffi()->g_type_name($type); if (($flags & ArgumentFlags::INPUT) && ($flags & ArgumentFlags::REQUIRED) && @@ -198,27 +197,25 @@ public function __construct($name) array_splice($this->method_args, $index); } - Utils::debugLog($name, ['introspect' => strval($this)]); + Utils::debugLog($operation_name, ['introspect' => strval($this)]); } - public function __toString() + public function __toString(): string { - $result = ""; - - $result .= "$this->name:\n"; + $result = "$this->name:\n"; foreach ($this->arguments as $name => $details) { $flags = $details["flags"]; $blurb = $details["blurb"]; $type = $details["type"]; - $typeName = Config::ffi()->g_type_name($type); + $typeName = Config::gobject()->g_type_name($type); $result .= " $name:\n"; $result .= " flags: $flags\n"; - foreach (ArgumentFlags::NAMES as $name => $flag) { + foreach (ArgumentFlags::NAMES as $flag_name => $flag) { if ($flags & $flag) { - $result .= " $name\n"; + $result .= " $flag_name\n"; } } diff --git a/src/Utils.php b/src/Utils.php index d5ee068..808af39 100644 --- a/src/Utils.php +++ b/src/Utils.php @@ -69,16 +69,16 @@ public static function debugLog(string $name, array $arguments): void /** * Log an error message. * - * @param string $message The error message. - * @param \Exception $exception The exception. + * @param string $message The error message. + * @param \Exception|null $exception The exception, if any. * * @return void */ - public static function errorLog(string $message, \Exception $exception): void + public static function errorLog(string $message, ?\Exception $exception = null): void { $logger = Config::getLogger(); if ($logger) { - $logger->error($message, ['exception' => $exception]); + $logger->error($message, $exception == null ? [] : ['exception' => $exception]); } } @@ -92,7 +92,25 @@ public static function errorLog(string $message, \Exception $exception): void */ public static function typeFromName(string $name): int { - return Config::ffi()->g_type_from_name($name); + return Config::gobject()->g_type_from_name($name); + } + + public static function filenameGetFilename(string $name): string + { + $pointer = Config::vips()->vips_filename_get_filename($name); + $filename = \FFI::string($pointer); + Config::glib()->g_free($pointer); + + return $filename; + } + + public static function filenameGetOptions(string $name): string + { + $pointer = Config::vips()->vips_filename_get_options($name); + $options = \FFI::string($pointer); + Config::glib()->g_free($pointer); + + return $options; } } diff --git a/src/VipsObject.php b/src/VipsObject.php index 2a027cf..22f5c3b 100644 --- a/src/VipsObject.php +++ b/src/VipsObject.php @@ -66,37 +66,35 @@ abstract class VipsObject extends GObject */ private \FFI\CData $gObject; - public function __construct($pointer) + public function __construct(\FFI\CData $pointer) { - $this->pointer = Config::ffi()-> - cast(Config::ctypes("VipsObject"), $pointer); - $this->gObject = Config::ffi()-> - cast(Config::ctypes("GObject"), $pointer); + $this->pointer = \FFI::cast(Config::ctypes("VipsObject"), $pointer); + $this->gObject = \FFI::cast(Config::ctypes("GObject"), $pointer); parent::__construct($pointer); } // print a table of all active vipsobjects ... handy for debugging - public static function printAll() + public static function printAll(): void { - Config::ffi()->vips_object_print_all(); + Config::vips()->vips_object_print_all(); } - public function getDescription() + public function getDescription(): string { - return Config::ffi()->vips_object_get_description($this->pointer); + return Config::vips()->vips_object_get_description($this->pointer); } // get the pspec for a property // NULL for no such name // very slow! avoid if possible // FIXME add a cache for this thing - public function getPspec(string $name) + public function getPspec(string $name): ?\FFI\CData { - $pspec = Config::ffi()->new("GParamSpec*[1]"); - $argument_class = Config::ffi()->new("VipsArgumentClass*[1]"); - $argument_instance = Config::ffi()->new("VipsArgumentInstance*[1]"); - $result = Config::ffi()->vips_object_get_argument( + $pspec = Config::gobject()->new("GParamSpec*[1]"); + $argument_class = Config::vips()->new("VipsArgumentClass*[1]"); + $argument_instance = Config::vips()->new("VipsArgumentInstance*[1]"); + $result = Config::vips()->vips_object_get_argument( $this->pointer, $name, $pspec, @@ -113,12 +111,12 @@ public function getPspec(string $name) // get the type of a property from a VipsObject // 0 if no such property - public function getType(string $name) + public function getType(string $name): int { $pspec = $this->getPspec($name); if (\FFI::isNull($pspec)) { # need to clear any error, this is horrible - Config::ffi()->vips_error_clear(); + Config::vips()->vips_error_clear(); return 0; } else { return $pspec->value_type; @@ -128,21 +126,24 @@ public function getType(string $name) public function getBlurb(string $name): string { $pspec = $this->getPspec($name); - return Config::ffi()->g_param_spec_get_blurb($pspec); + return Config::gobject()->g_param_spec_get_blurb($pspec); } public function getArgumentDescription(string $name): string { $pspec = $this->getPspec($name); - return Config::ffi()->g_param_spec_get_description($pspec); + return Config::gobject()->g_param_spec_get_description($pspec); } + /** + * @throws Exception + */ public function get(string $name) { $gvalue = new GValue(); $gvalue->setType($this->getType($name)); - Config::ffi()-> + Config::gobject()-> g_object_get_property($this->gObject, $name, $gvalue->pointer); $value = $gvalue->get(); @@ -151,7 +152,10 @@ public function get(string $name) return $value; } - public function set(string $name, $value) + /** + * @throws Exception + */ + public function set(string $name, $value): void { Utils::debugLog("set", [$name => $value]); @@ -159,21 +163,21 @@ public function set(string $name, $value) $gvalue->setType($this->getType($name)); $gvalue->set($value); - Config::ffi()-> + Config::gobject()-> g_object_set_property($this->gObject, $name, $gvalue->pointer); } - public function setString(string $string_options) + public function setString(string $string_options): bool { - $result = Config::ffi()-> + $result = Config::vips()-> vips_object_set_from_string($this->pointer, $string_options); return $result == 0; } - public function unrefOutputs() + public function unrefOutputs(): void { - Config::ffi()->vips_object_unref_outputs($this->pointer); + Config::vips()->vips_object_unref_outputs($this->pointer); } } diff --git a/src/VipsOperation.php b/src/VipsOperation.php index 5148e42..67266e9 100644 --- a/src/VipsOperation.php +++ b/src/VipsOperation.php @@ -64,25 +64,28 @@ class VipsOperation extends VipsObject */ public Introspect $introspect; - public function __construct($pointer) + public function __construct(\FFI\CData $pointer) { - $this->pointer = Config::ffi()-> + $this->pointer = Config::vips()-> cast(Config::ctypes("VipsOperation"), $pointer); parent::__construct($pointer); } - public static function newFromName($name) + /** + * @throws Exception + */ + public static function newFromName($name): VipsOperation { - $pointer = Config::ffi()->vips_operation_new($name); + $pointer = Config::vips()->vips_operation_new($name); if ($pointer == null) { - Config::error(); + throw new Exception(); } return new VipsOperation($pointer); } - public function setMatch($name, $match_image, $value) + public function setMatch($name, $match_image, $value): void { $flags = $this->introspect->arguments[$name]["flags"]; $gtype = $this->introspect->arguments[$name]["type"]; @@ -140,27 +143,6 @@ private static function findInside($predicate, $x) return null; } - /** - * Unwrap an array of stuff ready to pass down to the vips_ layer. We - * swap instances of Image for the ffi pointer. - * - * @param array $result Unwrap this. - * - * @return array $result unwrapped, ready for vips. - * - * @internal - */ - private static function unwrap(array $result): array - { - array_walk_recursive($result, function (&$value) { - if ($value instanceof Image) { - $value = $value->image; - } - }); - - return $result; - } - /** * Is $value a VipsImage. * @@ -211,28 +193,6 @@ private static function wrapResult($result) return $result; } - /** - * Check the result of a vips_ call for an error, and throw an exception - * if we see one. - * - * This won't work for things like __get where a non-array return can be - * a valid return. - * - * @param mixed $result Test this. - * - * @throws Exception - * - * @return void - * - * @internal - */ - private static function errorIsArray($result): void - { - if (!is_array($result)) { - Config::error(); - } - } - /** * Call any vips operation. The final element of $arguments can be * (but doesn't have to be) an array of options to pass to the operation. @@ -286,22 +246,20 @@ public static function callBase( */ $n_required = count($operation->introspect->required_input); $n_supplied = count($arguments); - $used_instance = false; $n_used = 0; foreach ($operation->introspect->required_input as $name) { if ($name == $operation->introspect->member_this) { if (!$instance) { $operation->unrefOutputs(); - Config::error("instance argument not supplied"); + throw new Exception("instance argument not supplied"); } $operation->setMatch($name, $match_image, $instance); - $used_instance = true; } elseif ($n_used < $n_supplied) { $operation->setMatch($name, $match_image, $arguments[$n_used]); $n_used += 1; } else { $operation->unrefOutputs(); - Config::error("$n_required arguments required, " . + throw new Exception("$n_required arguments required, " . "but $n_supplied supplied"); } } @@ -316,7 +274,7 @@ public static function callBase( if ($n_supplied != $n_used) { $operation->unrefOutputs(); - Config::error("$n_required arguments required, " . + throw new Exception("$n_required arguments required, " . "but $n_supplied supplied"); } @@ -335,7 +293,7 @@ public static function callBase( if (!in_array($name, $operation->introspect->optional_input) && !in_array($name, $operation->introspect->optional_output)) { $operation->unrefOutputs(); - Config::error("optional argument '$name' does not exist"); + throw new Exception("optional argument '$name' does not exist"); } $operation->setMatch($name, $match_image, $value); @@ -343,11 +301,11 @@ public static function callBase( /* Build the operation */ - $pointer = Config::ffi()-> + $pointer = Config::vips()-> vips_cache_operation_build($operation->pointer); if ($pointer == null) { $operation->unrefOutputs(); - Config::error(); + throw new Exception(); } $operation = new VipsOperation($pointer); $operation->introspect = self::introspect($operation_name); @@ -377,7 +335,7 @@ public static function callBase( $result = self::wrapResult($result); - Utils::debugLog($name, ['result' => var_export($result, true)]); + Utils::debugLog($operation_name, ['result' => var_export($result, true)]); return $result; } From e2874ce26b88d62ad1917b55226796895ab1ea22 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 4 Jul 2022 15:56:54 +0100 Subject: [PATCH 038/115] note changes for 2.0.3 release --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a3a719..866a153 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to `:vips` will be documented in this file. +## 2.0.3 - 2022-07-04 + +- Fix on Windows [kleisauke] +- Fix 32-bit support [kleisauke] +- Code cleanups [kleisauke] + ## 2.0.2 - 2022-4-14 - Fix extra optional string args on file open From be4a59d3bb710d7eecdb2725819ff6bdebf44660 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Tue, 19 Jul 2022 19:54:09 +0200 Subject: [PATCH 039/115] Explicitly unset images during `tearDown()` (#150) To facilitate debugging of possible refleaks. --- src/Config.php | 3 +++ tests/ConvenienceTest.php | 6 ++++++ tests/ExceptionTest.php | 11 +++++------ tests/MetaTest.php | 6 ++++++ tests/ShortcutTest.php | 6 ++++++ 5 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/Config.php b/src/Config.php index 5a9294f..91c19ce 100644 --- a/src/Config.php +++ b/src/Config.php @@ -781,6 +781,9 @@ private static function init(): void self::$gobject = \FFI::cdef($gobject_decls, $gobject_libname); self::$vips = \FFI::cdef($vips_decls, $vips_libname); + # Useful for debugging + # self::$vips->vips_leak_set(1); + # force the creation of some types we need self::$vips->vips_blend_mode_get_type(); self::$vips->vips_interpretation_get_type(); diff --git a/tests/ConvenienceTest.php b/tests/ConvenienceTest.php index 34fa4f9..48c7bf7 100644 --- a/tests/ConvenienceTest.php +++ b/tests/ConvenienceTest.php @@ -24,6 +24,12 @@ protected function setUp(): void $this->pixel = $this->image->getpoint(0, 0); } + protected function tearDown(): void + { + unset($this->image); + unset($this->pixel); + } + public function testVipsBandjoin() { $image = Vips\Image::newFromArray([[1, 2, 3], [4, 5, 6]]); diff --git a/tests/ExceptionTest.php b/tests/ExceptionTest.php index 262e519..343b521 100644 --- a/tests/ExceptionTest.php +++ b/tests/ExceptionTest.php @@ -12,16 +12,15 @@ class ExceptionTest extends TestCase */ private $image; - /** - * The original value of pixel (0, 0). - */ - private $pixel; - protected function setUp(): void { $filename = __DIR__ . '/images/img_0076.jpg'; $this->image = Vips\Image::newFromFile($filename); - $this->pixel = $this->image->getpoint(0, 0); + } + + protected function tearDown(): void + { + unset($this->image); } public function testVipsNewFromFileException() diff --git a/tests/MetaTest.php b/tests/MetaTest.php index 9447fe3..8b8bb06 100644 --- a/tests/MetaTest.php +++ b/tests/MetaTest.php @@ -26,6 +26,12 @@ protected function setUp(): void $this->png_image = Vips\Image::newFromFile($png_filename); } + protected function tearDown(): void + { + unset($this->image); + unset($this->png_image); + } + public function testVipsSetGet() { $this->image->poop = 'banana'; diff --git a/tests/ShortcutTest.php b/tests/ShortcutTest.php index a4cc80c..4be1a46 100644 --- a/tests/ShortcutTest.php +++ b/tests/ShortcutTest.php @@ -37,6 +37,12 @@ protected function setUp(): void $this->pixel = $this->image->getpoint(0, 0); } + protected function tearDown(): void + { + unset($this->image); + unset($this->pixel); + } + public function testVipsPow() { $real = self::mapNumeric($this->pixel, function ($value) { From 2abe71c6c1381685cf8e2c235c970e31252fa5d4 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Wed, 20 Jul 2022 11:22:46 +0200 Subject: [PATCH 040/115] Fix `var_dump()` of instances (#151) --- src/Config.php | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/Config.php b/src/Config.php index 91c19ce..629915b 100644 --- a/src/Config.php +++ b/src/Config.php @@ -346,6 +346,20 @@ private static function init(): void typedef int64_t gint64; typedef $gtype GType; + +typedef struct _GData GData; + +typedef struct _GTypeClass GTypeClass; + +typedef struct _GTypeInstance { + GTypeClass *g_class; +} GTypeInstance; + +typedef struct _GObject { + GTypeInstance g_type_instance; + unsigned int ref_count; + GData *qdata; +} GObject; EOS; // GLib declarations @@ -361,14 +375,6 @@ private static function init(): void guint64 data[2]; } GValue; -typedef struct _GData GData; - -typedef struct _GTypeClass GTypeClass; - -typedef struct _GTypeInstance { - GTypeClass *g_class; -} GTypeInstance; - typedef struct _GParamSpec { GTypeInstance g_type_instance; @@ -386,12 +392,6 @@ private static function init(): void unsigned int param_id; } GParamSpec; -typedef struct _GObject { - GTypeInstance g_type_instance; - unsigned int ref_count; - GData *qdata; -} GObject; - const char* g_type_name (GType gtype); GType g_type_from_name (const char* name); @@ -478,8 +478,7 @@ private static function init(): void typedef struct _VipsImage VipsImage; typedef struct _VipsProgress VipsProgress; -// Defined in GObject, just typedef to void* -typedef void* GObject; +// Defined in GObject, just typedef to void typedef void GParamSpec; typedef void GValue; From fad7f0a34ffc1bfac05d9250ecaef4b35fa75159 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 11 Aug 2022 09:34:04 +0100 Subject: [PATCH 041/115] support use of "-" in arg names php-vips 1.x used to allow "-" as a component separator in arg names, but we forgot to implement this in v2. Thanks andrews05 See https://github.com/libvips/php-vips/issues/153 --- CHANGELOG.md | 4 ++++ install-vips.sh | 25 ------------------------- src/Introspect.php | 4 ++-- src/VipsObject.php | 3 +++ src/VipsOperation.php | 6 +++++- tests/CallTest.php | 16 +++++++++++++++- 6 files changed, 29 insertions(+), 29 deletions(-) delete mode 100755 install-vips.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 866a153..bff6018 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to `:vips` will be documented in this file. +## master + +- allow "-" as a name component separator [andrews05] + ## 2.0.3 - 2022-07-04 - Fix on Windows [kleisauke] diff --git a/install-vips.sh b/install-vips.sh deleted file mode 100755 index db7c4b6..0000000 --- a/install-vips.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash - -version=$VIPS_VERSION -vips_tarball=https://github.com/libvips/libvips/releases/download/v$version/vips-$version.tar.gz - -set -e - -# do we already have the correct vips built? early exit if yes -# we could check the configure params as well I guess -if [ -d "$HOME/vips/bin" ]; then - installed_version=$($HOME/vips/bin/vips --version | awk -F- '{print $2}') - echo "Need vips $version" - echo "Found vips $installed_version" - - if [ "$installed_version" == "$version" ]; then - echo "Using cached vips directory" - exit 0 - fi -fi - -rm -rf $HOME/vips -curl -Ls $vips_tarball | tar xz -cd vips-$version -CXXFLAGS=-D_GLIBCXX_USE_CXX11_ABI=0 ./configure --prefix=$HOME/vips "$@" -make -j`nproc` && make install diff --git a/src/Introspect.php b/src/Introspect.php index 61a711d..f252bc6 100644 --- a/src/Introspect.php +++ b/src/Introspect.php @@ -121,8 +121,8 @@ public function __construct($operation_name) $argumentFlags = []; for ($i = 0; $i < $n_args; $i++) { if (($p_flags[$i] & ArgumentFlags::CONSTRUCT) != 0) { - # libvips uses '-' to separate parts of arg names, but we - # need '_' for php + # make sure we're using "_" to separate arg components, though + # I think libvips is "_" everywhere now $name = \FFI::string($p_names[$i]); $name = str_replace("-", "_", $name); $argumentFlags[$name] = $p_flags[$i]; diff --git a/src/VipsObject.php b/src/VipsObject.php index 22f5c3b..cf7db76 100644 --- a/src/VipsObject.php +++ b/src/VipsObject.php @@ -91,6 +91,7 @@ public function getDescription(): string // FIXME add a cache for this thing public function getPspec(string $name): ?\FFI\CData { + $name = str_replace("-", "_", $name); $pspec = Config::gobject()->new("GParamSpec*[1]"); $argument_class = Config::vips()->new("VipsArgumentClass*[1]"); $argument_instance = Config::vips()->new("VipsArgumentInstance*[1]"); @@ -140,6 +141,7 @@ public function getArgumentDescription(string $name): string */ public function get(string $name) { + $name = str_replace("-", "_", $name); $gvalue = new GValue(); $gvalue->setType($this->getType($name)); @@ -159,6 +161,7 @@ public function set(string $name, $value): void { Utils::debugLog("set", [$name => $value]); + $name = str_replace("-", "_", $name); $gvalue = new GValue(); $gvalue->setType($this->getType($name)); $gvalue->set($value); diff --git a/src/VipsOperation.php b/src/VipsOperation.php index 67266e9..b3f546b 100644 --- a/src/VipsOperation.php +++ b/src/VipsOperation.php @@ -290,6 +290,7 @@ public static function callBase( /* Set optional. */ foreach ($options as $name => $value) { + $name = str_replace("-", "_", $name); if (!in_array($name, $operation->introspect->optional_input) && !in_array($name, $operation->introspect->optional_output)) { $operation->unrefOutputs(); @@ -324,6 +325,7 @@ public static function callBase( */ $option_keys = array_keys($options); foreach ($operation->introspect->optional_output as $name) { + $name = str_replace("-", "_", $name); if (in_array($name, $option_keys)) { $result[$name] = $operation->get($name); } @@ -335,7 +337,9 @@ public static function callBase( $result = self::wrapResult($result); - Utils::debugLog($operation_name, ['result' => var_export($result, true)]); + Utils::debugLog($operation_name, [ + 'result' => var_export($result, true) + ]); return $result; } diff --git a/tests/CallTest.php b/tests/CallTest.php index 1406ec9..99cf97f 100644 --- a/tests/CallTest.php +++ b/tests/CallTest.php @@ -10,13 +10,27 @@ class CallTest extends TestCase public function testVipsCall() { $image = Vips\Image::newFromArray([1, 2, 3]); - $image = $image->embed(10, 20, 3000, 2000, ['extend' => Vips\Extend::COPY]); + $image = $image->embed(10, 20, 3000, 2000, [ + 'extend' => Vips\Extend::COPY + ]); $this->assertEquals($image->width, 3000); $this->assertEquals($image->height, 2000); $this->assertEquals($image->bands, 1); } + public function testVipsCallHyphen() + { + # should work with "-" as well as "_" + $image = Vips\Image::worley(64, 64, [ + "cell-size" => 8 + ]); + + $this->assertEquals($image->width, 64); + $this->assertEquals($image->height, 64); + $this->assertEquals($image->bands, 1); + } + public function testVipsCallStatic() { $image = Vips\Image::black(1, 4, ['bands' => 3]); From e736e51340e69123152d6fd72fb7d1066979b98d Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Tue, 11 Oct 2022 10:37:38 +0200 Subject: [PATCH 042/115] Move FFI functions to a separate class (#147) * Move FFI functions to a separate class * Ensure libvips can be found on Apple Silicon Allow users to set the `VIPSHOME` env when libvips is installed in a non-standard path. On macOS, always search in `/opt/homebrew/lib/` as a fallback. In most cases, either the `LD_LIBRARY_PATH` or `DYLD_LIBRARY_PATH` env is preferred. On macOS, however, that no longer works since Apple introduced System Integrity Protection (SIP). See: https://github.com/libvips/php-vips/issues/140. * Prefer the strict equality operator --- src/Config.php | 694 +----------------------------------- src/Exception.php | 4 +- src/FFI.php | 804 ++++++++++++++++++++++++++++++++++++++++++ src/GObject.php | 6 +- src/GValue.php | 142 ++++---- src/Image.php | 56 +-- src/Interpolate.php | 4 +- src/Introspect.php | 14 +- src/Utils.php | 10 +- src/VipsObject.php | 30 +- src/VipsOperation.php | 14 +- 11 files changed, 950 insertions(+), 828 deletions(-) create mode 100644 src/FFI.php diff --git a/src/Config.php b/src/Config.php index 629915b..f7824d7 100644 --- a/src/Config.php +++ b/src/Config.php @@ -58,53 +58,6 @@ class Config */ private static ?LoggerInterface $logger = null; - /** - * The FFI handle we use for the glib binary. - * - * @internal - */ - private static \FFI $glib; - - - /** - * The FFI handle we use for the gobject binary. - * - * @internal - */ - private static \FFI $gobject; - - /** - * The FFI handle we use for the libvips binary. - * - * @internal - */ - private static \FFI $vips; - - /** - * Are the above FFI handles initialized? - * - * @internal - */ - private static bool $ffi_inited = false; - - /** - * The library version number we detect. - * - * @internal - */ - private static int $library_major; - private static int $library_minor; - private static int $library_micro; - - /** - * Look up these once. - * - * @internal - */ - private static array $ctypes; - private static array $gtypes; - private static array $ftypes; - /** * Sets a logger. This can be handy for debugging. For example: * @@ -141,7 +94,7 @@ public static function getLogger(): ?LoggerInterface */ public static function cacheSetMax(int $value): void { - self::vips()->vips_cache_set_max($value); + FFI::vips()->vips_cache_set_max($value); } /** @@ -155,7 +108,7 @@ public static function cacheSetMax(int $value): void */ public static function cacheSetMaxMem(int $value): void { - self::vips()->vips_cache_set_max_mem($value); + FFI::vips()->vips_cache_set_max_mem($value); } /** @@ -168,7 +121,7 @@ public static function cacheSetMaxMem(int $value): void */ public static function cacheSetMaxFiles(int $value): void { - self::vips()->vips_cache_set_max_files($value); + FFI::vips()->vips_cache_set_max_files($value); } /** @@ -182,49 +135,7 @@ public static function cacheSetMaxFiles(int $value): void */ public static function concurrencySet(int $value): void { - self::vips()->vips_concurrency_set($value); - } - - public static function glib(): \FFI - { - self::init(); - - return self::$glib; - } - - public static function gobject(): \FFI - { - self::init(); - - return self::$gobject; - } - - public static function vips(): \FFI - { - self::init(); - - return self::$vips; - } - - public static function ctypes(string $name): \FFI\CType - { - self::init(); - - return self::$ctypes[$name]; - } - - public static function gtypes(string $name): int - { - self::init(); - - return self::$gtypes[$name]; - } - - public static function ftypes(string $name): string - { - self::init(); - - return self::$ftypes[$name]; + FFI::vips()->vips_concurrency_set($value); } /** @@ -235,21 +146,7 @@ public static function ftypes(string $name): string */ public static function version(): string { - self::init(); - - return self::$library_major . "." . - self::$library_minor . "." . - self::$library_micro; - } - - /** - * Shut down libvips. Call this just before process exit. - * - * @return void - */ - public static function shutDown(): void - { - self::vips()->vips_shutdown(); + return FFI::version(); } /** @@ -261,586 +158,7 @@ public static function shutDown(): void */ public static function atLeast(int $x, int $y, int $z = 0): bool { - return self::$library_major > $x || - self::$library_major == $x && self::$library_minor > $y || - self::$library_major == $x && self::$library_minor == $y && self::$library_micro >= $z; - } - - private static function libraryName(string $name, int $abi): string - { - switch (PHP_OS_FAMILY) { - case "Windows": - return "$name-$abi.dll"; - - case "OSX": - case "Darwin": - return "$name.$abi.dylib"; - - default: - // most *nix - return "$name.so.$abi"; - } - } - - private static function init(): void - { - // Already initialized. - if (self::$ffi_inited) { - return; - } - - $vips_libname = self::libraryName("libvips", 42); - if (PHP_OS_FAMILY === "Windows") { - $glib_libname = self::libraryName("libglib-2.0", 0); - $gobject_libname = self::libraryName("libgobject-2.0", 0); - } else { - $glib_libname = $vips_libname; - $gobject_libname = $vips_libname; - } - - Utils::debugLog("init", ["library" => $vips_libname]); - - /* FIXME ... maybe display a helpful message on failure? This will - * probably be the main point of failure. - */ - $vips = \FFI::cdef(<<vips_init(""); - if ($result != 0) { - throw new Exception("libvips error: " . $vips->vips_error_buffer()); - } - Utils::debugLog("init", ["vips_init" => $result]); - - # get the library version number, then we can build the API - self::$library_major = $vips->vips_version(0); - self::$library_minor = $vips->vips_version(1); - self::$library_micro = $vips->vips_version(2); - Utils::debugLog("init", [ - "libvips version" => [ - self::$library_major, - self::$library_minor, - self::$library_micro - ] - ]); - - if (!self::atLeast(8, 7)) { - throw new Exception("your libvips is too old -- " . - "8.7 or later required"); - } - - $is_64bits = PHP_INT_SIZE === 8; - - // GType is the size of a pointer - $gtype = $is_64bits ? "guint64" : "guint32"; - - // Typedefs shared across the libvips, GLib and GObject declarations - $typedefs = <<vips_leak_set(1); - - # force the creation of some types we need - self::$vips->vips_blend_mode_get_type(); - self::$vips->vips_interpretation_get_type(); - self::$vips->vips_operation_flags_get_type(); - self::$vips->vips_band_format_get_type(); - self::$vips->vips_token_get_type(); - self::$vips->vips_saveable_get_type(); - self::$vips->vips_image_type_get_type(); - - // look these up in advance - self::$ctypes = [ - "GObject" => self::$gobject->type("GObject*"), - "GParamSpec" => self::$gobject->type("GParamSpec*"), - "VipsObject" => self::$vips->type("VipsObject*"), - "VipsOperation" => self::$vips->type("VipsOperation*"), - "VipsImage" => self::$vips->type("VipsImage*"), - "VipsInterpolate" => self::$vips->type("VipsInterpolate*"), - ]; - - self::$gtypes = [ - "gboolean" => self::$gobject->g_type_from_name("gboolean"), - "gint" => self::$gobject->g_type_from_name("gint"), - "gint64" => self::$gobject->g_type_from_name("gint64"), - "guint64" => self::$gobject->g_type_from_name("guint64"), - "gdouble" => self::$gobject->g_type_from_name("gdouble"), - "gchararray" => self::$gobject->g_type_from_name("gchararray"), - "VipsRefString" => self::$gobject->g_type_from_name("VipsRefString"), - - "GEnum" => self::$gobject->g_type_from_name("GEnum"), - "GFlags" => self::$gobject->g_type_from_name("GFlags"), - "VipsBandFormat" => self::$gobject->g_type_from_name("VipsBandFormat"), - "VipsBlendMode" => self::$gobject->g_type_from_name("VipsBlendMode"), - "VipsArrayInt" => self::$gobject->g_type_from_name("VipsArrayInt"), - "VipsArrayDouble" => - self::$gobject->g_type_from_name("VipsArrayDouble"), - "VipsArrayImage" => self::$gobject->g_type_from_name("VipsArrayImage"), - "VipsBlob" => self::$gobject->g_type_from_name("VipsBlob"), - - "GObject" => self::$gobject->g_type_from_name("GObject"), - "VipsImage" => self::$gobject->g_type_from_name("VipsImage"), - ]; - - // map vips format names to c type names - self::$ftypes = [ - "char" => "char", - "uchar" => "unsigned char", - "short" => "short", - "ushort" => "unsigned short", - "int" => "int", - "uint" => "unsigned int", - "float" => "float", - "double" => "double", - "complex" => "float", - "dpcomplex" => "double", - ]; - - Utils::debugLog("init", ["done"]); - self::$ffi_inited = true; + return FFI::atLeast($x, $y, $z); } } diff --git a/src/Exception.php b/src/Exception.php index f8279b0..ce5df1d 100644 --- a/src/Exception.php +++ b/src/Exception.php @@ -54,8 +54,8 @@ class Exception extends \Exception public function __construct($message = "", $code = 0, \Throwable $previous = null) { if ($message == "") { - $message = "libvips error: " . Config::vips()->vips_error_buffer(); - Config::vips()->vips_error_clear(); + $message = "libvips error: " . FFI::vips()->vips_error_buffer(); + FFI::vips()->vips_error_clear(); } Utils::errorLog($message); diff --git a/src/FFI.php b/src/FFI.php new file mode 100644 index 0000000..6db84ff --- /dev/null +++ b/src/FFI.php @@ -0,0 +1,804 @@ + + * @copyright 2016 John Cupitt + * @license https://opensource.org/licenses/MIT MIT + * @link https://github.com/jcupitt/php-vips + */ + +namespace Jcupitt\Vips; + +/** + * This class contains the libvips FFI methods. + * + * @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 + */ +class FFI +{ + + /** + * The FFI handle we use for the glib binary. + * + * @internal + */ + private static \FFI $glib; + + /** + * The FFI handle we use for the gobject binary. + * + * @internal + */ + private static \FFI $gobject; + + /** + * The FFI handle we use for the libvips binary. + * + * @internal + */ + private static \FFI $vips; + + /** + * Are the above FFI handles initialized? + * + * @internal + */ + private static bool $ffi_inited = false; + + /** + * Look up these once. + * + * @internal + */ + private static array $ctypes; + private static array $gtypes; + private static array $ftypes; + + /** + * The library version number we detect. + * + * @internal + */ + private static int $library_major; + private static int $library_minor; + private static int $library_micro; + + public static function glib(): \FFI + { + self::init(); + + return self::$glib; + } + + public static function gobject(): \FFI + { + self::init(); + + return self::$gobject; + } + + public static function vips(): \FFI + { + self::init(); + + return self::$vips; + } + + public static function ctypes(string $name): \FFI\CType + { + self::init(); + + return self::$ctypes[$name]; + } + + public static function gtypes(string $name): int + { + self::init(); + + return self::$gtypes[$name]; + } + + public static function ftypes(string $name): string + { + self::init(); + + return self::$ftypes[$name]; + } + + /** + * Gets the libvips version number as a string of the form + * MAJOR.MINOR.MICRO, for example "8.6.1". + * + * @return string + */ + public static function version(): string + { + self::init(); + + return self::$library_major . "." . + self::$library_minor . "." . + self::$library_micro; + } + + /** + * Is this at least libvips major.minor[.patch]? + * @param int $x Major component. + * @param int $y Minor component. + * @param int $z Patch component. + * @return bool `true` if at least libvips major.minor[.patch]; otherwise, `false`. + */ + public static function atLeast(int $x, int $y, int $z = 0): bool + { + return self::$library_major > $x || + self::$library_major == $x && self::$library_minor > $y || + self::$library_major == $x && self::$library_minor == $y && self::$library_micro >= $z; + } + + /** + * Shut down libvips. Call this just before process exit. + * + * @return void + */ + public static function shutDown(): void + { + self::vips()->vips_shutdown(); + } + + private static function libraryName(string $name, int $abi): string + { + switch (PHP_OS_FAMILY) { + case "Windows": + return "$name-$abi.dll"; + + case "OSX": + case "Darwin": + return "$name.$abi.dylib"; + + default: + // most *nix + return "$name.so.$abi"; + } + } + + private static function init(): void + { + // Already initialized. + if (self::$ffi_inited) { + return; + } + + $vips_libname = self::libraryName("libvips", 42); + if (PHP_OS_FAMILY === "Windows") { + $glib_libname = self::libraryName("libglib-2.0", 0); + $gobject_libname = self::libraryName("libgobject-2.0", 0); + } else { + $glib_libname = $vips_libname; + $gobject_libname = $vips_libname; + } + + Utils::debugLog("init", ["library" => $vips_libname]); + + $is_64bits = PHP_INT_SIZE === 8; + + $libraryPaths = [ + "" // system library + ]; + + $vipshome = getenv("VIPSHOME"); + if ($vipshome) { + // lib/ predicates lib/ + $libraryPaths[] = $vipshome . ($is_64bits ? "/lib64/" : "/lib32/"); + // lib/ is always searched + $libraryPaths[] = $vipshome . "/lib/"; + } + + if (PHP_OS_FAMILY === "OSX" || + PHP_OS_FAMILY === "Darwin") { + $libraryPaths[] = "/opt/homebrew/lib/"; // Homebrew on Apple Silicon + } + + // attempt to open libraries using the system library search method + // (no prefix) and a couple of fallback paths, if any + $vips = null; + foreach ($libraryPaths as $path) { + Utils::debugLog("init", ["path" => $path]); + + try { + $vips = \FFI::cdef(<< "library load failed", "exception" => $e]); + } + } + + if ($vips === null) { + array_shift($libraryPaths); + + $msg = "Unable to find library '$vips_libname'"; + if (!empty($libraryPaths)) { + $msg .= " in any of ['" . implode("', '", $libraryPaths) . "']"; + } + $msg .= ". Make sure that you've installed libvips and that '$vips_libname'"; + $msg .= " is on your system's library search path."; + throw new Exception($msg); + } + + $result = $vips->vips_init(""); + if ($result != 0) { + throw new Exception("libvips error: " . $vips->vips_error_buffer()); + } + Utils::debugLog("init", ["vips_init" => $result]); + + # get the library version number, then we can build the API + self::$library_major = $vips->vips_version(0); + self::$library_minor = $vips->vips_version(1); + self::$library_micro = $vips->vips_version(2); + Utils::debugLog("init", [ + "libvips version" => [ + self::$library_major, + self::$library_minor, + self::$library_micro + ] + ]); + + if (!self::atLeast(8, 7)) { + throw new Exception("your libvips is too old -- " . + "8.7 or later required"); + } + + // GType is the size of a pointer + $gtype = $is_64bits ? "guint64" : "guint32"; + + // Typedefs shared across the libvips, GLib and GObject declarations + $typedefs = <<vips_leak_set(1); + + # force the creation of some types we need + self::$vips->vips_blend_mode_get_type(); + self::$vips->vips_interpretation_get_type(); + self::$vips->vips_operation_flags_get_type(); + self::$vips->vips_band_format_get_type(); + self::$vips->vips_token_get_type(); + self::$vips->vips_saveable_get_type(); + self::$vips->vips_image_type_get_type(); + + // look these up in advance + self::$ctypes = [ + "GObject" => self::$gobject->type("GObject*"), + "GParamSpec" => self::$gobject->type("GParamSpec*"), + "VipsObject" => self::$vips->type("VipsObject*"), + "VipsOperation" => self::$vips->type("VipsOperation*"), + "VipsImage" => self::$vips->type("VipsImage*"), + "VipsInterpolate" => self::$vips->type("VipsInterpolate*"), + ]; + + self::$gtypes = [ + "gboolean" => self::$gobject->g_type_from_name("gboolean"), + "gint" => self::$gobject->g_type_from_name("gint"), + "gint64" => self::$gobject->g_type_from_name("gint64"), + "guint64" => self::$gobject->g_type_from_name("guint64"), + "gdouble" => self::$gobject->g_type_from_name("gdouble"), + "gchararray" => self::$gobject->g_type_from_name("gchararray"), + "VipsRefString" => self::$gobject->g_type_from_name("VipsRefString"), + + "GEnum" => self::$gobject->g_type_from_name("GEnum"), + "GFlags" => self::$gobject->g_type_from_name("GFlags"), + "VipsBandFormat" => self::$gobject->g_type_from_name("VipsBandFormat"), + "VipsBlendMode" => self::$gobject->g_type_from_name("VipsBlendMode"), + "VipsArrayInt" => self::$gobject->g_type_from_name("VipsArrayInt"), + "VipsArrayDouble" => + self::$gobject->g_type_from_name("VipsArrayDouble"), + "VipsArrayImage" => self::$gobject->g_type_from_name("VipsArrayImage"), + "VipsBlob" => self::$gobject->g_type_from_name("VipsBlob"), + + "GObject" => self::$gobject->g_type_from_name("GObject"), + "VipsImage" => self::$gobject->g_type_from_name("VipsImage"), + ]; + + // map vips format names to c type names + self::$ftypes = [ + "char" => "char", + "uchar" => "unsigned char", + "short" => "short", + "ushort" => "unsigned short", + "int" => "int", + "uint" => "unsigned int", + "float" => "float", + "double" => "double", + "complex" => "float", + "dpcomplex" => "double", + ]; + + Utils::debugLog("init", ["done"]); + self::$ffi_inited = true; + } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: expandtab sw=4 ts=4 fdm=marker + * vim<600: expandtab sw=4 ts=4 + */ diff --git a/src/GObject.php b/src/GObject.php index 46620b3..4ebc500 100644 --- a/src/GObject.php +++ b/src/GObject.php @@ -71,7 +71,7 @@ abstract class GObject */ public function __construct(\FFI\CData $pointer) { - $this->pointer = \FFI::cast(Config::ctypes("GObject"), $pointer); + $this->pointer = \FFI::cast(FFI::ctypes("GObject"), $pointer); } public function __destruct() @@ -86,12 +86,12 @@ public function __clone() public function ref(): void { - Config::gobject()->g_object_ref($this->pointer); + FFI::gobject()->g_object_ref($this->pointer); } public function unref(): void { - Config::gobject()->g_object_unref($this->pointer); + FFI::gobject()->g_object_unref($this->pointer); } // TODO signal marshalling to go in diff --git a/src/GValue.php b/src/GValue.php index 32d28d1..8560b90 100644 --- a/src/GValue.php +++ b/src/GValue.php @@ -46,7 +46,7 @@ class GValue public function __construct() { # allocate a gvalue on the heap, and make it persistent between requests - $this->struct = Config::gobject()->new("GValue", true, true); + $this->struct = FFI::gobject()->new("GValue", true, true); $this->pointer = \FFI::addr($this->struct); # GValue needs to be inited to all zero @@ -60,7 +60,7 @@ public function __construct() public static function toEnum(int $gtype, $value): int { if (is_string($value)) { - $enum_value = Config::vips()-> + $enum_value = FFI::vips()-> vips_enum_from_nick("php-vips", $gtype, $value); if ($enum_value < 0) { throw new Exception(); @@ -78,7 +78,7 @@ public static function toEnum(int $gtype, $value): int */ public static function fromEnum(int $gtype, int $value): string { - $result = Config::vips()->vips_enum_nick($gtype, $value); + $result = FFI::vips()->vips_enum_nick($gtype, $value); if ($result === null) { throw new Exception("value not in enum"); } @@ -88,12 +88,12 @@ public static function fromEnum(int $gtype, int $value): string public function __destruct() { - Config::gobject()->g_value_unset($this->pointer); + FFI::gobject()->g_value_unset($this->pointer); } public function setType(int $gtype): void { - Config::gobject()->g_value_init($this->pointer, $gtype); + FFI::gobject()->g_value_init($this->pointer, $gtype); } public function getType(): int @@ -113,36 +113,36 @@ public function set($value): void $gtype = $this->getType(); switch ($gtype) { - case Config::gtypes("gboolean"): - Config::gobject()->g_value_set_boolean($this->pointer, $value); + case FFI::gtypes("gboolean"): + FFI::gobject()->g_value_set_boolean($this->pointer, $value); break; - case Config::gtypes("gint"): - Config::gobject()->g_value_set_int($this->pointer, $value); + case FFI::gtypes("gint"): + FFI::gobject()->g_value_set_int($this->pointer, $value); break; - case Config::gtypes("gint64"): - Config::gobject()->g_value_set_int64($this->pointer, $value); + case FFI::gtypes("gint64"): + FFI::gobject()->g_value_set_int64($this->pointer, $value); break; - case Config::gtypes("guint64"): - Config::gobject()->g_value_set_uint64($this->pointer, $value); + case FFI::gtypes("guint64"): + FFI::gobject()->g_value_set_uint64($this->pointer, $value); break; - case Config::gtypes("gdouble"): - Config::gobject()->g_value_set_double($this->pointer, $value); + case FFI::gtypes("gdouble"): + FFI::gobject()->g_value_set_double($this->pointer, $value); break; - case Config::gtypes("gchararray"): - Config::gobject()->g_value_set_string($this->pointer, $value); + case FFI::gtypes("gchararray"): + FFI::gobject()->g_value_set_string($this->pointer, $value); break; - case Config::gtypes("VipsRefString"): - Config::vips()-> + case FFI::gtypes("VipsRefString"): + FFI::vips()-> vips_value_set_ref_string($this->pointer, $value); break; - case Config::gtypes("VipsArrayInt"): + case FFI::gtypes("VipsArrayInt"): if (!is_array($value)) { $value = [$value]; } @@ -152,11 +152,11 @@ public function set($value): void for ($i = 0; $i < $n; $i++) { $array[$i] = $value[$i]; } - Config::vips()-> + FFI::vips()-> vips_value_set_array_int($this->pointer, $array, $n); break; - case Config::gtypes("VipsArrayDouble"): + case FFI::gtypes("VipsArrayDouble"): if (!is_array($value)) { $value = [$value]; } @@ -166,17 +166,17 @@ public function set($value): void for ($i = 0; $i < $n; $i++) { $array[$i] = $value[$i]; } - Config::vips()-> + FFI::vips()-> vips_value_set_array_double($this->pointer, $array, $n); break; - case Config::gtypes("VipsArrayImage"): + case FFI::gtypes("VipsArrayImage"): if (!is_array($value)) { $value = [$value]; } $n = count($value); - Config::vips()->vips_value_set_array_image($this->pointer, $n); - $array = Config::vips()-> + FFI::vips()->vips_value_set_array_image($this->pointer, $n); + $array = FFI::vips()-> vips_value_get_array_image($this->pointer, null); for ($i = 0; $i < $n; $i++) { $image = $value[$i]; @@ -185,7 +185,7 @@ public function set($value): void } break; - case Config::gtypes("VipsBlob"): + case FFI::gtypes("VipsBlob"): # we need to set the blob to a copy of the data that vips_lib # can own and free $n = strlen($value); @@ -194,34 +194,34 @@ public function set($value): void for ($i = 0; $i < $n; $i++) { $memory[$i] = $value[$i]; } - Config::vips()-> + FFI::vips()-> vips_value_set_blob_free($this->pointer, $memory, $n); break; default: - $fundamental = Config::gobject()->g_type_fundamental($gtype); + $fundamental = FFI::gobject()->g_type_fundamental($gtype); switch ($fundamental) { - case Config::gtypes("GObject"): - Config::gobject()-> + case FFI::gtypes("GObject"): + FFI::gobject()-> g_value_set_object($this->pointer, $value->pointer); break; - case Config::gtypes("GEnum"): - Config::gobject()->g_value_set_enum( + case FFI::gtypes("GEnum"): + FFI::gobject()->g_value_set_enum( $this->pointer, self::toEnum($gtype, $value) ); break; - case Config::gtypes("GFlags"): + case FFI::gtypes("GFlags"): /* Just set as int. */ - Config::gobject()-> + FFI::gobject()-> g_value_set_flags($this->pointer, $value); break; default: - $typeName = Config::gobject()->g_type_name($gtype); + $typeName = FFI::gobject()->g_type_name($gtype); throw new \BadMethodCallException( "gtype $typeName ($gtype) not implemented" ); @@ -242,48 +242,48 @@ public function get() $result = null; switch ($gtype) { - case Config::gtypes("gboolean"): - $result = Config::gobject()->g_value_get_boolean($this->pointer); + case FFI::gtypes("gboolean"): + $result = FFI::gobject()->g_value_get_boolean($this->pointer); break; - case Config::gtypes("gint"): - $result = Config::gobject()->g_value_get_int($this->pointer); + case FFI::gtypes("gint"): + $result = FFI::gobject()->g_value_get_int($this->pointer); break; - case Config::gtypes("gint64"): - $result = Config::gobject()->g_value_get_int64($this->pointer); + case FFI::gtypes("gint64"): + $result = FFI::gobject()->g_value_get_int64($this->pointer); break; - case Config::gtypes("guint64"): - $result = Config::gobject()->g_value_get_uint64($this->pointer); + case FFI::gtypes("guint64"): + $result = FFI::gobject()->g_value_get_uint64($this->pointer); break; - case Config::gtypes("gdouble"): - $result = Config::gobject()->g_value_get_double($this->pointer); + case FFI::gtypes("gdouble"): + $result = FFI::gobject()->g_value_get_double($this->pointer); break; - case Config::gtypes("gchararray"): - $result = Config::gobject()->g_value_get_string($this->pointer); + case FFI::gtypes("gchararray"): + $result = FFI::gobject()->g_value_get_string($this->pointer); break; - case Config::gtypes("VipsRefString"): - $p_size = Config::vips()->new("size_t[1]"); - $result = Config::vips()-> + case FFI::gtypes("VipsRefString"): + $p_size = FFI::vips()->new("size_t[1]"); + $result = FFI::vips()-> vips_value_get_ref_string($this->pointer, $p_size); # $p_size[0] will be the string length, but assume it's null # terminated break; - case Config::gtypes("VipsImage"): - $pointer = Config::gobject()->g_value_get_object($this->pointer); + case FFI::gtypes("VipsImage"): + $pointer = FFI::gobject()->g_value_get_object($this->pointer); $result = new Image($pointer); // get_object does not increment the ref count $result->ref(); break; - case Config::gtypes("VipsArrayInt"): - $p_len = Config::vips()->new("int[1]"); - $pointer = Config::vips()-> + case FFI::gtypes("VipsArrayInt"): + $p_len = FFI::vips()->new("int[1]"); + $pointer = FFI::vips()-> vips_value_get_array_int($this->pointer, $p_len); $result = []; for ($i = 0; $i < $p_len[0]; $i++) { @@ -291,9 +291,9 @@ public function get() } break; - case Config::gtypes("VipsArrayDouble"): - $p_len = Config::vips()->new("int[1]"); - $pointer = Config::vips()-> + case FFI::gtypes("VipsArrayDouble"): + $p_len = FFI::vips()->new("int[1]"); + $pointer = FFI::vips()-> vips_value_get_array_double($this->pointer, $p_len); $result = []; for ($i = 0; $i < $p_len[0]; $i++) { @@ -301,9 +301,9 @@ public function get() } break; - case Config::gtypes("VipsArrayImage"): - $p_len = Config::vips()->new("int[1]"); - $pointer = Config::vips()-> + case FFI::gtypes("VipsArrayImage"): + $p_len = FFI::vips()->new("int[1]"); + $pointer = FFI::vips()-> vips_value_get_array_image($this->pointer, $p_len); $result = []; for ($i = 0; $i < $p_len[0]; $i++) { @@ -313,31 +313,31 @@ public function get() } break; - case Config::gtypes("VipsBlob"): - $p_len = Config::vips()->new("size_t[1]"); - $pointer = Config::vips()-> + case FFI::gtypes("VipsBlob"): + $p_len = FFI::vips()->new("size_t[1]"); + $pointer = FFI::vips()-> vips_value_get_blob($this->pointer, $p_len); $result = \FFI::string($pointer, $p_len[0]); break; default: - $fundamental = Config::gobject()->g_type_fundamental($gtype); + $fundamental = FFI::gobject()->g_type_fundamental($gtype); switch ($fundamental) { - case Config::gtypes("GEnum"): - $result = Config::gobject()-> + case FFI::gtypes("GEnum"): + $result = FFI::gobject()-> g_value_get_enum($this->pointer); $result = self::fromEnum($gtype, $result); break; - case Config::gtypes("GFlags"): + case FFI::gtypes("GFlags"): /* Just get as int. */ - $result = Config::gobject()-> + $result = FFI::gobject()-> g_value_get_flags($this->pointer); break; default: - $typeName = Config::gobject()->g_type_name($gtype); + $typeName = FFI::gobject()->g_type_name($gtype); throw new \BadMethodCallException( "gtype $typeName ($gtype) not implemented" ); diff --git a/src/Image.php b/src/Image.php index 414198f..5516f15 100644 --- a/src/Image.php +++ b/src/Image.php @@ -494,7 +494,7 @@ class Image extends ImageAutodoc implements \ArrayAccess */ public function __construct(\FFI\CData $pointer) { - $this->pointer = \FFI::cast(Config::ctypes("VipsImage"), $pointer); + $this->pointer = \FFI::cast(FFI::ctypes("VipsImage"), $pointer); parent::__construct($pointer); } @@ -689,7 +689,7 @@ private function callEnum( */ public static function findLoad(string $filename): ?string { - return Config::vips()->vips_foreign_find_load($filename); + return FFI::vips()->vips_foreign_find_load($filename); } /** @@ -734,7 +734,7 @@ public static function newFromFile( */ public static function findLoadBuffer(string $buffer): ?string { - return Config::vips()-> + return FFI::vips()-> vips_foreign_find_load_buffer($buffer, strlen($buffer)); } @@ -805,15 +805,15 @@ public static function newFromArray( } } - $pointer = Config::vips()-> + $pointer = FFI::vips()-> vips_image_new_matrix_from_array($width, $height, $a, $n); if ($pointer == null) { throw new Exception(); } $result = new Image($pointer); - $result->setType(Config::gtypes("gdouble"), 'scale', $scale); - $result->setType(Config::gtypes("gdouble"), 'offset', $offset); + $result->setType(FFI::gtypes("gdouble"), 'scale', $scale); + $result->setType(FFI::gtypes("gdouble"), 'offset', $offset); return $result; } @@ -842,7 +842,7 @@ public static function newFromMemory( * * TODO add a references system instead, see pyvips. */ - $pointer = Config::vips()->vips_image_new_from_memory_copy( + $pointer = FFI::vips()->vips_image_new_from_memory_copy( $data, strlen($data), $width, @@ -918,7 +918,7 @@ public function writeToFile(string $name, array $options = []): void $filename = Utils::filenameGetFilename($name); $string_options = Utils::filenameGetOptions($name); - $saver = Config::vips()->vips_foreign_find_save($filename); + $saver = FFI::vips()->vips_foreign_find_save($filename); if ($saver == "") { throw new Exception(); } @@ -952,7 +952,7 @@ public function writeToBuffer(string $suffix, array $options = []): string $filename = Utils::filenameGetFilename($suffix); $string_options = Utils::filenameGetOptions($suffix); - $saver = Config::vips()->vips_foreign_find_save_buffer($filename); + $saver = FFI::vips()->vips_foreign_find_save_buffer($filename); if ($saver == "") { throw new Exception(); } @@ -978,7 +978,7 @@ public function writeToMemory(): string $ctype = \FFI::arrayType(\FFI::type("size_t"), [1]); $p_size = \FFI::new($ctype); - $pointer = Config::vips()-> + $pointer = FFI::vips()-> vips_image_write_to_memory($this->pointer, $p_size); if ($pointer == null) { throw new Exception(); @@ -987,7 +987,7 @@ public function writeToMemory(): string // string() takes a copy $result = \FFI::string($pointer, $p_size[0]); - Config::glib()->g_free($pointer); + FFI::glib()->g_free($pointer); return $result; } @@ -1020,7 +1020,7 @@ public function writeToArray(): array $ctype = \FFI::arrayType(\FFI::type("size_t"), [1]); $p_size = \FFI::new($ctype); - $pointer = Config::vips()-> + $pointer = FFI::vips()-> vips_image_write_to_memory($this->pointer, $p_size); if ($pointer == null) { throw new Exception(); @@ -1028,7 +1028,7 @@ public function writeToArray(): array // wrap pointer up as a C array of the right type $n = $this->width * $this->height * $this->bands; - $type_name = Config::ftypes($this->format); + $type_name = FFI::ftypes($this->format); $ctype = \FFI::arrayType(\FFI::type($type_name), [$n]); $array = \FFI::cast($ctype, $pointer); @@ -1039,7 +1039,7 @@ public function writeToArray(): array } // the vips result is not PHP memory, so we must free it - Config::glib()->g_free($pointer); + FFI::glib()->g_free($pointer); return $result; } @@ -1060,7 +1060,7 @@ public function writeToArray(): array */ public function copyMemory(): Image { - $pointer = Config::vips()->vips_image_copy_memory($this->pointer); + $pointer = FFI::vips()->vips_image_copy_memory($this->pointer); if ($pointer == null) { throw new Exception(); } @@ -1125,7 +1125,7 @@ public function __isset(string $name): bool public function get(string $name) { $gvalue = new GValue(); - if (Config::vips()-> + if (FFI::vips()-> vips_image_get($this->pointer, $name, $gvalue->pointer) != 0) { throw new Exception(); } @@ -1144,7 +1144,7 @@ public function get(string $name) */ public function getType(string $name): int { - return Config::vips()->vips_image_get_typeof($this->pointer, $name); + return FFI::vips()->vips_image_get_typeof($this->pointer, $name); } /** @@ -1182,27 +1182,27 @@ public function set(string $name, $value): void if ($gtype == 0) { if (is_array($value)) { if (is_int($value[0])) { - $gtype = Config::gtypes("VipsArrayInt"); + $gtype = FFI::gtypes("VipsArrayInt"); } elseif (is_float($value[0])) { - $gtype = Config::gtypes("VipsArrayDouble"); + $gtype = FFI::gtypes("VipsArrayDouble"); } else { - $gtype = Config::gtypes("VipsArrayImage"); + $gtype = FFI::gtypes("VipsArrayImage"); } } elseif (is_int($value)) { - $gtype = Config::gtypes("gint"); + $gtype = FFI::gtypes("gint"); } elseif (is_float($value)) { - $gtype = Config::gtypes("gdouble"); + $gtype = FFI::gtypes("gdouble"); } elseif (is_string($value)) { - $gtype = Config::gtypes("VipsRefString"); + $gtype = FFI::gtypes("VipsRefString"); } else { - $gtype = Config::gtypes("VipsImage"); + $gtype = FFI::gtypes("VipsImage"); } } $gvalue->setType($gtype); $gvalue->set($value); - Config::vips()->vips_image_set($this->pointer, $name, $gvalue->pointer); + FFI::vips()->vips_image_set($this->pointer, $name, $gvalue->pointer); } /** @@ -1227,7 +1227,7 @@ public function setType($type, string $name, $value): void $gvalue = new GValue(); $gvalue->setType($type); $gvalue->set($value); - Config::vips()->vips_image_set($this->pointer, $name, $gvalue->pointer); + FFI::vips()->vips_image_set($this->pointer, $name, $gvalue->pointer); } /** @@ -1241,7 +1241,7 @@ public function setType($type, string $name, $value): void */ public function remove(string $name): void { - if (!Config::vips()->vips_image_remove($this->pointer, $name)) { + if (!FFI::vips()->vips_image_remove($this->pointer, $name)) { throw new Exception(); } } @@ -1920,7 +1920,7 @@ public function composite($other, $mode, array $options = []): Image # composite takes an arrayint, but it's really an array of blend modes # gvalue doesn't know this, so we must do name -> enum value mapping $mode = array_map(function ($x) { - return GValue::toEnum(Config::gtypes("VipsBlendMode"), $x); + return GValue::toEnum(FFI::gtypes("VipsBlendMode"), $x); }, $mode); return VipsOperation::call( diff --git a/src/Interpolate.php b/src/Interpolate.php index 9276b90..cb5eee5 100644 --- a/src/Interpolate.php +++ b/src/Interpolate.php @@ -62,7 +62,7 @@ class Interpolate extends VipsObject public function __construct(\FFI\CData $pointer) { - $this->pointer = \FFI::cast(Config::ctypes("VipsInterpolate"), $pointer); + $this->pointer = \FFI::cast(FFI::ctypes("VipsInterpolate"), $pointer); parent::__construct($pointer); } @@ -85,7 +85,7 @@ public function __construct(\FFI\CData $pointer) */ public static function newFromName(string $name): Interpolate { - $pointer = Config::vips()->vips_interpolate_new($name); + $pointer = FFI::vips()->vips_interpolate_new($name); if ($pointer == null) { throw new Exception(); } diff --git a/src/Introspect.php b/src/Introspect.php index f252bc6..ce3208c 100644 --- a/src/Introspect.php +++ b/src/Introspect.php @@ -101,11 +101,11 @@ public function __construct($operation_name) $this->description = $operation->getDescription(); - $p_names = Config::vips()->new("char**[1]"); - $p_flags = Config::vips()->new("int*[1]"); - $p_n_args = Config::vips()->new("int[1]"); - $result = Config::vips()->vips_object_get_args( - \FFI::cast(Config::ctypes("VipsObject"), $operation->pointer), + $p_names = FFI::vips()->new("char**[1]"); + $p_flags = FFI::vips()->new("int*[1]"); + $p_n_args = FFI::vips()->new("int[1]"); + $result = FFI::vips()->vips_object_get_args( + \FFI::cast(FFI::ctypes("VipsObject"), $operation->pointer), $p_names, $p_flags, $p_n_args @@ -183,7 +183,7 @@ public function __construct($operation_name) $this->member_this = ""; foreach ($this->required_input as $name) { $type = $this->arguments[$name]["type"]; - if ($type == Config::gtypes("VipsImage")) { + if ($type == FFI::gtypes("VipsImage")) { $this->member_this = $name; break; } @@ -208,7 +208,7 @@ public function __toString(): string $flags = $details["flags"]; $blurb = $details["blurb"]; $type = $details["type"]; - $typeName = Config::gobject()->g_type_name($type); + $typeName = FFI::gobject()->g_type_name($type); $result .= " $name:\n"; diff --git a/src/Utils.php b/src/Utils.php index 808af39..31e26fc 100644 --- a/src/Utils.php +++ b/src/Utils.php @@ -92,23 +92,23 @@ public static function errorLog(string $message, ?\Exception $exception = null): */ public static function typeFromName(string $name): int { - return Config::gobject()->g_type_from_name($name); + return FFI::gobject()->g_type_from_name($name); } public static function filenameGetFilename(string $name): string { - $pointer = Config::vips()->vips_filename_get_filename($name); + $pointer = FFI::vips()->vips_filename_get_filename($name); $filename = \FFI::string($pointer); - Config::glib()->g_free($pointer); + FFI::glib()->g_free($pointer); return $filename; } public static function filenameGetOptions(string $name): string { - $pointer = Config::vips()->vips_filename_get_options($name); + $pointer = FFI::vips()->vips_filename_get_options($name); $options = \FFI::string($pointer); - Config::glib()->g_free($pointer); + FFI::glib()->g_free($pointer); return $options; } diff --git a/src/VipsObject.php b/src/VipsObject.php index cf7db76..05abdc6 100644 --- a/src/VipsObject.php +++ b/src/VipsObject.php @@ -68,8 +68,8 @@ abstract class VipsObject extends GObject public function __construct(\FFI\CData $pointer) { - $this->pointer = \FFI::cast(Config::ctypes("VipsObject"), $pointer); - $this->gObject = \FFI::cast(Config::ctypes("GObject"), $pointer); + $this->pointer = \FFI::cast(FFI::ctypes("VipsObject"), $pointer); + $this->gObject = \FFI::cast(FFI::ctypes("GObject"), $pointer); parent::__construct($pointer); } @@ -77,12 +77,12 @@ public function __construct(\FFI\CData $pointer) // print a table of all active vipsobjects ... handy for debugging public static function printAll(): void { - Config::vips()->vips_object_print_all(); + FFI::vips()->vips_object_print_all(); } public function getDescription(): string { - return Config::vips()->vips_object_get_description($this->pointer); + return FFI::vips()->vips_object_get_description($this->pointer); } // get the pspec for a property @@ -92,10 +92,10 @@ public function getDescription(): string public function getPspec(string $name): ?\FFI\CData { $name = str_replace("-", "_", $name); - $pspec = Config::gobject()->new("GParamSpec*[1]"); - $argument_class = Config::vips()->new("VipsArgumentClass*[1]"); - $argument_instance = Config::vips()->new("VipsArgumentInstance*[1]"); - $result = Config::vips()->vips_object_get_argument( + $pspec = FFI::gobject()->new("GParamSpec*[1]"); + $argument_class = FFI::vips()->new("VipsArgumentClass*[1]"); + $argument_instance = FFI::vips()->new("VipsArgumentInstance*[1]"); + $result = FFI::vips()->vips_object_get_argument( $this->pointer, $name, $pspec, @@ -117,7 +117,7 @@ public function getType(string $name): int $pspec = $this->getPspec($name); if (\FFI::isNull($pspec)) { # need to clear any error, this is horrible - Config::vips()->vips_error_clear(); + FFI::vips()->vips_error_clear(); return 0; } else { return $pspec->value_type; @@ -127,13 +127,13 @@ public function getType(string $name): int public function getBlurb(string $name): string { $pspec = $this->getPspec($name); - return Config::gobject()->g_param_spec_get_blurb($pspec); + return FFI::gobject()->g_param_spec_get_blurb($pspec); } public function getArgumentDescription(string $name): string { $pspec = $this->getPspec($name); - return Config::gobject()->g_param_spec_get_description($pspec); + return FFI::gobject()->g_param_spec_get_description($pspec); } /** @@ -145,7 +145,7 @@ public function get(string $name) $gvalue = new GValue(); $gvalue->setType($this->getType($name)); - Config::gobject()-> + FFI::gobject()-> g_object_get_property($this->gObject, $name, $gvalue->pointer); $value = $gvalue->get(); @@ -166,13 +166,13 @@ public function set(string $name, $value): void $gvalue->setType($this->getType($name)); $gvalue->set($value); - Config::gobject()-> + FFI::gobject()-> g_object_set_property($this->gObject, $name, $gvalue->pointer); } public function setString(string $string_options): bool { - $result = Config::vips()-> + $result = FFI::vips()-> vips_object_set_from_string($this->pointer, $string_options); return $result == 0; @@ -180,7 +180,7 @@ public function setString(string $string_options): bool public function unrefOutputs(): void { - Config::vips()->vips_object_unref_outputs($this->pointer); + FFI::vips()->vips_object_unref_outputs($this->pointer); } } diff --git a/src/VipsOperation.php b/src/VipsOperation.php index b3f546b..b1eb86e 100644 --- a/src/VipsOperation.php +++ b/src/VipsOperation.php @@ -66,8 +66,8 @@ class VipsOperation extends VipsObject public function __construct(\FFI\CData $pointer) { - $this->pointer = Config::vips()-> - cast(Config::ctypes("VipsOperation"), $pointer); + $this->pointer = FFI::vips()-> + cast(FFI::ctypes("VipsOperation"), $pointer); parent::__construct($pointer); } @@ -77,7 +77,7 @@ public function __construct(\FFI\CData $pointer) */ public static function newFromName($name): VipsOperation { - $pointer = Config::vips()->vips_operation_new($name); + $pointer = FFI::vips()->vips_operation_new($name); if ($pointer == null) { throw new Exception(); } @@ -91,9 +91,9 @@ public function setMatch($name, $match_image, $value): void $gtype = $this->introspect->arguments[$name]["type"]; if ($match_image != null) { - if ($gtype == Config::gtypes("VipsImage")) { + if ($gtype == FFI::gtypes("VipsImage")) { $value = $match_image->imageize($value); - } elseif ($gtype == Config::gtypes("VipsArrayImage") && + } elseif ($gtype == FFI::gtypes("VipsArrayImage") && is_array($value)) { $new_value = []; foreach ($value as $x) { @@ -155,7 +155,7 @@ private static function findInside($predicate, $x) private static function isImagePointer($value): bool { return $value instanceof \FFI\CData && - \FFI::typeof($value) == Config::ctypes("VipsImage"); + \FFI::typeof($value) == FFI::ctypes("VipsImage"); } /** @@ -302,7 +302,7 @@ public static function callBase( /* Build the operation */ - $pointer = Config::vips()-> + $pointer = FFI::vips()-> vips_cache_operation_build($operation->pointer); if ($pointer == null) { $operation->unrefOutputs(); From c46db5539768b132a58f18c88790541b5ebbe440 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 11 Oct 2022 09:45:25 +0100 Subject: [PATCH 043/115] update for 2.1 --- CHANGELOG.md | 4 +++- README.md | 13 +++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bff6018..bfe47c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,11 @@ All notable changes to `:vips` will be documented in this file. -## master +## 2.1.0 - 2022-10-11 - allow "-" as a name component separator [andrews05] +- split FFI into a separate class [kleisauke] +- improve finding of library binary [jcupitt] ## 2.0.3 - 2022-07-04 diff --git a/README.md b/README.md index 93ecce9..a091cf7 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ to your `composer.json`: ``` "require": { - "jcupitt/vips" : "2.0.0" + "jcupitt/vips" : "2.1.0" } ``` @@ -112,7 +112,7 @@ either 255 or the original image. Note that libvips operators always make new images, they don't modify existing images, so after the line above, `$image` is unchanged. -You use long, double, array and image as parameters. For example: +You can use long, double, array and image as parameters. For example: ```php $image = $image->add(2); @@ -136,7 +136,7 @@ to add two images. Or: $image = $image->add([[1, 2, 3], [4, 5, 6]]); ``` -To make a 2 x 3 image from the array, then add that image to the original. +To make a 3 x 2 image from the array, then add that image to the original. Almost all methods can take an extra final argument: an array of options. For example: @@ -145,7 +145,7 @@ For example: $image->writeToFile("fred.jpg", ["Q" => 90]); ``` -`php-vips` comes [with full API +`php-vips` comes [with API docs](https://libvips.github.io/php-vips/classes/Jcupitt-Vips-Image.html). To regenerate these from your sources, type: @@ -155,8 +155,9 @@ $ vendor/bin/phpdoc And look in `docs/`. -There are around 300 operations in the library, see the vips docs for an -introduction: +Unfortunatly, due to php-doc limitations, these do not list every option +to every operation. For a full API description you need to see the main +libvips documentation: https://libvips.org/API/current From 7a832fded0ea51a19027e334c0bca8a80fefeafe Mon Sep 17 00:00:00 2001 From: Christian Stocker Date: Thu, 13 Oct 2022 10:46:46 +0200 Subject: [PATCH 044/115] Remove mixed here, not supported by PHP 7.4 (#161) --- src/Image.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Image.php b/src/Image.php index 5516f15..d599757 100644 --- a/src/Image.php +++ b/src/Image.php @@ -832,7 +832,7 @@ public static function newFromArray( * @return Image A new Image. */ public static function newFromMemory( - mixed $data, + $data, int $width, int $height, int $bands, From 247ab4f2d4c70f03ed7d98ee8c62a1e80e12e1c7 Mon Sep 17 00:00:00 2001 From: Christian Stocker Date: Thu, 13 Oct 2022 10:48:04 +0200 Subject: [PATCH 045/115] =?UTF-8?q?Avoid=20=E2=80=9CDeprecate=20dynamic=20?= =?UTF-8?q?properties=E2=80=9D=20warning=20in=20PHP=208.2=20(#159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/generate_phpdoc.py | 2 ++ src/ImageAutodoc.php | 2 ++ 2 files changed, 4 insertions(+) diff --git a/examples/generate_phpdoc.py b/examples/generate_phpdoc.py index 9676c8e..0f2c60f 100755 --- a/examples/generate_phpdoc.py +++ b/examples/generate_phpdoc.py @@ -265,6 +265,8 @@ def add_nickname(gtype, a, b): f.write(' */\n') f.write('abstract class ImageAutodoc extends VipsObject\n') f.write('{\n') + f.write(' abstract public function __set(string $name, $value);\n') + f.write(' abstract public function __get(string $name);\n') f.write('}\n') diff --git a/src/ImageAutodoc.php b/src/ImageAutodoc.php index 531084a..529c834 100644 --- a/src/ImageAutodoc.php +++ b/src/ImageAutodoc.php @@ -703,4 +703,6 @@ */ abstract class ImageAutodoc extends VipsObject { + abstract public function __set(string $name, $value); + abstract public function __get(string $name); } From 9496dd2a36e64dc6a04249200e4e795b22231bde Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 13 Nov 2022 12:37:06 +0000 Subject: [PATCH 046/115] WIP -- work around php-ffi memory leaks (#171) * work around a php-ffi memleak Due to a bug (I think?) in php-ffi we leaked c. 100 bytes for every call to getPspec(). This could really add up in long running programs. Thanks @levmv! See https://github.com/libvips/php-vips/issues/167 * credit levmv in changelog * work around a php-ffi leak in arrayType php-ffi seems to leak if you use arrayType ... this commit switches to string arguments instead * tests pass * better test for disabled ffi see https://github.com/libvips/php-vips/issues/172 --- CHANGELOG.md | 7 +++++++ src/FFI.php | 13 ++++++++++--- src/GValue.php | 9 +++------ src/Image.php | 14 +++++--------- src/VipsObject.php | 31 ++++++++++++++++++++++--------- src/VipsOperation.php | 20 +++++++++++--------- 6 files changed, 58 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bfe47c7..8b215c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to `:vips` will be documented in this file. +## master + +- refactor callBase() for maintainability +- work around a php-ffi memory leak in getPspec() [levmv] +- work around a php-ffi memory leak in arrayType() +- better test for disabled ffi + ## 2.1.0 - 2022-10-11 - allow "-" as a name component separator [andrews05] diff --git a/src/FFI.php b/src/FFI.php index 6db84ff..f7a8277 100644 --- a/src/FFI.php +++ b/src/FFI.php @@ -201,6 +201,13 @@ private static function init(): void return; } + // try experimentally binding a bit of stdio ... if this fails, FFI + // has probably not been installed or enabled, and php will throw a + // useful error message + $stdio = \FFI::cdef(<< vips_image_write_to_memory($this->pointer, $p_size); @@ -1017,8 +1015,7 @@ public function writeToMemory(): string */ public function writeToArray(): array { - $ctype = \FFI::arrayType(\FFI::type("size_t"), [1]); - $p_size = \FFI::new($ctype); + $p_size = \FFI::new("size_t[1]"); $pointer = FFI::vips()-> vips_image_write_to_memory($this->pointer, $p_size); @@ -1027,10 +1024,9 @@ public function writeToArray(): array } // wrap pointer up as a C array of the right type - $n = $this->width * $this->height * $this->bands; $type_name = FFI::ftypes($this->format); - $ctype = \FFI::arrayType(\FFI::type($type_name), [$n]); - $array = \FFI::cast($ctype, $pointer); + $n = $this->width * $this->height * $this->bands; + $array = \FFI::cast("{$type_name}[$n]", $pointer); // copy to PHP memory as a flat array $result = []; diff --git a/src/VipsObject.php b/src/VipsObject.php index 05abdc6..74bb83b 100644 --- a/src/VipsObject.php +++ b/src/VipsObject.php @@ -88,26 +88,39 @@ public function getDescription(): string // get the pspec for a property // NULL for no such name // very slow! avoid if possible - // FIXME add a cache for this thing + // FIXME add a cache for this thing, see code in pyvips public function getPspec(string $name): ?\FFI\CData { $name = str_replace("-", "_", $name); - $pspec = FFI::gobject()->new("GParamSpec*[1]"); - $argument_class = FFI::vips()->new("VipsArgumentClass*[1]"); - $argument_instance = FFI::vips()->new("VipsArgumentInstance*[1]"); + $pspec_array = FFI::gobject()->new("GParamSpec*[1]"); + $argument_class_array = FFI::vips()->new("VipsArgumentClass*[1]"); + $argument_instance_array = FFI::vips()->new("VipsArgumentInstance*[1]"); $result = FFI::vips()->vips_object_get_argument( $this->pointer, $name, - $pspec, - $argument_class, - $argument_instance + $pspec_array, + $argument_class_array, + $argument_instance_array ); if ($result != 0) { - return null; + $pspec = null; } else { - return $pspec[0]; + /* php-ffi seems to leak if we do the obvious $pspec_array[0] to + * get the return result ... instead, we must make a new pointer + * object and copy the value ourselves + * + * the returns values from vips_object_get_argument() are static, + * so this is safe + */ + $pspec = FFI::gobject()->new("GParamSpec*"); + $to_pointer = \FFI::addr($pspec); + $from_pointer = \FFI::addr($pspec_array); + $size = \FFI::sizeof($from_pointer); + \FFI::memcpy($to_pointer, $from_pointer, $size); } + + return $pspec; } // get the type of a property from a VipsObject diff --git a/src/VipsOperation.php b/src/VipsOperation.php index b1eb86e..cf212af 100644 --- a/src/VipsOperation.php +++ b/src/VipsOperation.php @@ -81,8 +81,9 @@ public static function newFromName($name): VipsOperation if ($pointer == null) { throw new Exception(); } + $operation = new VipsOperation($pointer); - return new VipsOperation($pointer); + return $operation; } public function setMatch($name, $match_image, $value): void @@ -223,6 +224,7 @@ public static function callBase( $operation = self::newFromName($operation_name); $operation->introspect = self::introspect($operation_name); + $introspect = $operation->introspect; /* the first image argument is the thing we expand constants to * match ... look inside tables for images, since we may be passing @@ -244,11 +246,11 @@ public static function callBase( * args, using instance if required, and only check the nargs after * this pass. */ - $n_required = count($operation->introspect->required_input); + $n_required = count($introspect->required_input); $n_supplied = count($arguments); $n_used = 0; - foreach ($operation->introspect->required_input as $name) { - if ($name == $operation->introspect->member_this) { + foreach ($introspect->required_input as $name) { + if ($name == $introspect->member_this) { if (!$instance) { $operation->unrefOutputs(); throw new Exception("instance argument not supplied"); @@ -291,8 +293,8 @@ public static function callBase( */ foreach ($options as $name => $value) { $name = str_replace("-", "_", $name); - if (!in_array($name, $operation->introspect->optional_input) && - !in_array($name, $operation->introspect->optional_output)) { + if (!in_array($name, $introspect->optional_input) && + !in_array($name, $introspect->optional_output)) { $operation->unrefOutputs(); throw new Exception("optional argument '$name' does not exist"); } @@ -309,7 +311,7 @@ public static function callBase( throw new Exception(); } $operation = new VipsOperation($pointer); - $operation->introspect = self::introspect($operation_name); + $operation->introspect = $introspect; # TODO .. need to attach input refs to output, see _find_inside in # pyvips @@ -317,14 +319,14 @@ public static function callBase( /* Fetch required output args (and modified input args). */ $result = []; - foreach ($operation->introspect->required_output as $name) { + foreach ($introspect->required_output as $name) { $result[$name] = $operation->get($name); } /* Any optional output args. */ $option_keys = array_keys($options); - foreach ($operation->introspect->optional_output as $name) { + foreach ($introspect->optional_output as $name) { $name = str_replace("-", "_", $name); if (in_array($name, $option_keys)) { $result[$name] = $operation->get($name); From 57983ad1b96185713974d7cffaa87147f3a7dc93 Mon Sep 17 00:00:00 2001 From: Christian Stocker Date: Sun, 13 Nov 2022 13:41:33 +0100 Subject: [PATCH 047/115] Add tests with GitHub actions (#160) * add github tests * Remove Travis tests * incorporate feedback from PR * Rename tests to CI * --no-install-recommends --- .github/workflows/ci.yml | 37 +++++++++++++++++++++++++ .travis.yml | 60 ---------------------------------------- 2 files changed, 37 insertions(+), 60 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5b7dd18 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,37 @@ +name: CI + +on: [ push, pull_request ] + +jobs: + CI: + name: ${{ matrix.php }} + runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + include: + - php: '7.4' + - php: '8.0' + - php: '8.1' + - php: '8.2' + + steps: + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + tools: composer:v2 + coverage: none + + - name: Checkout code + uses: actions/checkout@v3 + + - name: Install vips + run: sudo apt install -y libvips --no-install-recommends + + - name: Install composer dependencies + run: | + composer update --prefer-dist --no-interaction --no-progress --no-ansi + + - name: PHPUnit + run: composer test diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index fb20029..0000000 --- a/.travis.yml +++ /dev/null @@ -1,60 +0,0 @@ -language: php - -dist: bionic - -php: - - 7.3 - - 7.4 - -env: - global: - - VIPS_VERSION=8.10.0 - - PATH=$HOME/vips/bin:$PATH - - LD_LIBRARY_PATH=$HOME/vips/lib:$LD_LIBRARY_PATH - - PKG_CONFIG_PATH=$HOME/vips/lib/pkgconfig:$PKG_CONFIG_PATH - -cache: - apt: true - directories: - - $HOME/.composer/cache - - $HOME/vips - -addons: - apt: - packages: - # main dependencies - - libcfitsio-dev - - libexif-dev - - libexpat1-dev - - libfftw3-dev - - libgif-dev - - libgsf-1-dev - - libgsl-dev - - liblcms2-dev - - libmagickwand-dev - - libmatio-dev - - libnifti-dev - - libopenexr-dev - - libopenslide-dev - - liborc-0.4-dev - - libpango1.0-dev - - libpng-dev - - libpoppler-glib-dev - - librsvg2-dev - - libtiff5-dev - - libwebp-dev - # needed for building libvips from source - - gtk-doc-tools - - gobject-introspection - -before_install: - - bash install-vips.sh - --disable-dependency-tracking - --disable-introspection - --disable-gtk-doc-html - --disable-gtk-doc - - yes '' | pecl install vips - -install: composer install --prefer-dist - -script: composer test From c388e68219d1165fec8d242b8d07779d016477ac Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 13 Nov 2022 12:43:17 +0000 Subject: [PATCH 048/115] remove error_buffer_copy (#163) We had a stray declaration of vips_error_buffer_copy() in FFI.php. This would prevent php-vips starting with libvips before 8.9. Thanks Waschnick. see https://github.com/libvips/php-vips/issues/162 --- CHANGELOG.md | 2 ++ README.md | 2 +- src/FFI.php | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b215c0..6184fab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to `:vips` will be documented in this file. ## master +- remove unused vips_error_buffer_copy() declaration to fix compatibility with + libvips before 8.9 [Waschnick] - refactor callBase() for maintainability - work around a php-ffi memory leak in getPspec() [levmv] - work around a php-ffi memory leak in arrayType() diff --git a/README.md b/README.md index a091cf7..4089894 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ to your `composer.json`: php-vips does not yet support preloading, so you need to enable FFI globally. This has some security implications, since anyone who can run php on your -server can use it to make calls off into any native library they can find. +server can use it to call any native library they have access to. Of course if attackers are running their own PHP code on your webserver you are probably already toast, unfortunately. diff --git a/src/FFI.php b/src/FFI.php index f7a8277..0da75ca 100644 --- a/src/FFI.php +++ b/src/FFI.php @@ -443,7 +443,6 @@ private static function init(): void int vips_shutdown (void); const char *vips_error_buffer (void); -char *vips_error_buffer_copy (void); void vips_error_clear (void); void vips_error_freeze (void); void vips_error_thaw (void); From 87e6c974af52ca69a030c64ba2e9477d3a1cb4c2 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Sun, 13 Nov 2022 14:56:05 +0100 Subject: [PATCH 049/115] Update CI badge in README.md (#173) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4089894..4747982 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # PHP binding for libvips -[![Build Status](https://travis-ci.org/libvips/php-vips.svg?branch=master)](https://travis-ci.org/libvips/php-vips) +[![CI](https://github.com/libvips/php-vips/workflows/CI/badge.svg)](https://github.com/libvips/php-vips/actions) `php-vips` is a binding for [libvips](https://github.com/libvips/libvips) 8.7 and later that runs on PHP 7.4 and later. From ff7cb36fde54887ca61647b3ff76598caa69bd25 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 13 Nov 2022 16:59:57 +0000 Subject: [PATCH 050/115] tag as v2.1.1 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6184fab..d55e196 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to `:vips` will be documented in this file. -## master +## 2.1.1 - 2022-11-13 - remove unused vips_error_buffer_copy() declaration to fix compatibility with libvips before 8.9 [Waschnick] From cfe001ceaeb76a9427f69642f34334050c504853 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 24 Jan 2023 15:41:45 +0000 Subject: [PATCH 051/115] fix startup on windows Binding printf() won't work on windows. We should find a better way to test if ffi is enabled. Thanks West14. See https://github.com/libvips/php-vips/issues/183 --- CHANGELOG.md | 4 ++++ examples/addconst.php | 2 +- examples/bench.php | 2 +- examples/class.php | 2 +- examples/sig.php | 2 +- examples/vips-magick.php | 2 +- src/FFI.php | 12 +++++++++--- 7 files changed, 18 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d55e196..acc5eae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to `:vips` will be documented in this file. +## master + +- fix startup on windows [West14] + ## 2.1.1 - 2022-11-13 - remove unused vips_error_buffer_copy() declaration to fix compatibility with diff --git a/examples/addconst.php b/examples/addconst.php index 9722a89..971d757 100755 --- a/examples/addconst.php +++ b/examples/addconst.php @@ -1,7 +1,7 @@ #!/usr/bin/env php Date: Tue, 24 Jan 2023 19:11:31 +0000 Subject: [PATCH 052/115] improve ffi startup again thanks West14 see https://github.com/libvips/php-vips/issues/183 --- CHANGELOG.md | 2 +- examples/composer.json | 2 +- src/FFI.php | 17 ++++++----------- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index acc5eae..950805b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to `:vips` will be documented in this file. ## master -- fix startup on windows [West14] +- improve FFI startup [West14] ## 2.1.1 - 2022-11-13 diff --git a/examples/composer.json b/examples/composer.json index 2de9b0e..46f0eba 100644 --- a/examples/composer.json +++ b/examples/composer.json @@ -1,5 +1,5 @@ { "require": { - "jcupitt/vips": "2.0.0" + "jcupitt/vips": "2.1.1" } } diff --git a/src/FFI.php b/src/FFI.php index fff9c77..b76c8bf 100644 --- a/src/FFI.php +++ b/src/FFI.php @@ -201,17 +201,12 @@ private static function init(): void return; } - // try experimentally binding a bit of stdio ... if this fails, FFI - // has probably not been installed or enabled, and php will throw a - // useful error message - // - // this won't work on windows since there's no run time linker - // - // FIXME ... find a better way to test if FFI has been enabled - if (PHP_OS_FAMILY !== "Windows") { - $stdio = \FFI::cdef(<< Date: Sun, 26 Feb 2023 16:54:25 +0100 Subject: [PATCH 053/115] 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 054/115] 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 055/115] 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 056/115] 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 057/115] 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 058/115] 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 059/115] 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 a95fbcbfe61d9265f0d645f9719823363c621928 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 5 Mar 2023 13:09:54 +0000 Subject: [PATCH 060/115] use `composer.json` in the project root So the examples always use the latest version. --- CHANGELOG.md | 1 + examples/addconst.php | 2 +- examples/bench.php | 7 ++++++- examples/class.php | 2 +- examples/composer.json | 5 ----- examples/sig.php | 7 ++++++- examples/vips-magick.php | 7 ++++++- examples/watermark-image.php | 4 ++-- examples/watermark-text.php | 2 +- 9 files changed, 24 insertions(+), 13 deletions(-) delete mode 100644 examples/composer.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 950805b..f2d86ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to `:vips` will be documented in this file. ## master - improve FFI startup [West14] +- revise example use of composer [jcupitt] ## 2.1.1 - 2022-11-13 diff --git a/examples/addconst.php b/examples/addconst.php index 971d757..e9d330e 100755 --- a/examples/addconst.php +++ b/examples/addconst.php @@ -1,7 +1,7 @@ #!/usr/bin/env php Vips\Access::SEQUENTIAL]); $im = $im->crop(100, 100, $im->width - 200, $im->height - 200); diff --git a/examples/class.php b/examples/class.php index c98b72a..47c5b3b 100755 --- a/examples/class.php +++ b/examples/class.php @@ -1,7 +1,7 @@ #!/usr/bin/env php Vips\Access::SEQUENTIAL]); /** diff --git a/examples/vips-magick.php b/examples/vips-magick.php index 5d355c6..42e6691 100755 --- a/examples/vips-magick.php +++ b/examples/vips-magick.php @@ -1,10 +1,15 @@ #!/usr/bin/env php Date: Mon, 13 Mar 2023 12:27:59 +0100 Subject: [PATCH 061/115] 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 d11e3e28bd043954827fee8d2c1b2bafed0ca916 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 24 Mar 2023 15:15:00 +0000 Subject: [PATCH 062/115] fix bandrank We were not forming the argument image list correctly. see https://github.com/libvips/php-vips/issues/195 --- CHANGELOG.md | 1 + src/Image.php | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2d86ba..13e1709 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to `:vips` will be documented in this file. - improve FFI startup [West14] - revise example use of composer [jcupitt] +- fix bandrank [axkirillov] ## 2.1.1 - 2022-11-13 diff --git a/src/Image.php b/src/Image.php index bbddf07..ab2c2b4 100644 --- a/src/Image.php +++ b/src/Image.php @@ -1885,7 +1885,11 @@ public function bandrank($other, array $options = []): Image $other = (array) $other; } - return VipsOperation::call('bandrank', $this, $other, $options); + return VipsOperation::call( + 'bandrank', + null, + [array_merge([$this], $other)], + $options); } /** From aa86adc368fa8a70369999e7770bdf013b6ecb9c Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 3 May 2023 08:52:46 +0100 Subject: [PATCH 063/115] fix FFI startup log message thanks ganicus see https://github.com/libvips/php-vips/issues/201 --- src/FFI.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/FFI.php b/src/FFI.php index b76c8bf..4f9d726 100644 --- a/src/FFI.php +++ b/src/FFI.php @@ -253,7 +253,10 @@ private static function init(): void EOS, $path . $vips_libname); break; } catch (\FFI\Exception $e) { - Utils::debugLog("init", ["msg" => "library load failed", "exception" => $e]); + Utils::debugLog("init", [ + "msg" => "library load failed", + "exception" => $e->getMessage() + ]); } } From 972f7c3c4c71b534db21cb7e23ffc01d24c2950f Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 3 May 2023 08:54:18 +0100 Subject: [PATCH 064/115] credit ganicus in changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13e1709..de03a92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ All notable changes to `:vips` will be documented in this file. - improve FFI startup [West14] - revise example use of composer [jcupitt] - fix bandrank [axkirillov] +- fix FFI startup log error [ganicus] ## 2.1.1 - 2022-11-13 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 065/115] 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 066/115] 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 067/115] 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 068/115] 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 069/115] 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 070/115] 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 071/115] 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); } From 8196dafe21d739698cabcb15ba707c8a25d17b1e Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Thu, 29 Jun 2023 21:46:24 +0200 Subject: [PATCH 072/115] Fix CI (#202) --- src/FFI.php | 2 +- src/Image.php | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/FFI.php b/src/FFI.php index 4f9d726..16cc02d 100644 --- a/src/FFI.php +++ b/src/FFI.php @@ -254,7 +254,7 @@ private static function init(): void break; } catch (\FFI\Exception $e) { Utils::debugLog("init", [ - "msg" => "library load failed", + "msg" => "library load failed", "exception" => $e->getMessage() ]); } diff --git a/src/Image.php b/src/Image.php index ab2c2b4..39646f1 100644 --- a/src/Image.php +++ b/src/Image.php @@ -1889,7 +1889,8 @@ public function bandrank($other, array $options = []): Image 'bandrank', null, [array_merge([$this], $other)], - $options); + $options + ); } /** From cbd5b0528578e51b0b050be4016762ef6c783bee Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 30 Jun 2023 01:36:58 +0100 Subject: [PATCH 073/115] update enums A few were dropped in https://github.com/libvips/php-vips/pull/191 --- src/ForeignDzLayout.php | 1 + src/ForeignHeifEncoder.php | 57 ++++++++++++++++++++++++++++++++++++++ src/ForeignPpmFormat.php | 1 + src/ImageAutodoc.php | 44 +++++++++++++++++++++++------ src/OperationMath.php | 6 ++++ src/OperationMath2.php | 1 + src/TextWrap.php | 56 +++++++++++++++++++++++++++++++++++++ 7 files changed, 158 insertions(+), 8 deletions(-) create mode 100644 src/ForeignHeifEncoder.php create mode 100644 src/TextWrap.php diff --git a/src/ForeignDzLayout.php b/src/ForeignDzLayout.php index 021d75f..ae36259 100644 --- a/src/ForeignDzLayout.php +++ b/src/ForeignDzLayout.php @@ -53,4 +53,5 @@ abstract class ForeignDzLayout const ZOOMIFY = 'zoomify'; const GOOGLE = 'google'; const IIIF = 'iiif'; + const IIIF3 = 'iiif3'; } diff --git a/src/ForeignHeifEncoder.php b/src/ForeignHeifEncoder.php new file mode 100644 index 0000000..718644b --- /dev/null +++ b/src/ForeignHeifEncoder.php @@ -0,0 +1,57 @@ + + * @copyright 2016 John Cupitt + * @license https://opensource.org/licenses/MIT MIT + * @link https://github.com/jcupitt/php-vips + */ + +namespace Jcupitt\Vips; + +/** + * The ForeignHeifEncoder 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 ForeignHeifEncoder +{ + const AUTO = 'auto'; + const AOM = 'aom'; + const RAV1E = 'rav1e'; + const SVT = 'svt'; + const X265 = 'x265'; +} diff --git a/src/ForeignPpmFormat.php b/src/ForeignPpmFormat.php index ed28dc8..e476cc6 100644 --- a/src/ForeignPpmFormat.php +++ b/src/ForeignPpmFormat.php @@ -53,4 +53,5 @@ abstract class ForeignPpmFormat const PGM = 'pgm'; const PPM = 'ppm'; const PFM = 'pfm'; + const PNM = 'pnm'; } diff --git a/src/ImageAutodoc.php b/src/ImageAutodoc.php index 36f017b..e8b80e7 100644 --- a/src/ImageAutodoc.php +++ b/src/ImageAutodoc.php @@ -203,6 +203,8 @@ * @throws Exception * @method string dzsave_buffer(array $options = []) Save image to dz buffer. * @throws Exception + * @method void dzsave_target(Target $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. @@ -260,6 +262,12 @@ * @throws Exception * @method static Image gifload_source(Source $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(Target $target, array $options = []) Save as gif. + * @throws Exception * @method Image globalbalance(array $options = []) Global balance an image mosaic. * @throws Exception * @method Image gravity(string $direction, integer $width, integer $height, array $options = []) Place an image within a larger image with a certain gravity. @@ -326,6 +334,18 @@ * @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(Source $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(Target $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. @@ -358,11 +378,11 @@ * @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. + * @method static Image magickload(string $filename, array $options = []) Load file with ImageMagick7. * @throws Exception - * @method static Image magickload_buffer(string $buffer, array $options = []) Load buffer with ImageMagick. + * @method static Image magickload_buffer(string $buffer, array $options = []) Load buffer with ImageMagick7. * @throws Exception * @method void magicksave(string $filename, array $options = []) Save file with ImageMagick. * @throws Exception @@ -461,9 +481,9 @@ * @throws Exception * @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. + * @method void pngsave(string $filename, array $options = []) Save image to file as PNG. * @throws Exception - * @method string pngsave_buffer(array $options = []) Save image to png buffer. + * @method string pngsave_buffer(array $options = []) Save image to buffer as PNG. * @throws Exception * @method void pngsave_target(Target $target, array $options = []) Save image to target as PNG. * @throws Exception @@ -477,6 +497,8 @@ * @throws Exception * @method Image premultiply(array $options = []) Premultiply image alpha. * @throws Exception + * @method Image prewitt(array $options = []) Prewitt edge detector. + * @throws Exception * @method array profile(array $options = []) Find image profiles. * Return array with: [ * 'columns' => @type Image First non-zero pixel in column @@ -557,6 +579,8 @@ * @throws Exception * @method Image scale(array $options = []) Scale an image to uchar. * @throws Exception + * @method Image scharr(array $options = []) Scharr edge detector. + * @throws Exception * @method Image sequential(array $options = []) Check sequential access. * @throws Exception * @method Image sharpen(array $options = []) Unsharp masking for print. @@ -619,6 +643,8 @@ * @throws Exception * @method string tiffsave_buffer(array $options = []) Save image to tiff buffer. * @throws Exception + * @method void tiffsave_target(Target $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. @@ -641,11 +667,13 @@ * @throws Exception * @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. + * @method void webpsave(string $filename, array $options = []) Save as WebP. + * @throws Exception + * @method string webpsave_buffer(array $options = []) Save as WebP. * @throws Exception - * @method string webpsave_buffer(array $options = []) Save image to webp buffer. + * @method void webpsave_mime(array $options = []) Save image to webp mime. * @throws Exception - * @method void webpsave_target(Target $target, array $options = []) Save image to webp target. + * @method void webpsave_target(Target $target, array $options = []) Save as WebP. * @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 52ffef1..857b488 100644 --- a/src/OperationMath.php +++ b/src/OperationMath.php @@ -59,4 +59,10 @@ 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 9e86093..b1fc69e 100644 --- a/src/OperationMath2.php +++ b/src/OperationMath2.php @@ -51,4 +51,5 @@ abstract class OperationMath2 { const POW = 'pow'; const WOP = 'wop'; + const ATAN2 = 'atan2'; } diff --git a/src/TextWrap.php b/src/TextWrap.php new file mode 100644 index 0000000..db6ff14 --- /dev/null +++ b/src/TextWrap.php @@ -0,0 +1,56 @@ + + * @copyright 2016 John Cupitt + * @license https://opensource.org/licenses/MIT MIT + * @link https://github.com/jcupitt/php-vips + */ + +namespace Jcupitt\Vips; + +/** + * The TextWrap 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 TextWrap +{ + const WORD = 'word'; + const CHAR = 'char'; + const WORD_CHAR = 'word-char'; + const NONE = 'none'; +} From a38b72c0a131da073a9aa119c205286bf61003f6 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 30 Jun 2023 01:48:52 +0100 Subject: [PATCH 074/115] Improve library finding (#206) * improve library finding This PR adds a libraryLoad() function which searches a path for a library that supports an API, then uses that to load libvips, libglib and libgobject. This fixes #178 and #201 See also https://github.com/libvips/php-vips/pull/198 Tested on macOS 13.4 * fix formatting --- src/FFI.php | 70 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 45 insertions(+), 25 deletions(-) diff --git a/src/FFI.php b/src/FFI.php index ce00d33..7ed1f3b 100644 --- a/src/FFI.php +++ b/src/FFI.php @@ -204,6 +204,29 @@ private static function libraryName(string $name, int $abi): string // most *nix return "$name.so.$abi"; } + + return null; + } + + private static function libraryLoad( + array $libraryPaths, + string $libraryName, + string $interface + ): \FFI { + Utils::debugLog("trying to open", ["libraryName" => $libraryName]); + foreach ($libraryPaths as $path) { + Utils::debugLog("trying path", ["path" => $path]); + try { + $library = \FFI::cdef($interface, $path . $libraryName); + Utils::debugLog("success", []); + return $library; + } catch (\FFI\Exception $e) { + Utils::debugLog("init", [ + "msg" => "library load failed", + "exception" => $e->getMessage() + ]); + } + } } private static function init(): void @@ -251,30 +274,15 @@ private static function init(): void $libraryPaths[] = "/opt/homebrew/lib/"; // Homebrew on Apple Silicon } - // attempt to open libraries using the system library search method - // (no prefix) and a couple of fallback paths, if any - $vips = null; - foreach ($libraryPaths as $path) { - Utils::debugLog("init", ["path" => $path]); - - try { - $vips = \FFI::cdef(<< "library load failed", - "exception" => $e->getMessage() - ]); - } - } + $vips = self::libraryLoad($libraryPaths, $vips_libname, <<vips_leak_set(1); From bbedf8de38f6012bc30e7e946e22e9fdb35e504a Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 30 Jun 2023 01:55:06 +0100 Subject: [PATCH 075/115] add setProgress And an example program to demo it. Uses signalConnect, see https://github.com/libvips/php-vips/pull/191 --- examples/progress.php | 28 ++++++++++++++++++++++++++++ src/FFI.php | 1 + src/GObject.php | 2 +- src/Image.php | 17 +++++++++++++++++ 4 files changed, 47 insertions(+), 1 deletion(-) create mode 100755 examples/progress.php diff --git a/examples/progress.php b/examples/progress.php new file mode 100755 index 0000000..eca9a57 --- /dev/null +++ b/examples/progress.php @@ -0,0 +1,28 @@ +#!/usr/bin/env php +setProgress(true); + +$image->signalConnect("preeval", function($image, $progress) { + echo "preeval:\n"; +}); +$image->signalConnect("eval", function($image, $progress) { + echo "eval: $progress->percent % complete\r"; +}); + +$image->signalConnect("posteval", function($image, $progress) { + echo "\nposteval:\n"; +}); + +// trigger evaluation +$image->avg(); + +$image = null; + +Vips\FFI::shutDown(); diff --git a/src/FFI.php b/src/FFI.php index 7ed1f3b..8b2859f 100644 --- a/src/FFI.php +++ b/src/FFI.php @@ -808,6 +808,7 @@ private static function init(): void "VipsSourceCustom" => self::$vips->type("VipsSourceCustom*"), "VipsTarget" => self::$vips->type("VipsTarget*"), "VipsTargetCustom" => self::$vips->type("VipsTargetCustom*"), + "VipsProgress" => self::$vips->type("VipsProgress*"), ]; self::$gtypes = [ diff --git a/src/GObject.php b/src/GObject.php index 0047e20..97be094 100644 --- a/src/GObject.php +++ b/src/GObject.php @@ -128,7 +128,7 @@ private static function getMarshaler(string $name, callable $callback): ?Closure CData $hint, ?CData $data ) use (&$callback) { - assert($numberOfParams === 3); + assert($numberOfParams === 2); /** * Signature: void(VipsImage* image, void* progress, void* handle) */ diff --git a/src/Image.php b/src/Image.php index e9c6c78..f1f4056 100644 --- a/src/Image.php +++ b/src/Image.php @@ -1296,6 +1296,23 @@ public function remove(string $name): void } } + /** + * Enable progress reporting on an image. + * + * The preeval, eval and posteval signals will be emitted on the + * most-downstream image for which setProgress() was enabled. @see + * GObject::signalConnect(). + * + * @param bool $progress TRUE to enable progress reporting. + * + * @return void + */ + public function setProgress(bool $progress): void + { + FFI::vips()->vips_image_set_progress($this->pointer, $progress); + + } + /** * Makes a string-ified version of the Image. * From b15049bff13177ed815b6c8694a090ba29d8b9d3 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 30 Jun 2023 02:09:42 +0100 Subject: [PATCH 076/115] add streaming-custom example See https://github.com/libvips/php-vips/pull/191 --- CHANGELOG.md | 6 +++- examples/streaming-custom.php | 56 +++++++++++++++++++++++++++++++++++ src/GObject.php | 10 +++---- 3 files changed, 66 insertions(+), 6 deletions(-) create mode 100755 examples/streaming-custom.php diff --git a/CHANGELOG.md b/CHANGELOG.md index de03a92..7c88bc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,14 @@ All notable changes to `:vips` will be documented in this file. ## master -- improve FFI startup [West14] +- improve FFI startup [West14, jcupitt] - revise example use of composer [jcupitt] - fix bandrank [axkirillov] - fix FFI startup log error [ganicus] +- add Source, Target, SourceResource, TargetResource, SourceCustom, + TargetCustom [L3tum] +- add setProgress and progress example [jcupitt] +- add streaming-custom example [jcupitt] ## 2.1.1 - 2022-11-13 diff --git a/examples/streaming-custom.php b/examples/streaming-custom.php new file mode 100755 index 0000000..fa65405 --- /dev/null +++ b/examples/streaming-custom.php @@ -0,0 +1,56 @@ +#!/usr/bin/env php +onRead(function ($bufferLength) use (&$in_file) { + // return 0 for EOF, -ve for read error + return fread($in_file, $bufferLength); +}); +// seek is optional +$source->onSeek(function ($offset, $whence) use (&$in_file) { + if (fseek($in_file, $offset, $whence)) { + return -1; + } + + return ftell($in_file); +}); + +// open for write and read ... formats like tiff need to be able to seek back +// in the output and update bytes later +$out_file = fopen($argv[2], 'w+'); +$target = new Vips\TargetCustom(); +$target->onWrite(function ($buffer) use (&$out_file) { + $result = fwrite($out_file, $buffer); + if ($result === false) { + // IO error + return -1; + } + else + return $result; +}); +// read and seek are optional +$target->onSeek(function ($offset, $whence) use (&$out_file) { + if (fseek($out_file, $offset, $whence)) { + return -1; + } + + return ftell($out_file); +}); +$target->onRead(function ($bufferLength) use (&$out_file) { + return fread($out_file, $bufferLength); +}); + +$image = Vips\Image::newFromSource($source); +$image->writeToTarget($target, $argv[3]); + diff --git a/src/GObject.php b/src/GObject.php index 97be094..964393a 100644 --- a/src/GObject.php +++ b/src/GObject.php @@ -151,7 +151,7 @@ private static function getMarshaler(string $name, callable $callback): ?Closure CData $hint, ?CData $data ) use (&$callback): void { - assert($numberOfParams === 4); + assert($numberOfParams === 3); /* * Signature: gint64(VipsSourceCustom* source, void* buffer, gint64 length, void* handle) */ @@ -179,7 +179,7 @@ private static function getMarshaler(string $name, callable $callback): ?Closure CData $hint, ?CData $data ) use (&$callback): void { - assert($numberOfParams === 4); + assert($numberOfParams === 3); /* * Signature: gint64(VipsSourceCustom* source, gint64 offset, int whence, void* handle) */ @@ -200,7 +200,7 @@ private static function getMarshaler(string $name, callable $callback): ?Closure CData $hint, ?CData $data ) use (&$callback): void { - assert($numberOfParams === 4); + assert($numberOfParams === 3); /* * Signature: gint64(VipsTargetCustom* target, void* buffer, gint64 length, void* handle) */ @@ -223,7 +223,7 @@ private static function getMarshaler(string $name, callable $callback): ?Closure CData $hint, ?CData $data ) use (&$callback): void { - assert($numberOfParams === 2); + assert($numberOfParams === 0); /** * Signature: void(VipsTargetCustom* target, void* handle) */ @@ -242,7 +242,7 @@ private static function getMarshaler(string $name, callable $callback): ?Closure CData $hint, ?CData $data ) use (&$callback): void { - assert($numberOfParams === 2); + assert($numberOfParams === 0); /** * Signature: int(VipsTargetCustom* target, void* handle) */ From 908bf980681f9e2fb6d417974fed395555f7cd7d Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 30 Jun 2023 02:15:12 +0100 Subject: [PATCH 077/115] fix formatting --- examples/progress.php | 8 ++++---- examples/streaming-custom.php | 7 +++---- src/Image.php | 3 +-- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/examples/progress.php b/examples/progress.php index eca9a57..d53faac 100755 --- a/examples/progress.php +++ b/examples/progress.php @@ -9,14 +9,14 @@ $image = Vips\Image::black(1, 1000000); $image->setProgress(true); -$image->signalConnect("preeval", function($image, $progress) { +$image->signalConnect("preeval", function ($image, $progress) { echo "preeval:\n"; }); -$image->signalConnect("eval", function($image, $progress) { +$image->signalConnect("eval", function ($image, $progress) { echo "eval: $progress->percent % complete\r"; -}); +}); -$image->signalConnect("posteval", function($image, $progress) { +$image->signalConnect("posteval", function ($image, $progress) { echo "\nposteval:\n"; }); diff --git a/examples/streaming-custom.php b/examples/streaming-custom.php index fa65405..d3b78f8 100755 --- a/examples/streaming-custom.php +++ b/examples/streaming-custom.php @@ -27,7 +27,7 @@ }); // open for write and read ... formats like tiff need to be able to seek back -// in the output and update bytes later +// in the output and update bytes later $out_file = fopen($argv[2], 'w+'); $target = new Vips\TargetCustom(); $target->onWrite(function ($buffer) use (&$out_file) { @@ -35,9 +35,9 @@ if ($result === false) { // IO error return -1; - } - else + } else { return $result; + } }); // read and seek are optional $target->onSeek(function ($offset, $whence) use (&$out_file) { @@ -53,4 +53,3 @@ $image = Vips\Image::newFromSource($source); $image->writeToTarget($target, $argv[3]); - diff --git a/src/Image.php b/src/Image.php index f1f4056..1b6c257 100644 --- a/src/Image.php +++ b/src/Image.php @@ -1299,7 +1299,7 @@ public function remove(string $name): void /** * Enable progress reporting on an image. * - * The preeval, eval and posteval signals will be emitted on the + * The preeval, eval and posteval signals will be emitted on the * most-downstream image for which setProgress() was enabled. @see * GObject::signalConnect(). * @@ -1310,7 +1310,6 @@ public function remove(string $name): void public function setProgress(bool $progress): void { FFI::vips()->vips_image_set_progress($this->pointer, $progress); - } /** From ccc7d95c7e6022b95c52f379e26661aa509c52a7 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 9 Jul 2023 12:16:52 +0100 Subject: [PATCH 078/115] link to main API docs for newFromFile see https://github.com/libvips/php-vips/issues/208 --- src/Image.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Image.php b/src/Image.php index ab2c2b4..7d30ff7 100644 --- a/src/Image.php +++ b/src/Image.php @@ -96,8 +96,10 @@ * default mode is `random`, this allows for full random access to image pixels, * but is slower and needs more memory. * - * You can also load formatted images from - * strings or create images from PHP arrays. + * You can also load formatted images from strings or create images from + * PHP arrays. + * + * See the [main libvips documentation](https://www.libvips.org/API/current/VipsImage.html#vips-image-new-from-file) for a more detailed explaination. * * The next line: * @@ -695,6 +697,8 @@ public static function findLoad(string $filename): ?string /** * Create a new Image from a file on disc. * + * See the [main libvips documentation](https://www.libvips.org/API/current/VipsImage.html#vips-image-new-from-file) for a more detailed explaination. + * * @param string $name The file to open. * @param array $options Any options to pass on to the load operation. * From e9c57c5a3cabde7838c9d57e6ae69921b69ab7bf Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 18 Jul 2023 06:46:33 +0100 Subject: [PATCH 079/115] fix layout warning --- src/Image.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Image.php b/src/Image.php index 5bf74a1..bf2aeb3 100644 --- a/src/Image.php +++ b/src/Image.php @@ -96,10 +96,12 @@ * default mode is `random`, this allows for full random access to image pixels, * but is slower and needs more memory. * - * You can also load formatted images from strings or create images from + * You can also load formatted images from strings or create images from * PHP arrays. * - * See the [main libvips documentation](https://www.libvips.org/API/current/VipsImage.html#vips-image-new-from-file) for a more detailed explaination. + * See the [main libvips + * documentation](https://www.libvips.org/API/current/VipsImage.html#vips-image-new-from-file) + * for a more detailed explaination. * * The next line: * @@ -697,7 +699,9 @@ public static function findLoad(string $filename): ?string /** * Create a new Image from a file on disc. * - * See the [main libvips documentation](https://www.libvips.org/API/current/VipsImage.html#vips-image-new-from-file) for a more detailed explaination. + * See the [main libvips + * documentation](https://www.libvips.org/API/current/VipsImage.html#vips-image-new-from-file) + * for a more detailed explaination. * * @param string $name The file to open. * @param array $options Any options to pass on to the load operation. From ce833a6d38cea395aad5cd24f9c142ed36ab70b4 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 17 Aug 2023 00:49:01 +0100 Subject: [PATCH 080/115] update for 2.2 --- CHANGELOG.md | 2 ++ README.md | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c88bc9..9c714db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to `:vips` will be documented in this file. ## master +## 2.2.0 - 2023-08-17 + - improve FFI startup [West14, jcupitt] - revise example use of composer [jcupitt] - fix bandrank [axkirillov] diff --git a/README.md b/README.md index 4747982..a68e3ad 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ to your `composer.json`: ``` "require": { - "jcupitt/vips" : "2.1.0" + "jcupitt/vips" : "2.2.0" } ``` From 97c3fd8b42ab9eec38cee027117855479835f60a Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 6 Sep 2023 14:54:53 +0100 Subject: [PATCH 081/115] add getFields() and an example program see https://github.com/libvips/php-vips/discussions/218 --- CHANGELOG.md | 2 ++ examples/fields.php | 25 +++++++++++++++++++++++++ src/FFI.php | 1 + src/Image.php | 21 +++++++++++++++++++++ 4 files changed, 49 insertions(+) create mode 100755 examples/fields.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c714db..18b8c74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to `:vips` will be documented in this file. ## master +- add getFields() to fetch an array of field names + ## 2.2.0 - 2023-08-17 - improve FFI startup [West14, jcupitt] diff --git a/examples/fields.php b/examples/fields.php new file mode 100755 index 0000000..b3f6f62 --- /dev/null +++ b/examples/fields.php @@ -0,0 +1,25 @@ +#!/usr/bin/env php +getFields(); + +echo "$argv[1]\n"; +foreach ($names as &$name) { + $value = $im->get($name); + echo "$name: $value\n"; +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: expandtab sw=4 ts=4 fdm=marker + * vim<600: expandtab sw=4 ts=4 + */ diff --git a/src/FFI.php b/src/FFI.php index 8b2859f..304a3b4 100644 --- a/src/FFI.php +++ b/src/FFI.php @@ -348,6 +348,7 @@ private static function init(): void $glib_decls = $typedefs . <<get('ipct-data')`. * + * Use `$image->getFields()` to get an array of all the possible field names. + * * Next we have: * * ```php @@ -1217,6 +1219,25 @@ public function typeOf(string $name): int return $this->getType($name); } + /** + * Get the field names available for an image. + * + * @return Array + */ + public function getFields(): array + { + $str_array = FFI::vips()->vips_image_get_fields($this->pointer); + + $fields = []; + for ($i = 0; $str_array[$i] != null; $i++) { + array_push($fields, \FFI::string($str_array[$i])); + } + + FFI::glib()->g_free($str_array); + + return $fields; + } + /** * Set any property on the underlying image. * From 14df89980b4186209bbfe6e920fceec817b6ef8f Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 26 Sep 2023 10:07:04 +0100 Subject: [PATCH 082/115] update for 2.3.0 --- CHANGELOG.md | 4 +++- README.md | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18b8c74..ee09959 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,9 @@ All notable changes to `:vips` will be documented in this file. ## master -- add getFields() to fetch an array of field names +## 2.3.0 - 2023-09-26 + +- add getFields() to fetch an array of field names [jcupitt] ## 2.2.0 - 2023-08-17 diff --git a/README.md b/README.md index a68e3ad..d6b4bad 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ to your `composer.json`: ``` "require": { - "jcupitt/vips" : "2.2.0" + "jcupitt/vips" : "2.3.0" } ``` From 24831a1cf95f85f0ca2fc36b6405061505f7c1ca Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 9 Nov 2023 20:19:19 +0000 Subject: [PATCH 083/115] add animate-image.php example --- CHANGELOG.md | 2 ++ examples/animate-image.php | 41 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100755 examples/animate-image.php diff --git a/CHANGELOG.md b/CHANGELOG.md index ee09959..032a444 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to `:vips` will be documented in this file. ## master +- added `animate-image.php` example [jcupitt] + ## 2.3.0 - 2023-09-26 - add getFields() to fetch an array of field names [jcupitt] diff --git a/examples/animate-image.php b/examples/animate-image.php new file mode 100755 index 0000000..c1feb30 --- /dev/null +++ b/examples/animate-image.php @@ -0,0 +1,41 @@ +#!/usr/bin/env php + 300, "rgba" => true]); +$animation = NULL; +$delay = []; + +for ($x = 0; $x < $image->width + $text->width; $x += 10) +{ + // append the frame to the image vertically ... we make a very tall, thin + // strip of frames to save + $frame = $image->composite2($text, "over", [ + "x" => $x - $text->width, + "y" => $image->height / 2 - $text->height / 2 + ]); + if ($animation == NULL) + $animation = $frame; + else + $animation = $animation->join($frame, "vertical"); + + // frame delay in ms + array_push($delay, 30); +} + +// set animation properties +$animation->set("delay", $delay); +$animation->set("loop", 0); +$animation->set("page-height", $image->height); + +$animation->writeToFile($argv[2]); From f8b965b527f218435358068ad568163187665901 Mon Sep 17 00:00:00 2001 From: Mahmood Dehghani Date: Thu, 28 Dec 2023 23:44:17 +0330 Subject: [PATCH 084/115] removed unreachable return statement (#228) --- src/FFI.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/FFI.php b/src/FFI.php index 304a3b4..194db6f 100644 --- a/src/FFI.php +++ b/src/FFI.php @@ -204,8 +204,6 @@ private static function libraryName(string $name, int $abi): string // most *nix return "$name.so.$abi"; } - - return null; } private static function libraryLoad( From 2dd6ae6f96a22ba666fbeb05f838bf7aeed03fe6 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 28 Dec 2023 20:16:12 +0000 Subject: [PATCH 085/115] fix formatting for phpcs --- examples/animate-image.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/animate-image.php b/examples/animate-image.php index c1feb30..1525259 100755 --- a/examples/animate-image.php +++ b/examples/animate-image.php @@ -13,21 +13,22 @@ $image = Vips\Image::newFromFile($argv[1]); $text = Vips\Image::text($argv[3], ["dpi" => 300, "rgba" => true]); -$animation = NULL; +$animation = null; $delay = []; -for ($x = 0; $x < $image->width + $text->width; $x += 10) -{ +for ($x = 0; $x < $image->width + $text->width; $x += 10) { // append the frame to the image vertically ... we make a very tall, thin // strip of frames to save $frame = $image->composite2($text, "over", [ "x" => $x - $text->width, "y" => $image->height / 2 - $text->height / 2 ]); - if ($animation == NULL) + if ($animation == null) { $animation = $frame; - else + } + else { $animation = $animation->join($frame, "vertical"); + } // frame delay in ms array_push($delay, 30); From bbeef608c6a6ac0f12172a463eee6b0b7d9d49e2 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 28 Dec 2023 20:20:19 +0000 Subject: [PATCH 086/115] another formatting fix --- examples/animate-image.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/animate-image.php b/examples/animate-image.php index 1525259..9d06d49 100755 --- a/examples/animate-image.php +++ b/examples/animate-image.php @@ -25,8 +25,7 @@ ]); if ($animation == null) { $animation = $frame; - } - else { + } else { $animation = $animation->join($frame, "vertical"); } From 85affa54a16aa8a0b292e7e99945a14826b2650f Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 22 Jan 2024 20:17:03 +0000 Subject: [PATCH 087/115] add flags support see https://github.com/libvips/php-vips/issues/229 --- CHANGELOG.md | 2 + examples/fields.php | 21 +++++---- examples/generate_phpdoc.py | 61 ++++++++++++++++++++++--- examples/keep.php | 41 +++++++++++++++++ src/{ImageType.php => ForeignKeep.php} | 19 ++++---- src/{Token.php => ForeignPngFilter.php} | 14 +++--- src/ImageAutodoc.php | 28 ++++++++++-- 7 files changed, 151 insertions(+), 35 deletions(-) create mode 100755 examples/keep.php rename src/{ImageType.php => ForeignKeep.php} (85%) rename src/{Token.php => ForeignPngFilter.php} (90%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 032a444..d4e6616 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ All notable changes to `:vips` will be documented in this file. ## master - added `animate-image.php` example [jcupitt] +- added flags support [jcupitt] +- added `keep.php` example [jcupitt] ## 2.3.0 - 2023-09-26 diff --git a/examples/fields.php b/examples/fields.php index b3f6f62..5678be7 100755 --- a/examples/fields.php +++ b/examples/fields.php @@ -5,16 +5,21 @@ use Jcupitt\Vips; -$im = Vips\Image::newFromFile($argv[1]); - -$names = $im->getFields(); - -echo "$argv[1]\n"; -foreach ($names as &$name) { - $value = $im->get($name); - echo "$name: $value\n"; +function printMetadata($im) +{ + foreach ($im->getFields() as &$name) { + $value = $im->get($name); + if (str_ends_with($name, "-data")) { + $len = strlen($value); + $value = "<$len bytes of binary data>"; + } + echo " $name: $value\n"; + } } +$im = Vips\Image::newFromFile($argv[1]); +printMetadata($im); + /* * Local variables: * tab-width: 4 diff --git a/examples/generate_phpdoc.py b/examples/generate_phpdoc.py index 632db36..5a7d310 100755 --- a/examples/generate_phpdoc.py +++ b/examples/generate_phpdoc.py @@ -1,12 +1,12 @@ #!/usr/bin/python3 +# needs pyvips 2.2.3 or later + from pyvips import Image, Introspect, GValue, Error, \ - ffi, values_for_enum, vips_lib, gobject_lib, \ + ffi, enum_dict, flags_dict, vips_lib, gobject_lib, \ type_map, type_name, type_from_name, nickname_find # This file generates the phpdoc comments for the magic methods and properties. -# It's in Python, since we use the whole of FFI, not just the -# small bit exposed by php-vips-ext. # Regenerate docs with something like: # @@ -292,6 +292,10 @@ def add_enum(gtype, a, b): type_map(type_from_name('GEnum'), add_enum) + # Filter internal enums + blacklist = ['VipsImageType', 'VipsToken'] + all_enums = [name for name in all_enums if name not in blacklist] + for name in all_enums: gtype = type_from_name(name) php_name = remove_prefix(name) @@ -310,14 +314,59 @@ def add_enum(gtype, a, b): f.write('abstract class {0}\n'.format(php_name)) f.write('{\n') - for value in values_for_enum(gtype): - php_name = value.replace('-', '_').upper() + for key, value in enum_dict(gtype).items(): + php_name = key.replace('-', '_').upper() + if php_name in reserved_php_names: + php_name = reserved_php_names[php_name] + f.write(' const {0} = \'{1}\';\n'.format(php_name, key)) + + f.write('}\n') + + +def generate_flags(): + all_flags = [] + + def add_flags(gtype, a, b): + nickname = type_name(gtype) + all_flags.append(nickname) + + type_map(gtype, add_flags) + + return ffi.NULL + + type_map(type_from_name('GFlags'), add_flags) + + # Filter internal flags + blacklist = ['VipsForeignFlags'] + all_flags = [name for name in all_flags if name not in blacklist] + + for name in all_flags: + gtype = type_from_name(name) + php_name = remove_prefix(name) + + print('Generating {0}.php ...'.format(php_name)) + + with open('{0}.php'.format(php_name), 'w') as f: + f.write(preamble) + f.write('\n') + f.write('namespace Jcupitt\\Vips;\n') + f.write('\n') + f.write('/**\n') + f.write(' * The {0} flags.\n'.format(php_name)) + f.write(class_header) + f.write(' */\n') + f.write('abstract class {0}\n'.format(php_name)) + f.write('{\n') + + for key, value in flags_dict(gtype).items(): + php_name = key.replace('-', '_').upper() if php_name in reserved_php_names: php_name = reserved_php_names[php_name] - f.write(' const {0} = \'{1}\';\n'.format(php_name, value)) + f.write(' const {0} = {1};\n'.format(php_name, value)) f.write('}\n') generate_auto_doc('ImageAutodoc.php') generate_enums() +generate_flags() diff --git a/examples/keep.php b/examples/keep.php new file mode 100755 index 0000000..84e5322 --- /dev/null +++ b/examples/keep.php @@ -0,0 +1,41 @@ +#!/usr/bin/env php +getFields() as &$name) { + $value = $im->get($name); + if (str_ends_with($name, "-data")) { + $len = strlen($value); + $value = "<$len bytes of binary data>"; + } + echo " $name: $value\n"; + } +} + +$im = Vips\Image::newFromFile($argv[1]); +echo "$argv[1]\n"; +printMetadata($im); + +echo "\nafter keep => icc\n"; +$buf = $im->tiffsave_buffer(['keep' => Vips\ForeignKeep::ICC]); +$im2 = Vips\Image::newFromBuffer($buf, ""); +printMetadata($im2); + +echo "\nafter keep => exif|xmp\n"; +$buf = $im->tiffsave_buffer(['keep' => Vips\ForeignKeep::ICC | Vips\ForeignKeep::XMP]); +$im2 = Vips\Image::newFromBuffer($buf, ""); +printMetadata($im2); + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: expandtab sw=4 ts=4 fdm=marker + * vim<600: expandtab sw=4 ts=4 + */ diff --git a/src/ImageType.php b/src/ForeignKeep.php similarity index 85% rename from src/ImageType.php rename to src/ForeignKeep.php index 3678d22..b7bec4b 100644 --- a/src/ImageType.php +++ b/src/ForeignKeep.php @@ -39,7 +39,7 @@ namespace Jcupitt\Vips; /** - * The ImageType enum. + * The ForeignKeep flags. * @category Images * @package Jcupitt\Vips * @author John Cupitt @@ -47,14 +47,13 @@ * @license https://opensource.org/licenses/MIT MIT * @link https://github.com/jcupitt/php-vips */ -abstract class ImageType +abstract class ForeignKeep { - const ERROR = 'error'; - const NONE = 'none'; - const SETBUF = 'setbuf'; - const SETBUF_FOREIGN = 'setbuf-foreign'; - const OPENIN = 'openin'; - const MMAPIN = 'mmapin'; - const MMAPINRW = 'mmapinrw'; - const OPENOUT = 'openout'; + const NONE = 0; + const EXIF = 1; + const XMP = 2; + const IPTC = 4; + const ICC = 8; + const OTHER = 16; + const ALL = 31; } diff --git a/src/Token.php b/src/ForeignPngFilter.php similarity index 90% rename from src/Token.php rename to src/ForeignPngFilter.php index 40df593..485c05d 100644 --- a/src/Token.php +++ b/src/ForeignPngFilter.php @@ -39,7 +39,7 @@ namespace Jcupitt\Vips; /** - * The Token enum. + * The ForeignPngFilter flags. * @category Images * @package Jcupitt\Vips * @author John Cupitt @@ -47,10 +47,12 @@ * @license https://opensource.org/licenses/MIT MIT * @link https://github.com/jcupitt/php-vips */ -abstract class Token +abstract class ForeignPngFilter { - const LEFT = 'left'; - const RIGHT = 'right'; - const STRING = 'string'; - const EQUALS = 'equals'; + const NONE = 8; + const SUB = 16; + const UP = 32; + const AVG = 64; + const PAETH = 128; + const ALL = 248; } diff --git a/src/ImageAutodoc.php b/src/ImageAutodoc.php index e8b80e7..c773791 100644 --- a/src/ImageAutodoc.php +++ b/src/ImageAutodoc.php @@ -160,7 +160,7 @@ * @throws Exception * @method Image convi(Image $mask, array $options = []) Int convolution operation. * @throws Exception - * @method Image convsep(Image $mask, array $options = []) Seperable convolution operation. + * @method Image convsep(Image $mask, array $options = []) Separable convolution operation. * @throws Exception * @method Image copy(array $options = []) Copy an image. * @throws Exception @@ -372,6 +372,18 @@ * @throws Exception * @method void jxlsave_target(Target $target, array $options = []) Save image in JPEG-XL format. * @throws Exception + * @method static Image kakaduload(string $filename, array $options = []) Load JPEG2000 image. + * @throws Exception + * @method static Image kakaduload_buffer(string $buffer, array $options = []) Load JPEG2000 image. + * @throws Exception + * @method static Image kakaduload_source(Source $source, array $options = []) Load JPEG2000 image. + * @throws Exception + * @method void kakadusave(string $filename, array $options = []) Save image in JPEG2000 format. + * @throws Exception + * @method string kakadusave_buffer(array $options = []) Save image in JPEG2000 format. + * @throws Exception + * @method void kakadusave_target(Target $target, array $options = []) Save image in JPEG2000 format. + * @throws Exception * @method Image labelregions(array $options = []) Label regions in an image. * @throws Exception * @method Image linear(float[]|float $a, float[]|float $b, array $options = []) Calculate (a * in + b). @@ -380,9 +392,9 @@ * @throws Exception * @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 ImageMagick7. + * @method static Image magickload(string $filename, array $options = []) Load file with ImageMagick. * @throws Exception - * @method static Image magickload_buffer(string $buffer, array $options = []) Load buffer with ImageMagick7. + * @method static Image magickload_buffer(string $buffer, array $options = []) Load buffer with ImageMagick. * @throws Exception * @method void magicksave(string $filename, array $options = []) Save file with ImageMagick. * @throws Exception @@ -457,6 +469,12 @@ * @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(Source $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. @@ -481,9 +499,9 @@ * @throws Exception * @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 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(Target $target, array $options = []) Save image to target as PNG. * @throws Exception From 7cf9d499fe43207ad2ac4e62238b939a7d401ab5 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 14 Feb 2024 12:45:17 +0000 Subject: [PATCH 088/115] use Target save to implement writeToBuffer (#207) if possible --- examples/streaming-bench.php | 0 examples/streaming.php | 0 src/GObject.php | 4 ++-- src/Image.php | 43 +++++++++++++++++++++++++++++------- 4 files changed, 37 insertions(+), 10 deletions(-) mode change 100644 => 100755 examples/streaming-bench.php mode change 100644 => 100755 examples/streaming.php diff --git a/examples/streaming-bench.php b/examples/streaming-bench.php old mode 100644 new mode 100755 diff --git a/examples/streaming.php b/examples/streaming.php old mode 100644 new mode 100755 diff --git a/src/GObject.php b/src/GObject.php index 964393a..40f34d9 100644 --- a/src/GObject.php +++ b/src/GObject.php @@ -223,7 +223,7 @@ private static function getMarshaler(string $name, callable $callback): ?Closure CData $hint, ?CData $data ) use (&$callback): void { - assert($numberOfParams === 0); + assert($numberOfParams === 1); /** * Signature: void(VipsTargetCustom* target, void* handle) */ @@ -242,7 +242,7 @@ private static function getMarshaler(string $name, callable $callback): ?Closure CData $hint, ?CData $data ) use (&$callback): void { - assert($numberOfParams === 0); + assert($numberOfParams === 1); /** * Signature: int(VipsTargetCustom* target, void* handle) */ diff --git a/src/Image.php b/src/Image.php index 10817e6..01b1494 100644 --- a/src/Image.php +++ b/src/Image.php @@ -993,18 +993,45 @@ public function writeToBuffer(string $suffix, array $options = []): string $filename = Utils::filenameGetFilename($suffix); $string_options = Utils::filenameGetOptions($suffix); - $saver = FFI::vips()->vips_foreign_find_save_buffer($filename); - if ($saver == "") { - throw new Exception(); + $saver = null; + + // see if we can save with the Target API ... we need 8.9 or later for + // Target, and we need this libvips to have a target saver for this + // format + if (FFI::atLeast(8, 9)) { + FFI::vips()->vips_error_freeze(); + $saver = FFI::vips()->vips_foreign_find_save_target($filename); + FFI::vips()->vips_error_thaw(); } - if (strlen($string_options) != 0) { - $options = array_merge([ - "string_options" => $string_options, - ], $options); + if ($saver !== null) { + $target = Target::newToMemory(); + if (strlen($string_options) != 0) { + $options = array_merge([ + "string_options" => $string_options, + ], $options); + } + + VipsOperation::call($saver, $this, [$target], $options); + + $buffer = $target->get("blob"); + } else { + // fall back to the old _buffer API + $saver = FFI::vips()->vips_foreign_find_save_buffer($filename); + if ($saver == "") { + throw new Exception(); + } + + if (strlen($string_options) != 0) { + $options = array_merge([ + "string_options" => $string_options, + ], $options); + } + + $buffer = VipsOperation::call($saver, $this, [], $options); } - return VipsOperation::call($saver, $this, [], $options); + return $buffer; } /** From 57b593a37f53f35b7cd4eadf969ccaa38953e2c3 Mon Sep 17 00:00:00 2001 From: Christian Sciberras Date: Wed, 14 Feb 2024 13:49:12 +0100 Subject: [PATCH 089/115] FFI class improvements (fixes #221) (#230) * FFI class improvements * Update missed heredoc; use generic lib location --- src/FFI.php | 59 +++++++++++++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/src/FFI.php b/src/FFI.php index 194db6f..d8ac18a 100644 --- a/src/FFI.php +++ b/src/FFI.php @@ -180,10 +180,10 @@ public static function shutDown(): void 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. + // 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? @@ -210,7 +210,7 @@ private static function libraryLoad( array $libraryPaths, string $libraryName, string $interface - ): \FFI { + ): ?\FFI { Utils::debugLog("trying to open", ["libraryName" => $libraryName]); foreach ($libraryPaths as $path) { Utils::debugLog("trying path", ["path" => $path]); @@ -225,6 +225,7 @@ private static function libraryLoad( ]); } } + return null; } private static function init(): void @@ -234,7 +235,7 @@ private static function init(): void return; } - // the two usual install problems + // the two usual installation problems if (!extension_loaded('ffi')) { throw new Exception('FFI extension not loaded'); } @@ -267,16 +268,18 @@ private static function init(): void $libraryPaths[] = $vipshome . "/lib/"; } - if (PHP_OS_FAMILY === "OSX" || - PHP_OS_FAMILY === "Darwin") { - $libraryPaths[] = "/opt/homebrew/lib/"; // Homebrew on Apple Silicon + if (PHP_OS_FAMILY === "OSX" || PHP_OS_FAMILY === "Darwin") { + // Homebrew on Apple Silicon + $libraryPaths[] = "/opt/homebrew/lib/"; + // See https://github.com/Homebrew/brew/issues/13481#issuecomment-1207203483 + $libraryPaths[] = "/usr/local/lib/"; } - $vips = self::libraryLoad($libraryPaths, $vips_libname, << Date: Wed, 14 Feb 2024 13:55:02 +0100 Subject: [PATCH 090/115] Fix broken assertion; minor other fixes (#233) --- src/FFI.php | 6 +++--- src/GObject.php | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/FFI.php b/src/FFI.php index d8ac18a..1ac0585 100644 --- a/src/FFI.php +++ b/src/FFI.php @@ -163,9 +163,9 @@ public static function version(): string */ public static function atLeast(int $x, int $y, int $z = 0): bool { - return self::$library_major > $x || - self::$library_major == $x && self::$library_minor > $y || - self::$library_major == $x && self::$library_minor == $y && self::$library_micro >= $z; + return self::$library_major > $x + || (self::$library_major === $x && self::$library_minor > $y) + || (self::$library_major === $x && self::$library_minor === $y && self::$library_micro >= $z); } /** diff --git a/src/GObject.php b/src/GObject.php index 40f34d9..bf3b512 100644 --- a/src/GObject.php +++ b/src/GObject.php @@ -152,7 +152,7 @@ private static function getMarshaler(string $name, callable $callback): ?Closure ?CData $data ) use (&$callback): void { assert($numberOfParams === 3); - /* + /** * Signature: gint64(VipsSourceCustom* source, void* buffer, gint64 length, void* handle) */ $bufferLength = (int)FFI::gobject()->g_value_get_int64(\FFI::addr($params[2])); @@ -180,7 +180,7 @@ private static function getMarshaler(string $name, callable $callback): ?Closure ?CData $data ) use (&$callback): void { assert($numberOfParams === 3); - /* + /** * Signature: gint64(VipsSourceCustom* source, gint64 offset, int whence, void* handle) */ $offset = (int)FFI::gobject()->g_value_get_int64(\FFI::addr($params[1])); @@ -201,7 +201,7 @@ private static function getMarshaler(string $name, callable $callback): ?Closure ?CData $data ) use (&$callback): void { assert($numberOfParams === 3); - /* + /** * Signature: gint64(VipsTargetCustom* target, void* buffer, gint64 length, void* handle) */ $bufferPointer = FFI::gobject()->g_value_get_pointer(\FFI::addr($params[1])); From c365be7e94538f4bc7692ab20f3dbec3a598fb7a Mon Sep 17 00:00:00 2001 From: Christian Sciberras Date: Wed, 14 Feb 2024 13:57:30 +0100 Subject: [PATCH 091/115] Avoid PHP 8.3 FFI deprecations (fixes #226) (#231) * Avoid PHP 8.3 FFI::cast deprecation * Avoid PHP 8.3 FFI::new deprecation * Run CI tests on PHP 8.3 --- .github/workflows/ci.yml | 1 + src/Connection.php | 2 +- src/GObject.php | 4 ++-- src/GValue.php | 6 +++--- src/Image.php | 14 ++++++++------ src/Interpolate.php | 2 +- src/Introspect.php | 2 +- src/Source.php | 4 ++-- src/Target.php | 2 +- src/VipsObject.php | 4 ++-- 10 files changed, 22 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5b7dd18..3025f83 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,6 +14,7 @@ jobs: - php: '8.0' - php: '8.1' - php: '8.2' + - php: '8.3' steps: - name: Setup PHP diff --git a/src/Connection.php b/src/Connection.php index ea38166..096a422 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -14,7 +14,7 @@ abstract class Connection extends VipsObject public function __construct(\FFI\CData $pointer) { - $this->pointer = \FFI::cast(FFI::ctypes('VipsConnection'), $pointer); + $this->pointer = FFI::vips()->cast(FFI::ctypes('VipsConnection'), $pointer); parent::__construct($pointer); } diff --git a/src/GObject.php b/src/GObject.php index bf3b512..2959e25 100644 --- a/src/GObject.php +++ b/src/GObject.php @@ -74,7 +74,7 @@ abstract class GObject */ public function __construct(CData $pointer) { - $this->pointer = \FFI::cast(FFI::ctypes("GObject"), $pointer); + $this->pointer = FFI::vips()->cast(FFI::ctypes("GObject"), $pointer); } public function __destruct() @@ -135,7 +135,7 @@ private static function getMarshaler(string $name, callable $callback): ?Closure $vi = FFI::gobject()->g_value_get_object(\FFI::addr($params[0])); FFI::gobject()->g_object_ref($vi); $image = new Image($vi); - $pr = \FFI::cast( + $pr = FFI::vips()->cast( FFI::ctypes('VipsProgress'), FFI::gobject()->g_value_get_pointer(\FFI::addr($params[1])) ); diff --git a/src/GValue.php b/src/GValue.php index c310e6a..2bd657e 100644 --- a/src/GValue.php +++ b/src/GValue.php @@ -147,7 +147,7 @@ public function set($value): void $value = [$value]; } $n = count($value); - $array = \FFI::new("int[$n]"); + $array = FFI::vips()->new("int[$n]"); for ($i = 0; $i < $n; $i++) { $array[$i] = $value[$i]; } @@ -160,7 +160,7 @@ public function set($value): void $value = [$value]; } $n = count($value); - $array = \FFI::new("double[$n]"); + $array = FFI::vips()->new("double[$n]"); for ($i = 0; $i < $n; $i++) { $array[$i] = $value[$i]; } @@ -187,7 +187,7 @@ public function set($value): void # we need to set the blob to a copy of the data that vips_lib # can own and free $n = strlen($value); - $memory = \FFI::new("char[$n]", false, true); + $memory = FFI::vips()->new("char[$n]", false, true); \FFI::memcpy($memory, $value, $n); FFI::vips()-> vips_value_set_blob_free($this->pointer, $memory, $n); diff --git a/src/Image.php b/src/Image.php index 01b1494..afc9b45 100644 --- a/src/Image.php +++ b/src/Image.php @@ -500,7 +500,7 @@ class Image extends ImageAutodoc implements \ArrayAccess */ public function __construct(\FFI\CData $pointer) { - $this->pointer = \FFI::cast(FFI::ctypes("VipsImage"), $pointer); + $this->pointer = FFI::vips()->cast(FFI::ctypes("VipsImage"), $pointer); parent::__construct($pointer); } @@ -807,7 +807,7 @@ public static function newFromArray( $width = count($array[0]); $n = $width * $height; - $a = \FFI::new("double[$n]", true, true); + $a = FFI::vips()->new("double[$n]", true, true); for ($y = 0; $y < $height; $y++) { for ($x = 0; $x < $width; $x++) { $a[$x + $y * $width] = $array[$y][$x]; @@ -921,7 +921,9 @@ public function newFromImage($value): Image */ public static function findLoadSource(Source $source): ?string { - return FFI::vips()->vips_foreign_find_load_source(\FFI::cast(FFI::ctypes('VipsSource'), $source->pointer)); + return FFI::vips()->vips_foreign_find_load_source( + FFI::vips()->cast(FFI::ctypes('VipsSource'), $source->pointer) + ); } /** @@ -1043,7 +1045,7 @@ public function writeToBuffer(string $suffix, array $options = []): string */ public function writeToMemory(): string { - $p_size = \FFI::new("size_t[1]"); + $p_size = FFI::vips()->new("size_t[1]"); $pointer = FFI::vips()-> vips_image_write_to_memory($this->pointer, $p_size); @@ -1084,7 +1086,7 @@ public function writeToMemory(): string */ public function writeToArray(): array { - $p_size = \FFI::new("size_t[1]"); + $p_size = FFI::vips()->new("size_t[1]"); $pointer = FFI::vips()-> vips_image_write_to_memory($this->pointer, $p_size); @@ -1095,7 +1097,7 @@ public function writeToArray(): array // wrap pointer up as a C array of the right type $type_name = FFI::ftypes($this->format); $n = $this->width * $this->height * $this->bands; - $array = \FFI::cast("{$type_name}[$n]", $pointer); + $array = FFI::vips()->cast("{$type_name}[$n]", $pointer); // copy to PHP memory as a flat array $result = []; diff --git a/src/Interpolate.php b/src/Interpolate.php index cb5eee5..e20ba12 100644 --- a/src/Interpolate.php +++ b/src/Interpolate.php @@ -62,7 +62,7 @@ class Interpolate extends VipsObject public function __construct(\FFI\CData $pointer) { - $this->pointer = \FFI::cast(FFI::ctypes("VipsInterpolate"), $pointer); + $this->pointer = FFI::vips()->cast(FFI::ctypes("VipsInterpolate"), $pointer); parent::__construct($pointer); } diff --git a/src/Introspect.php b/src/Introspect.php index ce3208c..489364d 100644 --- a/src/Introspect.php +++ b/src/Introspect.php @@ -105,7 +105,7 @@ public function __construct($operation_name) $p_flags = FFI::vips()->new("int*[1]"); $p_n_args = FFI::vips()->new("int[1]"); $result = FFI::vips()->vips_object_get_args( - \FFI::cast(FFI::ctypes("VipsObject"), $operation->pointer), + FFI::vips()->cast(FFI::ctypes("VipsObject"), $operation->pointer), $p_names, $p_flags, $p_n_args diff --git a/src/Source.php b/src/Source.php index 60975fe..9a028a7 100644 --- a/src/Source.php +++ b/src/Source.php @@ -14,7 +14,7 @@ class Source extends Connection public function __construct(\FFI\CData $pointer) { - $this->pointer = \FFI::cast(FFI::ctypes('VipsSource'), $pointer); + $this->pointer = FFI::vips()->cast(FFI::ctypes('VipsSource'), $pointer); parent::__construct($pointer); } @@ -67,7 +67,7 @@ public static function newFromMemory(string $data): self # 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); + $memory = FFI::vips()->new("char[$n]", false, true); \FFI::memcpy($memory, $data, $n); $pointer = FFI::vips()->vips_source_new_from_memory($memory, $n); diff --git a/src/Target.php b/src/Target.php index 9b31e61..eda298d 100644 --- a/src/Target.php +++ b/src/Target.php @@ -14,7 +14,7 @@ class Target extends Connection public function __construct(\FFI\CData $pointer) { - $this->pointer = \FFI::cast(FFI::ctypes('VipsTarget'), $pointer); + $this->pointer = FFI::vips()->cast(FFI::ctypes('VipsTarget'), $pointer); parent::__construct($pointer); } diff --git a/src/VipsObject.php b/src/VipsObject.php index 74bb83b..e0b18a5 100644 --- a/src/VipsObject.php +++ b/src/VipsObject.php @@ -68,8 +68,8 @@ abstract class VipsObject extends GObject public function __construct(\FFI\CData $pointer) { - $this->pointer = \FFI::cast(FFI::ctypes("VipsObject"), $pointer); - $this->gObject = \FFI::cast(FFI::ctypes("GObject"), $pointer); + $this->pointer = FFI::vips()->cast(FFI::ctypes("VipsObject"), $pointer); + $this->gObject = FFI::vips()->cast(FFI::ctypes("GObject"), $pointer); parent::__construct($pointer); } From a2c7609089d3f0a83def80ea59d2934c6f83ceb4 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 14 Feb 2024 13:05:45 +0000 Subject: [PATCH 092/115] udpate CHANGELOG and a minor formatting fix --- CHANGELOG.md | 3 +++ src/FFI.php | 7 ++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4e6616..efa435f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ All notable changes to `:vips` will be documented in this file. - added `animate-image.php` example [jcupitt] - added flags support [jcupitt] - added `keep.php` example [jcupitt] +- added streaming examples [jcupitt] +- fix php 8.3 compatibility [uuf6429] +- better library finding [uuf6429] ## 2.3.0 - 2023-09-26 diff --git a/src/FFI.php b/src/FFI.php index 1ac0585..3fc8a97 100644 --- a/src/FFI.php +++ b/src/FFI.php @@ -163,9 +163,10 @@ public static function version(): string */ public static function atLeast(int $x, int $y, int $z = 0): bool { - return self::$library_major > $x - || (self::$library_major === $x && self::$library_minor > $y) - || (self::$library_major === $x && self::$library_minor === $y && self::$library_micro >= $z); + return self::$library_major > $x || + (self::$library_major === $x && self::$library_minor > $y) || + (self::$library_major === $x && self::$library_minor === $y && + self::$library_micro >= $z); } /** From 90075e3ee4ede10805e16836f2f077e630172365 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 14 Feb 2024 14:39:21 +0000 Subject: [PATCH 093/115] remove php 8.3 from CI it's not working for some reason I don't understand --- .github/workflows/ci.yml | 5 ++--- src/VipsOperation.php | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3025f83..9acd05d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,6 @@ jobs: - php: '8.0' - php: '8.1' - php: '8.2' - - php: '8.3' steps: - name: Setup PHP @@ -25,14 +24,14 @@ jobs: coverage: none - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install vips run: sudo apt install -y libvips --no-install-recommends - name: Install composer dependencies run: | - composer update --prefer-dist --no-interaction --no-progress --no-ansi + composer update --prefer-dist --no-interaction --no-progress --no-ansi - name: PHPUnit run: composer test diff --git a/src/VipsOperation.php b/src/VipsOperation.php index cf212af..46e4a45 100644 --- a/src/VipsOperation.php +++ b/src/VipsOperation.php @@ -304,8 +304,7 @@ public static function callBase( /* Build the operation */ - $pointer = FFI::vips()-> - vips_cache_operation_build($operation->pointer); + $pointer = FFI::vips()->vips_cache_operation_build($operation->pointer); if ($pointer == null) { $operation->unrefOutputs(); throw new Exception(); From 31515355f4de8f04f99ba7a2f1eac6a1ce1233e0 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Wed, 14 Feb 2024 15:40:48 +0100 Subject: [PATCH 094/115] Fix CI (#237) --- .github/workflows/ci.yml | 1 + tests/StreamingTest.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9acd05d..4af1d7c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,6 +20,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} + ini-values: zend.max_allowed_stack_size=-1 tools: composer:v2 coverage: none diff --git a/tests/StreamingTest.php b/tests/StreamingTest.php index 9d07184..689ea01 100644 --- a/tests/StreamingTest.php +++ b/tests/StreamingTest.php @@ -27,7 +27,7 @@ public function sourceAndTargetProvider(): Generator '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')) + 'Resource (not readable)' => fn() => new TargetResource(fopen('php://memory', 'wb')) ]; foreach ($sources as $sourceName => $source) { From 50cb534445192d8c91fd28fc95cbc1b57e9eb679 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 14 Feb 2024 14:41:24 +0000 Subject: [PATCH 095/115] Revert "remove php 8.3 from CI" This reverts commit 90075e3ee4ede10805e16836f2f077e630172365. --- .github/workflows/ci.yml | 5 +++-- src/VipsOperation.php | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9acd05d..3025f83 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,6 +14,7 @@ jobs: - php: '8.0' - php: '8.1' - php: '8.2' + - php: '8.3' steps: - name: Setup PHP @@ -24,14 +25,14 @@ jobs: coverage: none - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v3 - name: Install vips run: sudo apt install -y libvips --no-install-recommends - name: Install composer dependencies run: | - composer update --prefer-dist --no-interaction --no-progress --no-ansi + composer update --prefer-dist --no-interaction --no-progress --no-ansi - name: PHPUnit run: composer test diff --git a/src/VipsOperation.php b/src/VipsOperation.php index 46e4a45..cf212af 100644 --- a/src/VipsOperation.php +++ b/src/VipsOperation.php @@ -304,7 +304,8 @@ public static function callBase( /* Build the operation */ - $pointer = FFI::vips()->vips_cache_operation_build($operation->pointer); + $pointer = FFI::vips()-> + vips_cache_operation_build($operation->pointer); if ($pointer == null) { $operation->unrefOutputs(); throw new Exception(); From 81d2281d38d45ee152e22c94fea23fe56225aa70 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 14 Feb 2024 16:50:29 +0000 Subject: [PATCH 096/115] check max_allowed_stack_size during startup see https://github.com/libvips/php-vips/pull/237 --- README.md | 12 ++++++++++++ src/FFI.php | 6 +++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d6b4bad..716fe50 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,18 @@ server can use it to call any native library they have access to. Of course if attackers are running their own PHP code on your webserver you are probably already toast, unfortunately. +Finally, on php 8.3 and later you need to disable stack overflow +tests. php-vips executes FFI callbacks off the main thread and this confuses +those checks, at least in php 8.3.0. + +Add: + +``` +zend.max_allowed_stack_size=-1 +``` + +To your `php.ini`. + ### Example ```php diff --git a/src/FFI.php b/src/FFI.php index 3fc8a97..56a30ae 100644 --- a/src/FFI.php +++ b/src/FFI.php @@ -236,13 +236,17 @@ private static function init(): void return; } - // the two usual installation problems + // detect the most common installation problems if (!extension_loaded('ffi')) { throw new Exception('FFI extension not loaded'); } if (!ini_get('ffi.enable')) { throw new Exception("ffi.enable not set to 'true'"); } + if (version_compare(PHP_VERSION, '8.3') && + ini_get('zend.max_allowed_stack_size') != '-1') { + throw new Exception("zend.max_allowed_stack_size not set to '-1'"); + } $vips_libname = self::libraryName("libvips", 42); if (PHP_OS_FAMILY === "Windows") { From 439b280d22213a2b5c7c76251d7af57734d0c3dc Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 14 Feb 2024 16:53:14 +0000 Subject: [PATCH 097/115] oop, for the condition --- src/FFI.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FFI.php b/src/FFI.php index 56a30ae..8f2ac0c 100644 --- a/src/FFI.php +++ b/src/FFI.php @@ -243,7 +243,7 @@ private static function init(): void if (!ini_get('ffi.enable')) { throw new Exception("ffi.enable not set to 'true'"); } - if (version_compare(PHP_VERSION, '8.3') && + if (version_compare(PHP_VERSION, '8.3', '>=') && ini_get('zend.max_allowed_stack_size') != '-1') { throw new Exception("zend.max_allowed_stack_size not set to '-1'"); } From dbfcee0c951e184cd3a686342b3fb85641cb9753 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 24 Feb 2024 19:21:20 +0000 Subject: [PATCH 098/115] try to improve support for 8.7 --- CHANGELOG.md | 1 + src/FFI.php | 15 ++++++++++----- tests/StreamingTest.php | 10 ++++++++++ 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efa435f..71935e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ All notable changes to `:vips` will be documented in this file. - added streaming examples [jcupitt] - fix php 8.3 compatibility [uuf6429] - better library finding [uuf6429] +- fix compat with libvips before 8.9 [allanvb] ## 2.3.0 - 2023-09-26 diff --git a/src/FFI.php b/src/FFI.php index 8f2ac0c..70a399b 100644 --- a/src/FFI.php +++ b/src/FFI.php @@ -812,14 +812,19 @@ 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*"), "VipsProgress" => self::$vips->type("VipsProgress*"), ]; + if (self::atLeast(8, 9)) { + self::$ctypes = array_merge(self::$ctypes, [ + "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 = [ "gboolean" => self::$gobject->g_type_from_name("gboolean"), "gint" => self::$gobject->g_type_from_name("gint"), diff --git a/tests/StreamingTest.php b/tests/StreamingTest.php index 689ea01..7efcc90 100644 --- a/tests/StreamingTest.php +++ b/tests/StreamingTest.php @@ -3,6 +3,7 @@ namespace Jcupitt\Vips\Test; use Generator; +use Jcupitt\Vips\FFI; use Jcupitt\Vips\Exception; use Jcupitt\Vips\Image; use Jcupitt\Vips\Source; @@ -13,6 +14,15 @@ class StreamingTest extends TestCase { + protected function setUp(): void + { + parent::setUp(); + + if (!FFI::atLeast(8, 9)) { + $this->markTestSkipped('libvips too old for streaming tests'); + } + } + /** * @throws Exception */ From 69c343e0bc7bac6dc82f4f16f985d8d6a15eb05a Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 9 Apr 2024 10:27:18 +0100 Subject: [PATCH 099/115] update ready for 2.4 --- CHANGELOG.md | 4 +++- README.md | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71935e3..1854df8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,11 @@ # Changelog -All notable changes to `:vips` will be documented in this file. +All notable changes to `php-vips` will be documented in this file. ## master +## 2.4.0 - 2024-04-09 + - added `animate-image.php` example [jcupitt] - added flags support [jcupitt] - added `keep.php` example [jcupitt] diff --git a/README.md b/README.md index 716fe50..39ed779 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ to your `composer.json`: ``` "require": { - "jcupitt/vips" : "2.3.0" + "jcupitt/vips" : "2.4.0" } ``` From 5f8ed1049f90b1a95a39a127fabd43b2147789d0 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 2 Oct 2024 13:30:46 +0100 Subject: [PATCH 100/115] improve TargetCustom docs --- src/SourceCustom.php | 9 ++++++--- src/TargetCustom.php | 35 ++++++++++++++++++++++++++--------- 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/SourceCustom.php b/src/SourceCustom.php index 47d9bb4..20ecc7c 100644 --- a/src/SourceCustom.php +++ b/src/SourceCustom.php @@ -20,6 +20,7 @@ public function __construct() /** * 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 @@ -32,16 +33,18 @@ public function onRead(callable $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 + * - 0 => start + * - 1 => current position + * - 2 => end */ public function onSeek(callable $callback): void { diff --git a/src/TargetCustom.php b/src/TargetCustom.php index dedb149..99b1757 100644 --- a/src/TargetCustom.php +++ b/src/TargetCustom.php @@ -20,8 +20,13 @@ public function __construct() /** * 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. + * + * The interface is similar to fwrite(). The handler is given a + * bytes-like object to write, and should return the number of bytes + * written. + * + * Unlike fwrite, you should return -1 on error. + * * @throws Exception */ public function onWrite(callable $callback): void @@ -31,10 +36,12 @@ public function onWrite(callable $callback): void /** * Attach a read handler. - * The interface is similar to fread. 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 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). @@ -48,16 +55,19 @@ public function onRead(callable $callback): void /** * Attach a seek handler. - * The interface is the same as fseek, so the handler is passed + * + * The interface is similar to 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 + * - 0 => start + * - 1 => current position + * - 2 => end */ public function onSeek(callable $callback): void { @@ -68,9 +78,12 @@ public function onSeek(callable $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 + * + * Automatically falls back to onFinish if libvips <8.13. + * * @throws Exception */ public function onEnd(callable $callback): void @@ -84,8 +97,12 @@ public function onEnd(callable $callback): void /** * Attach a finish handler. - * For libvips 8.13 and later, this method is deprecated in favour of @throws Exception + * + * For libvips 8.13 and later, this method is deprecated in favour of + * onEnd(). + * * @see TargetCustom::onEnd() + * @throws Exception */ public function onFinish(callable $callback): void { From 0c20db91c8402a9729aa75175df91a7833d441e3 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 2 Oct 2024 13:33:32 +0100 Subject: [PATCH 101/115] remove stray whitespace --- src/TargetCustom.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/TargetCustom.php b/src/TargetCustom.php index 99b1757..a30b677 100644 --- a/src/TargetCustom.php +++ b/src/TargetCustom.php @@ -21,9 +21,9 @@ public function __construct() /** * Attach a write handler. * - * The interface is similar to fwrite(). The handler is given a - * bytes-like object to write, and should return the number of bytes - * written. + * The interface is similar to fwrite(). The handler is given a + * bytes-like object to write, and should return the number of bytes + * written. * * Unlike fwrite, you should return -1 on error. * @@ -98,7 +98,7 @@ public function onEnd(callable $callback): void /** * Attach a finish handler. * - * For libvips 8.13 and later, this method is deprecated in favour of + * For libvips 8.13 and later, this method is deprecated in favour of * onEnd(). * * @see TargetCustom::onEnd() From a8cc66c421f5b71568f9cf52a5bfcb818af52142 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 12 Oct 2024 14:37:22 +0100 Subject: [PATCH 102/115] update for 8.16 --- examples/generate_phpdoc.py | 2 +- src/ImageAutodoc.php | 33 +++++++++++----------- src/SdfShape.php | 56 +++++++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 18 deletions(-) create mode 100644 src/SdfShape.php diff --git a/examples/generate_phpdoc.py b/examples/generate_phpdoc.py index 5a7d310..5b89216 100755 --- a/examples/generate_phpdoc.py +++ b/examples/generate_phpdoc.py @@ -1,4 +1,4 @@ -#!/usr/bin/python3 +#!/usr/bin/env python3 # needs pyvips 2.2.3 or later diff --git a/src/ImageAutodoc.php b/src/ImageAutodoc.php index c773791..942aa15 100644 --- a/src/ImageAutodoc.php +++ b/src/ImageAutodoc.php @@ -87,6 +87,8 @@ * @throws Exception * @method Image abs(array $options = []) Absolute value of an image. * @throws Exception + * @method Image addalpha(array $options = []) Append an alpha channel. + * @throws Exception * @method Image affine(float[]|float $matrix, array $options = []) Affine transform of an image. * @throws Exception * @method static Image analyzeload(string $filename, array $options = []) Load an Analyze6 image. @@ -120,8 +122,6 @@ * @throws Exception * @method Image byteswap(array $options = []) Byteswap an image. * @throws Exception - * @method Image cache(array $options = []) Cache an image. - * @throws Exception * @method Image canny(array $options = []) Canny edge detector. * @throws Exception * @method Image case(Image[]|Image $cases, array $options = []) Use pixel values to pick cases from an array of images. @@ -129,6 +129,8 @@ * @method Image cast(string $format, array $options = []) Cast an image. * @see BandFormat for possible values for $format * @throws Exception + * @method Image clamp(array $options = []) Clamp values of an image. + * @throws Exception * @method Image colourspace(string $space, array $options = []) Convert to a new colorspace. * @see Interpretation for possible values for $space * @throws Exception @@ -372,18 +374,6 @@ * @throws Exception * @method void jxlsave_target(Target $target, array $options = []) Save image in JPEG-XL format. * @throws Exception - * @method static Image kakaduload(string $filename, array $options = []) Load JPEG2000 image. - * @throws Exception - * @method static Image kakaduload_buffer(string $buffer, array $options = []) Load JPEG2000 image. - * @throws Exception - * @method static Image kakaduload_source(Source $source, array $options = []) Load JPEG2000 image. - * @throws Exception - * @method void kakadusave(string $filename, array $options = []) Save image in JPEG2000 format. - * @throws Exception - * @method string kakadusave_buffer(array $options = []) Save image in JPEG2000 format. - * @throws Exception - * @method void kakadusave_target(Target $target, array $options = []) Save image in JPEG2000 format. - * @throws Exception * @method Image labelregions(array $options = []) Label regions in an image. * @throws Exception * @method Image linear(float[]|float $a, float[]|float $b, array $options = []) Calculate (a * in + b). @@ -451,6 +441,8 @@ * @throws Exception * @method float max(array $options = []) Find image maximum. * @throws Exception + * @method Image maxpair(Image $right, array $options = []) Maximum of a pair of images. + * @throws Exception * @method Image measure(integer $h, integer $v, array $options = []) Measure a set of patches on a color chart. * @throws Exception * @method Image merge(Image $sec, string $direction, integer $dx, integer $dy, array $options = []) Merge two images. @@ -458,6 +450,8 @@ * @throws Exception * @method float min(array $options = []) Find image minimum. * @throws Exception + * @method Image minpair(Image $right, array $options = []) Minimum of a pair of images. + * @throws Exception * @method Image morph(Image $mask, string $morph, array $options = []) Morphology operation. * @see OperationMorphology for possible values for $morph * @throws Exception @@ -499,9 +493,9 @@ * @throws Exception * @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. + * @method void pngsave(string $filename, array $options = []) Save image to file as PNG. * @throws Exception - * @method string pngsave_buffer(array $options = []) Save image to png buffer. + * @method string pngsave_buffer(array $options = []) Save image to buffer as PNG. * @throws Exception * @method void pngsave_target(Target $target, array $options = []) Save image to target as PNG. * @throws Exception @@ -553,7 +547,9 @@ * @throws Exception * @method void rawsave(string $filename, array $options = []) Save image to raw file. * @throws Exception - * @method void rawsave_fd(integer $fd, array $options = []) Write raw image to file descriptor. + * @method string rawsave_buffer(array $options = []) Write raw image to buffer. + * @throws Exception + * @method void rawsave_target(Target $target, array $options = []) Write raw image to target. * @throws Exception * @method Image recomb(Image $m, array $options = []) Linear recombination with matrix. * @throws Exception @@ -599,6 +595,9 @@ * @throws Exception * @method Image scharr(array $options = []) Scharr edge detector. * @throws Exception + * @method static Image sdf(integer $width, integer $height, string $shape, array $options = []) Create an SDF image. + * @see SdfShape for possible values for $shape + * @throws Exception * @method Image sequential(array $options = []) Check sequential access. * @throws Exception * @method Image sharpen(array $options = []) Unsharp masking for print. diff --git a/src/SdfShape.php b/src/SdfShape.php new file mode 100644 index 0000000..cbb590e --- /dev/null +++ b/src/SdfShape.php @@ -0,0 +1,56 @@ + + * @copyright 2016 John Cupitt + * @license https://opensource.org/licenses/MIT MIT + * @link https://github.com/jcupitt/php-vips + */ + +namespace Jcupitt\Vips; + +/** + * The SdfShape 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 SdfShape +{ + const CIRCLE = 'circle'; + const BOX = 'box'; + const ROUNDED_BOX = 'rounded-box'; + const LINE = 'line'; +} From f158e3e96095e050ce2b2414637c2fb9e5157579 Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 10 Jan 2025 10:30:37 -0600 Subject: [PATCH 103/115] PHP 8.4 support (#258) --- .github/workflows/ci.yml | 11 +++++++---- src/Exception.php | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index efa5647..5d60e0f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,7 @@ on: [ push, pull_request ] jobs: CI: name: ${{ matrix.php }} - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: @@ -15,21 +15,24 @@ jobs: - php: '8.1' - php: '8.2' - php: '8.3' + - php: '8.3' + - php: '8.4' steps: - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - ini-values: zend.max_allowed_stack_size=-1 + ini-values: zend.max_allowed_stack_size=-1,ffi.enable=true + extensions: ffi, exif tools: composer:v2 coverage: none - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install vips - run: sudo apt install -y libvips --no-install-recommends + run: sudo apt install -y --no-install-recommends libvips - name: Install composer dependencies run: | diff --git a/src/Exception.php b/src/Exception.php index ce5df1d..a3704e7 100644 --- a/src/Exception.php +++ b/src/Exception.php @@ -51,7 +51,7 @@ */ class Exception extends \Exception { - public function __construct($message = "", $code = 0, \Throwable $previous = null) + public function __construct($message = "", $code = 0, ?\Throwable $previous = null) { if ($message == "") { $message = "libvips error: " . FFI::vips()->vips_error_buffer(); From b4478a8549eba5c9c87adcdd3eee7f4eed6fd73e Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Mon, 13 Jan 2025 12:39:33 +0100 Subject: [PATCH 104/115] Cleanup CI (#259) - Test with PHP 8.3 only once. - Remove redundant exif extension. --- .github/workflows/ci.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5d60e0f..16248b1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,6 @@ jobs: - php: '8.1' - php: '8.2' - php: '8.3' - - php: '8.3' - php: '8.4' steps: @@ -23,8 +22,8 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - ini-values: zend.max_allowed_stack_size=-1,ffi.enable=true - extensions: ffi, exif + ini-values: ffi.enable=true, zend.max_allowed_stack_size=-1 + extensions: ffi tools: composer:v2 coverage: none From 256575d7c5247d4fb3e98bf10ab1e10a571684c7 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 13 Jan 2025 16:58:09 +0000 Subject: [PATCH 105/115] get ready for 2.4.1 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1854df8..07b5b2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to `php-vips` will be documented in this file. ## master +## 2.4.1 - 2025-01-13 + +- fix php 8.4 compatibility [deluxetom] + ## 2.4.0 - 2024-04-09 - added `animate-image.php` example [jcupitt] From 3b50384f980a20de65fe830071530c12ec353ae7 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Fri, 4 Apr 2025 13:24:38 +0200 Subject: [PATCH 106/115] Move `zend.max_allowed_stack_size=-1` check to `signalConnect()` (#261) Most users don't need to set this INI directive; it's only required when explicitly calling `signalConnect()` or using the `SourceCustom` and/or `TargetCustom` classes. --- src/FFI.php | 4 ---- src/GObject.php | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/FFI.php b/src/FFI.php index 70a399b..a48aeed 100644 --- a/src/FFI.php +++ b/src/FFI.php @@ -243,10 +243,6 @@ private static function init(): void if (!ini_get('ffi.enable')) { throw new Exception("ffi.enable not set to 'true'"); } - if (version_compare(PHP_VERSION, '8.3', '>=') && - ini_get('zend.max_allowed_stack_size') != '-1') { - throw new Exception("zend.max_allowed_stack_size not set to '-1'"); - } $vips_libname = self::libraryName("libvips", 42); if (PHP_OS_FAMILY === "Windows") { diff --git a/src/GObject.php b/src/GObject.php index 2959e25..50ae8d1 100644 --- a/src/GObject.php +++ b/src/GObject.php @@ -60,6 +60,14 @@ abstract class GObject */ private CData $pointer; + /** + * libvips executes FFI callbacks off the main thread and this confuses + * the stack limit checks available since PHP 8.3.0. We need to check + * if `zend.max_allowed_stack_size` is set to `-1`. + * See: https://github.com/libvips/php-vips/pull/237. + */ + private static bool $check_max_stack_size = true; + /** * Wrap a GObject around an underlying vips resource. The GObject takes * ownership of the pointer and will unref it on finalize. @@ -104,6 +112,16 @@ public function unref(): void */ public function signalConnect(string $name, callable $callback): void { + if (self::$check_max_stack_size) { + $max_allowed_stack_size = ini_get('zend.max_allowed_stack_size'); + if ($max_allowed_stack_size !== false && + $max_allowed_stack_size !== '-1') { + throw new Exception("signalConnect() requires zend.max_allowed_stack_size set to '-1'"); + } + + self::$check_max_stack_size = false; + } + $marshaler = self::getMarshaler($name, $callback); if ($marshaler === null) { throw new Exception("unsupported signal $name"); From 9ada9ec83a546b8a8230cc63da506de6722240b1 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Fri, 4 Apr 2025 13:25:19 +0200 Subject: [PATCH 107/115] Add libvips version check to example in README.md (#262) Useful for troubleshooting installation issues. --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 39ed779..7d3dbc9 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,9 @@ To your `php.ini`. require __DIR__ . '/vendor/autoload.php'; use Jcupitt\Vips; +// check libvips version +echo 'libvips version: ' . Vips\Config::version() . PHP_EOL; + // fast thumbnail generator $image = Vips\Image::thumbnail('somefile.jpg', 128); $image->writeToFile('tiny.jpg'); From efbf4c4314d24b5d84e64de52683594bb08920c7 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Fri, 4 Apr 2025 13:26:58 +0200 Subject: [PATCH 108/115] Revise logic for unified (semistatic) libvips binaries (#263) Resolves: #246. --- src/FFI.php | 42 ++++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/src/FFI.php b/src/FFI.php index a48aeed..6af6dce 100644 --- a/src/FFI.php +++ b/src/FFI.php @@ -245,13 +245,8 @@ private static function init(): void } $vips_libname = self::libraryName("libvips", 42); - if (PHP_OS_FAMILY === "Windows") { - $glib_libname = self::libraryName("libglib-2.0", 0); - $gobject_libname = self::libraryName("libgobject-2.0", 0); - } else { - $glib_libname = $vips_libname; - $gobject_libname = $vips_libname; - } + $glib_libname = self::libraryName("libglib-2.0", 0); + $gobject_libname = self::libraryName("libgobject-2.0", 0); Utils::debugLog("init", ["library" => $vips_libname]); @@ -771,21 +766,24 @@ private static function init(): void } Utils::debugLog("init", ["binding ..."]); - self::$glib = self::libraryLoad( - $libraryPaths, - $glib_libname, - $glib_decls - ); - self::$gobject = self::libraryLoad( - $libraryPaths, - $gobject_libname, - $gobject_decls - ); - self::$vips = self::libraryLoad( - $libraryPaths, - $vips_libname, - $vips_decls - ); + + /** + * We can sometimes get dependent libraries from libvips -- either the platform + * will open dependencies for us automatically, or the libvips binary has been + * built to includes all main dependencies (common on Windows, can happen + * elsewhere). + * + * We must get GLib functions from libvips if we can, since it will be the + * one that libvips itself is using, and they will share runtime types. + */ + self::$glib = + self::libraryLoad($libraryPaths, $vips_libname, $glib_decls) ?? + self::libraryLoad($libraryPaths, $glib_libname, $glib_decls); + self::$gobject = + self::libraryLoad($libraryPaths, $vips_libname, $gobject_decls) ?? + self::libraryLoad($libraryPaths, $gobject_libname, $gobject_decls); + + self::$vips = self::libraryLoad($libraryPaths, $vips_libname, $vips_decls); # Useful for debugging # self::$vips->vips_leak_set(1); From 1ffcd1f57ae4a2275dcf5579dc15640fdd523c4c Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Fri, 4 Apr 2025 15:07:32 +0200 Subject: [PATCH 109/115] Add `FFI::addLibraryPath` utility (#264) * Add `FFI::addLibraryPath` utility In favor of the `VIPSHOME` env. Resolves: #232. * Incorporate review comment --- README.md | 3 ++ src/FFI.php | 79 ++++++++++++++++++++++++++++++++++++----------------- 2 files changed, 57 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 7d3dbc9..e435a8e 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,9 @@ To your `php.ini`. require __DIR__ . '/vendor/autoload.php'; use Jcupitt\Vips; +// handy for Windows +Vips\FFI::addLibraryPath("C:/vips-dev-8.16/bin"); + // check libvips version echo 'libvips version: ' . Vips\Config::version() . PHP_EOL; diff --git a/src/FFI.php b/src/FFI.php index 6af6dce..f259f7b 100644 --- a/src/FFI.php +++ b/src/FFI.php @@ -79,6 +79,15 @@ class FFI */ private static bool $ffi_inited = false; + /** + * A list of paths where libvips might reside. + * + * @internal + */ + private static array $libraryPaths = [ + "" // system library + ]; + /** * Look up these once. * @@ -169,6 +178,39 @@ public static function atLeast(int $x, int $y, int $z = 0): bool self::$library_micro >= $z); } + /** + * Adds a directory to the search path for shared libraries. + * + * This method has no effect if FFI handles are already initialized, + * if the specified path is non-existent, or if the path is already + * included. + * + * @param string $path The path of the library. + * @return bool `true` if the path was added; otherwise, `false`. + */ + public static function addLibraryPath(string $path): bool + { + // Already initialized. + if (self::$ffi_inited) { + return false; + } + + $path = realpath($path); + if ($path === false) { + return false; + } + + $path .= DIRECTORY_SEPARATOR; + + if (in_array($path, self::$libraryPaths)) { + return false; + } + + self::$libraryPaths[] = $path; + + return true; + } + /** * Shut down libvips. Call this just before process exit. * @@ -208,12 +250,11 @@ private static function libraryName(string $name, int $abi): string } private static function libraryLoad( - array $libraryPaths, string $libraryName, string $interface ): ?\FFI { Utils::debugLog("trying to open", ["libraryName" => $libraryName]); - foreach ($libraryPaths as $path) { + foreach (self::$libraryPaths as $path) { Utils::debugLog("trying path", ["path" => $path]); try { $library = \FFI::cdef($interface, $path . $libraryName); @@ -252,26 +293,14 @@ private static function init(): void $is_64bits = PHP_INT_SIZE === 8; - $libraryPaths = [ - "" // system library - ]; - - $vipshome = getenv("VIPSHOME"); - if ($vipshome) { - // lib/ predicates lib/ - $libraryPaths[] = $vipshome . ($is_64bits ? "/lib64/" : "/lib32/"); - // lib/ is always searched - $libraryPaths[] = $vipshome . "/lib/"; - } - if (PHP_OS_FAMILY === "OSX" || PHP_OS_FAMILY === "Darwin") { // Homebrew on Apple Silicon - $libraryPaths[] = "/opt/homebrew/lib/"; + self::addLibraryPath("/opt/homebrew/lib"); // See https://github.com/Homebrew/brew/issues/13481#issuecomment-1207203483 - $libraryPaths[] = "/usr/local/lib/"; + self::addLibraryPath("/usr/local/lib"); } - $vips = self::libraryLoad($libraryPaths, $vips_libname, <<<'CPP' + $vips = self::libraryLoad($vips_libname, <<<'CPP' int vips_init (const char *argv0); const char *vips_error_buffer (void); int vips_version(int flag); @@ -279,10 +308,10 @@ private static function init(): void if ($vips === null) { // drop the "" (system path) member - array_shift($libraryPaths); + array_shift(self::$libraryPaths); $msg = "Unable to open library '$vips_libname'"; - if (!empty($libraryPaths)) { - $msg .= " in any of ['" . implode("', '", $libraryPaths) . "']"; + if (!empty(self::$libraryPaths)) { + $msg .= " in any of ['" . implode("', '", self::$libraryPaths) . "']"; } $msg .= ". Make sure that you've installed libvips and that '$vips_libname'"; $msg .= " is on your system's library search path."; @@ -777,13 +806,13 @@ private static function init(): void * one that libvips itself is using, and they will share runtime types. */ self::$glib = - self::libraryLoad($libraryPaths, $vips_libname, $glib_decls) ?? - self::libraryLoad($libraryPaths, $glib_libname, $glib_decls); + self::libraryLoad($vips_libname, $glib_decls) ?? + self::libraryLoad($glib_libname, $glib_decls); self::$gobject = - self::libraryLoad($libraryPaths, $vips_libname, $gobject_decls) ?? - self::libraryLoad($libraryPaths, $gobject_libname, $gobject_decls); + self::libraryLoad($vips_libname, $gobject_decls) ?? + self::libraryLoad($gobject_libname, $gobject_decls); - self::$vips = self::libraryLoad($libraryPaths, $vips_libname, $vips_decls); + self::$vips = self::libraryLoad($vips_libname, $vips_decls); # Useful for debugging # self::$vips->vips_leak_set(1); From 679e3cc5b64b6adc3cca43ca7fd162f4b0b8a4fb Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 4 Apr 2025 16:41:49 +0100 Subject: [PATCH 110/115] update for 2.4.2 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07b5b2d..009d1e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to `php-vips` will be documented in this file. ## master +## 2.4.2 - 2025-04-04 + +- only test `max_allowed_stack_size` when we have to [kleisauke] +- better libvips finding [kleisauke] + ## 2.4.1 - 2025-01-13 - fix php 8.4 compatibility [deluxetom] From a54c1cceea581b592a199edd61a7c06f44a24c08 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 4 Apr 2025 18:10:13 +0100 Subject: [PATCH 111/115] release 2.5.0 and delete 2,.4.2, sorry about that there has been an API addition, so there should be a minor version bump --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 009d1e5..b543c23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,10 @@ All notable changes to `php-vips` will be documented in this file. ## master -## 2.4.2 - 2025-04-04 +## 2.5.0 - 2025-04-04 +- add addLibraryPath() to let users set their libvips location [kleisauke] - only test `max_allowed_stack_size` when we have to [kleisauke] -- better libvips finding [kleisauke] ## 2.4.1 - 2025-01-13 From 693250c2b5870abe91b3e91cda9fcb33ce6c49bc Mon Sep 17 00:00:00 2001 From: Karel Wintersky Date: Wed, 30 Apr 2025 07:45:17 +0300 Subject: [PATCH 112/115] Create .gitattributes (#269) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR adds a .gitattributes file to exclude specific files and directories from exports/installing composer package. Purpose: Keeps repository metadata and tooling files out of distribution packages. Follows best practices for cleaner project exports. Impact: No runtime changes—this only affects repository operations like git archive. --- .gitattributes | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..ebf5613 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,18 @@ +* text=auto +* eol=lf + +.git export-ignore +.gitattributes export-ignore +.gitignore export-ignore +CREDITS export-ignore +phpcs-ruleset.xml export-ignore +phpdoc.xml export-ignore +phpunit.xml export-ignore +scrutinizer.yml export-ignore +.github export-ignore +examples export-ignore +tests export-ignore +API-1.0.0 export-ignore +RELEASE-2.0.0 export-ignore +CONTRIBUTING.md export-ignore +CHANGELOG.md export-ignore From cf920e59feaf367e244aaee3351317b8154c12f2 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Wed, 30 Apr 2025 14:02:53 +0200 Subject: [PATCH 113/115] Update docs and enums for upcoming libvips 8.17 (#271) --- src/ImageAutodoc.php | 8 ++++++-- src/Intent.php | 1 + src/Kernel.php | 2 ++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/ImageAutodoc.php b/src/ImageAutodoc.php index 942aa15..a0b2b4d 100644 --- a/src/ImageAutodoc.php +++ b/src/ImageAutodoc.php @@ -427,12 +427,14 @@ * @throws Exception * @method static Image matload(string $filename, array $options = []) Load mat from file. * @throws Exception - * @method Image matrixinvert(array $options = []) Invert an matrix. + * @method Image matrixinvert(array $options = []) Invert a matrix. * @throws Exception * @method static Image matrixload(string $filename, array $options = []) Load matrix. * @throws Exception * @method static Image matrixload_source(Source $source, array $options = []) Load matrix. * @throws Exception + * @method Image matrixmultiply(Image $right, array $options = []) Multiply two matrices. + * @throws Exception * @method void matrixprint(array $options = []) Print matrix. * @throws Exception * @method void matrixsave(string $filename, array $options = []) Save image to matrix. @@ -567,6 +569,8 @@ * @throws Exception * @method Image remainder_const(float[]|float $c, array $options = []) Remainder after integer division of an image and a constant. * @throws Exception + * @method Image remosaic(string $old_str, string $new_str, array $options = []) Rebuild an mosaiced image. + * @throws Exception * @method Image replicate(integer $across, integer $down, array $options = []) Replicate an image. * @throws Exception * @method Image resize(float $scale, array $options = []) Resize an image. @@ -589,7 +593,7 @@ * @throws Exception * @method Image scRGB2XYZ(array $options = []) Transform scRGB to XYZ. * @throws Exception - * @method Image scRGB2sRGB(array $options = []) Convert an scRGB image to sRGB. + * @method Image scRGB2sRGB(array $options = []) Convert scRGB to sRGB. * @throws Exception * @method Image scale(array $options = []) Scale an image to uchar. * @throws Exception diff --git a/src/Intent.php b/src/Intent.php index fab3249..658264e 100644 --- a/src/Intent.php +++ b/src/Intent.php @@ -53,4 +53,5 @@ abstract class Intent const RELATIVE = 'relative'; const SATURATION = 'saturation'; const ABSOLUTE = 'absolute'; + const AUTO = 'auto'; } diff --git a/src/Kernel.php b/src/Kernel.php index d5b76dc..27b335c 100644 --- a/src/Kernel.php +++ b/src/Kernel.php @@ -55,4 +55,6 @@ abstract class Kernel const MITCHELL = 'mitchell'; const LANCZOS2 = 'lanczos2'; const LANCZOS3 = 'lanczos3'; + const MKS2013 = 'mks2013'; + const MKS2021 = 'mks2021'; } From 1411b036f8a01b88dba3e206d905f57de335ddd1 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Wed, 30 Apr 2025 14:03:28 +0200 Subject: [PATCH 114/115] Remove redundant files (#270) --- .gitattributes | 3 --- API-1.0.0 | 0 RELEASE-2.0.0 | 0 scrutinizer.yml | 34 ---------------------------------- 4 files changed, 37 deletions(-) delete mode 100644 API-1.0.0 delete mode 100644 RELEASE-2.0.0 delete mode 100644 scrutinizer.yml diff --git a/.gitattributes b/.gitattributes index ebf5613..2748a19 100644 --- a/.gitattributes +++ b/.gitattributes @@ -8,11 +8,8 @@ CREDITS export-ignore phpcs-ruleset.xml export-ignore phpdoc.xml export-ignore phpunit.xml export-ignore -scrutinizer.yml export-ignore .github export-ignore examples export-ignore tests export-ignore -API-1.0.0 export-ignore -RELEASE-2.0.0 export-ignore CONTRIBUTING.md export-ignore CHANGELOG.md export-ignore diff --git a/API-1.0.0 b/API-1.0.0 deleted file mode 100644 index e69de29..0000000 diff --git a/RELEASE-2.0.0 b/RELEASE-2.0.0 deleted file mode 100644 index e69de29..0000000 diff --git a/scrutinizer.yml b/scrutinizer.yml deleted file mode 100644 index af8cd38..0000000 --- a/scrutinizer.yml +++ /dev/null @@ -1,34 +0,0 @@ -filter: - excluded_paths: [tests/*] -checks: - php: - code_rating: true - remove_extra_empty_lines: true - remove_php_closing_tag: true - remove_trailing_whitespace: true - fix_use_statements: - remove_unused: true - preserve_multiple: false - preserve_blanklines: true - order_alphabetically: true - fix_php_opening_tag: true - fix_linefeed: true - fix_line_ending: true - fix_identation_4spaces: true - fix_doc_comments: true -tools: - external_code_coverage: - timeout: 600 - runs: 3 - php_code_coverage: false - php_code_sniffer: - config: - standard: PSR2 - filter: - paths: ['src'] - php_loc: - enabled: true - excluded_dirs: [vendor, tests] - php_cpd: - enabled: true - excluded_dirs: [vendor, tests] \ No newline at end of file From b9ac979726b67fa1147f3197d964b02159bd7dd3 Mon Sep 17 00:00:00 2001 From: Kleis Auke Wolthuizen Date: Sat, 17 May 2025 12:40:38 +0200 Subject: [PATCH 115/115] CI: run `apt-get update` before installing libvips (#273) --- .github/workflows/ci.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 16248b1..cdc293e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,8 +30,10 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Install vips - run: sudo apt install -y --no-install-recommends libvips + - name: Install libvips + run: | + sudo apt-get update + sudo apt-get install --no-install-recommends libvips - name: Install composer dependencies run: |