diff options
Diffstat (limited to 'src/jake2/imageio/PCXImageReader.java')
-rw-r--r-- | src/jake2/imageio/PCXImageReader.java | 322 |
1 files changed, 322 insertions, 0 deletions
diff --git a/src/jake2/imageio/PCXImageReader.java b/src/jake2/imageio/PCXImageReader.java new file mode 100644 index 0000000..c81e623 --- /dev/null +++ b/src/jake2/imageio/PCXImageReader.java @@ -0,0 +1,322 @@ +/* + * Created on Nov 17, 2003 + * + */ +package jake2.imageio; + +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.imageio.IIOException; +import javax.imageio.ImageReadParam; +import javax.imageio.ImageReader; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.stream.ImageInputStream; + +/** + * @author cwei + * + */ +public class PCXImageReader extends ImageReader { + + private static Logger logger = + Logger.getLogger(PCXImageReader.class.getName()); + + ImageInputStream stream = null; + PCX.Header header = null; + + public PCXImageReader(ImageReaderSpi originatingProvider) { + super(originatingProvider); + } + + public void setInput(Object input, boolean seekForwardOnly) { + super.setInput(input, seekForwardOnly); + if (input == null) { + this.stream = null; + return; + } + if (input instanceof ImageInputStream) { + this.stream = (ImageInputStream) input; + } else { + throw new IllegalArgumentException("bad input"); + } + } + + public int getHeight(int imageIndex) throws IOException { + checkIndex(imageIndex); + readHeader(); + return header.getHeight(); + } + + public int getWidth(int imageIndex) throws IOException { + checkIndex(imageIndex); + readHeader(); + return header.getWidth(); + } + + public int getNumImages(boolean allowSearch) throws IOException { + // only 1 image + return 1; + } + + public Iterator getImageTypes(int imageIndex) throws IOException { + checkIndex(imageIndex); + readHeader(); + + ImageTypeSpecifier imageType = null; + java.util.List l = new ArrayList(1); + + imageType = + ImageTypeSpecifier.createIndexed( + Q2ColorMap.RED, + Q2ColorMap.GREEN, + Q2ColorMap.BLUE, + Q2ColorMap.ALPHA, + 8, + DataBuffer.TYPE_BYTE); + + l.add(imageType); + return l.iterator(); + } + + public IIOMetadata getStreamMetadata() throws IOException { + return null; + } + + public IIOMetadata getImageMetadata(int imageIndex) throws IOException { + return null; + } + + public BufferedImage read(int imageIndex, ImageReadParam param) + throws IOException { + + checkIndex(imageIndex); + readHeader(); + + int width = header.getWidth(); + int height = header.getHeight(); + + // Compute initial source region, clip against destination later + Rectangle sourceRegion = getSourceRegion(param, width, height); + + // Set everything to default values + int sourceXSubsampling = 1; + int sourceYSubsampling = 1; + int[] sourceBands = null; + int[] destinationBands = null; + Point destinationOffset = new Point(0, 0); + + // Get values from the ImageReadParam, if any + if (param != null) { + sourceXSubsampling = param.getSourceXSubsampling(); + sourceYSubsampling = param.getSourceYSubsampling(); + sourceBands = param.getSourceBands(); + destinationBands = param.getDestinationBands(); + destinationOffset = param.getDestinationOffset(); + } + + // Get the specified detination image or create a new one + BufferedImage dst = + getDestination(param, getImageTypes(0), width, height); + + // Enure band settings from param are compatible with images + int inputBands = 1; + checkReadParamBandSettings( + param, + inputBands, + dst.getSampleModel().getNumBands()); + + int[] bandOffsets = new int[inputBands]; + for (int i = 0; i < inputBands; i++) { + bandOffsets[i] = i; + } + int bytesPerRow = width * inputBands; + DataBufferByte rowDB = new DataBufferByte(bytesPerRow); + WritableRaster rowRas = + Raster.createInterleavedRaster( + rowDB, + width, + 1, + bytesPerRow, + inputBands, + bandOffsets, + new Point(0, 0)); + byte[] rowBuf = rowDB.getData(); + + // Create an int[] that can a single pixel + int[] pixel = rowRas.getPixel(0, 0, (int[]) null); + + WritableRaster imRas = dst.getWritableTile(0, 0); + int dstMinX = imRas.getMinX(); + int dstMaxX = dstMinX + imRas.getWidth() - 1; + int dstMinY = imRas.getMinY(); + int dstMaxY = dstMinY + imRas.getHeight() - 1; + + // Create a child raster exposing only the desired source bands + if (sourceBands != null) { + rowRas = + rowRas.createWritableChild(0, 0, width, 1, 0, 0, sourceBands); + } + + // Create a child raster exposing only the desired dest bands + if (destinationBands != null) { + imRas = + imRas.createWritableChild( + 0, + 0, + imRas.getWidth(), + imRas.getHeight(), + 0, + 0, + destinationBands); + + } + + int dataByte = 0; + int runLength = 0; + + for (int srcY = 0; srcY < height; srcY++) { + // Read the row + try { + /* + * run length decoding for PCX images + */ + int index = 0; + + while (index < rowBuf.length) { + while (runLength-- > 0 && index < rowBuf.length) { + rowBuf[index++] = (byte) (dataByte & 0xff); + } + dataByte = stream.readUnsignedByte(); + if ((dataByte & 0xc0) == 0xc0) { + runLength = dataByte & 0x3f; + dataByte = stream.readUnsignedByte(); + } else { + runLength = 1; + } + } + } catch (IOException e) { + throw new IIOException("Error reading line " + srcY, e); + } + + // Reject rows that lie outside the source region, + // or which aren't part of the subsampling + if ((srcY < sourceRegion.y) + || (srcY >= sourceRegion.y + sourceRegion.height) + || (((srcY - sourceRegion.y) % sourceYSubsampling) != 0)) { + continue; + } + + // Determine where the row will go in the destination + int dstY = + destinationOffset.y + + (srcY - sourceRegion.y) / sourceYSubsampling; + if (dstY < dstMinY) { + continue; // The row is above imRas + } + if (dstY > dstMaxY) { + break; // We're done with the image + } + + // Copy each (subsampled) source pixel into imRas + for (int srcX = sourceRegion.x; + srcX < sourceRegion.x + sourceRegion.width; + srcX++) { + if (((srcX - sourceRegion.x) % sourceXSubsampling) != 0) { + continue; + } + int dstX = + destinationOffset.x + + (srcX - sourceRegion.x) / sourceXSubsampling; + if (dstX < dstMinX) { + continue; // The pixel is to the left of imRas + } + if (dstX > dstMaxX) { + break; // We're done with the row + } + + // Copy the pixel, sub-banding is done automatically + rowRas.getPixel(srcX, 0, pixel); + imRas.setPixel(dstX, dstY, pixel); + } + } + if ((stream.readUnsignedByte()) == 0x0c) { + logger.log( + Level.FINE, + "PCX has a color palette with " + + (stream.length() - stream.getStreamPosition()) + + " Bytes, but use the default palette (quake2)"); + } + return dst; + } + + private void checkIndex(int imageIndex) { + if (imageIndex != 0) { + throw new IndexOutOfBoundsException("bad image index"); + } + } + +// buggy version +/* private void decodeRow(byte[] buffer) throws IOException { + int dataByte = 0; + int runLength = 0; + int index = 0; + + while (index < buffer.length) { + dataByte = stream.readUnsignedByte(); + if ((dataByte & 0xc0) == 0xc0) { + runLength = dataByte & 0x3f; + dataByte = stream.readUnsignedByte(); + } else { + runLength = 1; + } + + while (runLength-- > 0 && index < buffer.length) { + buffer[index++] = (byte) (dataByte & 0xff); + } + assert(runLength == -1) : "runLength decoding bug: " + runLength; + } + } +*/ + private void readHeader() throws IIOException { + + if (header != null) + return; + + logger.log(Level.FINE, "PCX read header"); + + if (stream == null) { + if (this.input == null) { + throw new IllegalStateException("No input stream"); + } + stream = (ImageInputStream) input; + } + + byte[] buffer = new byte[PCX.HEADER_SIZE]; + + try { + stream.readFully(buffer); + this.header = new PCX.Header(buffer); + logger.log( + Level.FINE, + "PCX horzRes: " + + header.getWidth() + + " height: " + + header.getHeight()); + } catch (IOException e) { + throw new IIOException("Error reading quake2 PCX header", e); + } + } +} |