Module: SafeImage::VipsGlue

Defined in:
lib/safe_image/vips_glue.rb

Overview

Minimal Fiddle binding for libvips. Instead of libvips’ variadic C convenience API, operations are invoked through the fixed-signature GObject layer: vips_operation_new -> set properties as GValues -> vips_cache_operation_build -> read outputs -> unref. The invocation pattern is modelled on ruby-vips (MIT, Copyright © 2016 John Cupitt, github.com/libvips/ruby-vips), trimmed to exactly the operations SafeImage::Native performs — the function table below doubles as an operation allowlist.

Constant Summary collapse

GVALUE_SIZE =
24
GVALUE_ZERO =
("\0" * GVALUE_SIZE).freeze
PSPEC_VALUE_TYPE_OFFSET =

Public, decades-stable GParamSpec ABI: GTypeInstance(8) + name(8) + flags(4 + padding) puts value_type at byte 24.

24
LIBRARY_CANDIDATES =
%w[libvips.so.42 libvips.42.dylib libvips.dylib libvips.so].freeze
TYPE =
{
  void: Fiddle::TYPE_VOID,
  int: Fiddle::TYPE_INT,
  double: Fiddle::TYPE_DOUBLE,
  size_t: Fiddle::TYPE_SIZE_T,
  ptr: Fiddle::TYPE_VOIDP
}.freeze
SIGNATURES =

Fixed-signature entry points only; no varargs anywhere. The g_* symbols resolve through the libvips handle via its GLib dependency.

{
  vips_init: [%i[ptr], :int],
  vips_version: [%i[int], :int],
  vips_error_buffer: [[], :ptr],
  vips_error_clear: [[], :void],
  vips_block_untrusted_set: [%i[int], :void],
  vips_operation_block_set: [%i[ptr int], :void],
  vips_concurrency_set: [%i[int], :void],
  vips_cache_set_max: [%i[int], :void],
  vips_cache_set_max_mem: [%i[size_t], :void],
  vips_cache_set_max_files: [%i[int], :void],
  vips_type_find: [%i[ptr ptr], :size_t],
  vips_enum_from_nick: [%i[ptr size_t ptr], :int],
  vips_operation_new: [%i[ptr], :ptr],
  vips_cache_operation_build: [%i[ptr], :ptr],
  vips_object_unref_outputs: [%i[ptr], :void],
  vips_value_set_array_double: [%i[ptr ptr int], :void],
  vips_image_get_width: [%i[ptr], :int],
  vips_image_get_height: [%i[ptr], :int],
  vips_image_get_bands: [%i[ptr], :int],
  vips_image_get_n_pages: [%i[ptr], :int],
  vips_image_get_orientation: [%i[ptr], :int],
  vips_image_hasalpha: [%i[ptr], :int],
  vips_colourspace_issupported: [%i[ptr], :int],
  vips_image_new_from_memory_copy: [%i[ptr size_t int int int int], :ptr],
  vips_image_write_to_memory: [%i[ptr ptr], :ptr],
  g_object_ref: [%i[ptr], :ptr],
  g_object_unref: [%i[ptr], :void],
  g_object_set_property: [%i[ptr ptr ptr], :void],
  g_object_get_property: [%i[ptr ptr ptr], :void],
  g_object_class_find_property: [%i[ptr ptr], :ptr],
  g_value_init: [%i[ptr size_t], :ptr],
  g_value_unset: [%i[ptr], :void],
  g_value_set_boolean: [%i[ptr int], :void],
  g_value_set_int: [%i[ptr int], :void],
  g_value_set_double: [%i[ptr double], :void],
  g_value_set_string: [%i[ptr ptr], :void],
  g_value_set_enum: [%i[ptr int], :void],
  g_value_set_flags: [%i[ptr int], :void],
  g_value_set_object: [%i[ptr ptr], :void],
  g_value_get_object: [%i[ptr], :ptr],
  g_type_fundamental: [%i[size_t], :size_t],
  g_type_from_name: [%i[ptr], :size_t],
  g_free: [%i[ptr], :void]
}.freeze

Class Method Summary collapse

Class Method Details

.alpha?(image_ptr) ⇒ Boolean

Returns:

  • (Boolean)


201
# File 'lib/safe_image/vips_glue.rb', line 201

def alpha?(image_ptr) = !c(:vips_image_hasalpha, image_ptr).zero?

.available?Boolean

True when libvips loaded (or loads) successfully. A load failure is memoized; the gem keeps working through the ImageMagick paths.

Returns:

  • (Boolean)


119
120
121
122
123
124
# File 'lib/safe_image/vips_glue.rb', line 119

def available?
  init!
  true
rescue VipsUnavailableError
  false
end

.bands(image_ptr) ⇒ Object



198
# File 'lib/safe_image/vips_glue.rb', line 198

def bands(image_ptr) = c(:vips_image_get_bands, image_ptr)

.c(name, *args) ⇒ Object

Calls a bound C function by name.



127
128
129
# File 'lib/safe_image/vips_glue.rb', line 127

def c(name, *args)
  @functions.fetch(name).call(*args)
end

.colourspace_supported?(image_ptr) ⇒ Boolean

Returns:

  • (Boolean)


202
# File 'lib/safe_image/vips_glue.rb', line 202

def colourspace_supported?(image_ptr) = !c(:vips_colourspace_issupported, image_ptr).zero?

.error!Object

Raises:



131
132
133
134
135
# File 'lib/safe_image/vips_glue.rb', line 131

def error!
  message = error_message
  c(:vips_error_clear)
  raise InvalidImageError, message
end

.error_messageObject



137
138
139
140
141
# File 'lib/safe_image/vips_glue.rb', line 137

def error_message
  ptr = c(:vips_error_buffer)
  message = ptr.null? ? "" : ptr.to_s
  message.empty? ? "libvips error" : message.strip
end

.height(image_ptr) ⇒ Object



197
# File 'lib/safe_image/vips_glue.rb', line 197

def height(image_ptr) = c(:vips_image_get_height, image_ptr)

.image_bytes(image_ptr) ⇒ Object

Copies the image’s pixel data out as a binary string (used to read the tiny vips_stats matrix without binding the variadic getpoint).



213
214
215
216
217
218
219
220
221
222
# File 'lib/safe_image/vips_glue.rb', line 213

def image_bytes(image_ptr)
  size_out = Fiddle::Pointer.malloc(Fiddle::SIZEOF_SIZE_T, Fiddle::RUBY_FREE)
  buffer = c(:vips_image_write_to_memory, image_ptr, size_out)
  error! if buffer.null?
  begin
    buffer[0, size_out[0, Fiddle::SIZEOF_SIZE_T].unpack1("J")]
  ensure
    c(:g_free, buffer)
  end
end

.image_from_memory(bytes, width, height, bands, format_number) ⇒ Object



204
205
206
207
208
209
# File 'lib/safe_image/vips_glue.rb', line 204

def image_from_memory(bytes, width, height, bands, format_number)
  init!
  ptr = c(:vips_image_new_from_memory_copy, bytes, bytes.bytesize, width, height, bands, format_number)
  error! if ptr.null?
  ptr
end

.init!Object

Raises:

  • (@load_error)


84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/safe_image/vips_glue.rb', line 84

def init!
  return if @initialized
  raise @load_error if @load_error

  @init_mutex.synchronize do
    next if @initialized
    raise @load_error if @load_error

    handle = open_library
    @functions = SIGNATURES.to_h do |name, (args, ret)|
      address = handle[name.to_s]
      [name, Fiddle::Function.new(address, args.map { |t| TYPE.fetch(t) }, TYPE.fetch(ret))]
    end

    silence_vips_log!
    raise Error, "vips_init failed: #{error_message}" if c(:vips_init, "safe_image") != 0

    major = c(:vips_version, 0)
    minor = c(:vips_version, 1)
    @version = [major, minor]
    raise Error, "libvips >= 8.13 is required (found #{major}.#{minor})" if (@version <=> [8, 13]).negative?

    harden!
    resolve_gtypes!
    @initialized = true
  end
end

.operation(nickname, inputs, output: "out") ⇒ Object

Invokes one vips operation. Property values are converted according to the property’s GType: booleans, ints, doubles, strings, enums (given as nick strings), flags, double arrays and VipsImage pointers. Returns the named output image pointer (caller owns one reference), or nil when output is nil (savers).



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/safe_image/vips_glue.rb', line 172

def operation(nickname, inputs, output: "out")
  op = c(:vips_operation_new, nickname)
  raise UnsupportedFormatError, "unknown vips operation: #{nickname}" if op.null?

  begin
    inputs.each { |name, value| set_property(op, name.to_s, value) }

    built = c(:vips_cache_operation_build, op)
    if built.null?
      c(:vips_object_unref_outputs, op)
      error!
    end

    begin
      output ? image_output(built, output) : nil
    ensure
      c(:vips_object_unref_outputs, built)
      c(:g_object_unref, built)
    end
  ensure
    c(:g_object_unref, op)
  end
end

.orientation(image_ptr) ⇒ Object



200
# File 'lib/safe_image/vips_glue.rb', line 200

def orientation(image_ptr) = c(:vips_image_get_orientation, image_ptr)

.pages(image_ptr) ⇒ Object



199
# File 'lib/safe_image/vips_glue.rb', line 199

def pages(image_ptr) = c(:vips_image_get_n_pages, image_ptr)

.type_find?(nickname) ⇒ Boolean

Returns:

  • (Boolean)


143
144
145
146
# File 'lib/safe_image/vips_glue.rb', line 143

def type_find?(nickname)
  init!
  !c(:vips_type_find, "VipsOperation", nickname).zero?
end

.unref(image_ptr) ⇒ Object



148
149
150
# File 'lib/safe_image/vips_glue.rb', line 148

def unref(image_ptr)
  c(:g_object_unref, image_ptr) if image_ptr && !image_ptr.null?
end

.versionObject



112
113
114
115
# File 'lib/safe_image/vips_glue.rb', line 112

def version
  init!
  @version
end

.width(image_ptr) ⇒ Object



196
# File 'lib/safe_image/vips_glue.rb', line 196

def width(image_ptr) = c(:vips_image_get_width, image_ptr)

.with_imagesObject

Tracks every acquired VipsImage pointer and releases all of them when the block exits, success or failure. Pipelines are strictly linear, so deterministic unref in reverse order is sufficient.



155
156
157
158
159
160
161
162
163
164
165
# File 'lib/safe_image/vips_glue.rb', line 155

def with_images
  acquired = []
  track = lambda do |ptr|
    acquired << ptr
    ptr
  end
  init!
  yield track
ensure
  acquired.reverse_each { |ptr| unref(ptr) }
end