vips 8.8.4 → 8.12.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,24 +4,31 @@
4
4
  # Author:: John Cupitt (mailto:jcupitt@gmail.com)
5
5
  # License:: MIT
6
6
 
7
- require 'ffi'
7
+ require "ffi"
8
+ require "set"
8
9
 
9
10
  module Vips
10
11
  private
11
12
 
12
13
  attach_function :vips_operation_new, [:string], :pointer
13
14
 
14
- attach_function :vips_cache_operation_build, [:pointer], :pointer
15
+ # We may well block during this (eg. if it's avg, or perhaps jpegsave), and
16
+ # libvips might trigger some signals which ruby has handles for.
17
+ #
18
+ # We need FFI to drop the GIL lock during this call and reacquire it when
19
+ # the call ends, or we'll deadlock.
20
+ attach_function :vips_cache_operation_build, [:pointer], :pointer,
21
+ blocking: true
15
22
  attach_function :vips_object_unref_outputs, [:pointer], :void
16
23
 
17
24
  callback :argument_map_fn, [:pointer,
18
- GObject::GParamSpec.ptr,
19
- ArgumentClass.ptr,
20
- ArgumentInstance.ptr,
21
- :pointer, :pointer], :pointer
25
+ GObject::GParamSpec.ptr,
26
+ ArgumentClass.ptr,
27
+ ArgumentInstance.ptr,
28
+ :pointer, :pointer], :pointer
22
29
  attach_function :vips_argument_map, [:pointer,
23
- :argument_map_fn,
24
- :pointer, :pointer], :pointer
30
+ :argument_map_fn,
31
+ :pointer, :pointer], :pointer
25
32
 
26
33
  OPERATION_SEQUENTIAL = 1
27
34
  OPERATION_NOCACHE = 4
@@ -35,6 +42,155 @@ module Vips
35
42
 
36
43
  attach_function :vips_operation_get_flags, [:pointer], :int
37
44
 
