ruby-vips 2.0.15 → 2.1.2

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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +42 -0
  3. data/.github/workflows/test.yml +80 -0
  4. data/.standard.yml +17 -0
  5. data/.yardopts +0 -1
  6. data/CHANGELOG.md +39 -0
  7. data/Gemfile +3 -1
  8. data/README.md +42 -41
  9. data/Rakefile +13 -21
  10. data/TODO +14 -14
  11. data/VERSION +1 -1
  12. data/example/annotate.rb +6 -6
  13. data/example/connection.rb +26 -0
  14. data/example/daltonize8.rb +6 -6
  15. data/example/draw_lines.rb +30 -0
  16. data/example/example1.rb +4 -4
  17. data/example/example2.rb +6 -6
  18. data/example/example3.rb +5 -5
  19. data/example/example4.rb +2 -2
  20. data/example/example5.rb +4 -4
  21. data/example/inheritance_with_refcount.rb +35 -36
  22. data/example/progress.rb +30 -0
  23. data/example/thumb.rb +6 -6
  24. data/example/trim8.rb +1 -1
  25. data/example/watermark.rb +2 -2
  26. data/example/wobble.rb +1 -1
  27. data/lib/ruby-vips.rb +1 -1
  28. data/lib/vips.rb +191 -79
  29. data/lib/vips/blend_mode.rb +29 -25
  30. data/lib/vips/connection.rb +46 -0
  31. data/lib/vips/gobject.rb +27 -12
  32. data/lib/vips/gvalue.rb +62 -50
  33. data/lib/vips/image.rb +475 -256
  34. data/lib/vips/interpolate.rb +3 -2
  35. data/lib/vips/methods.rb +788 -121
  36. data/lib/vips/mutableimage.rb +173 -0
  37. data/lib/vips/object.rb +171 -54
  38. data/lib/vips/operation.rb +272 -117
  39. data/lib/vips/region.rb +73 -0
  40. data/lib/vips/source.rb +88 -0
  41. data/lib/vips/sourcecustom.rb +89 -0
  42. data/lib/vips/target.rb +86 -0
  43. data/lib/vips/targetcustom.rb +77 -0
  44. data/lib/vips/version.rb +1 -1
  45. data/ruby-vips.gemspec +26 -20
  46. metadata +39 -50
  47. data/.rubocop.yml +0 -22
  48. data/.rubocop_todo.yml +0 -515
  49. data/.travis.yml +0 -62
  50. data/install-vips.sh +0 -26
