001: /*
002: * $RCSfile: PCXImageReader.java,v $
003: *
004: *
005: * Copyright (c) 2007 Sun Microsystems, Inc. All Rights Reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * - Redistribution of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: *
014: * - Redistribution in binary form must reproduce the above copyright
015: * notice, this list of conditions and the following disclaimer in
016: * the documentation and/or other materials provided with the
017: * distribution.
018: *
019: * Neither the name of Sun Microsystems, Inc. or the names of
020: * contributors may be used to endorse or promote products derived
021: * from this software without specific prior written permission.
022: *
023: * This software is provided "AS IS," without a warranty of any
024: * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
025: * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
026: * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
027: * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
028: * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
029: * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
030: * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
031: * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
032: * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
033: * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
034: * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
035: * POSSIBILITY OF SUCH DAMAGES.
036: *
037: * You acknowledge that this software is not designed or intended for
038: * use in the design, construction, operation or maintenance of any
039: * nuclear facility.
040: *
041: * $Revision: 1.3 $
042: * $Date: 2007/09/07 19:13:02 $
043: * $State: Exp $
044: */
045: package com.sun.media.imageioimpl.plugins.pcx;
046:
047: import java.awt.*;
048: import java.awt.color.ColorSpace;
049: import java.awt.image.*;
050: import java.io.*;
051: import java.nio.ByteOrder;
052: import java.util.*;
053:
054: import javax.imageio.*;
055: import javax.imageio.metadata.IIOMetadata;
056: import javax.imageio.stream.ImageInputStream;
057:
058: public class PCXImageReader extends ImageReader implements PCXConstants {
059:
060: private ImageInputStream iis;
061: private int width, height;
062: private boolean gotHeader = false;
063: private byte manufacturer;
064: private byte encoding;
065: private short xmax, ymax;
066: private byte[] smallPalette = new byte[3 * 16];
067: private byte[] largePalette = new byte[3 * 256];
068: private byte colorPlanes;
069: private short bytesPerLine;
070: private short paletteType;
071:
072: private PCXMetadata metadata;
073:
074: private SampleModel sampleModel, originalSampleModel;
075: private ColorModel colorModel, originalColorModel;
076:
077: /** The destination region. */
078: private Rectangle destinationRegion;
079:
080: /** The source region. */
081: private Rectangle sourceRegion;
082:
083: /** The destination image. */
084: private BufferedImage bi;
085:
086: /** Indicates whether subsampled, subregion is required, and offset is
087: * defined
088: */
089: private boolean noTransform = true;
090:
091: /** Indicates whether subband is selected. */
092: private boolean seleBand = false;
093:
094: /** The scaling factors. */
095: private int scaleX, scaleY;
096:
097: /** source and destination bands. */
098: private int[] sourceBands, destBands;
099:
100: public PCXImageReader(PCXImageReaderSpi imageReaderSpi) {
101: super (imageReaderSpi);
102: }
103:
104: public void setInput(Object input, boolean seekForwardOnly,
105: boolean ignoreMetadata) {
106: super .setInput(input, seekForwardOnly, ignoreMetadata);
107: iis = (ImageInputStream) input; // Always works
108: if (iis != null)
109: iis.setByteOrder(ByteOrder.LITTLE_ENDIAN);
110: gotHeader = false;
111: }
112:
113: public int getHeight(int imageIndex) throws IOException {
114: checkIndex(imageIndex);
115: readHeader();
116: return height;
117: }
118:
119: public IIOMetadata getImageMetadata(int imageIndex)
120: throws IOException {
121: checkIndex(imageIndex);
122: readHeader();
123: return metadata;
124: }
125:
126: public Iterator getImageTypes(int imageIndex) throws IOException {
127: checkIndex(imageIndex);
128: readHeader();
129: return Collections.singletonList(
130: new ImageTypeSpecifier(originalColorModel,
131: originalSampleModel)).iterator();
132: }
133:
134: public int getNumImages(boolean allowSearch) throws IOException {
135: if (iis == null) {
136: throw new IllegalStateException("input is null");
137: }
138: if (seekForwardOnly && allowSearch) {
139: throw new IllegalStateException(
140: "cannot search with forward only input");
141: }
142: return 1;
143: }
144:
145: public IIOMetadata getStreamMetadata() throws IOException {
146: return null;
147: }
148:
149: public int getWidth(int imageIndex) throws IOException {
150: checkIndex(imageIndex);
151: readHeader();
152: return width;
153: }
154:
155: public BufferedImage read(int imageIndex, ImageReadParam param)
156: throws IOException {
157: checkIndex(imageIndex);
158: readHeader();
159:
160: if (iis == null)
161: throw new IllegalStateException("input is null");
162:
163: BufferedImage img;
164:
165: clearAbortRequest();
166: processImageStarted(imageIndex);
167:
168: if (param == null)
169: param = getDefaultReadParam();
170:
171: sourceRegion = new Rectangle(0, 0, 0, 0);
172: destinationRegion = new Rectangle(0, 0, 0, 0);
173:
174: computeRegions(param, this .width, this .height, param
175: .getDestination(), sourceRegion, destinationRegion);
176:
177: scaleX = param.getSourceXSubsampling();
178: scaleY = param.getSourceYSubsampling();
179:
180: // If the destination band is set used it
181: sourceBands = param.getSourceBands();
182: destBands = param.getDestinationBands();
183:
184: seleBand = (sourceBands != null) && (destBands != null);
185: noTransform = destinationRegion.equals(new Rectangle(0, 0,
186: width, height))
187: || seleBand;
188:
189: if (!seleBand) {
190: sourceBands = new int[colorPlanes];
191: destBands = new int[colorPlanes];
192: for (int i = 0; i < colorPlanes; i++)
193: destBands[i] = sourceBands[i] = i;
194: }
195:
196: // If the destination is provided, then use it. Otherwise, create new one
197: bi = param.getDestination();
198:
199: // Get the image data.
200: WritableRaster raster = null;
201:
202: if (bi == null) {
203: if (sampleModel != null && colorModel != null) {
204: sampleModel = sampleModel.createCompatibleSampleModel(
205: destinationRegion.width + destinationRegion.x,
206: destinationRegion.height + destinationRegion.y);
207: if (seleBand)
208: sampleModel = sampleModel
209: .createSubsetSampleModel(sourceBands);
210: raster = Raster.createWritableRaster(sampleModel,
211: new Point(0, 0));
212: bi = new BufferedImage(colorModel, raster, false, null);
213: }
214: } else {
215: raster = bi.getWritableTile(0, 0);
216: sampleModel = bi.getSampleModel();
217: colorModel = bi.getColorModel();
218:
219: noTransform &= destinationRegion.equals(raster.getBounds());
220: }
221:
222: byte bdata[] = null; // buffer for byte data
223:
224: if (sampleModel.getDataType() == DataBuffer.TYPE_BYTE)
225: bdata = (byte[]) ((DataBufferByte) raster.getDataBuffer())
226: .getData();
227:
228: readImage(bdata);
229:
230: if (abortRequested())
231: processReadAborted();
232: else
233: processImageComplete();
234:
235: return bi;
236: }
237:
238: private void readImage(byte[] data) throws IOException {
239:
240: byte[] scanline = new byte[bytesPerLine * colorPlanes];
241:
242: if (noTransform) {
243: try {
244: int offset = 0;
245: int nbytes = (width * metadata.bitsPerPixel + 8 - metadata.bitsPerPixel) / 8;
246: for (int line = 0; line < height; line++) {
247: readScanLine(scanline);
248: for (int band = 0; band < colorPlanes; band++) {
249: System.arraycopy(scanline, bytesPerLine * band,
250: data, offset, nbytes);
251: offset += nbytes;
252: }
253: processImageProgress(100.0F * line / height);
254: }
255: } catch (EOFException e) {
256: }
257: } else {
258: if (metadata.bitsPerPixel == 1)
259: read1Bit(data);
260: else if (metadata.bitsPerPixel == 4)
261: read4Bit(data);
262: else
263: read8Bit(data);
264: }
265: }
266:
267: private void read1Bit(byte[] data) throws IOException {
268: byte[] scanline = new byte[bytesPerLine];
269:
270: try {
271: // skip until source region
272: for (int line = 0; line < sourceRegion.y; line++) {
273: readScanLine(scanline);
274: }
275: int lineStride = ((MultiPixelPackedSampleModel) sampleModel)
276: .getScanlineStride();
277:
278: // cache the values to avoid duplicated computation
279: int[] srcOff = new int[destinationRegion.width];
280: int[] destOff = new int[destinationRegion.width];
281: int[] srcPos = new int[destinationRegion.width];
282: int[] destPos = new int[destinationRegion.width];
283:
284: for (int i = destinationRegion.x, x = sourceRegion.x, j = 0; i < destinationRegion.x
285: + destinationRegion.width; i++, j++, x += scaleX) {
286: srcPos[j] = x >> 3;
287: srcOff[j] = 7 - (x & 7);
288: destPos[j] = i >> 3;
289: destOff[j] = 7 - (i & 7);
290: }
291:
292: int k = destinationRegion.y * lineStride;
293:
294: for (int line = 0; line < sourceRegion.height; line++) {
295: readScanLine(scanline);
296: if (line % scaleY == 0) {
297: for (int i = 0; i < destinationRegion.width; i++) {
298: //get the bit and assign to the data buffer of the raster
299: int v = (scanline[srcPos[i]] >> srcOff[i]) & 1;
300: data[k + destPos[i]] |= v << destOff[i];
301: }
302: k += lineStride;
303: }
304: processImageProgress(100.0F * line
305: / sourceRegion.height);
306: }
307: } catch (EOFException e) {
308: }
309: }
310:
311: private void read4Bit(byte[] data) throws IOException {
312: byte[] scanline = new byte[bytesPerLine];
313: try {
314: int lineStride = ((MultiPixelPackedSampleModel) sampleModel)
315: .getScanlineStride();
316:
317: // cache the values to avoid duplicated computation
318: int[] srcOff = new int[destinationRegion.width];
319: int[] destOff = new int[destinationRegion.width];
320: int[] srcPos = new int[destinationRegion.width];
321: int[] destPos = new int[destinationRegion.width];
322:
323: for (int i = destinationRegion.x, x = sourceRegion.x, j = 0; i < destinationRegion.x
324: + destinationRegion.width; i++, j++, x += scaleX) {
325: srcPos[j] = x >> 1;
326: srcOff[j] = (1 - (x & 1)) << 2;
327: destPos[j] = i >> 1;
328: destOff[j] = (1 - (i & 1)) << 2;
329: }
330:
331: int k = destinationRegion.y * lineStride;
332:
333: for (int line = 0; line < sourceRegion.height; line++) {
334: readScanLine(scanline);
335:
336: if (abortRequested())
337: break;
338: if (line % scaleY == 0) {
339: for (int i = 0; i < destinationRegion.width; i++) {
340: //get the bit and assign to the data buffer of the raster
341: int v = (scanline[srcPos[i]] >> srcOff[i]) & 0x0F;
342: data[k + destPos[i]] |= v << destOff[i];
343: }
344: k += lineStride;
345: }
346: processImageProgress(100.0F * line
347: / sourceRegion.height);
348: }
349: } catch (EOFException e) {
350: }
351: }
352:
353: /* also handles 24 bit (three 8 bit planes) */
354: private void read8Bit(byte[] data) throws IOException {
355: byte[] scanline = new byte[colorPlanes * bytesPerLine];
356: try {
357: // skip until source region
358: for (int line = 0; line < sourceRegion.y; line++) {
359: readScanLine(scanline);
360: }
361: int dstOffset = destinationRegion.y
362: * (destinationRegion.x + destinationRegion.width)
363: * colorPlanes;
364: for (int line = 0; line < sourceRegion.height; line++) {
365: readScanLine(scanline);
366: if (line % scaleY == 0) {
367: int srcOffset = sourceRegion.x;
368: for (int band = 0; band < colorPlanes; band++) {
369: dstOffset += destinationRegion.x;
370: for (int x = 0; x < destinationRegion.width; x += scaleX) {
371: data[dstOffset++] = scanline[srcOffset + x];
372: }
373: srcOffset += bytesPerLine;
374: }
375: }
376: processImageProgress(100.0F * line
377: / sourceRegion.height);
378: }
379: } catch (EOFException e) {
380: }
381: }
382:
383: private void readScanLine(byte[] buffer) throws IOException {
384: int max = bytesPerLine * colorPlanes;
385: for (int j = 0; j < max;) {
386: int val = iis.readUnsignedByte();
387:
388: if ((val & 0xC0) == 0xC0) {
389: int count = val & ~0xC0;
390: val = iis.readUnsignedByte();
391: for (int k = 0; k < count && j < max; k++) {
392: buffer[j++] = (byte) (val & 0xFF);
393: }
394: } else {
395: buffer[j++] = (byte) (val & 0xFF);
396: }
397: }
398: }
399:
400: private void checkIndex(int imageIndex) {
401: if (imageIndex != 0) {
402: throw new IndexOutOfBoundsException(
403: "only one image exists in the stream");
404: }
405: }
406:
407: private void readHeader() throws IOException {
408: if (gotHeader) {
409: iis.seek(128);
410: return;
411: }
412:
413: metadata = new PCXMetadata();
414:
415: manufacturer = iis.readByte(); // manufacturer
416: if (manufacturer != MANUFACTURER)
417: throw new IllegalStateException("image is not a PCX file");
418: metadata.version = iis.readByte(); // version
419: encoding = iis.readByte(); // encoding
420: if (encoding != ENCODING)
421: throw new IllegalStateException(
422: "image is not a PCX file, invalid encoding "
423: + encoding);
424:
425: metadata.bitsPerPixel = iis.readByte();
426:
427: metadata.xmin = iis.readShort();
428: metadata.ymin = iis.readShort();
429: xmax = iis.readShort();
430: ymax = iis.readShort();
431:
432: metadata.hdpi = iis.readShort();
433: metadata.vdpi = iis.readShort();
434:
435: iis.readFully(smallPalette);
436:
437: iis.readByte(); // reserved
438:
439: colorPlanes = iis.readByte();
440: bytesPerLine = iis.readShort();
441: paletteType = iis.readShort();
442:
443: metadata.hsize = iis.readShort();
444: metadata.vsize = iis.readShort();
445:
446: iis.skipBytes(54); // skip filler
447:
448: width = xmax - metadata.xmin + 1;
449: height = ymax - metadata.ymin + 1;
450:
451: if (colorPlanes == 1) {
452: if (paletteType == PALETTE_GRAYSCALE) {
453: ColorSpace cs = ColorSpace
454: .getInstance(ColorSpace.CS_GRAY);
455: int[] nBits = { 8 };
456: colorModel = new ComponentColorModel(cs, nBits, false,
457: false, Transparency.OPAQUE,
458: DataBuffer.TYPE_BYTE);
459: sampleModel = new ComponentSampleModel(
460: DataBuffer.TYPE_BYTE, width, height, 1, width,
461: new int[] { 0 });
462: } else {
463: if (metadata.bitsPerPixel == 8) {
464: // read palette from end of file, then reset back to image data
465: iis.mark();
466:
467: if (iis.length() == -1) {
468: // read until eof, and work backwards
469: while (iis.read() != -1)
470: ;
471: iis.seek(iis.getStreamPosition() - 256 * 3 - 1);
472: } else {
473: iis.seek(iis.length() - 256 * 3 - 1);
474: }
475:
476: int palletteMagic = iis.read();
477: if (palletteMagic != 12)
478: processWarningOccurred("Expected palette magic number 12; instead read "
479: + palletteMagic + " from this image.");
480:
481: iis.readFully(largePalette);
482: iis.reset();
483:
484: colorModel = new IndexColorModel(
485: metadata.bitsPerPixel, 256, largePalette,
486: 0, false);
487: sampleModel = colorModel
488: .createCompatibleSampleModel(width, height);
489: } else {
490: int msize = metadata.bitsPerPixel == 1 ? 2 : 16;
491: colorModel = new IndexColorModel(
492: metadata.bitsPerPixel, msize, smallPalette,
493: 0, false);
494: sampleModel = colorModel
495: .createCompatibleSampleModel(width, height);
496: }
497: }
498: } else {
499: ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
500: int[] nBits = { 8, 8, 8 };
501: colorModel = new ComponentColorModel(cs, nBits, false,
502: false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
503: sampleModel = new ComponentSampleModel(
504: DataBuffer.TYPE_BYTE, width, height, 1, width
505: * colorPlanes, new int[] { 0, width,
506: width * 2 });
507: }
508:
509: originalSampleModel = sampleModel;
510: originalColorModel = colorModel;
511:
512: gotHeader = true;
513: }
514: }
|