ruby-vips 2.0.17 → 2.1.0

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 +13 -0
  7. data/Gemfile +3 -1
  8. data/README.md +4 -4
  9. data/Rakefile +13 -21
  10. data/TODO +3 -6
  11. data/VERSION +1 -1
  12. data/example/annotate.rb +6 -6
  13. data/example/connection.rb +18 -9
  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 +3 -3
  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 +121 -75
  29. data/lib/vips/blend_mode.rb +29 -25
  30. data/lib/vips/connection.rb +4 -4
  31. data/lib/vips/gobject.rb +18 -11
  32. data/lib/vips/gvalue.rb +54 -54
  33. data/lib/vips/image.rb +232 -155
  34. data/lib/vips/interpolate.rb +3 -2
  35. data/lib/vips/methods.rb +165 -15
  36. data/lib/vips/mutableimage.rb +154 -0
  37. data/lib/vips/object.rb +84 -85
  38. data/lib/vips/operation.rb +161 -82
  39. data/lib/vips/region.rb +6 -6
  40. data/lib/vips/source.rb +11 -12
  41. data/lib/vips/sourcecustom.rb +7 -8
  42. data/lib/vips/target.rb +12 -13
  43. data/lib/vips/targetcustom.rb +9 -10
  44. data/lib/vips/version.rb +1 -1
  45. data/ruby-vips.gemspec +26 -22
  46. metadata +28 -48
  47. data/.rubocop.yml +0 -22
  48. data/.rubocop_todo.yml +0 -473
  49. data/.travis.yml +0 -57
  50. data/install-vips.sh +0 -26
@@ -4,7 +4,8 @@
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
@@ -12,22 +13,22 @@ module Vips
12
13
  attach_function :vips_operation_new, [:string], :pointer
13
14
 
14
15
  # 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
+ # libvips might trigger some signals which ruby has handles for.
16
17
  #
17
- # We need FFI to drop the GIL lock during this call and reacquire it when
18
+ # We need FFI to drop the GIL lock during this call and reacquire it when
18
19
  # the call ends, or we'll deadlock.
19
- attach_function :vips_cache_operation_build, [:pointer], :pointer,
20
+ attach_function :vips_cache_operation_build, [:pointer], :pointer,
20
21
  blocking: true
21
22
  attach_function :vips_object_unref_outputs, [:pointer], :void
22
23
 
23
24
  callback :argument_map_fn, [:pointer,
24
- GObject::GParamSpec.ptr,
25
- ArgumentClass.ptr,
26
- ArgumentInstance.ptr,
27
- :pointer, :pointer], :pointer
25
+ GObject::GParamSpec.ptr,
26
+ ArgumentClass.ptr,
27
+ ArgumentInstance.ptr,
28
+ :pointer, :pointer], :pointer
28
29
  attach_function :vips_argument_map, [:pointer,
29
- :argument_map_fn,
30
- :pointer, :pointer], :pointer
30
+ :argument_map_fn,
31
+ :pointer, :pointer], :pointer
31
32
 
32
33
  OPERATION_SEQUENTIAL = 1
33
34
  OPERATION_NOCACHE = 4
@@ -45,14 +46,25 @@ module Vips
45
46
  # everything we know about it. This is used for doc generation as well as
46
47
  # call.
47
48
  class Introspect
48
- attr_reader :name, :description, :flags, :args, :required_input,
49
- :optional_input, :required_output, :optional_output, :member_x,
50
- :method_args
49
+ attr_reader :name, :description, :flags, :args, :required_input,
50
+ :optional_input, :required_output, :optional_output, :member_x,
51
+ :method_args, :vips_name, :destructive
51
52
 
52
53
  @@introspect_cache = {}
53
54
 
54
55
  def initialize name
55
- @op = Operation.new 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
56
68
  @args = []
57
69
  @required_input = []
58
70
  @optional_input = {}
@@ -66,12 +78,14 @@ module Vips
66
78
  # names can include - as punctuation, but we always use _ in
67
79
  # Ruby
68
80
  arg_name = pspec[:name].tr("-", "_")