@@ -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,141 @@ 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
52
+
53
+ @@introspect_cache = {}
54
+
55
+ def initialize name
56
+ # if there's a trailing "!", this is a destructive version of an
57
+ # operation
58
+ if name[-1] == "!"
59
+ @destructive = true
60
+ # strip the trailing "!"
61
+ @vips_name = name[0...-1]
62
+ else
63
+ @destructive = false
64
+ @vips_name = name
65
+ end
66
+
67
+ @op = Operation.new @vips_name
68
+ @args = []
69
+ @required_input = []
70
+ @optional_input = {}
71
+ @required_output = []
72
+ @optional_output = {}
73
+
74
+ # find all the arguments the operator can take
75
+ @op.argument_map do |pspec, argument_class, _argument_instance|
76
+ flags = argument_class[:flags]
77
+ if (flags & ARGUMENT_CONSTRUCT) != 0
78
+ # names can include - as punctuation, but we always use _ in
79
+ # Ruby
80
+ arg_name = pspec[:name].tr("-", "_")
81
+ @args << {
82
+ arg_name: arg_name,
83
+ flags: flags,
84
+ gtype: pspec[:value_type]
85
+ }
86
+ end
87
+
88
+ nil
89
+ end
90
+
91
+ @args.each do |details|
92
+ arg_name = details[:arg_name]
93
+ flags = details[:flags]
94
+
95
+ if (flags & ARGUMENT_INPUT) != 0
96
+ if (flags & ARGUMENT_REQUIRED) != 0 &&
97
+ (flags & ARGUMENT_DEPRECATED) == 0
98
+ @required_input << details
99
+ else
100
+ # we allow deprecated optional args
101
+ @optional_input[arg_name] = details
102
+ end
103
+
104
+ # MODIFY INPUT args count as OUTPUT as well in non-destructive mode
105
+ if (flags & ARGUMENT_MODIFY) != 0 &&
106
+ !@destructive
107
+ if (flags & ARGUMENT_REQUIRED) != 0 &&
108
+ (flags & ARGUMENT_DEPRECATED) == 0
109
+ @required_output << details
110
+ else
111
+ @optional_output[arg_name] = details
112
+ end
113
+ end
114
+ elsif (flags & ARGUMENT_OUTPUT) != 0
115
+ if (flags & ARGUMENT_REQUIRED) != 0 &&
116
+ (flags & ARGUMENT_DEPRECATED) == 0
117
+ @required_output << details
118
+ else
119
+ # again, allow deprecated optional args
120
+ @optional_output[arg_name] = details
121
+ end
122
+ end
123
+ end
124
+
125
+ # in destructive mode, the first required input arg must be MODIFY and
126
+ # must be an image
127
+ if @destructive
128
+ if @required_input.length < 1 ||
129
+ @required_input[0][:flags] & ARGUMENT_MODIFY == 0 ||
130
+ @required_input[0][:gtype] != IMAGE_TYPE
131
+ raise Vips::Error, "operation #{@vips_name} is not destructive"
132
+ end
133
+ end
134
+ end
135
+
136
+ # Yard comment generation needs a little more introspection. We add this
137
+ # extra metadata in a separate method to keep the main path as fast as
138
+ # we can.
139
+ def add_yard_introspection name
140
+ @name = name
141
+ @description = Vips.vips_object_get_description @op
142
+ @flags = Vips.vips_operation_get_flags @op
143
+ @member_x = nil
144
+ @method_args = []
145
+
146
+ @args.each do |details|
147
+ arg_name = details[:arg_name]
148
+ flags = details[:flags]
149
+ gtype = details[:gtype]
150
+
151
+ details[:yard_name] = arg_name == "in" ? "im" : arg_name
152
+ pspec = @op.get_pspec arg_name
153
+ details[:blurb] = GObject.g_param_spec_get_blurb pspec
154
+
155
+ if (flags & ARGUMENT_INPUT) != 0 &&
156
+ (flags & ARGUMENT_REQUIRED) != 0 &&
157
+ (flags & ARGUMENT_DEPRECATED) == 0
158
+ # the first required input image is the thing we will be a method
159
+ # of
160
+ if @member_x.nil? && gtype == IMAGE_TYPE
161
+ @member_x = details
162
+ else
163
+ @method_args << details
164
+ end
165
+ end
166
+ end
167
+ end
168
+
169
+ def self.get name
170
+ @@introspect_cache[name] ||= Introspect.new name
171
+ end
172
+
173
+ def self.get_yard name
174
+ introspect = Introspect.get name
175
+ introspect.add_yard_introspection name
176
+ introspect
177
+ end
178
+ end
179
+
38
180
  class Operation < Object
39
181
  # the layout of the VipsOperation struct
40
182
  module OperationLayout
@@ -58,94 +200,73 @@ module Vips
58
200
  # allow init with a pointer so we can wrap the return values from
59
201
  # things like _build
60
202
  if value.is_a? String
61
- value = Vips::vips_operation_new value
62
- raise Vips::Error if value == nil
203
+ value = Vips.vips_operation_new value
204
+ raise Vips::Error if value.null?
63
205
  end
64
206
 
65
207
  super value
66
208
  end
67
209
 
68
210
  def build
69
- op = Vips::vips_cache_operation_build self
70
- if op == nil
211
+ op = Vips.vips_cache_operation_build self
212
+ if op.null?
213
+ Vips.vips_object_unref_outputs self
71
214
  raise Vips::Error
72
215
  end
73
216
 
74
- return Operation.new op
217
+ Operation.new op
75
218
  end
76
219
 
77
220
  def argument_map &block
78
- fn = Proc.new do |_op, pspec, argument_class, argument_instance, _a, _b|
221
+ fn = proc do |_op, pspec, argument_class, argument_instance, _a, _b|
79
222
  block.call pspec, argument_class, argument_instance
80
223
  end
81
-
82
- Vips::vips_argument_map self, fn, nil, nil
224
+ Vips.vips_argument_map self, fn, nil, nil
83
225
  end
