lib/vips/image.rb in vips-8.8.4 vs lib/vips/image.rb in vips-8.9.1

- old
+ new

@@ -12,49 +12,53 @@ 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_set_progress, [:pointer, :bool], :void + attach_function :vips_image_set_kill, [:pointer, :bool], :void + 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 + if Vips::at_least_libvips?(8, 9) + attach_function :vips_foreign_find_load_source, [:pointer], :string + attach_function :vips_foreign_find_save_target, [:string], :string + end + 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 - # vips_image_get_fields was added in libvips 8.5 - begin + if Vips::at_least_libvips?(8, 5) attach_function :vips_image_get_fields, [:pointer], :pointer - rescue FFI::NotFoundError - nil + 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_band_format_iscomplex, [:int], :int attach_function :vips_band_format_isfloat, [:int], :int attach_function :nickname_find, :vips_nickname_find, [:GType], :string + attach_function :vips_image_invalidate_all, [:pointer], :void + # 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 @@ -65,10 +69,14 @@ # for an introduction to using this class. class Image < Vips::Object alias_method :parent_get_typeof, :get_typeof + def close + Vips.vips_image_invalidate_all(self) + end + private # the layout of the VipsImage struct module ImageLayout def self.included base @@ -113,11 +121,11 @@ def self.run_cmplx image, &block original_format = image.format unless Image::complex? image.format if image.bands % 2 != 0 - raise Error, "not an even number of bands" + raise Vips::Error, "not an even number of bands" end unless Image::float? image.format image = image.cast :float end @@ -143,38 +151,10 @@ else Vips::Operation.call name.to_s + "_const", [self, enum, other] end 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. - - @@generational_gc = RUBY_ENGINE == "ruby" && RUBY_VERSION.to_f >= 2.1 - - @@gc_interval = 100 - @@gc_countdown = @@gc_interval - - 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 - public def inspect "#<Image #{width}x#{height} #{format}, #{bands} bands, #{interpretation}>" end @@ -217,17 +197,17 @@ # 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]" + # image = Vips::Image.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 + # image = Vips::Image.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: # @@ -294,15 +274,54 @@ # @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 + raise Vips::Error if loader.nil? Vips::Operation.call loader, [data], opts, option_string end + # Create a new {Image} from a source. Load options may be passed as + # strings or appended as a hash. For example: + # + # ``` + # source = Vips::Source.new_from_file("k2.jpg") + # image = Vips::Image.new_from_source source, "shrink=2" + # ``` + # + # or alternatively: + # + # ``` + # image = Vips::Image.new_from_source source, "", shrink: 2 + # ``` + # + # The options available depend on the file format. Try something like: + # + # ``` + # $ vips jpegload_source + # ``` + # + # at the command-line to see the available options. Not all loaders + # support load from source, but at least JPEG, PNG and + # TIFF images will work. + # + # Loading is fast: only enough data is read to be able to fill + # out the header. Pixels will only be read and decompressed when they are + # needed. + # + # @param source [Vips::Source] the source to load from + # @param option_string [String] load options as a string + # @macro vips.loadopts + # @return [Image] the loaded image + def self.new_from_source source, option_string, **opts + loader = Vips::vips_foreign_find_load_source source + raise Vips::Error if loader.nil? + + Vips::Operation.call loader, [source], opts, option_string + 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 @@ -315,17 +334,17 @@ # convolutions. # # For example: # # ``` - # image = Vips::new_from_array [1, 2, 3] + # image = Vips::Image.new_from_array [1, 2, 3] # ``` # # or # # ``` - # image = Vips::new_from_array [ + # image = Vips::Image.new_from_array [ # [-1, -1, -1], # [-1, 16, -1], # [-1, -1, -1]], 8 # ``` # @@ -424,12 +443,10 @@ if saver == nil raise Vips::Error, "No known saver for '#{filename}'." end Vips::Operation.call saver, [self, filename], opts, option_string - - write_gc end # Write this image to a memory buffer. Save options may be encoded in # the format_string or given as a hash. For example: # @@ -458,21 +475,56 @@ 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}'." + raise Vips::Error, "No known buffer saver for '#{filename}'." end buffer = Vips::Operation.call saver, [self], opts, option_string raise Vips::Error if buffer == nil - write_gc - return buffer end + # Write this image to a target. Save options may be encoded in + # the format_string or given as a hash. For example: + # + # ```ruby + # target = Vips::Target.new_to_file "k2.jpg" + # image.write_to_target target, ".jpg[Q=90]" + # ``` + # + # or equivalently: + # + # ```ruby + # image.write_to_target target, ".jpg", Q: 90 + # ``` + # + # The full set of save options depend on the selected saver. Try + # something like: + # + # ``` + # $ vips jpegsave_target + # ``` + # + # to see all the available options for JPEG save. + # + # @param target [Vips::Target] the target to write to + # @param format_string [String] save format plus string options + # @macro vips.saveopts + def write_to_target target, 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_target filename + if saver == nil + raise Vips::Error, "No known target saver for '#{filename}'." + end + + Vips::Operation.call saver, [self, target], opts, option_string + 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 @@ -483,10 +535,32 @@ ptr = FFI::AutoPointer.new(ptr, GLib::G_FREE) ptr.get_bytes 0, len[:value] end + # Turn progress signalling on and off. + # + # If this is on, the most-downstream image from this image will issue + # progress signals. + # + # @see Object#signal_connect + # @param state [Boolean] progress signalling state + def set_progress state + Vips::vips_image_set_progress self, state + end + + # Kill computation of this time. + # + # Set true to stop computation of this image. You can call this from a + # progress handler, for example. + # + # @see Object#signal_connect + # @param kill [Boolean] stop computation + def set_kill kill + Vips::vips_image_set_kill self, kill + 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 @@ -521,14 +595,15 @@ unless Vips::at_least_libvips?(8, 5) return super if parent_get_typeof(name) != 0 end gvalue = GObject::GValue.alloc - result = Vips::vips_image_get self, name, gvalue - raise Vips::Error if result != 0 + raise Vips::Error if Vips::vips_image_get(self, name, gvalue) != 0 + result = gvalue.get + gvalue.unset - gvalue.get + result end # Get the names of all fields on an image. Use this to loop over all # image metadata. # @@ -569,10 +644,11 @@ def set_type gtype, name, value gvalue = GObject::GValue.alloc gvalue.init gtype gvalue.set value Vips::vips_image_set self, name, gvalue + gvalue.unset 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. @@ -1057,11 +1133,11 @@ # @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 + def composite overlay, mode, **options unless overlay.is_a? Array overlay = [overlay] end unless mode.is_a? Array mode = [mode] @@ -1069,11 +1145,11 @@ mode = mode.map do |x| GObject::GValue.from_nick Vips::BLEND_MODE_TYPE, x end - Vips::Image.composite([self] + overlay, mode, opts) + Vips::Image.composite([self] + overlay, mode, **options) end # Return the coordinates of the image maximum. # # @return [Real, Real, Real] maximum value, x coordinate of maximum, y @@ -1318,182 +1394,174 @@ # 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 + def scaleimage **options + Vips::Image.scale self, **options end end end module Vips - # This method generates yard comments for all the dynamically bound + # This module generates yard comments for all the dynamically bound # vips operations. # # Regenerate with something like: # # ``` # $ ruby > methods.rb - # require 'vips'; Vips::generate_yard + # require 'vips'; Vips::Yard.generate # ^D # ``` - def self.generate_yard - # these have hand-written methods, see above - no_generate = ["scale", "bandjoin", "composite", "ifthenelse"] - + module Yard # map gobject's type names to Ruby - map_go_to_ruby = { + MAP_GO_TO_RUBY = { "gboolean" => "Boolean", "gint" => "Integer", "gdouble" => "Float", "gfloat" => "Float", "gchararray" => "String", "VipsImage" => "Vips::Image", "VipsInterpolate" => "Vips::Interpolate", + "VipsConnection" => "Vips::Connection", + "VipsSource" => "Vips::Source", + "VipsTarget" => "Vips::Target", + "VipsSourceCustom" => "Vips::SourceCustom", + "VipsTargetCustom" => "Vips::TargetCustom", "VipsArrayDouble" => "Array<Double>", "VipsArrayInt" => "Array<Integer>", "VipsArrayImage" => "Array<Image>", "VipsArrayString" => "Array<String>", } - 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 + # these have hand-written methods, see above + NO_GENERATE = ["scale", "bandjoin", "composite", "ifthenelse"] - description = Vips::vips_object_get_description op + # these are aliased (appear under several names) + ALIAS = ["crop"] - # 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 + # turn a gtype into a ruby type name + def self.gtype_to_ruby gtype + fundamental = GObject::g_type_fundamental gtype + type_name = GObject::g_type_name gtype - 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 - } + if MAP_GO_TO_RUBY.include? type_name + type_name = MAP_GO_TO_RUBY[type_name] + 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 - - # 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 + if fundamental == GObject::GFLAGS_TYPE || + fundamental == GObject::GENUM_TYPE + type_name = "Vips::" + type_name[/Vips(.*)/, 1] end + type_name + end + + def self.generate_operation introspect + return if (introspect.flags & OPERATION_DEPRECATED) != 0 + return if NO_GENERATE.include? introspect.name + + method_args = introspect.method_args + required_output = introspect.required_output + optional_input = introspect.optional_input + optional_output = introspect.optional_output + print "# @!method " - print "self." unless member_x - print "#{nickname}(" - print required_input.map { |x| x[:name] }.join(", ") - print ", " if required_input.length > 0 + print "self." unless introspect.member_x + print "#{introspect.name}(" + print method_args.map{ |x| x[:yard_name] }.join(", ") + print ", " if method_args.length > 0 puts "**opts)" - puts "# #{description.capitalize}." + puts "# #{introspect.description.capitalize}." - required_input.each do |arg| - puts "# @param #{arg[:name]} [#{arg[:type_name]}] #{arg[:blurb]}" + method_args.each do |details| + yard_name = details[:yard_name] + gtype = details[:gtype] + blurb = details[:blurb] + + puts "# @param #{yard_name} [#{gtype_to_ruby(gtype)}] #{blurb}" end puts "# @param opts [Hash] Set of options" - optional_input.each do |arg| - puts "# @option opts [#{arg[:type_name]}] :#{arg[:name]} " + - "#{arg[:blurb]}" + optional_input.each do |arg_name, details| + yard_name = details[:yard_name] + gtype = details[:gtype] + blurb = details[:blurb] + + puts "# @option opts [#{gtype_to_ruby(gtype)}] :#{yard_name} " + + "#{blurb}" end - optional_output.each do |arg| - print "# @option opts [#{arg[:type_name]}] :#{arg[:name]}" - puts " Output #{arg[:blurb]}" + optional_output.each do |arg_name, details| + yard_name = details[:yard_name] + gtype = details[:gtype] + blurb = details[:blurb] + + print "# @option opts [#{gtype_to_ruby(gtype)}] :#{yard_name}" + puts " Output #{blurb}" end print "# @return [" if required_output.length == 0 print "nil" elsif required_output.length == 1 - print required_output.first[:type_name] + print gtype_to_ruby(required_output.first[:gtype]) else print "Array<" - print required_output.map { |x| x[:type_name] }.join(", ") + print required_output.map{ |x| gtype_to_ruby(x[:gtype]) }.join(", ") print ">" end if optional_output.length > 0 print ", Hash<Symbol => Object>" end print "] " - print required_output.map { |x| x[:blurb] }.join(", ") + 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 - generate_class = lambda do |gtype, _| - nickname = Vips::nickname_find gtype + def self.generate + alias_gtypes = {} + ALIAS.each do |name| + gtype = Vips::type_find "VipsOperation", name + alias_gtypes[gtype] = name + end - if nickname - begin - # can fail for abstract types - op = Vips::Operation.new nickname - rescue Vips::Error - nil + generate_class = lambda do |gtype, _| + if alias_gtypes.key? gtype + name = alias_gtypes[gtype] + else + name = Vips::nickname_find gtype end - generate_operation.(gtype, nickname, op) if op - end + if name + begin + # can fail for abstract types + introspect = Vips::Introspect.get_yard name + rescue Vips::Error + nil + end - Vips::vips_type_map gtype, generate_class, nil - end + generate_operation(introspect) if introspect + end - puts "module Vips" - puts " class Image" - puts "" + Vips::vips_type_map gtype, generate_class, nil + end - generate_class.(GObject::g_type_from_name("VipsOperation"), nil) + puts "module Vips" + puts " class Image" + puts "" - puts " end" - puts "end" + generate_class.(GObject::g_type_from_name("VipsOperation"), nil) + + puts " end" + puts "end" + end end end