# 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