84
226
 
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]
227
+ # Search an object for the first element to match a predicate. Search
228
+ # inside subarrays and sub-hashes. Equlvalent to x.flatten.find{}.
229
+ def self.flat_find object, &block
230
+ if object.respond_to? :each
231
+ object.each do |x|
232
+ result = flat_find x, &block
233
+ return result unless result.nil?
101
234
  end
235
+ elsif yield object
236
+ return object
102
237
  end
103
238
 
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
239
+ nil
117
240
  end
118
241
 
119
242
  # expand a constant into an image
120
243
  def self.imageize match_image, value
121
- return value if value.is_a? Image
244
+ return value if value.is_a?(Image) || value.is_a?(MutableImage)
122
245
 
123
246
  # 2D array values become tiny 2D images
124
247
  # 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
248
+ if (value.is_a?(Array) && value[0].is_a?(Array)) || match_image.nil?
249
+ Image.new_from_array value
128
250
  else
129
251
  # we have a 1D array ... use that as a pixel constant and
130
252
  # expand to match match_image
131
- return match_image.new_from_image value
253
+ match_image.new_from_image value
132
254
  end
133
255
  end
134
256
 
135
257
  # set an operation argument, expanding constants and copying images as
136
258
  # required
137
- def set name, value, match_image = nil, flags = 0
138
- gtype = get_typeof name
139
-
259
+ def set name, value, match_image, flags, gtype, destructive
140
260
  if gtype == IMAGE_TYPE
141
- value = Operation::imageize match_image, value
261
+ value = Operation.imageize match_image, value
142
262
 
143
- if (flags & ARGUMENT_MODIFY) != 0
144
- # make sure we have a unique copy
263
+ # in non-destructive mode, make sure we have a unique copy
264
+ if (flags & ARGUMENT_MODIFY) != 0 &&
265
+ !destructive
145
266
  value = value.copy.copy_memory
146
267
  end
147
268
  elsif gtype == ARRAY_IMAGE_TYPE
148
- value = value.map { |x| Operation::imageize match_image, x }
269
+ value = value.map { |x| Operation.imageize match_image, x }
149
270
  end
150
271
 
151
272
  super name, value
@@ -221,67 +342,42 @@ module Vips
221
342
  # the constant value 255.
222
343
 
223
344
  def self.call name, supplied, optional = {}, option_string = ""
224
- GLib::logger.debug("Vips::VipsOperation.call") {
225
- "name = #{name}, supplied = #{supplied}, " +
345
+ GLib.logger.debug("Vips::VipsOperation.call") {
346
+ "name = #{name}, supplied = #{supplied}, " \
226
347
  "optional = #{optional}, option_string = #{option_string}"
227
348
  }
228
349
 
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
350
+ introspect = Introspect.get name
351
+ required_input = introspect.required_input
352
+ required_output = introspect.required_output
353
+ optional_input = introspect.optional_input
354
+ optional_output = introspect.optional_output
355
+ destructive = introspect.destructive
247
356
 
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
357
  unless supplied.is_a? Array
263
- raise Vips::Error, "unable to call #{name}: " +
264
- "argument array is not an array"
358
+ raise Vips::Error, "unable to call #{name}: " \
359
+ "argument array is not an array"
265
360
  end
266
361
  unless optional.is_a? Hash
267
- raise Vips::Error, "unable to call #{name}: " +
268
- "optional arguments are not a hash"
362
+ raise Vips::Error, "unable to call #{name}: " \
363
+ "optional arguments are not a hash"
269
364
  end
365
+
270
366
  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}."
367
+ raise Vips::Error, "unable to call #{name}: " \
368
+ "you supplied #{supplied.length} arguments, " \
369
+ "but operation needs #{required_input.length}."
274
370
  end
275
371
 
276
- # very that all supplied_optional keys are in optional_input or
372
+ # all supplied_optional keys should be in optional_input or
277
373
  # optional_output
278
374
  optional.each do |key, _value|
279
375
  arg_name = key.to_s
280
376
 
281
377
  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}"
378
+ optional_output.has_key?(arg_name)
379
+ raise Vips::Error, "unable to call #{name}: " \
380
+ "unknown option #{arg_name}"
285
381
  end
286
382
  end
287
383
 
@@ -290,24 +386,68 @@ module Vips
290
386
  #
291
387
  # look inside array and hash arguments, since we may be passing an
292
388
  # array of images
