vips 8.10.5 → 8.12.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,180 @@
1
+ # This module provides an interface to the vips image processing library
2
+ # via ruby-ffi.
3
+ #
4
+ # Author:: John Cupitt (mailto:jcupitt@gmail.com)
5
+ # License:: MIT
6
+
7
+ require "ffi"
8
+ require "forwardable"
9
+
10
+ module Vips
11
+ # This class represents a libvips image which can be modified. See
12
+ # {Vips::Image#mutate}.
13
+ class MutableImage < Vips::Object
14
+ extend Forwardable
15
+ alias_method :parent_get_typeof, :get_typeof
16
+ def_instance_delegators :@image, :width, :height, :bands, :format,
17
+ :interpretation, :filename, :xoffset, :yoffset, :xres, :yres, :size,
18
+ :get, :get_typeof, :get_fields
19
+
20
+ # layout is exactly as {Image} (since we are also wrapping a VipsImage
21
+ # object)
22
+ module MutableImageLayout
23
+ def self.included base
24
+ base.class_eval do
25
+ layout :parent, Vips::Object::Struct
26
+ # rest opaque
27
+ end
28
+ end
29
+ end
30
+
31
+ class Struct < Vips::Object::Struct
32
+ include MutableImageLayout
33
+ end
34
+
35
+ class ManagedStruct < Vips::Object::ManagedStruct
36
+ include MutableImageLayout
37
+ end
38
+
39
+ # Get the {Image} this {MutableImage} is modifying. Only use this once you
40
+ # have finished all modifications.
41
+ #
42
+ # This is for internal use only. See {Vips::Image#mutate} for the
43
+ # user-facing interface.
44
+ attr_reader :image
45
+
46
+ # Make a {MutableImage} from a regular {Image}.
47
+ #
48
+ # This is for internal use only. See {Vips::Image#mutate} for the
49
+ # user-facing interface.
50
+ def initialize(image)
51
+ # We take a copy of the regular Image to ensure we have an unshared
52
+ # (unique) object. We forward things like #width and #height to this, and
53
+ # it's the thing we return at the end of the mutate block.
54
+ copy_image = image.copy
55
+
56
+ # Use ptr since we need the raw unwrapped pointer inside the image ...
57
+ # and make the ref that gobject will unref when it finishes.
58
+ # See also the comment on set_type! before changing this.
59
+ pointer = copy_image.ptr
60
+ ::GObject.g_object_ref pointer
61
+ super pointer
62
+
63
+ # and save the copy ready for when we finish mutating
64
+ @image = copy_image
65
+ end
66
+
67
+ def inspect
68
+ "#<MutableImage #{width}x#{height} #{format}, #{bands} bands, #{interpretation}>"
69
+ end
70
+
71
+ def respond_to? name, include_all = false
72
+ # To support keyword args, we need to tell Ruby that final image
73
+ # arguments cannot be hashes of keywords.
74
+ #
75
+ # https://makandracards.com/makandra/
76
+ # 36013-heads-up-ruby-implicitly-converts-a-hash-to-keyword-arguments
77
+ return false if name == :to_hash
78
+
79
+ super
80
+ end
81
+
82
+ def respond_to_missing? name, include_all = false
83
+ # Respond to all vips operations by nickname.
84
+ return true if Vips.type_find("VipsOperation", name.to_s) != 0
85
+
86
+ super
87
+ end
88
+
89
+ # Invoke a vips operation with {Vips::Operation#call}, using self as
90
+ # the first input argument. {Vips::Operation#call} will only allow
91
+ # operations that modify self when passed a {MutableImage}.
92
+ #
93
+ # @param name [String] vips operation to call
94
+ # @return result of vips operation
95
+ def method_missing name, *args, **options
96
+ Vips::Operation.call name.to_s, [self, *args], options
97
+ end
98
+
99
+ # Draw a point on an image.
100
+ #
101
+ # See {Image#draw_rect}.
102
+ def draw_point! ink, left, top, **opts
103
+ draw_rect! ink, left, top, 1, 1, **opts
104
+ end
105
+
106
+ # Create a metadata item on an image of the specifed type. Ruby types
107
+ # are automatically transformed into the matching glib type (eg.
108
+ # {GObject::GINT_TYPE}), if possible.
109
+ #
110
+ # For example, you can use this to set an image's ICC profile:
111
+ #
112
+ # ```ruby
113
+ # x.set_type! Vips::BLOB_TYPE, "icc-profile-data", profile
114
+ # ```
115
+ #
116
+ # where `profile` is an ICC profile held as a binary string object.
117
+ #
118
+ # @see set!
119
+ # @param gtype [Integer] GType of item
120
+ # @param name [String] Metadata field to set
121
+ # @param value [Object] Value to set
122
+ def set_type! gtype, name, value
123
+ gvalue = GObject::GValue.alloc
124
+ gvalue.init gtype
125
+ gvalue.set value
126
+
127
+ # libvips 8.9.1 had a terrible misfeature which would block metadata
128
+ # modification unless the object had a ref_count of 1. MutableImage
129
+ # will always have a ref_count of at least 2 (the parent gobject keeps a
130
+ # ref, and we keep a ref to the copy ready to return to our caller),
131
+ # so we must temporarily drop the refs to 1 around metadata changes.
132
+ #
133
+ # See https://github.com/libvips/ruby-vips/issues/291
134
+ begin
135
+ ::GObject.g_object_unref ptr
136
+ Vips.vips_image_set self, name, gvalue
137
+ ensure
138
+ ::GObject.g_object_ref ptr
139
+ end
140
+
141
+ gvalue.unset
142
+ end
143
+
144
+ # Set the value of a metadata item on an image. The metadata item must
145
+ # already exist. Ruby types are automatically transformed into the
146
+ # matching {GObject::GValue}, if possible.
147
+ #
148
+ # For example, you can use this to set an image's ICC profile:
149
+ #
150
+ # ```
151
+ # x.set! "icc-profile-data", profile
152
+ # ```
153
+ #
154
+ # where `profile` is an ICC profile held as a binary string object.
155
+ #
156
+ # @see set_type!
157
+ # @param name [String] Metadata field to set
158
+ # @param value [Object] Value to set
159
+ def set! name, value
160
+ set_type! get_typeof(name), name, value
161
+ end
162
+
163
+ # Remove a metadata item from an image.
164
+ #
165
+ # For example:
166
+ #
167
+ # ```
168
+ # x.remove! "icc-profile-data"
169
+ # ```
170
+ #
171
+ # @param name [String] Metadata field to remove
172
+ def remove! name
173
+ # See set_type! for an explanation. Image#remove can't throw an
174
+ # exception, so there's no need to ensure we unref.
175
+ ::GObject.g_object_unref ptr
176
+ Vips.vips_image_remove self, name
177
+ ::GObject.g_object_ref ptr
178
+ end
179
+ end
180
+ end
data/lib/vips/object.rb CHANGED
@@ -4,7 +4,7 @@
4
4
  # Author:: John Cupitt (mailto:jcupitt@gmail.com)