45
+ # Introspect a vips operation and return a large structure containing
46
+ # everything we know about it. This is used for doc generation as well as
47
+ # call.
48
+ class Introspect
49
+ attr_reader :name, :description, :flags, :args, :required_input,
50
+ :optional_input, :required_output, :optional_output, :member_x,
51
+ :method_args, :vips_name, :destructive, :doc_optional_input,
52
+ :doc_optional_output
53
+
54
+ @@introspect_cache = {}
55
+
56
+ def initialize name
57
+ # if there's a trailing "!", this is a destructive version of an
58
+ # operation
59
+ if name[-1] == "!"
60
+ @destructive = true
61
+ # strip the trailing "!"
62
+ @vips_name = name[0...-1]
63
+ else
64
+ @destructive = false
65
+ @vips_name = name
66
+ end
67
+
68
+ @op = Operation.new @vips_name
69
+ @args = []
70
+ @required_input = []
71
+ @optional_input = {}
72
+ @required_output = []
73
+ @optional_output = {}
74
+
75
+ # find all the arguments the operator can take
76
+ @op.argument_map do |pspec, argument_class, _argument_instance|
77
+ flags = argument_class[:flags]
78
+ if (flags & ARGUMENT_CONSTRUCT) != 0
79
+ # names can include - as punctuation, but we always use _ in
80
+ # Ruby
81
+ arg_name = pspec[:name].tr("-", "_")
82
+ @args << {
83
+ arg_name: arg_name,
84
+ flags: flags,
85
+ gtype: pspec[:value_type]
86
+ }
87
+ end
88
+
89
+ nil
90
+ end
91
+
92
+ @args.each do |details|
93
+ arg_name = details[:arg_name]
94
+ flags = details[:flags]
95
+
96
+ if (flags & ARGUMENT_INPUT) != 0
97
+ if (flags & ARGUMENT_REQUIRED) != 0 &&
98
+ (flags & ARGUMENT_DEPRECATED) == 0
99
+ @required_input << details
100
+ else
101
+ # we allow deprecated optional args
102
+ @optional_input[arg_name] = details
103
+ end
104
+
105
+ # MODIFY INPUT args count as OUTPUT as well in non-destructive mode
106
+ if (flags & ARGUMENT_MODIFY) != 0 &&
107
+ !@destructive
108
+ if (flags & ARGUMENT_REQUIRED) != 0 &&
109
+ (flags & ARGUMENT_DEPRECATED) == 0
110
+ @required_output << details
111
+ else
112
+ @optional_output[arg_name] = details
113
+ end
114
+ end
115
+ elsif (flags & ARGUMENT_OUTPUT) != 0
116
+ if (flags & ARGUMENT_REQUIRED) != 0 &&
117
+ (flags & ARGUMENT_DEPRECATED) == 0
118
+ @required_output << details
119
+ else
120
+ # again, allow deprecated optional args
121
+ @optional_output[arg_name] = details
122
+ end
123
+ end
124
+ end
125
+
126
+ # in destructive mode, the first required input arg must be MODIFY and
127
+ # must be an image
128
+ if @destructive
129
+ if @required_input.length < 1 ||
130
+ @required_input[0][:flags] & ARGUMENT_MODIFY == 0 ||
131
+ @required_input[0][:gtype] != IMAGE_TYPE
132
+ raise Vips::Error, "operation #{@vips_name} is not destructive"
133
+ end
134
+ end
135
+ end
136
+
137
+ # Yard comment generation needs a little more introspection. We add this
138
+ # extra metadata in a separate method to keep the main path as fast as
139
+ # we can.
140
+ def add_yard_introspection name
141
+ @name = name
142
+ @description = Vips.vips_object_get_description @op
143
+ @flags = Vips.vips_operation_get_flags @op
144
+ @member_x = nil
145
+ @method_args = []
146
+ @doc_optional_input = {}
147
+ @doc_optional_output = {}
148
+
149
+ @args.each do |details|
150
+ arg_name = details[:arg_name]
151
+ flags = details[:flags]
152
+ gtype = details[:gtype]
153
+
154
+ details[:yard_name] = arg_name == "in" ? "im" : arg_name
155
+ pspec = @op.get_pspec arg_name
156
+ details[:blurb] = GObject.g_param_spec_get_blurb pspec
157
+
158
+ if (flags & ARGUMENT_INPUT) != 0 &&
159
+ (flags & ARGUMENT_REQUIRED) != 0 &&
160
+ (flags & ARGUMENT_DEPRECATED) == 0
161
+ # the first required input image is the thing we will be a method
162
+ # of
163
+ if @member_x.nil? && gtype == IMAGE_TYPE
164
+ @member_x = details
165
+ else
166
+ @method_args << details
167
+ end
168
+ end
169
+ end
170
+
171
+ # and make the arg sets to document by filtering out deprecated args
172
+ @optional_input.each do |arg_name, details|
173
+ next if (details[:flags] & ARGUMENT_DEPRECATED) != 0
174
+ @doc_optional_input[details[:arg_name]] = details
175
+ end
176
+
177
+ @optional_output.each do |arg_name, details|
178
+ next if (details[:flags] & ARGUMENT_DEPRECATED) != 0
179
+ @doc_optional_output[details[:arg_name]] = details
180
+ end
181
+ end
182
+
183
+ def self.get name
184
+ @@introspect_cache[name] ||= Introspect.new name
185
+ end
186
+
187
+ def self.get_yard name
188
+ introspect = Introspect.get name
189
+ introspect.add_yard_introspection name
190
+ introspect
191
+ end
192
+ end
193
+
38
194
  class Operation < Object
39
195
  # the layout of the VipsOperation struct
40
196
  module OperationLayout
@@ -58,94 +214,73 @@ module Vips
58
214
  # allow init with a pointer so we can wrap the return values from
59
215
  # things like _build
60
216
  if value.is_a? String
61
- value = Vips::vips_operation_new value
62
- raise Vips::Error if value == nil
217
+ value = Vips.vips_operation_new value
218
+ raise Vips::Error if value.null?
63
219
  end
64
220
 
65
221
  super value
66
222
  end
67
223
 
68
224
  def build
69
- op = Vips::vips_cache_operation_build self
70
- if op == nil
225
+ op = Vips.vips_cache_operation_build self
226
+ if op.null?
227
+ Vips.vips_object_unref_outputs self
71
228
  raise Vips::Error
72
229
  end
73
230
 
74
- return Operation.new op
231
+ Operation.new op
75
232
  end
