ruby-vips 2.0.13 → 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 (69) 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 +44 -0
  7. data/Gemfile +3 -1
  8. data/README.md +45 -47
  9. data/Rakefile +13 -15
  10. data/TODO +19 -10
  11. data/VERSION +1 -1
  12. data/example/annotate.rb +7 -7
  13. data/example/connection.rb +26 -0
  14. data/example/daltonize8.rb +27 -29
  15. data/example/draw_lines.rb +30 -0
  16. data/example/example1.rb +5 -6
  17. data/example/example2.rb +11 -11
  18. data/example/example3.rb +9 -9
  19. data/example/example4.rb +8 -8
  20. data/example/example5.rb +8 -9
  21. data/example/inheritance_with_refcount.rb +203 -221
  22. data/example/progress.rb +30 -0
  23. data/example/thumb.rb +12 -14
  24. data/example/trim8.rb +7 -7
  25. data/example/watermark.rb +15 -36
  26. data/example/wobble.rb +25 -25
  27. data/lib/ruby-vips.rb +1 -1
  28. data/lib/vips.rb +473 -338
  29. data/lib/vips/access.rb +9 -9
  30. data/lib/vips/align.rb +7 -8
  31. data/lib/vips/angle.rb +8 -9
  32. data/lib/vips/angle45.rb +12 -13
  33. data/lib/vips/bandformat.rb +16 -18
  34. data/lib/vips/blend_mode.rb +36 -0
  35. data/lib/vips/coding.rb +11 -12
  36. data/lib/vips/compass_direction.rb +13 -14
  37. data/lib/vips/connection.rb +46 -0
  38. data/lib/vips/direction.rb +7 -8
  39. data/lib/vips/extend.rb +13 -14
  40. data/lib/vips/gobject.rb +111 -100
  41. data/lib/vips/gvalue.rb +243 -237
  42. data/lib/vips/image.rb +1501 -1338
  43. data/lib/vips/interesting.rb +10 -11
  44. data/lib/vips/interpolate.rb +50 -54
  45. data/lib/vips/interpretation.rb +25 -26
  46. data/lib/vips/kernel.rb +18 -19
  47. data/lib/vips/methods.rb +929 -309
  48. data/lib/vips/mutableimage.rb +154 -0
  49. data/lib/vips/object.rb +318 -208
  50. data/lib/vips/operation.rb +467 -320
  51. data/lib/vips/operationboolean.rb +10 -11
  52. data/lib/vips/operationcomplex.rb +8 -9
  53. data/lib/vips/operationcomplex2.rb +6 -7
  54. data/lib/vips/operationcomplexget.rb +7 -8
  55. data/lib/vips/operationmath.rb +14 -15
  56. data/lib/vips/operationmath2.rb +6 -7
  57. data/lib/vips/operationrelational.rb +11 -12
  58. data/lib/vips/operationround.rb +7 -8
  59. data/lib/vips/region.rb +73 -0
  60. data/lib/vips/size.rb +9 -10
  61. data/lib/vips/source.rb +88 -0
  62. data/lib/vips/sourcecustom.rb +89 -0
  63. data/lib/vips/target.rb +86 -0
  64. data/lib/vips/targetcustom.rb +77 -0
  65. data/lib/vips/version.rb +1 -2
  66. data/ruby-vips.gemspec +29 -20
  67. metadata +51 -40
  68. data/.travis.yml +0 -55
  69. data/install-vips.sh +0 -26
