image_voodoo 0.5 → 0.6

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.
@@ -5,6 +5,8 @@ README.txt
5
5
  LICENSE.txt
6
6
  lib/image_science.rb
7
7
  lib/image_voodoo
8
+ lib/image_voodoo/awt.rb
9
+ lib/image_voodoo/gae.rb
8
10
  lib/image_voodoo/version.rb
9
11
  lib/image_voodoo.rb
10
12
  samples/bench.rb
@@ -27,62 +27,29 @@
27
27
  class ImageVoodoo
28
28
  include Java
29
29
 
30
- import java.awt.RenderingHints
31
- import java.awt.color.ColorSpace
32
- import java.awt.geom.AffineTransform
33
- import java.awt.image.BufferedImage
34
- import java.awt.image.ByteLookupTable
35
- import java.awt.image.ColorConvertOp
36
- import java.awt.image.LookupOp
37
- import java.awt.image.RescaleOp
38
30
  JFile = java.io.File
39
- import java.io.ByteArrayInputStream
40
- import java.io.ByteArrayOutputStream
41
- import javax.imageio.ImageIO
42
- import javax.swing.JFrame
43
31
 
44
- NEGATIVE_OP = LookupOp.new(ByteLookupTable.new(0, (0...254).to_a.reverse.to_java(:byte)), nil)
45
- GREY_OP = ColorConvertOp.new(ColorSpace.getInstance(ColorSpace::CS_GRAY), nil)
46
- ARGB = BufferedImage::TYPE_INT_ARGB
47
- RGB = BufferedImage::TYPE_INT_RGB
48
- SCALE_SMOOTH = java.awt.Image::SCALE_SMOOTH
32
+ # FIXME: This has an issue when used in test/unit where the classcastexception
33
+ # is throwing the stack trace to output. This does not happen when used
34
+ # directly. Not sure....
35
+ # gae and awt define the technology-specific methods and more importantly
36
+ # all the *_impl methods which you will see referenced in this file.
37
+ begin
38
+ require 'image_voodoo/gae'
39
+ rescue
40
+ require 'image_voodoo/awt'
41
+ end
49
42
 
50
43
  def initialize(src)
51
44
  @src = src
52
45
  end
53
46
 
54
- #
55
- # Add a border to the image and yield/return a new image. The following
56
- # options are supported:
57
- # - width: How thick is the border (default: 3)
58
- # - color: Which color is the border (in rrggbb hex value)
59
- # - style: etched, raised, plain (default: plain)
60
- #
61
- def add_border(options = {})
62
- border_width = options[:width].to_i || 2
63
- color = hex_to_color(options[:color]) || hex_to_color("000000")
64
- style = options[:style]
65
- style = nil if style.to_sym == :plain
66
- new_width, new_height = width + 2*border_width, height + 2*border_width
67
- target = paint(BufferedImage.new(new_width, new_height, color_type)) do |g|
68
- g.color = color
69
- if style
70
- raised = style.to_sym == :raised ? true : false
71
- g.fill3DRect(0, 0, new_width, new_height, raised)
72
- else
73
- g.fill_rect(0, 0, new_width, new_height)
74
- end
75
- g.draw_image(@src, nil, border_width, border_width)
76
- end
77
- block_given? ? yield(target) : target
78
- end
79
-
80
47
  #
81
48
  # Adjusts the brightness of each pixel in image by the following formula:
82
49
  # new_pixel = pixel * scale + offset
83
50
  #
84
51
  def adjust_brightness(scale, offset)
85
- image = internal_transform(RescaleOp.new(scale, offset, nil))
52
+ image = guard { adjust_brightness_impl(scale, offset) }
86
53
  block_given? ? yield(image) : image
87
54
  end
88
55
 
@@ -91,16 +58,7 @@ class ImageVoodoo
91
58
  # image.
92
59
  #
93
60
  def alpha(rgb)
94
- color = hex_to_color(rgb)
95
- target = paint(BufferedImage.new(width, height, ARGB)) do |g|
96
- g.set_composite(java.awt.AlphaComposite::Src)
97
- g.draw_image(@src, nil, 0, 0)
98
- 0.upto(height-1) do |i|
99
- 0.upto(width-1) do |j|
100
- target.setRGB(j, i, 0x8F1C1C) if target.getRGB(j, i) == color.getRGB
101
- end
102
- end
103
- end
61
+ target = guard { alpha_impl(rgb) }
104
62
  block_given? ? yield(target) : target
