vips 8.7.0.1 → 8.8.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +25 -22
  3. data/CHANGELOG.md +266 -0
  4. data/example/annotate.rb +2 -2
  5. data/example/daltonize8.rb +14 -14
  6. data/example/example2.rb +6 -6
  7. data/example/example3.rb +5 -5
  8. data/example/example4.rb +4 -4
  9. data/example/example5.rb +2 -2
  10. data/example/inheritance_with_refcount.rb +207 -207
  11. data/example/thumb.rb +10 -10
  12. data/example/trim8.rb +2 -2
  13. data/example/watermark.rb +14 -35
  14. data/example/wobble.rb +24 -24
  15. data/lib/vips.rb +335 -306
  16. data/lib/vips/access.rb +9 -9
  17. data/lib/vips/align.rb +7 -7
  18. data/lib/vips/angle.rb +8 -8
  19. data/lib/vips/angle45.rb +12 -12
  20. data/lib/vips/bandformat.rb +16 -16
  21. data/lib/vips/blend_mode.rb +34 -0
  22. data/lib/vips/coding.rb +11 -11
  23. data/lib/vips/compass_direction.rb +13 -13
  24. data/lib/vips/direction.rb +7 -7
  25. data/lib/vips/extend.rb +13 -13
  26. data/lib/vips/gobject.rb +94 -94
  27. data/lib/vips/gvalue.rb +232 -232
  28. data/lib/vips/image.rb +1329 -1327
  29. data/lib/vips/interesting.rb +10 -10
  30. data/lib/vips/interpolate.rb +51 -51
  31. data/lib/vips/interpretation.rb +25 -25
  32. data/lib/vips/kernel.rb +18 -18
  33. data/lib/vips/methods.rb +2226 -2139
  34. data/lib/vips/object.rb +208 -208
  35. data/lib/vips/operation.rb +323 -321
  36. data/lib/vips/operationboolean.rb +10 -10
  37. data/lib/vips/operationcomplex.rb +8 -8
  38. data/lib/vips/operationcomplex2.rb +6 -6
  39. data/lib/vips/operationcomplexget.rb +7 -7
  40. data/lib/vips/operationmath.rb +14 -14
  41. data/lib/vips/operationmath2.rb +6 -6
  42. data/lib/vips/operationrelational.rb +11 -11
  43. data/lib/vips/operationround.rb +7 -7
  44. data/lib/vips/size.rb +9 -9
  45. data/lib/vips/version.rb +1 -1
  46. data/vips.gemspec +1 -1
  47. metadata +9 -8
data/lib/vips/image.rb CHANGED
@@ -7,1494 +7,1496 @@
7
7
  require 'ffi'
8
8
 
9
9
  module Vips
10
- private
11
-
12
- attach_function :vips_image_new_matrix_from_array,
13
- [:int, :int, :pointer, :int], :pointer
14
-
15
- attach_function :vips_image_copy_memory, [:pointer], :pointer
16
-
17
- attach_function :vips_filename_get_filename, [:string], :string
18
- attach_function :vips_filename_get_options, [:string], :string
19
-
20
- attach_function :vips_foreign_find_load, [:string], :string
21
- attach_function :vips_foreign_find_save, [:string], :string
22
- attach_function :vips_foreign_find_load_buffer, [:pointer, :size_t], :string
23
- attach_function :vips_foreign_find_save_buffer, [:string], :string
24
-
25
- attach_function :vips_image_write_to_memory,
26
- [:pointer, SizeStruct.ptr], :pointer
27
-
28
- attach_function :vips_image_get_typeof, [:pointer, :string], :GType
29
- attach_function :vips_image_get,
30
- [:pointer, :string, GObject::GValue.ptr], :int
31
-
32
- # vips_image_get_fields was added in libvips 8.5
33
- begin
34
- attach_function :vips_image_get_fields, [:pointer], :pointer
35
- rescue FFI::NotFoundError
36
- end
37
-
38
- # vips_addalpha was added in libvips 8.6
39
- if Vips::at_least_libvips?(8, 6)
40
- attach_function :vips_addalpha, [:pointer, :pointer, :varargs], :int
41
- end
42
- if Vips::at_least_libvips?(8, 5)
43
- attach_function :vips_image_hasalpha, [:pointer], :int
44
- end
10
+ private
45
11
 
46
- attach_function :vips_image_set,
47
- [:pointer, :string, GObject::GValue.ptr], :void
48
- attach_function :vips_image_remove, [:pointer, :string], :void
12
+ attach_function :vips_image_new_matrix_from_array,
13
+ [:int, :int, :pointer, :int], :pointer
49
14
 
50
- attach_function :vips_band_format_iscomplex, [:int], :int
51
- attach_function :vips_band_format_isfloat, [:int], :int
15
+ attach_function :vips_image_copy_memory, [:pointer], :pointer
52
16
 
53
- attach_function :nickname_find, :vips_nickname_find, [:GType], :string
17
+ attach_function :vips_filename_get_filename, [:string], :pointer
18
+ attach_function :vips_filename_get_options, [:string], :pointer
54
19
 
55
- public
56
-
57
- # This class represents a libvips image. See the {Vips} module documentation
58
- # for an introduction to using this class.
59
-
60
- class Image < Vips::Object
61
- alias_method :parent_get_typeof, :get_typeof
62
-
63
- private
20
+ attach_function :vips_foreign_find_load, [:string], :string
21
+ attach_function :vips_foreign_find_save, [:string], :string
22
+ attach_function :vips_foreign_find_load_buffer, [:pointer, :size_t], :string
23
+ attach_function :vips_foreign_find_save_buffer, [:string], :string
64
24
 
65
- # the layout of the VipsImage struct
66
- module ImageLayout
67
- def self.included base
68
- base.class_eval do
69
- layout :parent, Vips::Object::Struct
70
- # rest opaque
71
- end
72
- end
73
- end
25
+ attach_function :vips_image_write_to_memory,
26
+ [:pointer, SizeStruct.ptr], :pointer
74
27
 
75
- class Struct < Vips::Object::Struct
76
- include ImageLayout
77
-
78
- end
79
-
80
- class ManagedStruct < Vips::Object::ManagedStruct
81
- include ImageLayout
82
-
83
- end
84
-
85
- class GenericPtr < FFI::Struct
86
- layout :value, :pointer
87
- end
88
-
89
- # handy for overloads ... want to be able to apply a function to an
90
- # array or to a scalar
91
- def self.smap x, &block
92
- x.is_a?(Array) ? x.map {|y| smap(y, &block)} : block.(x)
93
- end
28
+ attach_function :vips_image_get_typeof, [:pointer, :string], :GType
29
+ attach_function :vips_image_get,
30
+ [:pointer, :string, GObject::GValue.ptr], :int
94
31
 
95
- def self.complex? format
96
- format_number = GObject::GValue.from_nick BAND_FORMAT_TYPE, format
97
- Vips::vips_band_format_iscomplex(format_number) != 0
98
- end
32
+ # vips_image_get_fields was added in libvips 8.5
33
+ begin
34
+ attach_function :vips_image_get_fields, [:pointer], :pointer
35
+ rescue FFI::NotFoundError
36
+ nil
37
+ end
99
38
 
100
- def self.float? format
101
- format_number = GObject::GValue.from_nick BAND_FORMAT_TYPE, format
102
- Vips::vips_band_format_isfloat(format_number) != 0
103
- end
39
+ # vips_addalpha was added in libvips 8.6
40
+ if Vips::at_least_libvips?(8, 6)
41
+ attach_function :vips_addalpha, [:pointer, :pointer, :varargs], :int
42
+ end
43
+ if Vips::at_least_libvips?(8, 5)
44
+ attach_function :vips_image_hasalpha, [:pointer], :int
45
+ end
104
46
 
105
- # run a complex operation on a complex image, or an image with an even
106
- # number of bands ... handy for things like running .polar on .index
107
- # images
108
- def self.run_cmplx image, &block
109
- original_format = image.format
47
+ attach_function :vips_image_set,
48
+ [:pointer, :string, GObject::GValue.ptr], :void
49
+ attach_function :vips_image_remove, [:pointer, :string], :void
110
50
 
111
- unless Image::complex? image.format
112
- if image.bands % 2 != 0
113
- raise Error, "not an even number of bands"
114
- end
51
+ attach_function :vips_band_format_iscomplex, [:int], :int
52
+ attach_function :vips_band_format_isfloat, [:int], :int
115
53
 
116
- unless Image::float? image.format
117
- image = image.cast :float
118
- end
54
+ attach_function :nickname_find, :vips_nickname_find, [:GType], :string
119
55
 
120
- new_format = image.format == :double ? :dpcomplex : :complex
121
- image = image.copy format: new_format, bands: image.bands / 2
122
- end
56
+ # turn a raw pointer that must be freed into a self-freeing Ruby string
57
+ def self.p2str(pointer)
58
+ pointer = FFI::AutoPointer.new(pointer, GLib::G_FREE)
59
+ pointer.read_string
60
+ end
123
61
 
124
- image = block.(image)
62
+ public
125
63
 
126
- unless Image::complex? original_format
127
- new_format = image.format == :dpcomplex ? :double : :float
128
- image = image.copy format: new_format, bands: image.bands * 2
129
- end
64
+ # This class represents a libvips image. See the {Vips} module documentation
65
+ # for an introduction to using this class.
130
66
 
131
- image
132
- end
67
+ class Image < Vips::Object
68
+ alias_method :parent_get_typeof, :get_typeof
133
69
 
134
- # handy for expanding enum operations
135
- def call_enum(name, other, enum)
136
- if other.is_a?(Vips::Image)
137
- Vips::Operation.call name.to_s, [self, other, enum]
138
- else
139
- Vips::Operation.call name.to_s + "_const", [self, enum, other]
140
- end
141
- end
70
+ private
142
71
 
143
- # Write can fail due to no file descriptors and memory can fill if
144
- # large objects are not collected fairly soon. We can't try a
145
- # write and GC and retry on fail, since the write may take a
146
- # long time and may not be repeatable.
147
- #
148
- # GCing before every write would have a horrible effect on
149
- # performance, so as a compromise we GC every @@gc_interval writes.
150
- #
151
- # ruby2.1 introduced a generational GC which is fast enough to be
152
- # able to GC on every write.
153
-
154
- @@generational_gc = RUBY_ENGINE == "ruby" && RUBY_VERSION.to_f >= 2.1
155
-
156
- @@gc_interval = 100
157
- @@gc_countdown = @@gc_interval
158
-
159
- def write_gc
160
- if @@generational_gc
161
- GC.start full_mark: false
162
- else
163
- @@gc_countdown -= 1
164
- if @@gc_countdown < 0
165
- @@gc_countdown = @@gc_interval
166
- GC.start
167
- end
168
- end
72
+ # the layout of the VipsImage struct
73
+ module ImageLayout
74
+ def self.included base
75
+ base.class_eval do
76
+ layout :parent, Vips::Object::Struct
77
+ # rest opaque
169
78
  end
79
+ end
80
+ end
170
81
 
171
- public
172
-
173
- def inspect
174
- "#<Image #{width}x#{height} #{format}, #{bands} bands, " +
175
- "#{interpretation}>"
176
- end
82
+ class Struct < Vips::Object::Struct
83
+ include ImageLayout
177
84
 
178
- def respond_to? name, include_all = false
179
- # To support keyword args, we need to tell Ruby that final image
180
- # arguments cannot be hashes of keywords.
181
- #
182
- # https://makandracards.com/makandra/36013-heads-up-ruby-implicitly-converts-a-hash-to-keyword-arguments
183
- return false if name == :to_hash
85
+ end
184
86
 
185
- # respond to all vips operations by nickname
186
- return true if Vips::type_find("VipsOperation", name.to_s) != 0
87
+ class ManagedStruct < Vips::Object::ManagedStruct
88
+ include ImageLayout
187
89
 
188
- super
189
- end
90
+ end
190
91
 
191
- def self.respond_to? name, include_all = false
192
- # respond to all vips operations by nickname
193
- return true if Vips::type_find("VipsOperation", name.to_s) != 0
92
+ class GenericPtr < FFI::Struct
93
+ layout :value, :pointer
94
+ end
194
95
 
195
- super
196
- end
96
+ # handy for overloads ... want to be able to apply a function to an
97
+ # array or to a scalar
98
+ def self.smap x, &block
99
+ x.is_a?(Array) ? x.map {|y| smap(y, &block)} : block.(x)
100
+ end
197
101
 
