image_voodoo 0.8.8 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,18 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'image_voodoo/awt/core_ext/buffered_image'
4
+ require 'image_voodoo/awt/core_ext/graphics2d'
1
5
  require 'image_voodoo/awt/shapes'
2
6
 
7
+ # AWT Implementation
3
8
  class ImageVoodoo
4
9
  include ImageVoodoo::Shapes
5
10
 
11
+ java_import java.awt.AlphaComposite
12
+ java_import java.awt.Color
13
+ java_import java.awt.Label
14
+ java_import java.awt.MediaTracker
6
15
  java_import java.awt.RenderingHints
16
+ java_import java.awt.Toolkit
7
17
  java_import java.awt.color.ColorSpace
18
+ java_import java.awt.event.WindowAdapter
8
19
  java_import java.awt.geom.AffineTransform
9
- java_import java.awt.image.BufferedImage
10
20
  java_import java.awt.image.ShortLookupTable
11
21
  java_import java.awt.image.ColorConvertOp
12
22
  java_import java.awt.image.LookupOp
13
23
  java_import java.awt.image.RescaleOp
14
24
  java_import java.io.ByteArrayInputStream
15
25
  java_import java.io.ByteArrayOutputStream
26
+ java_import java.io.IOException
27
+ java_import java.net.MalformedURLException
28
+ java_import java.net.URL
16
29
  java_import javax.imageio.ImageIO
17
30
  java_import javax.imageio.IIOImage
18
31
  java_import javax.imageio.ImageWriteParam
@@ -20,38 +33,7 @@ class ImageVoodoo
20
33
  java_import javax.swing.JFrame
21
34
  java_import javax.imageio.IIOException
22
35
 
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
36
  # A simple swing wrapper around an image voodoo object.
54
- #
55
37
  class JImagePanel < javax.swing.JPanel
56
38
  def initialize(image, x=0, y=0)
57
39
  super()
@@ -75,131 +57,170 @@ class ImageVoodoo
75
57
  ImageVoodoo::JImagePanel.__persistent__ = true
76
58
 
77
59
  # Internal class for closing preview window
78
- class WindowClosed
60
+ class WindowClosed < WindowAdapter
79
61
  def initialize(block = nil)
80
62
  @block = block || proc { java.lang.System.exit(0) }
63
+ super()
64
+ end
65
+
66
+ def windowClosing(_)
67
+ @block.call
81
68
  end
82
- def method_missing(meth,*args); end
83
- def windowClosing(event); @block.call; end
84
69
  end
85
70
 
86
- ##
87
- #
88
- # *AWT* Creates a viewable frame displaying current image within it.
89
- #
71
+ # *AWT-only* Return awt Color object.
72
+ def color_at(x, y)
73
+ Color.new(pixel(x, y))
74
+ end
75
+
76
+ # *AWT-only* Creates a viewable frame displaying current image within it.
90
77
  def preview(&block)
91
- frame = JFrame.new("Preview")
78
+ frame = JFrame.new('Preview')
92
79
  frame.add_window_listener WindowClosed.new(block)
93
80
  frame.set_bounds 0, 0, width + 20, height + 40
94
81
  frame.add JImagePanel.new(self, 10, 10)
95
82
  frame.visible = true
96
83
  end
97
84
 
98
- ##
99
- # *AWT* paint/render to the source
100
- #
85
+ # *AWT-only* paint/render to the source
101
86
  def paint(src=dup_src)
102
- yield src.graphics
87
+ yield src.graphics, src
103
88
  src.graphics.dispose
104
89
  ImageVoodoo.new(@io, src, @format)
105
90
  end
106
91
 
107
- ##
108
- #
109
92
  # 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
- #
93
+ # Experimental: Read an image from the url source and yield/return that image.
113
94
  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)
95
+ image = image_from_url source
119
96
  target = paint(BufferedImage.new(image.width, image.height, RGB)) do |g|