69
- args << {
70
- :arg_name => arg_name,
71
- :flags => flags,
72
- :gtype => pspec[:value_type]
81
+ @args << {
82
+ arg_name: arg_name,
83
+ flags: flags,
84
+ gtype: pspec[:value_type]
73
85
  }
74
86
  end
87
+
88
+ nil
75
89
  end
76
90
 
77
91
  @args.each do |details|
@@ -79,26 +93,27 @@ module Vips
79
93
  flags = details[:flags]
80
94
 
81
95
  if (flags & ARGUMENT_INPUT) != 0
82
- if (flags & ARGUMENT_REQUIRED) != 0 &&
83
- (flags & ARGUMENT_DEPRECATED) == 0
96
+ if (flags & ARGUMENT_REQUIRED) != 0 &&
97
+ (flags & ARGUMENT_DEPRECATED) == 0
84
98
  @required_input << details
85
99
  else
86
100
  # we allow deprecated optional args
87
101
  @optional_input[arg_name] = details
88
102
  end
89
103
 
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
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
94
109
  @required_output << details
95
110
  else
96
111
  @optional_output[arg_name] = details
97
112
  end
98
113
  end
99
114
  elsif (flags & ARGUMENT_OUTPUT) != 0
100
- if (flags & ARGUMENT_REQUIRED) != 0 &&
101
- (flags & ARGUMENT_DEPRECATED) == 0
115
+ if (flags & ARGUMENT_REQUIRED) != 0 &&
116
+ (flags & ARGUMENT_DEPRECATED) == 0
102
117
  @required_output << details
103
118
  else
104
119
  # again, allow deprecated optional args
@@ -106,6 +121,16 @@ module Vips
106
121
  end
107
122
  end
108
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
109
134
  end
110
135
 
111
136
  # Yard comment generation needs a little more introspection. We add this
@@ -113,8 +138,8 @@ module Vips
113
138
  # we can.
114
139
  def add_yard_introspection name
115
140
  @name = name
116
- @description = Vips::vips_object_get_description @op
117
- @flags = Vips::vips_operation_get_flags @op
141
+ @description = Vips.vips_object_get_description @op
142
+ @flags = Vips.vips_operation_get_flags @op
118
143
  @member_x = nil
119
144
  @method_args = []
120
145
 
@@ -125,15 +150,15 @@ module Vips
125
150
 
126
151
  details[:yard_name] = arg_name == "in" ? "im" : arg_name
127
152
  pspec = @op.get_pspec arg_name
128
- details[:blurb] = GObject::g_param_spec_get_blurb pspec
153
+ details[:blurb] = GObject.g_param_spec_get_blurb pspec
129
154
 
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
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
134
159
  # of
135
- if @member_x == nil && gtype == IMAGE_TYPE
136
- @member_x = details
160
+ if @member_x.nil? && gtype == IMAGE_TYPE
161
+ @member_x = details
137
162
  else
138
163
  @method_args << details
139
164
  end
@@ -150,7 +175,6 @@ module Vips
150
175
  introspect.add_yard_introspection name
151
176
  introspect
152
177
  end
153
-
154
178
  end
155
179
 
156
180
  class Operation < Object
@@ -176,7 +200,7 @@ module Vips
176
200
  # allow init with a pointer so we can wrap the return values from
177
201
  # things like _build
178
202
  if value.is_a? String
179
- value = Vips::vips_operation_new value
203
+ value = Vips.vips_operation_new value
180
204
  raise Vips::Error if value.null?
181
205
  end
182
206
 
@@ -184,66 +208,65 @@ module Vips
184
208
  end
185
209
 
186
210
  def build
187
- op = Vips::vips_cache_operation_build self
211
+ op = Vips.vips_cache_operation_build self
188
212
  if op.null?
189
- Vips::vips_object_unref_outputs self
213
+ Vips.vips_object_unref_outputs self
190
214
  raise Vips::Error
191
215
  end
192
216
 
193
- return Operation.new op
217
+ Operation.new op
194
218
  end
195
219
 
196
220
  def argument_map &block
197
- fn = Proc.new do |_op, pspec, argument_class, argument_instance, _a, _b|
221
+ fn = proc do |_op, pspec, argument_class, argument_instance, _a, _b|
198
222
  block.call pspec, argument_class, argument_instance
199
223
  end
200
-
201
- Vips::vips_argument_map self, fn, nil, nil
224
+ Vips.vips_argument_map self, fn, nil, nil
202
225
  end
203
226
 
204
- # Search an object for the first element to match a predicate. Search
227
+ # Search an object for the first element to match a predicate. Search
205
228
  # inside subarrays and sub-hashes. Equlvalent to x.flatten.find{}.
206
229
  def self.flat_find object, &block
207
230
  if object.respond_to? :each
208
- object.each do |x|
209
- result = flat_find x, &block
210
- return result if result != nil
231
+ object.each do |x|
232
+ result = flat_find x, &block
233
+ return result unless result.nil?
211
234
  end
212
- else
213
- return object if yield object
235
+ elsif yield object
236
+ return object
214
237
  end
215
238
 
216
- return nil
239
+ nil
217
240
  end
218
241
 
219
242
  # expand a constant into an image
220
243
  def self.imageize match_image, value
221
- return value if value.is_a? Image
244
+ return value if value.is_a?(Image) || value.is_a?(MutableImage)
222
245
 
223
246
  # 2D array values become tiny 2D images
224
247
  # if there's nothing to match to, we also make a 2D image
225
- if (value.is_a?(Array) && value[0].is_a?(Array)) ||
226
- match_image == nil
227
- 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
228
250
  else
229
251
  # we have a 1D array ... use that as a pixel constant and
230
252
  # expand to match match_image
231
- return match_image.new_from_image value
253
+ match_image.new_from_image value
232
254
  end
233
255
  end
234
256
 
235
257
  # set an operation argument, expanding constants and copying images as
236
258
  # required
237
- def set name, value, match_image, flags, gtype
259
+ def set name, value, match_image, flags, gtype, destructive
238
260
  if gtype == IMAGE_TYPE
239
- value = Operation::imageize match_image, value
261
+ value = Operation.imageize match_image, value
240
262
 
241
- if (flags & ARGUMENT_MODIFY) != 0
242
- # 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
243
266
  value = value.copy.copy_memory
244
267
  end
245
268
  elsif gtype == ARRAY_IMAGE_TYPE
246
- value = value.map { |x| Operation::imageize match_image, x }
269
+ value = value.map { |x| Operation.imageize match_image, x }
247
270
  end
248
271
 
249
272
  super name, value
@@ -319,8 +342,8 @@ module Vips
319
342
  # the constant value 255.
320
343
 
321
344
  def self.call name, supplied, optional = {}, option_string = ""
322
- GLib::logger.debug("Vips::VipsOperation.call") {
323
- "name = #{name}, supplied = #{supplied}, " +
345
+ GLib.logger.debug("Vips::VipsOperation.call") {
346
+ "name = #{name}, supplied = #{supplied}, " \
324
347
  "optional = #{optional}, option_string = #{option_string}"
325
348
  }
326
349
 
@@ -329,20 +352,21 @@ module Vips
329
352
  required_output = introspect.required_output
330
353
  optional_input = introspect.optional_input
331
354
  optional_output = introspect.optional_output
355
+ destructive = introspect.destructive
332
356
 
333
357
  unless supplied.is_a? Array
334
- raise Vips::Error, "unable to call #{name}: " +
335
- "argument array is not an array"
358
+ raise Vips::Error, "unable to call #{name}: " \
359
+ "argument array is not an array"
336
360
  end
337
361
  unless optional.is_a? Hash
338
- raise Vips::Error, "unable to call #{name}: " +
339
- "optional arguments are not a hash"
362
+ raise Vips::Error, "unable to call #{name}: " \
363
+ "optional arguments are not a hash"
340
364
  end
341
365
 
342
366
  if supplied.length != required_input.length
343
- raise Vips::Error, "unable to call #{name}: " +
344
- "you supplied #{supplied.length} arguments, " +
345
- "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}."
346
370
  end
347
371
 
348
372
  # all supplied_optional keys should be in optional_input or
@@ -352,8 +376,8 @@ module Vips
352
376
 
353
377
  unless optional_input.has_key?(arg_name) ||
354
378
  optional_output.has_key?(arg_name)
355
- raise Vips::Error, "unable to call #{name}: " +
356
- "unknown option #{arg_name}"
379
+ raise Vips::Error, "unable to call #{name}: " \
380
+ "unknown option #{arg_name}"
357
381
  end
358
382
  end
359
383
 
@@ -362,17 +386,58 @@ module Vips
362
386
  #
363
387
  # look inside array and hash arguments, since we may be passing an
364
388
  # array of images
365
- match_image = flat_find(supplied) { |value| 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
418
+ end
366
419
 
367
- op = Operation.new name
420
+ op = Operation.new introspect.vips_name
368
421
 
369
422
  # set any string args first so they can't be overridden
370
- if option_string != nil
371
- 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
372
425
  raise Vips::Error
373
426
  end
374
427
  end
375
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
+
376
441
  # set all required inputs
377
442
  required_input.each_index do |i|
378
443
  details = required_input[i]
@@ -381,7 +446,8 @@ module Vips
381
446
  gtype = details[:gtype]
382
447
  value = supplied[i]
383
448
 
384
- op.set arg_name, value, match_image, flags, gtype
449
+ flat_find value, &add_reference
450
+ op.set arg_name, value, match_image, flags, gtype, destructive
385
451
  end
386
452
 
387
453
  # set all optional inputs
@@ -395,16 +461,27 @@ module Vips
395
461
  flags = details[:flags]
396
462
  gtype = details[:gtype]
397
463
 
398
- op.set arg_name, value, match_image, flags, gtype
464
+ flat_find value, &add_reference
465
+ op.set arg_name, value, match_image, flags, gtype, destructive
399
466
  end
400
467
  end
401
468
 
402
469
  op = op.build
403
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
+
404
479
  # get all required results
405
480
  result = []
406
481
  required_output.each do |details|
407
- result << op.get(details[:arg_name])
482
+ value = details[:arg_name]
483
+ flat_find value, &set_reference
484
+ result << op.get(value)
408
485
  end
409
486
 
410
487
  # fetch all optional ones
@@ -413,7 +490,9 @@ module Vips
413
490
  arg_name = key.to_s
414
491
 
415
492
  if optional_output.has_key? arg_name
416
- 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
417
496
  end
418
497
  end
419
498
 
@@ -425,11 +504,11 @@ module Vips
425
504
  result = nil
426
505
  end
427
506
 
428
- GLib::logger.debug("Vips::Operation.call") { "result = #{result}" }
507
+ GLib.logger.debug("Vips::Operation.call") { "result = #{result}" }
429
508
 
430
- Vips::vips_object_unref_outputs op
509
+ Vips.vips_object_unref_outputs op
431
510
 
432
- return result
511
+ result
433
512
  end
434
513
  end
435
514
  end