axon 0.1.1 → 0.2.0

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.
@@ -0,0 +1,164 @@
1
+ package axon;
2
+
3
+ import java.awt.Rectangle;
4
+ import java.awt.image.BufferedImage;
5
+ import java.awt.image.DataBufferByte;
6
+ import java.awt.image.Raster;
7
+ import java.io.IOException;
8
+ import java.util.Iterator;
9
+
10
+ import javax.imageio.ImageIO;
11
+ import javax.imageio.ImageReadParam;
12
+ import javax.imageio.ImageReader;
13
+ import javax.imageio.ImageTypeSpecifier;
14
+ import javax.imageio.stream.ImageInputStream;
15
+
16
+ import org.jruby.Ruby;
17
+ import org.jruby.RubyClass;
18
+ import org.jruby.RubyString;
19
+ import org.jruby.RubyModule;
20
+ import org.jruby.RubyObject;
21
+ import org.jruby.anno.JRubyMethod;
22
+ import org.jruby.runtime.ObjectAllocator;
23
+ import org.jruby.runtime.ThreadContext;
24
+ import org.jruby.runtime.builtin.IRubyObject;
25
+ import org.jruby.util.IOInputStream;
26
+
27
+ public class PNGReader extends RubyObject {
28
+ private ImageReader reader;
29
+ private IRubyObject rb_io_in;
30
+ private ImageTypeSpecifier its;
31
+ private int lineno_i;
32
+
33
+ private static ObjectAllocator ALLOCATOR = new ObjectAllocator() {
34
+ public IRubyObject allocate(Ruby runtime, RubyClass klass) {
35
+ return new PNGReader(runtime, klass);
36
+ }
37
+ };
38
+
39
+ @JRubyMethod
40
+ public IRubyObject initialize(IRubyObject io) {
41
+ Iterator readers;
42
+ ImageInputStream iis;
43
+
44
+ rb_io_in = io;
45
+ lineno_i = 0;
46
+
47
+ readers = ImageIO.getImageReadersByFormatName("png");
48
+ reader = (ImageReader)readers.next();
49
+
50
+ try {
51
+ iis = ImageIO.createImageInputStream(new IOInputStream(rb_io_in));
52
+ }
53
+ catch(IOException ioe) {
54
+ throw getRuntime().newIOErrorFromException(ioe);
55
+ }
56
+
57
+ reader.setInput(iis, true);
58
+ try {
59
+ its = reader.getImageTypes(0).next();
60
+ }
61
+ catch(IOException ioe) {
62
+ throw getRuntime().newRuntimeError("An IO error occured while reading.");
63
+ }
64
+
65
+ return this;
66
+ }
67
+
68
+ @JRubyMethod
69
+ public IRubyObject width(ThreadContext context) {
70
+ try {
71
+ return getRuntime().newFixnum(reader.getWidth(0));
72
+ }
73
+ catch(IOException ioe) {
74
+ throw getRuntime().newIOErrorFromException(ioe);
75
+ }
76
+ }
77
+
78
+ @JRubyMethod
79
+ public IRubyObject height(ThreadContext context) {
80
+ try {
81
+ return getRuntime().newFixnum(reader.getHeight(0));
82
+ }
83
+ catch(IOException ioe) {
84
+ throw getRuntime().newIOErrorFromException(ioe);
85
+ }
86
+ }
87
+
88
+ @JRubyMethod
89
+ public IRubyObject components(ThreadContext context) {
90
+ try {
91
+ return getRuntime().newFixnum(getBands());
92
+ }
93
+ catch(IOException ioe) {
94
+ throw getRuntime().newIOErrorFromException(ioe);
95
+ }
96
+ }
97
+
98
+ @JRubyMethod
99
+ public IRubyObject gets(ThreadContext context) throws IOException {
100
+ BufferedImage image;
101
+ ImageReadParam irp;
102
+ Raster raster;
103
+ DataBufferByte buffer;
104
+ byte[] data;
105
+ int numbands;
106
+ int[] bands;
107
+
108
+ /* Return nil if we are already at the bottom of the image */
109
+ if (lineno_i >= reader.getHeight(0))
110
+ return(context.nil);
111
+
112
+ /* request one scanline */
113
+ irp = reader.getDefaultReadParam();
114
+ irp.setSourceRegion(new Rectangle(0, lineno_i, reader.getWidth(0), 1));
115
+
116
+ numbands = getBands();
117
+ switch (numbands) {
118
+ case 3:
119
+ bands = new int[3];
120
+ bands[0] = 2;
121
+ bands[1] = 1;
122
+ bands[2] = 0;
123
+ irp.setDestinationBands(bands);
124
+ break;
125
+ case 4:
126
+ bands = new int[4];
127
+ bands[0] = 2;
128
+ bands[1] = 1;
129
+ bands[2] = 0;
130
+ bands[3] = 3;
131
+ irp.setDestinationBands(bands);
132
+ break;
133
+ }
134
+ image = reader.read(0, irp);
135
+
136
+ /* get the raw bytes from the scanline */
137
+ raster = image.getRaster();
138
+ buffer = (DataBufferByte)raster.getDataBuffer();
139
+ data = buffer.getData();
140
+
141
+ lineno_i += 1;
142
+ return(new RubyString(getRuntime(), getRuntime().getString(), data));
143
+ }
144
+
145
+ @JRubyMethod
146
+ public IRubyObject lineno(ThreadContext context) {
147
+ return getRuntime().newFixnum(lineno_i);
148
+ }
149
+
150
+ static void initPNGReader(Ruby runtime) {
151
+ RubyModule axon = runtime.defineModule("Axon");
152
+ RubyModule png = axon.defineModuleUnder("PNG");
153
+ RubyClass pngReader = png.defineClassUnder("Reader", runtime.getObject(), ALLOCATOR);
154
+ pngReader.defineAnnotatedMethods(PNGReader.class);
155
+ }
156
+
157
+ public PNGReader(Ruby runtime, RubyClass klass) {
158
+ super(runtime, klass);
159
+ }
160
+
161
+ private int getBands() throws IOException {
162
+ return its.getNumComponents();
163
+ }
164
+ }
@@ -0,0 +1,216 @@
1
+ package axon;
2
+
3
+ import java.awt.Point;
4
+ import java.awt.Rectangle;
5
+ import java.awt.Transparency;
6
+ import java.awt.color.ColorSpace;
7
+ import java.awt.color.ICC_ColorSpace;
8
+ import java.awt.color.ICC_Profile;
9
+ import java.awt.image.DataBuffer;
10
+ import java.awt.image.DataBufferByte;
11
+ import java.awt.image.ColorModel;
12
+ import java.awt.image.ComponentColorModel;
13
+ import java.awt.image.Raster;
14
+ import java.awt.image.RenderedImage;
15
+ import java.awt.image.SampleModel;
16
+ import java.awt.image.BandedSampleModel;
17
+ import java.awt.image.WritableRaster;
18
+ import java.io.ByteArrayOutputStream;
19
+ import java.util.Vector;
20
+
21
+ import org.jruby.Ruby;
22
+ import org.jruby.RubyFixnum;
23
+ import org.jruby.RubyString;
24
+ import org.jruby.runtime.ThreadContext;
25
+ import org.jruby.runtime.builtin.IRubyObject;
26
+
27
+ public class RubyImage implements RenderedImage {
28
+ private IRubyObject rb_image;
29
+
30
+ public RubyImage(IRubyObject img) {
31
+ rb_image = img;
32
+ }
33
+
34
+ public WritableRaster copyData(WritableRaster raster) {
35
+ throw new UnsupportedOperationException("copyData");
36
+ }
37
+
38
+ public ColorModel getColorModel() {
39
+ int components, cs_type;
40
+ boolean has_alpha;
41
+
42
+ components = components();
43
+
44
+ if (components == 1 || components == 2)
45
+ cs_type = ColorSpace.CS_GRAY;
46
+ else if (components == 3 || components == 4)
47
+ cs_type = ColorSpace.CS_sRGB;
48
+ else
49
+ cs_type = ColorSpace.TYPE_RGB; // FIXME: Raise an exception instead
50
+
51
+ has_alpha = components == 2 || components == 4;
52
+
53
+ return(new ComponentColorModel(ColorSpace.getInstance(cs_type),
54
+ has_alpha, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE));
55
+ }
56
+
57
+ public ColorModel getICCColorModel(byte[] data) {
58
+ int components;
59
+ ICC_ColorSpace cs;
60
+ boolean has_alpha;
61
+
62
+ components = components();
63
+
64
+ cs = new ICC_ColorSpace(ICC_Profile.getInstance(data));
65
+
66
+ has_alpha = components == 2 || components == 4;
67
+
68
+ return(new ComponentColorModel(cs, has_alpha, false,
69
+ Transparency.OPAQUE, DataBuffer.TYPE_BYTE));
70
+ }
71
+
72
+ public Raster getTile(int tileX, int tileY) {
73
+ throw new UnsupportedOperationException("getTile");
74
+ }
75
+
76
+ public Raster getData() {
77
+ return getData(new Rectangle(getWidth(), getHeight()));
78
+ }
79
+
80
+ public Raster getData(Rectangle rect) {
81
+ DataBuffer dataBuffer;
82
+ int w, h, numbands, lineno, sl_size;
83
+ RubyString sl;
84
+ ByteArrayOutputStream stream;
85
+ byte[] sl_bytes;
86
+ int[] bandOffsets;
87
+ Raster raster;
88
+
89
+ w = getWidth();
90
+ h = getHeight();
91
+ numbands = components();
92
+ lineno = lineno();
93
+ sl_size = w * numbands;
94
+
95
+ bandOffsets = new int[numbands];
96
+ for (int i=0; i<numbands; i++)
97
+ bandOffsets[i] = i;
98
+
99
+ if (rect.height < 1 || rect.width < 1)
100
+ throw runtime().newRuntimeError("Requested image region has invalid size.");
101
+
102
+ stream = new ByteArrayOutputStream(rect.height * rect.width);
103
+
104
+ for (int j=0; j<rect.height; j++) {
105
+ sl = RubyString.objAsString(context(), callMethod("gets"));
106
+ sl_bytes = sl.getBytes();
107
+
108
+ if(sl_bytes.length != sl_size)
109
+ throw runtime().newRuntimeError("Image returned a scanline with a bad size.");
110
+
111
+ try {
112
+ stream.write(sl_bytes, 0, sl_size);
113
+ }
114
+ catch(IndexOutOfBoundsException iob) {
115
+ throw runtime().newRuntimeError("An index out of bounds error occurred while writing.");
116
+ }
117
+ }
118
+
119
+ dataBuffer = new DataBufferByte(stream.toByteArray(), stream.size());
120
+ try {
121
+ raster = Raster.createInterleavedRaster(dataBuffer, w, rect.height,
122
+ sl_size, numbands, bandOffsets, new Point(0, lineno));
123
+ }
124
+ catch(IllegalArgumentException iae) {
125
+ throw runtime().newRuntimeError("An argument exception occurred while preparing image data.");
126
+ }
127
+
128
+ return(raster);
129
+ }
130
+
131
+ public Vector<RenderedImage> getSources() {
132
+ throw new UnsupportedOperationException("getSources");
133
+ }
134
+
135
+ public Object getProperty(String name) {
136
+ throw new UnsupportedOperationException("getProperty");
137
+ }
138
+
139
+ public String[] getPropertyNames() {
140
+ throw new UnsupportedOperationException("getPropertyNames");
141
+ }
142
+
143
+ public SampleModel getSampleModel() {
144
+ int w;
145
+ w = getWidth();
146
+ return new BandedSampleModel(DataBuffer.TYPE_BYTE, w, 1, components());
147
+ }
148
+
149
+ public int getWidth() {
150
+ return RubyFixnum.num2int(callMethod("width"));
151
+ }
152
+
153
+ public int getHeight() {
154
+ return RubyFixnum.num2int(callMethod("height"));
155
+ }
156
+
157
+ public int getMinX() {
158
+ return 0;
159
+ }
160
+
161
+ public int getMinY() {
162
+ return lineno();
163
+ }
164
+
165
+ public int getNumXTiles() {
166
+ return 1;
167
+ }
168
+
169
+ public int getNumYTiles() {
170
+ return getHeight();
171
+ }
172
+
173
+ public int getMinTileX() {
174
+ return 1;
175
+ }
176
+
177
+ public int getMinTileY() {
178
+ return getMinY();
179
+ }
180
+
181
+ public int getTileWidth() {
182
+ throw new UnsupportedOperationException("getTileWidth");
183
+ }
184
+
185
+ public int getTileHeight() {
186
+ throw new UnsupportedOperationException("getTileHeight");
187
+ }
188
+
189
+ public int getTileGridXOffset() {
190
+ throw new UnsupportedOperationException("getTileGridXOffset");
191
+ }
192
+
193
+ public int getTileGridYOffset() {
194
+ throw new UnsupportedOperationException("getTileGridYOffset");
195
+ }
196
+
197
+ private int lineno() {
198
+ return RubyFixnum.num2int(callMethod("lineno"));
199
+ }
200
+
201
+ private int components() {
202
+ return RubyFixnum.num2int(callMethod("components"));
203
+ }
204
+
205
+ private IRubyObject callMethod(String name) {
206
+ return rb_image.callMethod(context(), name);
207
+ }
208
+
209
+ private ThreadContext context() {
210
+ return runtime().getCurrentContext();
211
+ }
212
+
213
+ private Ruby runtime() {
214
+ return rb_image.getRuntime();
215
+ }
216
+ }
@@ -5,10 +5,11 @@ require 'axon/cropper'
5
5
  require 'axon/fit'