76
233
 
77
234
  def argument_map &block
78
- fn = Proc.new do |_op, pspec, argument_class, argument_instance, _a, _b|
235
+ fn = proc do |_op, pspec, argument_class, argument_instance, _a, _b|
79
236
  block.call pspec, argument_class, argument_instance
80
237
  end
81
-
82
- Vips::vips_argument_map self, fn, nil, nil
83
- end
84
-
85
- def get_flags
86
- Vips::vips_operation_get_flags self
238
+ Vips.vips_argument_map self, fn, nil, nil
87
239
  end
88
240
 
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]
241
+ # Search an object for the first element to match a predicate. Search
242
+ # inside subarrays and sub-hashes. Equlvalent to x.flatten.find{}.
243
+ def self.flat_find object, &block
244
+ if object.respond_to? :each
245
+ object.each do |x|
246
+ result = flat_find x, &block
247
+ return result unless result.nil?
101
248
  end
249
+ elsif yield object
250
+ return object
102
251
  end
103
252
 
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 }
114
- end
115
-
116
- return nil
253
+ nil
117
254
  end
118
255
 
119
256
  # expand a constant into an image
120
257
  def self.imageize match_image, value
121
- return value if value.is_a? Image
258
+ return value if value.is_a?(Image) || value.is_a?(MutableImage)
122
259
 
123
260
  # 2D array values become tiny 2D images
124
261
  # if there's nothing to match to, we also make a 2D image
125
- if (value.is_a?(Array) && value[0].is_a?(Array)) ||
126
- match_image == nil
127
- return Image.new_from_array value
262
+ if (value.is_a?(Array) && value[0].is_a?(Array)) || match_image.nil?
263
+ Image.new_from_array value
128
264
  else
129
265
  # we have a 1D array ... use that as a pixel constant and
130
266
  # expand to match match_image
131
- return match_image.new_from_image value
267
+ match_image.new_from_image value
132
268
  end
133
269
  end
134
270
 
135
271
  # set an operation argument, expanding constants and copying images as
136
272
  # required
137
- def set name, value, match_image = nil, flags = 0
138
- gtype = get_typeof name
139
-
273
+ def set name, value, match_image, flags, gtype, destructive
140
274
  if gtype == IMAGE_TYPE
141
- value = Operation::imageize match_image, value
275
+ value = Operation.imageize match_image, value
142
276
 
143
- if (flags & ARGUMENT_MODIFY) != 0
144
- # make sure we have a unique copy
277
+ # in non-destructive mode, make sure we have a unique copy
278
+ if (flags & ARGUMENT_MODIFY) != 0 &&
279
+ !destructive
145
280
  value = value.copy.copy_memory
146
281
  end
147
282
  elsif gtype == ARRAY_IMAGE_TYPE
148
- value = value.map { |x| Operation::imageize match_image, x }
283
+ value = value.map { |x| Operation.imageize match_image, x }
149
284
  end
150
285
 
151
286
  super name, value
@@ -221,67 +356,42 @@ module Vips
221
356
  # the constant value 255.
222
357
 
223
358
  def self.call name, supplied, optional = {}, option_string = ""