5
5
  # License:: MIT
6
6
 
7
- require 'ffi'
7
+ require "ffi"
8
8
 
9
9
  module Vips
10
10
  private
@@ -21,20 +21,20 @@ module Vips
21
21
  public
22
22
 
23
23
  # some handy gtypes
24
- IMAGE_TYPE = GObject::g_type_from_name "VipsImage"
25
- ARRAY_INT_TYPE = GObject::g_type_from_name "VipsArrayInt"
26
- ARRAY_DOUBLE_TYPE = GObject::g_type_from_name "VipsArrayDouble"
27
- ARRAY_IMAGE_TYPE = GObject::g_type_from_name "VipsArrayImage"
28
- REFSTR_TYPE = GObject::g_type_from_name "VipsRefString"
29
- BLOB_TYPE = GObject::g_type_from_name "VipsBlob"
30
-
31
- BAND_FORMAT_TYPE = Vips::vips_band_format_get_type
32
- INTERPRETATION_TYPE = Vips::vips_interpretation_get_type
33
- CODING_TYPE = Vips::vips_coding_get_type
34
-
35
- if Vips::at_least_libvips?(8, 6)
24
+ IMAGE_TYPE = GObject.g_type_from_name "VipsImage"
25
+ ARRAY_INT_TYPE = GObject.g_type_from_name "VipsArrayInt"
26
+ ARRAY_DOUBLE_TYPE = GObject.g_type_from_name "VipsArrayDouble"
27
+ ARRAY_IMAGE_TYPE = GObject.g_type_from_name "VipsArrayImage"
28
+ REFSTR_TYPE = GObject.g_type_from_name "VipsRefString"
29
+ BLOB_TYPE = GObject.g_type_from_name "VipsBlob"
30
+
31
+ BAND_FORMAT_TYPE = Vips.vips_band_format_get_type
32
+ INTERPRETATION_TYPE = Vips.vips_interpretation_get_type
33
+ CODING_TYPE = Vips.vips_coding_get_type
34
+
35
+ if Vips.at_least_libvips?(8, 6)
36
36
  attach_function :vips_blend_mode_get_type, [], :GType