6
6
  require 'axon/scalers'
7
7
  require 'axon/generators'
8
+ require 'axon/alpha_stripper'
8
9
  require 'stringio'
9
10
 
10
11
  module Axon
11
- VERSION = '0.1.1'
12
+ VERSION = '0.2.0'
12
13
 
13
14
  # :call-seq:
14
15
  # Axon.jpeg(thing [, markers]) -> image
@@ -39,6 +40,31 @@ module Axon
39
40
  Image.new(reader)
40
41
  end
41
42
 
43
+ # :call-seq:
44
+ # Axon.jpeg_file(path [, markers], &block) -> block_result
45
+ #
46
+ # Opens the file located at +path+ and reads it as a compressed JPEG image.
47
+ #
48
+ # +markers+ should be an array of valid JPEG header marker symbols. Valid
49
+ # symbols are :APP0 through :APP15 and :COM.
50
+ #
51
+ # If performance is important and you don't care about any markers, you can
52
+ # avaoid reading any header markers by supplying an empty array for +markers+.
53
+ #
54
+ # When +markers+ is not given, all known JPEG markers will be read.
55
+ #
56
+ # This method returns the result of the block.
57
+ #
58
+ # Axon.jpeg_file("image.jpg", [:APP2]) do |image|
59
+ # image.png_file("image.png") # saves "image.jpg" to "image.png"
60
+ # end
61
+ #
62
+ def self.jpeg_file(path, *args)
63
+ File.open(path, 'rb') do |f|
64
+ yield jpeg(f, *args)
65
+ end
66
+ end
67
+
42
68
  # :call-seq:
43
69
  # Axon.png(thing) -> image
44
70
  #
@@ -57,6 +83,23 @@ module Axon
57
83
  Image.new(reader)
58
84
  end
59
85
 
86
+ # :call-seq:
87
+ # Axon.png_file(path, &block) -> block_result
88
+ #
89
+ # Opens the file located at +path+ and reads it as a compressed PNG image.
90
+ #
91
+ # This method returns the result of the block.
92
+ #
93
+ # Axon.png_file("image.png") do |image|
94
+ # image.jpeg_file("image.jpg") # saves "image.png" to "image.jpeg"
95
+ # end
96
+ #
97
+ def self.png_file(path, *args)
98
+ File.open(path, 'rb') do |f|
99
+ yield png(f, *args)
100
+ end
101
+ end
102
+
60
103
  class Image
61
104
  # :call-seq:
62
105
  # Image.new(image_in)
@@ -176,6 +219,8 @@ module Axon
176
219
  # Writes the image to +io_out+ as compressed JPEG data. Returns the number
177
220
  # of bytes written.
178
221
  #
222
+ # If the image has an alpha channel it will be stripped.
223
+ #
179
224
  # +options+ may contain the following symbols:
180
225
  #
181
226
  # * :bufsize -- the maximum size in bytes of the writes that will be
