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