37
- BLEND_MODE_TYPE = Vips::vips_blend_mode_get_type
37
+ BLEND_MODE_TYPE = Vips.vips_blend_mode_get_type
38
38
  else
39
39
  BLEND_MODE_TYPE = nil
40
40
  end
@@ -45,37 +45,34 @@ module Vips
45
45
  layout :im, :pointer,
46
46
  :run, :int,
47
47
  :eta, :int,
48
- :tpels, :int64_t,
49
- :npels, :int64_t,
50
- :percent, :int,
51
- :start, :pointer
48
+ :tpels, :int64_t,
49
+ :npels, :int64_t,
50
+ :percent, :int,
51
+ :start, :pointer
52
52
  end
53
53
 
54
- # Our signal marshalers.
54
+ # Our signal marshalers.
55
55
  #
56
- # These are functions which take the handler as a param and return a
56
+ # These are functions which take the handler as a param and return a
57
57
  # closure with the right FFI signature for g_signal_connect for this
58
- # specific signal.
58
+ # specific signal.
59
59
  #
60
- # ruby-ffi makes it hard to use the g_signal_connect user data param
60
+ # ruby-ffi makes it hard to use the g_signal_connect user data param
61
61
  # to pass the function pointer through, unfortunately.
62
62
  #
63
63
  # We can't throw exceptions across C, so we must catch everything.
64
64
 
65
- MARSHAL_PROGRESS = Proc.new do |handler|
65
+ MARSHAL_PROGRESS = proc do |handler|
66
66
  FFI::Function.new(:void, [:pointer, :pointer, :pointer]) do |vi, prog, cb|
67
- begin
68
- handler.(Progress.new(prog))
69
- rescue Exception => e
70
- puts "progress: #{e}"
71
- end
67
+ # this can't throw an exception, so no catch is necessary
68
+ handler.call(Progress.new(prog))
72
69
  end
73
70
  end
74
71
 
75
- MARSHAL_READ = Proc.new do |handler|
72
+ MARSHAL_READ = proc do |handler|
76
73
  FFI::Function.new(:int64_t, [:pointer, :pointer, :int64_t]) do |i, p, len|
77
74
  begin
78
- result = handler.(p, len)
75
+ result = handler.call(p, len)
79
76
  rescue Exception => e
80
77
  puts "read: #{e}"
81
78
  result = 0
@@ -85,10 +82,10 @@ module Vips
85
82
  end
86
83
  end
87
84
 
88
- MARSHAL_SEEK = Proc.new do |handler|
85
+ MARSHAL_SEEK = proc do |handler|
89
86
  FFI::Function.new(:int64_t, [:pointer, :int64_t, :int]) do |i, off, whence|
90
87
  begin
91
- result = handler.(off, whence)
88
+ result = handler.call(off, whence)
92
89
  rescue Exception => e
93
90
  puts "seek: #{e}"
94
91
  result = -1
@@ -98,10 +95,10 @@ module Vips
98
95
  end
99
96
  end
100
97
 
101
- MARSHAL_WRITE = Proc.new do |handler|
98
+ MARSHAL_WRITE = proc do |handler|
102
99
  FFI::Function.new(:int64_t, [:pointer, :pointer, :int64_t]) do |i, p, len|
103
100
  begin
104
- result = handler.(p, len)
101
+ result = handler.call(p, len)
105
102
  rescue Exception => e
106
103
  puts "write: #{e}"
107
104
  result = 0
@@ -111,41 +108,38 @@ module Vips
111
108
  end
112
109
  end
113
110
 
114
- MARSHAL_FINISH = Proc.new do |handler|
111
+ MARSHAL_FINISH = proc do |handler|
115
112
  FFI::Function.new(:void, [:pointer, :pointer]) do |i, cb|
116
- begin
117
- handler.()
118
- rescue Exception => e
119
- puts "finish: #{e}"
120
- end
113
+ # this can't throw an exception, so no catch is necessary
114
+ handler.call
121
115
  end
122
116
  end
123
117
 
124
118
  # map signal name to marshal proc
