ruby-vips 2.0.14 → 2.1.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.
Files changed (70) 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 +41 -0
  7. data/Gemfile +3 -1
  8. data/README.md +42 -41
  9. data/Rakefile +13 -21
  10. data/TODO +18 -10
  11. data/VERSION +1 -1
  12. data/example/annotate.rb +6 -6
  13. data/example/connection.rb +26 -0
  14. data/example/daltonize8.rb +16 -18
  15. data/example/draw_lines.rb +30 -0
  16. data/example/example1.rb +5 -6
  17. data/example/example2.rb +6 -6
  18. data/example/example3.rb +5 -5
  19. data/example/example4.rb +4 -4
  20. data/example/example5.rb +6 -7
  21. data/example/inheritance_with_refcount.rb +36 -54
  22. data/example/progress.rb +30 -0
  23. data/example/thumb.rb +8 -10
  24. data/example/trim8.rb +5 -5
  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 +199 -93
  29. data/lib/vips/align.rb +0 -1
  30. data/lib/vips/angle.rb +0 -1
  31. data/lib/vips/angle45.rb +0 -1
  32. data/lib/vips/bandformat.rb +0 -2
  33. data/lib/vips/blend_mode.rb +29 -27
  34. data/lib/vips/coding.rb +0 -1
  35. data/lib/vips/compass_direction.rb +0 -1
  36. data/lib/vips/connection.rb +46 -0
  37. data/lib/vips/direction.rb +0 -1
  38. data/lib/vips/extend.rb +0 -1
  39. data/lib/vips/gobject.rb +26 -15
  40. data/lib/vips/gvalue.rb +61 -55
  41. data/lib/vips/image.rb +455 -282
  42. data/lib/vips/interesting.rb +0 -1
  43. data/lib/vips/interpolate.rb +3 -7
  44. data/lib/vips/interpretation.rb +0 -1
  45. data/lib/vips/kernel.rb +0 -1
  46. data/lib/vips/methods.rb +791 -124
  47. data/lib/vips/mutableimage.rb +173 -0
  48. data/lib/vips/object.rb +178 -68
  49. data/lib/vips/operation.rb +277 -130
  50. data/lib/vips/operationboolean.rb +0 -1
  51. data/lib/vips/operationcomplex.rb +0 -1
  52. data/lib/vips/operationcomplex2.rb +0 -1
  53. data/lib/vips/operationcomplexget.rb +0 -1
  54. data/lib/vips/operationmath.rb +0 -1
  55. data/lib/vips/operationmath2.rb +0 -1
  56. data/lib/vips/operationrelational.rb +0 -1
  57. data/lib/vips/operationround.rb +0 -1
  58. data/lib/vips/region.rb +73 -0
  59. data/lib/vips/size.rb +0 -1
  60. data/lib/vips/source.rb +88 -0
  61. data/lib/vips/sourcecustom.rb +89 -0
  62. data/lib/vips/target.rb +86 -0
  63. data/lib/vips/targetcustom.rb +77 -0
  64. data/lib/vips/version.rb +1 -2
  65. data/ruby-vips.gemspec +26 -21
  66. metadata +39 -51
  67. data/.rubocop.yml +0 -10
  68. data/.rubocop_todo.yml +0 -730
  69. data/.travis.yml +0 -62
  70. data/install-vips.sh +0 -26
@@ -4,39 +4,180 @@
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
28
35
  OPERATION_DEPRECATED = 8
29
36
 
30
37
  OPERATION_FLAGS = {
31
- :sequential => OPERATION_SEQUENTIAL,
32
- :nocache => OPERATION_NOCACHE,
33
- :deprecated => OPERATION_DEPRECATED
38
+ sequential: OPERATION_SEQUENTIAL,
39
+ nocache: OPERATION_NOCACHE,
40
+ deprecated: OPERATION_DEPRECATED
34
41
  }
35
42
 
36
43
  attach_function :vips_operation_get_flags, [:pointer], :int
37
44
 
38
- class Operation < Object
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
39
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
+
180
+ class Operation < Object
40
181
  # the layout of the VipsOperation struct
41
182
  module OperationLayout
42
183
  def self.included base
@@ -49,106 +190,83 @@ module Vips
49
190
 
50
191
  class Struct < Object::Struct
51
192
  include OperationLayout
52
-
53
193
  end
54
194
 
55
195
  class ManagedStruct < Object::ManagedStruct
56
196
  include OperationLayout
57
-
58
197
  end
59
198
 
60
199
  def initialize value
61
200
  # allow init with a pointer so we can wrap the return values from
62
201
  # things like _build
63
202
  if value.is_a? String