120
97
  g.draw_image image, 0, 0, nil
121
98
  end
122
99
  block_given? ? yield(target) : target
123
- rescue java.io.IOException, java.net.MalformedURLException
124
- raise ArgumentError.new "Trouble retrieving image: #{$!.message}"
125
100
  end
126
101
 
127
- ##
128
- # *AWT* Create an image of width x height filled with a single color.
129
- #
102
+ def self.image_from_url(source)
103
+ image = Toolkit.default_toolkit.create_image(URL.new(source))
104
+ tracker = MediaTracker.new(Label.new(''))
105
+ tracker.addImage(image, 0)
106
+ tracker.waitForID(0)
107
+ image
108
+ rescue IOException, MalformedURLException
109
+ raise ArgumentError, "Trouble retrieving image: #{$!.message}"
110
+ end
111
+
112
+ # *AWT-only* Create an image of width x height filled with a single color.
130
113
  def self.canvas(width, height, rgb='000000')
131
114
  image = ImageVoodoo.new(@io, BufferedImage.new(width, height, ARGB))
132
115
  image.rect(0, 0, width, height, rgb)
133
116
  end
134
117
 
135
- private
118
+ class << self
119
+ private
136
120
 
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
121
+ def detect_format_from_input(input)
122
+ stream = ImageIO.createImageInputStream(input)
123
+ readers = ImageIO.getImageReaders(stream)
124
+ readers.has_next ? readers.next.format_name.upcase : nil
125
+ end
142
126
 
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
127
+ # FIXME: use library to figure this out
128
+ def determine_image_type_from_ext(ext)
129
+ case ext
130
+ when 'jpg' then RGB
131
+ else ARGB
132
+ end
133
+ end
148
134
 
149
- def self.read_image_from_input(input)
150
- ImageIO.read(input)
151
- rescue IIOException
152
- require 'CMYKDemo.jar'
135
+ def determine_format_from_file_name(file_name)
136
+ ext = file_name.split('.')[-1]
153
137
 
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
138
+ raise ArgumentError, "no extension in file name #{file_name}" unless ext
139
+
140
+ ext
141
+ end
142
+
143
+ def new_image_impl(width, height, file_name)
144
+ format = determine_format_from_file_name file_name
145
+ image_type = determine_image_type_from_ext format
146
+ buffered_image = BufferedImage.new width, height, image_type
147
+ ImageVoodoo.new file_name, buffered_image, format
148
+ end
149
+
150
+ def read_image_from_input(input)
151
+ ImageIO.read(input)
152
+ rescue IIOException
153
+ require 'CMYKDemo.jar'
154
+ jpeg = org.monte.media.jpeg
155
+
156
+ cmyk_reader = jpeg.CMYKJPEGImageReader.new jpeg.CMYKJPEGImageReaderSpi.new
157
+ cmyk_reader.input = ImageIO.createImageInputStream(input)
158
+ cmyk_reader.read 0
159
+ end
159
160
 
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
161
+ def with_bytes_impl(bytes)
162
+ input_stream = ByteArrayInputStream.new(bytes)
163
+ format = detect_format_from_input(input_stream)
164
+ input_stream.reset
165
+ buffered_image = read_image_from_input(input_stream)
166
+ input_stream.reset
167
+ ImageVoodoo.new(input_stream, buffered_image, format)
168
+ end
169
+
170
+ def with_image_impl(file)
171
+ format = detect_format_from_input(file)
172
+ buffered_image = read_image_from_input(file)
173
+ buffered_image ? ImageVoodoo.new(file, buffered_image, format) : nil
174
+ end
164
175
  end
165
176
 
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)
177
+ # Save using the format string (jpg, gif, etc..) to the open Java File
178
+ # instance passed in.
179
+ def save_impl(format, file)
180
+ write_new_image format, FileImageOutputStream.new(file)
173
181
  end
174
182
 
175
- #
183
+ private
184
+
176
185
  # Converts a RGB hex value into a java.awt.Color object or dies trying
