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.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +42 -0
- data/.github/workflows/test.yml +80 -0
- data/.standard.yml +17 -0
- data/.yardopts +0 -1
- data/CHANGELOG.md +44 -0
- data/Gemfile +3 -1
- data/README.md +45 -47
- data/Rakefile +13 -15
- data/TODO +19 -10
- data/VERSION +1 -1
- data/example/annotate.rb +7 -7
- data/example/connection.rb +26 -0
- data/example/daltonize8.rb +27 -29
- data/example/draw_lines.rb +30 -0
- data/example/example1.rb +5 -6
- data/example/example2.rb +11 -11
- data/example/example3.rb +9 -9
- data/example/example4.rb +8 -8
- data/example/example5.rb +8 -9
- data/example/inheritance_with_refcount.rb +203 -221
- data/example/progress.rb +30 -0
- data/example/thumb.rb +12 -14
- data/example/trim8.rb +7 -7
- data/example/watermark.rb +15 -36
- data/example/wobble.rb +25 -25
- data/lib/ruby-vips.rb +1 -1
- data/lib/vips.rb +473 -338
- data/lib/vips/access.rb +9 -9
- data/lib/vips/align.rb +7 -8
- data/lib/vips/angle.rb +8 -9
- data/lib/vips/angle45.rb +12 -13
- data/lib/vips/bandformat.rb +16 -18
- data/lib/vips/blend_mode.rb +36 -0
- data/lib/vips/coding.rb +11 -12
- data/lib/vips/compass_direction.rb +13 -14
- data/lib/vips/connection.rb +46 -0
- data/lib/vips/direction.rb +7 -8
- data/lib/vips/extend.rb +13 -14
- data/lib/vips/gobject.rb +111 -100
- data/lib/vips/gvalue.rb +243 -237
- data/lib/vips/image.rb +1501 -1338
- data/lib/vips/interesting.rb +10 -11
- data/lib/vips/interpolate.rb +50 -54
- data/lib/vips/interpretation.rb +25 -26
- data/lib/vips/kernel.rb +18 -19
- data/lib/vips/methods.rb +929 -309
- data/lib/vips/mutableimage.rb +154 -0
- data/lib/vips/object.rb +318 -208
- data/lib/vips/operation.rb +467 -320
- data/lib/vips/operationboolean.rb +10 -11
- data/lib/vips/operationcomplex.rb +8 -9
- data/lib/vips/operationcomplex2.rb +6 -7
- data/lib/vips/operationcomplexget.rb +7 -8
- data/lib/vips/operationmath.rb +14 -15
- data/lib/vips/operationmath2.rb +6 -7
- data/lib/vips/operationrelational.rb +11 -12
- data/lib/vips/operationround.rb +7 -8
- data/lib/vips/region.rb +73 -0
- data/lib/vips/size.rb +9 -10
- data/lib/vips/source.rb +88 -0
- data/lib/vips/sourcecustom.rb +89 -0
- data/lib/vips/target.rb +86 -0
- data/lib/vips/targetcustom.rb +77 -0
- data/lib/vips/version.rb +1 -2
- data/ruby-vips.gemspec +29 -20
- metadata +51 -40
- data/.travis.yml +0 -55
- data/install-vips.sh +0 -26
data/lib/vips/operation.rb
CHANGED
@@ -4,364 +4,511 @@
|
|
4
4
|
# Author:: John Cupitt (mailto:jcupitt@gmail.com)
|
5
5
|
# License:: MIT
|
6
6
|
|
7
|
-
require
|
7
|
+
require "ffi"
|
8
|
+
require "set"
|
8
9
|
|
9
10
|
module Vips
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
#
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
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
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
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
|
-
|
89
|
-
|
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
|
-
|
93
|
-
|
94
|
-
|
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
|
-
|
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
|
-
|
111
|
-
|
112
|
-
|
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
|
-
|
120
|
-
|
195
|
+
class ManagedStruct < Object::ManagedStruct
|
196
|
+
include OperationLayout
|
197
|
+
end
|
121
198
|
|
122
|
-
|
123
|
-
|
124
|
-
|
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
|
-
|
127
|
-
|
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
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
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
|
-
|
144
|
-
|
217
|
+
Operation.new op
|
218
|
+
end
|
145
219
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
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
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
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
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
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
|
-
|
272
|
+
super name, value
|
273
|
+
end
|
322
274
|
|
323
|
-
|
324
|
-
|
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
|
-
|
327
|
-
|
328
|
-
|
416
|
+
# keep looping
|
417
|
+
false
|
418
|
+
end
|
329
419
|
|
330
|
-
|
420
|
+
op = Operation.new introspect.vips_name
|
331
421
|
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
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
|
-
|
339
|
-
|
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
|
-
|
344
|
-
|
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
|
-
|
347
|
-
end
|
348
|
-
end
|
469
|
+
op = op.build
|
349
470
|
|
350
|
-
|
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
|
-
|
353
|
-
result = result.first
|
354
|
-
elsif result.length == 0
|
355
|
-
result = nil
|
356
|
-
end
|
499
|
+
result << optional_results if optional_results != {}
|
357
500
|
|
358
|
-
|
501
|
+
if result.length == 1
|
502
|
+
result = result.first
|
503
|
+
elsif result.length == 0
|
504
|
+
result = nil
|
505
|
+
end
|
359
506
|
|
360
|
-
|
507
|
+
GLib.logger.debug("Vips::Operation.call") { "result = #{result}" }
|
361
508
|
|
362
|
-
|
363
|
-
end
|
509
|
+
Vips.vips_object_unref_outputs op
|
364
510
|
|
511
|
+
result
|
365
512
|
end
|
366
|
-
|
513
|
+
end
|
367
514
|
end
|