vips 8.8.4 → 8.12.1
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/development.yml +54 -0
- data/.standard.yml +17 -0
- data/.travis.yml +1 -0
- data/.yardopts +0 -1
- data/CHANGELOG.md +50 -0
- data/Gemfile +8 -1
- data/README.md +31 -15
- data/Rakefile +23 -18
- data/TODO +43 -0
- 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 +46 -39
- 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/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 +548 -287
- data/lib/vips/interpolate.rb +3 -2
- data/lib/vips/methods.rb +2877 -2319
- data/lib/vips/mutableimage.rb +173 -0
- data/lib/vips/object.rb +159 -54
- data/lib/vips/operation.rb +286 -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/lib/vips.rb +199 -80
- data/vips.gemspec +3 -3
- metadata +36 -22
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,155 @@ 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, :doc_optional_input,
|
52
|
+
:doc_optional_output
|
53
|
+
|
54
|
+
@@introspect_cache = {}
|
55
|
+
|
56
|
+
def initialize name
|
57
|
+
# if there's a trailing "!", this is a destructive version of an
|
58
|
+
# operation
|
59
|
+
if name[-1] == "!"
|
60
|
+
@destructive = true
|
61
|
+
# strip the trailing "!"
|
62
|
+
@vips_name = name[0...-1]
|
63
|
+
else
|
64
|
+
@destructive = false
|
65
|
+
@vips_name = name
|
66
|
+
end
|
67
|
+
|
68
|
+
@op = Operation.new @vips_name
|
69
|
+
@args = []
|
70
|
+
@required_input = []
|
71
|
+
@optional_input = {}
|
72
|
+
@required_output = []
|
73
|
+
@optional_output = {}
|
74
|
+
|
75
|
+
# find all the arguments the operator can take
|
76
|
+
@op.argument_map do |pspec, argument_class, _argument_instance|
|
77
|
+
flags = argument_class[:flags]
|
78
|
+
if (flags & ARGUMENT_CONSTRUCT) != 0
|
79
|
+
# names can include - as punctuation, but we always use _ in
|
80
|
+
# Ruby
|
81
|
+
arg_name = pspec[:name].tr("-", "_")
|
82
|
+
@args << {
|
83
|
+
arg_name: arg_name,
|
84
|
+
flags: flags,
|
85
|
+
gtype: pspec[:value_type]
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
nil
|
90
|
+
end
|
91
|
+
|
92
|
+
@args.each do |details|
|
93
|
+
arg_name = details[:arg_name]
|
94
|
+
flags = details[:flags]
|
95
|
+
|
96
|
+
if (flags & ARGUMENT_INPUT) != 0
|
97
|
+
if (flags & ARGUMENT_REQUIRED) != 0 &&
|
98
|
+
(flags & ARGUMENT_DEPRECATED) == 0
|
99
|
+
@required_input << details
|
100
|
+
else
|
101
|
+
# we allow deprecated optional args
|
102
|
+
@optional_input[arg_name] = details
|
103
|
+
end
|
104
|
+
|
105
|
+
# MODIFY INPUT args count as OUTPUT as well in non-destructive mode
|
106
|
+
if (flags & ARGUMENT_MODIFY) != 0 &&
|
107
|
+
!@destructive
|
108
|
+
if (flags & ARGUMENT_REQUIRED) != 0 &&
|
109
|
+
(flags & ARGUMENT_DEPRECATED) == 0
|
110
|
+
@required_output << details
|
111
|
+
else
|
112
|
+
@optional_output[arg_name] = details
|
113
|
+
end
|
114
|
+
end
|
115
|
+
elsif (flags & ARGUMENT_OUTPUT) != 0
|
116
|
+
if (flags & ARGUMENT_REQUIRED) != 0 &&
|
117
|
+
(flags & ARGUMENT_DEPRECATED) == 0
|
118
|
+
@required_output << details
|
119
|
+
else
|
120
|
+
# again, allow deprecated optional args
|
121
|
+
@optional_output[arg_name] = details
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# in destructive mode, the first required input arg must be MODIFY and
|
127
|
+
# must be an image
|
128
|
+
if @destructive
|
129
|
+
if @required_input.length < 1 ||
|
130
|
+
@required_input[0][:flags] & ARGUMENT_MODIFY == 0 ||
|
131
|
+
@required_input[0][:gtype] != IMAGE_TYPE
|
132
|
+
raise Vips::Error, "operation #{@vips_name} is not destructive"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Yard comment generation needs a little more introspection. We add this
|
138
|
+
# extra metadata in a separate method to keep the main path as fast as
|
139
|
+
# we can.
|
140
|
+
def add_yard_introspection name
|
141
|
+
@name = name
|
142
|
+
@description = Vips.vips_object_get_description @op
|
143
|
+
@flags = Vips.vips_operation_get_flags @op
|
144
|
+
@member_x = nil
|
145
|
+
@method_args = []
|
146
|
+
@doc_optional_input = {}
|
147
|
+
@doc_optional_output = {}
|
148
|
+
|
149
|
+
@args.each do |details|
|
150
|
+
arg_name = details[:arg_name]
|
151
|
+
flags = details[:flags]
|
152
|
+
gtype = details[:gtype]
|
153
|
+
|
154
|
+
details[:yard_name] = arg_name == "in" ? "im" : arg_name
|
155
|
+
pspec = @op.get_pspec arg_name
|
156
|
+
details[:blurb] = GObject.g_param_spec_get_blurb pspec
|
157
|
+
|
158
|
+
if (flags & ARGUMENT_INPUT) != 0 &&
|
159
|
+
(flags & ARGUMENT_REQUIRED) != 0 &&
|
160
|
+
(flags & ARGUMENT_DEPRECATED) == 0
|
161
|
+
# the first required input image is the thing we will be a method
|
162
|
+
# of
|
163
|
+
if @member_x.nil? && gtype == IMAGE_TYPE
|
164
|
+
@member_x = details
|
165
|
+
else
|
166
|
+
@method_args << details
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# and make the arg sets to document by filtering out deprecated args
|
172
|
+
@optional_input.each do |arg_name, details|
|
173
|
+
next if (details[:flags] & ARGUMENT_DEPRECATED) != 0
|
174
|
+
@doc_optional_input[details[:arg_name]] = details
|
175
|
+
end
|
176
|
+
|
177
|
+
@optional_output.each do |arg_name, details|
|
178
|
+
next if (details[:flags] & ARGUMENT_DEPRECATED) != 0
|
179
|
+
@doc_optional_output[details[:arg_name]] = details
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def self.get name
|
184
|
+
@@introspect_cache[name] ||= Introspect.new name
|
185
|
+
end
|
186
|
+
|
187
|
+
def self.get_yard name
|
188
|
+
introspect = Introspect.get name
|
189
|
+
introspect.add_yard_introspection name
|
190
|
+
introspect
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
38
194
|
class Operation < Object
|
39
195
|
# the layout of the VipsOperation struct
|
40
196
|
module OperationLayout
|
@@ -58,94 +214,73 @@ module Vips
|
|
58
214
|
# allow init with a pointer so we can wrap the return values from
|
59
215
|
# things like _build
|
60
216
|
if value.is_a? String
|
61
|
-
value = Vips
|
62
|
-
raise Vips::Error if value
|
217
|
+
value = Vips.vips_operation_new value
|
218
|
+
raise Vips::Error if value.null?
|
63
219
|
end
|
64
220
|
|
65
221
|
super value
|
66
222
|
end
|
67
223
|
|
68
224
|
def build
|
69
|
-
op = Vips
|
70
|
-
if op
|
225
|
+
op = Vips.vips_cache_operation_build self
|
226
|
+
if op.null?
|
227
|
+
Vips.vips_object_unref_outputs self
|
71
228
|
raise Vips::Error
|
72
229
|
end
|
73
230
|
|
74
|
-
|
231
|
+
Operation.new op
|
75
232
|
end
|
76
233
|
|
77
234
|
def argument_map &block
|
78
|
-
fn =
|
235
|
+
fn = proc do |_op, pspec, argument_class, argument_instance, _a, _b|
|
79
236
|
block.call pspec, argument_class, argument_instance
|
80
237
|
end
|
81
|
-
|
82
|
-
Vips::vips_argument_map self, fn, nil, nil
|
83
|
-
end
|
84
|
-
|
85
|
-
def get_flags
|
86
|
-
Vips::vips_operation_get_flags self
|
238
|
+
Vips.vips_argument_map self, fn, nil, nil
|
87
239
|
end
|
88
240
|
|
89
|
-
#
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
# names can include - as punctuation, but we always use _ in
|
97
|
-
# Ruby
|
98
|
-
name = pspec[:name].tr("-", "_")
|
99
|
-
|
100
|
-
args << [name, flags]
|
241
|
+
# Search an object for the first element to match a predicate. Search
|
242
|
+
# inside subarrays and sub-hashes. Equlvalent to x.flatten.find{}.
|
243
|
+
def self.flat_find object, &block
|
244
|
+
if object.respond_to? :each
|
245
|
+
object.each do |x|
|
246
|
+
result = flat_find x, &block
|
247
|
+
return result unless result.nil?
|
101
248
|
end
|
249
|
+
elsif yield object
|
250
|
+
return object
|
102
251
|
end
|
103
252
|
|
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
|
253
|
+
nil
|
117
254
|
end
|
118
255
|
|
119
256
|
# expand a constant into an image
|
120
257
|
def self.imageize match_image, value
|
121
|
-
return value if value.is_a?
|
258
|
+
return value if value.is_a?(Image) || value.is_a?(MutableImage)
|
122
259
|
|
123
260
|
# 2D array values become tiny 2D images
|
124
261
|
# 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
|
262
|
+
if (value.is_a?(Array) && value[0].is_a?(Array)) || match_image.nil?
|
263
|
+
Image.new_from_array value
|
128
264
|
else
|
129
265
|
# we have a 1D array ... use that as a pixel constant and
|
130
266
|
# expand to match match_image
|
131
|
-
|
267
|
+
match_image.new_from_image value
|
132
268
|
end
|
133
269
|
end
|
134
270
|
|
135
271
|
# set an operation argument, expanding constants and copying images as
|
136
272
|
# required
|
137
|
-
def set name, value, match_image
|
138
|
-
gtype = get_typeof name
|
139
|
-
|
273
|
+
def set name, value, match_image, flags, gtype, destructive
|
140
274
|
if gtype == IMAGE_TYPE
|
141
|
-
value = Operation
|
275
|
+
value = Operation.imageize match_image, value
|
142
276
|
|
143
|
-
|
144
|
-
|
277
|
+
# in non-destructive mode, make sure we have a unique copy
|
278
|
+
if (flags & ARGUMENT_MODIFY) != 0 &&
|
279
|
+
!destructive
|
145
280
|
value = value.copy.copy_memory
|
146
281
|
end
|
147
282
|
elsif gtype == ARRAY_IMAGE_TYPE
|
148
|
-
value = value.map { |x| Operation
|
283
|
+
value = value.map { |x| Operation.imageize match_image, x }
|
149
284
|
end
|
150
285
|
|
151
286
|
super name, value
|
@@ -221,67 +356,42 @@ module Vips
|
|
221
356
|
# the constant value 255.
|
222
357
|
|
223
358
|
def self.call name, supplied, optional = {}, option_string = ""
|
224
|
-
GLib
|
225
|
-
"name = #{name}, supplied = #{supplied}, "
|
359
|
+
GLib.logger.debug("Vips::VipsOperation.call") {
|
360
|
+
"name = #{name}, supplied = #{supplied}, " \
|
226
361
|
"optional = #{optional}, option_string = #{option_string}"
|
227
362
|
}
|
228
363
|
|
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
|
364
|
+
introspect = Introspect.get name
|
365
|
+
required_input = introspect.required_input
|
366
|
+
required_output = introspect.required_output
|
367
|
+
optional_input = introspect.optional_input
|
368
|
+
optional_output = introspect.optional_output
|
369
|
+
destructive = introspect.destructive
|
247
370
|
|
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
371
|
unless supplied.is_a? Array
|
263
|
-
raise Vips::Error, "unable to call #{name}: "
|
264
|
-
|
372
|
+
raise Vips::Error, "unable to call #{name}: " \
|
373
|
+
"argument array is not an array"
|
265
374
|
end
|
266
375
|
unless optional.is_a? Hash
|
267
|
-
raise Vips::Error, "unable to call #{name}: "
|
268
|
-
|
376
|
+
raise Vips::Error, "unable to call #{name}: " \
|
377
|
+
"optional arguments are not a hash"
|
269
378
|
end
|
379
|
+
|
270
380
|
if supplied.length != required_input.length
|
271
|
-
raise Vips::Error, "unable to call #{name}: "
|
272
|
-
|
273
|
-
|
381
|
+
raise Vips::Error, "unable to call #{name}: " \
|
382
|
+
"you supplied #{supplied.length} arguments, " \
|
383
|
+
"but operation needs #{required_input.length}."
|
274
384
|
end
|
275
385
|
|
276
|
-
#
|
386
|
+
# all supplied_optional keys should be in optional_input or
|
277
387
|
# optional_output
|
278
388
|
optional.each do |key, _value|
|
279
389
|
arg_name = key.to_s
|
280
390
|
|
281
391
|
unless optional_input.has_key?(arg_name) ||
|
282
|
-
|
283
|
-
raise Vips::Error, "unable to call #{name}: "
|
284
|
-
|
392
|
+
optional_output.has_key?(arg_name)
|
393
|
+
raise Vips::Error, "unable to call #{name}: " \
|
394
|
+
"unknown option #{arg_name}"
|
285
395
|
end
|
286
396
|
end
|
287
397
|
|
@@ -290,24 +400,68 @@ module Vips
|
|
290
400
|
#
|
291
401
|
# look inside array and hash arguments, since we may be passing an
|
292
402
|
# array of images
|
293
|
-
|
294
|
-
|
403
|
+
#
|
404
|
+
# also enforce the rules around mutable and non-mutable images
|
405
|
+
match_image = nil
|
406
|
+
flat_find(supplied) do |value|
|
407
|
+
if match_image
|
408
|
+
# no non-first image arg can ever be mutable
|
409
|
+
if value.is_a?(MutableImage)
|
410
|
+
raise Vips::Error, "unable to call #{name}: " \
|
411
|
+
"only the first image argument can be mutable"
|
412
|
+
end
|
413
|
+
elsif destructive
|
414
|
+
if value.is_a?(Image)
|
415
|
+
raise Vips::Error, "unable to call #{name}: " \
|
416
|
+
"first image argument to a destructive " \
|
417
|
+
"operation must be mutable"
|
418
|
+
elsif value.is_a?(MutableImage)
|
419
|
+
match_image = value
|
420
|
+
end
|
421
|
+
elsif value.is_a?(MutableImage)
|
422
|
+
# non destructive operation, so no mutable images
|
423
|
+
raise Vips::Error, "unable to call #{name}: " \
|
424
|
+
"must not pass mutable images to " \
|
425
|
+
"non-destructive operations"
|
426
|
+
elsif value.is_a?(Image)
|
427
|
+
match_image = value
|
428
|
+
end
|
429
|
+
|
430
|
+
# keep looping
|
431
|
+
false
|
295
432
|
end
|
296
433
|
|
434
|
+
op = Operation.new introspect.vips_name
|
435
|
+
|
297
436
|
# set any string args first so they can't be overridden
|
298
|
-
|
299
|
-
if Vips
|
437
|
+
unless option_string.nil?
|
438
|
+
if Vips.vips_object_set_from_string(op, option_string) != 0
|
300
439
|
raise Vips::Error
|
301
440
|
end
|
302
441
|
end
|
303
442
|
|
443
|
+
# collect a list of all input references here
|
444
|
+
references = Set.new
|
445
|
+
|
446
|
+
add_reference = lambda do |x|
|
447
|
+
if x.is_a?(Vips::Image)
|
448
|
+
x.references.each do |i|
|
449
|
+
references << i
|
450
|
+
end
|
451
|
+
end
|
452
|
+
false
|
453
|
+
end
|
454
|
+
|
304
455
|
# set all required inputs
|
305
456
|
required_input.each_index do |i|
|
306
|
-
|
307
|
-
|
457
|
+
details = required_input[i]
|
458
|
+
arg_name = details[:arg_name]
|
459
|
+
flags = details[:flags]
|
460
|
+
gtype = details[:gtype]
|
308
461
|
value = supplied[i]
|
309
462
|
|
310
|
-
|
463
|
+
flat_find value, &add_reference
|
464
|
+
op.set arg_name, value, match_image, flags, gtype, destructive
|
311
465
|
end
|
312
466
|
|
313
467
|
# set all optional inputs
|
@@ -317,18 +471,31 @@ module Vips
|
|
317
471
|
arg_name = key.to_s
|
318
472
|
|
319
473
|
if optional_input.has_key? arg_name
|
320
|
-
|
474
|
+
details = optional_input[arg_name]
|
475
|
+
flags = details[:flags]
|
476
|
+
gtype = details[:gtype]
|
321
477
|
|
322
|
-
|
478
|
+
flat_find value, &add_reference
|
479
|
+
op.set arg_name, value, match_image, flags, gtype, destructive
|
323
480
|
end
|
324
481
|
end
|
325
482
|
|
326
483
|
op = op.build
|
327
484
|
|
485
|
+
# attach all input refs to output x
|
486
|
+
set_reference = lambda do |x|
|
487
|
+
if x.is_a? Vips::Image
|
488
|
+
x.references += references
|
489
|
+
end
|
490
|
+
false
|
491
|
+
end
|
492
|
+
|
328
493
|
# get all required results
|
329
494
|
result = []
|
330
|
-
required_output.each do |
|
331
|
-
|
495
|
+
required_output.each do |details|
|
496
|
+
value = details[:arg_name]
|
497
|
+
flat_find value, &set_reference
|
498
|
+
result << op.get(value)
|
332
499
|
end
|
333
500
|
|
334
501
|
# fetch all optional ones
|
@@ -337,7 +504,9 @@ module Vips
|
|
337
504
|
arg_name = key.to_s
|
338
505
|
|
339
506
|
if optional_output.has_key? arg_name
|
340
|
-
|
507
|
+
value = op.get arg_name
|
508
|
+
flat_find value, &set_reference
|
509
|
+
optional_results[arg_name] = value
|
341
510
|
end
|
342
511
|
end
|
343
512
|
|
@@ -349,11 +518,11 @@ module Vips
|
|
349
518
|
result = nil
|
350
519
|
end
|
351
520
|
|
352
|
-
GLib
|
521
|
+
GLib.logger.debug("Vips::Operation.call") { "result = #{result}" }
|
353
522
|
|
354
|
-
Vips
|
523
|
+
Vips.vips_object_unref_outputs op
|
355
524
|
|
356
|
-
|
525
|
+
result
|
357
526
|
end
|
358
527
|
end
|
359
528
|
end
|
data/lib/vips/region.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
# This module provides an interface to the top level bits of libvips
|
2
|
+
# via ruby-ffi.
|
3
|
+
#
|
4
|
+
# Author:: John Cupitt (mailto:jcupitt@gmail.com)
|
5
|
+
# License:: MIT
|
6
|
+
|
7
|
+
require "ffi"
|
8
|
+
|
9
|
+
module Vips
|
10
|
+
attach_function :vips_region_new, [:pointer], :pointer
|
11
|
+
|
12
|
+
if Vips.at_least_libvips?(8, 8)
|
13
|
+
attach_function :vips_region_fetch, [:pointer, :int, :int, :int, :int, SizeStruct.ptr], :pointer
|
14
|
+
attach_function :vips_region_width, [:pointer], :int
|
15
|
+
attach_function :vips_region_height, [:pointer], :int
|
16
|
+
end
|
17
|
+
|
18
|
+
# A region on an image. Create one, then use `fetch` to quickly get a region
|
19
|
+
# of pixels.
|
20
|
+
#
|
21
|
+
# For example:
|
22
|
+
#
|
23
|
+
# ```ruby
|
24
|
+
# region = Vips::Region.new(image)
|
25
|
+
# pixels = region.fetch(10, 10, 100, 100)
|
26
|
+
# ```
|
27
|
+
class Region < Vips::Object
|
28
|
+
# The layout of the VipsRegion struct.
|
29
|
+
module RegionLayout
|
30
|
+
def self.included(base)
|
31
|
+
base.class_eval do
|
32
|
+
layout :parent, Vips::Object::Struct
|
33
|
+
# rest opaque
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class Struct < Vips::Object::Struct
|
39
|
+
include RegionLayout
|
40
|
+
end
|
41
|
+
|
42
|
+
class ManagedStruct < Vips::Object::ManagedStruct
|
43
|
+
include RegionLayout
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize(name)
|
47
|
+
ptr = Vips.vips_region_new name
|
48
|
+
raise Vips::Error if ptr.null?
|
49
|
+
|
50
|
+
super ptr
|
51
|
+
end
|
52
|
+
|
53
|
+
def width
|
54
|
+
Vips.vips_region_width self
|
55
|
+
end
|
56
|
+
|
57
|
+
def height
|
58
|
+
Vips.vips_region_height self
|
59
|
+
end
|
60
|
+
|
61
|
+
# Fetch a region filled with pixel data.
|
62
|
+
def fetch(left, top, width, height)
|
63
|
+
len = Vips::SizeStruct.new
|
64
|
+
ptr = Vips.vips_region_fetch self, left, top, width, height, len
|
65
|
+
raise Vips::Error if ptr.null?
|
66
|
+
|
67
|
+
# wrap up as an autopointer
|
68
|
+
ptr = FFI::AutoPointer.new(ptr, GLib::G_FREE)
|
69
|
+
|
70
|
+
ptr.get_bytes 0, len[:value]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|