ruby-vips 2.0.16 → 2.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -41,6 +41,97 @@ module Vips
41
41
 
42
42
  private
43
43
 
44
+ class Progress < FFI::Struct
45
+ layout :im, :pointer,
46
+ :run, :int,
47
+ :eta, :int,
48
+ :tpels, :int64_t,
49
+ :npels, :int64_t,
50
+ :percent, :int,
51
+ :start, :pointer
52
+ end
53
+
54
+ # Our signal marshalers.
55
+ #
56
+ # These are functions which take the handler as a param and return a
57
+ # closure with the right FFI signature for g_signal_connect for this
58
+ # specific signal.
59
+ #
60
+ # ruby-ffi makes it hard to use the g_signal_connect user data param
61
+ # to pass the function pointer through, unfortunately.
62
+ #
63
+ # We can't throw exceptions across C, so we must catch everything.
64
+
65
+ MARSHAL_PROGRESS = Proc.new do |handler|
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
72
+ end
73
+ end
74
+
75
+ MARSHAL_READ = Proc.new do |handler|
76
+ FFI::Function.new(:int64_t, [:pointer, :pointer, :int64_t]) do |i, p, len|
77
+ begin
78
+ result = handler.(p, len)
79
+ rescue Exception => e
80
+ puts "read: #{e}"
81
+ result = 0
82
+ end
83
+
84
+ result
85
+ end
86
+ end
87
+
88
+ MARSHAL_SEEK = Proc.new do |handler|
89
+ FFI::Function.new(:int64_t, [:pointer, :int64_t, :int]) do |i, off, whence|
90
+ begin
91
+ result = handler.(off, whence)
92
+ rescue Exception => e
93
+ puts "seek: #{e}"
94
+ result = -1
95
+ end
96
+
97
+ result
98
+ end
99
+ end
100
+
101
+ MARSHAL_WRITE = Proc.new do |handler|
102
+ FFI::Function.new(:int64_t, [:pointer, :pointer, :int64_t]) do |i, p, len|
103
+ begin
104
+ result = handler.(p, len)
105
+ rescue Exception => e
106
+ puts "write: #{e}"
107
+ result = 0
108
+ end
109
+
110
+ result
111
+ end
112
+ end
113
+
114
+ MARSHAL_FINISH = Proc.new do |handler|
115
+ FFI::Function.new(:void, [:pointer, :pointer]) do |i, cb|
116
+ begin
117
+ handler.()
118
+ rescue Exception => e
119
+ puts "finish: #{e}"
120
+ end
121
+ end
122
+ end
123
+
124
+ # map signal name to marshal proc
125
+ 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,
133
+ }
134
+
44
135
  attach_function :vips_enum_from_nick, [:string, :GType, :string], :int
45
136
  attach_function :vips_enum_nick, [:GType, :int], :string
46
137
 
@@ -115,15 +206,15 @@ module Vips
115
206
  # return a pspec, or nil ... nil wil leave a message in the error log
116
207
  # which you must clear
117
208
  def get_pspec name
118
- pspec = GObject::GParamSpecPtr.new
209
+ ppspec = GObject::GParamSpecPtr.new
119
210
  argument_class = Vips::ArgumentClassPtr.new
120
211
  argument_instance = Vips::ArgumentInstancePtr.new
121
212
 
122
213
  result = Vips::vips_object_get_argument self, name,
123
- pspec, argument_class, argument_instance
214
+ ppspec, argument_class, argument_instance
124
215
  return nil if result != 0
125
216
 
126
- pspec
217
+ ppspec[:value]
127
218
  end
128
219
 
129
220
  # return a gtype, raise an error on not found
@@ -131,7 +222,7 @@ module Vips
131
222
  pspec = get_pspec name
132
223
  raise Vips::Error unless pspec
133
224
 
134
- pspec[:value][:value_type]
225
+ pspec[:value_type]
135
226
  end
136
227
 
137
228
  # return a gtype, 0 on not found
@@ -142,7 +233,7 @@ module Vips
142
233
  return 0
143
234
  end
144
235
 
145
- pspec[:value][:value_type]
236
+ pspec[:value_type]
146
237
  end
147
238
 
148
239
  def get name
@@ -151,6 +242,7 @@ module Vips
151
242
  gvalue.init gtype
152
243
  GObject::g_object_get_property self, name, gvalue
153
244
  result = gvalue.get
245
+ gvalue.unset
154
246
 
155
247
  GLib::logger.debug("Vips::Object.get") { "#{name} == #{result}" }
156
248
 
@@ -165,7 +257,33 @@ module Vips
165
257
  gvalue.init gtype