105
63
  end
106
64
 
@@ -108,9 +66,8 @@ class ImageVoodoo
108
66
  # Write current image out as a stream of bytes using provided format.
109
67
  #
110
68
  def bytes(format)
111
- out = ByteArrayOutputStream.new
112
- ImageIO.write(@src, format, out)
113
- String.from_java_bytes(out.to_byte_array)
69
+ java_bytes = guard { bytes_impl(format) }
70
+ String.from_java_bytes java_bytes
114
71
  end
115
72
 
116
73
  #
@@ -130,9 +87,7 @@ class ImageVoodoo
130
87
  # Flips the image horizontally and yields/returns the new image.
131
88
  #
132
89
  def flip_horizontally
133
- target = paint do |g|
134
- g.draw_image @src, 0, 0, width, height, width, 0, 0, height, nil
135
- end
90
+ target = guard { flip_horizontally_impl }
136
91
  block_given? ? yield(target) : target
137
92
  end
138
93
 
@@ -140,9 +95,7 @@ class ImageVoodoo
140
95
  # Flips the image vertically and yields/returns the new image.
141
96
  #
142
97
  def flip_vertically
143
- target = paint do |g|
144
- g.draw_image @src, 0, 0, width, height, 0, height, width, 0, nil
145
- end
98
+ target = guard { flip_vertically_impl }
146
99
  block_given? ? yield(target) : target
147
100
  end
148
101
 
@@ -150,7 +103,7 @@ class ImageVoodoo
150
103
  # Creates a grayscale version of image and yields/returns the new image.
151
104
  #
152
105
  def greyscale
153
- target = internal_transform(GREY_OP)
106
+ target = guard { greyscale_impl }
154
107
  block_given? ? yield(target) : target
155
108
  end
156
109
  alias_method :grayscale, :greyscale
@@ -159,19 +112,15 @@ class ImageVoodoo
159
112
  # Creates a negative and yields/returns the new image.
160
113
  #
161
114
  def negative
162
- target = internal_transform(NEGATIVE_OP)
115
+ target = guard { negative_impl }
163
116
  block_given? ? yield(target) : target
164
117
  end
165
118
 
166
119
  #
167
- # Resizes the image to width and height using bicubic interpolation and
168
- # yields/returns the new image.
120
+ # Resizes the image to width and height and yields/returns the new image.
169
121
  #
170
122
  def resize(width, height)
171
- target = paint(BufferedImage.new(width, height, color_type)) do |g|
172
- scaled_image = @src.get_scaled_instance width, height, SCALE_SMOOTH
173
- g.draw_image scaled_image, 0, 0, nil
174
- end
123
+ target = guard { resize_impl(width, height) }
175
124
  block_given? ? yield(target) : target
176
125
  rescue NativeException => ne
177
126
  raise ArgumentError, ne.message
@@ -185,7 +134,7 @@ class ImageVoodoo
185
134
  format = File.extname(file)
186
135
  return false if format == ""
187
136
  format = format[1..-1].downcase
188
- ImageIO.write(@src, format, JFile.new(file))
137
+ guard { save_impl(format, JFile.new(file)) }
189
138
  true
190
139
  end
191
140
 
@@ -213,79 +162,16 @@ class ImageVoodoo
213
162
  # new image.
214
163
  #
215
164
  def with_crop(left, top, right, bottom)
216
- image = ImageVoodoo.new @src.get_subimage(left, top, right-left, bottom-top)
165
+ image = guard { with_crop_impl(left, top, right, bottom) }
217
166
  block_given? ? yield(image) : image
218
167
  end
219
168
 
