aboutsummaryrefslogtreecommitdiffstats
path: root/src/jake2/imageio/PCXImageReader.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/jake2/imageio/PCXImageReader.java')
-rw-r--r--src/jake2/imageio/PCXImageReader.java322
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);
+ }
+ }
+}