125
119
  MARSHAL_ALL = {
126
- :preeval => MARSHAL_PROGRESS,
127
- :eval => MARSHAL_PROGRESS,
128
- :posteval => MARSHAL_PROGRESS,
129
- :read => MARSHAL_READ,
130
- :seek => MARSHAL_SEEK,
131
- :write => MARSHAL_WRITE,
132
- :finish => MARSHAL_FINISH,
120
+ preeval: MARSHAL_PROGRESS,
121
+ eval: MARSHAL_PROGRESS,
122
+ posteval: MARSHAL_PROGRESS,
123
+ read: MARSHAL_READ,
124
+ seek: MARSHAL_SEEK,
125
+ write: MARSHAL_WRITE,
126
+ finish: MARSHAL_FINISH
133
127
  }
134
128
 
135
129
  attach_function :vips_enum_from_nick, [:string, :GType, :string], :int
136
130
  attach_function :vips_enum_nick, [:GType, :int], :string
137
131
 
138
132
  attach_function :vips_value_set_ref_string,
139
- [GObject::GValue.ptr, :string], :void
133
+ [GObject::GValue.ptr, :string], :void
140
134
  attach_function :vips_value_set_array_double,
141
- [GObject::GValue.ptr, :pointer, :int], :void
135
+ [GObject::GValue.ptr, :pointer, :int], :void
142
136
  attach_function :vips_value_set_array_int,
143
- [GObject::GValue.ptr, :pointer, :int], :void
137
+ [GObject::GValue.ptr, :pointer, :int], :void
144
138
  attach_function :vips_value_set_array_image,
145
- [GObject::GValue.ptr, :int], :void
139
+ [GObject::GValue.ptr, :int], :void
146
140
  callback :free_fn, [:pointer], :void
147
141
  attach_function :vips_value_set_blob,
148
- [GObject::GValue.ptr, :free_fn, :pointer, :size_t], :void
142
+ [GObject::GValue.ptr, :free_fn, :pointer, :size_t], :void
149
143
 
150
144
  class SizeStruct < FFI::Struct
151
145
  layout :value, :size_t
@@ -156,15 +150,15 @@ module Vips
156
150
  end
157
151
 
158
152
  attach_function :vips_value_get_ref_string,
159
- [GObject::GValue.ptr, SizeStruct.ptr], :string
153
+ [GObject::GValue.ptr, SizeStruct.ptr], :string
160
154
  attach_function :vips_value_get_array_double,
161
- [GObject::GValue.ptr, IntStruct.ptr], :pointer
155
+ [GObject::GValue.ptr, IntStruct.ptr], :pointer
162
156
  attach_function :vips_value_get_array_int,
163
- [GObject::GValue.ptr, IntStruct.ptr], :pointer
157
+ [GObject::GValue.ptr, IntStruct.ptr], :pointer
164
158
  attach_function :vips_value_get_array_image,
165
- [GObject::GValue.ptr, IntStruct.ptr], :pointer
159
+ [GObject::GValue.ptr, IntStruct.ptr], :pointer
166
160
  attach_function :vips_value_get_blob,
167
- [GObject::GValue.ptr, SizeStruct.ptr], :pointer
161
+ [GObject::GValue.ptr, SizeStruct.ptr], :pointer
168
162
 
169
163
  attach_function :type_find, :vips_type_find, [:string, :string], :GType
170
164
 
@@ -173,7 +167,7 @@ module Vips
173
167
  # debugging ruby-vips.
174
168
  def self.print_all
175
169
  GC.start
176
- Vips::vips_object_print_all
170
+ Vips.vips_object_print_all
177
171
  end
178
172
 
179
173
  # the layout of the VipsObject struct
@@ -182,15 +176,15 @@ module Vips
182
176
  base.class_eval do
183
177
  # don't actually need most of these
184
178
  layout :parent, GObject::GObject::Struct,
185
- :constructed, :int,
186
- :static_object, :int,
187
- :argument_table, :pointer,
188
- :nickname, :string,
189
- :description, :string,
190
- :preclose, :int,
191
- :close, :int,
192
- :postclose, :int,
193
- :local_memory, :size_t
179
+ :constructed, :int,
180
+ :static_object, :int,
181
+ :argument_table, :pointer,
182
+ :nickname, :string,
183
+ :description, :string,
184
+ :preclose, :int,
185
+ :close, :int,
186
+ :postclose, :int,
187
+ :local_memory, :size_t
194
188
  end
