ruby-vips 2.0.14 → 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
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