166
258
  gvalue.set value
167
259
  GObject::g_object_set_property self, name, gvalue
260
+ gvalue.unset
261
+ end
262
+
263
+ def signal_connect name, handler=nil
264
+ marshal = MARSHAL_ALL[name.to_sym]
265
+ raise Vips::Error, "unsupported signal #{name}" if marshal == nil
266
+
267
+ if block_given?
268
+ # This will grab any block given to us and make it into a proc
269
+ prc = Proc.new
270
+ elsif handler
271
+ # We assume the hander is a proc (perhaps we should test)
272
+ prc = handler
273
+ else
274
+ raise Vips::Error, "must supply either block or handler"
275
+ end
276
+
277
+ # The marshal function will make a closure with the right type signature
278
+ # for the selected signal
279
+ callback = marshal.(prc)
280
+
281
+ # we need to make sure this is not GCd while self is alive
282
+ @references << callback
283
+
284
+ GObject::g_signal_connect_data(self, name.to_s, callback, nil, nil, 0)
168
285
  end
286
+
169
287
  end
170
288
 
171
289
  class ObjectClass < FFI::Struct
@@ -11,7 +11,13 @@ module Vips
11
11
 
12
12
  attach_function :vips_operation_new, [:string], :pointer
13
13
 
14
- attach_function :vips_cache_operation_build, [:pointer], :pointer
14
+ # We may well block during this (eg. if it's avg, or perhaps jpegsave), and
15
+ # libvips might trigger some signals which ruby has handles for.
16
+ #
17
+ # We need FFI to drop the GIL lock during this call and reacquire it when
18
+ # the call ends, or we'll deadlock.
19
+ attach_function :vips_cache_operation_build, [:pointer], :pointer,
20
+ blocking: true
15
21
  attach_function :vips_object_unref_outputs, [:pointer], :void
16
22
 
