image_voodoo 0.8.8 → 0.9.2

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,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