293
- match_image = find_inside(supplied) do |value|
294
- value.is_a? Image
389
+ #
390
+ # also enforce the rules around mutable and non-mutable images
391
+ match_image = nil
392
+ flat_find(supplied) do |value|
393
+ if match_image
394
+ # no non-first image arg can ever be mutable
395
+ if value.is_a?(MutableImage)
396
+ raise Vips::Error, "unable to call #{name}: " \
397
+ "only the first image argument can be mutable"
398
+ end
399
+ elsif destructive
400
+ if value.is_a?(Image)
401
+ raise Vips::Error, "unable to call #{name}: " \
402
+ "first image argument to a destructive " \
403
+ "operation must be mutable"
404
+ elsif value.is_a?(MutableImage)
405
+ match_image = value
406
+ end
407
+ elsif value.is_a?(MutableImage)
408
+ # non destructive operation, so no mutable images
409
+ raise Vips::Error, "unable to call #{name}: " \
410
+ "must not pass mutable images to " \
411
+ "non-destructive operations"
412
+ elsif value.is_a?(Image)
413
+ match_image = value
414
+ end
415
+
416
+ # keep looping
417
+ false
295
418
  end
296
419
 
420
+ op = Operation.new introspect.vips_name
421
+
297
422
  # 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
423
+ unless option_string.nil?
424
+ if Vips.vips_object_set_from_string(op, option_string) != 0
300
425
  raise Vips::Error
301
426
  end
302
427
  end
303
428
 
429
+ # collect a list of all input references here
430
+ references = Set.new
431
+
432
+ add_reference = lambda do |x|
433
+ if x.is_a?(Vips::Image)
434
+ x.references.each do |i|
435
+ references << i
436
+ end
437
+ end
438
+ false
439
+ end
440
+
304
441
  # set all required inputs
305
442
  required_input.each_index do |i|
306
- arg_name = required_input[i][0]
307
- flags = required_input[i][1]
443
+ details = required_input[i]
444
+ arg_name = details[:arg_name]
445
+ flags = details[:flags]
446
+ gtype = details[:gtype]
308
447
  value = supplied[i]
309
448
 
310
- op.set arg_name, value, match_image, flags
449
+ flat_find value, &add_reference
450
+ op.set arg_name, value, match_image, flags, gtype, destructive
311
451
  end
312
452
 
313
453
  # set all optional inputs
@@ -317,18 +457,31 @@ module Vips
317
457
  arg_name = key.to_s
318
458
 
319
459
  if optional_input.has_key? arg_name
320
- flags = optional_input[arg_name]
460
+ details = optional_input[arg_name]
461
+ flags = details[:flags]
462
+ gtype = details[:gtype]
321
463
 
322
- op.set arg_name, value, match_image, flags
464
+ flat_find value, &add_reference
465
+ op.set arg_name, value, match_image, flags, gtype, destructive
323
466
  end
324
467
  end
325
468
 
326
469
  op = op.build
327
470
 
471
+ # attach all input refs to output x
472
+ set_reference = lambda do |x|
473
+ if x.is_a? Vips::Image
474
+ x.references += references
475
+ end
476
+ false
477
+ end
478
+
328
479
  # get all required results
329
480
  result = []
330
- required_output.each do |arg_name, _flags|
331
- result << op.get(arg_name)
481
+ required_output.each do |details|
482
+ value = details[:arg_name]
483
+ flat_find value, &set_reference
484
+ result << op.get(value)
332
485
  end
333
486
 
334
487
  # fetch all optional ones
@@ -337,7 +490,9 @@ module Vips
337
490
  arg_name = key.to_s
338
491
 
339
492
  if optional_output.has_key? arg_name
340
- optional_results[arg_name] = op.get arg_name
493
+ value = op.get arg_name
494
+ flat_find value, &set_reference
495
+ optional_results[arg_name] = value
341
496
  end
342
497
  end
343
498
 
@@ -349,11 +504,11 @@ module Vips
349
504
  result = nil
350
505
  end
351
506
 
352
- GLib::logger.debug("Vips::Operation.call") { "result = #{result}" }
507
+ GLib.logger.debug("Vips::Operation.call") { "result = #{result}" }
353
508
 
354
- Vips::vips_object_unref_outputs op
509
+ Vips.vips_object_unref_outputs op
355
510
 
356
- return result
511
+ result
357
512
  end
358
513
  end
359
514
  end