198
- # Invoke a vips operation with {Vips::Operation.call}, using self as
199
- # the first input argument.
200
- #
201
- # @param name [String] vips operation to call
202
- # @return result of vips operation
203
- def method_missing name, *args, **options
204
- Vips::Operation.call name.to_s, [self, *args], options
205
- end
102
+ def self.complex? format
103
+ format_number = GObject::GValue.from_nick BAND_FORMAT_TYPE, format
104
+ Vips::vips_band_format_iscomplex(format_number) != 0
105
+ end
206
106
 
207
- # Invoke a vips operation with {Vips::Operation.call}.
208
- def self.method_missing name, *args, **options
209
- Vips::Operation.call name.to_s, args, options
210
- end
107
+ def self.float? format
108
+ format_number = GObject::GValue.from_nick BAND_FORMAT_TYPE, format
109
+ Vips::vips_band_format_isfloat(format_number) != 0
110
+ end
211
111
 
212
- # Return a new {Image} for a file on disc. This method can load
213
- # images in any format supported by vips. The filename can include
214
- # load options, for example:
215
- #
216
- # ```
217
- # image = Vips::new_from_file "fred.jpg[shrink=2]"
218
- # ```
219
- #
220
- # You can also supply options as a hash, for example:
221
- #
222
- # ```
223
- # image = Vips::new_from_file "fred.jpg", shrink: 2
224
- # ```
225
- #
226
- # The full set of options available depend upon the load operation that
227
- # will be executed. Try something like:
228
- #
229
- # ```
230
- # $ vips jpegload
231
- # ```
232
- #
233
- # at the command-line to see a summary of the available options for the
234
- # JPEG loader.
235
- #
236
- # Loading is fast: only enough of the image is loaded to be able to fill
237
- # out the header. Pixels will only be decompressed when they are needed.
238
- #
239
- # @!macro [new] vips.loadopts
240
- # @param opts [Hash] set of options
241
- # @option opts [Boolean] :disc (true) Open large images via a
242
- # temporary disc file
243
- # @option opts [Vips::Access] :access (:random) Access mode for file
244
- #
245
- # @param name [String] the filename to load from
246
- # @macro vips.loadopts
247
- # @return [Image] the loaded image
248
- def self.new_from_file name, opts = {}
249
- # very common, and Vips::vips_filename_get_filename will segv if we
250
- # pass this
251
- raise Vips::Error, "filename is nil" if name == nil
252
-
253
- filename = Vips::vips_filename_get_filename name
254
- option_string = Vips::vips_filename_get_options name
255
- loader = Vips::vips_foreign_find_load filename
256
- raise Vips::Error if loader == nil
257
-
258
- Operation.call loader, [filename], opts, option_string
259
- end
112
+ # run a complex operation on a complex image, or an image with an even
113
+ # number of bands ... handy for things like running .polar on .index
114
+ # images
115
+ def self.run_cmplx image, &block
116
+ original_format = image.format
260
117
 
261
- # Create a new {Image} for an image encoded, in a format such as
262
- # JPEG, in a memory string. Load options may be passed as
263
- # strings or appended as a hash. For example:
264
- #
265
- # ```
266
- # image = Vips::Image.new_from_buffer memory_buffer, "shrink=2"
267
- # ```
268
- #
269
- # or alternatively:
270
- #
271
- # ```
272
- # image = Vips::Image.new_from_buffer memory_buffer, "", shrink: 2
273
- # ```
274
- #
275
- # The options available depend on the file format. Try something like:
276
- #
277
- # ```
278
- # $ vips jpegload_buffer
279
- # ```
280
- #
281
- # at the command-line to see the available options. Not all loaders
282
- # support load from buffer, but at least JPEG, PNG and
283
- # TIFF images will work.
284
- #
285
- # Loading is fast: only enough of the image is loaded to be able to fill
286
- # out the header. Pixels will only be decompressed when they are needed.
287
- #
288
- # @param data [String] the data to load from
289
- # @param option_string [String] load options as a string
290
- # @macro vips.loadopts
291
- # @return [Image] the loaded image
292
- def self.new_from_buffer data, option_string, opts = {}
293
- loader = Vips::vips_foreign_find_load_buffer data, data.bytesize
294
- raise Vips::Error if loader == nil
295
-
296
- Vips::Operation.call loader, [data], opts, option_string
118
+ unless Image::complex? image.format
119
+ if image.bands % 2 != 0
120
+ raise Error, "not an even number of bands"
297
121
  end
298
122
 
299
- def self.matrix_from_array width, height, array
300
- ptr = FFI::MemoryPointer.new :double, array.length
301
- ptr.write_array_of_double array
302
- image = Vips::vips_image_new_matrix_from_array width, height,
303
- ptr, array.length
304
- Vips::Image.new image
123
+ unless Image::float? image.format
124
+ image = image.cast :float
305
125
  end
306
126
 
307
- # Create a new Image from a 1D or 2D array. A 1D array becomes an
308
- # image with height 1. Use `scale` and `offset` to set the scale and
309
- # offset fields in the header. These are useful for integer
310
- # convolutions.
311
- #
312
- # For example:
313
- #
314
- # ```
315
- # image = Vips::new_from_array [1, 2, 3]
316
- # ```
317
- #
318
- # or
319
- #
320
- # ```
321
- # image = Vips::new_from_array [
322
- # [-1, -1, -1],
323
- # [-1, 16, -1],
324
- # [-1, -1, -1]], 8
325
- # ```
326
- #
327
- # for a simple sharpening mask.
328
- #
329
- # @param array [Array] the pixel data as an array of numbers
330
- # @param scale [Real] the convolution scale
331
- # @param offset [Real] the convolution offset
332
- # @return [Image] the image
333
- def self.new_from_array array, scale = 1, offset = 0
334
- # we accept a 1D array and assume height == 1, or a 2D array
335
- # and check all lines are the same length
336
- unless array.is_a? Array
337
- raise Vips::Error, "Argument is not an array."
338
- end
127
+ new_format = image.format == :double ? :dpcomplex : :complex
128
+ image = image.copy format: new_format, bands: image.bands / 2
129
+ end
339
130
 
340
- if array[0].is_a? Array
341
- height = array.length
342
- width = array[0].length
343
- unless array.all? {|x| x.is_a? Array}
344
- raise Vips::Error, "Not a 2D array."
345
- end
346
- unless array.all? {|x| x.length == width}
347
- raise Vips::Error, "Array not rectangular."
348
- end
349
- array = array.flatten
350
- else
351
- height = 1
352
- width = array.length
353
- end
354
-
355
- unless array.all? {|x| x.is_a? Numeric}
356
- raise Vips::Error, "Not all array elements are Numeric."
357
- end
131
+ image = block.(image)
358
132
 
359
- image = Vips::Image.matrix_from_array width, height, array
360
- raise Vips::Error if image == nil
133
+ unless Image::complex? original_format
134
+ new_format = image.format == :dpcomplex ? :double : :float
135
+ image = image.copy format: new_format, bands: image.bands * 2
136
+ end
361
137
 
362
- # be careful to set them as double
363
- image.set_type GObject::GDOUBLE_TYPE, 'scale', scale.to_f
364
- image.set_type GObject::GDOUBLE_TYPE, 'offset', offset.to_f
365
-
366
- return image
367
- end
368
-
369
- # A new image is created with the same width, height, format,
370
- # interpretation, resolution and offset as self, but with every pixel
371
- # set to the specified value.
372
- #
373
- # You can pass an array to make a many-band image, or a single value to
374
- # make a one-band image.
375
- #
376
- # @param value [Real, Array<Real>] value to put in each pixel
377
- # @return [Image] constant image
378
- def new_from_image value
379
- pixel = (Vips::Image.black(1, 1) + value).cast(format)
380
- image = pixel.embed 0, 0, width, height, extend: :copy
381
- image.copy interpretation: interpretation,
382
- xres: xres, yres: yres, xoffset: xoffset, yoffset: yoffset
383
- end
384
-
385
- # Write this image to a file. Save options may be encoded in the
386
- # filename or given as a hash. For example:
387
- #
388
- # ```
389
- # image.write_to_file "fred.jpg[Q=90]"
390
- # ```
391
- #
392
- # or equivalently:
393
- #
394
- # ```
395
- # image.write_to_file "fred.jpg", Q: 90
396
- # ```
397
- #
398
- # The full set of save options depend on the selected saver. Try
399
- # something like:
400
- #
401
- # ```
402
- # $ vips jpegsave
403
- # ```
404
- #
405
- # to see all the available options for JPEG save.
406
- #
407
- # @!macro [new] vips.saveopts
408
- # @param opts [Hash] set of options
409
- # @option opts [Boolean] :strip (false) Strip all metadata from image
410
- # @option opts [Array<Float>] :background (0) Background colour to
411
- # flatten alpha against, if necessary
412
- #
413
- # @param name [String] filename to write to
414
- def write_to_file name, opts = {}
415
- filename = Vips::vips_filename_get_filename name
416
- option_string = Vips::vips_filename_get_options name
417
- saver = Vips::vips_foreign_find_save filename
418
- if saver == nil
419
- raise Vips::Error, "No known saver for '#{filename}'."
420
- end
421
-
422
- Vips::Operation.call saver, [self, filename], opts, option_string
138
+ image
139
+ end
423
140
 
424
- write_gc
425
- end
141
+ # handy for expanding enum operations
142
+ def call_enum(name, other, enum)
143
+ if other.is_a?(Vips::Image)
144
+ Vips::Operation.call name.to_s, [self, other, enum]
145
+ else
146
+ Vips::Operation.call name.to_s + "_const", [self, enum, other]
147
+ end
148
+ end
426
149
 
427
- # Write this image to a memory buffer. Save options may be encoded in
428
- # the format_string or given as a hash. For example:
429
- #
430
- # ```
431
- # buffer = image.write_to_buffer ".jpg[Q=90]"
432
- # ```
433
- #
434
- # or equivalently:
435
- #
436
- # ```
437
- # image.write_to_buffer ".jpg", Q: 90
438
- # ```
439
- #
440
- # The full set of save options depend on the selected saver. Try
441
- # something like:
442
- #
443
- # ```
444
- # $ vips jpegsave
445
- # ```
446
- #
447
- # to see all the available options for JPEG save.
448
- #
449
- # @param format_string [String] save format plus options
450
- # @macro vips.saveopts
451
- # @return [String] the image saved in the specified format
452
- def write_to_buffer format_string, opts = {}
453
- filename = Vips::vips_filename_get_filename format_string
454
- option_string = Vips::vips_filename_get_options format_string
455
- saver = Vips::vips_foreign_find_save_buffer filename
456
- if saver == nil
457
- raise Vips::Error, "No known saver for '#{filename}'."
458
- end
150
+ # Write can fail due to no file descriptors and memory can fill if
151
+ # large objects are not collected fairly soon. We can't try a
152
+ # write and GC and retry on fail, since the write may take a
153
+ # long time and may not be repeatable.
154
+ #
155
+ # GCing before every write would have a horrible effect on
156
+ # performance, so as a compromise we GC every @@gc_interval writes.
157
+ #
158
+ # ruby2.1 introduced a generational GC which is fast enough to be
159
+ # able to GC on every write.
459
160
 
460
- buffer = Vips::Operation.call saver, [self], opts, option_string
461
- raise Vips::Error if buffer == nil
161
+ @@generational_gc = RUBY_ENGINE == "ruby" && RUBY_VERSION.to_f >= 2.1
462
162
 
463
- write_gc
163
+ @@gc_interval = 100
164
+ @@gc_countdown = @@gc_interval
464
165
 
465
- return buffer
166
+ def write_gc
167
+ if @@generational_gc
168
+ GC.start full_mark: false
169
+ else
170
+ @@gc_countdown -= 1
171
+ if @@gc_countdown < 0
172
+ @@gc_countdown = @@gc_interval
173
+ GC.start
466
174
  end
175
+ end
176
+ end
467
177
 
468
- # Write this image to a large memory buffer.
469
- #
470
- # @return [String] the pixels as a huge binary string
471
- def write_to_memory
472
- len = Vips::SizeStruct.new
473
- ptr = Vips::vips_image_write_to_memory self, len
178
+ public
474
179
 
475
- # wrap up as an autopointer
476
- ptr = FFI::AutoPointer.new(ptr, GLib::G_FREE)
180
+ def inspect
181
+ "#<Image #{width}x#{height} #{format}, #{bands} bands, " +
182
+ "#{interpretation}>"
183
+ end
477
184
 
478
- ptr.get_bytes 0, len[:value]
479
- end
185
+ def respond_to? name, include_all = false
186
+ # To support keyword args, we need to tell Ruby that final image
187
+ # arguments cannot be hashes of keywords.
188
+ #
189
+ # https://makandracards.com/makandra/36013-heads-up-ruby-implicitly-converts-a-hash-to-keyword-arguments
190
+ return false if name == :to_hash
480
191
 