@@ -4,364 +4,511 @@
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
- private
11
-
12
- attach_function :vips_operation_new, [:string], :pointer
13
-
14
- attach_function :vips_cache_operation_build, [:pointer], :pointer
15
- attach_function :vips_object_unref_outputs, [:pointer], :void
16
-
17
- callback :argument_map_fn, [:pointer,
18
- GObject::GParamSpec.ptr,
19
- ArgumentClass.ptr,
20
- ArgumentInstance.ptr,
21
- :pointer, :pointer], :pointer
22
- attach_function :vips_argument_map, [:pointer,
23
- :argument_map_fn,
24
- :pointer, :pointer], :pointer
25
-
26
- OPERATION_SEQUENTIAL = 1
27
- OPERATION_NOCACHE = 4
28
- OPERATION_DEPRECATED = 8
29
-
30
- OPERATION_FLAGS = {
31
- :sequential => OPERATION_SEQUENTIAL,
32
- :nocache => OPERATION_NOCACHE,
33
- :deprecated => OPERATION_DEPRECATED
34
- }
35
-
36
- attach_function :vips_operation_get_flags, [:pointer], :int
37
-
38
- class Operation < Object
39
-
40
- # the layout of the VipsOperation struct
41
- module OperationLayout
42
- def self.included base
43
- base.class_eval do
44
- layout :parent, Object::Struct
45
- # rest opaque
46
- end
47
- end
11
+ private
12
+
13
+ attach_function :vips_operation_new, [:string], :pointer
14
+
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
22
+ attach_function :vips_object_unref_outputs, [:pointer], :void
23
+
24
+ callback :argument_map_fn, [:pointer,
25
+ GObject::GParamSpec.ptr,
26
+ ArgumentClass.ptr,
27
+ ArgumentInstance.ptr,
28
+ :pointer, :pointer], :pointer
29
+ attach_function :vips_argument_map, [:pointer,
30
+ :argument_map_fn,
31
+ :pointer, :pointer], :pointer
32
+
33
+ OPERATION_SEQUENTIAL = 1
34
+ OPERATION_NOCACHE = 4
35
+ OPERATION_DEPRECATED = 8
36
+
37
+ OPERATION_FLAGS = {
38
+ sequential: OPERATION_SEQUENTIAL,
39
+ nocache: OPERATION_NOCACHE,
40
+ deprecated: OPERATION_DEPRECATED
41
+ }
42
+
43
+ attach_function :vips_operation_get_flags, [:pointer], :int
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
+ }
48
86
  end
49
87
 
50
- class Struct < Object::Struct
51
- include OperationLayout
52
-
53
- end
54
-
55
- class ManagedStruct < Object::ManagedStruct
56
- include OperationLayout
57
-
58
- end
59
-
60
- def initialize value
61
- # allow init with a pointer so we can wrap the return values from
62
- # things like _build
63
- if value.is_a? String
64
- value = Vips::vips_operation_new value
65
- raise Vips::Error if value == nil
66
- end
67
-
68
- super value
69
- end
70
-
71
- def build
72
- op = Vips::vips_cache_operation_build self
73
- if op == nil
74
- raise Vips::Error
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
75
112
  end
76
-
77
- return Operation.new op
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
78
122
  end
79
-
80
- def argument_map &block
81
- fn = Proc.new do |op, pspec, argument_class, argument_instance, a, b|
82
- block.call pspec, argument_class, argument_instance
83
- end
84
-
85
- Vips::vips_argument_map self, fn, nil, nil
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"
86
132
  end
133
+ end
134
+ end
87
135
 
88
- def get_flags
89
- Vips::vips_operation_get_flags self
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
90
165
  end
166
+ end
167
+ end
91
168
 
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]
104
- end
105
- end
169
+ def self.get name
170
+ @@introspect_cache[name] ||= Introspect.new name
171
+ end
106
172
 
107
- return args
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
181
+ # the layout of the VipsOperation struct
182
+ module OperationLayout
183
+ def self.included base
184
+ base.class_eval do
185
+ layout :parent, Object::Struct
186
+ # rest opaque
108
187
  end
188
+ end
189
+ end
109
190
 
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
191
+ class Struct < Object::Struct
192
+ include OperationLayout
193
+ end
118
194
 
119
- return nil
120
- end
195
+ class ManagedStruct < Object::ManagedStruct
196
+ include OperationLayout
197
+ end
121
198
 
122
- # expand a constant into an image
123
- def self.imageize match_image, value
124
- return value if value.is_a? Image
199
+ def initialize value
200
+ # allow init with a pointer so we can wrap the return values from
201
+ # things like _build
202
+ if value.is_a? String
203
+ value = Vips.vips_operation_new value
204
+ raise Vips::Error if value.null?
205
+ end
125
206
 
126
- # 2D array values become tiny 2D images
127
- # 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
131
- else
132
- # we have a 1D array ... use that as a pixel constant and
133
- # expand to match match_image
134
- return match_image.new_from_image value
135
- end
136
- end
207
+ super value
208
+ end
137
209
 
138
- # set an operation argument, expanding constants and copying images as
139
- # required
140
- def set name, value, match_image = nil, flags = 0
141
- gtype = get_typeof name
210
+ def build
211
+ op = Vips.vips_cache_operation_build self
212
+ if op.null?
213
+ Vips.vips_object_unref_outputs self
214
+ raise Vips::Error
215
+ end
142
216
 
