ruby-vips 2.0.16 → 2.0.17
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/.rubocop_todo.yml +1 -1
- data/.travis.yml +1 -6
- data/CHANGELOG.md +11 -0
- data/README.md +38 -37
- data/TODO +1 -1
- data/VERSION +1 -1
- data/example/connection.rb +17 -0
- data/example/progress.rb +30 -0
- data/lib/vips.rb +64 -4
- data/lib/vips/connection.rb +46 -0
- data/lib/vips/gobject.rb +9 -1
- data/lib/vips/gvalue.rb +13 -4
- data/lib/vips/image.rb +213 -112
- data/lib/vips/methods.rb +330 -40
- data/lib/vips/object.rb +123 -5
- data/lib/vips/operation.rb +156 -80
- data/lib/vips/region.rb +2 -2
- data/lib/vips/source.rb +89 -0
- data/lib/vips/sourcecustom.rb +90 -0
- data/lib/vips/target.rb +87 -0
- data/lib/vips/targetcustom.rb +78 -0
- data/lib/vips/version.rb +1 -1
- data/ruby-vips.gemspec +3 -1
- metadata +14 -6
data/lib/vips/object.rb
CHANGED
@@ -41,6 +41,97 @@ module Vips
|
|
41
41
|
|
42
42
|
private
|
43
43
|
|
44
|
+
class Progress < FFI::Struct
|
45
|
+
layout :im, :pointer,
|
46
|
+
:run, :int,
|
47
|
+
:eta, :int,
|
48
|
+
:tpels, :int64_t,
|
49
|
+
:npels, :int64_t,
|
50
|
+
:percent, :int,
|
51
|
+
:start, :pointer
|
52
|
+
end
|
53
|
+
|
54
|
+
# Our signal marshalers.
|
55
|
+
#
|
56
|
+
# These are functions which take the handler as a param and return a
|
57
|
+
# closure with the right FFI signature for g_signal_connect for this
|
58
|
+
# specific signal.
|
59
|
+
#
|
60
|
+
# ruby-ffi makes it hard to use the g_signal_connect user data param
|
61
|
+
# to pass the function pointer through, unfortunately.
|
62
|
+
#
|
63
|
+
# We can't throw exceptions across C, so we must catch everything.
|
64
|
+
|
65
|
+
MARSHAL_PROGRESS = Proc.new do |handler|
|
66
|
+
FFI::Function.new(:void, [:pointer, :pointer, :pointer]) do |vi, prog, cb|
|
67
|
+
begin
|
68
|
+
handler.(Progress.new(prog))
|
69
|
+
rescue Exception => e
|
70
|
+
puts "progress: #{e}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
MARSHAL_READ = Proc.new do |handler|
|
76
|
+
FFI::Function.new(:int64_t, [:pointer, :pointer, :int64_t]) do |i, p, len|
|
77
|
+
begin
|
78
|
+
result = handler.(p, len)
|
79
|
+
rescue Exception => e
|
80
|
+
puts "read: #{e}"
|
81
|
+
result = 0
|
82
|
+
end
|
83
|
+
|
84
|
+
result
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
MARSHAL_SEEK = Proc.new do |handler|
|
89
|
+
FFI::Function.new(:int64_t, [:pointer, :int64_t, :int]) do |i, off, whence|
|
90
|
+
begin
|
91
|
+
result = handler.(off, whence)
|
92
|
+
rescue Exception => e
|
93
|
+
puts "seek: #{e}"
|
94
|
+
result = -1
|
95
|
+
end
|
96
|
+
|
97
|
+
result
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
MARSHAL_WRITE = Proc.new do |handler|
|
102
|
+
FFI::Function.new(:int64_t, [:pointer, :pointer, :int64_t]) do |i, p, len|
|
103
|
+
begin
|
104
|
+
result = handler.(p, len)
|
105
|
+
rescue Exception => e
|
106
|
+
puts "write: #{e}"
|
107
|
+
result = 0
|
108
|
+
end
|
109
|
+
|
110
|
+
result
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
MARSHAL_FINISH = Proc.new do |handler|
|
115
|
+
FFI::Function.new(:void, [:pointer, :pointer]) do |i, cb|
|
116
|
+
begin
|
117
|
+
handler.()
|
118
|
+
rescue Exception => e
|
119
|
+
puts "finish: #{e}"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# map signal name to marshal proc
|
125
|
+
MARSHAL_ALL = {
|
126
|
+
:preeval => MARSHAL_PROGRESS,
|
127
|
+
:eval => MARSHAL_PROGRESS,
|
128
|
+
:posteval => MARSHAL_PROGRESS,
|
129
|
+
:read => MARSHAL_READ,
|
130
|
+
:seek => MARSHAL_SEEK,
|
131
|
+
:write => MARSHAL_WRITE,
|
132
|
+
:finish => MARSHAL_FINISH,
|
133
|
+
}
|
134
|
+
|
44
135
|
attach_function :vips_enum_from_nick, [:string, :GType, :string], :int
|
45
136
|
attach_function :vips_enum_nick, [:GType, :int], :string
|
46
137
|
|
@@ -115,15 +206,15 @@ module Vips
|
|
115
206
|
# return a pspec, or nil ... nil wil leave a message in the error log
|
116
207
|
# which you must clear
|
117
208
|
def get_pspec name
|
118
|
-
|
209
|
+
ppspec = GObject::GParamSpecPtr.new
|
119
210
|
argument_class = Vips::ArgumentClassPtr.new
|
120
211
|
argument_instance = Vips::ArgumentInstancePtr.new
|
121
212
|
|
122
213
|
result = Vips::vips_object_get_argument self, name,
|
123
|
-
|
214
|
+
ppspec, argument_class, argument_instance
|
124
215
|
return nil if result != 0
|
125
216
|
|
126
|
-
|
217
|
+
ppspec[:value]
|
127
218
|
end
|
128
219
|
|
129
220
|
# return a gtype, raise an error on not found
|
@@ -131,7 +222,7 @@ module Vips
|
|
131
222
|
pspec = get_pspec name
|
132
223
|
raise Vips::Error unless pspec
|
133
224
|
|
134
|
-
pspec[:
|
225
|
+
pspec[:value_type]
|
135
226
|
end
|
136
227
|
|
137
228
|
# return a gtype, 0 on not found
|
@@ -142,7 +233,7 @@ module Vips
|
|
142
233
|
return 0
|
143
234
|
end
|
144
235
|
|
145
|
-
pspec[:
|
236
|
+
pspec[:value_type]
|
146
237
|
end
|
147
238
|
|
148
239
|
def get name
|
@@ -151,6 +242,7 @@ module Vips
|
|
151
242
|
gvalue.init gtype
|
152
243
|
GObject::g_object_get_property self, name, gvalue
|
153
244
|
result = gvalue.get
|
245
|
+
gvalue.unset
|
154
246
|
|
155
247
|
GLib::logger.debug("Vips::Object.get") { "#{name} == #{result}" }
|
156
248
|
|
@@ -165,7 +257,33 @@ module Vips
|
|
165
257
|
gvalue.init gtype
|
166
258
|
gvalue.set value
|
167
259
|
GObject::g_object_set_property self, name, gvalue
|
260
|
+
gvalue.unset
|
261
|
+
end
|
262
|
+
|
263
|
+
def signal_connect name, handler=nil
|
264
|
+
marshal = MARSHAL_ALL[name.to_sym]
|
265
|
+
raise Vips::Error, "unsupported signal #{name}" if marshal == nil
|
266
|
+
|
267
|
+
if block_given?
|
268
|
+
# This will grab any block given to us and make it into a proc
|
269
|
+
prc = Proc.new
|
270
|
+
elsif handler
|
271
|
+
# We assume the hander is a proc (perhaps we should test)
|
272
|
+
prc = handler
|
273
|
+
else
|
274
|
+
raise Vips::Error, "must supply either block or handler"
|
275
|
+
end
|
276
|
+
|
277
|
+
# The marshal function will make a closure with the right type signature
|
278
|
+
# for the selected signal
|
279
|
+
callback = marshal.(prc)
|
280
|
+
|
281
|
+
# we need to make sure this is not GCd while self is alive
|
282
|
+
@references << callback
|
283
|
+
|
284
|
+
GObject::g_signal_connect_data(self, name.to_s, callback, nil, nil, 0)
|
168
285
|
end
|
286
|
+
|
169
287
|
end
|
170
288
|
|
171
289
|
class ObjectClass < FFI::Struct
|
data/lib/vips/operation.rb
CHANGED
@@ -11,7 +11,13 @@ module Vips
|
|
11
11
|
|
12
12
|
attach_function :vips_operation_new, [:string], :pointer
|
13
13
|
|
14
|
-
|
14
|
+
# 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
|
+
#
|
17
|
+
# We need FFI to drop the GIL lock during this call and reacquire it when
|
18
|
+
# the call ends, or we'll deadlock.
|
19
|
+
attach_function :vips_cache_operation_build, [:pointer], :pointer,
|
20
|
+
blocking: true
|
15
21
|
attach_function :vips_object_unref_outputs, [:pointer], :void
|
16
22
|
|
17
23
|
callback :argument_map_fn, [:pointer,
|
@@ -35,6 +41,118 @@ module Vips
|
|
35
41
|
|
36
42
|
attach_function :vips_operation_get_flags, [:pointer], :int
|
37
43
|
|
44
|
+
# Introspect a vips operation and return a large structure containing
|
45
|
+
# everything we know about it. This is used for doc generation as well as
|
46
|
+
# call.
|
47
|
+
class Introspect
|
48
|
+
attr_reader :name, :description, :flags, :args, :required_input,
|
49
|
+
:optional_input, :required_output, :optional_output, :member_x,
|
50
|
+
:method_args
|
51
|
+
|
52
|
+
@@introspect_cache = {}
|
53
|
+
|
54
|
+
def initialize name
|
55
|
+
@op = Operation.new name
|
56
|
+
@args = []
|
57
|
+
@required_input = []
|
58
|
+
@optional_input = {}
|
59
|
+
@required_output = []
|
60
|
+
@optional_output = {}
|
61
|
+
|
62
|
+
# find all the arguments the operator can take
|
63
|
+
@op.argument_map do |pspec, argument_class, _argument_instance|
|
64
|
+
flags = argument_class[:flags]
|
65
|
+
if (flags & ARGUMENT_CONSTRUCT) != 0
|
66
|
+
# names can include - as punctuation, but we always use _ in
|
67
|
+
# Ruby
|
68
|
+
arg_name = pspec[:name].tr("-", "_")
|
69
|
+
args << {
|
70
|
+
:arg_name => arg_name,
|
71
|
+
:flags => flags,
|
72
|
+
:gtype => pspec[:value_type]
|
73
|
+
}
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
@args.each do |details|
|
78
|
+
arg_name = details[:arg_name]
|
79
|
+
flags = details[:flags]
|
80
|
+
|
81
|
+
if (flags & ARGUMENT_INPUT) != 0
|
82
|
+
if (flags & ARGUMENT_REQUIRED) != 0 &&
|
83
|
+
(flags & ARGUMENT_DEPRECATED) == 0
|
84
|
+
@required_input << details
|
85
|
+
else
|
86
|
+
# we allow deprecated optional args
|
87
|
+
@optional_input[arg_name] = details
|
88
|
+
end
|
89
|
+
|
90
|
+
# MODIFY INPUT args count as OUTPUT as well
|
91
|
+
if (flags & ARGUMENT_MODIFY) != 0
|
92
|
+
if (flags & ARGUMENT_REQUIRED) != 0 &&
|
93
|
+
(flags & ARGUMENT_DEPRECATED) == 0
|
94
|
+
@required_output << details
|
95
|
+
else
|
96
|
+
@optional_output[arg_name] = details
|
97
|
+
end
|
98
|
+
end
|
99
|
+
elsif (flags & ARGUMENT_OUTPUT) != 0
|
100
|
+
if (flags & ARGUMENT_REQUIRED) != 0 &&
|
101
|
+
(flags & ARGUMENT_DEPRECATED) == 0
|
102
|
+
@required_output << details
|
103
|
+
else
|
104
|
+
# again, allow deprecated optional args
|
105
|
+
@optional_output[arg_name] = details
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Yard comment generation needs a little more introspection. We add this
|
112
|
+
# extra metadata in a separate method to keep the main path as fast as
|
113
|
+
# we can.
|
114
|
+
def add_yard_introspection name
|
115
|
+
@name = name
|
116
|
+
@description = Vips::vips_object_get_description @op
|
117
|
+
@flags = Vips::vips_operation_get_flags @op
|
118
|
+
@member_x = nil
|
119
|
+
@method_args = []
|
120
|
+
|
121
|
+
@args.each do |details|
|
122
|
+
arg_name = details[:arg_name]
|
123
|
+
flags = details[:flags]
|
124
|
+
gtype = details[:gtype]
|
125
|
+
|
126
|
+
details[:yard_name] = arg_name == "in" ? "im" : arg_name
|
127
|
+
pspec = @op.get_pspec arg_name
|
128
|
+
details[:blurb] = GObject::g_param_spec_get_blurb pspec
|
129
|
+
|
130
|
+
if (flags & ARGUMENT_INPUT) != 0 &&
|
131
|
+
(flags & ARGUMENT_REQUIRED) != 0 &&
|
132
|
+
(flags & ARGUMENT_DEPRECATED) == 0
|
133
|
+
# the first required input image is the thing we will be a method
|
134
|
+
# of
|
135
|
+
if @member_x == nil && gtype == IMAGE_TYPE
|
136
|
+
@member_x = details
|
137
|
+
else
|
138
|
+
@method_args << details
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.get name
|
145
|
+
@@introspect_cache[name] ||= Introspect.new name
|
146
|
+
end
|
147
|
+
|
148
|
+
def self.get_yard name
|
149
|
+
introspect = Introspect.get name
|
150
|
+
introspect.add_yard_introspection name
|
151
|
+
introspect
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
|
38
156
|
class Operation < Object
|
39
157
|
# the layout of the VipsOperation struct
|
40
158
|
module OperationLayout
|
@@ -59,7 +177,7 @@ module Vips
|
|
59
177
|
# things like _build
|
60
178
|
if value.is_a? String
|
61
179
|
value = Vips::vips_operation_new value
|
62
|
-
raise Vips::Error if value
|
180
|
+
raise Vips::Error if value.null?
|
63
181
|
end
|
64
182
|
|
65
183
|
super value
|
@@ -67,7 +185,8 @@ module Vips
|
|
67
185
|
|
68
186
|
def build
|
69
187
|
op = Vips::vips_cache_operation_build self
|
70
|
-
if op
|
188
|
+
if op.null?
|
189
|
+
Vips::vips_object_unref_outputs self
|
71
190
|
raise Vips::Error
|
72
191
|
end
|
73
192
|
|
@@ -82,35 +201,16 @@ module Vips
|
|
82
201
|
Vips::vips_argument_map self, fn, nil, nil
|
83
202
|
end
|
84
203
|
|
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]
|
204
|
+
# Search an object for the first element to match a predicate. Search
|
205
|
+
# inside subarrays and sub-hashes. Equlvalent to x.flatten.find{}.
|
206
|
+
def self.flat_find object, &block
|
207
|
+
if object.respond_to? :each
|
208
|
+
object.each do |x|
|
209
|
+
result = flat_find x, &block
|
210
|
+
return result if result != nil
|
101
211
|
end
|
102
|
-
|
103
|
-
|
104
|
-
return args
|
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 }
|
212
|
+
else
|
213
|
+
return object if yield object
|
114
214
|
end
|
115
215
|
|
116
216
|
return nil
|
@@ -122,7 +222,7 @@ module Vips
|
|
122
222
|
|
123
223
|
# 2D array values become tiny 2D images
|
124
224
|
# if there's nothing to match to, we also make a 2D image
|
125
|
-
if (value.is_a?(Array) && value[0].is_a?(Array)) ||
|
225
|
+
if (value.is_a?(Array) && value[0].is_a?(Array)) ||
|
126
226
|
match_image == nil
|
127
227
|
return Image.new_from_array value
|
128
228
|
else
|
@@ -134,9 +234,7 @@ module Vips
|
|
134
234
|
|
135
235
|
# set an operation argument, expanding constants and copying images as
|
136
236
|
# required
|
137
|
-
def set name, value, match_image
|
138
|
-
gtype = get_typeof name
|
139
|
-
|
237
|
+
def set name, value, match_image, flags, gtype
|
140
238
|
if gtype == IMAGE_TYPE
|
141
239
|
value = Operation::imageize match_image, value
|
142
240
|
|
@@ -226,39 +324,12 @@ module Vips
|
|
226
324
|
"optional = #{optional}, option_string = #{option_string}"
|
227
325
|
}
|
228
326
|
|
229
|
-
|
327
|
+
introspect = Introspect.get name
|
328
|
+
required_input = introspect.required_input
|
329
|
+
required_output = introspect.required_output
|
330
|
+
optional_input = introspect.optional_input
|
331
|
+
optional_output = introspect.optional_output
|
230
332
|
|
231
|
-
# find and classify all the arguments the operator can take
|
232
|
-
args = op.get_construct_args
|
233
|
-
required_input = []
|
234
|
-
optional_input = {}
|
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
|
247
|
-
|
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
333
|
unless supplied.is_a? Array
|
263
334
|
raise Vips::Error, "unable to call #{name}: " +
|
264
335
|
"argument array is not an array"
|
@@ -267,19 +338,20 @@ module Vips
|
|
267
338
|
raise Vips::Error, "unable to call #{name}: " +
|
268
339
|
"optional arguments are not a hash"
|
269
340
|
end
|
341
|
+
|
270
342
|
if supplied.length != required_input.length
|
271
343
|
raise Vips::Error, "unable to call #{name}: " +
|
272
344
|
"you supplied #{supplied.length} arguments, " +
|
273
|
-
"but operation needs #{required_input.length}."
|
345
|
+
"but operation needs " + "#{required_input.length}."
|
274
346
|
end
|
275
347
|
|
276
|
-
#
|
348
|
+
# all supplied_optional keys should be in optional_input or
|
277
349
|
# optional_output
|
278
350
|
optional.each do |key, _value|
|
279
351
|
arg_name = key.to_s
|
280
352
|
|
281
353
|
unless optional_input.has_key?(arg_name) ||
|
282
|
-
|
354
|
+
optional_output.has_key?(arg_name)
|
283
355
|
raise Vips::Error, "unable to call #{name}: " +
|
284
356
|
"unknown option #{arg_name}"
|
285
357
|
end
|
@@ -290,9 +362,9 @@ module Vips
|
|
290
362
|
#
|
291
363
|
# look inside array and hash arguments, since we may be passing an
|
292
364
|
# array of images
|
293
|
-
match_image =
|
294
|
-
|
295
|
-
|
365
|
+
match_image = flat_find(supplied) { |value| value.is_a? Image }
|
366
|
+
|
367
|
+
op = Operation.new name
|
296
368
|
|
297
369
|
# set any string args first so they can't be overridden
|
298
370
|
if option_string != nil
|
@@ -303,11 +375,13 @@ module Vips
|
|
303
375
|
|
304
376
|
# set all required inputs
|
305
377
|
required_input.each_index do |i|
|
306
|
-
|
307
|
-
|
378
|
+
details = required_input[i]
|
379
|
+
arg_name = details[:arg_name]
|
380
|
+
flags = details[:flags]
|
381
|
+
gtype = details[:gtype]
|
308
382
|
value = supplied[i]
|
309
383
|
|
310
|
-
op.set arg_name, value, match_image, flags
|
384
|
+
op.set arg_name, value, match_image, flags, gtype
|
311
385
|
end
|
312
386
|
|
313
387
|
# set all optional inputs
|
@@ -317,9 +391,11 @@ module Vips
|
|
317
391
|
arg_name = key.to_s
|
318
392
|
|
319
393
|
if optional_input.has_key? arg_name
|
320
|
-
|
394
|
+
details = optional_input[arg_name]
|
395
|
+
flags = details[:flags]
|
396
|
+
gtype = details[:gtype]
|
321
397
|
|
322
|
-
op.set arg_name, value, match_image, flags
|
398
|
+
op.set arg_name, value, match_image, flags, gtype
|
323
399
|
end
|
324
400
|
end
|
325
401
|
|
@@ -327,8 +403,8 @@ module Vips
|
|
327
403
|
|
328
404
|
# get all required results
|
329
405
|
result = []
|
330
|
-
required_output.each do |
|
331
|
-
result << op.get(arg_name)
|
406
|
+
required_output.each do |details|
|
407
|
+
result << op.get(details[:arg_name])
|
332
408
|
end
|
333
409
|
|
334
410
|
# fetch all optional ones
|