220
- #
221
- # A simple swing wrapper around an image voodoo object.
222
- #
223
- class JImagePanel < javax.swing.JPanel
224
- def initialize(image, x=0, y=0)
225
- super()
226
- @image, @x, @y = image, x, y
227
- end
228
-
229
- def image=(image)
230
- @image = image
231
- invalidate
232
- end
233
-
234
- def getPreferredSize
235
- java.awt.Dimension.new(@image.width, @image.height)
236
- end
237
-
238
- def paintComponent(graphics)
239
- graphics.draw_image(@image.to_java, @x, @y, nil)
240
- end
241
- end
242
-
243
- # Internal class for closing preview window
244
- class WindowClosed
245
- def initialize(block = nil)
246
- @block = block || proc { java.lang.System.exit(0) }
247
- end
248
- def method_missing(meth,*args); end
249
- def windowClosing(event); @block.call; end
250
- end
251
-
252
- #
253
- # Creates a viewable frame displaying current image within it.
254
- #
255
- def preview(&block)
256
- frame = JFrame.new("Preview")
257
- frame.add_window_listener WindowClosed.new(block)
258
- frame.set_bounds 0, 0, width + 20, height + 40
259
- frame.add JImagePanel.new(self, 10, 10)
260
- frame.visible = true
261
- end
262
-
263
- #
264
- # TODO: Figure out how to determine whether source has alpha or not
265
- # Experimental: Read an image from the url source and yield/return that
266
- # image.
267
- #
268
- def self.from_url(source)
269
- url = java.net.URL.new(source)
270
- image = java.awt.Toolkit.default_toolkit.create_image(url)
271
- tracker = java.awt.MediaTracker.new(java.awt.Label.new(""))
272
- tracker.addImage(image, 0);
273
- tracker.waitForID(0)
274
- target = paint(BufferedImage.new(image.width, image.height, RGB)) do |g|
275
- g.draw_image image, 0, 0, nil
276
- end
277
- block_given? ? yield(target) : target
278
- rescue java.io.IOException, java.net.MalformedURLException
279
- raise ArgumentError.new "Trouble retrieving image: #{$!.message}"
280
- end
281
-
282
169
  #
283
170
  # A top-level image loader opens path and then yields/returns the image.
284
171
  #
285
172
  def self.with_image(file)
286
173
  raise ArgumentError, "file does not exist" unless File.file?(file)
287
- buffered_image = ImageIO.read(JFile.new(file))
288
- image = ImageVoodoo.new(buffered_image) if buffered_image
174
+ image = guard { with_image_impl(JFile.new(file)) }
289
175
  image && block_given? ? yield(image) : image
290
176
  end
291
177
 
@@ -294,10 +180,26 @@ class ImageVoodoo
294
180
  #
295
181
  def self.with_bytes(bytes)
296
182
  bytes = bytes.to_java_bytes if String === bytes
297
- image = ImageVoodoo.new ImageIO.read(ByteArrayInputStream.new(bytes))
183
+ image = guard { with_bytes_impl(bytes) }
298
184
  block_given? ? yield(image) : image
299
185
  end
300
186
 
187
+ #
188
+ # *_impl providers only need provide the implementation if it can
189
+ # support it. Otherwise, this method will detect that the method is
190
+ # missing.
191
+ #
192
+ def self.guard(&block)
193
+ begin
194
+ return block.call
195
+ rescue NoMethodError => e
196
+ "Unimplemented Feature: #{e}"
197
+ end
198
+ end
199
+ def guard(&block)
200
+ ImageVoodoo.guard(&block)
201
+ end
202
+
301
203
  #
302
204
  # Returns the height of the image, in pixels.
303
205
  #
@@ -313,55 +215,11 @@ class ImageVoodoo
313
215
  end
314
216
 
315
217
  #
316
- # Returns the underlying Java BufferedImage associated with this object.
218
+ # Returns the underlying Java class associated with this object. Note:
219
+ # Depending on whether you are using AWT or GAE/J you will get a totally
220
+ # different Java class. So caveat emptor!
317
221
  #
318
222
  def to_java
319
223
  @src
320
224
  end