64
- value = Vips::vips_operation_new value
65
- raise Vips::Error if value == nil
203
+ value = Vips.vips_operation_new value
204
+ raise Vips::Error if value.null?
66
205
  end
67
206
 
68
207
  super value
69
208
  end
70
209
 
71
210
  def build
72
- op = Vips::vips_cache_operation_build self
73
- if op == nil
211
+ op = Vips.vips_cache_operation_build self
212
+ if op.null?
213
+ Vips.vips_object_unref_outputs self
74
214
  raise Vips::Error
75
215
  end
76
216
 
77
- return Operation.new op
217
+ Operation.new op
78
218
  end
79
219
 
80
220
  def argument_map &block
81
- fn = Proc.new do |op, pspec, argument_class, argument_instance, a, b|
221
+ fn = proc do |_op, pspec, argument_class, argument_instance, _a, _b|
82
222
  block.call pspec, argument_class, argument_instance
83
223
  end
84
-
85
- Vips::vips_argument_map self, fn, nil, nil
86
- end
87
-
88
- def get_flags
89
- Vips::vips_operation_get_flags self
224
+ Vips.vips_argument_map self, fn, nil, nil
90
225
  end
91
226
 
92
- # not quick! try to call this infrequently
93
- def get_construct_args
94
- args = []
95
-
96
- argument_map do |pspec, argument_class, argument_instance|
97
- flags = argument_class[:flags]
98
- if (flags & ARGUMENT_CONSTRUCT) != 0
99
- # names can include - as punctuation, but we always use _ in
100
- # Ruby
101
- name = pspec[:name].tr("-", "_")
102
-
103
- 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?
104
234
  end
235
+ elsif yield object
236
+ return object
105
237
  end
106
238
 
107
- return args
108
- end
109
-
110
- # search array for the first element to match a predicate ...
111
- # search inside subarrays and sub-hashes
112
- def self.find_inside object, &block
113
- return object if block.call object
114
-
115
- if object.is_a? Enumerable
116
- object.find {|value| block.call value, block}
117
- end
118
-
119
- return nil
239
+ nil
120
240
  end
121
241
 
122
242
  # expand a constant into an image
123
243
  def self.imageize match_image, value
124
- return value if value.is_a? Image
244
+ return value if value.is_a?(Image) || value.is_a?(MutableImage)
125
245
 
126
246
  # 2D array values become tiny 2D images
127
247
  # if there's nothing to match to, we also make a 2D image
128
- if (value.is_a?(Array) && value[0].is_a?(Array)) ||
129
- match_image == nil
130
- 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
131
250
  else
132
251
  # we have a 1D array ... use that as a pixel constant and
133
252
  # expand to match match_image
134
- return match_image.new_from_image value
253
+ match_image.new_from_image value
135
254
  end
136
255
  end
137
256
 
138
257
  # set an operation argument, expanding constants and copying images as
139
258
  # required
140
- def set name, value, match_image = nil, flags = 0
141
- gtype = get_typeof name
142
-
259
+ def set name, value, match_image, flags, gtype, destructive
143
260
  if gtype == IMAGE_TYPE
144
- value = Operation::imageize match_image, value
261
+ value = Operation.imageize match_image, value
145
262
 
146
- if (flags & ARGUMENT_MODIFY) != 0
147
- # 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
148
266
  value = value.copy.copy_memory
149
267
  end
150
268
  elsif gtype == ARRAY_IMAGE_TYPE
151
- value = value.map {|x| Operation::imageize match_image, x}
269
+ value = value.map { |x| Operation.imageize match_image, x }
152
270
  end
153
271
 
154
272
  super name, value
@@ -224,68 +342,42 @@ module Vips
224
342
  # the constant value 255.
225
343
 
226
344
  def self.call name, supplied, optional = {}, option_string = ""