195
189
  end
196
190
  end
@@ -210,8 +204,8 @@ module Vips
210
204
  argument_class = Vips::ArgumentClassPtr.new
211
205
  argument_instance = Vips::ArgumentInstancePtr.new
212
206
 
213
- result = Vips::vips_object_get_argument self, name,
214
- ppspec, argument_class, argument_instance
207
+ result = Vips.vips_object_get_argument self, name,
208
+ ppspec, argument_class, argument_instance
215
209
  return nil if result != 0
216
210
 
217
211
  ppspec[:value]
@@ -229,7 +223,7 @@ module Vips
229
223
  def get_typeof name
230
224
  pspec = get_pspec name
231
225
  unless pspec
232
- Vips::vips_error_clear
226
+ Vips.vips_error_clear
233
227
  return 0
234
228
  end
235
229
 
@@ -240,44 +234,43 @@ module Vips
240
234
  gtype = get_typeof_error name
241
235
  gvalue = GObject::GValue.alloc
242
236
  gvalue.init gtype
243
- GObject::g_object_get_property self, name, gvalue
237
+ GObject.g_object_get_property self, name, gvalue
244
238
  result = gvalue.get
245
239
  gvalue.unset
246
240
 
247
- GLib::logger.debug("Vips::Object.get") { "#{name} == #{result}" }
241
+ GLib.logger.debug("Vips::Object.get") { "#{name} == #{result}" }
248
242
 
249
- return result
243
+ result
250
244
  end
251
245
 
252
246
  def set name, value
253
- GLib::logger.debug("Vips::Object.set") { "#{name} = #{value}" }
247
+ GLib.logger.debug("Vips::Object.set") { "#{name} = #{value}" }
254
248
 
255
249
  gtype = get_typeof_error name
256
250
  gvalue = GObject::GValue.alloc
257
251
  gvalue.init gtype
258
252
  gvalue.set value
259
- GObject::g_object_set_property self, name, gvalue
253
+ GObject.g_object_set_property self, name, gvalue
260
254
  gvalue.unset
261
255
  end
262
256
 
263
- def signal_connect name, handler=nil, &block
257
+ def signal_connect name, handler = nil, &block
264
258
  marshal = MARSHAL_ALL[name.to_sym]
265
- raise Vips::Error, "unsupported signal #{name}" if marshal == nil
259
+ raise Vips::Error, "unsupported signal #{name}" if marshal.nil?
266
260
 
267
261
  unless handler ||= block
268
262
  raise Vips::Error, "must supply either block or handler"
269
263
  end
270
264
 
271
- # The marshal function will make a closure with the right type signature
265
+ # The marshal function will make a closure with the right type signature
272
266
  # for the selected signal
273
267
  callback = marshal.(handler)
274
268
 
275
269
  # we need to make sure this is not GCd while self is alive
276
270
  @references << callback
277
271
 
278
- GObject::g_signal_connect_data(self, name.to_s, callback, nil, nil, 0)
272
+ GObject.g_signal_connect_data(self, name.to_s, callback, nil, nil, 0)
279
273
  end
280
-
281
274
  end
282
275
 
283
276
  class ObjectClass < FFI::Struct
@@ -316,10 +309,10 @@ module Vips
316
309
 
317
310
  class ArgumentClass < Argument
318
311
  layout :parent, Argument,
319
- :object_class, ObjectClass.ptr,
320
- :flags, :uint,
321
- :priority, :int,
322
- :offset, :ulong_long
312
+ :object_class, ObjectClass.ptr,
313
+ :flags, :uint,
314
+ :priority, :int,
315
+ :offset, :ulong_long
323
316
  end
324
317
 
325
318
  class ArgumentClassPtr < FFI::Struct
@@ -333,10 +326,10 @@ module Vips
333
326
  # just use :pointer, not VipsObject.ptr, to avoid casting gobject
334
327
  # subclasses
335
328
  attach_function :vips_object_get_argument,
336
- [:pointer, :string,
337
- GObject::GParamSpecPtr.ptr,
338
- ArgumentClassPtr.ptr, ArgumentInstancePtr.ptr],
339
- :int
329
+ [:pointer, :string,
330
+ GObject::GParamSpecPtr.ptr,
331
+ ArgumentClassPtr.ptr, ArgumentInstancePtr.ptr],
332
+ :int
340
333
 
341
334
  attach_function :vips_object_set_from_string, [:pointer, :string], :int
342
335