481
- # Fetch a `GType` from an image. `GType` will be 0 for no such field.
482
- #
483
- # @see get
484
- # @param name [String] Metadata field to fetch
485
- # @return [Integer] GType
486
- def get_typeof name
487
- # on libvips before 8.5, property types must be searched first,
488
- # since vips_image_get_typeof returned built-in enums as int
489
- unless Vips::at_least_libvips?(8, 5)
490
- gtype = parent_get_typeof name
491
- return gtype if gtype != 0
492
- end
192
+ # respond to all vips operations by nickname
193
+ return true if Vips::type_find("VipsOperation", name.to_s) != 0
493
194
 
494
- Vips::vips_image_get_typeof self, name
495
- end
195
+ super
196
+ end
496
197
 
497
- # Get a metadata item from an image. Ruby types are constructed
498
- # automatically from the `GValue`, if possible.
499
- #
500
- # For example, you can read the ICC profile from an image like this:
501
- #
502
- # ```
503
- # profile = image.get "icc-profile-data"
504
- # ```
505
- #
506
- # and profile will be an array containing the profile.
507
- #
508
- # @param name [String] Metadata field to get
509
- # @return [Object] Value of field
510
- def get name
511
- # with old libvips, we must fetch properties (as opposed to
512
- # metadata) via VipsObject
513
- unless Vips::at_least_libvips?(8, 5)
514
- return super if parent_get_typeof(name) != 0
515
- end
198
+ def self.respond_to? name, include_all = false
199
+ # respond to all vips operations by nickname
200
+ return true if Vips::type_find("VipsOperation", name.to_s) != 0
516
201
 
517
- gvalue = GObject::GValue.alloc
518
- result = Vips::vips_image_get self, name, gvalue
519
- raise Vips::Error if result != 0
202
+ super
203
+ end
520
204
 
521
- return gvalue.get
522
- end
205
+ # Invoke a vips operation with {Vips::Operation.call}, using self as
206
+ # the first input argument.
207
+ #
208
+ # @param name [String] vips operation to call
209
+ # @return result of vips operation
210
+ def method_missing name, *args, **options
211
+ Vips::Operation.call name.to_s, [self, *args], options
212
+ end
523
213
 
524
- # Get the names of all fields on an image. Use this to loop over all
525
- # image metadata.
526
- #
527
- # @return [[String]] array of field names
528
- def get_fields
529
- # vips_image_get_fields() was added in libvips 8.5
530
- return [] unless Vips.respond_to? :vips_image_get_fields
531
-
532
- array = Vips::vips_image_get_fields self
533
-
534
- names = []
535
- p = array
536
- until ((q = p.read_pointer).null?)
537
- names << q.read_string
538
- GLib::g_free q
539
- p += FFI::Type::POINTER.size
540
- end
541
- GLib::g_free array
214
+ # Invoke a vips operation with {Vips::Operation.call}.
215
+ def self.method_missing name, *args, **options
216
+ Vips::Operation.call name.to_s, args, options
217
+ end
542
218
 
543
- return names
544
- end
219
+ # Return a new {Image} for a file on disc. This method can load
220
+ # images in any format supported by vips. The filename can include
221
+ # load options, for example:
222
+ #
223
+ # ```
224
+ # image = Vips::new_from_file "fred.jpg[shrink=2]"
225
+ # ```
226
+ #
227
+ # You can also supply options as a hash, for example:
228
+ #
229
+ # ```
230
+ # image = Vips::new_from_file "fred.jpg", shrink: 2
231
+ # ```
232
+ #
233
+ # The full set of options available depend upon the load operation that
234
+ # will be executed. Try something like:
235
+ #
236
+ # ```
237
+ # $ vips jpegload
238
+ # ```
239
+ #
240
+ # at the command-line to see a summary of the available options for the
241
+ # JPEG loader.
242
+ #
243
+ # Loading is fast: only enough of the image is loaded to be able to fill
244
+ # out the header. Pixels will only be decompressed when they are needed.
245
+ #
246
+ # @!macro [new] vips.loadopts
247
+ # @param opts [Hash] set of options
248
+ # @option opts [Boolean] :disc (true) Open large images via a
249
+ # temporary disc file
250
+ # @option opts [Vips::Access] :access (:random) Access mode for file
251
+ #
252
+ # @param name [String] the filename to load from
253
+ # @macro vips.loadopts
254
+ # @return [Image] the loaded image
255
+ def self.new_from_file name, opts = {}
256
+ # very common, and Vips::vips_filename_get_filename will segv if we
257
+ # pass this
258
+ raise Vips::Error, "filename is nil" if name == nil
259
+
260
+ filename = Vips::p2str(Vips::vips_filename_get_filename name)
261
+ option_string = Vips::p2str(Vips::vips_filename_get_options name)
262
+ loader = Vips::vips_foreign_find_load filename
263
+ raise Vips::Error if loader == nil
264
+
265
+ Operation.call loader, [filename], opts, option_string
266
+ end
545
267
 
546
- # Create a metadata item on an image, of the specifed type. Ruby types
547
- # are automatically
548
- # transformed into the matching `GType`, if possible.
549
- #
550
- # For example, you can use this to set an image's ICC profile:
551
- #
552
- # ```
553
- # x = y.set_type Vips::BLOB_TYPE, "icc-profile-data", profile
554
- # ```
555
- #
556
- # where `profile` is an ICC profile held as a binary string object.
557
- #
558
- # @see set
559
- # @param gtype [Integer] GType of item
560
- # @param name [String] Metadata field to set
561
- # @param value [Object] Value to set
562
- def set_type gtype, name, value
563
- gvalue = GObject::GValue.alloc
564
- gvalue.init gtype
565
- gvalue.set value
566
- Vips::vips_image_set self, name, gvalue
567
- end
268
+ # Create a new {Image} for an image encoded, in a format such as
269
+ # JPEG, in a binary string. Load options may be passed as
270
+ # strings or appended as a hash. For example:
271
+ #
272
+ # ```
273
+ # image = Vips::Image.new_from_buffer memory_buffer, "shrink=2"
274
+ # ```
275
+ #
276
+ # or alternatively:
277
+ #
278
+ # ```
279
+ # image = Vips::Image.new_from_buffer memory_buffer, "", shrink: 2
280
+ # ```
281
+ #
282
+ # The options available depend on the file format. Try something like:
283
+ #
284
+ # ```
285
+ # $ vips jpegload_buffer
286
+ # ```
287
+ #
288
+ # at the command-line to see the available options. Not all loaders
289
+ # support load from buffer, but at least JPEG, PNG and
290
+ # TIFF images will work.
291
+ #
292
+ # Loading is fast: only enough of the image is loaded to be able to fill
293
+ # out the header. Pixels will only be decompressed when they are needed.
294
+ #
295
+ # @param data [String] the data to load from
296
+ # @param option_string [String] load options as a string
297
+ # @macro vips.loadopts
298
+ # @return [Image] the loaded image
299
+ def self.new_from_buffer data, option_string, opts = {}
300
+ loader = Vips::vips_foreign_find_load_buffer data, data.bytesize
301
+ raise Vips::Error if loader == nil
302
+
303
+ Vips::Operation.call loader, [data], opts, option_string
304
+ end
568
305
 
569
- # Set the value of a metadata item on an image. The metadata item must
570
- # already exist. Ruby types are automatically
571
- # transformed into the matching `GValue`, if possible.
572
- #
573
- # For example, you can use this to set an image's ICC profile:
574
- #
575
- # ```
576
- # x = y.set "icc-profile-data", profile
577
- # ```
578
- #
579
- # where `profile` is an ICC profile held as a binary string object.
580
- #
581
- # @see set_type
582
- # @param name [String] Metadata field to set
583
- # @param value [Object] Value to set
584
- def set name, value
585
- set_type get_typeof(name), name, value
586
- end
306
+ def self.matrix_from_array width, height, array
307
+ ptr = FFI::MemoryPointer.new :double, array.length
308
+ ptr.write_array_of_double array
309
+ image = Vips::vips_image_new_matrix_from_array width, height,
310
+ ptr, array.length
311
+ Vips::Image.new image
312
+ end
587
313
 
588
- # Remove a metadata item from an image.
589
- #
590
- # @param name [String] Metadata field to remove
591
- def remove name
592
- Vips::vips_image_remove self, name
593
- end
314
+ # Create a new Image from a 1D or 2D array. A 1D array becomes an
315
+ # image with height 1. Use `scale` and `offset` to set the scale and
316
+ # offset fields in the header. These are useful for integer
317
+ # convolutions.
318
+ #
319
+ # For example:
320
+ #
321
+ # ```
322
+ # image = Vips::new_from_array [1, 2, 3]
323
+ # ```
324
+ #
325
+ # or
326
+ #
327
+ # ```
328
+ # image = Vips::new_from_array [
329
+ # [-1, -1, -1],
330
+ # [-1, 16, -1],
331
+ # [-1, -1, -1]], 8
332
+ # ```
333
+ #
334
+ # for a simple sharpening mask.
335
+ #
336
+ # @param array [Array] the pixel data as an array of numbers
337
+ # @param scale [Real] the convolution scale
338
+ # @param offset [Real] the convolution offset
339
+ # @return [Image] the image
340
+ def self.new_from_array array, scale = 1, offset = 0
341
+ # we accept a 1D array and assume height == 1, or a 2D array
342
+ # and check all lines are the same length
343
+ unless array.is_a? Array
344
+ raise Vips::Error, "Argument is not an array."
345
+ end
346
+
347
+ if array[0].is_a? Array
348
+ height = array.length
349
+ width = array[0].length
350
+ unless array.all? {|x| x.is_a? Array}
351
+ raise Vips::Error, "Not a 2D array."
352
+ end
353
+ unless array.all? {|x| x.length == width}
354
+ raise Vips::Error, "Array not rectangular."
355
+ end
356
+ array = array.flatten
357
+ else
358
+ height = 1
359
+ width = array.length
360
+ end
361
+
362
+ unless array.all? {|x| x.is_a? Numeric}
363
+ raise Vips::Error, "Not all array elements are Numeric."
364
+ end
365
+
366
+ image = Vips::Image.matrix_from_array width, height, array
367
+ raise Vips::Error if image == nil
368
+
369
+ # be careful to set them as double
370
+ image.set_type GObject::GDOUBLE_TYPE, 'scale', scale.to_f
371
+ image.set_type GObject::GDOUBLE_TYPE, 'offset', offset.to_f
372
+
373
+ return image
374
+ end
594
375
 
595
- # compatibility: old name for get
596
- def get_value name
597
- get name
598
- end
376
+ # A new image is created with the same width, height, format,
377
+ # interpretation, resolution and offset as self, but with every pixel
378
+ # set to the specified value.
379
+ #
380
+ # You can pass an array to make a many-band image, or a single value to
381
+ # make a one-band image.
382
+ #
383
+ # @param value [Real, Array<Real>] value to put in each pixel
384
+ # @return [Image] constant image
385
+ def new_from_image value
386
+ pixel = (Vips::Image.black(1, 1) + value).cast(format)
387
+ image = pixel.embed 0, 0, width, height, extend: :copy
388
+ image.copy interpretation: interpretation,
389
+ xres: xres, yres: yres, xoffset: xoffset, yoffset: yoffset
390
+ end
599
391
 
600
- # compatibility: old name for set
601
- def set_value name, value
602
- set name, value
603
- end
392
+ # Write this image to a file. Save options may be encoded in the
393
+ # filename or given as a hash. For example:
394
+ #
395
+ # ```
396
+ # image.write_to_file "fred.jpg[Q=90]"
397
+ # ```
398
+ #
399
+ # or equivalently:
400
+ #
401
+ # ```
402
+ # image.write_to_file "fred.jpg", Q: 90
403
+ # ```
404
+ #
405
+ # The full set of save options depend on the selected saver. Try
406
+ # something like:
407
+ #
408
+ # ```
409
+ # $ vips jpegsave
410
+ # ```
411
+ #
412
+ # to see all the available options for JPEG save.
413
+ #
414
+ # @!macro [new] vips.saveopts
415
+ # @param opts [Hash] set of options
416
+ # @option opts [Boolean] :strip (false) Strip all metadata from image
417
+ # @option opts [Array<Float>] :background (0) Background colour to
418
+ # flatten alpha against, if necessary
419
+ #
420
+ # @param name [String] filename to write to
421
+ def write_to_file name, opts = {}
422
+ filename = Vips::p2str(Vips::vips_filename_get_filename name)
423
+ option_string = Vips::p2str(Vips::vips_filename_get_options name)
424
+ saver = Vips::vips_foreign_find_save filename
425
+ if saver == nil
426
+ raise Vips::Error, "No known saver for '#{filename}'."
427
+ end
428
+
429
+ Vips::Operation.call saver, [self, filename], opts, option_string
430
+
431
+ write_gc
432
+ end
604
433
 
