ruby-vips 2.0.15 → 2.1.2
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 +39 -0
- data/Gemfile +3 -1
- data/README.md +42 -41
- data/Rakefile +13 -21
- data/TODO +14 -14
- data/VERSION +1 -1
- data/example/annotate.rb +6 -6
- data/example/connection.rb +26 -0
- data/example/daltonize8.rb +6 -6
- data/example/draw_lines.rb +30 -0
- data/example/example1.rb +4 -4
- data/example/example2.rb +6 -6
- data/example/example3.rb +5 -5
- data/example/example4.rb +2 -2
- data/example/example5.rb +4 -4
- data/example/inheritance_with_refcount.rb +35 -36
- data/example/progress.rb +30 -0
- data/example/thumb.rb +6 -6
- data/example/trim8.rb +1 -1
- data/example/watermark.rb +2 -2
- data/example/wobble.rb +1 -1
- data/lib/ruby-vips.rb +1 -1
- data/lib/vips.rb +191 -79
- data/lib/vips/blend_mode.rb +29 -25
- data/lib/vips/connection.rb +46 -0
- data/lib/vips/gobject.rb +27 -12
- data/lib/vips/gvalue.rb +62 -50
- data/lib/vips/image.rb +475 -256
- data/lib/vips/interpolate.rb +3 -2
- data/lib/vips/methods.rb +788 -121
- data/lib/vips/mutableimage.rb +173 -0
- data/lib/vips/object.rb +171 -54
- data/lib/vips/operation.rb +272 -117
- data/lib/vips/region.rb +73 -0
- 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 -1
- data/ruby-vips.gemspec +26 -20
- metadata +39 -50
- data/.rubocop.yml +0 -22
- data/.rubocop_todo.yml +0 -515
- data/.travis.yml +0 -62
- data/install-vips.sh +0 -26
data/lib/vips/operation.rb
CHANGED
@@ -4,24 +4,31 @@
|
|
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
|
private
|
11
12
|
|
12
13
|
attach_function :vips_operation_new, [:string], :pointer
|
13
14
|
|
14
|
-
|
15
|
+
# We may well block during this (eg. if it's avg, or perhaps jpegsave), and
|
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
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
25
|
+
GObject::GParamSpec.ptr,
|
26
|
+
ArgumentClass.ptr,
|
27
|
+
ArgumentInstance.ptr,
|
28
|
+
:pointer, :pointer], :pointer
|
22
29
|
attach_function :vips_argument_map, [:pointer,
|
23
|
-
|
24
|
-
|
30
|
+
:argument_map_fn,
|
31
|
+
:pointer, :pointer], :pointer
|
25
32
|
|
26
33
|
OPERATION_SEQUENTIAL = 1
|
27
34
|
OPERATION_NOCACHE = 4
|
@@ -35,6 +42,141 @@ module Vips
|
|
35
42
|
|
36
43
|
attach_function :vips_operation_get_flags, [:pointer], :int
|
37
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
|
+
}
|
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
|
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
|
+
|
38
180
|
class Operation < Object
|
39
181
|
# the layout of the VipsOperation struct
|
40
182
|
module OperationLayout
|
@@ -58,94 +200,73 @@ module Vips
|
|
58
200
|
# allow init with a pointer so we can wrap the return values from
|
59
201
|
# things like _build
|
60
202
|
if value.is_a? String
|
61
|
-
value = Vips
|
62
|
-
raise Vips::Error if value
|
203
|
+
value = Vips.vips_operation_new value
|
204
|
+
raise Vips::Error if value.null?
|
63
205
|
end
|
64
206
|
|
65
207
|
super value
|
66
208
|
end
|
67
209
|
|
68
210
|
def build
|
69
|
-
op = Vips
|
70
|
-
if op
|
211
|
+
op = Vips.vips_cache_operation_build self
|
212
|
+
if op.null?
|
213
|
+
Vips.vips_object_unref_outputs self
|
71
214
|
raise Vips::Error
|
72
215
|
end
|
73
216
|
|
74
|
-
|
217
|
+
Operation.new op
|
75
218
|
end
|
76
219
|
|
77
220
|
def argument_map &block
|
78
|
-
fn =
|
221
|
+
fn = proc do |_op, pspec, argument_class, argument_instance, _a, _b|
|
79
222
|
block.call pspec, argument_class, argument_instance
|
80
223
|
end
|
81
|
-
|
82
|
-
Vips::vips_argument_map self, fn, nil, nil
|
224
|
+
Vips.vips_argument_map self, fn, nil, nil
|
83
225
|
end
|
84
226
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
argument_map do |pspec, argument_class, _argument_instance|
|
94
|
-
flags = argument_class[:flags]
|
95
|
-
if (flags & ARGUMENT_CONSTRUCT) != 0
|
96
|
-
# names can include - as punctuation, but we always use _ in
|
97
|
-
# Ruby
|
98
|
-
name = pspec[:name].tr("-", "_")
|
99
|
-
|
100
|
-
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?
|
101
234
|
end
|
235
|
+
elsif yield object
|
236
|
+
return object
|
102
237
|
end
|
103
238
|
|
104
|
-
|
105
|
-
end
|
106
|
-
|
107
|
-
# search array for the first element to match a predicate ...
|
108
|
-
# search inside subarrays and sub-hashes
|
109
|
-
def self.find_inside object, &block
|
110
|
-
return object if block.call object
|
111
|
-
|
112
|
-
if object.is_a? Enumerable
|
113
|
-
object.find { |value| block.call value, block }
|
114
|
-
end
|
115
|
-
|
116
|
-
return nil
|
239
|
+
nil
|
117
240
|
end
|
118
241
|
|
119
242
|
# expand a constant into an image
|
120
243
|
def self.imageize match_image, value
|
121
|
-
return value if value.is_a?
|
244
|
+
return value if value.is_a?(Image) || value.is_a?(MutableImage)
|
122
245
|
|
123
246
|
# 2D array values become tiny 2D images
|
124
247
|
# if there's nothing to match to, we also make a 2D image
|
125
|
-
if (value.is_a?(Array) && value[0].is_a?(Array)) ||
|
126
|
-
|
127
|
-
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
|
128
250
|
else
|
129
251
|
# we have a 1D array ... use that as a pixel constant and
|
130
252
|
# expand to match match_image
|
131
|
-
|
253
|
+
match_image.new_from_image value
|
132
254
|
end
|
133
255
|
end
|
134
256
|
|
135
257
|
# set an operation argument, expanding constants and copying images as
|
136
258
|
# required
|
137
|
-
def set name, value, match_image
|
138
|
-
gtype = get_typeof name
|
139
|
-
|
259
|
+
def set name, value, match_image, flags, gtype, destructive
|
140
260
|
if gtype == IMAGE_TYPE
|
141
|
-
value = Operation
|
261
|
+
value = Operation.imageize match_image, value
|
142
262
|
|
143
|
-
|
144
|
-
|
263
|
+
# in non-destructive mode, make sure we have a unique copy
|
264
|
+
if (flags & ARGUMENT_MODIFY) != 0 &&
|
265
|
+
!destructive
|
145
266
|
value = value.copy.copy_memory
|
146
267
|
end
|
147
268
|
elsif gtype == ARRAY_IMAGE_TYPE
|
148
|
-
value = value.map { |x| Operation
|
269
|
+
value = value.map { |x| Operation.imageize match_image, x }
|
149
270
|
end
|
150
271
|
|
151
272
|
super name, value
|
@@ -221,67 +342,42 @@ module Vips
|
|
221
342
|
# the constant value 255.
|
222
343
|
|
223
344
|
def self.call name, supplied, optional = {}, option_string = ""
|
224
|
-
GLib
|
225
|
-
"name = #{name}, supplied = #{supplied}, "
|
345
|
+
GLib.logger.debug("Vips::VipsOperation.call") {
|
346
|
+
"name = #{name}, supplied = #{supplied}, " \
|
226
347
|
"optional = #{optional}, option_string = #{option_string}"
|
227
348
|
}
|
228
349
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
required_output = []
|
236
|
-
optional_output = {}
|
237
|
-
args.each do |arg_name, flags|
|
238
|
-
next if (flags & ARGUMENT_DEPRECATED) != 0
|
239
|
-
|
240
|
-
if (flags & ARGUMENT_INPUT) != 0
|
241
|
-
if (flags & ARGUMENT_REQUIRED) != 0
|
242
|
-
required_input << [arg_name, flags]
|
243
|
-
else
|
244
|
-
optional_input[arg_name] = flags
|
245
|
-
end
|
246
|
-
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
|
247
356
|
|
248
|
-
# MODIFY INPUT args count as OUTPUT as well
|
249
|
-
if (flags & ARGUMENT_OUTPUT) != 0 ||
|
250
|
-
((flags & ARGUMENT_INPUT) != 0 &&
|
251
|
-
(flags & ARGUMENT_MODIFY) != 0)
|
252
|
-
if (flags & ARGUMENT_REQUIRED) != 0
|
253
|
-
required_output << [arg_name, flags]
|
254
|
-
else
|
255
|
-
optional_output[arg_name] = flags
|
256
|
-
end
|
257
|
-
end
|
258
|
-
end
|
259
|
-
|
260
|
-
# so we should have been supplied with n_required_input values, or
|
261
|
-
# n_required_input + 1 if there's a hash of options at the end
|
262
357
|
unless supplied.is_a? Array
|
263
|
-
raise Vips::Error, "unable to call #{name}: "
|
264
|
-
|
358
|
+
raise Vips::Error, "unable to call #{name}: " \
|
359
|
+
"argument array is not an array"
|
265
360
|
end
|
266
361
|
unless optional.is_a? Hash
|
267
|
-
raise Vips::Error, "unable to call #{name}: "
|
268
|
-
|
362
|
+
raise Vips::Error, "unable to call #{name}: " \
|
363
|
+
"optional arguments are not a hash"
|
269
364
|
end
|
365
|
+
|
270
366
|
if supplied.length != required_input.length
|
271
|
-
raise Vips::Error, "unable to call #{name}: "
|
272
|
-
|
273
|
-
|
367
|
+
raise Vips::Error, "unable to call #{name}: " \
|
368
|
+
"you supplied #{supplied.length} arguments, " \
|
369
|
+
"but operation needs #{required_input.length}."
|
274
370
|
end
|
275
371
|
|
276
|
-
#
|
372
|
+
# all supplied_optional keys should be in optional_input or
|
277
373
|
# optional_output
|
278
374
|
optional.each do |key, _value|
|
279
375
|
arg_name = key.to_s
|
280
376
|
|
281
377
|
unless optional_input.has_key?(arg_name) ||
|
282
|
-
|
283
|
-
raise Vips::Error, "unable to call #{name}: "
|
284
|
-
|
378
|
+
optional_output.has_key?(arg_name)
|
379
|
+
raise Vips::Error, "unable to call #{name}: " \
|
380
|
+
"unknown option #{arg_name}"
|
285
381
|
end
|
286
382
|
end
|
287
383
|
|
@@ -290,24 +386,68 @@ module Vips
|
|
290
386
|
#
|
291
387
|
# look inside array and hash arguments, since we may be passing an
|
292
388
|
# array of images
|
293
|
-
|
294
|
-
|
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
|
295
418
|
end
|
296
419
|
|
420
|
+
op = Operation.new introspect.vips_name
|
421
|
+
|
297
422
|
# set any string args first so they can't be overridden
|
298
|
-
|
299
|
-
if Vips
|
423
|
+
unless option_string.nil?
|
424
|
+
if Vips.vips_object_set_from_string(op, option_string) != 0
|
300
425
|
raise Vips::Error
|
301
426
|
end
|
302
427
|
end
|
303
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
|
+
|
304
441
|
# set all required inputs
|
305
442
|
required_input.each_index do |i|
|
306
|
-
|
307
|
-
|
443
|
+
details = required_input[i]
|
444
|
+
arg_name = details[:arg_name]
|
445
|
+
flags = details[:flags]
|
446
|
+
gtype = details[:gtype]
|
308
447
|
value = supplied[i]
|
309
448
|
|
310
|
-
|
449
|
+
flat_find value, &add_reference
|
450
|
+
op.set arg_name, value, match_image, flags, gtype, destructive
|
311
451
|
end
|
312
452
|
|
313
453
|
# set all optional inputs
|
@@ -317,18 +457,31 @@ module Vips
|
|
317
457
|
arg_name = key.to_s
|
318
458
|
|
319
459
|
if optional_input.has_key? arg_name
|
320
|
-
|
460
|
+
details = optional_input[arg_name]
|
461
|
+
flags = details[:flags]
|
462
|
+
gtype = details[:gtype]
|
321
463
|
|
322
|
-
|
464
|
+
flat_find value, &add_reference
|
465
|
+
op.set arg_name, value, match_image, flags, gtype, destructive
|
323
466
|
end
|
324
467
|
end
|
325
468
|
|
326
469
|
op = op.build
|
327
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
|
+
|
328
479
|
# get all required results
|
329
480
|
result = []
|
330
|
-
required_output.each do |
|
331
|
-
|
481
|
+
required_output.each do |details|
|
482
|
+
value = details[:arg_name]
|
483
|
+
flat_find value, &set_reference
|
484
|
+
result << op.get(value)
|
332
485
|
end
|
333
486
|
|
334
487
|
# fetch all optional ones
|
@@ -337,7 +490,9 @@ module Vips
|
|
337
490
|
arg_name = key.to_s
|
338
491
|
|
339
492
|
if optional_output.has_key? arg_name
|
340
|
-
|
493
|
+
value = op.get arg_name
|
494
|
+
flat_find value, &set_reference
|
495
|
+
optional_results[arg_name] = value
|
341
496
|
end
|
342
497
|
end
|
343
498
|
|
@@ -349,11 +504,11 @@ module Vips
|
|
349
504
|
result = nil
|
350
505
|
end
|
351
506
|
|
352
|
-
GLib
|
507
|
+
GLib.logger.debug("Vips::Operation.call") { "result = #{result}" }
|
353
508
|
|
354
|
-
Vips
|
509
|
+
Vips.vips_object_unref_outputs op
|
355
510
|
|
356
|
-
|
511
|
+
result
|
357
512
|
end
|
358
513
|
end
|
359
514
|
end
|