143
- if gtype == IMAGE_TYPE
144
- value = Operation::imageize match_image, value
217
+ Operation.new op
218
+ end
145
219
 
146
- if (flags & ARGUMENT_MODIFY) != 0
147
- # make sure we have a unique copy
148
- value = value.copy.copy_memory
149
- end
150
- elsif gtype == ARRAY_IMAGE_TYPE
151
- value = value.map {|x| Operation::imageize match_image, x}
152
- end
220
+ def argument_map &block
221
+ fn = proc do |_op, pspec, argument_class, argument_instance, _a, _b|
222
+ block.call pspec, argument_class, argument_instance
223
+ end
224
+ Vips.vips_argument_map self, fn, nil, nil
225
+ end
153
226
 
154
- super name, value
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?
155
234
  end
235
+ elsif yield object
236
+ return object
237
+ end
156
238
 
157
- public
158
-
159
- # This is the public entry point for the vips binding. {call} will run
160
- # any vips operation, for example:
161
- #
162
- # ```ruby
163
- # out = Vips::Operation.call "black", [100, 100], {:bands => 12}
164
- # ```
165
- #
166
- # will call the C function
167
- #
168
- # ```C
169
- # vips_black( &out, 100, 100, "bands", 12, NULL );
170
- # ```
171
- #
172
- # There are {Image#method_missing} hooks which will run {call} for you
173
- # on {Image} for undefined instance or class methods. So you can also
174
- # write:
175
- #
176
- # ```ruby
177
- # out = Vips::Image.black 100, 100, bands: 12
178
- # ```
179
- #
180
- # Or perhaps:
181
- #
182
- # ```ruby
183
- # x = Vips::Image.black 100, 100
184
- # y = x.invert
185
- # ```
186
- #
187
- # to run the `vips_invert()` operator.
188
- #
189
- # There are also a set of operator overloads and some convenience
190
- # functions, see {Image}.
191
- #
192
- # If the operator needs a vector constant, {call} will turn a scalar
193
- # into a
194
- # vector for you. So for `x.linear a, b`, which calculates
195
- # `x * a + b` where `a` and `b` are vector constants, you can write:
196
- #
197
- # ```ruby
198
- # x = Vips::Image.black 100, 100, bands: 3
199
- # y = x.linear 1, 2
200
- # y = x.linear [1], 4
201
- # y = x.linear [1, 2, 3], 4
202
- # ```
203
- #
204
- # or any other combination. The operator overloads use this facility to
205
- # support all the variations on:
206
- #
207
- # ```ruby
208
- # x = Vips::Image.black 100, 100, bands: 3
209
- # y = x * 2
210
- # y = x + [1,2,3]
211
- # y = x % [1]
212
- # ```
213
- #
214
- # Similarly, wherever an image is required, you can use a constant. The
215
- # constant will be expanded to an image matching the first input image
216
- # argument. For example, you can write:
217
- #
218
- # ```
219
- # x = Vips::Image.black 100, 100, bands: 3
220
- # y = x.bandjoin 255
221
- # ```
222
- #
223
- # to add an extra band to the image where each pixel in the new band has
224
- # the constant value 255.
225
-
226
- 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}"
230
- }
231
-
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
263
-
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
- unless supplied.is_a? Array
267
- raise Vips::Error, "unable to call #{name}: " +
268
- "argument array is not an array"
269
- end
270
- unless optional.is_a? Hash
271
- raise Vips::Error, "unable to call #{name}: " +
272
- "optional arguments are not a hash"
273
- end
274
- 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}."
278
- end
279
-
280
- # very that all supplied_optional keys are in optional_input or
281
- # optional_output
282
- optional.each do |key, value|
283
- arg_name = key.to_s
284
-
285
- unless optional_input.has_key?(arg_name) ||
286
- optional_output.has_key?(arg_name)
287
- raise Vips::Error, "unable to call #{name}: " +
288
- "unknown option #{arg_name}"
289
- end
290
- end
291
-
292
- # the first image arg is the thing we expand constants to match ...
293
- # we need to find it
294
- #
295
- # look inside array and hash arguments, since we may be passing an
296
- # array of images
297
- match_image = find_inside(supplied) do |value|
298
- value.is_a? Image
299
- end
239
+ nil
240
+ end
300
241
 
