ruby-vips 2.0.17 → 2.1.3

Sign up to get free protection for your applications and to get access to all the features.
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 +26 -0
  7. data/Gemfile +3 -1
  8. data/README.md +12 -11
  9. data/Rakefile +13 -21
  10. data/TODO +4 -8
  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 +46 -39
  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 +127 -76
  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 +289 -165
  34. data/lib/vips/interpolate.rb +3 -2
  35. data/lib/vips/methods.rb +484 -107
  36. data/lib/vips/mutableimage.rb +173 -0
  37. data/lib/vips/object.rb +86 -93
  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 +29 -49
  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