vips 8.8.4 → 8.12.1

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.
@@ -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