17
23
  callback :argument_map_fn, [:pointer,
@@ -35,6 +41,118 @@ module Vips
35
41
 
36
42
  attach_function :vips_operation_get_flags, [:pointer], :int
37
43
 
44
+ # Introspect a vips operation and return a large structure containing
45
+ # everything we know about it. This is used for doc generation as well as
46
+ # call.
47
+ class Introspect
48
+ attr_reader :name, :description, :flags, :args, :required_input,
49
+ :optional_input, :required_output, :optional_output, :member_x,
50
+ :method_args
51
+
52
+ @@introspect_cache = {}
53
+
54
+ def initialize name
55
+ @op = Operation.new name
56
+ @args = []
57
+ @required_input = []
58
+ @optional_input = {}
59
+ @required_output = []
60
+ @optional_output = {}
61
+
62
+ # find all the arguments the operator can take
63
+ @op.argument_map do |pspec, argument_class, _argument_instance|
64
+ flags = argument_class[:flags]
65
+ if (flags & ARGUMENT_CONSTRUCT) != 0
66
+ # names can include - as punctuation, but we always use _ in
67
+ # Ruby
68
+ arg_name = pspec[:name].tr("-", "_")
69
+ args << {
70
+ :arg_name => arg_name,
71
+ :flags => flags,
72
+ :gtype => pspec[:value_type]
73
+ }
74
+ end
75
+ end
76
+
77
+ @args.each do |details|
78
+ arg_name = details[:arg_name]
79
+ flags = details[:flags]
80
+
81
+ if (flags & ARGUMENT_INPUT) != 0
82
+ if (flags & ARGUMENT_REQUIRED) != 0 &&
83
+ (flags & ARGUMENT_DEPRECATED) == 0
84
+ @required_input << details
85
+ else
86
+ # we allow deprecated optional args
87
+ @optional_input[arg_name] = details
88
+ end
89
+
90
+ # MODIFY INPUT args count as OUTPUT as well
91
+ if (flags & ARGUMENT_MODIFY) != 0
92
+ if (flags & ARGUMENT_REQUIRED) != 0 &&
93
+ (flags & ARGUMENT_DEPRECATED) == 0
94
+ @required_output << details
95
+ else
96
+ @optional_output[arg_name] = details
97
+ end
98
+ end
99
+ elsif (flags & ARGUMENT_OUTPUT) != 0
100
+ if (flags & ARGUMENT_REQUIRED) != 0 &&
101
+ (flags & ARGUMENT_DEPRECATED) == 0
102
+ @required_output << details
103
+ else
104
+ # again, allow deprecated optional args
105
+ @optional_output[arg_name] = details
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ # Yard comment generation needs a little more introspection. We add this
112
+ # extra metadata in a separate method to keep the main path as fast as
113
+ # we can.
114
+ def add_yard_introspection name
115
+ @name = name
116
+ @description = Vips::vips_object_get_description @op
117
+ @flags = Vips::vips_operation_get_flags @op
118
+ @member_x = nil
119
+ @method_args = []
120
+
121
+ @args.each do |details|
122
+ arg_name = details[:arg_name]
123
+ flags = details[:flags]
124
+ gtype = details[:gtype]
125
+
126
+ details[:yard_name] = arg_name == "in" ? "im" : arg_name
127
+ pspec = @op.get_pspec arg_name
128
+ details[:blurb] = GObject::g_param_spec_get_blurb pspec
129
+
130
+ if (flags & ARGUMENT_INPUT) != 0 &&
131
+ (flags & ARGUMENT_REQUIRED) != 0 &&
132
+ (flags & ARGUMENT_DEPRECATED) == 0
133
+ # the first required input image is the thing we will be a method
134
+ # of
135
+ if @member_x == nil && gtype == IMAGE_TYPE
136
+ @member_x = details
137
+ else
138
+ @method_args << details
139
+ end
140
+ end
141
+ end
142
+ end
143
+
144
+ def self.get name
145
+ @@introspect_cache[name] ||= Introspect.new name
146
+ end
147
+
148
+ def self.get_yard name
149
+ introspect = Introspect.get name
150
+ introspect.add_yard_introspection name
151
+ introspect
152
+ end
153
+
154
+ end
155
+
38
156
  class Operation < Object
39
157
  # the layout of the VipsOperation struct
40
158
  module OperationLayout
@@ -59,7 +177,7 @@ module Vips
59
177
  # things like _build
60
178
  if value.is_a? String
61
179
  value = Vips::vips_operation_new value
62
- raise Vips::Error if value == nil
180
+ raise Vips::Error if value.null?
63
181
  end
64
182
 
65
183
  super value
@@ -67,7 +185,8 @@ module Vips
67
185
 
68
186
  def build
69
187
  op = Vips::vips_cache_operation_build self
70
- if op == nil
188
+ if op.null?
189
+ Vips::vips_object_unref_outputs self
71
190
  raise Vips::Error
72
191
  end
73
192
 
@@ -82,35 +201,16 @@ module Vips
82
201
  Vips::vips_argument_map self, fn, nil, nil
83
202
  end
84
203
 
85
- def get_flags
86
- Vips::vips_operation_get_flags self
87
- end
88
-
89
- # not quick! try to call this infrequently
90
- def get_construct_args
91
- args = []
92
-
93
- argument_map do |pspec, argument_class, _argument_instance|
94
- flags = argument_class[:flags]
95
- if (flags & ARGUMENT_CONSTRUCT) != 0
96
- # names can include - as punctuation, but we always use _ in
97
- # Ruby
98
- name = pspec[:name].tr("-", "_")
99
-
100
- args << [name, flags]
204
+ # Search an object for the first element to match a predicate. Search
205
+ # inside subarrays and sub-hashes. Equlvalent to x.flatten.find{}.
206
+ def self.flat_find object, &block
207
+ if object.respond_to? :each
208
+ object.each do |x|
209
+ result = flat_find x, &block
210
+ return result if result != nil
101
211
  end
102
- end
103
-
104
- return args
105
- end
106
-
107
- # search array for the first element to match a predicate ...
108
- # search inside subarrays and sub-hashes
109
- def self.find_inside object, &block
110
- return object if block.call object
111
-
112
- if object.is_a? Enumerable
113
- object.find { |value| block.call value, block }
212
+ else
213
+ return object if yield object
114
214
  end
115
215
 
116
216
  return nil
@@ -122,7 +222,7 @@ module Vips
122
222
 
123
223
  # 2D array values become tiny 2D images
124
224
  # if there's nothing to match to, we also make a 2D image
125
- if (value.is_a?(Array) && value[0].is_a?(Array)) ||
225
+ if (value.is_a?(Array) && value[0].is_a?(Array)) ||
126
226
  match_image == nil
127
227
  return Image.new_from_array value
128
228
  else
@@ -134,9 +234,7 @@ module Vips
134
234
 
135
235
  # set an operation argument, expanding constants and copying images as
136
236
  # required
137
- def set name, value, match_image = nil, flags = 0
138
- gtype = get_typeof name
139
-
237
+ def set name, value, match_image, flags, gtype
140
238
  if gtype == IMAGE_TYPE
141
239
  value = Operation::imageize match_image, value
142
240
 
@@ -226,39 +324,12 @@ module Vips
226
324
  "optional = #{optional}, option_string = #{option_string}"
227
325
  }