321
-
322
- private
323
-
324
- #
325
- # Converts a RGB hex value into a java.awt.Color object or dies trying
326
- # with an ArgumentError.
327
- #
328
- def hex_to_color(rgb)
329
- raise ArgumentError.new "hex rrggbb needed" if rgb !~ /[[:xdigit:]]{6,6}/
330
-
331
- java.awt.Color.new(rgb[0,2].to_i(16), rgb[2,2].to_i(16), rgb[4,2].to_i(16))
332
- end
333
-
334
- #
335
- # Determines the best colorspace for a new image based on whether the
336
- # existing image contains an alpha channel or not.
337
- #
338
- def color_type
339
- @src.color_model.has_alpha ? ARGB : RGB
340
- end
341
-
342
- #
343
- # DRY up drawing setup+teardown
344
- #
345
- def paint(src=dup_src)
346
- yield src.graphics
347
- src.graphics.dispose
348
- ImageVoodoo.new src
349
- end
350
-
351
- #
352
- # Make a duplicate of the underlying Java src image
353
- #
354
- def dup_src
355
- BufferedImage.new width, height, color_type
356
- end
357
-
358
- #
359
- # Do simple AWT operation transformation to target.
360
- #
361
- def internal_transform(operation, target=dup_src)
362
- paint(target) do |g|
363
- g.draw_image(@src, 0, 0, nil)
364
- g.draw_image(operation.filter(target, nil), 0, 0, nil)
365
- end
366
- end
367
225
  end