605
- # Get image width, in pixels.
606
- #
607
- # @return [Integer] image width, in pixels
608
- def width
609
- get "width"
610
- end
434
+ # Write this image to a memory buffer. Save options may be encoded in
435
+ # the format_string or given as a hash. For example:
436
+ #
437
+ # ```
438
+ # buffer = image.write_to_buffer ".jpg[Q=90]"
439
+ # ```
440
+ #
441
+ # or equivalently:
442
+ #
443
+ # ```
444
+ # image.write_to_buffer ".jpg", Q: 90
445
+ # ```
446
+ #
447
+ # The full set of save options depend on the selected saver. Try
448
+ # something like:
449
+ #
450
+ # ```
451
+ # $ vips jpegsave
452
+ # ```
453
+ #
454
+ # to see all the available options for JPEG save.
455
+ #
456
+ # @param format_string [String] save format plus options
457
+ # @macro vips.saveopts
458
+ # @return [String] the image saved in the specified format
459
+ def write_to_buffer format_string, opts = {}
460
+ filename =
461
+ Vips::p2str(Vips::vips_filename_get_filename format_string)
462
+ option_string =
463
+ Vips::p2str(Vips::vips_filename_get_options format_string)
464
+ saver = Vips::vips_foreign_find_save_buffer filename
465
+ if saver == nil
466
+ raise Vips::Error, "No known saver for '#{filename}'."
467
+ end
468
+
469
+ buffer = Vips::Operation.call saver, [self], opts, option_string
470
+ raise Vips::Error if buffer == nil
471
+
472
+ write_gc
473
+
474
+ return buffer
475
+ end
611
476
 
612
- # Get image height, in pixels.
613
- #
614
- # @return [Integer] image height, in pixels
615
- def height
616
- get "height"
617
- end
477
+ # Write this image to a large memory buffer.
478
+ #
479
+ # @return [String] the pixels as a huge binary string
480
+ def write_to_memory
481
+ len = Vips::SizeStruct.new
482
+ ptr = Vips::vips_image_write_to_memory self, len
618
483
 
619
- # Get number of image bands.
620
- #
621
- # @return [Integer] number of image bands
622
- def bands
623
- get "bands"
624
- end
484
+ # wrap up as an autopointer
485
+ ptr = FFI::AutoPointer.new(ptr, GLib::G_FREE)
625
486
 
626
- # Get image format.
627
- #
628
- # @return [Symbol] image format
629
- def format
630
- get "format"
631
- end
487
+ ptr.get_bytes 0, len[:value]
488
+ end
632
489
 
633
- # Get image interpretation.
634
- #
635
- # @return [Symbol] image interpretation
636
- def interpretation
637
- get "interpretation"
638
- end
490
+ # Get the `GType` of a metadata field. The result is 0 if no such field
491
+ # exists.
492
+ #
493
+ # @see get
494
+ # @param name [String] Metadata field to fetch
495
+ # @return [Integer] GType
496
+ def get_typeof name
497
+ # on libvips before 8.5, property types must be searched first,
498
+ # since vips_image_get_typeof returned built-in enums as int
499
+ unless Vips::at_least_libvips?(8, 5)
500
+ gtype = parent_get_typeof name
501
+ return gtype if gtype != 0
502
+ end
503
+
504
+ Vips::vips_image_get_typeof self, name
505
+ end
639
506
 
640
- # Get image coding.
641
- #
642
- # @return [Symbol] image coding
643
- def coding
644
- get "coding"
645
- end
507
+ # Get a metadata item from an image. Ruby types are constructed
508
+ # automatically from the `GValue`, if possible.
509
+ #
510
+ # For example, you can read the ICC profile from an image like this:
511
+ #
512
+ # ```
513
+ # profile = image.get "icc-profile-data"
514
+ # ```
515
+ #
516
+ # and profile will be an array containing the profile.
517
+ #
518
+ # @param name [String] Metadata field to get
519
+ # @return [Object] Value of field
520
+ def get name
521
+ # with old libvips, we must fetch properties (as opposed to
522
+ # metadata) via VipsObject
523
+ unless Vips::at_least_libvips?(8, 5)
524
+ return super if parent_get_typeof(name) != 0
525
+ end
526
+
527
+ gvalue = GObject::GValue.alloc
528
+ result = Vips::vips_image_get self, name, gvalue
529
+ raise Vips::Error if result != 0
530
+
531
+ gvalue.get
532
+ end
646
533
 
647
- # Get image filename, if any.
648
- #
649
- # @return [String] image filename
650
- def filename
651
- get "filename"
652
- end
534
+ # Get the names of all fields on an image. Use this to loop over all
535
+ # image metadata.
536
+ #
537
+ # @return [[String]] array of field names
538
+ def get_fields
539
+ # vips_image_get_fields() was added in libvips 8.5
540
+ return [] unless Vips.respond_to? :vips_image_get_fields
541
+
542
+ array = Vips::vips_image_get_fields self
543
+
544
+ names = []
545
+ p = array
546
+ until (q = p.read_pointer).null?
547
+ names << q.read_string
548
+ GLib::g_free q
549
+ p += FFI::Type::POINTER.size
550
+ end
551
+ GLib::g_free array
552
+
553
+ names
554
+ end
653
555
 
654
- # Get image xoffset.
655
- #
656
- # @return [Integer] image xoffset
657
- def xoffset
658
- get "xoffset"
659
- end
556
+ # Create a metadata item on an image of the specifed type. Ruby types
557
+ # are automatically transformed into the matching `GType`, if possible.
558
+ #
559
+ # For example, you can use this to set an image's ICC profile:
560
+ #
561
+ # ```
562
+ # x = y.set_type Vips::BLOB_TYPE, "icc-profile-data", profile
563
+ # ```
564
+ #
565
+ # where `profile` is an ICC profile held as a binary string object.
566
+ #
567
+ # @see set
568
+ # @param gtype [Integer] GType of item
569
+ # @param name [String] Metadata field to set
570
+ # @param value [Object] Value to set
571
+ def set_type gtype, name, value
572
+ gvalue = GObject::GValue.alloc
573
+ gvalue.init gtype
574
+ gvalue.set value
575
+ Vips::vips_image_set self, name, gvalue
576
+ end
660
577
 
661
- # Get image yoffset.
662
- #
663
- # @return [Integer] image yoffset
664
- def yoffset
665
- get "yoffset"
666
- end
578
+ # Set the value of a metadata item on an image. The metadata item must
579
+ # already exist. Ruby types are automatically transformed into the
580
+ # matching `GValue`, if possible.
581
+ #
582
+ # For example, you can use this to set an image's ICC profile:
583
+ #
584
+ # ```
585
+ # x = y.set "icc-profile-data", profile
586
+ # ```
587
+ #
588
+ # where `profile` is an ICC profile held as a binary string object.
589
+ #
590
+ # @see set_type
591
+ # @param name [String] Metadata field to set
592
+ # @param value [Object] Value to set
593
+ def set name, value
594
+ set_type get_typeof(name), name, value
595
+ end
667
596
 
668
- # Get image x resolution.
669
- #
670
- # @return [Float] image x resolution
671
- def xres
672
- get "xres"
673
- end
597
+ # Remove a metadata item from an image.
598
+ #
599
+ # @param name [String] Metadata field to remove
600
+ def remove name
601
+ Vips::vips_image_remove self, name
602
+ end
674
603
 
675
- # Get image y resolution.
676
- #
677
- # @return [Float] image y resolution
678
- def yres
679
- get "yres"
680
- end
604
+ # compatibility: old name for get
605
+ def get_value name
606
+ get name
607
+ end
681
608
 
682
- # Get scale metadata.
683
- #
684
- # @return [Float] image scale
685
- def scale
686
- return 1 if get_typeof("scale") == 0
609
+ # compatibility: old name for set
610
+ def set_value name, value
611
+ set name, value
612
+ end
687
613
 
688
- get "scale"
689
- end
614
+ # Get image width, in pixels.
615
+ #
616
+ # @return [Integer] image width, in pixels
617
+ def width
618
+ get "width"
619
+ end
690
620
 
691
- # Get offset metadata.
692
- #
693
- # @return [Float] image offset
694
- def offset
695
- return 0 if get_typeof("offset") == 0
621
+ # Get image height, in pixels.
622
+ #
623
+ # @return [Integer] image height, in pixels
624
+ def height
625
+ get "height"
626
+ end
696
627
 
697
- get "offset"
698
- end
628
+ # Get number of image bands.
629
+ #
630
+ # @return [Integer] number of image bands
631
+ def bands
632
+ get "bands"
633
+ end
699
634
 
700
- # Get the image size.
701
- #
702
- # @return [Integer, Integer] image width and height
703
- def size
704
- [width, height]
705
- end
635
+ # Get image format.
636
+ #
637
+ # @return [Symbol] image format
638
+ def format
639
+ get "format"
640
+ end
706
641
 
707
- if Vips::at_least_libvips?(8, 5)
708
- # Detect if image has an alpha channel
709
- #
710
- # @return [Boolean] true if image has an alpha channel.
711
- def has_alpha?
712
- return Vips::vips_image_hasalpha(self) != 0
713
- end
714
- end
642
+ # Get image interpretation.
643
+ #
644
+ # @return [Symbol] image interpretation
645
+ def interpretation
646
+ get "interpretation"
647
+ end
715
648
 
716
- # vips_addalpha was added in libvips 8.6
717
- if Vips::at_least_libvips?(8, 6)
718
- # Append an alpha channel to an image.
719
- #
720
- # @return [Image] new image
721
- def add_alpha
722
- ptr = GenericPtr.new
723
- result = Vips::vips_addalpha self, ptr
724
- raise Vips::Error if result != 0
725
-
726
- Vips::Image.new ptr[:value]
727
- end
728
- end
649
+ # Get image coding.
650
+ #
651
+ # @return [Symbol] image coding
652
+ def coding
653
+ get "coding"
654
+ end
729
655
 
730
- # Copy an image to a memory area.
731
- #
732
- # This can be useful for reusing results, but can obviously use a lot of
733
- # memory for large images. See {Image#tilecache} for a way of caching
734
- # parts of an image.
735
- #
736
- # @return [Image] new memory image
737
- def copy_memory
738
- new_image = Vips::vips_image_copy_memory self
739
- Vips::Image.new new_image
740
- end
656
+ # Get image filename, if any.
657
+ #
658
+ # @return [String] image filename
659
+ def filename
660
+ get "filename"
661
+ end
741
662
 
742
- # Draw a point on an image.
743
- #
744
- # See {Image#draw_rect}.
745
- #
746
- # @return [Image] modified image
747
- def draw_point ink, left, top, opts = {}
748
- draw_rect ink, left, top, 1, 1, opts
749
- end
663
+ # Get image xoffset.
664
+ #
665
+ # @return [Integer] image xoffset
666
+ def xoffset
667
+ get "xoffset"
668
+ end
750
669
 
751
- # Add an image, constant or array.
752
- #
753
- # @param other [Image, Real, Array<Real>] Thing to add to self
754
- # @return [Image] result of addition
755
- def + other
756
- other.is_a?(Vips::Image) ?
757
- add(other) : linear(1, other)
758
- end
670
+ # Get image yoffset.
671
+ #
672
+ # @return [Integer] image yoffset
673
+ def yoffset
674
+ get "yoffset"
675
+ end
759
676
 
760
- # Subtract an image, constant or array.
761
- #
762
- # @param other [Image, Real, Array<Real>] Thing to subtract from self
763
- # @return [Image] result of subtraction
764
- def - other
765
- other.is_a?(Vips::Image) ?
766
- subtract(other) : linear(1, Image::smap(other) {|x| x * -1})
767
- end
677
+ # Get image x resolution.
678
+ #
679
+ # @return [Float] image x resolution
680
+ def xres
681
+ get "xres"
682
+ end
768
683
 
769
- # Multiply an image, constant or array.
770
- #
771
- # @param other [Image, Real, Array<Real>] Thing to multiply by self
772
- # @return [Image] result of multiplication
773
- def * other
774
- other.is_a?(Vips::Image) ?
775
- multiply(other) : linear(other, 0)
776
- end
684
+ # Get image y resolution.
685
+ #
686
+ # @return [Float] image y resolution
687
+ def yres
688
+ get "yres"
689
+ end
777
690
 