228
326
 
229
- op = Operation.new name
327
+ introspect = Introspect.get name
328
+ required_input = introspect.required_input
329
+ required_output = introspect.required_output
330
+ optional_input = introspect.optional_input
331
+ optional_output = introspect.optional_output
230
332
 
231
- # find and classify all the arguments the operator can take
232
- args = op.get_construct_args
233
- required_input = []
234
- optional_input = {}
235
- required_output = []
236
- optional_output = {}
237
- args.each do |arg_name, flags|
238
- next if (flags & ARGUMENT_DEPRECATED) != 0
239
-
240
- if (flags & ARGUMENT_INPUT) != 0
241
- if (flags & ARGUMENT_REQUIRED) != 0
242
- required_input << [arg_name, flags]
243
- else
244
- optional_input[arg_name] = flags
245
- end
246
- end
247
-
248
- # MODIFY INPUT args count as OUTPUT as well
249
- if (flags & ARGUMENT_OUTPUT) != 0 ||
250
- ((flags & ARGUMENT_INPUT) != 0 &&
251
- (flags & ARGUMENT_MODIFY) != 0)
252
- if (flags & ARGUMENT_REQUIRED) != 0
253
- required_output << [arg_name, flags]
254
- else
255
- optional_output[arg_name] = flags
256
- end
257
- end
258
- end
259
-
260
- # so we should have been supplied with n_required_input values, or
261
- # n_required_input + 1 if there's a hash of options at the end
262
333
  unless supplied.is_a? Array
263
334
  raise Vips::Error, "unable to call #{name}: " +
264
335
  "argument array is not an array"
@@ -267,19 +338,20 @@ module Vips
267
338
  raise Vips::Error, "unable to call #{name}: " +
268
339
  "optional arguments are not a hash"
269
340
  end
341
+
270
342
  if supplied.length != required_input.length
271
343
  raise Vips::Error, "unable to call #{name}: " +
272
344
  "you supplied #{supplied.length} arguments, " +
273
- "but operation needs #{required_input.length}."
345
+ "but operation needs " + "#{required_input.length}."
274
346
  end
275
347
 
276
- # very that all supplied_optional keys are in optional_input or
348
+ # all supplied_optional keys should be in optional_input or
277
349
  # optional_output
278
350
  optional.each do |key, _value|
279
351
  arg_name = key.to_s
280
352
 
281
353
  unless optional_input.has_key?(arg_name) ||
282
- optional_output.has_key?(arg_name)
354
+ optional_output.has_key?(arg_name)
283
355
  raise Vips::Error, "unable to call #{name}: " +
284
356
  "unknown option #{arg_name}"
285
357
  end
@@ -290,9 +362,9 @@ module Vips
290
362
  #
291
363
  # look inside array and hash arguments, since we may be passing an
292
364
  # array of images
293
- match_image = find_inside(supplied) do |value|
294
- value.is_a? Image
295
- end
365
+ match_image = flat_find(supplied) { |value| value.is_a? Image }
366
+
367
+ op = Operation.new name
296
368
 
297
369
  # set any string args first so they can't be overridden
298
370
  if option_string != nil
@@ -303,11 +375,13 @@ module Vips
303
375
 
304
376
  # set all required inputs
305
377
  required_input.each_index do |i|
306
- arg_name = required_input[i][0]
307
- flags = required_input[i][1]
378
+ details = required_input[i]
379
+ arg_name = details[:arg_name]
380
+ flags = details[:flags]
381
+ gtype = details[:gtype]
308
382
  value = supplied[i]
309
383
 
310
- op.set arg_name, value, match_image, flags
384
+ op.set arg_name, value, match_image, flags, gtype
311
385
  end
312
386
 
313
387
  # set all optional inputs
@@ -317,9 +391,11 @@ module Vips
317
391
  arg_name = key.to_s
318
392
 
319
393
  if optional_input.has_key? arg_name
320
- flags = optional_input[arg_name]
394
+ details = optional_input[arg_name]
395
+ flags = details[:flags]
396
+ gtype = details[:gtype]
321
397
 
322
- op.set arg_name, value, match_image, flags
398
+ op.set arg_name, value, match_image, flags, gtype
323
399
  end
324
400
  end
325
401
 
@@ -327,8 +403,8 @@ module Vips
327
403
 
328
404
  # get all required results
329
405
  result = []
330
- required_output.each do |arg_name, _flags|
331
- result << op.get(arg_name)
406
+ required_output.each do |details|
407
+ result << op.get(details[:arg_name])
332
408
  end
333
409
 
334
410
  # fetch all optional ones