axon 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +6 -0
- data/README.rdoc +23 -49
- data/Rakefile +33 -7
- data/TODO.rdoc +6 -1
- data/ext/axon/jpeg.c +32 -66
- data/ext/axon/png.c +46 -71
- data/ext/java/axon/AxonService.java +15 -0
- data/ext/java/axon/Interpolation.java +127 -0
- data/ext/java/axon/JPEG.java +119 -0
- data/ext/java/axon/JPEGReader.java +185 -0
- data/ext/java/axon/PNG.java +47 -0
- data/ext/java/axon/PNGReader.java +164 -0
- data/ext/java/axon/RubyImage.java +216 -0
- data/lib/axon.rb +93 -7
- data/lib/axon/alpha_stripper.rb +69 -0
- data/lib/axon/axon.jar +0 -0
- data/lib/axon/cropper.rb +6 -10
- data/lib/axon/fit.rb +53 -35
- data/lib/axon/generators.rb +1 -10
- data/lib/axon/scalers.rb +2 -16
- data/test/helper.rb +7 -9
- data/test/reader_tests.rb +0 -8
- data/test/test_alpha_stripper.rb +33 -0
- data/test/test_fit.rb +38 -0
- data/test/test_jpeg_reader.rb +49 -26
- data/test/test_jpeg_writer.rb +8 -0
- data/test/writer_tests.rb +51 -40
- metadata +96 -87
@@ -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
|
+
}
|
data/lib/axon.rb
CHANGED
@@ -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.
|
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
|
-
#
|
283
|
+
# :call-seq:
|
284
|
+
# png_file(path)
|
214
285
|
#
|
215
|
-
|
216
|
-
|
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
|
301
|
+
# Gets the components in the image.
|
220
302
|
#
|
221
|
-
def
|
222
|
-
@source.
|
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
|