224
- GLib::logger.debug("Vips::VipsOperation.call") {
225
- "name = #{name}, supplied = #{supplied}, " +
359
+ GLib.logger.debug("Vips::VipsOperation.call") {
360
+ "name = #{name}, supplied = #{supplied}, " \
226
361
  "optional = #{optional}, option_string = #{option_string}"
227
362
  }
228
363
 
229
- op = Operation.new name
230
-
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
364
+ introspect = Introspect.get name
365
+ required_input = introspect.required_input
366
+ required_output = introspect.required_output
367
+ optional_input = introspect.optional_input
368
+ optional_output = introspect.optional_output
369
+ destructive = introspect.destructive
247
370
 
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
371
  unless supplied.is_a? Array
263
- raise Vips::Error, "unable to call #{name}: " +
264
- "argument array is not an array"
372
+ raise Vips::Error, "unable to call #{name}: " \
373
+ "argument array is not an array"
265
374
  end
266
375
  unless optional.is_a? Hash
267
- raise Vips::Error, "unable to call #{name}: " +
268
- "optional arguments are not a hash"
376
+ raise Vips::Error, "unable to call #{name}: " \
377
+ "optional arguments are not a hash"
269
378
  end
379
+
270
380
  if supplied.length != required_input.length
271
- raise Vips::Error, "unable to call #{name}: " +
272
- "you supplied #{supplied.length} arguments, " +
273
- "but operation needs #{required_input.length}."
381
+ raise Vips::Error, "unable to call #{name}: " \
382
+ "you supplied #{supplied.length} arguments, " \
383
+ "but operation needs #{required_input.length}."
274
384
  end
275
385
 
276
- # very that all supplied_optional keys are in optional_input or
386
+ # all supplied_optional keys should be in optional_input or
277
387
  # optional_output
278
388
  optional.each do |key, _value|
279
389
  arg_name = key.to_s
280
390
 
281
391
  unless optional_input.has_key?(arg_name) ||
282
- optional_output.has_key?(arg_name)
283
- raise Vips::Error, "unable to call #{name}: " +
284
- "unknown option #{arg_name}"
392
+ optional_output.has_key?(arg_name)
393
+ raise Vips::Error, "unable to call #{name}: " \
394
+ "unknown option #{arg_name}"
285
395
  end
286
396
  end
287
397
 
@@ -290,24 +400,68 @@ module Vips
290
400
  #
291
401
  # look inside array and hash arguments, since we may be passing an
292
402
  # array of images
293
- match_image = find_inside(supplied) do |value|
294
- value.is_a? Image
403
+ #
404
+ # also enforce the rules around mutable and non-mutable images
405
+ match_image = nil
406
+ flat_find(supplied) do |value|
407
+ if match_image
408
+ # no non-first image arg can ever be mutable
409
+ if value.is_a?(MutableImage)
410
+ raise Vips::Error, "unable to call #{name}: " \
411
+ "only the first image argument can be mutable"
412
+ end
413
+ elsif destructive
414
+ if value.is_a?(Image)
415
+ raise Vips::Error, "unable to call #{name}: " \
416
+ "first image argument to a destructive " \
417
+ "operation must be mutable"
418
+ elsif value.is_a?(MutableImage)
419
+ match_image = value
420
+ end
421
+ elsif value.is_a?(MutableImage)
422
+ # non destructive operation, so no mutable images
423
+ raise Vips::Error, "unable to call #{name}: " \
424
+ "must not pass mutable images to " \
425
+ "non-destructive operations"
426
+ elsif value.is_a?(Image)
427
+ match_image = value
428
+ end
429
+
430
+ # keep looping
431
+ false
295
432
  end
296
433
 
434
+ op = Operation.new introspect.vips_name
435
+
297
436
  # set any string args first so they can't be overridden
298
- if option_string != nil
299
- if Vips::vips_object_set_from_string(op, option_string) != 0
437
+ unless option_string.nil?
438
+ if Vips.vips_object_set_from_string(op, option_string) != 0
300
439
  raise Vips::Error
301
440
  end
302
441
  end
303
442
 
443
+ # collect a list of all input references here
444
+ references = Set.new
445
+
446
+ add_reference = lambda do |x|
447
+ if x.is_a?(Vips::Image)
448
+ x.references.each do |i|
449
+ references << i
450
+ end
451
+ end
452
+ false
453
+ end
454
+
304
455
  # set all required inputs
305
456
  required_input.each_index do |i|
306
- arg_name = required_input[i][0]
307
- flags = required_input[i][1]
457
+ details = required_input[i]
458
+ arg_name = details[:arg_name]
459
+ flags = details[:flags]
460
+ gtype = details[:gtype]
308
461
  value = supplied[i]
309
462
 
310
- op.set arg_name, value, match_image, flags
463
+ flat_find value, &add_reference
464
+ op.set arg_name, value, match_image, flags, gtype, destructive
311
465
  end
312
466
 
313
467
  # set all optional inputs
@@ -317,18 +471,31 @@ module Vips
317
471
  arg_name = key.to_s
318
472
 
319
473
  if optional_input.has_key? arg_name
320
- flags = optional_input[arg_name]
474
+ details = optional_input[arg_name]
475
+ flags = details[:flags]
476
+ gtype = details[:gtype]
321
477
 
322
- op.set arg_name, value, match_image, flags
478
+ flat_find value, &add_reference
479
+ op.set arg_name, value, match_image, flags, gtype, destructive
323
480
  end
324
481
  end
325
482
 
326
483
  op = op.build
327
484
 
485
+ # attach all input refs to output x
486
+ set_reference = lambda do |x|
487
+ if x.is_a? Vips::Image
488
+ x.references += references
489
+ end
490
+ false
491
+ end
492
+
328
493
  # get all required results
329
494
  result = []
330
- required_output.each do |arg_name, _flags|
331
- result << op.get(arg_name)
495
+ required_output.each do |details|
496
+ value = details[:arg_name]
497
+ flat_find value, &set_reference
498
+ result << op.get(value)
332
499
  end
333
500
 
334
501
  # fetch all optional ones
@@ -337,7 +504,9 @@ module Vips
337
504
  arg_name = key.to_s
338
505
 
339
506
  if optional_output.has_key? arg_name
340
- optional_results[arg_name] = op.get arg_name
507
+ value = op.get arg_name
508
+ flat_find value, &set_reference
509
+ optional_results[arg_name] = value
341
510
  end
342
511
  end
343
512
 
@@ -349,11 +518,11 @@ module Vips
349
518
  result = nil
350
519
  end
351
520
 
352
- GLib::logger.debug("Vips::Operation.call") { "result = #{result}" }
521
+ GLib.logger.debug("Vips::Operation.call") { "result = #{result}" }
353
522
 
354
- Vips::vips_object_unref_outputs op
523
+ Vips.vips_object_unref_outputs op
355
524
 
356
- return result
525
+ result
357
526
  end
358
527
  end
359
528
  end
@@ -0,0 +1,73 @@
1
+ # This module provides an interface to the top level bits of libvips
2
+ # via ruby-ffi.
3
+ #
4
+ # Author:: John Cupitt (mailto:jcupitt@gmail.com)
5
+ # License:: MIT
6
+
7
+ require "ffi"
8
+
9
+ module Vips
10
+ attach_function :vips_region_new, [:pointer], :pointer
11
+
12
+ if Vips.at_least_libvips?(8, 8)
13
+ attach_function :vips_region_fetch, [:pointer, :int, :int, :int, :int, SizeStruct.ptr], :pointer
14
+ attach_function :vips_region_width, [:pointer], :int
15
+ attach_function :vips_region_height, [:pointer], :int
16
+ end
17
+
18
+ # A region on an image. Create one, then use `fetch` to quickly get a region
19
+ # of pixels.
20
+ #
21
+ # For example:
22
+ #
23
+ # ```ruby
24
+ # region = Vips::Region.new(image)
25
+ # pixels = region.fetch(10, 10, 100, 100)
26
+ # ```
27
+ class Region < Vips::Object
28
+ # The layout of the VipsRegion struct.
29
+ module RegionLayout
30
+ def self.included(base)
31
+ base.class_eval do
32
+ layout :parent, Vips::Object::Struct
33
+ # rest opaque
34
+ end
35
+ end
36
+ end
37
+
38
+ class Struct < Vips::Object::Struct
39
+ include RegionLayout
40
+ end
41
+
42
+ class ManagedStruct < Vips::Object::ManagedStruct
43
+ include RegionLayout
44
+ end
45
+
46
+ def initialize(name)
47
+ ptr = Vips.vips_region_new name
48
+ raise Vips::Error if ptr.null?
49
+
50
+ super ptr
51
+ end
52
+
53
+ def width
54
+ Vips.vips_region_width self
55
+ end
56
+
57
+ def height
58
+ Vips.vips_region_height self
59
+ end
60
+
61
+ # Fetch a region filled with pixel data.
62
+ def fetch(left, top, width, height)
63
+ len = Vips::SizeStruct.new
64
+ ptr = Vips.vips_region_fetch self, left, top, width, height, len
65
+ raise Vips::Error if ptr.null?
66
+
67
+ # wrap up as an autopointer
68
+ ptr = FFI::AutoPointer.new(ptr, GLib::G_FREE)
69
+
70
+ ptr.get_bytes 0, len[:value]
71
+ end
72
+ end
73
+ end