778
- # Divide an image, constant or array.
779
- #
780
- # @param other [Image, Real, Array<Real>] Thing to divide self by
781
- # @return [Image] result of division
782
- def / other
783
- other.is_a?(Vips::Image) ?
784
- divide(other) : linear(Image::smap(other) {|x| 1.0 / x}, 0)
785
- end
691
+ # Get scale metadata.
692
+ #
693
+ # @return [Float] image scale
694
+ def scale
695
+ return 1 if get_typeof("scale") == 0
786
696
 
787
- # Remainder after integer division with an image, constant or array.
788
- #
789
- # @param other [Image, Real, Array<Real>] self modulo this
790
- # @return [Image] result of modulo
791
- def % other
792
- other.is_a?(Vips::Image) ?
793
- remainder(other) : remainder_const(other)
794
- end
697
+ get "scale"
698
+ end
795
699
 
796
- # Raise to power of an image, constant or array.
797
- #
798
- # @param other [Image, Real, Array<Real>] self to the power of this
799
- # @return [Image] result of power
800
- def ** other
801
- call_enum "math2", other, :pow
802
- end
700
+ # Get offset metadata.
701
+ #
702
+ # @return [Float] image offset
703
+ def offset
704
+ return 0 if get_typeof("offset") == 0
803
705
 
804
- # Integer left shift with an image, constant or array.
805
- #
806
- # @param other [Image, Real, Array<Real>] shift left by this much
807
- # @return [Image] result of left shift
808
- def << other
809
- call_enum "boolean", other, :lshift
810
- end
706
+ get "offset"
707
+ end
811
708
 
812
- # Integer right shift with an image, constant or array.
813
- #
814
- # @param other [Image, Real, Array<Real>] shift right by this much
815
- # @return [Image] result of right shift
816
- def >> other
817
- call_enum "boolean", other, :rshift
818
- end
709
+ # Get the image size.
710
+ #
711
+ # @return [Integer, Integer] image width and height
712
+ def size
713
+ [width, height]
714
+ end
819
715
 
820
- # Integer bitwise OR with an image, constant or array.
821
- #
822
- # @param other [Image, Real, Array<Real>] bitwise OR with this
823
- # @return [Image] result of bitwise OR
824
- def | other
825
- call_enum "boolean", other, :or
826
- end
716
+ if Vips::at_least_libvips?(8, 5)
717
+ # Detect if image has an alpha channel
718
+ #
719
+ # @return [Boolean] true if image has an alpha channel.
720
+ def has_alpha?
721
+ return Vips::vips_image_hasalpha(self) != 0
722
+ end
723
+ end
827
724
 
828
- # Integer bitwise AND with an image, constant or array.
829
- #
830
- # @param other [Image, Real, Array<Real>] bitwise AND with this
831
- # @return [Image] result of bitwise AND
832
- def & other
833
- call_enum "boolean", other, :and
834
- end
725
+ # vips_addalpha was added in libvips 8.6
726
+ if Vips::at_least_libvips?(8, 6)
727
+ # Append an alpha channel to an image.
728
+ #
729
+ # @return [Image] new image
730
+ def add_alpha
731
+ ptr = GenericPtr.new
732
+ result = Vips::vips_addalpha self, ptr
733
+ raise Vips::Error if result != 0
734
+
735
+ Vips::Image.new ptr[:value]
736
+ end
737
+ end
835
738
 
836
- # Integer bitwise EOR with an image, constant or array.
837
- #
838
- # @param other [Image, Real, Array<Real>] bitwise EOR with this
839
- # @return [Image] result of bitwise EOR
840
- def ^ other
841
- call_enum "boolean", other, :eor
842
- end
739
+ # Copy an image to a memory area.
740
+ #
741
+ # This can be useful for reusing results, but can obviously use a lot of
742
+ # memory for large images. See {Image#tilecache} for a way of caching
743
+ # parts of an image.
744
+ #
745
+ # @return [Image] new memory image
746
+ def copy_memory
747
+ new_image = Vips::vips_image_copy_memory self
748
+ Vips::Image.new new_image
749
+ end
843
750
 
844
- # Equivalent to image ^ -1
845
- #
846
- # @return [Image] image with bits flipped
847
- def !
848
- self ^ -1
849
- end
751
+ # Draw a point on an image.
752
+ #
753
+ # See {Image#draw_rect}.
754
+ #
755
+ # @return [Image] modified image
756
+ def draw_point ink, left, top, opts = {}
757
+ draw_rect ink, left, top, 1, 1, opts
758
+ end
850
759
 
851
- # Equivalent to image ^ -1
852
- #
853
- # @return [Image] image with bits flipped
854
- def ~
855
- self ^ -1
856
- end
760
+ # Add an image, constant or array.
761
+ #
762
+ # @param other [Image, Real, Array<Real>] Thing to add to self
763
+ # @return [Image] result of addition
764
+ def + other
765
+ other.is_a?(Vips::Image) ?
766
+ add(other) : linear(1, other)
767
+ end
857
768
 
858
- # @return [Image] image
859
- def +@
860
- self
861
- end
769
+ # Subtract an image, constant or array.
770
+ #
771
+ # @param other [Image, Real, Array<Real>] Thing to subtract from self
772
+ # @return [Image] result of subtraction
773
+ def - other
774
+ other.is_a?(Vips::Image) ?
775
+ subtract(other) : linear(1, Image::smap(other) {|x| x * -1})
776
+ end
862
777
 
863
- # Equivalent to image * -1
864
- #
865
- # @return [Image] negative of image
866
- def -@
867
- self * -1
868
- end
778
+ # Multiply an image, constant or array.
779
+ #
780
+ # @param other [Image, Real, Array<Real>] Thing to multiply by self
781
+ # @return [Image] result of multiplication
782
+ def * other
783
+ other.is_a?(Vips::Image) ?
784
+ multiply(other) : linear(other, 0)
785
+ end
869
786
 
870
- # Relational less than with an image, constant or array.
871
- #
872
- # @param other [Image, Real, Array<Real>] relational less than with this
873
- # @return [Image] result of less than
874
- def < other
875
- call_enum "relational", other, :less
876
- end
787
+ # Divide an image, constant or array.
788
+ #
789
+ # @param other [Image, Real, Array<Real>] Thing to divide self by
790
+ # @return [Image] result of division
791
+ def / other
792
+ other.is_a?(Vips::Image) ?
793
+ divide(other) : linear(Image::smap(other) {|x| 1.0 / x}, 0)
794
+ end
877
795
 
878
- # Relational less than or equal to with an image, constant or array.
879
- #
880
- # @param other [Image, Real, Array<Real>] relational less than or
881
- # equal to with this
882
- # @return [Image] result of less than or equal to
883
- def <= other
884
- call_enum "relational", other, :lesseq
885
- end
796
+ # Remainder after integer division with an image, constant or array.
797
+ #
798
+ # @param other [Image, Real, Array<Real>] self modulo this
799
+ # @return [Image] result of modulo
800
+ def % other
801
+ other.is_a?(Vips::Image) ?
802
+ remainder(other) : remainder_const(other)
803
+ end
886
804
 
887
- # Relational more than with an image, constant or array.
888
- #
889
- # @param other [Image, Real, Array<Real>] relational more than with this
890
- # @return [Image] result of more than
891
- def > other
892
- call_enum "relational", other, :more
893
- end
805
+ # Raise to power of an image, constant or array.
806
+ #
807
+ # @param other [Image, Real, Array<Real>] self to the power of this
808
+ # @return [Image] result of power
809
+ def ** other
810
+ call_enum "math2", other, :pow
811
+ end
894
812
 
895
- # Relational more than or equal to with an image, constant or array.
896
- #
897
- # @param other [Image, Real, Array<Real>] relational more than or
898
- # equal to with this
899
- # @return [Image] result of more than or equal to
900
- def >= other
901
- call_enum "relational", other, :moreeq
902
- end
813
+ # Integer left shift with an image, constant or array.
814
+ #
815
+ # @param other [Image, Real, Array<Real>] shift left by this much
816
+ # @return [Image] result of left shift
817
+ def << other
818
+ call_enum "boolean", other, :lshift
819
+ end
903
820
 
904
- # Compare equality to nil, an image, constant or array.
905
- #
906
- # @param other [nil, Image, Real, Array<Real>] test equality to this
907
- # @return [Image] result of equality
908
- def == other
909
- # for equality, we must allow tests against nil
910
- if other == nil
911
- false
912
- else
913
- call_enum "relational", other, :equal
914
- end
915
- end
821
+ # Integer right shift with an image, constant or array.
822
+ #
823
+ # @param other [Image, Real, Array<Real>] shift right by this much
824
+ # @return [Image] result of right shift
825
+ def >> other
826
+ call_enum "boolean", other, :rshift
827
+ end
916
828
 
917
- # Compare inequality to nil, an image, constant or array.
918
- #
919
- # @param other [nil, Image, Real, Array<Real>] test inequality to this
920
- # @return [Image] result of inequality
921
- def != other
922
- # for equality, we must allow tests against nil
923
- if other == nil
924
- true
925
- else
926
- call_enum "relational", other, :noteq
927
- end
928
- end
829
+ # Integer bitwise OR with an image, constant or array.
830
+ #
831
+ # @param other [Image, Real, Array<Real>] bitwise OR with this
832
+ # @return [Image] result of bitwise OR
833
+ def | other
834
+ call_enum "boolean", other, :or
835
+ end
929
836
 
930
- # Fetch bands using a number or a range
931
- #
932
- # @param index [Numeric, Range] extract these band(s)
933
- # @return [Image] extracted band(s)
934
- def [] index
935
- if index.is_a? Range
936
- n = index.size
937
- extract_band index.begin, n: n
938
- elsif index.is_a? Numeric
939
- extract_band index
940
- else
941
- raise Vips::Error, "[] index is not range or numeric."
942
- end
943
- end
837
+ # Integer bitwise AND with an image, constant or array.
838
+ #
839
+ # @param other [Image, Real, Array<Real>] bitwise AND with this
840
+ # @return [Image] result of bitwise AND
841
+ def & other
842
+ call_enum "boolean", other, :and
843
+ end
944
844
 
945
- # Convert to an Array. This will be slow for large images.
946
- #
947
- # @return [Array] array of Fixnum
948
- def to_a
949
- # we render the image to a big string, then unpack
950
- # as a Ruby array of the correct type
951
- memory = write_to_memory
952
-
953
- # make the template for unpack
954
- template = {
955
- :char => 'c',
956
- :uchar => 'C',
957
- :short => 's_',
958
- :ushort => 'S_',
959
- :int => 'i_',
960
- :uint => 'I_',
961
- :float => 'f',
962
- :double => 'd',
963
- :complex => 'f',
964
- :dpcomplex => 'd'
965
- }[format] + '*'
966
-
967
- # and unpack into something like [1, 2, 3, 4 ..]
968
- array = memory.unpack(template)
969
-
970
- # gather band elements together
971
- pixel_array = array.each_slice(bands).to_a
972
-
973
- # build rows
974
- row_array = pixel_array.each_slice(width).to_a
975
-
976
- return row_array
977
- end
845
+ # Integer bitwise EOR with an image, constant or array.
846
+ #
847
+ # @param other [Image, Real, Array<Real>] bitwise EOR with this
848
+ # @return [Image] result of bitwise EOR
849
+ def ^ other
850
+ call_enum "boolean", other, :eor
851
+ end
978
852
 
979
- # Return the largest integral value not greater than the argument.
980
- #
981
- # @return [Image] floor of image
982
- def floor
983
- round :floor
984
- end
853
+ # Equivalent to image ^ -1
854
+ #
855
+ # @return [Image] image with bits flipped
856
+ def !
857
+ self ^ -1
858
+ end
985
859
 
986
- # Return the smallest integral value not less than the argument.
987
- #
988
- # @return [Image] ceil of image
989
- def ceil
990
- round :ceil
991
- end
860
+ # Equivalent to image ^ -1
861
+ #
862
+ # @return [Image] image with bits flipped
863
+ def ~
864
+ self ^ -1
865
+ end
992
866
 
993
- # Return the nearest integral value.
994
- #
995
- # @return [Image] rint of image
996
- def rint
997
- round :rint
998
- end
867
+ # @return [Image] image
868
+ def +@
869
+ self
870
+ end
999
871
 
1000
- # AND the bands of an image together
1001
- #
1002
- # @return [Image] all bands ANDed together
1003
- def bandand
1004
- bandbool :and
1005
- end
872
+ # Equivalent to image * -1
873
+ #
874
+ # @return [Image] negative of image
875
+ def -@
876
+ self * -1
877
+ end
1006
878
 