@@ -0,0 +1,223 @@
1
+ class ImageVoodoo
2
+ java_import java.awt.RenderingHints
3
+ java_import java.awt.color.ColorSpace
4
+ java_import java.awt.geom.AffineTransform
5
+ java_import java.awt.image.BufferedImage
6
+ java_import java.awt.image.ByteLookupTable
7
+ java_import java.awt.image.ColorConvertOp
8
+ java_import java.awt.image.LookupOp
9
+ java_import java.awt.image.RescaleOp
10
+ java_import java.io.ByteArrayInputStream
11
+ java_import java.io.ByteArrayOutputStream
12
+ java_import javax.imageio.ImageIO
13
+ java_import javax.swing.JFrame
14
+
15
+ NEGATIVE_OP = LookupOp.new(ByteLookupTable.new(0, (0...254).to_a.reverse.to_java(:byte)), nil)
16
+ GREY_OP = ColorConvertOp.new(ColorSpace.getInstance(ColorSpace::CS_GRAY), nil)
17
+ ARGB = BufferedImage::TYPE_INT_ARGB
18
+ RGB = BufferedImage::TYPE_INT_RGB
19
+ SCALE_SMOOTH = java.awt.Image::SCALE_SMOOTH
20
+
21
+ #
22
+ # AWT-only (experimental)
23
+ # Add a border to the image and yield/return a new image. The following
24
+ # options are supported:
25
+ # - width: How thick is the border (default: 3)
26
+ # - color: Which color is the border (in rrggbb hex value)
27
+ # - style: etched, raised, plain (default: plain)
28
+ #
29
+ def add_border(options = {})
30
+ border_width = options[:width].to_i || 2
31
+ color = hex_to_color(options[:color]) || hex_to_color("000000")
32
+ style = options[:style]
33
+ style = nil if style.to_sym == :plain
34
+ new_width, new_height = width + 2*border_width, height + 2*border_width
35
+ target = paint(BufferedImage.new(new_width, new_height, color_type)) do |g|
36
+ g.color = color
37
+ if style
38
+ raised = style.to_sym == :raised ? true : false
39
+ g.fill3DRect(0, 0, new_width, new_height, raised)
40
+ else
41
+ g.fill_rect(0, 0, new_width, new_height)
42
+ end
43
+ g.draw_image(@src, nil, border_width, border_width)
44
+ end
45
+ block_given? ? yield(target) : target
46
+ end
47
+
48
+ def adjust_brightness_impl(scale, offset)
49
+ transform(RescaleOp.new(scale, offset, nil))
50
+ end
51
+
52
+ # AWT-only
53
+ def alpha_impl(rgb)
54
+ color = hex_to_color(rgb)
55
+ target = paint(BufferedImage.new(width, height, ARGB)) do |g|
56
+ g.set_composite(java.awt.AlphaComposite::Src)
57
+ g.draw_image(@src, nil, 0, 0)
58
+ 0.upto(height-1) do |i|
59
+ 0.upto(width-1) do |j|
60
+ target.setRGB(j, i, 0x8F1C1C) if target.getRGB(j, i) == color.getRGB
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ def bytes_impl(format)
67
+ out = ByteArrayOutputStream.new
68
+ ImageIO.write(@src, format, out)
69
+ out.to_byte_array
70
+ end
71
+
72
+ def flip_horizontally_impl
73
+ paint {|g| g.draw_image @src, 0, 0, width, height, width, 0, 0, height, nil}
74
+ end
75
+
76
+ def flip_vertically_impl
77
+ paint {|g| g.draw_image @src, 0, 0, width, height, 0, height, width, 0, nil}
78
+ end
79
+
80
+ def greyscale_impl
81
+ transform(GREY_OP)
82
+ end
83
+
84
+ def negative_impl
85
+ transform(NEGATIVE_OP)
86
+ end
87
+
88
+ def resize_impl(width, height)
89
+ paint(BufferedImage.new(width, height, color_type)) do |g|
90
+ scaled_image = @src.get_scaled_instance width, height, SCALE_SMOOTH
91
+ g.draw_image scaled_image, 0, 0, nil
92
+ end
93
+ end
94
+
95
+ #
96
+ # Save using the format string (jpg, gif, etc..) to the open Java File
97
+ # instance passed in.
98
+ #
99
+ def save_impl(format, file)
100
+ ImageIO.write(@src, format, file)
101
+ end
102
+
103
+ def with_crop_impl(left, top, right, bottom)
104
+ ImageVoodoo.new @src.get_subimage(left, top, right-left, bottom-top)
105
+ end
106
+
107
+ #
108
+ # A simple swing wrapper around an image voodoo object.
109
+ #
110
+ class JImagePanel < javax.swing.JPanel
111
+ def initialize(image, x=0, y=0)
112
+ super()
113
+ @image, @x, @y = image, x, y
114
+ end
115
+
116
+ def image=(image)
117
+ @image = image
118
+ invalidate
119
+ end
120
+
121
+ def getPreferredSize
122
+ java.awt.Dimension.new(@image.width, @image.height)
123
+ end
124
+
125
+ def paintComponent(graphics)
126
+ graphics.draw_image(@image.to_java, @x, @y, nil)
127
+ end
128
+ end
129
+
130
+ # Internal class for closing preview window
131
+ class WindowClosed
132
+ def initialize(block = nil)
133
+ @block = block || proc { java.lang.System.exit(0) }
134
+ end
135
+ def method_missing(meth,*args); end
136
+ def windowClosing(event); @block.call; end
137
+ end
138
+
139
+ #
140
+ # Creates a viewable frame displaying current image within it.
141
+ #
142
+ def preview(&block)
143
+ frame = JFrame.new("Preview")
144
+ frame.add_window_listener WindowClosed.new(block)
145
+ frame.set_bounds 0, 0, width + 20, height + 40
146
+ frame.add JImagePanel.new(self, 10, 10)
147
+ frame.visible = true
148
+ end
149
+
150
+ #
151
+ # TODO: Figure out how to determine whether source has alpha or not
152
+ # Experimental: Read an image from the url source and yield/return that
153
+ # image.
154
+ #
155
+ def self.from_url(source)
156
+ url = java.net.URL.new(source)
157
+ image = java.awt.Toolkit.default_toolkit.create_image(url)
158
+ tracker = java.awt.MediaTracker.new(java.awt.Label.new(""))
159
+ tracker.addImage(image, 0);
160
+ tracker.waitForID(0)
161
+ target = paint(BufferedImage.new(image.width, image.height, RGB)) do |g|
162
+ g.draw_image image, 0, 0, nil
163
+ end
164
+ block_given? ? yield(target) : target
165
+ rescue java.io.IOException, java.net.MalformedURLException
166
+ raise ArgumentError.new "Trouble retrieving image: #{$!.message}"
167
+ end
168
+
169
+ def self.with_image_impl(file)
170
+ buffered_image = ImageIO.read(file)
171
+ buffered_image ? ImageVoodoo.new(buffered_image) : nil
172
+ end
173
+
174
+ def self.with_bytes_impl(bytes)
175
+ ImageVoodoo.new ImageIO.read(ByteArrayInputStream.new(bytes))
176
+ end
177
+
178
+ private
179
+
180
+ #
181
+ # Converts a RGB hex value into a java.awt.Color object or dies trying
182
+ # with an ArgumentError.
183
+ #
184
+ def hex_to_color(rgb)
185
+ raise ArgumentError.new "hex rrggbb needed" if rgb !~ /[[:xdigit:]]{6,6}/
186
+
187
+ java.awt.Color.new(rgb[0,2].to_i(16), rgb[2,2].to_i(16), rgb[4,2].to_i(16))
188
+ end
189
+
190
+ #
191
+ # Determines the best colorspace for a new image based on whether the
192
+ # existing image contains an alpha channel or not.
193
+ #
194
+ def color_type
195
+ @src.color_model.has_alpha ? ARGB : RGB
196
+ end
197
+
198
+ #
199
+ # Make a duplicate of the underlying Java src image
200
+ #
201
+ def dup_src
202
+ BufferedImage.new width, height, color_type
203
+ end
204
+
205
+ #
206
+ # Do simple AWT operation transformation to target.
207
+ #
208
+ def transform(operation, target=dup_src)
209
+ paint(target) do |g|
210
+ g.draw_image(@src, 0, 0, nil)
211
+ g.draw_image(operation.filter(target, nil), 0, 0, nil)
212
+ end
213
+ end
214
+
215
+ #
216
+ # DRY up drawing setup+teardown
217
+ #
218
+ def paint(src=dup_src)
219
+ yield src.graphics
220
+ src.graphics.dispose
221
+ ImageVoodoo.new src
222
+ end
223
+ end
@@ -0,0 +1,53 @@
1
+ class ImageVoodoo
2
+ java_import com.google.appengine.api.images.Image
3
+ java_import com.google.appengine.api.images.ImagesService
4
+ java_import com.google.appengine.api.images.ImagesServiceFactory
5
+ java_import com.google.appengine.api.images.Transform
6
+
7
+ ImageService = ImagesServiceFactory.images_service
8
+
9
+ # Value Add methods for this backend
10
+
11
+ def i_am_feeling_lucky
12
+ transform(ImagesServiceFactory.make_im_feeling_lucky)
13
+ end
14
+
15
+ # Implementations of standard features
16
+
17
+ def flip_horizontally_impl
18
+ transform(ImagesServiceFactory.make_horizontal_flip)
19
+ end
20
+
21
+ def flip_vertically_impl
22
+ transform(ImagesServiceFactory.make_vertical_flip)
23
+ end
24
+
25
+ def resize_impl(width, height)
26
+ transform(ImagesServiceFactory.make_resize(width, height))
27
+ end
28
+
29
+ def with_crop_impl(left, top, right, bottom)
30
+ transform(ImagesServiceFactory.make_crop(left, top, right, bottom))
31
+ end
32
+
33
+ def self.with_bytes_impl(bytes)
34
+ ImageVoodoo.new ImageServicesFactory.make_image(bytes)
35
+ end
36
+
37
+ private
38
+
39
+ def from_java_bytes
40
+ String.from_java_bytes @src.image_data
41
+ end
42
+
43
+ #
44
+ # Make a duplicate of the underlying src image
45
+ #
46
+ def dup_src
47
+ ImageServicesFactory.make_image(from_java_bytes)
48
+ end
49
+
50
+ def transform(transform, target=dup_src)
51
+ ImageService.apply_transform(transform, target)
52
+ end
53
+ end
@@ -1,3 +1,3 @@
1
1
  class ImageVoodoo
2
- VERSION = "0.5"
2
+ VERSION = "0.6"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: image_voodoo
3
3
  version: !ruby/object:Gem::Version
4
- version: "0.5"
4
+ version: "0.6"
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas Enebo, Charles Nutter and JRuby contributors
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-04-13 00:00:00 -05:00
12
+ date: 2009-06-17 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -31,6 +31,8 @@ files:
31
31
  - LICENSE.txt
32
32
  - lib/image_science.rb
33
33
  - lib/image_voodoo
34
+ - lib/image_voodoo/awt.rb
35
+ - lib/image_voodoo/gae.rb
34
36
  - lib/image_voodoo/version.rb
35
37
  - lib/image_voodoo.rb
36
38
  - samples/bench.rb