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.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +1 -0
  3. data/.yardopts +10 -0
  4. data/CHANGELOG.md +5 -0
  5. data/Gemfile +15 -0
  6. data/Gemfile.lock +84 -0
  7. data/LICENSE.txt +20 -0
  8. data/README.md +170 -0
  9. data/Rakefile +45 -0
  10. data/TODO +11 -0
  11. data/VERSION +1 -0
  12. data/example/annotate.rb +17 -0
  13. data/example/daltonize8.rb +75 -0
  14. data/example/example1.rb +84 -0
  15. data/example/example2.rb +31 -0
  16. data/example/example3.rb +19 -0
  17. data/example/example4.rb +18 -0
  18. data/example/example5.rb +31 -0
  19. data/example/trim8.rb +41 -0
  20. data/example/watermark.rb +44 -0
  21. data/example/wobble.rb +36 -0
  22. data/lib/vips8.rb +153 -0
  23. data/lib/vips8/access.rb +14 -0
  24. data/lib/vips8/align.rb +11 -0
  25. data/lib/vips8/angle.rb +12 -0
  26. data/lib/vips8/angle45.rb +16 -0
  27. data/lib/vips8/argument.rb +163 -0
  28. data/lib/vips8/bandformat.rb +20 -0
  29. data/lib/vips8/call.rb +302 -0
  30. data/lib/vips8/coding.rb +14 -0
  31. data/lib/vips8/demandstyle.rb +35 -0
  32. data/lib/vips8/direction.rb +11 -0
  33. data/lib/vips8/error.rb +30 -0
  34. data/lib/vips8/extend.rb +22 -0
  35. data/lib/vips8/foreignflags.rb +20 -0
  36. data/lib/vips8/image.rb +1383 -0
  37. data/lib/vips8/interpolate.rb +37 -0
  38. data/lib/vips8/interpretation.rb +28 -0
  39. data/lib/vips8/methods.rb +1807 -0
  40. data/lib/vips8/operation.rb +19 -0
  41. data/ruby-vips8.gemspec +112 -0
  42. data/spec/image_spec.rb +515 -0
  43. data/spec/samples/balloon.v +0 -0
  44. data/spec/samples/ghost.ppm +405 -0
  45. data/spec/samples/huge.jpg +0 -0
  46. data/spec/samples/icc.jpg +0 -0
  47. data/spec/samples/lcd.icc +0 -0
  48. data/spec/samples/lion.svg +154 -0
  49. data/spec/samples/sample.csv +7 -0
  50. data/spec/samples/sample.exr +0 -0
  51. data/spec/samples/wagon.jpg +0 -0
  52. data/spec/samples/wagon.v +0 -0
  53. data/spec/spec_helper.rb +49 -0
  54. data/spec/vips_spec.rb +74 -0
  55. metadata +198 -0
@@ -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
@@ -0,0 +1,11 @@
1
+ module Vips
2
+
3
+ # Various types of alignment. See {Vips::Image.join}, for example.
4
+ #
5
+ # * `:low` Align on the low coordinate edge
6
+ # * `:centre` Align on the centre
7
+ # * `:high` Align on the high coordinate edge
8
+
9
+ class Align
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ module Vips
2
+
3
+ # Various fixed 90 degree rotation angles. See {Vips::Image.rot}.
4
+ #
5
+ # * `:d0` no rotate
6
+ # * `:d90` 90 degrees clockwise
7
+ # * `:d180` 180 degrees
8
+ # * `:d270` 90 degrees anti-clockwise
9
+
10
+ class Angle
11
+ end
12
+ end
@@ -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
@@ -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