1007
- # OR the bands of an image together
1008
- #
1009
- # @return [Image] all bands ORed together
1010
- def bandor
1011
- bandbool :or
1012
- end
879
+ # Relational less than with an image, constant or array.
880
+ #
881
+ # @param other [Image, Real, Array<Real>] relational less than with this
882
+ # @return [Image] result of less than
883
+ def < other
884
+ call_enum "relational", other, :less
885
+ end
1013
886
 
1014
- # EOR the bands of an image together
1015
- #
1016
- # @return [Image] all bands EORed together
1017
- def bandeor
1018
- bandbool :eor
1019
- end
887
+ # Relational less than or equal to with an image, constant or array.
888
+ #
889
+ # @param other [Image, Real, Array<Real>] relational less than or
890
+ # equal to with this
891
+ # @return [Image] result of less than or equal to
892
+ def <= other
893
+ call_enum "relational", other, :lesseq
894
+ end
1020
895
 
1021
- # Split an n-band image into n separate images.
1022
- #
1023
- # @return [Array<Image>] Array of n one-band images
1024
- def bandsplit
1025
- (0...bands).map {|i| extract_band i}
1026
- end
896
+ # Relational more than with an image, constant or array.
897
+ #
898
+ # @param other [Image, Real, Array<Real>] relational more than with this
899
+ # @return [Image] result of more than
900
+ def > other
901
+ call_enum "relational", other, :more
902
+ end
1027
903
 
1028
- # Join a set of images bandwise.
1029
- #
1030
- # @param other [Image, Array<Image>, Real, Array<Real>] bands to append
1031
- # @return [Image] many band image
1032
- def bandjoin other
1033
- unless other.is_a? Array
1034
- other = [other]
1035
- end
904
+ # Relational more than or equal to with an image, constant or array.
905
+ #
906
+ # @param other [Image, Real, Array<Real>] relational more than or
907
+ # equal to with this
908
+ # @return [Image] result of more than or equal to
909
+ def >= other
910
+ call_enum "relational", other, :moreeq
911
+ end
1036
912
 
1037
- # if other is just Numeric, we can use bandjoin_const
1038
- not_all_real = !other.all?{|x| x.is_a? Numeric}
913
+ # Compare equality to nil, an image, constant or array.
914
+ #
915
+ # @param other [nil, Image, Real, Array<Real>] test equality to this
916
+ # @return [Image] result of equality
917
+ def == other
918
+ # for equality, we must allow tests against nil
919
+ if other == nil
920
+ false
921
+ else
922
+ call_enum "relational", other, :equal
923
+ end
924
+ end
1039
925
 
1040
- if not_all_real
1041
- Vips::Image.bandjoin([self] + other)
1042
- else
1043
- bandjoin_const other
1044
- end
1045
- end
926
+ # Compare inequality to nil, an image, constant or array.
927
+ #
928
+ # @param other [nil, Image, Real, Array<Real>] test inequality to this
929
+ # @return [Image] result of inequality
930
+ def != other
931
+ # for equality, we must allow tests against nil
932
+ if other == nil
933
+ true
934
+ else
935
+ call_enum "relational", other, :noteq
936
+ end
937
+ end
1046
938
 
1047
- # Composite a set of images with a set of blend modes.
1048
- #
1049
- # @param other [Image, Array<Image>, Real, Array<Real>] bands to append
1050
- # @return [Image] many band image
1051
- def composite other, mode, opts = {}
1052
- unless other.is_a? Array
1053
- other = [other]
1054
- end
1055
- unless mode.is_a? Array
1056
- mode = [mode]
1057
- end
939
+ # Fetch bands using a number or a range
940
+ #
941
+ # @param index [Numeric, Range] extract these band(s)
942
+ # @return [Image] extracted band(s)
943
+ def [] index
944
+ if index.is_a? Range
945
+ n = index.size
946
+ extract_band index.begin, n: n
947
+ elsif index.is_a? Numeric
948
+ extract_band index
949
+ else
950
+ raise Vips::Error, "[] index is not range or numeric."
951
+ end
952
+ end
1058
953
 
1059
- mode = mode.map do |x|
1060
- GObject::GValue.from_nick Vips::BLEND_MODE_TYPE, x
1061
- end
954
+ # Convert to an Array. This will be slow for large images.
955
+ #
956
+ # @return [Array] array of Fixnum
957
+ def to_a
958
+ # we render the image to a big string, then unpack
959
+ # as a Ruby array of the correct type
960
+ memory = write_to_memory
961
+
962
+ # make the template for unpack
963
+ template = {
964
+ :char => 'c',
965
+ :uchar => 'C',
966
+ :short => 's_',
967
+ :ushort => 'S_',
968
+ :int => 'i_',
969
+ :uint => 'I_',
970
+ :float => 'f',
971
+ :double => 'd',
972
+ :complex => 'f',
973
+ :dpcomplex => 'd'
974
+ }[format] + '*'
975
+
976
+ # and unpack into something like [1, 2, 3, 4 ..]
977
+ array = memory.unpack(template)
978
+
979
+ # gather band elements together
980
+ pixel_array = array.each_slice(bands).to_a
981
+
982
+ # build rows
983
+ row_array = pixel_array.each_slice(width).to_a
984
+
985
+ return row_array
986
+ end
1062
987
 
1063
- Vips::Image.composite([self] + other, mode, opts)
1064
- end
988
+ # Return the largest integral value not greater than the argument.
989
+ #
990
+ # @return [Image] floor of image
991
+ def floor
992
+ round :floor
993
+ end
1065
994
 
1066
- # Return the coordinates of the image maximum.
1067
- #
1068
- # @return [Real, Real, Real] maximum value, x coordinate of maximum, y
1069
- # coordinate of maximum
1070
- def maxpos
1071
- v, opts = max x: true, y: true
1072
- x = opts['x']
1073
- y = opts['y']
1074
- return v, x, y
1075
- end
995
+ # Return the smallest integral value not less than the argument.
996
+ #
997
+ # @return [Image] ceil of image
998
+ def ceil
999
+ round :ceil
1000
+ end
1076
1001
 
1077
- # Return the coordinates of the image minimum.
1078
- #
1079
- # @return [Real, Real, Real] minimum value, x coordinate of minimum, y
1080
- # coordinate of minimum
1081
- def minpos
1082
- v, opts = min x: true, y: true
1083
- x = opts['x']
1084
- y = opts['y']
1085
- return v, x, y
1086
- end
1002
+ # Return the nearest integral value.
1003
+ #
1004
+ # @return [Image] rint of image
1005
+ def rint
1006
+ round :rint
1007
+ end
1087
1008
 
1088
- # get the value of a pixel as an array
1089
- #
1090
- # @param x [Integer] x coordinate to sample
1091
- # @param y [Integer] y coordinate to sample
1092
- # @return [Array<Float>] the pixel values as an array
1093
- def getpoint x, y
1094
- # vips has an operation that does this, but we can't call it via
1095
- # gobject-introspection 3.1 since it's missing array double
1096
- # get
1097
- #
1098
- # remove this def when gobject-introspection updates
1099
- crop(x, y, 1, 1).bandsplit.map(&:avg)
1100
- end
1009
+ # AND the bands of an image together
1010
+ #
1011
+ # @return [Image] all bands ANDed together
1012
+ def bandand
1013
+ bandbool :and
1014
+ end
1101
1015
 
1102
- # a median filter
1103
- #
1104
- # @param size [Integer] size of filter window
1105
- # @return [Image] result of median filter
1106
- def median size = 3
1107
- rank size, size, (size * size) / 2
1108
- end
1016
+ # OR the bands of an image together
1017
+ #
1018
+ # @return [Image] all bands ORed together
1019
+ def bandor
1020
+ bandbool :or
1021
+ end
1109
1022
 
1110
- # Return the real part of a complex image.
1111
- #
1112
- # @return [Image] real part of complex image
1113
- def real
1114
- complexget :real
1115
- end
1023
+ # EOR the bands of an image together
1024
+ #
1025
+ # @return [Image] all bands EORed together
1026
+ def bandeor
1027
+ bandbool :eor
1028
+ end
1116
1029
 
1117
- # Return the imaginary part of a complex image.
1118
- #
1119
- # @return [Image] imaginary part of complex image
1120
- def imag
1121
- complexget :imag
1122
- end
1030
+ # Split an n-band image into n separate images.
1031
+ #
1032
+ # @return [Array<Image>] Array of n one-band images
1033
+ def bandsplit
1034
+ (0...bands).map {|i| extract_band i}
1035
+ end
1123
1036
 
1124
- # Return an image with rectangular pixels converted to polar.
1125
- #
1126
- # The image
1127
- # can be complex, in which case the return image will also be complex,
1128
- # or must have an even number of bands, in which case pairs of
1129
- # bands are treated as (x, y) coordinates.
1130
- #
1131
- # @see xyz
1132
- # @return [Image] image converted to polar coordinates
1133
- def polar
1134
- Image::run_cmplx(self) {|x| x.complex :polar}
1135
- end
1037
+ # Join a set of images bandwise.
1038
+ #
1039
+ # @param other [Image, Array<Image>, Real, Array<Real>] bands to append
1040
+ # @return [Image] many band image
1041
+ def bandjoin other
1042
+ unless other.is_a? Array
1043
+ other = [other]
1044
+ end
1045
+
1046
+ # if other is just Numeric, we can use bandjoin_const
1047
+ not_all_real = !other.all?{|x| x.is_a? Numeric}
1048
+
1049
+ if not_all_real
1050
+ Vips::Image.bandjoin([self] + other)
1051
+ else
1052
+ bandjoin_const other
1053
+ end
1054
+ end
1136
1055
 
1137
- # Return an image with polar pixels converted to rectangular.
1138
- #
1139
- # The image
1140
- # can be complex, in which case the return image will also be complex,
1141
- # or must have an even number of bands, in which case pairs of
1142
- # bands are treated as (x, y) coordinates.
1143
- #
1144
- # @see xyz
1145
- # @return [Image] image converted to rectangular coordinates
1146
- def rect
1147
- Image::run_cmplx(self) {|x| x.complex :rect}
1148
- end
1056
+ # Composite a set of images with a set of blend modes.
1057
+ #
1058
+ # @param overlay [Image, Array<Image>] images to composite
1059
+ # @param mode [BlendMode, Array<BlendMode>] blend modes to use
1060
+ # @param opts [Hash] Set of options
1061
+ # @option opts [Vips::Interpretation] :compositing_space Composite images in this colour space
1062
+ # @option opts [Boolean] :premultiplied Images have premultiplied alpha
1063
+ # @return [Image] blended image
1064
+ def composite overlay, mode, opts = {}
1065
+ unless overlay.is_a? Array
1066
+ overlay = [overlay]
1067
+ end
1068
+ unless mode.is_a? Array
1069
+ mode = [mode]
1070
+ end
1071
+
1072
+ mode = mode.map do |x|
1073
+ GObject::GValue.from_nick Vips::BLEND_MODE_TYPE, x
1074
+ end
1075
+
1076
+ Vips::Image.composite([self] + overlay, mode, opts)
1077
+ end
1149
1078
 
1150
- # Return the complex conjugate of an image.
1151
- #
1152
- # The image
1153
- # can be complex, in which case the return image will also be complex,
1154
- # or must have an even number of bands, in which case pairs of
1155
- # bands are treated as (x, y) coordinates.
1156
- #
1157
- # @return [Image] complex conjugate
1158
- def conj
1159
- Image::run_cmplx(self) {|x| x.complex :conj}
1160
- end
1079
+ # Return the coordinates of the image maximum.
1080
+ #
1081
+ # @return [Real, Real, Real] maximum value, x coordinate of maximum, y
1082
+ # coordinate of maximum
1083
+ def maxpos
1084
+ v, opts = max x: true, y: true
1085
+ x = opts['x']
1086
+ y = opts['y']
1087
+ return v, x, y
1088
+ end
1161
1089
 
1162
- # Calculate the cross phase of two images.
1163
- #
1164
- # @param other [Image, Real, Array<Real>] cross phase with this
1165
- # @return [Image] cross phase
1166
- def cross_phase other
1167
- complex2 other, :cross_phase
1168
- end
1090
+ # Return the coordinates of the image minimum.
1091
+ #
1092
+ # @return [Real, Real, Real] minimum value, x coordinate of minimum, y
1093
+ # coordinate of minimum
1094
+ def minpos
1095
+ v, opts = min x: true, y: true
1096
+ x = opts['x']
1097
+ y = opts['y']
1098
+ return v, x, y
1099
+ end
1169
1100
 