227
- GLib::logger.debug("Vips::VipsOperation.call") {
228
- "name = #{name}, supplied = #{supplied}, " +
229
- "optional = #{optional}, option_string = #{option_string}"
345
+ GLib.logger.debug("Vips::VipsOperation.call") {
346
+ "name = #{name}, supplied = #{supplied}, " \
347
+ "optional = #{optional}, option_string = #{option_string}"
230
348
  }
231
349
 
232
- op = Operation.new name
233
-
234
- # find and classify all the arguments the operator can take
235
- args = op.get_construct_args
236
- required_input = []
237
- optional_input = {}
238
- required_output = []
239
- optional_output = {}
240
- args.each do |name, flags|
241
- next if (flags & ARGUMENT_DEPRECATED) != 0
242
-
243
- if (flags & ARGUMENT_INPUT) != 0
244
- if (flags & ARGUMENT_REQUIRED) != 0
245
- required_input << [name, flags]
246
- else
247
- optional_input[name] = flags
248
- end
249
- end
250
-
251
- # MODIFY INPUT args count as OUTPUT as well
252
- if (flags & ARGUMENT_OUTPUT) != 0 ||
253
- ((flags & ARGUMENT_INPUT) != 0 &&
254
- (flags & ARGUMENT_MODIFY) != 0)
255
- if (flags & ARGUMENT_REQUIRED) != 0
256
- required_output << [name, flags]
257
- else
258
- optional_output[name] = flags
259
- end
260
- end
261
-
262
- 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
263
356
 
264
- # so we should have been supplied with n_required_input values, or
265
- # n_required_input + 1 if there's a hash of options at the end
266
357
  unless supplied.is_a? Array
267
- raise Vips::Error, "unable to call #{name}: " +
268
- "argument array is not an array"
358
+ raise Vips::Error, "unable to call #{name}: " \
359
+ "argument array is not an array"
269
360
  end
270
361
  unless optional.is_a? Hash
271
- raise Vips::Error, "unable to call #{name}: " +
272
- "optional arguments are not a hash"
362
+ raise Vips::Error, "unable to call #{name}: " \
363
+ "optional arguments are not a hash"
273
364
  end
365
+
274
366
  if supplied.length != required_input.length
275
- raise Vips::Error, "unable to call #{name}: " +
276
- "you supplied #{supplied.length} arguments, " +
277
- "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}."
278
370
  end
279
371
 
280
- # very that all supplied_optional keys are in optional_input or
372
+ # all supplied_optional keys should be in optional_input or
281
373
  # optional_output
282
- optional.each do |key, value|
374
+ optional.each do |key, _value|
283
375
  arg_name = key.to_s
284
376
 
285
377
  unless optional_input.has_key?(arg_name) ||
286
378
  optional_output.has_key?(arg_name)
287
- raise Vips::Error, "unable to call #{name}: " +
288
- "unknown option #{arg_name}"
379
+ raise Vips::Error, "unable to call #{name}: " \
380
+ "unknown option #{arg_name}"
289
381
  end
290
382
  end
291
383
 
@@ -294,24 +386,68 @@ module Vips
294
386
  #
295
387
  # look inside array and hash arguments, since we may be passing an
296
388
  # array of images
297
- match_image = find_inside(supplied) do |value|
298
- 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
299
418
  end
300
419
 
420
+ op = Operation.new introspect.vips_name
421
+
301
422
  # set any string args first so they can't be overridden
302
- if option_string != nil
303
- 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
304
425
  raise Vips::Error
305
426
  end
306
427
  end
307
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
+
308
441
  # set all required inputs
309
442
  required_input.each_index do |i|
310
- arg_name = required_input[i][0]
311
- flags = required_input[i][1]
443
+ details = required_input[i]
444
+ arg_name = details[:arg_name]
445
+ flags = details[:flags]
446
+ gtype = details[:gtype]
312
447
  value = supplied[i]
313
448
 
314
- 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
315
451
  end
316
452
 
317
453
  # set all optional inputs
@@ -321,29 +457,42 @@ module Vips
321
457
  arg_name = key.to_s
322
458
 
323
459
  if optional_input.has_key? arg_name
324
- flags = optional_input[arg_name]
460
+ details = optional_input[arg_name]
461
+ flags = details[:flags]
462
+ gtype = details[:gtype]
325
463
 
326
- 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
327
466
  end
328
467
  end
329
468
 
330
469
  op = op.build
331
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
+
332
479
  # get all required results
333
480
  result = []
334
- required_output.each do |arg_name, flags|
335
- 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)
336
485
  end
337
486
 
338
487
  # fetch all optional ones
339
488
  optional_results = {}
340
- optional.each do |key, value|
489
+ optional.each do |key, _value|
341
490
  arg_name = key.to_s
342
491
 
343
492
  if optional_output.has_key? arg_name
344
- flags = optional_output[arg_name]
345
-
346
- 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
347
496
  end
348
497
  end
349
498
 
@@ -355,13 +504,11 @@ module Vips
355
504
  result = nil
356
505
  end
357
506
 
358
- GLib::logger.debug("Vips::Operation.call") {"result = #{result}"}
507
+ GLib.logger.debug("Vips::Operation.call") { "result = #{result}" }
359
508
 
360
- Vips::vips_object_unref_outputs op
509
+ Vips.vips_object_unref_outputs op
361
510
 
362
- return result
511
+ result
363
512
  end
364
-
365
513
  end
366
-
367
514
  end