# This module provides an interface to the top level bits of libvips # via ruby-ffi. # # Author:: John Cupitt (mailto:jcupitt@gmail.com) # License:: MIT require "ffi" module Vips private # we must init these by hand, since they are usually made on first image # create attach_function :vips_band_format_get_type, [], :GType attach_function :vips_interpretation_get_type, [], :GType attach_function :vips_coding_get_type, [], :GType public # some handy gtypes IMAGE_TYPE = GObject.g_type_from_name "VipsImage" ARRAY_INT_TYPE = GObject.g_type_from_name "VipsArrayInt" ARRAY_DOUBLE_TYPE = GObject.g_type_from_name "VipsArrayDouble" ARRAY_IMAGE_TYPE = GObject.g_type_from_name "VipsArrayImage" REFSTR_TYPE = GObject.g_type_from_name "VipsRefString" BLOB_TYPE = GObject.g_type_from_name "VipsBlob" BAND_FORMAT_TYPE = Vips.vips_band_format_get_type INTERPRETATION_TYPE = Vips.vips_interpretation_get_type CODING_TYPE = Vips.vips_coding_get_type if Vips.at_least_libvips?(8, 6) attach_function :vips_blend_mode_get_type, [], :GType BLEND_MODE_TYPE = Vips.vips_blend_mode_get_type else BLEND_MODE_TYPE = nil end private class Progress < FFI::Struct layout :im, :pointer, :run, :int, :eta, :int, :tpels, :int64_t, :npels, :int64_t, :percent, :int, :start, :pointer end # Our signal marshalers. # # These are functions which take the handler as a param and return a # closure with the right FFI signature for g_signal_connect for this # specific signal. # # ruby-ffi makes it hard to use the g_signal_connect user data param # to pass the function pointer through, unfortunately. # # We can't throw exceptions across C, so we must catch everything. MARSHAL_PROGRESS = proc do |handler| FFI::Function.new(:void, [:pointer, :pointer, :pointer]) do |vi, prog, cb| # this can't throw an exception, so no catch is necessary handler.call(Progress.new(prog)) end end MARSHAL_READ = proc do |handler| FFI::Function.new(:int64_t, [:pointer, :pointer, :int64_t]) do |i, p, len| begin result = handler.call(p, len) rescue Exception => e puts "read: #{e}" result = 0 end result end end MARSHAL_SEEK = proc do |handler| FFI::Function.new(:int64_t, [:pointer, :int64_t, :int]) do |i, off, whence| begin result = handler.call(off, whence) rescue Exception => e puts "seek: #{e}" result = -1 end result end end MARSHAL_WRITE = proc do |handler| FFI::Function.new(:int64_t, [:pointer, :pointer, :int64_t]) do |i, p, len| begin result = handler.call(p, len) rescue Exception => e puts "write: #{e}" result = 0 end result end end MARSHAL_FINISH = proc do |handler| FFI::Function.new(:void, [:pointer, :pointer]) do |i, cb| # this can't throw an exception, so no catch is necessary handler.call end end # map signal name to marshal proc MARSHAL_ALL = { preeval: MARSHAL_PROGRESS, eval: MARSHAL_PROGRESS, posteval: MARSHAL_PROGRESS, read: MARSHAL_READ, seek: MARSHAL_SEEK, write: MARSHAL_WRITE, finish: MARSHAL_FINISH } attach_function :vips_enum_from_nick, [:string, :GType, :string], :int attach_function :vips_enum_nick, [:GType, :int], :string attach_function :vips_value_set_ref_string, [GObject::GValue.ptr, :string], :void attach_function :vips_value_set_array_double, [GObject::GValue.ptr, :pointer, :int], :void attach_function :vips_value_set_array_int, [GObject::GValue.ptr, :pointer, :int], :void attach_function :vips_value_set_array_image, [GObject::GValue.ptr, :int], :void callback :free_fn, [:pointer], :void attach_function :vips_value_set_blob, [GObject::GValue.ptr, :free_fn, :pointer, :size_t], :void class SizeStruct < FFI::Struct layout :value, :size_t end class IntStruct < FFI::Struct layout :value, :int end attach_function :vips_value_get_ref_string, [GObject::GValue.ptr, SizeStruct.ptr], :string attach_function :vips_value_get_array_double, [GObject::GValue.ptr, IntStruct.ptr], :pointer attach_function :vips_value_get_array_int, [GObject::GValue.ptr, IntStruct.ptr], :pointer attach_function :vips_value_get_array_image, [GObject::GValue.ptr, IntStruct.ptr], :pointer attach_function :vips_value_get_blob, [GObject::GValue.ptr, SizeStruct.ptr], :pointer attach_function :type_find, :vips_type_find, [:string, :string], :GType class Object < GObject::GObject # print all active VipsObjects, with their reference counts. Handy for # debugging ruby-vips. def self.print_all GC.start Vips.vips_object_print_all end # the layout of the VipsObject struct module ObjectLayout def self.included base base.class_eval do # don't actually need most of these layout :parent, GObject::GObject::Struct, :constructed, :int, :static_object, :int, :argument_table, :pointer, :nickname, :string, :description, :string, :preclose, :int, :close, :int, :postclose, :int, :local_memory, :size_t end end end class Struct < GObject::GObject::Struct include ObjectLayout end class ManagedStruct < GObject::GObject::ManagedStruct include ObjectLayout end # return a pspec, or nil ... nil wil leave a message in the error log # which you must clear def get_pspec name ppspec = GObject::GParamSpecPtr.new argument_class = Vips::ArgumentClassPtr.new argument_instance = Vips::ArgumentInstancePtr.new result = Vips.vips_object_get_argument self, name, ppspec, argument_class, argument_instance return nil if result != 0 ppspec[:value] end # return a gtype, raise an error on not found def get_typeof_error name pspec = get_pspec name raise Vips::Error unless pspec pspec[:value_type] end # return a gtype, 0 on not found def get_typeof name pspec = get_pspec name unless pspec Vips.vips_error_clear return 0 end pspec[:value_type] end def get name gtype = get_typeof_error name gvalue = GObject::GValue.alloc gvalue.init gtype GObject.g_object_get_property self, name, gvalue result = gvalue.get gvalue.unset GLib.logger.debug("Vips::Object.get") { "#{name} == #{result}" } result end def set name, value GLib.logger.debug("Vips::Object.set") { "#{name} = #{value}" } gtype = get_typeof_error name gvalue = GObject::GValue.alloc gvalue.init gtype gvalue.set value GObject.g_object_set_property self, name, gvalue gvalue.unset end def signal_connect name, handler = nil, &block marshal = MARSHAL_ALL[name.to_sym] raise Vips::Error, "unsupported signal #{name}" if marshal.nil? if block # our block as a Proc prc = block elsif handler # We assume the hander is a Proc (perhaps we should test) prc = handler else raise Vips::Error, "must supply either block or handler" end # The marshal function will make a closure with the right type signature # for the selected signal callback = marshal.call(prc) # we need to make sure this is not GCd while self is alive @references << callback GObject.g_signal_connect_data(self, name.to_s, callback, nil, nil, 0) end end class ObjectClass < FFI::Struct # opaque end class Argument < FFI::Struct layout :pspec, GObject::GParamSpec.ptr end class ArgumentInstance < Argument layout :parent, Argument # rest opaque end # enum VipsArgumentFlags ARGUMENT_REQUIRED = 1 ARGUMENT_CONSTRUCT = 2 ARGUMENT_SET_ONCE = 4 ARGUMENT_SET_ALWAYS = 8 ARGUMENT_INPUT = 16 ARGUMENT_OUTPUT = 32 ARGUMENT_DEPRECATED = 64 ARGUMENT_MODIFY = 128 ARGUMENT_FLAGS = { required: ARGUMENT_REQUIRED, construct: ARGUMENT_CONSTRUCT, set_once: ARGUMENT_SET_ONCE, set_always: ARGUMENT_SET_ALWAYS, input: ARGUMENT_INPUT, output: ARGUMENT_OUTPUT, deprecated: ARGUMENT_DEPRECATED, modify: ARGUMENT_MODIFY } class ArgumentClass < Argument layout :parent, Argument, :object_class, ObjectClass.ptr, :flags, :uint, :priority, :int, :offset, :ulong_long end class ArgumentClassPtr < FFI::Struct layout :value, ArgumentClass.ptr end class ArgumentInstancePtr < FFI::Struct layout :value, ArgumentInstance.ptr end # just use :pointer, not VipsObject.ptr, to avoid casting gobject # subclasses attach_function :vips_object_get_argument, [:pointer, :string, GObject::GParamSpecPtr.ptr, ArgumentClassPtr.ptr, ArgumentInstancePtr.ptr], :int # debugging support attach_function :vips_object_print_all, [], :void attach_function :vips_object_set_from_string, [:pointer, :string], :int callback :type_map_fn, [:GType, :pointer], :pointer attach_function :vips_type_map, [:GType, :type_map_fn, :pointer], :pointer attach_function :vips_object_get_description, [:pointer], :string end