1170
- # Return the sine of an image in degrees.
1171
- #
1172
- # @return [Image] sine of each pixel
1173
- def sin
1174
- math :sin
1175
- end
1101
+ # a median filter
1102
+ #
1103
+ # @param size [Integer] size of filter window
1104
+ # @return [Image] result of median filter
1105
+ def median size = 3
1106
+ rank size, size, (size * size) / 2
1107
+ end
1176
1108
 
1177
- # Return the cosine of an image in degrees.
1178
- #
1179
- # @return [Image] cosine of each pixel
1180
- def cos
1181
- math :cos
1182
- end
1109
+ # Return the real part of a complex image.
1110
+ #
1111
+ # @return [Image] real part of complex image
1112
+ def real
1113
+ complexget :real
1114
+ end
1183
1115
 
1184
- # Return the tangent of an image in degrees.
1185
- #
1186
- # @return [Image] tangent of each pixel
1187
- def tan
1188
- math :tan
1189
- end
1116
+ # Return the imaginary part of a complex image.
1117
+ #
1118
+ # @return [Image] imaginary part of complex image
1119
+ def imag
1120
+ complexget :imag
1121
+ end
1190
1122
 
1191
- # Return the inverse sine of an image in degrees.
1192
- #
1193
- # @return [Image] inverse sine of each pixel
1194
- def asin
1195
- math :asin
1196
- end
1123
+ # Return an image with rectangular pixels converted to polar.
1124
+ #
1125
+ # The image
1126
+ # can be complex, in which case the return image will also be complex,
1127
+ # or must have an even number of bands, in which case pairs of
1128
+ # bands are treated as (x, y) coordinates.
1129
+ #
1130
+ # @see xyz
1131
+ # @return [Image] image converted to polar coordinates
1132
+ def polar
1133
+ Image::run_cmplx(self) {|x| x.complex :polar}
1134
+ end
1197
1135
 
1198
- # Return the inverse cosine of an image in degrees.
1199
- #
1200
- # @return [Image] inverse cosine of each pixel
1201
- def acos
1202
- math :acos
1203
- end
1136
+ # Return an image with polar pixels converted to rectangular.
1137
+ #
1138
+ # The image
1139
+ # can be complex, in which case the return image will also be complex,
1140
+ # or must have an even number of bands, in which case pairs of
1141
+ # bands are treated as (x, y) coordinates.
1142
+ #
1143
+ # @see xyz
1144
+ # @return [Image] image converted to rectangular coordinates
1145
+ def rect
1146
+ Image::run_cmplx(self) {|x| x.complex :rect}
1147
+ end
1204
1148
 
1205
- # Return the inverse tangent of an image in degrees.
1206
- #
1207
- # @return [Image] inverse tangent of each pixel
1208
- def atan
1209
- math :atan
1210
- end
1149
+ # Return the complex conjugate of an image.
1150
+ #
1151
+ # The image
1152
+ # can be complex, in which case the return image will also be complex,
1153
+ # or must have an even number of bands, in which case pairs of
1154
+ # bands are treated as (x, y) coordinates.
1155
+ #
1156
+ # @return [Image] complex conjugate
1157
+ def conj
1158
+ Image::run_cmplx(self) {|x| x.complex :conj}
1159
+ end
1211
1160
 
1212
- # Return the natural log of an image.
1213
- #
1214
- # @return [Image] natural log of each pixel
1215
- def log
1216
- math :log
1217
- end
1161
+ # Calculate the cross phase of two images.
1162
+ #
1163
+ # @param other [Image, Real, Array<Real>] cross phase with this
1164
+ # @return [Image] cross phase
1165
+ def cross_phase other
1166
+ complex2 other, :cross_phase
1167
+ end
1218
1168
 
1219
- # Return the log base 10 of an image.
1220
- #
1221
- # @return [Image] base 10 log of each pixel
1222
- def log10
1223
- math :log10
1224
- end
1169
+ # Return the sine of an image in degrees.
1170
+ #
1171
+ # @return [Image] sine of each pixel
1172
+ def sin
1173
+ math :sin
1174
+ end
1225
1175
 
1226
- # Return e ** pixel.
1227
- #
1228
- # @return [Image] e ** pixel
1229
- def exp
1230
- math :exp
1231
- end
1176
+ # Return the cosine of an image in degrees.
1177
+ #
1178
+ # @return [Image] cosine of each pixel
1179
+ def cos
1180
+ math :cos
1181
+ end
1232
1182
 
1233
- # Return 10 ** pixel.
1234
- #
1235
- # @return [Image] 10 ** pixel
1236
- def exp10
1237
- math :exp10
1238
- end
1183
+ # Return the tangent of an image in degrees.
1184
+ #
1185
+ # @return [Image] tangent of each pixel
1186
+ def tan
1187
+ math :tan
1188
+ end
1239
1189
 
1240
- # Flip horizontally.
1241
- #
1242
- # @return [Image] image flipped horizontally
1243
- def fliphor
1244
- flip :horizontal
1245
- end
1190
+ # Return the inverse sine of an image in degrees.
1191
+ #
1192
+ # @return [Image] inverse sine of each pixel
1193
+ def asin
1194
+ math :asin
1195
+ end
1246
1196
 
1247
- # Flip vertically.
1248
- #
1249
- # @return [Image] image flipped vertically
1250
- def flipver
1251
- flip :vertical
1252
- end
1197
+ # Return the inverse cosine of an image in degrees.
1198
+ #
1199
+ # @return [Image] inverse cosine of each pixel
1200
+ def acos
1201
+ math :acos
1202
+ end
1253
1203
 
1254
- # Erode with a structuring element.
1255
- #
1256
- # The structuring element must be an array with 0 for black, 255 for
1257
- # white and 128 for don't care.
1258
- #
1259
- # @param mask [Image, Array<Real>, Array<Array<Real>>] structuring
1260
- # element
1261
- # @return [Image] eroded image
1262
- def erode mask
1263
- morph mask, :erode
1264
- end
1204
+ # Return the inverse tangent of an image in degrees.
1205
+ #
1206
+ # @return [Image] inverse tangent of each pixel
1207
+ def atan
1208
+ math :atan
1209
+ end
1265
1210
 
1266
- # Dilate with a structuring element.
1267
- #
1268
- # The structuring element must be an array with 0 for black, 255 for
1269
- # white and 128 for don't care.
1270
- #
1271
- # @param mask [Image, Array<Real>, Array<Array<Real>>] structuring
1272
- # element
1273
- # @return [Image] dilated image
1274
- def dilate mask
1275
- morph mask, :dilate
1276
- end
1211
+ # Return the natural log of an image.
1212
+ #
1213
+ # @return [Image] natural log of each pixel
1214
+ def log
1215
+ math :log
1216
+ end
1277
1217
 
1278
- # Rotate by 90 degrees clockwise.
1279
- #
1280
- # @return [Image] rotated image
1281
- def rot90
1282
- rot :d90
1283
- end
1218
+ # Return the log base 10 of an image.
1219
+ #
1220
+ # @return [Image] base 10 log of each pixel
1221
+ def log10
1222
+ math :log10
1223
+ end
1284
1224
 
1285
- # Rotate by 180 degrees clockwise.
1286
- #
1287
- # @return [Image] rotated image
1288
- def rot180
1289
- rot :d180
1290
- end
1225
+ # Return e ** pixel.
1226
+ #
1227
+ # @return [Image] e ** pixel
1228
+ def exp
1229
+ math :exp
1230
+ end
1291
1231
 
1292
- # Rotate by 270 degrees clockwise.
1293
- #
1294
- # @return [Image] rotated image
1295
- def rot270
1296
- rot :d270
1297
- end
1232
+ # Return 10 ** pixel.
1233
+ #
1234
+ # @return [Image] 10 ** pixel
1235
+ def exp10
1236
+ math :exp10
1237
+ end
1298
1238
 
1299
- # Select pixels from `th` if `self` is non-zero and from `el` if
1300
- # `self` is zero. Use the `:blend` option to fade smoothly
1301
- # between `th` and `el`.
1302
- #
1303
- # @param th [Image, Real, Array<Real>] true values
1304
- # @param el [Image, Real, Array<Real>] false values
1305
- # @param opts [Hash] set of options
1306
- # @option opts [Boolean] :blend (false) Blend smoothly between th and el
1307
- # @return [Image] merged image
1308
- def ifthenelse(th, el, opts = {})
1309
- match_image = [th, el, self].find {|x| x.is_a? Vips::Image}
1310
-
1311
- unless th.is_a? Vips::Image
1312
- th = Operation.imageize match_image, th
1313
- end
1314
- unless el.is_a? Vips::Image
1315
- el = Operation.imageize match_image, el
1316
- end
1239
+ # Flip horizontally.
1240
+ #
1241
+ # @return [Image] image flipped horizontally
1242
+ def fliphor
1243
+ flip :horizontal
1244
+ end
1317
1245
 
1318
- Vips::Operation.call "ifthenelse", [self, th, el], opts
1319
- end
1246
+ # Flip vertically.
1247
+ #
1248
+ # @return [Image] image flipped vertically
1249
+ def flipver
1250
+ flip :vertical
1251
+ end
1320
1252
 
1321
- # Scale an image to uchar. This is the vips `scale` operation, but
1322
- # renamed to avoid a clash with the `.scale` property.
1323
- #
1324
- # @param opts [Hash] Set of options
1325
- # @return [Vips::Image] Output image
1326
- def scaleimage opts = {}
1327
- Vips::Image.scale self, opts
1328
- end
1253
+ # Erode with a structuring element.
1254
+ #
1255
+ # The structuring element must be an array with 0 for black, 255 for
1256
+ # white and 128 for don't care.
1257
+ #
1258
+ # @param mask [Image, Array<Real>, Array<Array<Real>>] structuring
1259
+ # element
1260
+ # @return [Image] eroded image
1261
+ def erode mask
1262
+ morph mask, :erode
1263
+ end
1329
1264
 
1265
+ # Dilate with a structuring element.
1266
+ #
1267
+ # The structuring element must be an array with 0 for black, 255 for
1268
+ # white and 128 for don't care.
1269
+ #
1270
+ # @param mask [Image, Array<Real>, Array<Array<Real>>] structuring
1271
+ # element
1272
+ # @return [Image] dilated image
1273
+ def dilate mask
1274
+ morph mask, :dilate
1330
1275
  end
1331
1276
 
1332
- # This method generates yard comments for all the dynamically bound
1333
- # vips operations.
1277
+ # Rotate by 90 degrees clockwise.
1334
1278
  #
1335
- # Regenerate with something like:
1279
+ # @return [Image] rotated image
1280
+ def rot90
1281
+ rot :d90
1282
+ end
1283
+
1284
+ # Rotate by 180 degrees clockwise.
1336
1285
  #
1337
- # ```
1338
- # $ ruby > methods.rb
1339
- # require 'vips'; Vips::generate_yard
1340
- # ^D
1341
- # ```
1286
+ # @return [Image] rotated image
1287
+ def rot180
1288
+ rot :d180
1289
+ end
1342
1290
 