301
- # 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
304
- raise Vips::Error
305
- end
306
- end
242
+ # expand a constant into an image
243
+ def self.imageize match_image, value
244
+ return value if value.is_a?(Image) || value.is_a?(MutableImage)
245
+
246
+ # 2D array values become tiny 2D images
247
+ # if there's nothing to match to, we also make a 2D image
248
+ if (value.is_a?(Array) && value[0].is_a?(Array)) || match_image.nil?
249
+ Image.new_from_array value
250
+ else
251
+ # we have a 1D array ... use that as a pixel constant and
252
+ # expand to match match_image
253
+ match_image.new_from_image value
254
+ end
255
+ end
307
256
 
308
- # set all required inputs
309
- required_input.each_index do |i|
310
- arg_name = required_input[i][0]
311
- flags = required_input[i][1]
312
- value = supplied[i]
257
+ # set an operation argument, expanding constants and copying images as
258
+ # required
259
+ def set name, value, match_image, flags, gtype, destructive
260
+ if gtype == IMAGE_TYPE
261
+ value = Operation.imageize match_image, value
313
262
 
314
- op.set arg_name, value, match_image, flags
315
- end
316
-
317
- # set all optional inputs
318
- optional.each do |key, value|
319
- next if value.nil?
263
+ # in non-destructive mode, make sure we have a unique copy
264
+ if (flags & ARGUMENT_MODIFY) != 0 &&
265
+ !destructive
266
+ value = value.copy.copy_memory
267
+ end
268
+ elsif gtype == ARRAY_IMAGE_TYPE
269
+ value = value.map { |x| Operation.imageize match_image, x }
270
+ end
320
271
 
321
- arg_name = key.to_s
272
+ super name, value
273
+ end
322
274
 
323
- if optional_input.has_key? arg_name
324
- flags = optional_input[arg_name]
275
+ public
276
+
277
+ # This is the public entry point for the vips binding. {call} will run
278
+ # any vips operation, for example:
279
+ #
280
+ # ```ruby
281
+ # out = Vips::Operation.call "black", [100, 100], {:bands => 12}
282
+ # ```
283
+ #
284
+ # will call the C function
285
+ #
286
+ # ```C
287
+ # vips_black( &out, 100, 100, "bands", 12, NULL );
288
+ # ```
289
+ #
290
+ # There are {Image#method_missing} hooks which will run {call} for you
291
+ # on {Image} for undefined instance or class methods. So you can also
292
+ # write:
293
+ #
294
+ # ```ruby
295
+ # out = Vips::Image.black 100, 100, bands: 12
296
+ # ```
297
+ #
298
+ # Or perhaps:
299
+ #
300
+ # ```ruby
301
+ # x = Vips::Image.black 100, 100
302
+ # y = x.invert
303
+ # ```
304
+ #
305
+ # to run the `vips_invert()` operator.
306
+ #
307
+ # There are also a set of operator overloads and some convenience
308
+ # functions, see {Image}.
309
+ #
310
+ # If the operator needs a vector constant, {call} will turn a scalar
311
+ # into a
312
+ # vector for you. So for `x.linear a, b`, which calculates
313
+ # `x * a + b` where `a` and `b` are vector constants, you can write:
314
+ #
315
+ # ```ruby
316
+ # x = Vips::Image.black 100, 100, bands: 3
317
+ # y = x.linear 1, 2
318
+ # y = x.linear [1], 4
319
+ # y = x.linear [1, 2, 3], 4
320
+ # ```
321
+ #
322
+ # or any other combination. The operator overloads use this facility to
323
+ # support all the variations on:
324
+ #
325
+ # ```ruby
326
+ # x = Vips::Image.black 100, 100, bands: 3
327
+ # y = x * 2
328
+ # y = x + [1,2,3]
329
+ # y = x % [1]
330
+ # ```
331
+ #
332
+ # Similarly, wherever an image is required, you can use a constant. The
333
+ # constant will be expanded to an image matching the first input image
334
+ # argument. For example, you can write:
335
+ #
336
+ # ```
337
+ # x = Vips::Image.black 100, 100, bands: 3
338
+ # y = x.bandjoin 255
339
+ # ```
340
+ #
341
+ # to add an extra band to the image where each pixel in the new band has
342
+ # the constant value 255.
343
+
344
+ def self.call name, supplied, optional = {}, option_string = ""
345
+ GLib.logger.debug("Vips::VipsOperation.call") {
346
+ "name = #{name}, supplied = #{supplied}, " \
347
+ "optional = #{optional}, option_string = #{option_string}"
348
+ }
349
+
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
356
+
357
+ unless supplied.is_a? Array
358
+ raise Vips::Error, "unable to call #{name}: " \
359
+ "argument array is not an array"
360
+ end
361
+ unless optional.is_a? Hash
362
+ raise Vips::Error, "unable to call #{name}: " \
363
+ "optional arguments are not a hash"
364
+ end
365
+
366
+ if supplied.length != required_input.length
367
+ raise Vips::Error, "unable to call #{name}: " \
368
+ "you supplied #{supplied.length} arguments, " \
369
+ "but operation needs #{required_input.length}."
370
+ end
371
+
372
+ # all supplied_optional keys should be in optional_input or
373
+ # optional_output
374
+ optional.each do |key, _value|
375
+ arg_name = key.to_s
376
+
377
+ unless optional_input.has_key?(arg_name) ||
378
+ optional_output.has_key?(arg_name)
379
+ raise Vips::Error, "unable to call #{name}: " \
380
+ "unknown option #{arg_name}"
381
+ end
382
+ end
383
+
384
+ # the first image arg is the thing we expand constants to match ...
385
+ # we need to find it
386
+ #
387
+ # look inside array and hash arguments, since we may be passing an
388
+ # array of images
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
325
415
 
