ruby-vips 2.0.17 → 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 +13 -0
- data/Gemfile +3 -1
- data/README.md +4 -4
- data/Rakefile +13 -21
- data/TODO +3 -6
- data/VERSION +1 -1
- data/example/annotate.rb +6 -6
- data/example/connection.rb +18 -9
- 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 +3 -3
- 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 +121 -75
- data/lib/vips/blend_mode.rb +29 -25
- data/lib/vips/connection.rb +4 -4
- data/lib/vips/gobject.rb +18 -11
- data/lib/vips/gvalue.rb +54 -54
- data/lib/vips/image.rb +232 -155
- data/lib/vips/interpolate.rb +3 -2
- data/lib/vips/methods.rb +165 -15
- data/lib/vips/mutableimage.rb +154 -0
- data/lib/vips/object.rb +84 -85
- data/lib/vips/operation.rb +161 -82
- data/lib/vips/region.rb +6 -6
- data/lib/vips/source.rb +11 -12
- data/lib/vips/sourcecustom.rb +7 -8
- data/lib/vips/target.rb +12 -13
- data/lib/vips/targetcustom.rb +9 -10
- data/lib/vips/version.rb +1 -1
- data/ruby-vips.gemspec +26 -22
- metadata +28 -48
- data/.rubocop.yml +0 -22
- data/.rubocop_todo.yml +0 -473
- data/.travis.yml +0 -57
- data/install-vips.sh +0 -26
data/lib/vips/operation.rb
CHANGED
@@ -4,7 +4,8 @@
|
|
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
|
@@ -12,22 +13,22 @@ module Vips
|
|
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
|
15
|
-
# libvips might trigger some signals which ruby has handles for.
|
16
|
+
# libvips might trigger some signals which ruby has handles for.
|
16
17
|
#
|
17
|
-
# We need FFI to drop the GIL lock during this call and reacquire it when
|
18
|
+
# We need FFI to drop the GIL lock during this call and reacquire it when
|
18
19
|
# the call ends, or we'll deadlock.
|
19
|
-
attach_function :vips_cache_operation_build, [:pointer], :pointer,
|
20
|
+
attach_function :vips_cache_operation_build, [:pointer], :pointer,
|
20
21
|
blocking: true
|
21
22
|
attach_function :vips_object_unref_outputs, [:pointer], :void
|
22
23
|
|
23
24
|
callback :argument_map_fn, [:pointer,
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
25
|
+
GObject::GParamSpec.ptr,
|
26
|
+
ArgumentClass.ptr,
|
27
|
+
ArgumentInstance.ptr,
|
28
|
+
:pointer, :pointer], :pointer
|
28
29
|
attach_function :vips_argument_map, [:pointer,
|
29
|
-
|
30
|
-
|
30
|
+
:argument_map_fn,
|
31
|
+
:pointer, :pointer], :pointer
|
31
32
|
|
32
33
|
OPERATION_SEQUENTIAL = 1
|
33
34
|
OPERATION_NOCACHE = 4
|
@@ -45,14 +46,25 @@ module Vips
|
|
45
46
|
# everything we know about it. This is used for doc generation as well as
|
46
47
|
# call.
|
47
48
|
class Introspect
|
48
|
-
attr_reader :name, :description, :flags, :args, :required_input,
|
49
|
-
:optional_input, :required_output, :optional_output, :member_x,
|
50
|
-
:method_args
|
49
|
+
attr_reader :name, :description, :flags, :args, :required_input,
|
50
|
+
:optional_input, :required_output, :optional_output, :member_x,
|
51
|
+
:method_args, :vips_name, :destructive
|
51
52
|
|
52
53
|
@@introspect_cache = {}
|
53
54
|
|
54
55
|
def initialize name
|
55
|
-
|
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
|
56
68
|
@args = []
|
57
69
|
@required_input = []
|
58
70
|
@optional_input = {}
|
@@ -66,12 +78,14 @@ module Vips
|
|
66
78
|
# names can include - as punctuation, but we always use _ in
|
67
79
|
# Ruby
|
68
80
|
arg_name = pspec[:name].tr("-", "_")
|
69
|
-
args << {
|
70
|
-
:
|
71
|
-
:
|
72
|
-
:
|
81
|
+
@args << {
|
82
|
+
arg_name: arg_name,
|
83
|
+
flags: flags,
|
84
|
+
gtype: pspec[:value_type]
|
73
85
|
}
|
74
86
|
end
|
87
|
+
|
88
|
+
nil
|
75
89
|
end
|
76
90
|
|
77
91
|
@args.each do |details|
|
@@ -79,26 +93,27 @@ module Vips
|
|
79
93
|
flags = details[:flags]
|
80
94
|
|
81
95
|
if (flags & ARGUMENT_INPUT) != 0
|
82
|
-
if (flags & ARGUMENT_REQUIRED) != 0 &&
|
83
|
-
|
96
|
+
if (flags & ARGUMENT_REQUIRED) != 0 &&
|
97
|
+
(flags & ARGUMENT_DEPRECATED) == 0
|
84
98
|
@required_input << details
|
85
99
|
else
|
86
100
|
# we allow deprecated optional args
|
87
101
|
@optional_input[arg_name] = details
|
88
102
|
end
|
89
103
|
|
90
|
-
# MODIFY INPUT args count as OUTPUT as well
|
91
|
-
if (flags & ARGUMENT_MODIFY) != 0
|
92
|
-
|
93
|
-
|
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
|
94
109
|
@required_output << details
|
95
110
|
else
|
96
111
|
@optional_output[arg_name] = details
|
97
112
|
end
|
98
113
|
end
|
99
114
|
elsif (flags & ARGUMENT_OUTPUT) != 0
|
100
|
-
if (flags & ARGUMENT_REQUIRED) != 0 &&
|
101
|
-
|
115
|
+
if (flags & ARGUMENT_REQUIRED) != 0 &&
|
116
|
+
(flags & ARGUMENT_DEPRECATED) == 0
|
102
117
|
@required_output << details
|
103
118
|
else
|
104
119
|
# again, allow deprecated optional args
|
@@ -106,6 +121,16 @@ module Vips
|
|
106
121
|
end
|
107
122
|
end
|
108
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
|
109
134
|
end
|
110
135
|
|
111
136
|
# Yard comment generation needs a little more introspection. We add this
|
@@ -113,8 +138,8 @@ module Vips
|
|
113
138
|
# we can.
|
114
139
|
def add_yard_introspection name
|
115
140
|
@name = name
|
116
|
-
@description = Vips
|
117
|
-
@flags = Vips
|
141
|
+
@description = Vips.vips_object_get_description @op
|
142
|
+
@flags = Vips.vips_operation_get_flags @op
|
118
143
|
@member_x = nil
|
119
144
|
@method_args = []
|
120
145
|
|
@@ -125,15 +150,15 @@ module Vips
|
|
125
150
|
|
126
151
|
details[:yard_name] = arg_name == "in" ? "im" : arg_name
|
127
152
|
pspec = @op.get_pspec arg_name
|
128
|
-
details[:blurb] = GObject
|
153
|
+
details[:blurb] = GObject.g_param_spec_get_blurb pspec
|
129
154
|
|
130
|
-
if (flags & ARGUMENT_INPUT) != 0 &&
|
131
|
-
|
132
|
-
|
133
|
-
# the first required input image is the thing we will be a method
|
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
|
134
159
|
# of
|
135
|
-
if @member_x
|
136
|
-
@member_x = details
|
160
|
+
if @member_x.nil? && gtype == IMAGE_TYPE
|
161
|
+
@member_x = details
|
137
162
|
else
|
138
163
|
@method_args << details
|
139
164
|
end
|
@@ -150,7 +175,6 @@ module Vips
|
|
150
175
|
introspect.add_yard_introspection name
|
151
176
|
introspect
|
152
177
|
end
|
153
|
-
|
154
178
|
end
|
155
179
|
|
156
180
|
class Operation < Object
|
@@ -176,7 +200,7 @@ module Vips
|
|
176
200
|
# allow init with a pointer so we can wrap the return values from
|
177
201
|
# things like _build
|
178
202
|
if value.is_a? String
|
179
|
-
value = Vips
|
203
|
+
value = Vips.vips_operation_new value
|
180
204
|
raise Vips::Error if value.null?
|
181
205
|
end
|
182
206
|
|
@@ -184,66 +208,65 @@ module Vips
|
|
184
208
|
end
|
185
209
|
|
186
210
|
def build
|
187
|
-
op = Vips
|
211
|
+
op = Vips.vips_cache_operation_build self
|
188
212
|
if op.null?
|
189
|
-
Vips
|
213
|
+
Vips.vips_object_unref_outputs self
|
190
214
|
raise Vips::Error
|
191
215
|
end
|
192
216
|
|
193
|
-
|
217
|
+
Operation.new op
|
194
218
|
end
|
195
219
|
|
196
220
|
def argument_map &block
|
197
|
-
fn =
|
221
|
+
fn = proc do |_op, pspec, argument_class, argument_instance, _a, _b|
|
198
222
|
block.call pspec, argument_class, argument_instance
|
199
223
|
end
|
200
|
-
|
201
|
-
Vips::vips_argument_map self, fn, nil, nil
|
224
|
+
Vips.vips_argument_map self, fn, nil, nil
|
202
225
|
end
|
203
226
|
|
204
|
-
# Search an object for the first element to match a predicate. Search
|
227
|
+
# Search an object for the first element to match a predicate. Search
|
205
228
|
# inside subarrays and sub-hashes. Equlvalent to x.flatten.find{}.
|
206
229
|
def self.flat_find object, &block
|
207
230
|
if object.respond_to? :each
|
208
|
-
object.each do |x|
|
209
|
-
result = flat_find x, &block
|
210
|
-
return result
|
231
|
+
object.each do |x|
|
232
|
+
result = flat_find x, &block
|
233
|
+
return result unless result.nil?
|
211
234
|
end
|
212
|
-
|
213
|
-
return object
|
235
|
+
elsif yield object
|
236
|
+
return object
|
214
237
|
end
|
215
238
|
|
216
|
-
|
239
|
+
nil
|
217
240
|
end
|
218
241
|
|
219
242
|
# expand a constant into an image
|
220
243
|
def self.imageize match_image, value
|
221
|
-
return value if value.is_a?
|
244
|
+
return value if value.is_a?(Image) || value.is_a?(MutableImage)
|
222
245
|
|
223
246
|
# 2D array values become tiny 2D images
|
224
247
|
# if there's nothing to match to, we also make a 2D image
|
225
|
-
if (value.is_a?(Array) && value[0].is_a?(Array)) ||
|
226
|
-
|
227
|
-
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
|
228
250
|
else
|
229
251
|
# we have a 1D array ... use that as a pixel constant and
|
230
252
|
# expand to match match_image
|
231
|
-
|
253
|
+
match_image.new_from_image value
|
232
254
|
end
|
233
255
|
end
|
234
256
|
|
235
257
|
# set an operation argument, expanding constants and copying images as
|
236
258
|
# required
|
237
|
-
def set name, value, match_image, flags, gtype
|
259
|
+
def set name, value, match_image, flags, gtype, destructive
|
238
260
|
if gtype == IMAGE_TYPE
|
239
|
-
value = Operation
|
261
|
+
value = Operation.imageize match_image, value
|
240
262
|
|
241
|
-
|
242
|
-
|
263
|
+
# in non-destructive mode, make sure we have a unique copy
|
264
|
+
if (flags & ARGUMENT_MODIFY) != 0 &&
|
265
|
+
!destructive
|
243
266
|
value = value.copy.copy_memory
|
244
267
|
end
|
245
268
|
elsif gtype == ARRAY_IMAGE_TYPE
|
246
|
-
value = value.map { |x| Operation
|
269
|
+
value = value.map { |x| Operation.imageize match_image, x }
|
247
270
|
end
|
248
271
|
|
249
272
|
super name, value
|
@@ -319,8 +342,8 @@ module Vips
|
|
319
342
|
# the constant value 255.
|
320
343
|
|
321
344
|
def self.call name, supplied, optional = {}, option_string = ""
|
322
|
-
GLib
|
323
|
-
"name = #{name}, supplied = #{supplied}, "
|
345
|
+
GLib.logger.debug("Vips::VipsOperation.call") {
|
346
|
+
"name = #{name}, supplied = #{supplied}, " \
|
324
347
|
"optional = #{optional}, option_string = #{option_string}"
|
325
348
|
}
|
326
349
|
|
@@ -329,20 +352,21 @@ module Vips
|
|
329
352
|
required_output = introspect.required_output
|
330
353
|
optional_input = introspect.optional_input
|
331
354
|
optional_output = introspect.optional_output
|
355
|
+
destructive = introspect.destructive
|
332
356
|
|
333
357
|
unless supplied.is_a? Array
|
334
|
-
raise Vips::Error, "unable to call #{name}: "
|
335
|
-
|
358
|
+
raise Vips::Error, "unable to call #{name}: " \
|
359
|
+
"argument array is not an array"
|
336
360
|
end
|
337
361
|
unless optional.is_a? Hash
|
338
|
-
raise Vips::Error, "unable to call #{name}: "
|
339
|
-
|
362
|
+
raise Vips::Error, "unable to call #{name}: " \
|
363
|
+
"optional arguments are not a hash"
|
340
364
|
end
|
341
365
|
|
342
366
|
if supplied.length != required_input.length
|
343
|
-
raise Vips::Error, "unable to call #{name}: "
|
344
|
-
|
345
|
-
|
367
|
+
raise Vips::Error, "unable to call #{name}: " \
|
368
|
+
"you supplied #{supplied.length} arguments, " \
|
369
|
+
"but operation needs #{required_input.length}."
|
346
370
|
end
|
347
371
|
|
348
372
|
# all supplied_optional keys should be in optional_input or
|
@@ -352,8 +376,8 @@ module Vips
|
|
352
376
|
|
353
377
|
unless optional_input.has_key?(arg_name) ||
|
354
378
|
optional_output.has_key?(arg_name)
|
355
|
-
raise Vips::Error, "unable to call #{name}: "
|
356
|
-
|
379
|
+
raise Vips::Error, "unable to call #{name}: " \
|
380
|
+
"unknown option #{arg_name}"
|
357
381
|
end
|
358
382
|
end
|
359
383
|
|
@@ -362,17 +386,58 @@ module Vips
|
|
362
386
|
#
|
363
387
|
# look inside array and hash arguments, since we may be passing an
|
364
388
|
# array of images
|
365
|
-
|
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
|
418
|
+
end
|
366
419
|
|
367
|
-
op = Operation.new
|
420
|
+
op = Operation.new introspect.vips_name
|
368
421
|
|
369
422
|
# set any string args first so they can't be overridden
|
370
|
-
|
371
|
-
if Vips
|
423
|
+
unless option_string.nil?
|
424
|
+
if Vips.vips_object_set_from_string(op, option_string) != 0
|
372
425
|
raise Vips::Error
|
373
426
|
end
|
374
427
|
end
|
375
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
|
+
|
376
441
|
# set all required inputs
|
377
442
|
required_input.each_index do |i|
|
378
443
|
details = required_input[i]
|
@@ -381,7 +446,8 @@ module Vips
|
|
381
446
|
gtype = details[:gtype]
|
382
447
|
value = supplied[i]
|
383
448
|
|
384
|
-
|
449
|
+
flat_find value, &add_reference
|
450
|
+
op.set arg_name, value, match_image, flags, gtype, destructive
|
385
451
|
end
|
386
452
|
|
387
453
|
# set all optional inputs
|
@@ -395,16 +461,27 @@ module Vips
|
|
395
461
|
flags = details[:flags]
|
396
462
|
gtype = details[:gtype]
|
397
463
|
|
398
|
-
|
464
|
+
flat_find value, &add_reference
|
465
|
+
op.set arg_name, value, match_image, flags, gtype, destructive
|
399
466
|
end
|
400
467
|
end
|
401
468
|
|
402
469
|
op = op.build
|
403
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
|
+
|
404
479
|
# get all required results
|
405
480
|
result = []
|
406
481
|
required_output.each do |details|
|
407
|
-
|
482
|
+
value = details[:arg_name]
|
483
|
+
flat_find value, &set_reference
|
484
|
+
result << op.get(value)
|
408
485
|
end
|
409
486
|
|
410
487
|
# fetch all optional ones
|
@@ -413,7 +490,9 @@ module Vips
|
|
413
490
|
arg_name = key.to_s
|
414
491
|
|
415
492
|
if optional_output.has_key? arg_name
|
416
|
-
|
493
|
+
value = op.get arg_name
|
494
|
+
flat_find value, &set_reference
|
495
|
+
optional_results[arg_name] = value
|
417
496
|
end
|
418
497
|
end
|
419
498
|
|
@@ -425,11 +504,11 @@ module Vips
|
|
425
504
|
result = nil
|
426
505
|
end
|
427
506
|
|
428
|
-
GLib
|
507
|
+
GLib.logger.debug("Vips::Operation.call") { "result = #{result}" }
|
429
508
|
|
430
|
-
Vips
|
509
|
+
Vips.vips_object_unref_outputs op
|
431
510
|
|
432
|
-
|
511
|
+
result
|
433
512
|
end
|
434
513
|
end
|
435
514
|
end
|