1343
- def self.generate_yard
1344
- # these have hand-written methods, see above
1345
- no_generate = ["scale", "bandjoin", "composite", "ifthenelse"]
1346
-
1347
- # map gobject's type names to Ruby
1348
- map_go_to_ruby = {
1349
- "gboolean" => "Boolean",
1350
- "gint" => "Integer",
1351
- "gdouble" => "Float",
1352
- "gfloat" => "Float",
1353
- "gchararray" => "String",
1354
- "VipsImage" => "Vips::Image",
1355
- "VipsInterpolate" => "Vips::Interpolate",
1356
- "VipsArrayDouble" => "Array<Double>",
1357
- "VipsArrayInt" => "Array<Integer>",
1358
- "VipsArrayImage" => "Array<Image>",
1359
- "VipsArrayString" => "Array<String>",
1360
- }
1361
-
1362
- generate_operation = lambda do |gtype, nickname, op|
1363
- op_flags = op.get_flags
1364
- return if (op_flags & OPERATION_DEPRECATED) != 0
1365
- return if no_generate.include? nickname
1366
- description = Vips::vips_object_get_description op
1367
-
1368
- # find and classify all the arguments the operator can take
1369
- required_input = []
1370
- optional_input = []
1371
- required_output = []
1372
- optional_output = []
1373
- member_x = nil
1374
- op.argument_map do |pspec, argument_class, argument_instance|
1375
- arg_flags = argument_class[:flags]
1376
- next if (arg_flags & ARGUMENT_CONSTRUCT) == 0
1377
- next if (arg_flags & ARGUMENT_DEPRECATED) != 0
1378
-
1379
- name = pspec[:name].tr("-", "_")
1380
- # 'in' as a param name confuses yard
1381
- name = "im" if name == "in"
1382
- gtype = pspec[:value_type]
1383
- fundamental = GObject::g_type_fundamental gtype
1384
- type_name = GObject::g_type_name gtype
1385
- if map_go_to_ruby.include? type_name
1386
- type_name = map_go_to_ruby[type_name]
1387
- end
1388
- if fundamental == GObject::GFLAGS_TYPE ||
1389
- fundamental == GObject::GENUM_TYPE
1390
- type_name = "Vips::" + type_name[/Vips(.*)/, 1]
1391
- end
1392
- blurb = GObject::g_param_spec_get_blurb pspec
1393
- value = {:name => name,
1394
- :flags => arg_flags,
1395
- :gtype => gtype,
1396
- :type_name => type_name,
1397
- :blurb => blurb}
1398
-
1399
- if (arg_flags & ARGUMENT_INPUT) != 0
1400
- if (arg_flags & ARGUMENT_REQUIRED) != 0
1401
- # note the first required input image, if any ... we
1402
- # will be a method of this instance
1403
- if !member_x && gtype == Vips::IMAGE_TYPE
1404
- member_x = value
1405
- else
1406
- required_input << value
1407
- end
1408
- else
1409
- optional_input << value
1410
- end
1411
- end
1412
-
1413
- # MODIFY INPUT args count as OUTPUT as well
1414
- if (arg_flags & ARGUMENT_OUTPUT) != 0 ||
1415
- ((arg_flags & ARGUMENT_INPUT) != 0 &&
1416
- (arg_flags & ARGUMENT_MODIFY) != 0)
1417
- if (arg_flags & ARGUMENT_REQUIRED) != 0
1418
- required_output << value
1419
- else
1420
- optional_output << value
1421
- end
1422
- end
1291
+ # Rotate by 270 degrees clockwise.
1292
+ #
1293
+ # @return [Image] rotated image
1294
+ def rot270
1295
+ rot :d270
1296
+ end
1423
1297
 
1424
- end
1298
+ # Select pixels from `th` if `self` is non-zero and from `el` if
1299
+ # `self` is zero. Use the `:blend` option to fade smoothly
1300
+ # between `th` and `el`.
1301
+ #
1302
+ # @param th [Image, Real, Array<Real>] true values
1303
+ # @param el [Image, Real, Array<Real>] false values
1304
+ # @param opts [Hash] set of options
1305
+ # @option opts [Boolean] :blend (false) Blend smoothly between th and el
1306
+ # @return [Image] merged image
1307
+ def ifthenelse(th, el, opts = {})
1308
+ match_image = [th, el, self].find {|x| x.is_a? Vips::Image}
1309
+
1310
+ unless th.is_a? Vips::Image
1311
+ th = Operation.imageize match_image, th
1312
+ end
1313
+ unless el.is_a? Vips::Image
1314
+ el = Operation.imageize match_image, el
1315
+ end
1316
+
1317
+ Vips::Operation.call "ifthenelse", [self, th, el], opts
1318
+ end
1425
1319
 
1426
- print "# @!method "
1427
- print "self." unless member_x
1428
- print "#{nickname}("
1429
- print required_input.map{|x| x[:name]}.join(", ")
1430
- print ", " if required_input.length > 0
1431
- puts "opts = {})"
1320
+ # Scale an image to uchar. This is the vips `scale` operation, but
1321
+ # renamed to avoid a clash with the `.scale` property.
1322
+ #
1323
+ # @param opts [Hash] Set of options
1324
+ # @return [Vips::Image] Output image
1325
+ def scaleimage opts = {}
1326
+ Vips::Image.scale self, opts
1327
+ end
1432
1328
 
1433
- puts "# #{description.capitalize}."
1329
+ end
1330
+ end
1434
1331
 
1435
- required_input.each do |arg|
1436
- puts "# @param #{arg[:name]} [#{arg[:type_name]}] " +
1437
- "#{arg[:blurb]}"
1438
- end
1332
+ module Vips
1439
1333
 
1440
- puts "# @param opts [Hash] Set of options"
1441
- optional_input.each do |arg|
1442
- puts "# @option opts [#{arg[:type_name]}] :#{arg[:name]} " +
1443
- "#{arg[:blurb]}"
1444
- end
1445
- optional_output.each do |arg|
1446
- print "# @option opts [#{arg[:type_name]}] :#{arg[:name]}"
1447
- puts " Output #{arg[:blurb]}"
1334
+ # This method generates yard comments for all the dynamically bound
1335
+ # vips operations.
1336
+ #
1337
+ # Regenerate with something like:
1338
+ #
1339
+ # ```
1340
+ # $ ruby > methods.rb
1341
+ # require 'vips'; Vips::generate_yard
1342
+ # ^D
1343
+ # ```
1344
+
1345
+ def self.generate_yard
1346
+ # these have hand-written methods, see above
1347
+ no_generate = ["scale", "bandjoin", "composite", "ifthenelse"]
1348
+
1349
+ # map gobject's type names to Ruby
1350
+ map_go_to_ruby = {
1351
+ "gboolean" => "Boolean",
1352
+ "gint" => "Integer",
1353
+ "gdouble" => "Float",
1354
+ "gfloat" => "Float",
1355
+ "gchararray" => "String",
1356
+ "VipsImage" => "Vips::Image",
1357
+ "VipsInterpolate" => "Vips::Interpolate",
1358
+ "VipsArrayDouble" => "Array<Double>",
1359
+ "VipsArrayInt" => "Array<Integer>",
1360
+ "VipsArrayImage" => "Array<Image>",
1361
+ "VipsArrayString" => "Array<String>",
1362
+ }
1363
+
1364
+ generate_operation = lambda do |gtype, nickname, op|
1365
+ op_flags = op.get_flags
1366
+ return if (op_flags & OPERATION_DEPRECATED) != 0
1367
+ return if no_generate.include? nickname
1368
+ description = Vips::vips_object_get_description op
1369
+
1370
+ # find and classify all the arguments the operator can take
1371
+ required_input = []
1372
+ optional_input = []
1373
+ required_output = []
1374
+ optional_output = []
1375
+ member_x = nil
1376
+ op.argument_map do |pspec, argument_class, argument_instance|
1377
+ arg_flags = argument_class[:flags]
1378
+ next if (arg_flags & ARGUMENT_CONSTRUCT) == 0
1379
+ next if (arg_flags & ARGUMENT_DEPRECATED) != 0
1380
+
1381
+ name = pspec[:name].tr("-", "_")
1382
+ # 'in' as a param name confuses yard
1383
+ name = "im" if name == "in"
1384
+ gtype = pspec[:value_type]
1385
+ fundamental = GObject::g_type_fundamental gtype
1386
+ type_name = GObject::g_type_name gtype
1387
+ if map_go_to_ruby.include? type_name
1388
+ type_name = map_go_to_ruby[type_name]
1389
+ end
1390
+ if fundamental == GObject::GFLAGS_TYPE ||
1391
+ fundamental == GObject::GENUM_TYPE
1392
+ type_name = "Vips::" + type_name[/Vips(.*)/, 1]
1393
+ end
1394
+ blurb = GObject::g_param_spec_get_blurb pspec
1395
+ value = {:name => name,
1396
+ :flags => arg_flags,
1397
+ :gtype => gtype,
1398
+ :type_name => type_name,
1399
+ :blurb => blurb}
1400
+
1401
+ if (arg_flags & ARGUMENT_INPUT) != 0
1402
+ if (arg_flags & ARGUMENT_REQUIRED) != 0
1403
+ # note the first required input image, if any ... we
1404
+ # will be a method of this instance
1405
+ if !member_x && gtype == Vips::IMAGE_TYPE
1406
+ member_x = value
1407
+ else
1408
+ required_input << value
1448
1409
  end
1410
+ else
1411
+ optional_input << value
1412
+ end
1413
+ end
1414
+
1415
+ # MODIFY INPUT args count as OUTPUT as well
1416
+ if (arg_flags & ARGUMENT_OUTPUT) != 0 ||
1417
+ ((arg_flags & ARGUMENT_INPUT) != 0 &&
1418
+ (arg_flags & ARGUMENT_MODIFY) != 0)
1419
+ if (arg_flags & ARGUMENT_REQUIRED) != 0
1420
+ required_output << value
1421
+ else
1422
+ optional_output << value
1423
+ end
1424
+ end
1425
+
1426
+ end
1427
+
1428
+ print "# @!method "
1429
+ print "self." unless member_x
1430
+ print "#{nickname}("
1431
+ print required_input.map{|x| x[:name]}.join(", ")
1432
+ print ", " if required_input.length > 0
1433
+ puts "opts = {})"
1434
+
1435
+ puts "# #{description.capitalize}."
1436
+
1437
+ required_input.each do |arg|
1438
+ puts "# @param #{arg[:name]} [#{arg[:type_name]}] " +
1439
+ "#{arg[:blurb]}"
1440
+ end
1441
+
1442
+ puts "# @param opts [Hash] Set of options"
1443
+ optional_input.each do |arg|
1444
+ puts "# @option opts [#{arg[:type_name]}] :#{arg[:name]} " +
1445
+ "#{arg[:blurb]}"
1446
+ end
1447
+ optional_output.each do |arg|
1448
+ print "# @option opts [#{arg[:type_name]}] :#{arg[:name]}"
1449
+ puts " Output #{arg[:blurb]}"
1450
+ end
1451
+
1452
+ print "# @return ["
1453
+ if required_output.length == 0
1454
+ print "nil"
1455
+ elsif required_output.length == 1
1456
+ print required_output.first[:type_name]
1457
+ elsif
1458
+ print "Array<"
1459
+ print required_output.map{|x| x[:type_name]}.join(", ")
1460
+ print ">"
1461
+ end
1462
+ if optional_output.length > 0
1463
+ print ", Hash<Symbol => Object>"
1464
+ end
1465
+ print "] "
1466
+ print required_output.map{|x| x[:blurb]}.join(", ")
1467
+ if optional_output.length > 0
1468
+ print ", " if required_output.length > 0
1469
+ print "Hash of optional output items"
1470
+ end
1471
+ puts ""
1472
+
1473
+ puts ""
1474
+ end
1449
1475
 
1450
- print "# @return ["
1451
- if required_output.length == 0
1452
- print "nil"
1453
- elsif required_output.length == 1
1454
- print required_output.first[:type_name]
1455
- elsif
1456
- print "Array<"
1457
- print required_output.map{|x| x[:type_name]}.join(", ")
1458
- print ">"
1459
- end
1460
- if optional_output.length > 0
1461
- print ", Hash<Symbol => Object>"
1462
- end
1463
- print "] "
1464
- print required_output.map{|x| x[:blurb]}.join(", ")
1465
- if optional_output.length > 0
1466
- print ", " if required_output.length > 0
1467
- print "Hash of optional output items"
1468
- end
1469
- puts ""
1476
+ generate_class = lambda do |gtype, a|
1477
+ nickname = Vips::nickname_find gtype
1470
1478
 
1471
- puts ""
1479
+ if nickname
1480
+ begin
1481
+ # can fail for abstract types
1482
+ op = Vips::Operation.new nickname
1483
+ rescue
1472
1484
  end
1473
1485
 
1474
- generate_class = lambda do |gtype, a|
1475
- nickname = Vips::nickname_find gtype
1476
-
1477
- if nickname
1478
- begin
1479
- # can fail for abstract types
1480
- op = Vips::Operation.new nickname
1481
- rescue
1482
- end
1483
-
1484
- generate_operation.(gtype, nickname, op) if op
1485
- end
1486
+ generate_operation.(gtype, nickname, op) if op
1487
+ end
1486
1488
 
1487
- Vips::vips_type_map gtype, generate_class, nil
1488
- end
1489
+ Vips::vips_type_map gtype, generate_class, nil
1490
+ end
1489
1491
 
1490
- puts "module Vips"
1491
- puts " class Image"
1492
- puts ""
1492
+ puts "module Vips"
1493
+ puts " class Image"
1494
+ puts ""
1493
1495
 
1494
- generate_class.(GObject::g_type_from_name("VipsOperation"), nil)
1496
+ generate_class.(GObject::g_type_from_name("VipsOperation"), nil)
1495
1497
 
1496
- puts " end"
1497
- puts "end"
1498
- end
1498
+ puts " end"
1499
+ puts "end"
1500
+ end
1499
1501
 
1500
1502
  end