001: /*
002: * $RCSfile: PNMImageDecoder.java,v $
003: *
004: * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
005: *
006: * Use is subject to license terms.
007: *
008: * $Revision: 1.3 $
009: * $Date: 2006/08/22 00:12:04 $
010: * $State: Exp $
011: */
012: package com.sun.media.jai.codecimpl;
013:
014: import java.awt.Point;
015: import java.awt.Rectangle;
016: import java.awt.RenderingHints;
017: import java.awt.Transparency;
018: import java.awt.color.ColorSpace;
019: import java.awt.image.ComponentColorModel;
020: import java.awt.image.DataBuffer;
021: import java.awt.image.DataBufferByte;
022: import java.awt.image.DataBufferInt;
023: import java.awt.image.DataBufferUShort;
024: import java.awt.image.IndexColorModel;
025: import java.awt.image.MultiPixelPackedSampleModel;
026: import java.awt.image.Raster;
027: import java.awt.image.RenderedImage;
028: import java.awt.image.SampleModel;
029: import java.awt.image.WritableRaster;
030: import java.io.IOException;
031: import com.sun.media.jai.codec.ImageCodec;
032: import com.sun.media.jai.codec.ImageDecoder;
033: import com.sun.media.jai.codec.ImageDecoderImpl;
034: import com.sun.media.jai.codec.ImageDecodeParam;
035: import com.sun.media.jai.codec.SeekableStream;
036: import com.sun.media.jai.codecimpl.util.RasterFactory;
037: import com.sun.media.jai.codecimpl.ImagingListenerProxy;
038: import com.sun.media.jai.codecimpl.util.ImagingException;
039:
040: /**
041: * @since EA2
042: */
043: public class PNMImageDecoder extends ImageDecoderImpl {
044:
045: public PNMImageDecoder(SeekableStream input, ImageDecodeParam param) {
046: super (input, param);
047: }
048:
049: public RenderedImage decodeAsRenderedImage(int page)
050: throws IOException {
051: if (page != 0) {
052: throw new IOException(JaiI18N.getString("PNMImageDecoder5"));
053: }
054: try {
055: return new PNMImage(input);
056: } catch (Exception e) {
057: throw CodecUtils.toIOException(e);
058: }
059: }
060: }
061:
062: class PNMImage extends SimpleRenderedImage {
063:
064: private static final int PBM_ASCII = '1';
065: private static final int PGM_ASCII = '2';
066: private static final int PPM_ASCII = '3';
067: private static final int PBM_RAW = '4';
068: private static final int PGM_RAW = '5';
069: private static final int PPM_RAW = '6';
070:
071: private static final int LINE_FEED = 0x0A;
072:
073: private SeekableStream input;
074:
075: private byte[] lineSeparator;
076:
077: /** File variant: PBM/PGM/PPM, ASCII/RAW. */
078: private int variant;
079:
080: /** Maximum pixel value. */
081: private int maxValue;
082:
083: /** Raster that is the entire image. */
084: private Raster theTile;
085:
086: private int numBands;
087:
088: private int dataType;
089:
090: /**
091: * Construct a PNMImage.
092: *
093: * @param input The SeekableStream for the PNM file.
094: */
095: public PNMImage(SeekableStream input) {
096: theTile = null;
097:
098: this .input = input;
099:
100: String ls = (String) java.security.AccessController
101: .doPrivileged(new sun.security.action.GetPropertyAction(
102: "line.separator"));
103: lineSeparator = ls.getBytes();
104:
105: // Read file header.
106: try {
107: if (this .input.read() != 'P') { // magic number
108: throw new RuntimeException(JaiI18N
109: .getString("PNMImageDecoder0"));
110: }
111:
112: variant = this .input.read(); // file variant
113: if ((variant < PBM_ASCII) || (variant > PPM_RAW)) {
114: throw new RuntimeException(JaiI18N
115: .getString("PNMImageDecoder1"));
116: }
117:
118: width = readInteger(this .input); // width
119: height = readInteger(this .input); // height
120:
121: if (variant == PBM_ASCII || variant == PBM_RAW) {
122: maxValue = 1;
123: } else {
124: maxValue = readInteger(this .input); // maximum value
125: }
126: } catch (IOException e) {
127: String message = JaiI18N.getString("PNMImageDecoder6");
128: sendExceptionToListener(message, e);
129: // e.printStackTrace();
130: // throw new RuntimeException(JaiI18N.getString("PNMImageDecoder2"));
131: }
132:
133: // The RAWBITS format can only support byte image data, which means
134: // maxValue should be less than 0x100. In case there's a conflict,
135: // base the maxValue on variant.
136: if (isRaw(variant) && maxValue >= 0x100) {
137: maxValue = 0xFF;
138: }
139:
140: // Reset image layout so there's only one tile.
141: tileWidth = width;
142: tileHeight = height;
143:
144: // Determine number of bands: pixmap (PPM) is 3 bands,
145: // bitmap (PBM) and greymap (PGM) are 1 band.
146: if (variant == PPM_ASCII || variant == PPM_RAW) {
147: this .numBands = 3;
148: } else {
149: this .numBands = 1;
150: }
151:
152: // Determine data type based on maxValue.
153: if (maxValue < 0x100) {
154: this .dataType = DataBuffer.TYPE_BYTE;
155: } else if (maxValue < 0x10000) {
156: this .dataType = DataBuffer.TYPE_USHORT;
157: } else {
158: this .dataType = DataBuffer.TYPE_INT;
159: }
160:
161: // Choose an appropriate SampleModel.
162: if ((variant == PBM_ASCII) || (variant == PBM_RAW)) {
163: // Each pixel takes 1 bit, pack 8 pixels into a byte.
164: sampleModel = new MultiPixelPackedSampleModel(
165: DataBuffer.TYPE_BYTE, width, height, 1);
166: colorModel = ImageCodec.createGrayIndexColorModel(
167: sampleModel, false);
168: } else {
169: int[] bandOffsets = numBands == 1 ? new int[] { 0 }
170: : new int[] { 0, 1, 2 };
171: sampleModel = RasterFactory
172: .createPixelInterleavedSampleModel(dataType,
173: tileWidth, tileHeight, numBands, tileWidth
174: * numBands, bandOffsets);
175:
176: colorModel = ImageCodec
177: .createComponentColorModel(sampleModel);
178: }
179: }
180:
181: /** Returns true if file variant is raw format, false if ASCII. */
182: private boolean isRaw(int v) {
183: return (v >= PBM_RAW);
184: }
185:
186: /** Reads the next integer. */
187: private int readInteger(SeekableStream in) throws IOException {
188: int ret = 0;
189: boolean foundDigit = false;
190:
191: int b;
192: while ((b = in.read()) != -1) {
193: char c = (char) b;
194: if (Character.isDigit(c)) {
195: ret = ret * 10 + Character.digit(c, 10);
196: foundDigit = true;
197: } else {
198: if (c == '#') { // skip to the end of comment line
199: int length = lineSeparator.length;
200:
201: while ((b = in.read()) != -1) {
202: boolean eol = false;
203: for (int i = 0; i < length; i++) {
204: if (b == lineSeparator[i]) {
205: eol = true;
206: break;
207: }
208: }
209: if (eol) {
210: break;
211: }
212: }
213: if (b == -1) {
214: break;
215: }
216: }
217: if (foundDigit) {
218: break;
219: }
220: }
221: }
222:
223: return ret;
224: }
225:
226: private Raster computeTile(int tileX, int tileY) {
227: // Create a new tile.
228: Point org = new Point(tileXToX(tileX), tileYToY(tileY));
229: WritableRaster tile = Raster.createWritableRaster(sampleModel,
230: org);
231: Rectangle tileRect = tile.getBounds();
232:
233: // There should only be one tile.
234: try {
235: switch (variant) {
236: case PBM_ASCII:
237: case PBM_RAW:
238: // SampleModel for these cases should be MultiPixelPacked.
239:
240: DataBuffer dataBuffer = tile.getDataBuffer();
241: if (isRaw(variant)) {
242: // Read the entire image.
243: byte[] buf = ((DataBufferByte) dataBuffer)
244: .getData();
245: input.readFully(buf, 0, buf.length);
246: } else {
247: // Read 8 rows at a time
248: byte[] pixels = new byte[8 * width];
249: int offset = 0;
250: for (int row = 0; row < tileHeight; row += 8) {
251: int rows = Math.min(8, tileHeight - row);
252: int len = (rows * width + 7) / 8;
253:
254: for (int i = 0; i < rows * width; i++) {
255: pixels[i] = (byte) readInteger(input);
256: }
257: sampleModel.setDataElements(tileRect.x, row,
258: tileRect.width, rows, pixels,
259: dataBuffer);
260: }
261: }
262: break;
263:
264: case PGM_ASCII:
265: case PGM_RAW:
266: case PPM_ASCII:
267: case PPM_RAW:
268: // SampleModel for these cases should be PixelInterleaved.
269: int size = width * height * numBands;
270:
271: switch (dataType) {
272: case DataBuffer.TYPE_BYTE:
273: DataBufferByte bbuf = (DataBufferByte) tile
274: .getDataBuffer();
275: byte[] byteArray = bbuf.getData();
276: if (isRaw(variant)) {
277: input.readFully(byteArray);
278: } else {
279: for (int i = 0; i < size; i++) {
280: byteArray[i] = (byte) readInteger(input);
281: }
282: }
283: break;
284:
285: case DataBuffer.TYPE_USHORT:
286: DataBufferUShort sbuf = (DataBufferUShort) tile
287: .getDataBuffer();
288: short[] shortArray = sbuf.getData();
289: for (int i = 0; i < size; i++) {
290: shortArray[i] = (short) readInteger(input);
291: }
292: break;
293:
294: case DataBuffer.TYPE_INT:
295: DataBufferInt ibuf = (DataBufferInt) tile
296: .getDataBuffer();
297: int[] intArray = ibuf.getData();
298: for (int i = 0; i < size; i++) {
299: intArray[i] = readInteger(input);
300: }
301: break;
302: }
303: break;
304: }
305:
306: // Close the PNM stream and release system resources.
307: input.close();
308: } catch (IOException e) {
309: String message = JaiI18N.getString("PNMImageDecoder7");
310: sendExceptionToListener(message, e);
311: // e.printStackTrace();
312: // throw new RuntimeException(JaiI18N.getString("PNMImageDecoder3"));
313: }
314:
315: return tile;
316: }
317:
318: public synchronized Raster getTile(int tileX, int tileY) {
319: if ((tileX != 0) || (tileY != 0)) {
320: throw new IllegalArgumentException(JaiI18N
321: .getString("PNMImageDecoder4"));
322: }
323:
324: if (theTile == null) {
325: theTile = computeTile(tileX, tileY);
326: }
327:
328: return theTile;
329: }
330:
331: public void dispose() {
332: theTile = null;
333: }
334:
335: private void sendExceptionToListener(String message, Exception e) {
336: ImagingListenerProxy.errorOccurred(message,
337: new ImagingException(message, e), this , false);
338: }
339: }
|