177
186
  # with an ArgumentError.
178
- #
179
- def self.hex_to_color(rgb)
180
- raise ArgumentError.new "hex rrggbb needed" if rgb !~ /[[:xdigit:]]{6,6}/
187
+ def hex_to_color(rgb='000000')
188
+ rgb ||= '000000'
189
+
190
+ raise ArgumentError, 'hex rrggbb needed' if rgb !~ /[[:xdigit:]]{6,6}/
181
191
 
182
- java.awt.Color.new(rgb[0,2].to_i(16), rgb[2,2].to_i(16), rgb[4,2].to_i(16))
192
+ Color.new(rgb[0, 2].to_i(16), rgb[2, 2].to_i(16), rgb[4, 2].to_i(16))
183
193
  end
184
194
 
185
- #
195
+ NEGATIVE_OP = LookupOp.new(ShortLookupTable.new(0, (0...256).to_a.reverse.to_java(:short)), nil)
196
+ GREY_OP = ColorConvertOp.new(ColorSpace.getInstance(ColorSpace::CS_GRAY), nil)
197
+ ARGB = BufferedImage::TYPE_INT_ARGB
198
+ RGB = BufferedImage::TYPE_INT_RGB
199
+ SCALE_SMOOTH = java.awt.Image::SCALE_SMOOTH
200
+
186
201
  # Determines the best colorspace for a new image based on whether the
187
202
  # existing image contains an alpha channel or not.
188
- #
189
203
  def color_type
190
204
  @src.color_model.has_alpha ? ARGB : RGB
191
205
  end
192
206
 
193
- #
194
207
  # Make a duplicate of the underlying Java src image
195
- #
196
208
  def dup_src
197
209
  BufferedImage.new to_java.color_model, to_java.raster, true, nil
198
210
  end
199
211
 
200
- #
212
+ def src_without_alpha
213
+ if @src.color_model.has_alpha
214
+ img = BufferedImage.new(width, height, RGB)
215
+ img.graphics.draw_image(@src, 0, 0, nil)
216
+ img.graphics.dispose
217
+ img
218
+ else
219
+ @src
220
+ end
221
+ end
222
+
201
223
  # Do simple AWT operation transformation to target.
202
- #
203
224
  def transform(operation, target=dup_src)
204
225
  paint(target) do |g|
205
226
  g.draw_image(@src, 0, 0, nil)
@@ -212,51 +233,45 @@ class ImageVoodoo
212
233
  end
213
234
 
214
235
  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)
236
+ color = hex_to_color(rgb).getRGB
237
+ paint(BufferedImage.new(width, height, ARGB)) do |g, target|
238
+ g.set_composite AlphaComposite::Src
218
239
  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
240
+ target.each do |i, j|
241
+ target.setRGB(i, j, 0x8F1C1C) if target.getRGB(i, j) == color
223
242
  end
224
243
  end
225
244
  end
226
245
 
227
246
  def bytes_impl(format)
228
- out = ByteArrayOutputStream.new
229
- write_new_image format, ImageIO.create_image_output_stream(out)
230
- out.to_byte_array
247
+ ByteArrayOutputStream.new.tap do |out|
248
+ write_new_image format, ImageIO.create_image_output_stream(out)
249
+ end.to_byte_array
231
250
  end
232
251
 
233
252
  def correct_orientation_impl
234
253
  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
254
+ when 2 then flip_horizontally
255
+ when 3 then rotate(180)
256
+ when 4 then flip_vertically
257
+ when 5 then flip_horizontally && rotate(90)
258
+ when 6 then rotate(90)
259
+ when 7 then flip_horizontally && rotate(270)
260
+ when 8 then rotate(270)
261
+ else self
251
262
  end
252
263
  end
253
264
 
254
265
  def flip_horizontally_impl
