image_voodoo 0.8.8 → 0.8.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +115 -0
- data/.travis.yml +7 -0
- data/Rakefile +2 -2
- data/bin/image_voodoo +71 -84
- data/image_voodoo.gemspec +6 -5
- data/lib/image_science.rb +1 -1
- data/lib/image_voodoo.rb +41 -93
- data/lib/image_voodoo/awt.rb +156 -153
- data/lib/image_voodoo/awt/core_ext/buffered_image.rb +13 -0
- data/lib/image_voodoo/awt/core_ext/graphics2d.rb +17 -0
- data/lib/image_voodoo/awt/shapes.rb +39 -3
- data/lib/image_voodoo/gae.rb +10 -6
- data/lib/image_voodoo/metadata.rb +115 -89
- data/lib/image_voodoo/needs_head.rb +1 -0
- data/lib/image_voodoo/version.rb +1 -1
- data/samples/bench.rb +29 -36
- data/samples/file_view.rb +2 -1
- data/samples/{in-memory.rb → in_memory.rb} +0 -0
- data/test/test_image_science.rb +32 -74
- data/test/test_image_voodoo.rb +82 -0
- data/test/test_metadata.rb +23 -16
- data/test/test_shapes.rb +21 -0
- data/tools/gen.rb +31 -27
- metadata +15 -8
data/lib/image_voodoo/awt.rb
CHANGED
@@ -1,18 +1,29 @@
|
|
1
|
+
require 'image_voodoo/awt/core_ext/buffered_image'
|
2
|
+
require 'image_voodoo/awt/core_ext/graphics2d'
|
1
3
|
require 'image_voodoo/awt/shapes'
|
2
4
|
|
5
|
+
# AWT Implementation
|
3
6
|
class ImageVoodoo
|
4
7
|
include ImageVoodoo::Shapes
|
5
8
|
|
9
|
+
java_import java.awt.AlphaComposite
|
10
|
+
java_import java.awt.Color
|
11
|
+
java_import java.awt.Label
|
12
|
+
java_import java.awt.MediaTracker
|
6
13
|
java_import java.awt.RenderingHints
|
14
|
+
java_import java.awt.Toolkit
|
7
15
|
java_import java.awt.color.ColorSpace
|
16
|
+
java_import java.awt.event.WindowAdapter
|
8
17
|
java_import java.awt.geom.AffineTransform
|
9
|
-
java_import java.awt.image.BufferedImage
|
10
18
|
java_import java.awt.image.ShortLookupTable
|
11
19
|
java_import java.awt.image.ColorConvertOp
|
12
20
|
java_import java.awt.image.LookupOp
|
13
21
|
java_import java.awt.image.RescaleOp
|
14
22
|
java_import java.io.ByteArrayInputStream
|
15
23
|
java_import java.io.ByteArrayOutputStream
|
24
|
+
java_import java.io.IOException
|
25
|
+
java_import java.net.MalformedURLException
|
26
|
+
java_import java.net.URL
|
16
27
|
java_import javax.imageio.ImageIO
|
17
28
|
java_import javax.imageio.IIOImage
|
18
29
|
java_import javax.imageio.ImageWriteParam
|
@@ -20,38 +31,7 @@ class ImageVoodoo
|
|
20
31
|
java_import javax.swing.JFrame
|
21
32
|
java_import javax.imageio.IIOException
|
22
33
|
|
23
|
-
# FIXME: Move and rewrite in terms of new shape
|
24
|
-
##
|
25
|
-
#
|
26
|
-
# *AWT* (experimental) Add a border to the image and yield/return a new
|
27
|
-
# image. The following options are supported:
|
28
|
-
# - width: How thick is the border (default: 3)
|
29
|
-
# - color: Which color is the border (in rrggbb hex value)
|
30
|
-
# - style: etched, raised, plain (default: plain)
|
31
|
-
#
|
32
|
-
def add_border(options = {})
|
33
|
-
border_width = options[:width].to_i || 2
|
34
|
-
color = hex_to_color(options[:color]) || hex_to_color("000000")
|
35
|
-
style = options[:style]
|
36
|
-
style = nil if style.to_sym == :plain
|
37
|
-
new_width, new_height = width + 2*border_width, height + 2*border_width
|
38
|
-
target = paint(BufferedImage.new(new_width, new_height, color_type)) do |g|
|
39
|
-
g.color = color
|
40
|
-
if style
|
41
|
-
raised = style.to_sym == :raised ? true : false
|
42
|
-
g.fill3DRect(0, 0, new_width, new_height, raised)
|
43
|
-
else
|
44
|
-
g.fill_rect(0, 0, new_width, new_height)
|
45
|
-
end
|
46
|
-
g.draw_image(@src, nil, border_width, border_width)
|
47
|
-
end
|
48
|
-
block_given? ? yield(target) : target
|
49
|
-
end
|
50
|
-
|
51
|
-
##
|
52
|
-
#
|
53
34
|
# A simple swing wrapper around an image voodoo object.
|
54
|
-
#
|
55
35
|
class JImagePanel < javax.swing.JPanel
|
56
36
|
def initialize(image, x=0, y=0)
|
57
37
|
super()
|
@@ -75,131 +55,166 @@ class ImageVoodoo
|
|
75
55
|
ImageVoodoo::JImagePanel.__persistent__ = true
|
76
56
|
|
77
57
|
# Internal class for closing preview window
|
78
|
-
class WindowClosed
|
58
|
+
class WindowClosed < WindowAdapter
|
79
59
|
def initialize(block = nil)
|
80
60
|
@block = block || proc { java.lang.System.exit(0) }
|
61
|
+
super()
|
62
|
+
end
|
63
|
+
|
64
|
+
def windowClosing(_)
|
65
|
+
@block.call
|
81
66
|
end
|
82
|
-
def method_missing(meth,*args); end
|
83
|
-
def windowClosing(event); @block.call; end
|
84
67
|
end
|
85
68
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
69
|
+
# *AWT-only* Return awt Color object.
|
70
|
+
def color_at(x, y)
|
71
|
+
Color.new(pixel(x, y))
|
72
|
+
end
|
73
|
+
|
74
|
+
# *AWT-only* Creates a viewable frame displaying current image within it.
|
90
75
|
def preview(&block)
|
91
|
-
frame = JFrame.new(
|
76
|
+
frame = JFrame.new('Preview')
|
92
77
|
frame.add_window_listener WindowClosed.new(block)
|
93
78
|
frame.set_bounds 0, 0, width + 20, height + 40
|
94
79
|
frame.add JImagePanel.new(self, 10, 10)
|
95
80
|
frame.visible = true
|
96
81
|
end
|
97
82
|
|
98
|
-
|
99
|
-
# *AWT* paint/render to the source
|
100
|
-
#
|
83
|
+
# *AWT-only* paint/render to the source
|
101
84
|
def paint(src=dup_src)
|
102
|
-
yield src.graphics
|
85
|
+
yield src.graphics, src
|
103
86
|
src.graphics.dispose
|
104
87
|
ImageVoodoo.new(@io, src, @format)
|
105
88
|
end
|
106
89
|
|
107
|
-
##
|
108
|
-
#
|
109
90
|
# TODO: Figure out how to determine whether source has alpha or not
|
110
|
-
# Experimental: Read an image from the url source and yield/return that
|
111
|
-
# image.
|
112
|
-
#
|
91
|
+
# Experimental: Read an image from the url source and yield/return that image.
|
113
92
|
def self.from_url(source)
|
114
|
-
|
115
|
-
image = java.awt.Toolkit.default_toolkit.create_image(url)
|
116
|
-
tracker = java.awt.MediaTracker.new(java.awt.Label.new(""))
|
117
|
-
tracker.addImage(image, 0);
|
118
|
-
tracker.waitForID(0)
|
93
|
+
image = image_from_url source
|
119
94
|
target = paint(BufferedImage.new(image.width, image.height, RGB)) do |g|
|
120
95
|
g.draw_image image, 0, 0, nil
|
121
96
|
end
|
122
97
|
block_given? ? yield(target) : target
|
123
|
-
rescue java.io.IOException, java.net.MalformedURLException
|
124
|
-
raise ArgumentError.new "Trouble retrieving image: #{$!.message}"
|
125
98
|
end
|
126
99
|
|
127
|
-
|
128
|
-
|
129
|
-
|
100
|
+
def self.image_from_url(source)
|
101
|
+
image = Toolkit.default_toolkit.create_image(URL.new(source))
|
102
|
+
tracker = MediaTracker.new(Label.new(''))
|
103
|
+
tracker.addImage(image, 0)
|
104
|
+
tracker.waitForID(0)
|
105
|
+
image
|
106
|
+
rescue IOException, MalformedURLException
|
107
|
+
raise ArgumentError, "Trouble retrieving image: #{$!.message}"
|
108
|
+
end
|
109
|
+
|
110
|
+
# *AWT-only* Create an image of width x height filled with a single color.
|
130
111
|
def self.canvas(width, height, rgb='000000')
|
131
112
|
image = ImageVoodoo.new(@io, BufferedImage.new(width, height, ARGB))
|
132
113
|
image.rect(0, 0, width, height, rgb)
|
133
114
|
end
|
134
115
|
|
135
|
-
|
116
|
+
class << self
|
117
|
+
private
|
136
118
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
119
|
+
def detect_format_from_input(input)
|
120
|
+
stream = ImageIO.createImageInputStream(input)
|
121
|
+
readers = ImageIO.getImageReaders(stream)
|
122
|
+
readers.has_next ? readers.next.format_name.upcase : nil
|
123
|
+
end
|
142
124
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
125
|
+
# FIXME: use library to figure this out
|
126
|
+
def determine_image_type_from_ext(ext)
|
127
|
+
case ext
|
128
|
+
when 'jpg' then RGB
|
129
|
+
else ARGB
|
130
|
+
end
|
131
|
+
end
|
148
132
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
133
|
+
def determine_format_from_file_name(file_name)
|
134
|
+
ext = file_name.split('.')[-1]
|
135
|
+
raise ArgumentError, "no extension in file name #{file_name}" unless ext
|
136
|
+
ext
|
137
|
+
end
|
153
138
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
139
|
+
def new_image_impl(width, height, file_name)
|
140
|
+
format = determine_format_from_file_name file_name
|
141
|
+
image_type = determine_image_type_from_ext format
|
142
|
+
buffered_image = BufferedImage.new width, height, image_type
|
143
|
+
ImageVoodoo.new file_name, buffered_image, format
|
144
|
+
end
|
145
|
+
|
146
|
+
def read_image_from_input(input)
|
147
|
+
ImageIO.read(input)
|
148
|
+
rescue IIOException
|
149
|
+
require 'CMYKDemo.jar'
|
150
|
+
jpeg = org.monte.media.jpeg
|
151
|
+
|
152
|
+
cmyk_reader = jpeg.CMYKJPEGImageReader.new jpeg.CMYKJPEGImageReaderSpi.new
|
153
|
+
cmyk_reader.input = ImageIO.createImageInputStream(input)
|
154
|
+
cmyk_reader.read 0
|
155
|
+
end
|
156
|
+
|
157
|
+
def with_bytes_impl(bytes)
|
158
|
+
input_stream = ByteArrayInputStream.new(bytes)
|
159
|
+
format = detect_format_from_input(input_stream)
|
160
|
+
input_stream.reset
|
161
|
+
buffered_image = read_image_from_input(input_stream)
|
162
|
+
input_stream.reset
|
163
|
+
ImageVoodoo.new(input_stream, buffered_image, format)
|
164
|
+
end
|
159
165
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
166
|
+
def with_image_impl(file)
|
167
|
+
format = detect_format_from_input(file)
|
168
|
+
buffered_image = read_image_from_input(file)
|
169
|
+
buffered_image ? ImageVoodoo.new(file, buffered_image, format) : nil
|
170
|
+
end
|
164
171
|
end
|
165
172
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
buffered_image = read_image_from_input(input_stream)
|
171
|
-
input_stream.reset
|
172
|
-
ImageVoodoo.new(input_stream, buffered_image, format)
|
173
|
+
# Save using the format string (jpg, gif, etc..) to the open Java File
|
174
|
+
# instance passed in.
|
175
|
+
def save_impl(format, file)
|
176
|
+
write_new_image format, FileImageOutputStream.new(file)
|
173
177
|
end
|
174
178
|
|
175
|
-
|
179
|
+
private
|
180
|
+
|
176
181
|
# Converts a RGB hex value into a java.awt.Color object or dies trying
|
177
182
|
# with an ArgumentError.
|
178
|
-
|
179
|
-
|
180
|
-
raise ArgumentError
|
181
|
-
|
182
|
-
java.awt.Color.new(rgb[0,2].to_i(16), rgb[2,2].to_i(16), rgb[4,2].to_i(16))
|
183
|
+
def hex_to_color(rgb='00000')
|
184
|
+
rgb = '000000' unless rgb
|
185
|
+
raise ArgumentError, 'hex rrggbb needed' if rgb !~ /[[:xdigit:]]{6,6}/
|
186
|
+
Color.new(rgb[0, 2].to_i(16), rgb[2, 2].to_i(16), rgb[4, 2].to_i(16))
|
183
187
|
end
|
184
188
|
|
185
|
-
|
189
|
+
NEGATIVE_OP = LookupOp.new(ShortLookupTable.new(0, (0...256).to_a.reverse.to_java(:short)), nil)
|
190
|
+
GREY_OP = ColorConvertOp.new(ColorSpace.getInstance(ColorSpace::CS_GRAY), nil)
|
191
|
+
ARGB = BufferedImage::TYPE_INT_ARGB
|
192
|
+
RGB = BufferedImage::TYPE_INT_RGB
|
193
|
+
SCALE_SMOOTH = java.awt.Image::SCALE_SMOOTH
|
194
|
+
|
186
195
|
# Determines the best colorspace for a new image based on whether the
|
187
196
|
# existing image contains an alpha channel or not.
|
188
|
-
#
|
189
197
|
def color_type
|
190
198
|
@src.color_model.has_alpha ? ARGB : RGB
|
191
199
|
end
|
192
200
|
|
193
|
-
#
|
194
201
|
# Make a duplicate of the underlying Java src image
|
195
|
-
#
|
196
202
|
def dup_src
|
197
203
|
BufferedImage.new to_java.color_model, to_java.raster, true, nil
|
198
204
|
end
|
199
205
|
|
200
|
-
|
206
|
+
def src_without_alpha
|
207
|
+
if @src.color_model.has_alpha
|
208
|
+
img = BufferedImage.new(width, height, RGB)
|
209
|
+
img.graphics.draw_image(@src, 0, 0, nil)
|
210
|
+
img.graphics.dispose
|
211
|
+
img
|
212
|
+
else
|
213
|
+
@src
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
201
217
|
# Do simple AWT operation transformation to target.
|
202
|
-
#
|
203
218
|
def transform(operation, target=dup_src)
|
204
219
|
paint(target) do |g|
|
205
220
|
g.draw_image(@src, 0, 0, nil)
|
@@ -212,51 +227,44 @@ class ImageVoodoo
|
|
212
227
|
end
|
213
228
|
|
214
229
|
def alpha_impl(rgb)
|
215
|
-
color = hex_to_color(rgb)
|
216
|
-
|
217
|
-
g.set_composite
|
230
|
+
color = hex_to_color(rgb).getRGB
|
231
|
+
paint(BufferedImage.new(width, height, ARGB)) do |g, target|
|
232
|
+
g.set_composite AlphaComposite::Src
|
218
233
|
g.draw_image(@src, nil, 0, 0)
|
219
|
-
|
220
|
-
|
221
|
-
target.setRGB(j, i, 0x8F1C1C) if target.getRGB(j, i) == color.getRGB
|
222
|
-
end
|
234
|
+
target.each do |i, j|
|
235
|
+
target.setRGB(i, j, 0x8F1C1C) if target.getRGB(i, j) == color
|
223
236
|
end
|
224
237
|
end
|
225
238
|
end
|
226
239
|
|
227
240
|
def bytes_impl(format)
|
228
|
-
|
229
|
-
|
230
|
-
|
241
|
+
ByteArrayOutputStream.new.tap do |out|
|
242
|
+
write_new_image format, ImageIO.create_image_output_stream(out)
|
243
|
+
end.to_byte_array
|
231
244
|
end
|
232
245
|
|
233
246
|
def correct_orientation_impl
|
234
247
|
case metadata.orientation
|
235
|
-
when 2 then
|
236
|
-
|
237
|
-
when
|
238
|
-
|
239
|
-
when
|
240
|
-
|
241
|
-
|
242
|
-
flip_horizontally
|
243
|
-
rotate 270
|
244
|
-
when 6 then
|
245
|
-
rotate 90
|
246
|
-
when 7 then
|
247
|
-
flip_horizontally
|
248
|
-
rotate 270
|
249
|
-
else
|
250
|
-
self
|
248
|
+
when 2 then flip_horizontally
|
249
|
+
when 3 then rotate(180)
|
250
|
+
when 4 then flip_vertically
|
251
|
+
when 5 then flip_horizontally && rotate(270)
|
252
|
+
when 6 then rotate(90)
|
253
|
+
when 7 then flip_horizontally && rotate(270)
|
254
|
+
else self
|
251
255
|
end
|
252
256
|
end
|
253
257
|
|
254
258
|
def flip_horizontally_impl
|
255
|
-
paint
|
259
|
+
paint do |g|
|
260
|
+
g.draw_image @src, 0, 0, width, height, width, 0, 0, height, nil
|
261
|
+
end
|
256
262
|
end
|
257
263
|
|
258
264
|
def flip_vertically_impl
|
259
|
-
paint
|
265
|
+
paint do |g|
|
266
|
+
g.draw_image @src, 0, 0, width, height, 0, height, width, 0, nil
|
267
|
+
end
|
260
268
|
end
|
261
269
|
|
262
270
|
def greyscale_impl
|
@@ -265,7 +273,7 @@ class ImageVoodoo
|
|
265
273
|
|
266
274
|
def metadata_impl
|
267
275
|
require 'image_voodoo/metadata'
|
268
|
-
|
276
|
+
|
269
277
|
@metadata ||= ImageVoodoo::Metadata.new(@io)
|
270
278
|
end
|
271
279
|
|
@@ -274,33 +282,27 @@ class ImageVoodoo
|
|
274
282
|
end
|
275
283
|
|
276
284
|
def resize_impl(width, height)
|
277
|
-
|
278
|
-
|
279
|
-
g.draw_image scaled_image, 0, 0, nil
|
285
|
+
paint_new_buffered_image(width, height) do |g|
|
286
|
+
g.draw_this_image(@src.get_scaled_instance(width, height, SCALE_SMOOTH))
|
280
287
|
end
|
281
288
|
end
|
282
289
|
|
283
|
-
def rotate_impl(
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
paint(BufferedImage.new(new_width, new_height, color_type)) do |g|
|
290
|
-
|
291
|
-
g.java_send :translate, [::Java::int, ::Java::int],
|
292
|
-
(new_width - width)/2, (new_height - height)/2
|
293
|
-
g.rotate(radians, width / 2, height / 2);
|
294
|
-
g.draw_image @src, 0, 0, nil
|
290
|
+
def rotate_impl(radians)
|
291
|
+
new_width, new_height = rotate_new_dimensions(radians)
|
292
|
+
paint_new_buffered_image(new_width, new_height) do |g|
|
293
|
+
g.jtranslate (new_width - width) / 2, (new_height - height) / 2
|
294
|
+
g.rotate radians, width / 2, height / 2
|
295
|
+
g.draw_this_image @src
|
295
296
|
end
|
296
297
|
end
|
297
298
|
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
def
|
303
|
-
|
299
|
+
def paint_new_buffered_image(width, height, color = color_type, &block)
|
300
|
+
paint BufferedImage.new(width, height, color), &block
|
301
|
+
end
|
302
|
+
|
303
|
+
def rotate_new_dimensions(radians)
|
304
|
+
sin, cos = Math.sin(radians).abs, Math.cos(radians).abs
|
305
|
+
[(width * cos + height * sin).floor, (width * sin + height * cos).floor]
|
304
306
|
end
|
305
307
|
|
306
308
|
def with_crop_impl(left, top, right, bottom)
|
@@ -318,6 +320,7 @@ class ImageVoodoo
|
|
318
320
|
param.compression_quality = @quality
|
319
321
|
end
|
320
322
|
|
321
|
-
|
323
|
+
src = format.downcase == 'jpg' ? src_without_alpha : @src
|
324
|
+
writer.write nil, IIOImage.new(src, nil, nil), param
|
322
325
|
end
|
323
326
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'java'
|
2
|
+
|
3
|
+
# Hide in ImageVoodoo so awt.rb can see this and we will not polute global
|
4
|
+
class ImageVoodoo
|
5
|
+
java_import java.awt.image.BufferedImage
|
6
|
+
|
7
|
+
# Re-open to add convenience methods.
|
8
|
+
class BufferedImage
|
9
|
+
def each
|
10
|
+
height.times { |j| width.times { |i| yield i, j } }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|