/*
* $RCSfile: PCXImageReader.java,v $
*
*
* Copyright (c) 2007 Sun Microsystems, Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistribution of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistribution in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* Neither the name of Sun Microsystems, Inc. or the names of
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any
* kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
* WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
* EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
* NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
* USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
* ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
* CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
* REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
* INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed or intended for
* use in the design, construction, operation or maintenance of any
* nuclear facility.
*
* $Revision: 1.3 $
* $Date: 2007/09/07 19:13:02 $
* $State: Exp $
*/
package com.sun.media.imageioimpl.plugins.pcx;
import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.image.*;
import java.io.*;
import java.nio.ByteOrder;
import java.util.*;
import javax.imageio.*;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.stream.ImageInputStream;
public class PCXImageReader extends ImageReader implements PCXConstants {
private ImageInputStream iis;
private int width, height;
private boolean gotHeader = false;
private byte manufacturer;
private byte encoding;
private short xmax, ymax;
private byte[] smallPalette = new byte[3 * 16];
private byte[] largePalette = new byte[3 * 256];
private byte colorPlanes;
private short bytesPerLine;
private short paletteType;
private PCXMetadata metadata;
private SampleModel sampleModel, originalSampleModel;
private ColorModel colorModel, originalColorModel;
/** The destination region. */
private Rectangle destinationRegion;
/** The source region. */
private Rectangle sourceRegion;
/** The destination image. */
private BufferedImage bi;
/** Indicates whether subsampled, subregion is required, and offset is
* defined
*/
private boolean noTransform = true;
/** Indicates whether subband is selected. */
private boolean seleBand = false;
/** The scaling factors. */
private int scaleX, scaleY;
/** source and destination bands. */
private int[] sourceBands, destBands;
public PCXImageReader(PCXImageReaderSpi imageReaderSpi) {
super(imageReaderSpi);
}
public void setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata) {
super.setInput(input, seekForwardOnly, ignoreMetadata);
iis = (ImageInputStream) input; // Always works
if (iis != null)
iis.setByteOrder(ByteOrder.LITTLE_ENDIAN);
gotHeader = false;
}
public int getHeight(int imageIndex) throws IOException {
checkIndex(imageIndex);
readHeader();
return height;
}
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
checkIndex(imageIndex);
readHeader();
return metadata;
}
public Iterator getImageTypes(int imageIndex) throws IOException {
checkIndex(imageIndex);
readHeader();
return Collections.singletonList(new ImageTypeSpecifier(originalColorModel, originalSampleModel)).iterator();
}
public int getNumImages(boolean allowSearch) throws IOException {
if (iis == null) {
throw new IllegalStateException("input is null");
}
if (seekForwardOnly && allowSearch) {
throw new IllegalStateException("cannot search with forward only input");
}
return 1;
}
public IIOMetadata getStreamMetadata() throws IOException {
return null;
}
public int getWidth(int imageIndex) throws IOException {
checkIndex(imageIndex);
readHeader();
return width;
}
public BufferedImage read(int imageIndex, ImageReadParam param) throws IOException {
checkIndex(imageIndex);
readHeader();
if (iis == null)
throw new IllegalStateException("input is null");
BufferedImage img;
clearAbortRequest();
processImageStarted(imageIndex);
if (param == null)
param = getDefaultReadParam();
sourceRegion = new Rectangle(0, 0, 0, 0);
destinationRegion = new Rectangle(0, 0, 0, 0);
computeRegions(param, this.width, this.height, param.getDestination(), sourceRegion, destinationRegion);
scaleX = param.getSourceXSubsampling();
scaleY = param.getSourceYSubsampling();
// If the destination band is set used it
sourceBands = param.getSourceBands();
destBands = param.getDestinationBands();
seleBand = (sourceBands != null) && (destBands != null);
noTransform = destinationRegion.equals(new Rectangle(0, 0, width, height)) || seleBand;
if (!seleBand) {
sourceBands = new int[colorPlanes];
destBands = new int[colorPlanes];
for (int i = 0; i < colorPlanes; i++)
destBands[i] = sourceBands[i] = i;
}
// If the destination is provided, then use it. Otherwise, create new one
bi = param.getDestination();
// Get the image data.
WritableRaster raster = null;
if (bi == null) {
if (sampleModel != null && colorModel != null) {
sampleModel = sampleModel.createCompatibleSampleModel(destinationRegion.width + destinationRegion.x, destinationRegion.height
+ destinationRegion.y);
if (seleBand)
sampleModel = sampleModel.createSubsetSampleModel(sourceBands);
raster = Raster.createWritableRaster(sampleModel, new Point(0, 0));
bi = new BufferedImage(colorModel, raster, false, null);
}
} else {
raster = bi.getWritableTile(0, 0);
sampleModel = bi.getSampleModel();
colorModel = bi.getColorModel();
noTransform &= destinationRegion.equals(raster.getBounds());
}
byte bdata[] = null; // buffer for byte data
if (sampleModel.getDataType() == DataBuffer.TYPE_BYTE)
bdata = (byte[]) ((DataBufferByte) raster.getDataBuffer()).getData();
readImage(bdata);
if (abortRequested())
processReadAborted();
else
processImageComplete();
return bi;
}
private void readImage(byte[] data) throws IOException {
byte[] scanline = new byte[bytesPerLine*colorPlanes];
if (noTransform) {
try {
int offset = 0;
int nbytes = (width * metadata.bitsPerPixel + 8 - metadata.bitsPerPixel) / 8;
for (int line = 0; line < height; line++) {
readScanLine(scanline);
for (int band = 0; band < colorPlanes; band++) {
System.arraycopy(scanline, bytesPerLine * band, data, offset, nbytes);
offset += nbytes;
}
processImageProgress(100.0F * line / height);
}
} catch (EOFException e) {
}
} else {
if (metadata.bitsPerPixel == 1)
read1Bit(data);
else if (metadata.bitsPerPixel == 4)
read4Bit(data);
else
read8Bit(data);
}
}
private void read1Bit(byte[] data) throws IOException {
byte[] scanline = new byte[bytesPerLine];
try {
// skip until source region
for (int line = 0; line < sourceRegion.y; line++) {
readScanLine(scanline);
}
int lineStride =
((MultiPixelPackedSampleModel)sampleModel).getScanlineStride();
// cache the values to avoid duplicated computation
int[] srcOff = new int[destinationRegion.width];
int[] destOff = new int[destinationRegion.width];
int[] srcPos = new int[destinationRegion.width];
int[] destPos = new int[destinationRegion.width];
for (int i = destinationRegion.x, x = sourceRegion.x, j = 0; i < destinationRegion.x + destinationRegion.width; i++, j++, x += scaleX) {
srcPos[j] = x >> 3;
srcOff[j] = 7 - (x & 7);
destPos[j] = i >> 3;
destOff[j] = 7 - (i & 7);
}
int k = destinationRegion.y * lineStride;
for (int line = 0; line < sourceRegion.height; line++) {
readScanLine(scanline);
if (line % scaleY == 0) {
for (int i = 0; i < destinationRegion.width; i++) {
//get the bit and assign to the data buffer of the raster
int v = (scanline[srcPos[i]] >> srcOff[i]) & 1;
data[k + destPos[i]] |= v << destOff[i];
}
k += lineStride;
}
processImageProgress(100.0F * line / sourceRegion.height);
}
} catch (EOFException e) {
}
}
private void read4Bit(byte[] data) throws IOException {
byte[] scanline = new byte[bytesPerLine];
try {
int lineStride =
((MultiPixelPackedSampleModel)sampleModel).getScanlineStride();
// cache the values to avoid duplicated computation
int[] srcOff = new int[destinationRegion.width];
int[] destOff = new int[destinationRegion.width];
int[] srcPos = new int[destinationRegion.width];
int[] destPos = new int[destinationRegion.width];
for (int i = destinationRegion.x, x = sourceRegion.x, j = 0;
i < destinationRegion.x + destinationRegion.width;
i++, j++, x += scaleX) {
srcPos[j] = x >> 1;
srcOff[j] = (1 - (x & 1)) << 2;
destPos[j] = i >> 1;
destOff[j] = (1 - (i & 1)) << 2;
}
int k = destinationRegion.y * lineStride;
for (int line = 0; line < sourceRegion.height; line++) {
readScanLine(scanline);
if (abortRequested())
break;
if (line % scaleY == 0) {
for (int i = 0; i < destinationRegion.width; i++) {
//get the bit and assign to the data buffer of the raster
int v = (scanline[srcPos[i]] >> srcOff[i]) & 0x0F;
data[k + destPos[i]] |= v << destOff[i];
}
k += lineStride;
}
processImageProgress(100.0F * line / sourceRegion.height);
}
}catch(EOFException e){
}
}
/* also handles 24 bit (three 8 bit planes) */
private void read8Bit(byte[] data) throws IOException {
byte[] scanline = new byte[colorPlanes * bytesPerLine];
try {
// skip until source region
for (int line = 0; line < sourceRegion.y; line++) {
readScanLine(scanline);
}
int dstOffset = destinationRegion.y * (destinationRegion.x + destinationRegion.width) * colorPlanes;
for (int line = 0; line < sourceRegion.height; line++) {
readScanLine(scanline);
if (line % scaleY == 0) {
int srcOffset = sourceRegion.x;
for (int band = 0; band < colorPlanes; band++) {
dstOffset += destinationRegion.x;
for (int x = 0; x < destinationRegion.width; x += scaleX) {
data[dstOffset++] = scanline[srcOffset + x];
}
srcOffset += bytesPerLine;
}
}
processImageProgress(100.0F * line / sourceRegion.height);
}
} catch (EOFException e) {
}
}
private void readScanLine(byte[] buffer) throws IOException {
int max = bytesPerLine * colorPlanes;
for (int j = 0; j < max;) {
int val = iis.readUnsignedByte();
if ((val & 0xC0) == 0xC0) {
int count = val & ~0xC0;
val = iis.readUnsignedByte();
for (int k = 0; k < count && j < max; k++) {
buffer[j++] = (byte) (val & 0xFF);
}
} else {
buffer[j++] = (byte) (val & 0xFF);
}
}
}
private void checkIndex(int imageIndex) {
if (imageIndex != 0) {
throw new IndexOutOfBoundsException("only one image exists in the stream");
}
}
private void readHeader() throws IOException {
if (gotHeader) {
iis.seek(128);
return;
}
metadata = new PCXMetadata();
manufacturer = iis.readByte(); // manufacturer
if (manufacturer != MANUFACTURER)
throw new IllegalStateException("image is not a PCX file");
metadata.version = iis.readByte(); // version
encoding = iis.readByte(); // encoding
if (encoding != ENCODING)
throw new IllegalStateException("image is not a PCX file, invalid encoding " + encoding);
metadata.bitsPerPixel = iis.readByte();
metadata.xmin = iis.readShort();
metadata.ymin = iis.readShort();
xmax = iis.readShort();
ymax = iis.readShort();
metadata.hdpi = iis.readShort();
metadata.vdpi = iis.readShort();
iis.readFully(smallPalette);
iis.readByte(); // reserved
colorPlanes = iis.readByte();
bytesPerLine = iis.readShort();
paletteType = iis.readShort();
metadata.hsize = iis.readShort();
metadata.vsize = iis.readShort();
iis.skipBytes(54); // skip filler
width = xmax - metadata.xmin + 1;
height = ymax - metadata.ymin + 1;
if (colorPlanes == 1) {
if (paletteType == PALETTE_GRAYSCALE) {
ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
int[] nBits = { 8 };
colorModel = new ComponentColorModel(cs, nBits, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
sampleModel = new ComponentSampleModel(DataBuffer.TYPE_BYTE, width, height, 1, width, new int[] { 0 });
} else {
if (metadata.bitsPerPixel == 8) {
// read palette from end of file, then reset back to image data
iis.mark();
if (iis.length() == -1) {
// read until eof, and work backwards
while (iis.read() != -1)
;
iis.seek(iis.getStreamPosition() - 256 * 3 - 1);
} else {
iis.seek(iis.length() - 256 * 3 - 1);
}
int palletteMagic = iis.read();
if(palletteMagic != 12)
processWarningOccurred("Expected palette magic number 12; instead read "+
palletteMagic+" from this image.");
iis.readFully(largePalette);
iis.reset();
colorModel = new IndexColorModel(metadata.bitsPerPixel, 256, largePalette, 0, false);
sampleModel = colorModel.createCompatibleSampleModel(width, height);
} else {
int msize = metadata.bitsPerPixel == 1 ? 2 : 16;
colorModel = new IndexColorModel(metadata.bitsPerPixel, msize, smallPalette, 0, false);
sampleModel = colorModel.createCompatibleSampleModel(width, height);
}
}
} else {
ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
int[] nBits = { 8, 8, 8 };
colorModel = new ComponentColorModel(cs, nBits, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
sampleModel = new ComponentSampleModel(DataBuffer.TYPE_BYTE, width, height, 1, width * colorPlanes, new int[] { 0, width, width * 2 });
}
originalSampleModel = sampleModel;
originalColorModel = colorModel;
gotHeader = true;
}
}
|