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.
@@ -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
- # *AWT* Creates a viewable frame displaying current image within it.
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("Preview")
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
- url = java.net.URL.new(source)
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
- # *AWT* Create an image of width x height filled with a single color.
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
- private
116
+ class << self
117
+ private
136
118
 
137
- NEGATIVE_OP = LookupOp.new(ShortLookupTable.new(0, (0...256).to_a.reverse.to_java(:short)), nil)
138
- GREY_OP = ColorConvertOp.new(ColorSpace.getInstance(ColorSpace::CS_GRAY), nil)
139
- ARGB = BufferedImage::TYPE_INT_ARGB
140
- RGB = BufferedImage::TYPE_INT_RGB
141
- SCALE_SMOOTH = java.awt.Image::SCALE_SMOOTH
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
- def self.with_image_impl(file)
144
- format = detect_format_from_input(file)
145
- buffered_image = read_image_from_input(file)
146
- buffered_image ? ImageVoodoo.new(file, buffered_image, format) : nil
147
- end
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
- def self.read_image_from_input(input)
150
- ImageIO.read(input)
151
- rescue IIOException
152
- require 'CMYKDemo.jar'
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
- cmyk_spi = org.monte.media.jpeg.CMYKJPEGImageReaderSpi.new
155
- cmyk_reader = org.monte.media.jpeg.CMYKJPEGImageReader.new cmyk_spi
156
- cmyk_reader.input = ImageIO.createImageInputStream(input)
157
- cmyk_reader.read 0
158
- end
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
- def self.detect_format_from_input(input)
161
- stream = ImageIO.createImageInputStream(input)
162
- readers = ImageIO.getImageReaders(stream)
163
- readers.has_next ? readers.next.format_name.upcase : nil
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
- def self.with_bytes_impl(bytes)
167
- input_stream = ByteArrayInputStream.new(bytes)
168
- format = detect_format_from_input(input_stream)
169
- input_stream.reset
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
- def self.hex_to_color(rgb)
180
- raise ArgumentError.new "hex rrggbb needed" if rgb !~ /[[:xdigit:]]{6,6}/
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
- target = paint(BufferedImage.new(width, height, ARGB)) do |g|
217
- g.set_composite(java.awt.AlphaComposite::Src)
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
- 0.upto(height-1) do |i|
220
- 0.upto(width-1) do |j|
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
- out = ByteArrayOutputStream.new
229
- write_new_image format, ImageIO.create_image_output_stream(out)
230
- out.to_byte_array
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
- flip_horizontally
237
- when 3 then
238
- rotate 180
239
- when 4 then
240
- flip_vertically
241
- when 5 then
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 {|g| g.draw_image @src, 0, 0, width, height, width, 0, 0, height, nil}
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 {|g| g.draw_image @src, 0, 0, width, height, 0, height, width, 0, nil}
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
- paint(BufferedImage.new(width, height, color_type)) do |g|
278
- scaled_image = @src.get_scaled_instance width, height, SCALE_SMOOTH
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(degrees)
284
- radians = degrees * Math::PI / 180
285
- sin, cos = Math.sin(radians).abs, Math.cos(radians).abs
286
- new_width = (width * cos + height * sin).floor
287
- new_height = (width * sin + height * cos).floor
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
- # Save using the format string (jpg, gif, etc..) to the open Java File
300
- # instance passed in.
301
- #
302
- def save_impl(format, file)
303
- write_new_image format, FileImageOutputStream.new(file)
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
- writer.write nil, IIOImage.new(@src, nil, nil), param
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