@@ -191,9 +236,34 @@ module Axon
191
236
  # i.jpeg(io_out, :quality => 88) # writes the image to output.jpg
192
237
  #
193
238
  def jpeg(*args)
239
+ case @source.components
240
+ when 2,4 then @source = AlphaStripper.new(@source)
241
+ end
194
242
  JPEG.write(@source, *args)
195
243
  end
196
244
 
245
+ # :call-seq:
246
+ # jpeg_file(path [, options])
247
+ #
248
+ # Writes the image to a new file at +path+ as compressed JPEG data.
249
+ # Returns the number of bytes written.
250
+ #
251
+ # If the image has an alpha channel it will be stripped.
252
+ #
253
+ # See Axon#jpeg for a description of +options+.
254
+ #
255
+ # == Example
256
+ #
257
+ # Axon.png_file("image.png") do |image|
258
+ # image.jpeg_file("image.jpg") # saves the image to "image.jpeg"
259
+ # end
260
+ #
261
+ def jpeg_file(path, *args)
262
+ File.open(path, 'wb') do |f|
263
+ jpeg(f, *args)
264
+ end
265
+ end
266
+
197
267
  # :call-seq:
198
268
  # png(io_out)
199
269
  #
@@ -210,16 +280,28 @@ module Axon
210
280
  PNG.write(@source, *args)
211
281
  end
212
282
 
213
- # Gets the components in the image.
283
+ # :call-seq:
284
+ # png_file(path)
214
285
  #
215
- def components
216
- @source.components
286
+ # Writes the image to a new file at +path+ as compressed PNG data. Returns
287
+ # the number of bytes written.
288
+ #
289
+ # == Example
290
+ #
291
+ # Axon.jpeg_file("image.jpg") do |image|
292
+ # image.png_file("image.png") # saves the image to "image.jpeg"
293
+ # end
294
+ #
295
+ def png_file(path, *args)
296
+ File.open(path, 'wb') do |f|
297
+ png(f, *args)
298
+ end
217
299
  end
218
300
 
219
- # Gets the color model of the image.
301
+ # Gets the components in the image.
220
302
  #
221
- def color_model
222
- @source.color_model
303
+ def components
304
+ @source.components
223
305
  end
224
306
 
225
307
  # Gets the width of the image.
@@ -245,5 +327,9 @@ module Axon
245
327
  def gets
246
328
  @source.gets
247
329
  end
330
+
331
+ def method_missing(name, *args)
332
+ @source.send(name, *args)
333
+ end
248
334
  end
249
335
  end