255
- paint {|g| g.draw_image @src, 0, 0, width, height, width, 0, 0, height, nil}
266
+ paint do |g|
267
+ g.draw_image @src, 0, 0, width, height, width, 0, 0, height, nil
268
+ end
256
269
  end
257
270
 
258
271
  def flip_vertically_impl
259
- paint {|g| g.draw_image @src, 0, 0, width, height, 0, height, width, 0, nil}
272
+ paint do |g|
273
+ g.draw_image @src, 0, 0, width, height, 0, height, width, 0, nil
274
+ end
260
275
  end
261
276
 
262
277
  def greyscale_impl
@@ -265,7 +280,7 @@ class ImageVoodoo
265
280
 
266
281
  def metadata_impl
267
282
  require 'image_voodoo/metadata'
268
-
283
+
269
284
  @metadata ||= ImageVoodoo::Metadata.new(@io)
270
285
  end
271
286
 
@@ -274,33 +289,27 @@ class ImageVoodoo
274
289
  end
275
290
 
276
291
  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
292
+ paint_new_buffered_image(width, height) do |g|
293
+ g.draw_this_image(@src.get_scaled_instance(width, height, SCALE_SMOOTH))
280
294
  end
281
295
  end
282
296
 
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
297
+ def rotate_impl(radians)
298
+ new_width, new_height = rotate_new_dimensions(radians)
299
+ paint_new_buffered_image(new_width, new_height) do |g|
300
+ g.translate (new_width - width) / 2, (new_height - height) / 2
301
+ g.rotate radians, width / 2, height / 2
302
+ g.draw_this_image @src
295
303
  end
296
304
  end
297
305
 
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)
306
+ def paint_new_buffered_image(width, height, color = color_type, &block)
307
+ paint BufferedImage.new(width, height, color), &block
308
+ end
309
+
310
+ def rotate_new_dimensions(radians)
311
+ sin, cos = Math.sin(radians).abs, Math.cos(radians).abs
312
+ [(width * cos + height * sin).floor, (width * sin + height * cos).floor]
304
313
  end
305
314
 
306
315
  def with_crop_impl(left, top, right, bottom)
@@ -318,6 +327,7 @@ class ImageVoodoo
318
327
  param.compression_quality = @quality
319
328
  end
320
329
 
321
- writer.write nil, IIOImage.new(@src, nil, nil), param
330
+ src = format.downcase == 'jpg' ? src_without_alpha : @src
331
+ writer.write nil, IIOImage.new(src, nil, nil), param
322
332
  end
323
333
  end
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Google App Engine implementation (does this work?)
1
4
  class ImageVoodoo
2
5
  java_import com.google.appengine.api.images.Image
3
6
  java_import com.google.appengine.api.images.ImagesService
@@ -20,12 +23,20 @@ class ImageVoodoo
20
23
  # Implementations of standard features
21
24
  #++
22
25
 
26
+ class << self
27
+ private
28
+
29
+ def with_bytes_impl(bytes)
30
+ image = ImageServicesFactory.make_image(bytes)
31
+ ImageVoodoo.new bytes, image, image.format.to_s.upcase
32
+ end
33
+ end
34
+
23
35
  private
24
36
 
25
37
  def flip_horizontally_impl
26
38
  transform(ImagesServiceFactory.make_horizontal_flip)
27
39
  end
28
- private :flip_horizontally_impl
29
40
 
30
41
  def flip_vertically_impl
31
42
  transform(ImagesServiceFactory.make_vertical_flip)
@@ -39,11 +50,6 @@ class ImageVoodoo
39
50
  transform(ImagesServiceFactory.make_crop(left, top, right, bottom))
40
51
  end
41
52
 
42
- def self.with_bytes_impl(bytes)
43
- image = ImageServicesFactory.make_image(bytes)
44
- ImageVoodoo.new bytes, image, image.format.to_s.upcase
45
- end
46
-
47
53
  def from_java_bytes
48
54
  String.from_java_bytes @src.image_data
49
55
  end