ruby-vips 2.0.13 → 2.1.0

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