lib/vips/image.rb in vips-8.7.0.1 vs lib/vips/image.rb in vips-8.8.0.1

- old
+ new

@@ -5,1496 +5,1498 @@ # License:: MIT require 'ffi' module Vips - private + private - attach_function :vips_image_new_matrix_from_array, - [:int, :int, :pointer, :int], :pointer + attach_function :vips_image_new_matrix_from_array, + [:int, :int, :pointer, :int], :pointer - attach_function :vips_image_copy_memory, [:pointer], :pointer + attach_function :vips_image_copy_memory, [:pointer], :pointer - attach_function :vips_filename_get_filename, [:string], :string - attach_function :vips_filename_get_options, [:string], :string + attach_function :vips_filename_get_filename, [:string], :pointer + attach_function :vips_filename_get_options, [:string], :pointer - attach_function :vips_foreign_find_load, [:string], :string - attach_function :vips_foreign_find_save, [:string], :string - attach_function :vips_foreign_find_load_buffer, [:pointer, :size_t], :string - attach_function :vips_foreign_find_save_buffer, [:string], :string + attach_function :vips_foreign_find_load, [:string], :string + attach_function :vips_foreign_find_save, [:string], :string + attach_function :vips_foreign_find_load_buffer, [:pointer, :size_t], :string + attach_function :vips_foreign_find_save_buffer, [:string], :string - attach_function :vips_image_write_to_memory, - [:pointer, SizeStruct.ptr], :pointer + attach_function :vips_image_write_to_memory, + [:pointer, SizeStruct.ptr], :pointer - attach_function :vips_image_get_typeof, [:pointer, :string], :GType - attach_function :vips_image_get, - [:pointer, :string, GObject::GValue.ptr], :int + attach_function :vips_image_get_typeof, [:pointer, :string], :GType + attach_function :vips_image_get, + [:pointer, :string, GObject::GValue.ptr], :int - # vips_image_get_fields was added in libvips 8.5 - begin - attach_function :vips_image_get_fields, [:pointer], :pointer - rescue FFI::NotFoundError - end + # vips_image_get_fields was added in libvips 8.5 + begin + attach_function :vips_image_get_fields, [:pointer], :pointer + rescue FFI::NotFoundError + nil + end - # vips_addalpha was added in libvips 8.6 - if Vips::at_least_libvips?(8, 6) - attach_function :vips_addalpha, [:pointer, :pointer, :varargs], :int - end - if Vips::at_least_libvips?(8, 5) - attach_function :vips_image_hasalpha, [:pointer], :int - end + # vips_addalpha was added in libvips 8.6 + if Vips::at_least_libvips?(8, 6) + attach_function :vips_addalpha, [:pointer, :pointer, :varargs], :int + end + if Vips::at_least_libvips?(8, 5) + attach_function :vips_image_hasalpha, [:pointer], :int + end - attach_function :vips_image_set, - [:pointer, :string, GObject::GValue.ptr], :void - attach_function :vips_image_remove, [:pointer, :string], :void + attach_function :vips_image_set, + [:pointer, :string, GObject::GValue.ptr], :void + attach_function :vips_image_remove, [:pointer, :string], :void - attach_function :vips_band_format_iscomplex, [:int], :int - attach_function :vips_band_format_isfloat, [:int], :int + attach_function :vips_band_format_iscomplex, [:int], :int + attach_function :vips_band_format_isfloat, [:int], :int - attach_function :nickname_find, :vips_nickname_find, [:GType], :string + attach_function :nickname_find, :vips_nickname_find, [:GType], :string - public + # turn a raw pointer that must be freed into a self-freeing Ruby string + def self.p2str(pointer) + pointer = FFI::AutoPointer.new(pointer, GLib::G_FREE) + pointer.read_string + end - # This class represents a libvips image. See the {Vips} module documentation - # for an introduction to using this class. + public - class Image < Vips::Object - alias_method :parent_get_typeof, :get_typeof + # This class represents a libvips image. See the {Vips} module documentation + # for an introduction to using this class. - private + class Image < Vips::Object + alias_method :parent_get_typeof, :get_typeof - # the layout of the VipsImage struct - module ImageLayout - def self.included base - base.class_eval do - layout :parent, Vips::Object::Struct - # rest opaque - end - end - end + private - class Struct < Vips::Object::Struct - include ImageLayout - + # the layout of the VipsImage struct + module ImageLayout + def self.included base + base.class_eval do + layout :parent, Vips::Object::Struct + # rest opaque end + end + end - class ManagedStruct < Vips::Object::ManagedStruct - include ImageLayout + class Struct < Vips::Object::Struct + include ImageLayout - end + end - class GenericPtr < FFI::Struct - layout :value, :pointer - end + class ManagedStruct < Vips::Object::ManagedStruct + include ImageLayout - # handy for overloads ... want to be able to apply a function to an - # array or to a scalar - def self.smap x, &block - x.is_a?(Array) ? x.map {|y| smap(y, &block)} : block.(x) - end + end - def self.complex? format - format_number = GObject::GValue.from_nick BAND_FORMAT_TYPE, format - Vips::vips_band_format_iscomplex(format_number) != 0 - end + class GenericPtr < FFI::Struct + layout :value, :pointer + end - def self.float? format - format_number = GObject::GValue.from_nick BAND_FORMAT_TYPE, format - Vips::vips_band_format_isfloat(format_number) != 0 - end + # handy for overloads ... want to be able to apply a function to an + # array or to a scalar + def self.smap x, &block + x.is_a?(Array) ? x.map {|y| smap(y, &block)} : block.(x) + end - # run a complex operation on a complex image, or an image with an even - # number of bands ... handy for things like running .polar on .index - # images - def self.run_cmplx image, &block - original_format = image.format + def self.complex? format + format_number = GObject::GValue.from_nick BAND_FORMAT_TYPE, format + Vips::vips_band_format_iscomplex(format_number) != 0 + end - unless Image::complex? image.format - if image.bands % 2 != 0 - raise Error, "not an even number of bands" - end + def self.float? format + format_number = GObject::GValue.from_nick BAND_FORMAT_TYPE, format + Vips::vips_band_format_isfloat(format_number) != 0 + end - unless Image::float? image.format - image = image.cast :float - end + # run a complex operation on a complex image, or an image with an even + # number of bands ... handy for things like running .polar on .index + # images + def self.run_cmplx image, &block + original_format = image.format - new_format = image.format == :double ? :dpcomplex : :complex - image = image.copy format: new_format, bands: image.bands / 2 - end - - image = block.(image) - - unless Image::complex? original_format - new_format = image.format == :dpcomplex ? :double : :float - image = image.copy format: new_format, bands: image.bands * 2 - end - - image + unless Image::complex? image.format + if image.bands % 2 != 0 + raise Error, "not an even number of bands" end - # handy for expanding enum operations - def call_enum(name, other, enum) - if other.is_a?(Vips::Image) - Vips::Operation.call name.to_s, [self, other, enum] - else - Vips::Operation.call name.to_s + "_const", [self, enum, other] - end + unless Image::float? image.format + image = image.cast :float end - # Write can fail due to no file descriptors and memory can fill if - # large objects are not collected fairly soon. We can't try a - # write and GC and retry on fail, since the write may take a - # long time and may not be repeatable. - # - # GCing before every write would have a horrible effect on - # performance, so as a compromise we GC every @@gc_interval writes. - # - # ruby2.1 introduced a generational GC which is fast enough to be - # able to GC on every write. + new_format = image.format == :double ? :dpcomplex : :complex + image = image.copy format: new_format, bands: image.bands / 2 + end - @@generational_gc = RUBY_ENGINE == "ruby" && RUBY_VERSION.to_f >= 2.1 + image = block.(image) - @@gc_interval = 100 - @@gc_countdown = @@gc_interval + unless Image::complex? original_format + new_format = image.format == :dpcomplex ? :double : :float + image = image.copy format: new_format, bands: image.bands * 2 + end - def write_gc - if @@generational_gc - GC.start full_mark: false - else - @@gc_countdown -= 1 - if @@gc_countdown < 0 - @@gc_countdown = @@gc_interval - GC.start - end - end - end + image + end - public + # handy for expanding enum operations + def call_enum(name, other, enum) + if other.is_a?(Vips::Image) + Vips::Operation.call name.to_s, [self, other, enum] + else + Vips::Operation.call name.to_s + "_const", [self, enum, other] + end + end - def inspect - "#<Image #{width}x#{height} #{format}, #{bands} bands, " + - "#{interpretation}>" - end + # Write can fail due to no file descriptors and memory can fill if + # large objects are not collected fairly soon. We can't try a + # write and GC and retry on fail, since the write may take a + # long time and may not be repeatable. + # + # GCing before every write would have a horrible effect on + # performance, so as a compromise we GC every @@gc_interval writes. + # + # ruby2.1 introduced a generational GC which is fast enough to be + # able to GC on every write. - def respond_to? name, include_all = false - # To support keyword args, we need to tell Ruby that final image - # arguments cannot be hashes of keywords. - # - # https://makandracards.com/makandra/36013-heads-up-ruby-implicitly-converts-a-hash-to-keyword-arguments - return false if name == :to_hash + @@generational_gc = RUBY_ENGINE == "ruby" && RUBY_VERSION.to_f >= 2.1 - # respond to all vips operations by nickname - return true if Vips::type_find("VipsOperation", name.to_s) != 0 + @@gc_interval = 100 + @@gc_countdown = @@gc_interval - super + def write_gc + if @@generational_gc + GC.start full_mark: false + else + @@gc_countdown -= 1 + if @@gc_countdown < 0 + @@gc_countdown = @@gc_interval + GC.start end + end + end - def self.respond_to? name, include_all = false - # respond to all vips operations by nickname - return true if Vips::type_find("VipsOperation", name.to_s) != 0 + public - super - end + def inspect + "#<Image #{width}x#{height} #{format}, #{bands} bands, " + + "#{interpretation}>" + end - # Invoke a vips operation with {Vips::Operation.call}, using self as - # the first input argument. - # - # @param name [String] vips operation to call - # @return result of vips operation - def method_missing name, *args, **options - Vips::Operation.call name.to_s, [self, *args], options - end + def respond_to? name, include_all = false + # To support keyword args, we need to tell Ruby that final image + # arguments cannot be hashes of keywords. + # + # https://makandracards.com/makandra/36013-heads-up-ruby-implicitly-converts-a-hash-to-keyword-arguments + return false if name == :to_hash - # Invoke a vips operation with {Vips::Operation.call}. - def self.method_missing name, *args, **options - Vips::Operation.call name.to_s, args, options - end + # respond to all vips operations by nickname + return true if Vips::type_find("VipsOperation", name.to_s) != 0 - # Return a new {Image} for a file on disc. This method can load - # images in any format supported by vips. The filename can include - # load options, for example: - # - # ``` - # image = Vips::new_from_file "fred.jpg[shrink=2]" - # ``` - # - # You can also supply options as a hash, for example: - # - # ``` - # image = Vips::new_from_file "fred.jpg", shrink: 2 - # ``` - # - # The full set of options available depend upon the load operation that - # will be executed. Try something like: - # - # ``` - # $ vips jpegload - # ``` - # - # at the command-line to see a summary of the available options for the - # JPEG loader. - # - # Loading is fast: only enough of the image is loaded to be able to fill - # out the header. Pixels will only be decompressed when they are needed. - # - # @!macro [new] vips.loadopts - # @param opts [Hash] set of options - # @option opts [Boolean] :disc (true) Open large images via a - # temporary disc file - # @option opts [Vips::Access] :access (:random) Access mode for file - # - # @param name [String] the filename to load from - # @macro vips.loadopts - # @return [Image] the loaded image - def self.new_from_file name, opts = {} - # very common, and Vips::vips_filename_get_filename will segv if we - # pass this - raise Vips::Error, "filename is nil" if name == nil + super + end - filename = Vips::vips_filename_get_filename name - option_string = Vips::vips_filename_get_options name - loader = Vips::vips_foreign_find_load filename - raise Vips::Error if loader == nil + def self.respond_to? name, include_all = false + # respond to all vips operations by nickname + return true if Vips::type_find("VipsOperation", name.to_s) != 0 - Operation.call loader, [filename], opts, option_string - end + super + end - # Create a new {Image} for an image encoded, in a format such as - # JPEG, in a memory string. Load options may be passed as - # strings or appended as a hash. For example: - # - # ``` - # image = Vips::Image.new_from_buffer memory_buffer, "shrink=2" - # ``` - # - # or alternatively: - # - # ``` - # image = Vips::Image.new_from_buffer memory_buffer, "", shrink: 2 - # ``` - # - # The options available depend on the file format. Try something like: - # - # ``` - # $ vips jpegload_buffer - # ``` - # - # at the command-line to see the available options. Not all loaders - # support load from buffer, but at least JPEG, PNG and - # TIFF images will work. - # - # Loading is fast: only enough of the image is loaded to be able to fill - # out the header. Pixels will only be decompressed when they are needed. - # - # @param data [String] the data to load from - # @param option_string [String] load options as a string - # @macro vips.loadopts - # @return [Image] the loaded image - def self.new_from_buffer data, option_string, opts = {} - loader = Vips::vips_foreign_find_load_buffer data, data.bytesize - raise Vips::Error if loader == nil + # Invoke a vips operation with {Vips::Operation.call}, using self as + # the first input argument. + # + # @param name [String] vips operation to call + # @return result of vips operation + def method_missing name, *args, **options + Vips::Operation.call name.to_s, [self, *args], options + end - Vips::Operation.call loader, [data], opts, option_string - end + # Invoke a vips operation with {Vips::Operation.call}. + def self.method_missing name, *args, **options + Vips::Operation.call name.to_s, args, options + end - def self.matrix_from_array width, height, array - ptr = FFI::MemoryPointer.new :double, array.length - ptr.write_array_of_double array - image = Vips::vips_image_new_matrix_from_array width, height, - ptr, array.length - Vips::Image.new image - end + # Return a new {Image} for a file on disc. This method can load + # images in any format supported by vips. The filename can include + # load options, for example: + # + # ``` + # image = Vips::new_from_file "fred.jpg[shrink=2]" + # ``` + # + # You can also supply options as a hash, for example: + # + # ``` + # image = Vips::new_from_file "fred.jpg", shrink: 2 + # ``` + # + # The full set of options available depend upon the load operation that + # will be executed. Try something like: + # + # ``` + # $ vips jpegload + # ``` + # + # at the command-line to see a summary of the available options for the + # JPEG loader. + # + # Loading is fast: only enough of the image is loaded to be able to fill + # out the header. Pixels will only be decompressed when they are needed. + # + # @!macro [new] vips.loadopts + # @param opts [Hash] set of options + # @option opts [Boolean] :disc (true) Open large images via a + # temporary disc file + # @option opts [Vips::Access] :access (:random) Access mode for file + # + # @param name [String] the filename to load from + # @macro vips.loadopts + # @return [Image] the loaded image + def self.new_from_file name, opts = {} + # very common, and Vips::vips_filename_get_filename will segv if we + # pass this + raise Vips::Error, "filename is nil" if name == nil - # Create a new Image from a 1D or 2D array. A 1D array becomes an - # image with height 1. Use `scale` and `offset` to set the scale and - # offset fields in the header. These are useful for integer - # convolutions. - # - # For example: - # - # ``` - # image = Vips::new_from_array [1, 2, 3] - # ``` - # - # or - # - # ``` - # image = Vips::new_from_array [ - # [-1, -1, -1], - # [-1, 16, -1], - # [-1, -1, -1]], 8 - # ``` - # - # for a simple sharpening mask. - # - # @param array [Array] the pixel data as an array of numbers - # @param scale [Real] the convolution scale - # @param offset [Real] the convolution offset - # @return [Image] the image - def self.new_from_array array, scale = 1, offset = 0 - # we accept a 1D array and assume height == 1, or a 2D array - # and check all lines are the same length - unless array.is_a? Array - raise Vips::Error, "Argument is not an array." - end + filename = Vips::p2str(Vips::vips_filename_get_filename name) + option_string = Vips::p2str(Vips::vips_filename_get_options name) + loader = Vips::vips_foreign_find_load filename + raise Vips::Error if loader == nil - if array[0].is_a? Array - height = array.length - width = array[0].length - unless array.all? {|x| x.is_a? Array} - raise Vips::Error, "Not a 2D array." - end - unless array.all? {|x| x.length == width} - raise Vips::Error, "Array not rectangular." - end - array = array.flatten - else - height = 1 - width = array.length - end + Operation.call loader, [filename], opts, option_string + end - unless array.all? {|x| x.is_a? Numeric} - raise Vips::Error, "Not all array elements are Numeric." - end + # Create a new {Image} for an image encoded, in a format such as + # JPEG, in a binary string. Load options may be passed as + # strings or appended as a hash. For example: + # + # ``` + # image = Vips::Image.new_from_buffer memory_buffer, "shrink=2" + # ``` + # + # or alternatively: + # + # ``` + # image = Vips::Image.new_from_buffer memory_buffer, "", shrink: 2 + # ``` + # + # The options available depend on the file format. Try something like: + # + # ``` + # $ vips jpegload_buffer + # ``` + # + # at the command-line to see the available options. Not all loaders + # support load from buffer, but at least JPEG, PNG and + # TIFF images will work. + # + # Loading is fast: only enough of the image is loaded to be able to fill + # out the header. Pixels will only be decompressed when they are needed. + # + # @param data [String] the data to load from + # @param option_string [String] load options as a string + # @macro vips.loadopts + # @return [Image] the loaded image + def self.new_from_buffer data, option_string, opts = {} + loader = Vips::vips_foreign_find_load_buffer data, data.bytesize + raise Vips::Error if loader == nil - image = Vips::Image.matrix_from_array width, height, array - raise Vips::Error if image == nil + Vips::Operation.call loader, [data], opts, option_string + end - # be careful to set them as double - image.set_type GObject::GDOUBLE_TYPE, 'scale', scale.to_f - image.set_type GObject::GDOUBLE_TYPE, 'offset', offset.to_f + def self.matrix_from_array width, height, array + ptr = FFI::MemoryPointer.new :double, array.length + ptr.write_array_of_double array + image = Vips::vips_image_new_matrix_from_array width, height, + ptr, array.length + Vips::Image.new image + end - return image - end + # Create a new Image from a 1D or 2D array. A 1D array becomes an + # image with height 1. Use `scale` and `offset` to set the scale and + # offset fields in the header. These are useful for integer + # convolutions. + # + # For example: + # + # ``` + # image = Vips::new_from_array [1, 2, 3] + # ``` + # + # or + # + # ``` + # image = Vips::new_from_array [ + # [-1, -1, -1], + # [-1, 16, -1], + # [-1, -1, -1]], 8 + # ``` + # + # for a simple sharpening mask. + # + # @param array [Array] the pixel data as an array of numbers + # @param scale [Real] the convolution scale + # @param offset [Real] the convolution offset + # @return [Image] the image + def self.new_from_array array, scale = 1, offset = 0 + # we accept a 1D array and assume height == 1, or a 2D array + # and check all lines are the same length + unless array.is_a? Array + raise Vips::Error, "Argument is not an array." + end - # A new image is created with the same width, height, format, - # interpretation, resolution and offset as self, but with every pixel - # set to the specified value. - # - # You can pass an array to make a many-band image, or a single value to - # make a one-band image. - # - # @param value [Real, Array<Real>] value to put in each pixel - # @return [Image] constant image - def new_from_image value - pixel = (Vips::Image.black(1, 1) + value).cast(format) - image = pixel.embed 0, 0, width, height, extend: :copy - image.copy interpretation: interpretation, - xres: xres, yres: yres, xoffset: xoffset, yoffset: yoffset + if array[0].is_a? Array + height = array.length + width = array[0].length + unless array.all? {|x| x.is_a? Array} + raise Vips::Error, "Not a 2D array." end - - # Write this image to a file. Save options may be encoded in the - # filename or given as a hash. For example: - # - # ``` - # image.write_to_file "fred.jpg[Q=90]" - # ``` - # - # or equivalently: - # - # ``` - # image.write_to_file "fred.jpg", Q: 90 - # ``` - # - # The full set of save options depend on the selected saver. Try - # something like: - # - # ``` - # $ vips jpegsave - # ``` - # - # to see all the available options for JPEG save. - # - # @!macro [new] vips.saveopts - # @param opts [Hash] set of options - # @option opts [Boolean] :strip (false) Strip all metadata from image - # @option opts [Array<Float>] :background (0) Background colour to - # flatten alpha against, if necessary - # - # @param name [String] filename to write to - def write_to_file name, opts = {} - filename = Vips::vips_filename_get_filename name - option_string = Vips::vips_filename_get_options name - saver = Vips::vips_foreign_find_save filename - if saver == nil - raise Vips::Error, "No known saver for '#{filename}'." - end - - Vips::Operation.call saver, [self, filename], opts, option_string - - write_gc + unless array.all? {|x| x.length == width} + raise Vips::Error, "Array not rectangular." end + array = array.flatten + else + height = 1 + width = array.length + end - # Write this image to a memory buffer. Save options may be encoded in - # the format_string or given as a hash. For example: - # - # ``` - # buffer = image.write_to_buffer ".jpg[Q=90]" - # ``` - # - # or equivalently: - # - # ``` - # image.write_to_buffer ".jpg", Q: 90 - # ``` - # - # The full set of save options depend on the selected saver. Try - # something like: - # - # ``` - # $ vips jpegsave - # ``` - # - # to see all the available options for JPEG save. - # - # @param format_string [String] save format plus options - # @macro vips.saveopts - # @return [String] the image saved in the specified format - def write_to_buffer format_string, opts = {} - filename = Vips::vips_filename_get_filename format_string - option_string = Vips::vips_filename_get_options format_string - saver = Vips::vips_foreign_find_save_buffer filename - if saver == nil - raise Vips::Error, "No known saver for '#{filename}'." - end + unless array.all? {|x| x.is_a? Numeric} + raise Vips::Error, "Not all array elements are Numeric." + end - buffer = Vips::Operation.call saver, [self], opts, option_string - raise Vips::Error if buffer == nil + image = Vips::Image.matrix_from_array width, height, array + raise Vips::Error if image == nil - write_gc + # be careful to set them as double + image.set_type GObject::GDOUBLE_TYPE, 'scale', scale.to_f + image.set_type GObject::GDOUBLE_TYPE, 'offset', offset.to_f - return buffer - end + return image + end - # Write this image to a large memory buffer. - # - # @return [String] the pixels as a huge binary string - def write_to_memory - len = Vips::SizeStruct.new - ptr = Vips::vips_image_write_to_memory self, len + # A new image is created with the same width, height, format, + # interpretation, resolution and offset as self, but with every pixel + # set to the specified value. + # + # You can pass an array to make a many-band image, or a single value to + # make a one-band image. + # + # @param value [Real, Array<Real>] value to put in each pixel + # @return [Image] constant image + def new_from_image value + pixel = (Vips::Image.black(1, 1) + value).cast(format) + image = pixel.embed 0, 0, width, height, extend: :copy + image.copy interpretation: interpretation, + xres: xres, yres: yres, xoffset: xoffset, yoffset: yoffset + end - # wrap up as an autopointer - ptr = FFI::AutoPointer.new(ptr, GLib::G_FREE) + # Write this image to a file. Save options may be encoded in the + # filename or given as a hash. For example: + # + # ``` + # image.write_to_file "fred.jpg[Q=90]" + # ``` + # + # or equivalently: + # + # ``` + # image.write_to_file "fred.jpg", Q: 90 + # ``` + # + # The full set of save options depend on the selected saver. Try + # something like: + # + # ``` + # $ vips jpegsave + # ``` + # + # to see all the available options for JPEG save. + # + # @!macro [new] vips.saveopts + # @param opts [Hash] set of options + # @option opts [Boolean] :strip (false) Strip all metadata from image + # @option opts [Array<Float>] :background (0) Background colour to + # flatten alpha against, if necessary + # + # @param name [String] filename to write to + def write_to_file name, opts = {} + filename = Vips::p2str(Vips::vips_filename_get_filename name) + option_string = Vips::p2str(Vips::vips_filename_get_options name) + saver = Vips::vips_foreign_find_save filename + if saver == nil + raise Vips::Error, "No known saver for '#{filename}'." + end - ptr.get_bytes 0, len[:value] - end + Vips::Operation.call saver, [self, filename], opts, option_string - # Fetch a `GType` from an image. `GType` will be 0 for no such field. - # - # @see get - # @param name [String] Metadata field to fetch - # @return [Integer] GType - def get_typeof name - # on libvips before 8.5, property types must be searched first, - # since vips_image_get_typeof returned built-in enums as int - unless Vips::at_least_libvips?(8, 5) - gtype = parent_get_typeof name - return gtype if gtype != 0 - end + write_gc + end - Vips::vips_image_get_typeof self, name - end + # Write this image to a memory buffer. Save options may be encoded in + # the format_string or given as a hash. For example: + # + # ``` + # buffer = image.write_to_buffer ".jpg[Q=90]" + # ``` + # + # or equivalently: + # + # ``` + # image.write_to_buffer ".jpg", Q: 90 + # ``` + # + # The full set of save options depend on the selected saver. Try + # something like: + # + # ``` + # $ vips jpegsave + # ``` + # + # to see all the available options for JPEG save. + # + # @param format_string [String] save format plus options + # @macro vips.saveopts + # @return [String] the image saved in the specified format + def write_to_buffer format_string, opts = {} + filename = + Vips::p2str(Vips::vips_filename_get_filename format_string) + option_string = + Vips::p2str(Vips::vips_filename_get_options format_string) + saver = Vips::vips_foreign_find_save_buffer filename + if saver == nil + raise Vips::Error, "No known saver for '#{filename}'." + end - # Get a metadata item from an image. Ruby types are constructed - # automatically from the `GValue`, if possible. - # - # For example, you can read the ICC profile from an image like this: - # - # ``` - # profile = image.get "icc-profile-data" - # ``` - # - # and profile will be an array containing the profile. - # - # @param name [String] Metadata field to get - # @return [Object] Value of field - def get name - # with old libvips, we must fetch properties (as opposed to - # metadata) via VipsObject - unless Vips::at_least_libvips?(8, 5) - return super if parent_get_typeof(name) != 0 - end + buffer = Vips::Operation.call saver, [self], opts, option_string + raise Vips::Error if buffer == nil - gvalue = GObject::GValue.alloc - result = Vips::vips_image_get self, name, gvalue - raise Vips::Error if result != 0 + write_gc - return gvalue.get - end + return buffer + end - # Get the names of all fields on an image. Use this to loop over all - # image metadata. - # - # @return [[String]] array of field names - def get_fields - # vips_image_get_fields() was added in libvips 8.5 - return [] unless Vips.respond_to? :vips_image_get_fields + # Write this image to a large memory buffer. + # + # @return [String] the pixels as a huge binary string + def write_to_memory + len = Vips::SizeStruct.new + ptr = Vips::vips_image_write_to_memory self, len - array = Vips::vips_image_get_fields self + # wrap up as an autopointer + ptr = FFI::AutoPointer.new(ptr, GLib::G_FREE) - names = [] - p = array - until ((q = p.read_pointer).null?) - names << q.read_string - GLib::g_free q - p += FFI::Type::POINTER.size - end - GLib::g_free array + ptr.get_bytes 0, len[:value] + end - return names - end + # Get the `GType` of a metadata field. The result is 0 if no such field + # exists. + # + # @see get + # @param name [String] Metadata field to fetch + # @return [Integer] GType + def get_typeof name + # on libvips before 8.5, property types must be searched first, + # since vips_image_get_typeof returned built-in enums as int + unless Vips::at_least_libvips?(8, 5) + gtype = parent_get_typeof name + return gtype if gtype != 0 + end - # Create a metadata item on an image, of the specifed type. Ruby types - # are automatically - # transformed into the matching `GType`, if possible. - # - # For example, you can use this to set an image's ICC profile: - # - # ``` - # x = y.set_type Vips::BLOB_TYPE, "icc-profile-data", profile - # ``` - # - # where `profile` is an ICC profile held as a binary string object. - # - # @see set - # @param gtype [Integer] GType of item - # @param name [String] Metadata field to set - # @param value [Object] Value to set - def set_type gtype, name, value - gvalue = GObject::GValue.alloc - gvalue.init gtype - gvalue.set value - Vips::vips_image_set self, name, gvalue - end + Vips::vips_image_get_typeof self, name + end - # Set the value of a metadata item on an image. The metadata item must - # already exist. Ruby types are automatically - # transformed into the matching `GValue`, if possible. - # - # For example, you can use this to set an image's ICC profile: - # - # ``` - # x = y.set "icc-profile-data", profile - # ``` - # - # where `profile` is an ICC profile held as a binary string object. - # - # @see set_type - # @param name [String] Metadata field to set - # @param value [Object] Value to set - def set name, value - set_type get_typeof(name), name, value - end + # Get a metadata item from an image. Ruby types are constructed + # automatically from the `GValue`, if possible. + # + # For example, you can read the ICC profile from an image like this: + # + # ``` + # profile = image.get "icc-profile-data" + # ``` + # + # and profile will be an array containing the profile. + # + # @param name [String] Metadata field to get + # @return [Object] Value of field + def get name + # with old libvips, we must fetch properties (as opposed to + # metadata) via VipsObject + unless Vips::at_least_libvips?(8, 5) + return super if parent_get_typeof(name) != 0 + end - # Remove a metadata item from an image. - # - # @param name [String] Metadata field to remove - def remove name - Vips::vips_image_remove self, name - end + gvalue = GObject::GValue.alloc + result = Vips::vips_image_get self, name, gvalue + raise Vips::Error if result != 0 - # compatibility: old name for get - def get_value name - get name - end + gvalue.get + end - # compatibility: old name for set - def set_value name, value - set name, value - end + # Get the names of all fields on an image. Use this to loop over all + # image metadata. + # + # @return [[String]] array of field names + def get_fields + # vips_image_get_fields() was added in libvips 8.5 + return [] unless Vips.respond_to? :vips_image_get_fields - # Get image width, in pixels. - # - # @return [Integer] image width, in pixels - def width - get "width" - end + array = Vips::vips_image_get_fields self - # Get image height, in pixels. - # - # @return [Integer] image height, in pixels - def height - get "height" - end + names = [] + p = array + until (q = p.read_pointer).null? + names << q.read_string + GLib::g_free q + p += FFI::Type::POINTER.size + end + GLib::g_free array - # Get number of image bands. - # - # @return [Integer] number of image bands - def bands - get "bands" - end + names + end - # Get image format. - # - # @return [Symbol] image format - def format - get "format" - end + # Create a metadata item on an image of the specifed type. Ruby types + # are automatically transformed into the matching `GType`, if possible. + # + # For example, you can use this to set an image's ICC profile: + # + # ``` + # x = y.set_type Vips::BLOB_TYPE, "icc-profile-data", profile + # ``` + # + # where `profile` is an ICC profile held as a binary string object. + # + # @see set + # @param gtype [Integer] GType of item + # @param name [String] Metadata field to set + # @param value [Object] Value to set + def set_type gtype, name, value + gvalue = GObject::GValue.alloc + gvalue.init gtype + gvalue.set value + Vips::vips_image_set self, name, gvalue + end - # Get image interpretation. - # - # @return [Symbol] image interpretation - def interpretation - get "interpretation" - end + # Set the value of a metadata item on an image. The metadata item must + # already exist. Ruby types are automatically transformed into the + # matching `GValue`, if possible. + # + # For example, you can use this to set an image's ICC profile: + # + # ``` + # x = y.set "icc-profile-data", profile + # ``` + # + # where `profile` is an ICC profile held as a binary string object. + # + # @see set_type + # @param name [String] Metadata field to set + # @param value [Object] Value to set + def set name, value + set_type get_typeof(name), name, value + end - # Get image coding. - # - # @return [Symbol] image coding - def coding - get "coding" - end + # Remove a metadata item from an image. + # + # @param name [String] Metadata field to remove + def remove name + Vips::vips_image_remove self, name + end - # Get image filename, if any. - # - # @return [String] image filename - def filename - get "filename" - end + # compatibility: old name for get + def get_value name + get name + end - # Get image xoffset. - # - # @return [Integer] image xoffset - def xoffset - get "xoffset" - end + # compatibility: old name for set + def set_value name, value + set name, value + end - # Get image yoffset. - # - # @return [Integer] image yoffset - def yoffset - get "yoffset" - end + # Get image width, in pixels. + # + # @return [Integer] image width, in pixels + def width + get "width" + end - # Get image x resolution. - # - # @return [Float] image x resolution - def xres - get "xres" - end + # Get image height, in pixels. + # + # @return [Integer] image height, in pixels + def height + get "height" + end - # Get image y resolution. - # - # @return [Float] image y resolution - def yres - get "yres" - end + # Get number of image bands. + # + # @return [Integer] number of image bands + def bands + get "bands" + end - # Get scale metadata. - # - # @return [Float] image scale - def scale - return 1 if get_typeof("scale") == 0 + # Get image format. + # + # @return [Symbol] image format + def format + get "format" + end - get "scale" - end + # Get image interpretation. + # + # @return [Symbol] image interpretation + def interpretation + get "interpretation" + end - # Get offset metadata. - # - # @return [Float] image offset - def offset - return 0 if get_typeof("offset") == 0 + # Get image coding. + # + # @return [Symbol] image coding + def coding + get "coding" + end - get "offset" - end + # Get image filename, if any. + # + # @return [String] image filename + def filename + get "filename" + end - # Get the image size. - # - # @return [Integer, Integer] image width and height - def size - [width, height] - end + # Get image xoffset. + # + # @return [Integer] image xoffset + def xoffset + get "xoffset" + end - if Vips::at_least_libvips?(8, 5) - # Detect if image has an alpha channel - # - # @return [Boolean] true if image has an alpha channel. - def has_alpha? - return Vips::vips_image_hasalpha(self) != 0 - end - end + # Get image yoffset. + # + # @return [Integer] image yoffset + def yoffset + get "yoffset" + end - # vips_addalpha was added in libvips 8.6 - if Vips::at_least_libvips?(8, 6) - # Append an alpha channel to an image. - # - # @return [Image] new image - def add_alpha - ptr = GenericPtr.new - result = Vips::vips_addalpha self, ptr - raise Vips::Error if result != 0 + # Get image x resolution. + # + # @return [Float] image x resolution + def xres + get "xres" + end - Vips::Image.new ptr[:value] - end - end + # Get image y resolution. + # + # @return [Float] image y resolution + def yres + get "yres" + end - # Copy an image to a memory area. - # - # This can be useful for reusing results, but can obviously use a lot of - # memory for large images. See {Image#tilecache} for a way of caching - # parts of an image. - # - # @return [Image] new memory image - def copy_memory - new_image = Vips::vips_image_copy_memory self - Vips::Image.new new_image - end + # Get scale metadata. + # + # @return [Float] image scale + def scale + return 1 if get_typeof("scale") == 0 - # Draw a point on an image. - # - # See {Image#draw_rect}. - # - # @return [Image] modified image - def draw_point ink, left, top, opts = {} - draw_rect ink, left, top, 1, 1, opts - end + get "scale" + end - # Add an image, constant or array. - # - # @param other [Image, Real, Array<Real>] Thing to add to self - # @return [Image] result of addition - def + other - other.is_a?(Vips::Image) ? - add(other) : linear(1, other) - end + # Get offset metadata. + # + # @return [Float] image offset + def offset + return 0 if get_typeof("offset") == 0 - # Subtract an image, constant or array. - # - # @param other [Image, Real, Array<Real>] Thing to subtract from self - # @return [Image] result of subtraction - def - other - other.is_a?(Vips::Image) ? - subtract(other) : linear(1, Image::smap(other) {|x| x * -1}) - end + get "offset" + end - # Multiply an image, constant or array. - # - # @param other [Image, Real, Array<Real>] Thing to multiply by self - # @return [Image] result of multiplication - def * other - other.is_a?(Vips::Image) ? - multiply(other) : linear(other, 0) - end + # Get the image size. + # + # @return [Integer, Integer] image width and height + def size + [width, height] + end - # Divide an image, constant or array. - # - # @param other [Image, Real, Array<Real>] Thing to divide self by - # @return [Image] result of division - def / other - other.is_a?(Vips::Image) ? - divide(other) : linear(Image::smap(other) {|x| 1.0 / x}, 0) - end + if Vips::at_least_libvips?(8, 5) + # Detect if image has an alpha channel + # + # @return [Boolean] true if image has an alpha channel. + def has_alpha? + return Vips::vips_image_hasalpha(self) != 0 + end + end - # Remainder after integer division with an image, constant or array. - # - # @param other [Image, Real, Array<Real>] self modulo this - # @return [Image] result of modulo - def % other - other.is_a?(Vips::Image) ? - remainder(other) : remainder_const(other) - end + # vips_addalpha was added in libvips 8.6 + if Vips::at_least_libvips?(8, 6) + # Append an alpha channel to an image. + # + # @return [Image] new image + def add_alpha + ptr = GenericPtr.new + result = Vips::vips_addalpha self, ptr + raise Vips::Error if result != 0 - # Raise to power of an image, constant or array. - # - # @param other [Image, Real, Array<Real>] self to the power of this - # @return [Image] result of power - def ** other - call_enum "math2", other, :pow - end + Vips::Image.new ptr[:value] + end + end - # Integer left shift with an image, constant or array. - # - # @param other [Image, Real, Array<Real>] shift left by this much - # @return [Image] result of left shift - def << other - call_enum "boolean", other, :lshift - end + # Copy an image to a memory area. + # + # This can be useful for reusing results, but can obviously use a lot of + # memory for large images. See {Image#tilecache} for a way of caching + # parts of an image. + # + # @return [Image] new memory image + def copy_memory + new_image = Vips::vips_image_copy_memory self + Vips::Image.new new_image + end - # Integer right shift with an image, constant or array. - # - # @param other [Image, Real, Array<Real>] shift right by this much - # @return [Image] result of right shift - def >> other - call_enum "boolean", other, :rshift - end + # Draw a point on an image. + # + # See {Image#draw_rect}. + # + # @return [Image] modified image + def draw_point ink, left, top, opts = {} + draw_rect ink, left, top, 1, 1, opts + end - # Integer bitwise OR with an image, constant or array. - # - # @param other [Image, Real, Array<Real>] bitwise OR with this - # @return [Image] result of bitwise OR - def | other - call_enum "boolean", other, :or - end + # Add an image, constant or array. + # + # @param other [Image, Real, Array<Real>] Thing to add to self + # @return [Image] result of addition + def + other + other.is_a?(Vips::Image) ? + add(other) : linear(1, other) + end - # Integer bitwise AND with an image, constant or array. - # - # @param other [Image, Real, Array<Real>] bitwise AND with this - # @return [Image] result of bitwise AND - def & other - call_enum "boolean", other, :and - end + # Subtract an image, constant or array. + # + # @param other [Image, Real, Array<Real>] Thing to subtract from self + # @return [Image] result of subtraction + def - other + other.is_a?(Vips::Image) ? + subtract(other) : linear(1, Image::smap(other) {|x| x * -1}) + end - # Integer bitwise EOR with an image, constant or array. - # - # @param other [Image, Real, Array<Real>] bitwise EOR with this - # @return [Image] result of bitwise EOR - def ^ other - call_enum "boolean", other, :eor - end + # Multiply an image, constant or array. + # + # @param other [Image, Real, Array<Real>] Thing to multiply by self + # @return [Image] result of multiplication + def * other + other.is_a?(Vips::Image) ? + multiply(other) : linear(other, 0) + end - # Equivalent to image ^ -1 - # - # @return [Image] image with bits flipped - def ! - self ^ -1 - end + # Divide an image, constant or array. + # + # @param other [Image, Real, Array<Real>] Thing to divide self by + # @return [Image] result of division + def / other + other.is_a?(Vips::Image) ? + divide(other) : linear(Image::smap(other) {|x| 1.0 / x}, 0) + end - # Equivalent to image ^ -1 - # - # @return [Image] image with bits flipped - def ~ - self ^ -1 - end + # Remainder after integer division with an image, constant or array. + # + # @param other [Image, Real, Array<Real>] self modulo this + # @return [Image] result of modulo + def % other + other.is_a?(Vips::Image) ? + remainder(other) : remainder_const(other) + end - # @return [Image] image - def +@ - self - end + # Raise to power of an image, constant or array. + # + # @param other [Image, Real, Array<Real>] self to the power of this + # @return [Image] result of power + def ** other + call_enum "math2", other, :pow + end - # Equivalent to image * -1 - # - # @return [Image] negative of image - def -@ - self * -1 - end + # Integer left shift with an image, constant or array. + # + # @param other [Image, Real, Array<Real>] shift left by this much + # @return [Image] result of left shift + def << other + call_enum "boolean", other, :lshift + end - # Relational less than with an image, constant or array. - # - # @param other [Image, Real, Array<Real>] relational less than with this - # @return [Image] result of less than - def < other - call_enum "relational", other, :less - end + # Integer right shift with an image, constant or array. + # + # @param other [Image, Real, Array<Real>] shift right by this much + # @return [Image] result of right shift + def >> other + call_enum "boolean", other, :rshift + end - # Relational less than or equal to with an image, constant or array. - # - # @param other [Image, Real, Array<Real>] relational less than or - # equal to with this - # @return [Image] result of less than or equal to - def <= other - call_enum "relational", other, :lesseq - end + # Integer bitwise OR with an image, constant or array. + # + # @param other [Image, Real, Array<Real>] bitwise OR with this + # @return [Image] result of bitwise OR + def | other + call_enum "boolean", other, :or + end - # Relational more than with an image, constant or array. - # - # @param other [Image, Real, Array<Real>] relational more than with this - # @return [Image] result of more than - def > other - call_enum "relational", other, :more - end + # Integer bitwise AND with an image, constant or array. + # + # @param other [Image, Real, Array<Real>] bitwise AND with this + # @return [Image] result of bitwise AND + def & other + call_enum "boolean", other, :and + end - # Relational more than or equal to with an image, constant or array. - # - # @param other [Image, Real, Array<Real>] relational more than or - # equal to with this - # @return [Image] result of more than or equal to - def >= other - call_enum "relational", other, :moreeq - end + # Integer bitwise EOR with an image, constant or array. + # + # @param other [Image, Real, Array<Real>] bitwise EOR with this + # @return [Image] result of bitwise EOR + def ^ other + call_enum "boolean", other, :eor + end - # Compare equality to nil, an image, constant or array. - # - # @param other [nil, Image, Real, Array<Real>] test equality to this - # @return [Image] result of equality - def == other - # for equality, we must allow tests against nil - if other == nil - false - else - call_enum "relational", other, :equal - end - end + # Equivalent to image ^ -1 + # + # @return [Image] image with bits flipped + def ! + self ^ -1 + end - # Compare inequality to nil, an image, constant or array. - # - # @param other [nil, Image, Real, Array<Real>] test inequality to this - # @return [Image] result of inequality - def != other - # for equality, we must allow tests against nil - if other == nil - true - else - call_enum "relational", other, :noteq - end - end + # Equivalent to image ^ -1 + # + # @return [Image] image with bits flipped + def ~ + self ^ -1 + end - # Fetch bands using a number or a range - # - # @param index [Numeric, Range] extract these band(s) - # @return [Image] extracted band(s) - def [] index - if index.is_a? Range - n = index.size - extract_band index.begin, n: n - elsif index.is_a? Numeric - extract_band index - else - raise Vips::Error, "[] index is not range or numeric." - end - end + # @return [Image] image + def +@ + self + end - # Convert to an Array. This will be slow for large images. - # - # @return [Array] array of Fixnum - def to_a - # we render the image to a big string, then unpack - # as a Ruby array of the correct type - memory = write_to_memory + # Equivalent to image * -1 + # + # @return [Image] negative of image + def -@ + self * -1 + end - # make the template for unpack - template = { - :char => 'c', - :uchar => 'C', - :short => 's_', - :ushort => 'S_', - :int => 'i_', - :uint => 'I_', - :float => 'f', - :double => 'd', - :complex => 'f', - :dpcomplex => 'd' - }[format] + '*' + # Relational less than with an image, constant or array. + # + # @param other [Image, Real, Array<Real>] relational less than with this + # @return [Image] result of less than + def < other + call_enum "relational", other, :less + end - # and unpack into something like [1, 2, 3, 4 ..] - array = memory.unpack(template) + # Relational less than or equal to with an image, constant or array. + # + # @param other [Image, Real, Array<Real>] relational less than or + # equal to with this + # @return [Image] result of less than or equal to + def <= other + call_enum "relational", other, :lesseq + end - # gather band elements together - pixel_array = array.each_slice(bands).to_a + # Relational more than with an image, constant or array. + # + # @param other [Image, Real, Array<Real>] relational more than with this + # @return [Image] result of more than + def > other + call_enum "relational", other, :more + end - # build rows - row_array = pixel_array.each_slice(width).to_a + # Relational more than or equal to with an image, constant or array. + # + # @param other [Image, Real, Array<Real>] relational more than or + # equal to with this + # @return [Image] result of more than or equal to + def >= other + call_enum "relational", other, :moreeq + end - return row_array - end + # Compare equality to nil, an image, constant or array. + # + # @param other [nil, Image, Real, Array<Real>] test equality to this + # @return [Image] result of equality + def == other + # for equality, we must allow tests against nil + if other == nil + false + else + call_enum "relational", other, :equal + end + end - # Return the largest integral value not greater than the argument. - # - # @return [Image] floor of image - def floor - round :floor - end + # Compare inequality to nil, an image, constant or array. + # + # @param other [nil, Image, Real, Array<Real>] test inequality to this + # @return [Image] result of inequality + def != other + # for equality, we must allow tests against nil + if other == nil + true + else + call_enum "relational", other, :noteq + end + end - # Return the smallest integral value not less than the argument. - # - # @return [Image] ceil of image - def ceil - round :ceil - end + # Fetch bands using a number or a range + # + # @param index [Numeric, Range] extract these band(s) + # @return [Image] extracted band(s) + def [] index + if index.is_a? Range + n = index.size + extract_band index.begin, n: n + elsif index.is_a? Numeric + extract_band index + else + raise Vips::Error, "[] index is not range or numeric." + end + end - # Return the nearest integral value. - # - # @return [Image] rint of image - def rint - round :rint - end + # Convert to an Array. This will be slow for large images. + # + # @return [Array] array of Fixnum + def to_a + # we render the image to a big string, then unpack + # as a Ruby array of the correct type + memory = write_to_memory - # AND the bands of an image together - # - # @return [Image] all bands ANDed together - def bandand - bandbool :and - end + # make the template for unpack + template = { + :char => 'c', + :uchar => 'C', + :short => 's_', + :ushort => 'S_', + :int => 'i_', + :uint => 'I_', + :float => 'f', + :double => 'd', + :complex => 'f', + :dpcomplex => 'd' + }[format] + '*' - # OR the bands of an image together - # - # @return [Image] all bands ORed together - def bandor - bandbool :or - end + # and unpack into something like [1, 2, 3, 4 ..] + array = memory.unpack(template) - # EOR the bands of an image together - # - # @return [Image] all bands EORed together - def bandeor - bandbool :eor - end + # gather band elements together + pixel_array = array.each_slice(bands).to_a - # Split an n-band image into n separate images. - # - # @return [Array<Image>] Array of n one-band images - def bandsplit - (0...bands).map {|i| extract_band i} - end + # build rows + row_array = pixel_array.each_slice(width).to_a - # Join a set of images bandwise. - # - # @param other [Image, Array<Image>, Real, Array<Real>] bands to append - # @return [Image] many band image - def bandjoin other - unless other.is_a? Array - other = [other] - end + return row_array + end - # if other is just Numeric, we can use bandjoin_const - not_all_real = !other.all?{|x| x.is_a? Numeric} + # Return the largest integral value not greater than the argument. + # + # @return [Image] floor of image + def floor + round :floor + end - if not_all_real - Vips::Image.bandjoin([self] + other) - else - bandjoin_const other - end - end + # Return the smallest integral value not less than the argument. + # + # @return [Image] ceil of image + def ceil + round :ceil + end - # Composite a set of images with a set of blend modes. - # - # @param other [Image, Array<Image>, Real, Array<Real>] bands to append - # @return [Image] many band image - def composite other, mode, opts = {} - unless other.is_a? Array - other = [other] - end - unless mode.is_a? Array - mode = [mode] - end + # Return the nearest integral value. + # + # @return [Image] rint of image + def rint + round :rint + end - mode = mode.map do |x| - GObject::GValue.from_nick Vips::BLEND_MODE_TYPE, x - end + # AND the bands of an image together + # + # @return [Image] all bands ANDed together + def bandand + bandbool :and + end - Vips::Image.composite([self] + other, mode, opts) - end + # OR the bands of an image together + # + # @return [Image] all bands ORed together + def bandor + bandbool :or + end - # Return the coordinates of the image maximum. - # - # @return [Real, Real, Real] maximum value, x coordinate of maximum, y - # coordinate of maximum - def maxpos - v, opts = max x: true, y: true - x = opts['x'] - y = opts['y'] - return v, x, y - end + # EOR the bands of an image together + # + # @return [Image] all bands EORed together + def bandeor + bandbool :eor + end - # Return the coordinates of the image minimum. - # - # @return [Real, Real, Real] minimum value, x coordinate of minimum, y - # coordinate of minimum - def minpos - v, opts = min x: true, y: true - x = opts['x'] - y = opts['y'] - return v, x, y - end + # Split an n-band image into n separate images. + # + # @return [Array<Image>] Array of n one-band images + def bandsplit + (0...bands).map {|i| extract_band i} + end - # get the value of a pixel as an array - # - # @param x [Integer] x coordinate to sample - # @param y [Integer] y coordinate to sample - # @return [Array<Float>] the pixel values as an array - def getpoint x, y - # vips has an operation that does this, but we can't call it via - # gobject-introspection 3.1 since it's missing array double - # get - # - # remove this def when gobject-introspection updates - crop(x, y, 1, 1).bandsplit.map(&:avg) - end + # Join a set of images bandwise. + # + # @param other [Image, Array<Image>, Real, Array<Real>] bands to append + # @return [Image] many band image + def bandjoin other + unless other.is_a? Array + other = [other] + end - # a median filter - # - # @param size [Integer] size of filter window - # @return [Image] result of median filter - def median size = 3 - rank size, size, (size * size) / 2 - end + # if other is just Numeric, we can use bandjoin_const + not_all_real = !other.all?{|x| x.is_a? Numeric} - # Return the real part of a complex image. - # - # @return [Image] real part of complex image - def real - complexget :real - end + if not_all_real + Vips::Image.bandjoin([self] + other) + else + bandjoin_const other + end + end - # Return the imaginary part of a complex image. - # - # @return [Image] imaginary part of complex image - def imag - complexget :imag - end + # Composite a set of images with a set of blend modes. + # + # @param overlay [Image, Array<Image>] images to composite + # @param mode [BlendMode, Array<BlendMode>] blend modes to use + # @param opts [Hash] Set of options + # @option opts [Vips::Interpretation] :compositing_space Composite images in this colour space + # @option opts [Boolean] :premultiplied Images have premultiplied alpha + # @return [Image] blended image + def composite overlay, mode, opts = {} + unless overlay.is_a? Array + overlay = [overlay] + end + unless mode.is_a? Array + mode = [mode] + end - # Return an image with rectangular pixels converted to polar. - # - # The image - # can be complex, in which case the return image will also be complex, - # or must have an even number of bands, in which case pairs of - # bands are treated as (x, y) coordinates. - # - # @see xyz - # @return [Image] image converted to polar coordinates - def polar - Image::run_cmplx(self) {|x| x.complex :polar} - end + mode = mode.map do |x| + GObject::GValue.from_nick Vips::BLEND_MODE_TYPE, x + end - # Return an image with polar pixels converted to rectangular. - # - # The image - # can be complex, in which case the return image will also be complex, - # or must have an even number of bands, in which case pairs of - # bands are treated as (x, y) coordinates. - # - # @see xyz - # @return [Image] image converted to rectangular coordinates - def rect - Image::run_cmplx(self) {|x| x.complex :rect} - end + Vips::Image.composite([self] + overlay, mode, opts) + end - # Return the complex conjugate of an image. - # - # The image - # can be complex, in which case the return image will also be complex, - # or must have an even number of bands, in which case pairs of - # bands are treated as (x, y) coordinates. - # - # @return [Image] complex conjugate - def conj - Image::run_cmplx(self) {|x| x.complex :conj} - end + # Return the coordinates of the image maximum. + # + # @return [Real, Real, Real] maximum value, x coordinate of maximum, y + # coordinate of maximum + def maxpos + v, opts = max x: true, y: true + x = opts['x'] + y = opts['y'] + return v, x, y + end - # Calculate the cross phase of two images. - # - # @param other [Image, Real, Array<Real>] cross phase with this - # @return [Image] cross phase - def cross_phase other - complex2 other, :cross_phase - end + # Return the coordinates of the image minimum. + # + # @return [Real, Real, Real] minimum value, x coordinate of minimum, y + # coordinate of minimum + def minpos + v, opts = min x: true, y: true + x = opts['x'] + y = opts['y'] + return v, x, y + end - # Return the sine of an image in degrees. - # - # @return [Image] sine of each pixel - def sin - math :sin - end + # a median filter + # + # @param size [Integer] size of filter window + # @return [Image] result of median filter + def median size = 3 + rank size, size, (size * size) / 2 + end - # Return the cosine of an image in degrees. - # - # @return [Image] cosine of each pixel - def cos - math :cos - end + # Return the real part of a complex image. + # + # @return [Image] real part of complex image + def real + complexget :real + end - # Return the tangent of an image in degrees. - # - # @return [Image] tangent of each pixel - def tan - math :tan - end + # Return the imaginary part of a complex image. + # + # @return [Image] imaginary part of complex image + def imag + complexget :imag + end - # Return the inverse sine of an image in degrees. - # - # @return [Image] inverse sine of each pixel - def asin - math :asin - end + # Return an image with rectangular pixels converted to polar. + # + # The image + # can be complex, in which case the return image will also be complex, + # or must have an even number of bands, in which case pairs of + # bands are treated as (x, y) coordinates. + # + # @see xyz + # @return [Image] image converted to polar coordinates + def polar + Image::run_cmplx(self) {|x| x.complex :polar} + end - # Return the inverse cosine of an image in degrees. - # - # @return [Image] inverse cosine of each pixel - def acos - math :acos - end + # Return an image with polar pixels converted to rectangular. + # + # The image + # can be complex, in which case the return image will also be complex, + # or must have an even number of bands, in which case pairs of + # bands are treated as (x, y) coordinates. + # + # @see xyz + # @return [Image] image converted to rectangular coordinates + def rect + Image::run_cmplx(self) {|x| x.complex :rect} + end - # Return the inverse tangent of an image in degrees. - # - # @return [Image] inverse tangent of each pixel - def atan - math :atan - end + # Return the complex conjugate of an image. + # + # The image + # can be complex, in which case the return image will also be complex, + # or must have an even number of bands, in which case pairs of + # bands are treated as (x, y) coordinates. + # + # @return [Image] complex conjugate + def conj + Image::run_cmplx(self) {|x| x.complex :conj} + end - # Return the natural log of an image. - # - # @return [Image] natural log of each pixel - def log - math :log - end + # Calculate the cross phase of two images. + # + # @param other [Image, Real, Array<Real>] cross phase with this + # @return [Image] cross phase + def cross_phase other + complex2 other, :cross_phase + end - # Return the log base 10 of an image. - # - # @return [Image] base 10 log of each pixel - def log10 - math :log10 - end + # Return the sine of an image in degrees. + # + # @return [Image] sine of each pixel + def sin + math :sin + end - # Return e ** pixel. - # - # @return [Image] e ** pixel - def exp - math :exp - end + # Return the cosine of an image in degrees. + # + # @return [Image] cosine of each pixel + def cos + math :cos + end - # Return 10 ** pixel. - # - # @return [Image] 10 ** pixel - def exp10 - math :exp10 - end + # Return the tangent of an image in degrees. + # + # @return [Image] tangent of each pixel + def tan + math :tan + end - # Flip horizontally. - # - # @return [Image] image flipped horizontally - def fliphor - flip :horizontal - end + # Return the inverse sine of an image in degrees. + # + # @return [Image] inverse sine of each pixel + def asin + math :asin + end - # Flip vertically. - # - # @return [Image] image flipped vertically - def flipver - flip :vertical - end + # Return the inverse cosine of an image in degrees. + # + # @return [Image] inverse cosine of each pixel + def acos + math :acos + end - # Erode with a structuring element. - # - # The structuring element must be an array with 0 for black, 255 for - # white and 128 for don't care. - # - # @param mask [Image, Array<Real>, Array<Array<Real>>] structuring - # element - # @return [Image] eroded image - def erode mask - morph mask, :erode - end + # Return the inverse tangent of an image in degrees. + # + # @return [Image] inverse tangent of each pixel + def atan + math :atan + end - # Dilate with a structuring element. - # - # The structuring element must be an array with 0 for black, 255 for - # white and 128 for don't care. - # - # @param mask [Image, Array<Real>, Array<Array<Real>>] structuring - # element - # @return [Image] dilated image - def dilate mask - morph mask, :dilate - end + # Return the natural log of an image. + # + # @return [Image] natural log of each pixel + def log + math :log + end - # Rotate by 90 degrees clockwise. - # - # @return [Image] rotated image - def rot90 - rot :d90 - end + # Return the log base 10 of an image. + # + # @return [Image] base 10 log of each pixel + def log10 + math :log10 + end - # Rotate by 180 degrees clockwise. - # - # @return [Image] rotated image - def rot180 - rot :d180 - end + # Return e ** pixel. + # + # @return [Image] e ** pixel + def exp + math :exp + end - # Rotate by 270 degrees clockwise. - # - # @return [Image] rotated image - def rot270 - rot :d270 - end + # Return 10 ** pixel. + # + # @return [Image] 10 ** pixel + def exp10 + math :exp10 + end - # Select pixels from `th` if `self` is non-zero and from `el` if - # `self` is zero. Use the `:blend` option to fade smoothly - # between `th` and `el`. - # - # @param th [Image, Real, Array<Real>] true values - # @param el [Image, Real, Array<Real>] false values - # @param opts [Hash] set of options - # @option opts [Boolean] :blend (false) Blend smoothly between th and el - # @return [Image] merged image - def ifthenelse(th, el, opts = {}) - match_image = [th, el, self].find {|x| x.is_a? Vips::Image} + # Flip horizontally. + # + # @return [Image] image flipped horizontally + def fliphor + flip :horizontal + end - unless th.is_a? Vips::Image - th = Operation.imageize match_image, th - end - unless el.is_a? Vips::Image - el = Operation.imageize match_image, el - end + # Flip vertically. + # + # @return [Image] image flipped vertically + def flipver + flip :vertical + end - Vips::Operation.call "ifthenelse", [self, th, el], opts - end + # Erode with a structuring element. + # + # The structuring element must be an array with 0 for black, 255 for + # white and 128 for don't care. + # + # @param mask [Image, Array<Real>, Array<Array<Real>>] structuring + # element + # @return [Image] eroded image + def erode mask + morph mask, :erode + end - # Scale an image to uchar. This is the vips `scale` operation, but - # renamed to avoid a clash with the `.scale` property. - # - # @param opts [Hash] Set of options - # @return [Vips::Image] Output image - def scaleimage opts = {} - Vips::Image.scale self, opts - end + # Dilate with a structuring element. + # + # The structuring element must be an array with 0 for black, 255 for + # white and 128 for don't care. + # + # @param mask [Image, Array<Real>, Array<Array<Real>>] structuring + # element + # @return [Image] dilated image + def dilate mask + morph mask, :dilate + end + # Rotate by 90 degrees clockwise. + # + # @return [Image] rotated image + def rot90 + rot :d90 end - # This method generates yard comments for all the dynamically bound - # vips operations. + # Rotate by 180 degrees clockwise. # - # Regenerate with something like: + # @return [Image] rotated image + def rot180 + rot :d180 + end + + # Rotate by 270 degrees clockwise. # - # ``` - # $ ruby > methods.rb - # require 'vips'; Vips::generate_yard - # ^D - # ``` + # @return [Image] rotated image + def rot270 + rot :d270 + end - def self.generate_yard - # these have hand-written methods, see above - no_generate = ["scale", "bandjoin", "composite", "ifthenelse"] + # Select pixels from `th` if `self` is non-zero and from `el` if + # `self` is zero. Use the `:blend` option to fade smoothly + # between `th` and `el`. + # + # @param th [Image, Real, Array<Real>] true values + # @param el [Image, Real, Array<Real>] false values + # @param opts [Hash] set of options + # @option opts [Boolean] :blend (false) Blend smoothly between th and el + # @return [Image] merged image + def ifthenelse(th, el, opts = {}) + match_image = [th, el, self].find {|x| x.is_a? Vips::Image} - # map gobject's type names to Ruby - map_go_to_ruby = { - "gboolean" => "Boolean", - "gint" => "Integer", - "gdouble" => "Float", - "gfloat" => "Float", - "gchararray" => "String", - "VipsImage" => "Vips::Image", - "VipsInterpolate" => "Vips::Interpolate", - "VipsArrayDouble" => "Array<Double>", - "VipsArrayInt" => "Array<Integer>", - "VipsArrayImage" => "Array<Image>", - "VipsArrayString" => "Array<String>", - } + unless th.is_a? Vips::Image + th = Operation.imageize match_image, th + end + unless el.is_a? Vips::Image + el = Operation.imageize match_image, el + end - generate_operation = lambda do |gtype, nickname, op| - op_flags = op.get_flags - return if (op_flags & OPERATION_DEPRECATED) != 0 - return if no_generate.include? nickname - description = Vips::vips_object_get_description op + Vips::Operation.call "ifthenelse", [self, th, el], opts + end - # find and classify all the arguments the operator can take - required_input = [] - optional_input = [] - required_output = [] - optional_output = [] - member_x = nil - op.argument_map do |pspec, argument_class, argument_instance| - arg_flags = argument_class[:flags] - next if (arg_flags & ARGUMENT_CONSTRUCT) == 0 - next if (arg_flags & ARGUMENT_DEPRECATED) != 0 + # Scale an image to uchar. This is the vips `scale` operation, but + # renamed to avoid a clash with the `.scale` property. + # + # @param opts [Hash] Set of options + # @return [Vips::Image] Output image + def scaleimage opts = {} + Vips::Image.scale self, opts + end - name = pspec[:name].tr("-", "_") - # 'in' as a param name confuses yard - name = "im" if name == "in" - gtype = pspec[:value_type] - fundamental = GObject::g_type_fundamental gtype - type_name = GObject::g_type_name gtype - if map_go_to_ruby.include? type_name - type_name = map_go_to_ruby[type_name] - end - if fundamental == GObject::GFLAGS_TYPE || - fundamental == GObject::GENUM_TYPE - type_name = "Vips::" + type_name[/Vips(.*)/, 1] - end - blurb = GObject::g_param_spec_get_blurb pspec - value = {:name => name, - :flags => arg_flags, - :gtype => gtype, - :type_name => type_name, - :blurb => blurb} + end +end - if (arg_flags & ARGUMENT_INPUT) != 0 - if (arg_flags & ARGUMENT_REQUIRED) != 0 - # note the first required input image, if any ... we - # will be a method of this instance - if !member_x && gtype == Vips::IMAGE_TYPE - member_x = value - else - required_input << value - end - else - optional_input << value - end - end +module Vips - # MODIFY INPUT args count as OUTPUT as well - if (arg_flags & ARGUMENT_OUTPUT) != 0 || - ((arg_flags & ARGUMENT_INPUT) != 0 && - (arg_flags & ARGUMENT_MODIFY) != 0) - if (arg_flags & ARGUMENT_REQUIRED) != 0 - required_output << value - else - optional_output << value - end - end + # This method generates yard comments for all the dynamically bound + # vips operations. + # + # Regenerate with something like: + # + # ``` + # $ ruby > methods.rb + # require 'vips'; Vips::generate_yard + # ^D + # ``` - end + def self.generate_yard + # these have hand-written methods, see above + no_generate = ["scale", "bandjoin", "composite", "ifthenelse"] - print "# @!method " - print "self." unless member_x - print "#{nickname}(" - print required_input.map{|x| x[:name]}.join(", ") - print ", " if required_input.length > 0 - puts "opts = {})" + # map gobject's type names to Ruby + map_go_to_ruby = { + "gboolean" => "Boolean", + "gint" => "Integer", + "gdouble" => "Float", + "gfloat" => "Float", + "gchararray" => "String", + "VipsImage" => "Vips::Image", + "VipsInterpolate" => "Vips::Interpolate", + "VipsArrayDouble" => "Array<Double>", + "VipsArrayInt" => "Array<Integer>", + "VipsArrayImage" => "Array<Image>", + "VipsArrayString" => "Array<String>", + } - puts "# #{description.capitalize}." + generate_operation = lambda do |gtype, nickname, op| + op_flags = op.get_flags + return if (op_flags & OPERATION_DEPRECATED) != 0 + return if no_generate.include? nickname + description = Vips::vips_object_get_description op - required_input.each do |arg| - puts "# @param #{arg[:name]} [#{arg[:type_name]}] " + - "#{arg[:blurb]}" - end + # find and classify all the arguments the operator can take + required_input = [] + optional_input = [] + required_output = [] + optional_output = [] + member_x = nil + op.argument_map do |pspec, argument_class, argument_instance| + arg_flags = argument_class[:flags] + next if (arg_flags & ARGUMENT_CONSTRUCT) == 0 + next if (arg_flags & ARGUMENT_DEPRECATED) != 0 - puts "# @param opts [Hash] Set of options" - optional_input.each do |arg| - puts "# @option opts [#{arg[:type_name]}] :#{arg[:name]} " + - "#{arg[:blurb]}" - end - optional_output.each do |arg| - print "# @option opts [#{arg[:type_name]}] :#{arg[:name]}" - puts " Output #{arg[:blurb]}" - end + name = pspec[:name].tr("-", "_") + # 'in' as a param name confuses yard + name = "im" if name == "in" + gtype = pspec[:value_type] + fundamental = GObject::g_type_fundamental gtype + type_name = GObject::g_type_name gtype + if map_go_to_ruby.include? type_name + type_name = map_go_to_ruby[type_name] + end + if fundamental == GObject::GFLAGS_TYPE || + fundamental == GObject::GENUM_TYPE + type_name = "Vips::" + type_name[/Vips(.*)/, 1] + end + blurb = GObject::g_param_spec_get_blurb pspec + value = {:name => name, + :flags => arg_flags, + :gtype => gtype, + :type_name => type_name, + :blurb => blurb} - print "# @return [" - if required_output.length == 0 - print "nil" - elsif required_output.length == 1 - print required_output.first[:type_name] - elsif - print "Array<" - print required_output.map{|x| x[:type_name]}.join(", ") - print ">" + if (arg_flags & ARGUMENT_INPUT) != 0 + if (arg_flags & ARGUMENT_REQUIRED) != 0 + # note the first required input image, if any ... we + # will be a method of this instance + if !member_x && gtype == Vips::IMAGE_TYPE + member_x = value + else + required_input << value end - if optional_output.length > 0 - print ", Hash<Symbol => Object>" - end - print "] " - print required_output.map{|x| x[:blurb]}.join(", ") - if optional_output.length > 0 - print ", " if required_output.length > 0 - print "Hash of optional output items" - end - puts "" + else + optional_input << value + end + end - puts "" + # MODIFY INPUT args count as OUTPUT as well + if (arg_flags & ARGUMENT_OUTPUT) != 0 || + ((arg_flags & ARGUMENT_INPUT) != 0 && + (arg_flags & ARGUMENT_MODIFY) != 0) + if (arg_flags & ARGUMENT_REQUIRED) != 0 + required_output << value + else + optional_output << value + end end - generate_class = lambda do |gtype, a| - nickname = Vips::nickname_find gtype + end - if nickname - begin - # can fail for abstract types - op = Vips::Operation.new nickname - rescue - end + print "# @!method " + print "self." unless member_x + print "#{nickname}(" + print required_input.map{|x| x[:name]}.join(", ") + print ", " if required_input.length > 0 + puts "opts = {})" - generate_operation.(gtype, nickname, op) if op - end + puts "# #{description.capitalize}." - Vips::vips_type_map gtype, generate_class, nil - end + required_input.each do |arg| + puts "# @param #{arg[:name]} [#{arg[:type_name]}] " + + "#{arg[:blurb]}" + end - puts "module Vips" - puts " class Image" - puts "" + puts "# @param opts [Hash] Set of options" + optional_input.each do |arg| + puts "# @option opts [#{arg[:type_name]}] :#{arg[:name]} " + + "#{arg[:blurb]}" + end + optional_output.each do |arg| + print "# @option opts [#{arg[:type_name]}] :#{arg[:name]}" + puts " Output #{arg[:blurb]}" + end - generate_class.(GObject::g_type_from_name("VipsOperation"), nil) + print "# @return [" + if required_output.length == 0 + print "nil" + elsif required_output.length == 1 + print required_output.first[:type_name] + elsif + print "Array<" + print required_output.map{|x| x[:type_name]}.join(", ") + print ">" + end + if optional_output.length > 0 + print ", Hash<Symbol => Object>" + end + print "] " + print required_output.map{|x| x[:blurb]}.join(", ") + if optional_output.length > 0 + print ", " if required_output.length > 0 + print "Hash of optional output items" + end + puts "" - puts " end" - puts "end" + puts "" end + + generate_class = lambda do |gtype, a| + nickname = Vips::nickname_find gtype + + if nickname + begin + # can fail for abstract types + op = Vips::Operation.new nickname + rescue + end + + generate_operation.(gtype, nickname, op) if op + end + + Vips::vips_type_map gtype, generate_class, nil + end + + puts "module Vips" + puts " class Image" + puts "" + + generate_class.(GObject::g_type_from_name("VipsOperation"), nil) + + puts " end" + puts "end" + end end