vips 8.8.4 → 8.9.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,27 @@ 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
168
261
  end
262
+
263
+ def signal_connect name, handler=nil, &block
264
+ marshal = MARSHAL_ALL[name.to_sym]
265
+ raise Vips::Error, "unsupported signal #{name}" if marshal == nil
266
+
267
+ unless handler ||= block
268
+ raise Vips::Error, "must supply either block or handler"
269
+ end
270
+
271
+ # The marshal function will make a closure with the right type signature
272
+ # for the selected signal
273
+ callback = marshal.(handler)
274
+
275
+ # we need to make sure this is not GCd while self is alive
276
+ @references << callback
277
+
278
+ GObject::g_signal_connect_data(self, name.to_s, callback, nil, nil, 0)
279
+ end
280
+
169
281
  end
170
282
 
171
283
  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