ruby-vips8 0.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 +7 -0
- data/.rspec +1 -0
- data/.yardopts +10 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +84 -0
- data/LICENSE.txt +20 -0
- data/README.md +170 -0
- data/Rakefile +45 -0
- data/TODO +11 -0
- data/VERSION +1 -0
- data/example/annotate.rb +17 -0
- data/example/daltonize8.rb +75 -0
- data/example/example1.rb +84 -0
- data/example/example2.rb +31 -0
- data/example/example3.rb +19 -0
- data/example/example4.rb +18 -0
- data/example/example5.rb +31 -0
- data/example/trim8.rb +41 -0
- data/example/watermark.rb +44 -0
- data/example/wobble.rb +36 -0
- data/lib/vips8.rb +153 -0
- data/lib/vips8/access.rb +14 -0
- data/lib/vips8/align.rb +11 -0
- data/lib/vips8/angle.rb +12 -0
- data/lib/vips8/angle45.rb +16 -0
- data/lib/vips8/argument.rb +163 -0
- data/lib/vips8/bandformat.rb +20 -0
- data/lib/vips8/call.rb +302 -0
- data/lib/vips8/coding.rb +14 -0
- data/lib/vips8/demandstyle.rb +35 -0
- data/lib/vips8/direction.rb +11 -0
- data/lib/vips8/error.rb +30 -0
- data/lib/vips8/extend.rb +22 -0
- data/lib/vips8/foreignflags.rb +20 -0
- data/lib/vips8/image.rb +1383 -0
- data/lib/vips8/interpolate.rb +37 -0
- data/lib/vips8/interpretation.rb +28 -0
- data/lib/vips8/methods.rb +1807 -0
- data/lib/vips8/operation.rb +19 -0
- data/ruby-vips8.gemspec +112 -0
- data/spec/image_spec.rb +515 -0
- data/spec/samples/balloon.v +0 -0
- data/spec/samples/ghost.ppm +405 -0
- data/spec/samples/huge.jpg +0 -0
- data/spec/samples/icc.jpg +0 -0
- data/spec/samples/lcd.icc +0 -0
- data/spec/samples/lion.svg +154 -0
- data/spec/samples/sample.csv +7 -0
- data/spec/samples/sample.exr +0 -0
- data/spec/samples/wagon.jpg +0 -0
- data/spec/samples/wagon.v +0 -0
- data/spec/spec_helper.rb +49 -0
- data/spec/vips_spec.rb +74 -0
- metadata +198 -0
data/lib/vips8/access.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
module Vips
|
2
|
+
# The type of access an operation has to supply.
|
3
|
+
#
|
4
|
+
# * `:random` means requests can come in any order.
|
5
|
+
#
|
6
|
+
# * `:sequential` means requests will be top-to-bottom, but with some
|
7
|
+
# amount of buffering behind the read point for small non-local
|
8
|
+
# accesses.
|
9
|
+
#
|
10
|
+
# * `:sequential_unbuffered` means requests will be strictly
|
11
|
+
# top-to-bottom with no read-behind. This can save some memory.
|
12
|
+
class Access
|
13
|
+
end
|
14
|
+
end
|
data/lib/vips8/align.rb
ADDED
data/lib/vips8/angle.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
module Vips
|
2
|
+
|
3
|
+
# Various fixed 45 degree rotation angles. See {Vips::Image.rot45}.
|
4
|
+
#
|
5
|
+
# * `:d0` no rotate
|
6
|
+
# * `:d45` 45 degrees clockwise
|
7
|
+
# * `:d90` 90 degrees clockwise
|
8
|
+
# * `:d135` 135 degrees clockwise
|
9
|
+
# * `:d180` 180 degrees
|
10
|
+
# * `:d225` 135 degrees anti-clockwise
|
11
|
+
# * `:d270` 90 degrees anti-clockwise
|
12
|
+
# * `:d315` 45 degrees anti-clockwise
|
13
|
+
|
14
|
+
class Angle45
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
|
2
|
+
module Vips
|
3
|
+
|
4
|
+
# This class is used internally to convert Ruby values to arguments to
|
5
|
+
# libvips operations.
|
6
|
+
# @private
|
7
|
+
class Argument
|
8
|
+
attr_reader :op, :name, :flags, :priority, :isset, :prop
|
9
|
+
attr_reader :blurb, :gtype, :type
|
10
|
+
|
11
|
+
# map gobject-introspection's ruby class names to ours
|
12
|
+
@@map_goi_to_vips = {
|
13
|
+
"TrueClass" => "Boolean",
|
14
|
+
"Vips::ArrayDouble" => "Array<Double>",
|
15
|
+
"Vips::ArrayInt" => "Array<Integer>",
|
16
|
+
"Vips::ArrayImage" => "Array<Image>",
|
17
|
+
"Vips::ArrayString" => "Array<String>",
|
18
|
+
}
|
19
|
+
|
20
|
+
def initialize(op, name)
|
21
|
+
@op = op
|
22
|
+
@name = name.tr '-', '_'
|
23
|
+
@prop = op.gtype.to_class.property name
|
24
|
+
@blurb = @prop.blurb
|
25
|
+
@gtype = prop.value_type
|
26
|
+
@flags = op.get_argument_flags name
|
27
|
+
@priority = op.get_argument_priority @name
|
28
|
+
@isset = op.argument_isset @name
|
29
|
+
|
30
|
+
type = GLib::Type[gtype.name].to_class.name
|
31
|
+
type = @@map_goi_to_vips[type] if @@map_goi_to_vips.include? type
|
32
|
+
@type = type
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def self.imageize match_image, value
|
38
|
+
return value if match_image == nil
|
39
|
+
return value if value.is_a? Vips::Image
|
40
|
+
|
41
|
+
# 2D array values become tiny 2D images
|
42
|
+
if value.is_a? Array and value[0].is_a? Array
|
43
|
+
return Vips::Image.new_from_array value
|
44
|
+
end
|
45
|
+
|
46
|
+
# if there's nothing to match to, we also make a 2D image
|
47
|
+
if match_image == nil
|
48
|
+
return Vips::Image.new_from_array value
|
49
|
+
end
|
50
|
+
|
51
|
+
# we have a 1D array ... use that as a pixel constant and expand
|
52
|
+
# to match match_image
|
53
|
+
pixel = (Vips::Image.black(1, 1) + value).cast(match_image.format)
|
54
|
+
pixel = pixel.copy :interpretation => match_image.interpretation,
|
55
|
+
:xres => match_image.xres, :yres => match_image.yres
|
56
|
+
pixel.embed(0, 0, match_image.width, match_image.height,
|
57
|
+
:extend => :copy)
|
58
|
+
end
|
59
|
+
|
60
|
+
# @private
|
61
|
+
class ArrayImageConst < Vips::ArrayImage
|
62
|
+
def self.new(value)
|
63
|
+
if not value.is_a? Array
|
64
|
+
value = [value]
|
65
|
+
end
|
66
|
+
|
67
|
+
match_image = value.find {|x| x.is_a? Vips::Image}
|
68
|
+
if match_image == nil
|
69
|
+
raise Vips::Error,
|
70
|
+
"Argument must contain at least one image."
|
71
|
+
end
|
72
|
+
|
73
|
+
value = value.map {|x| Argument::imageize match_image, x}
|
74
|
+
|
75
|
+
# we'd like to just
|
76
|
+
# super(value)
|
77
|
+
# to construct, but the gobject-introspection gem does not
|
78
|
+
# support new from object array ... instead, we build in stages
|
79
|
+
array = Vips::ArrayImage.empty
|
80
|
+
value.each {|x| array = array.append(x)}
|
81
|
+
|
82
|
+
return array
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# if this gtype needs an array, try to transform the value into one
|
87
|
+
def self.arrayize(gtype, value)
|
88
|
+
arrayize_map = {
|
89
|
+
GLib::Type["VipsArrayDouble"] => Vips::ArrayDouble,
|
90
|
+
GLib::Type["VipsArrayInt"] => Vips::ArrayInt,
|
91
|
+
GLib::Type["VipsArrayImage"] => ArrayImageConst
|
92
|
+
}
|
93
|
+
|
94
|
+
if arrayize_map.has_key? gtype
|
95
|
+
if not value.is_a? Array
|
96
|
+
value = [value]
|
97
|
+
end
|
98
|
+
|
99
|
+
value = arrayize_map[gtype].new(value)
|
100
|
+
end
|
101
|
+
|
102
|
+
value
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.unwrap value
|
106
|
+
[Vips::Blob, Vips::ArrayDouble, Vips::ArrayImage,
|
107
|
+
Vips::ArrayInt, Vips::RefString].each do |cls|
|
108
|
+
if value.is_a? cls
|
109
|
+
value, length = value.get
|
110
|
+
|
111
|
+
# blobs come from gobject-introspection as arrays ...
|
112
|
+
# repack as strings for convenience
|
113
|
+
if value and cls == Vips::Blob
|
114
|
+
value = value.pack("C*")
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
value
|
122
|
+
end
|
123
|
+
|
124
|
+
public
|
125
|
+
|
126
|
+
def set_value(match_image, value)
|
127
|
+
# array-ize
|
128
|
+
value = Argument::arrayize gtype, value
|
129
|
+
|
130
|
+
# blob-ize
|
131
|
+
if gtype.type_is_a? GLib::Type["VipsBlob"]
|
132
|
+
if not value.is_a? Vips::Blob
|
133
|
+
value = Vips::Blob.copy value
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# image-ize
|
138
|
+
if gtype.type_is_a? GLib::Type["VipsImage"]
|
139
|
+
if not value.is_a? Vips::Image
|
140
|
+
value = Argument::imageize match_image, value
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# MODIFY input images need to be copied before assigning them
|
145
|
+
if (flags & :modify) != 0
|
146
|
+
# don't use .copy(): we want to make a new pipeline with no
|
147
|
+
# reference back to the old stuff ... this way we can free the
|
148
|
+
# previous image earlier
|
149
|
+
new_image = Vips::Image.memory
|
150
|
+
value.write new_image
|
151
|
+
value = new_image
|
152
|
+
end
|
153
|
+
|
154
|
+
op.set_property @name, value
|
155
|
+
end
|
156
|
+
|
157
|
+
def get_value
|
158
|
+
Argument::unwrap @op.get_property(@name)
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Vips
|
2
|
+
|
3
|
+
# The format used for each band element. Each corresponds to a native C type
|
4
|
+
# for the current machine.
|
5
|
+
#
|
6
|
+
# * `:notset` invalid setting
|
7
|
+
# * `:uchar` unsigned char format
|
8
|
+
# * `:char` char format
|
9
|
+
# * `:ushort` unsigned short format
|
10
|
+
# * `:short` short format
|
11
|
+
# * `:uint` unsigned int format
|
12
|
+
# * `:int` int format
|
13
|
+
# * `:float` float format
|
14
|
+
# * `:complex` complex (two floats) format
|
15
|
+
# * `:double` double float format
|
16
|
+
# * `:dpcomplex` double complex (two double) format
|
17
|
+
class BandFormat
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
data/lib/vips8/call.rb
ADDED
@@ -0,0 +1,302 @@
|
|
1
|
+
module Vips
|
2
|
+
|
3
|
+
# call a vips operation ... this will crash if there's a GC during the call,
|
4
|
+
# I've really no idea why :-(
|
5
|
+
#
|
6
|
+
# Workaround: don't call this directly, use call_base (see below) instead.
|
7
|
+
# This will disable the GC, call this operation, then reenable it.
|
8
|
+
|
9
|
+
private
|
10
|
+
def self.call_base_nogc(name, instance, option_string, supplied_values)
|
11
|
+
log "in Vips::call_base"
|
12
|
+
log "name = #{name}"
|
13
|
+
log "instance = #{instance}"
|
14
|
+
log "option_string = #{option_string}"
|
15
|
+
log "supplied_values are:"
|
16
|
+
supplied_values.each {|x| log " #{x}"}
|
17
|
+
|
18
|
+
if supplied_values.last.is_a? Hash
|
19
|
+
optional_values = supplied_values.last
|
20
|
+
supplied_values.delete_at -1
|
21
|
+
else
|
22
|
+
optional_values = {}
|
23
|
+
end
|
24
|
+
|
25
|
+
begin
|
26
|
+
op = Vips::Operation.new name
|
27
|
+
rescue
|
28
|
+
raise Vips::Error, "no operator '#{name}'"
|
29
|
+
end
|
30
|
+
|
31
|
+
# set string options first
|
32
|
+
log "setting string options ..."
|
33
|
+
if option_string
|
34
|
+
if op.set_from_string(option_string) != 0
|
35
|
+
raise Error
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
all_args = op.get_args
|
40
|
+
|
41
|
+
# the instance, if supplied, must be a vips image ... we use it for
|
42
|
+
# match_image, below
|
43
|
+
if instance and not instance.is_a? Vips::Image
|
44
|
+
raise Vips::Error, "@instance is not a Vips::Image."
|
45
|
+
end
|
46
|
+
|
47
|
+
# if the op needs images but the user supplies constants, we expand
|
48
|
+
# them to match the first input image argument ... find the first
|
49
|
+
# image
|
50
|
+
log "searching for first image argument ..."
|
51
|
+
match_image = instance
|
52
|
+
if match_image == nil
|
53
|
+
match_image = supplied_values.find {|x| x.is_a? Vips::Image}
|
54
|
+
end
|
55
|
+
if match_image == nil
|
56
|
+
match = optional_values.find do |name, value|
|
57
|
+
value.is_a? Vips::Image
|
58
|
+
end
|
59
|
+
# if we found a match, it'll be [name, value]
|
60
|
+
if match
|
61
|
+
match_image = match[1]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# find unassigned required input args
|
66
|
+
log "finding unassigned required input arguments ..."
|
67
|
+
required_input = all_args.select do |arg|
|
68
|
+
not arg.isset and
|
69
|
+
(arg.flags & :input) != 0 and
|
70
|
+
(arg.flags & :required) != 0
|
71
|
+
end
|
72
|
+
|
73
|
+
# do we have a non-nil instance? set the first image arg with this
|
74
|
+
if instance != nil
|
75
|
+
log "setting first image arg with instance ..."
|
76
|
+
x = required_input.find do |arg|
|
77
|
+
gtype = GLib::Type["VipsImage"]
|
78
|
+
vtype = arg.prop.value_type
|
79
|
+
|
80
|
+
vtype.type_is_a? gtype
|
81
|
+
end
|
82
|
+
if x == nil
|
83
|
+
raise Vips::Error,
|
84
|
+
"No #{instance.class} argument to #{name}."
|
85
|
+
end
|
86
|
+
x.set_value match_image, instance
|
87
|
+
required_input.delete x
|
88
|
+
end
|
89
|
+
|
90
|
+
if required_input.length != supplied_values.length
|
91
|
+
raise Vips::Error,
|
92
|
+
"Wrong number of arguments. '#{name}' requires " +
|
93
|
+
"#{required_input.length} arguments, you supplied " +
|
94
|
+
"#{supplied_values.length}."
|
95
|
+
end
|
96
|
+
|
97
|
+
log "setting required input arguments ..."
|
98
|
+
required_input.zip(supplied_values).each do |arg, value|
|
99
|
+
arg.set_value match_image, value
|
100
|
+
end
|
101
|
+
|
102
|
+
# find optional unassigned input args
|
103
|
+
log "finding optional unassigned input arguments ..."
|
104
|
+
optional_input = all_args.select do |arg|
|
105
|
+
not arg.isset and
|
106
|
+
(arg.flags & :input) != 0 and
|
107
|
+
(arg.flags & :required) == 0
|
108
|
+
end
|
109
|
+
|
110
|
+
# make a hash from name to arg
|
111
|
+
optional_input = Hash[
|
112
|
+
optional_input.map(&:name).zip(optional_input)]
|
113
|
+
|
114
|
+
# find optional unassigned output args
|
115
|
+
log "finding optional unassigned output arguments ..."
|
116
|
+
optional_output = all_args.select do |arg|
|
117
|
+
not arg.isset and
|
118
|
+
(arg.flags & :output) != 0 and
|
119
|
+
(arg.flags & :required) == 0
|
120
|
+
end
|
121
|
+
optional_output = Hash[
|
122
|
+
optional_output.map(&:name).zip(optional_output)]
|
123
|
+
|
124
|
+
# set all optional args
|
125
|
+
log "setting optional values ..."
|
126
|
+
optional_values.each do |name, value|
|
127
|
+
# we are passed symbols as keys
|
128
|
+
name = name.to_s
|
129
|
+
if optional_input.has_key? name
|
130
|
+
log "setting #{name} to #{value}"
|
131
|
+
optional_input[name].set_value match_image, value
|
132
|
+
elsif optional_output.has_key? name and value != true
|
133
|
+
raise Vips::Error,
|
134
|
+
"Optional output argument #{name} must be true."
|
135
|
+
elsif not optional_output.has_key? name
|
136
|
+
raise Vips::Error, "No such option '#{name}',"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
log "building ..."
|
141
|
+
|
142
|
+
op2 = Vips::cache_operation_lookup op
|
143
|
+
if op2
|
144
|
+
log "cache hit"
|
145
|
+
op = op2
|
146
|
+
|
147
|
+
all_args = op.get_args
|
148
|
+
|
149
|
+
# find optional output args
|
150
|
+
optional_output = all_args.select do |arg|
|
151
|
+
(arg.flags & :output) != 0 and
|
152
|
+
(arg.flags & :required) == 0
|
153
|
+
end
|
154
|
+
optional_output = Hash[
|
155
|
+
optional_output.map(&:name).zip(optional_output)]
|
156
|
+
else
|
157
|
+
log "cache miss ... building"
|
158
|
+
if not op.build
|
159
|
+
raise Vips::Error
|
160
|
+
end
|
161
|
+
showall
|
162
|
+
|
163
|
+
log "adding to cache ... "
|
164
|
+
Vips::cache_operation_add op
|
165
|
+
end
|
166
|
+
|
167
|
+
log "fetching outputs ..."
|
168
|
+
|
169
|
+
# gather output args
|
170
|
+
out = []
|
171
|
+
|
172
|
+
all_args.each do |arg|
|
173
|
+
# required output
|
174
|
+
if (arg.flags & :output) != 0 and
|
175
|
+
(arg.flags & :required) != 0
|
176
|
+
log "fetching required output #{arg.name}"
|
177
|
+
out << arg.get_value
|
178
|
+
end
|
179
|
+
|
180
|
+
# modified input arg ... this will get the result of the
|
181
|
+
# copy() we did in Argument.set_value above
|
182
|
+
if (arg.flags & :input) != 0 and
|
183
|
+
(arg.flags & :modify) != 0
|
184
|
+
log "fetching modified input arg ..."
|
185
|
+
out << arg.get_value
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
opts = {}
|
190
|
+
optional_values.each do |name, value|
|
191
|
+
# we are passed symbols as keys
|
192
|
+
name = name.to_s
|
193
|
+
if optional_output.has_key? name
|
194
|
+
log "fetching optional output arg ..."
|
195
|
+
opts[name] = optional_output[name].get_value
|
196
|
+
end
|
197
|
+
end
|
198
|
+
out << opts if opts != {}
|
199
|
+
|
200
|
+
if out.length == 1
|
201
|
+
out = out[0]
|
202
|
+
elsif out.length == 0
|
203
|
+
out = nil
|
204
|
+
end
|
205
|
+
|
206
|
+
log "unreffing outputs ..."
|
207
|
+
op.unref_outputs
|
208
|
+
op = nil
|
209
|
+
# showall
|
210
|
+
|
211
|
+
log "success! #{name}.out = #{out}"
|
212
|
+
|
213
|
+
return out
|
214
|
+
end
|
215
|
+
|
216
|
+
# run call_base_nogc, with the GC disabled
|
217
|
+
private
|
218
|
+
def self.call_base(name, instance, option_string, supplied_values)
|
219
|
+
gc_was_enabled = GC.disable
|
220
|
+
begin
|
221
|
+
result = call_base_nogc name, instance,
|
222
|
+
option_string, supplied_values
|
223
|
+
ensure
|
224
|
+
GC.enable if gc_was_enabled
|
225
|
+
end
|
226
|
+
|
227
|
+
return result
|
228
|
+
end
|
229
|
+
|
230
|
+
public
|
231
|
+
|
232
|
+
# This is the public entry point for the vips8 binding. {call} will run
|
233
|
+
# any vips operation, for example:
|
234
|
+
#
|
235
|
+
# ```ruby
|
236
|
+
# out = Vips::call "black", 100, 100, :bands => 12
|
237
|
+
# ```
|
238
|
+
#
|
239
|
+
# will call the C function
|
240
|
+
#
|
241
|
+
# ```C
|
242
|
+
# vips_black( &out, 100, 100, "bands", 12, NULL );
|
243
|
+
# ```
|
244
|
+
#
|
245
|
+
# There are {Image#method_missing} hooks which will run {call} for you
|
246
|
+
# on {Image} for undefined instance or class methods. So you can also
|
247
|
+
# write:
|
248
|
+
#
|
249
|
+
# ```ruby
|
250
|
+
# out = Vips::Image.black 100, 100, :bands => 12
|
251
|
+
# ```
|
252
|
+
#
|
253
|
+
# Or perhaps:
|
254
|
+
#
|
255
|
+
# ```ruby
|
256
|
+
# x = Vips::Image.black 100, 100
|
257
|
+
# y = x.invert
|
258
|
+
# ```
|
259
|
+
#
|
260
|
+
# to run the `vips_invert()` operator.
|
261
|
+
#
|
262
|
+
# There are also a set of operator overloads and some convenience functions,
|
263
|
+
# see {Image}.
|
264
|
+
#
|
265
|
+
# If the operator needs a vector constant, {call} will turn a scalar into a
|
266
|
+
# vector for you. So for `x.linear(a, b)`, which calculates
|
267
|
+
# `x * a + b` where `a` and `b` are vector constants, you can write:
|
268
|
+
#
|
269
|
+
# ```ruby
|
270
|
+
# x = Vips::Image.black 100, 100, :bands => 3
|
271
|
+
# y = x.linear(1, 2)
|
272
|
+
# y = x.linear([1], 4)
|
273
|
+
# y = x.linear([1, 2, 3], 4)
|
274
|
+
# ```
|
275
|
+
#
|
276
|
+
# or any other combination. The operator overloads use this facility to
|
277
|
+
# support all the variations on:
|
278
|
+
#
|
279
|
+
# ```ruby
|
280
|
+
# x = Vips::Image.black 100, 100, :bands => 3
|
281
|
+
# y = x * 2
|
282
|
+
# y = x + [1,2,3]
|
283
|
+
# y = x % [1]
|
284
|
+
# ```
|
285
|
+
#
|
286
|
+
# Similarly, whereever an image is required, you can use a constant. The
|
287
|
+
# constant will be expanded to an image matching the first input image
|
288
|
+
# argument. For example, you can write:
|
289
|
+
#
|
290
|
+
# ```
|
291
|
+
# x = Vips::Image.black 100, 100, :bands => 3
|
292
|
+
# y = x.bandjoin(255)
|
293
|
+
# ```
|
294
|
+
#
|
295
|
+
# to add an extra band to the image where each pixel in the new band has
|
296
|
+
# the constant value 255.
|
297
|
+
|
298
|
+
def self.call(name, *args)
|
299
|
+
Vips::call_base name, nil, "", args
|
300
|
+
end
|
301
|
+
|
302
|
+
end
|