326
- op.set arg_name, value, match_image, flags
327
- end
328
- end
416
+ # keep looping
417
+ false
418
+ end
329
419
 
330
- op = op.build
420
+ op = Operation.new introspect.vips_name
331
421
 
332
- # get all required results
333
- result = []
334
- required_output.each do |arg_name, flags|
335
- result << op.get(arg_name)
336
- end
422
+ # set any string args first so they can't be overridden
423
+ unless option_string.nil?
424
+ if Vips.vips_object_set_from_string(op, option_string) != 0
425
+ raise Vips::Error
426
+ end
427
+ end
337
428
 
338
- # fetch all optional ones
339
- optional_results = {}
340
- optional.each do |key, value|
341
- arg_name = key.to_s
429
+ # collect a list of all input references here
430
+ references = Set.new
342
431
 
343
- if optional_output.has_key? arg_name
344
- flags = optional_output[arg_name]
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
+
441
+ # set all required inputs
442
+ required_input.each_index do |i|
443
+ details = required_input[i]
444
+ arg_name = details[:arg_name]
445
+ flags = details[:flags]
446
+ gtype = details[:gtype]
447
+ value = supplied[i]
448
+
449
+ flat_find value, &add_reference
450
+ op.set arg_name, value, match_image, flags, gtype, destructive
451
+ end
452
+
453
+ # set all optional inputs
454
+ optional.each do |key, value|
455
+ next if value.nil?
456
+
457
+ arg_name = key.to_s
458
+
459
+ if optional_input.has_key? arg_name
460
+ details = optional_input[arg_name]
461
+ flags = details[:flags]
462
+ gtype = details[:gtype]
463
+
464
+ flat_find value, &add_reference
465
+ op.set arg_name, value, match_image, flags, gtype, destructive
466
+ end
467
+ end
345
468
 
346
- optional_results[arg_name] = op.get arg_name
347
- end
348
- end
469
+ op = op.build
349
470
 
350
- result << optional_results if optional_results != {}
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
+
479
+ # get all required results
480
+ result = []
481
+ required_output.each do |details|
482
+ value = details[:arg_name]
483
+ flat_find value, &set_reference
484
+ result << op.get(value)
485
+ end
486
+
487
+ # fetch all optional ones
488
+ optional_results = {}
489
+ optional.each do |key, _value|
490
+ arg_name = key.to_s
491
+
492
+ if optional_output.has_key? arg_name
493
+ value = op.get arg_name
494
+ flat_find value, &set_reference
495
+ optional_results[arg_name] = value
496
+ end
497
+ end
351
498
 
352
- if result.length == 1
353
- result = result.first
354
- elsif result.length == 0
355
- result = nil
356
- end
499
+ result << optional_results if optional_results != {}
357
500
 
358
- GLib::logger.debug("Vips::Operation.call") {"result = #{result}"}
501
+ if result.length == 1
502
+ result = result.first
503
+ elsif result.length == 0
504
+ result = nil
505
+ end
359
506
 
360
- Vips::vips_object_unref_outputs op
507
+ GLib.logger.debug("Vips::Operation.call") { "result = #{result}" }
361
508
 
362
- return result
363
- end
509
+ Vips.vips_object_unref_outputs op
364
510
 
511
+ result
365
512
  end
366
-
513
+ end
367
514
  end