axon 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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