aboutsummaryrefslogtreecommitdiffstats
path: root/src/jake2/imageio
diff options
context:
space:
mode:
Diffstat (limited to 'src/jake2/imageio')
-rw-r--r--src/jake2/imageio/ImageFrame.java63
-rw-r--r--src/jake2/imageio/PCX.java229
-rw-r--r--src/jake2/imageio/PCXImageReader.java322
-rw-r--r--src/jake2/imageio/PCXImageReaderSpi.java99
-rw-r--r--src/jake2/imageio/Q2ColorMap.java91
-rw-r--r--src/jake2/imageio/WAL.java169
-rw-r--r--src/jake2/imageio/WALImageReader.java273
-rw-r--r--src/jake2/imageio/WALImageReaderSpi.java127
8 files changed, 1373 insertions, 0 deletions
diff --git a/src/jake2/imageio/ImageFrame.java b/src/jake2/imageio/ImageFrame.java
new file mode 100644
index 0000000..52cf9e4
--- /dev/null
+++ b/src/jake2/imageio/ImageFrame.java
@@ -0,0 +1,63 @@
+/*
+ * Created on Apr 26, 2003
+ *
+ */
+package jake2.imageio;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.image.BufferedImage;
+import javax.swing.JFrame;
+
+/**
+ * @author cwei
+ *
+ */
+public class ImageFrame extends JFrame {
+
+ BufferedImage image;
+ Component pane;
+
+ public ImageFrame(BufferedImage image) {
+ super();
+ this.image = image;
+
+ pane = getContentPane();
+ setIconImage(image);
+ setSize(640, 480);
+
+ addWindowListener(new WindowAdapter() {
+ public void windowClosing(WindowEvent e) {
+ System.exit(0);
+ }
+ });
+ }
+
+ public void paint(Graphics g) {
+ super.paint(g);
+ Graphics2D g2 = (Graphics2D) pane.getGraphics();
+ if (this.image != null) {
+ g2.drawImage(
+ image,
+ Math.max(0, (getWidth() - image.getWidth()) / 2),
+ Math.max(0, (getHeight() - image.getHeight()) / 2),
+ Color.LIGHT_GRAY,
+ pane);
+ } else {
+ g2.drawString(
+ "EMPTY IMAGE",
+ this.getWidth() / 4,
+ this.getHeight() / 2);
+ }
+ }
+
+ public void showImage(BufferedImage image) {
+ this.image = image;
+ this.repaint();
+ }
+
+}
diff --git a/src/jake2/imageio/PCX.java b/src/jake2/imageio/PCX.java
new file mode 100644
index 0000000..eb71465
--- /dev/null
+++ b/src/jake2/imageio/PCX.java
@@ -0,0 +1,229 @@
+/*
+ * Created on Nov 18, 2003
+ *
+ */
+package jake2.imageio;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * @author cwei
+ *
+ */
+public class PCX {
+
+ public static final int HEADER_SIZE = 128;
+
+ /**
+ * <code>
+ typedef struct
+ {
+ char manufacturer;
+ char version;
+ char encoding;
+ char bits_per_pixel;
+ unsigned short xmin,ymin,xmax,ymax;
+ unsigned short horzRes,vertRes;
+ unsigned char palette[48];
+ char reserved;
+ char color_planes;
+ unsigned short bytes_per_line;
+ unsigned short palette_type;
+ char filler[58];
+ unsigned char data; // unbounded
+ } pcx_t;
+
+ </code>
+ */
+ public static class Header {
+
+ // size of byte arrays
+ static final int PALETTE_SIZE = 48;
+ static final int FILLER_SIZE = 58;
+
+ byte manufacturer;
+ byte version;
+ byte encoding;
+ byte bitsPerPixel;
+ int xmin, ymin, xmax, ymax; // unsigned short
+ int horzRes, vertRes; // unsigned short
+ byte[] palette; //unsigned byte
+ byte reserved;
+ byte colorPlanes;
+ int bytesPerLine; // unsigned short
+ int paletteType; // unsigned short
+ byte[] filler;
+
+
+
+ public Header(byte[] headerBytes) {
+ if (headerBytes == null || headerBytes.length != 128) {
+ throw new IllegalArgumentException("invalid quake2 pcx header");
+ }
+
+ ByteBuffer b = ByteBuffer.wrap(headerBytes);
+ // is stored as little endian
+ b.order(ByteOrder.LITTLE_ENDIAN);
+
+ // fill header
+ manufacturer = b.get();
+ version = b.get();
+ encoding = b.get();
+ bitsPerPixel = b.get();
+ xmin = b.getShort() & 0xffff;
+ ymin = b.getShort() & 0xffff;
+ xmax = b.getShort() & 0xffff;
+ ymax = b.getShort() & 0xffff;
+ horzRes = b.getShort() & 0xffff;
+ vertRes = b.getShort() & 0xffff;
+ b.get(palette = new byte[PALETTE_SIZE]);
+ reserved = b.get();
+ colorPlanes = b.get();
+ bytesPerLine = b.getShort() & 0xffff;
+ paletteType = b.getShort() & 0xffff;
+ b.get(filler = new byte[FILLER_SIZE]);
+
+ // check some attributes
+ checkHeader();
+ }
+
+ private void checkHeader() {
+
+ if (this.getManufacturer() != 0x0a
+ || this.getVersion() != 5
+ || this.getEncoding() != 1
+ || this.getBitsPerPixel() != 8
+ || this.getXmax() >= 640
+ || this.getYmax() >= 480) {
+ throw new IllegalArgumentException("invalid quake2 pcx header");
+ }
+ }
+
+ /**
+ * @return
+ */
+ public byte getBitsPerPixel() {
+ return bitsPerPixel;
+ }
+
+ /**
+ * @return
+ */
+ public int getBytesPerLine() {
+ return bytesPerLine;
+ }
+
+ /**
+ * @return
+ */
+ public byte getColorPlanes() {
+ return colorPlanes;
+ }
+
+ /**
+ * @return
+ */
+ public byte getEncoding() {
+ return encoding;
+ }
+
+ /**
+ * @return
+ */
+ public byte[] getFiller() {
+ return filler;
+ }
+
+ /**
+ * @return
+ */
+ public int getHeight() {
+ return ymax - ymin + 1;
+ }
+
+ /**
+ * @return
+ */
+ public byte getManufacturer() {
+ return manufacturer;
+ }
+
+ /**
+ * @return
+ */
+ public byte[] getPalette() {
+ return palette;
+ }
+
+ /**
+ * @return
+ */
+ public int getPaletteType() {
+ return paletteType;
+ }
+
+ /**
+ * @return
+ */
+ public byte getReserved() {
+ return reserved;
+ }
+
+ /**
+ * @return
+ */
+ public byte getVersion() {
+ return version;
+ }
+
+ /**
+ * @return
+ */
+ public int getWidth() {
+ return xmax - xmin + 1;
+ }
+
+ /**
+ * @return
+ */
+ public int getXmax() {
+ return xmax;
+ }
+
+ /**
+ * @return
+ */
+ public int getXmin() {
+ return xmin;
+ }
+
+ /**
+ * @return
+ */
+ public int getYmax() {
+ return ymax;
+ }
+
+ /**
+ * @return
+ */
+ public int getYmin() {
+ return ymin;
+ }
+ /**
+ * @return
+ */
+ public int getHorzRes() {
+ return horzRes;
+ }
+
+ /**
+ * @return
+ */
+ public int getVertRes() {
+ return vertRes;
+ }
+
+ }
+} \ No newline at end of file
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);
+ }
+ }
+}
diff --git a/src/jake2/imageio/PCXImageReaderSpi.java b/src/jake2/imageio/PCXImageReaderSpi.java
new file mode 100644
index 0000000..f4089e3
--- /dev/null
+++ b/src/jake2/imageio/PCXImageReaderSpi.java
@@ -0,0 +1,99 @@
+/*
+ * Created on Nov 17, 2003
+ *
+ */
+package jake2.imageio;
+
+import java.io.IOException;
+import java.util.Locale;
+
+import javax.imageio.ImageReader;
+import javax.imageio.spi.ImageReaderSpi;
+import javax.imageio.stream.ImageInputStream;
+
+/**
+ * @author cwei
+ *
+ * put the own ImageReaderSpi class name in the file (relative to classpath):
+ * <code>
+ * META-INF/services/javax.imageio.spi.ImageReaderSpi
+ * </code>
+ */
+public class PCXImageReaderSpi extends ImageReaderSpi {
+
+ static final String vendorName = "[email protected]";
+ static final String version = "1.0_beta";
+ static final String[] names = { "quake2 pcx" };
+ static final String[] suffixes = { "pcx" };
+ static final String[] MIMETypes = { "image/x-quake2-pcx" };
+ static final String readerClassName = "jake2.imageio.PCXImageReader";
+ static final String[] writerSpiNames = null; // { "jake2.imageio.PCXImageWriterSpi" };
+
+ // Metadata formats, more information below
+ static final boolean supportsStandardStreamMetadataFormat = false;
+ static final String nativeStreamMetadataFormatName = null;
+
+ static final String nativeStreamMetadataFormatClassName = null;
+ static final String[] extraStreamMetadataFormatNames = null;
+ static final String[] extraStreamMetadataFormatClassNames = null;
+ static final boolean supportsStandardImageMetadataFormat = false;
+ static final String nativeImageMetadataFormatName =
+ "jake2.imageio.PCXMetaData_1.0";
+ static final String nativeImageMetadataFormatClassName = null; // "jake2.imageio.PCXMetadata";
+ static final String[] extraImageMetadataFormatNames = null;
+ static final String[] extraImageMetadataFormatClassNames = null;
+
+ public PCXImageReaderSpi() {
+
+ super(
+ vendorName,
+ version,
+ names,
+ suffixes,
+ MIMETypes,
+ readerClassName,
+ ImageReaderSpi.STANDARD_INPUT_TYPE,
+ // Accept ImageInputStreams
+ writerSpiNames,
+ supportsStandardStreamMetadataFormat,
+ nativeStreamMetadataFormatName,
+ nativeStreamMetadataFormatClassName,
+ extraStreamMetadataFormatNames,
+ extraStreamMetadataFormatClassNames,
+ supportsStandardImageMetadataFormat,
+ nativeImageMetadataFormatName,
+ nativeImageMetadataFormatClassName,
+ extraImageMetadataFormatNames,
+ extraImageMetadataFormatClassNames);
+ }
+
+ public boolean canDecodeInput(Object source) throws IOException {
+ if (!(source instanceof ImageInputStream)) {
+ return false;
+ }
+ ImageInputStream stream = (ImageInputStream)source;
+ byte[] buffer = new byte[PCX.HEADER_SIZE];
+ try {
+ stream.mark();
+ stream.readFully(buffer);
+ stream.reset();
+ // buffer will be converted to members and header checked
+ PCX.Header pcx = new PCX.Header(buffer);
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ return true;
+ }
+
+ public ImageReader createReaderInstance(Object extension)
+ throws IOException {
+ return new PCXImageReader(this);
+ }
+
+ /**
+ * @see javax.imageio.spi.IIOServiceProvider#getDescription(java.util.Locale)
+ */
+ public String getDescription(Locale locale) {
+ return "id-software's Quake2 pcx format";
+ }
+}
diff --git a/src/jake2/imageio/Q2ColorMap.java b/src/jake2/imageio/Q2ColorMap.java
new file mode 100644
index 0000000..6476e61
--- /dev/null
+++ b/src/jake2/imageio/Q2ColorMap.java
@@ -0,0 +1,91 @@
+/*
+ * Created on Nov 18, 2003
+ *
+ */
+package jake2.imageio;
+
+/**
+ * @author cwei
+ *
+ */
+public interface Q2ColorMap {
+ // Red channel
+ static final byte[] RED = {
+ 0, 15, 31, 47, 63, 75, 91, 107, 123, -117, -101, -85, -69, -53, -37, -21,
+ 99, 91, 83, 79, 71, 63, 59, 51, 47, 43, 39, 35, 27, 23, 19, 15,
+ 95, 91, 91, 87, 83, 79, 71, 63, 59, 51, 47, 39, 35, 27, 23, 19,
+ -113, 123, 115, 103, -49, -89, -117, 111, -21, -53, -81, -109, 119, 91, 63, 35,
+ -89, -97, -105, -117, 127, 115, 103, 87, 75, 67, 59, 51, 43, 35, 27, 19,
+ 123, 115, 107, 103, 95, 87, 83, 75, 67, 63, 55, 47, 39, 31, 23, 15,
+ 111, 95, 83, 67, 55, 39, 27, 15, -77, -65, -53, -41, -53, -77, -97, -121,
+ 115, 91, 71, 47, 23, 19, 15, 11, 7, 7, 7, 0, 0, 0, 0, 0,
+ -117, -125, 123, 115, 107, 99, 91, 87, 75, 63, 51, 43, 31, 19, 11, 0,
+ -105, -113, -121, 127, 119, 115, 107, 99, 91, 79, 67, 55, 47, 35, 23, 15,
+ -97, -109, -117, 127, 119, 107, 99, 87, 79, 67, 55, 43, 31, 23, 11, 0,
+ 119, 111, 103, 99, 91, 83, 75, 71, 63, 55, 47, 39, 35, 27, 19, 11,
+ -101, -113, -121, 123, 115, 103, 95, 87, 75, 63, 55, 47, 35, 27, 19, 11,
+ 0, 35, 63, 83, 95, 95, 95, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -17, -29, -45, -57, -73, -85, -101, -113, 127, 115, 95, 71, 47, 27,
+ -17, 55, -1, 0, 43, 27, 19, -21, -61, -97, 123, -21, -57, -89, -121, -97
+ };
+
+ // Green channel
+ static final byte[] GREEN = {
+ 0, 15, 31, 47, 63, 75, 91, 107, 123, -117, -101, -85, -69, -53, -37, -21,
+ 75, 67, 63, 59, 55, 47, 43, 39, 35, 31, 27, 23, 19, 15, 15, 11,
+ 95, 91, 83, 79, 75, 71, 63, 59, 55, 47, 43, 39, 35, 27, 23, 19,
+ 119, 99, 91, 79, -105, 123, 103, 83, -97, -117, 119, 99, 79, 59, 39, 23,
+ 59, 47, 43, 39, 31, 23, 23, 19, 15, 15, 15, 11, 11, 11, 7, 7,
+ 95, 87, 83, 79, 71, 67, 63, 55, 51, 47, 39, 35, 27, 23, 15, 11,
+ 59, 55, 47, 43, 35, 27, 19, 11, 91, 123, -101, -69, -41, -57, -73, -89,
+ -105, -121, 119, 103, 83, 75, 67, 63, 55, 47, 39, 31, 23, 15, 7, 0,
+ 87, 79, 71, 67, 59, 51, 47, 43, 35, 31, 27, 19, 15, 11, 7, 0,
+ -97, -105, -117, -125, 123, 115, 107, 99, 91, 79, 67, 55, 47, 35, 23, 15,
+ 75, 67, 59, 55, 47, 43, 35, 31, 27, 23, 19, 15, 11, 7, 0, 0,
+ 123, 115, 107, 99, 91, 87, 79, 71, 63, 55, 47, 39, 31, 23, 15, 7,
+ -85, -97, -105, -117, -125, 119, 111, 103, 91, 79, 67, 59, 47, 35, 23, 15,
+ -1, -25, -45, -69, -89, -113, 123, -1, -1, -1, -1, -1, -1, -21, -41, -65,
+ -85, -109, 127, 107, 87, 71, 59, 43, 31, 23, 15, 7, 0, 0, 0, 0,
+ 0, 55, 0, 0, 43, 27, 19, -105, 115, 87, 63, -45, -85, -117, 107, 91
+ };
+
+ // Blue channel
+ static final byte[] BLUE = {
+ 0, 15, 31, 47, 63, 75, 91, 107, 123, -117, -101, -85, -69, -53, -37, -21,
+ 35, 31, 31, 27, 27, 23, 23, 19, 19, 19, 15, 15, 11, 11, 7, 7,
+ 111, 103, 95, 91, 83, 75, 67, 59, 55, 47, 43, 39, 35, 27, 23, 19,
+ 83, 67, 59, 47, 75, 59, 47, 39, 39, 35, 31, 27, 23, 15, 11, 7,
+ 43, 35, 27, 19, 15, 11, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 75, 67, 63, 59, 55, 51, 47, 43, 39, 35, 27, 23, 19, 15, 11, 7,
+ 23, 23, 23, 23, 19, 15, 11, 7, 79, 111, -109, -73, -33, -45, -61, -73,
+ -89, -101, -117, 127, 111, 103, 91, 83, 75, 63, 51, 43, 31, 19, 11, 0,
+ 87, 79, 71, 67, 59, 51, 47, 43, 35, 31, 27, 19, 15, 11, 7, 0,
+ 123, 115, 107, 99, 95, 87, 79, 71, 67, 59, 51, 43, 35, 27, 19, 11,
+ 63, 55, 47, 39, 35, 27, 23, 19, 15, 11, 11, 7, 7, 0, 0, 0,
+ -49, -61, -73, -89, -101, -113, 127, 115, 103, 87, 75, 63, 47, 35, 23, 7,
+ 123, 111, 99, 87, 75, 67, 59, 51, 39, 27, 19, 11, 7, 0, 0, 0,
+ 0, 15, 27, 39, 47, 51, 51, -1, -45, -89, 127, 83, 39, 31, 23, 15,
+ 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, -1, 0, -1, 35, 23, 15, 127, 83, 51, 27, -57, -101, 119, 87, 83
+ };
+
+ // Alpha channel
+ static final byte[] ALPHA = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0
+ };
+}
diff --git a/src/jake2/imageio/WAL.java b/src/jake2/imageio/WAL.java
new file mode 100644
index 0000000..1c499ef
--- /dev/null
+++ b/src/jake2/imageio/WAL.java
@@ -0,0 +1,169 @@
+/*
+ * Created on Nov 18, 2003
+ *
+ */
+package jake2.imageio;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * @author cwei
+ *
+ */
+public class WAL {
+
+ public static final int HEADER_SIZE = 100;
+
+ /* struct wal_header
+ {
+ char name[32]; // name of the texture
+
+ uint32 width; // width (in pixels) of the largest mipmap level
+ uint32 height; // height (in pixels) of the largest mipmap level
+
+ int32 offset[4]; // byte offset of the start of each of the 4 mipmap levels
+
+ char next_name[32]; // name of the next texture in the animation
+
+ uint32 flags; // ?
+ uint32 contents; // ?
+ uint32 value; // ?
+ };
+ */
+ public static class Header {
+
+ // size of byte arrays
+ static final int NAME_SIZE = 32;
+ static final int OFFSET_SIZE = 4;
+
+ String name;
+ int width;
+ int height;
+ int[] offset; // file offsets for the 4 mipmap images
+ String nextName;
+ int flags; // unused
+ int contents; // unused
+ int value; // unused
+
+ public Header(byte[] headerBytes) {
+ if (headerBytes == null || headerBytes.length != HEADER_SIZE) {
+ throw new IllegalArgumentException("invalid quake2 wal header");
+ }
+
+ ByteBuffer b = ByteBuffer.wrap(headerBytes);
+ // is stored as little endian
+ b.order(ByteOrder.LITTLE_ENDIAN);
+
+ byte[] tmp = new byte[NAME_SIZE];
+ // fill header
+
+ // name
+ b.get(tmp);
+ try {
+ name = new String(tmp, "ISO-8859-1");
+ } catch (UnsupportedEncodingException e) {
+ name = new String(tmp);
+ }
+ // width
+ width = b.getInt();
+ assert(width >= 0) : "unsigned int bug"; // true means ok.
+ // height
+ height = b.getInt();
+ assert(height >= 0) : "unsigned int bug"; // true means ok.
+ // 4 offsets
+ offset =
+ new int[] { b.getInt(), b.getInt(), b.getInt(), b.getInt()};
+ // nextName
+ b.get(tmp);
+ try {
+ nextName = new String(tmp, "ISO-8859-1");
+ } catch (UnsupportedEncodingException e1) {
+ name = new String(tmp);
+ }
+ // unused entries
+ flags = b.getInt();
+ contents = b.getInt();
+ value = b.getInt();
+
+ // check some attributes
+ checkHeader();
+ }
+
+ private void checkHeader() {
+ // start of mipmaps
+ int mipmap0 = HEADER_SIZE;
+ int mipmap1 = mipmap0 + getWidth() * getHeight();
+ int mipmap2 = mipmap1 + getWidth() / 2 * getHeight() / 2;
+ int mipmap3 = mipmap2 + getWidth() / 4 * getHeight() / 4;
+
+ if (offset[0] != mipmap0
+ || offset[1] != mipmap1
+ || offset[2] != mipmap2
+ || offset[3] != mipmap3) {
+ throw new IllegalArgumentException("invalid quake2 wal header");
+ }
+ }
+
+ /**
+ * @return
+ */
+ public int getContents() {
+ return contents;
+ }
+
+ /**
+ * @return
+ */
+ public int getFlags() {
+ return flags;
+ }
+
+ /**
+ * @return
+ */
+ public int getHeight() {
+ return height;
+ }
+
+ /**
+ * @return
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @return
+ */
+ public String getNextName() {
+ return nextName;
+ }
+
+ /**
+ * @return
+ */
+ public int getOffset(int index) {
+ if (index < 0 || index > 3) {
+ throw new ArrayIndexOutOfBoundsException("mipmap offset range is 0 to 3");
+ }
+ return offset[index];
+ }
+
+ /**
+ * @return
+ */
+ public int getValue() {
+ return value;
+ }
+
+ /**
+ * @return
+ */
+ public int getWidth() {
+ return width;
+ }
+
+ }
+} \ No newline at end of file
diff --git a/src/jake2/imageio/WALImageReader.java b/src/jake2/imageio/WALImageReader.java
new file mode 100644
index 0000000..949b4f1
--- /dev/null
+++ b/src/jake2/imageio/WALImageReader.java
@@ -0,0 +1,273 @@
+/*
+ * Created on Nov 18, 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 WALImageReader extends ImageReader {
+
+ private static Logger logger =
+ Logger.getLogger(WALImageReader.class.getName());
+
+ ImageInputStream stream = null;
+ WAL.Header header = null;
+
+ public WALImageReader(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);
+
+ }
+
+ for (int srcY = 0; srcY < height; srcY++) {
+ // Read the row
+ try {
+ stream.readFully(rowBuf);
+ } 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);
+ }
+ }
+ return dst;
+ }
+
+ private void checkIndex(int imageIndex) {
+ if (imageIndex != 0) {
+ throw new IndexOutOfBoundsException("bad image index");
+ }
+ }
+
+ private void readHeader() throws IIOException {
+
+ if (header != null) return;
+
+ logger.log(Level.FINE, "WAL read header");
+
+ if (stream == null) {
+ if (this.input == null) {
+ throw new IllegalStateException("No input stream");
+ }
+ stream = (ImageInputStream) input;
+ }
+
+ byte[] buffer = new byte[WAL.HEADER_SIZE];
+
+ try {
+ stream.readFully(buffer);
+ this.header = new WAL.Header(buffer);
+ logger.log(
+ Level.FINE,
+ "WAL width: "
+ + header.getWidth()
+ + " height: "
+ + header.getHeight());
+ } catch (IOException e) {
+ throw new IIOException("Error reading quake2 WAL header", e);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/jake2/imageio/WALImageReaderSpi.java b/src/jake2/imageio/WALImageReaderSpi.java
new file mode 100644
index 0000000..f731165
--- /dev/null
+++ b/src/jake2/imageio/WALImageReaderSpi.java
@@ -0,0 +1,127 @@
+/*
+ * Created on Nov 18, 2003
+ *
+ */
+package jake2.imageio;
+
+import java.io.IOException;
+import java.util.Locale;
+import javax.imageio.ImageReader;
+import javax.imageio.spi.ImageReaderSpi;
+import javax.imageio.stream.ImageInputStream;
+
+/**
+ * @author cwei
+ *
+ * Quake 2 stores textures in a proprietary 2D image format called WAL.
+ * While this isn't actually part of the BSP file format,
+ * it's essential information for loading Quake 2 maps.
+ * WAL textures are stored in a 8-bit indexed color format with a specific palette being used by all textures.
+ * (this palette is stored in the PAK data file that comes with Quake 2 e.g. pics/colormap.pcx).
+ * Four mip-map levels are stored for each texture at sizes decreasing by a factor of two.
+ * This is mostly for software rendering since most 3D APIs will automatically generate
+ * the mip-map levels when you create a texture.
+ * Each frame of an animated texture is stored as an individual WAL file,
+ * and the animation sequence is encoded by storing the name of the next texture in the sequence for each frame;
+ * texture names are stored with paths and without any extension.
+ * The format for the WAL file header is the wal_header structure:
+ * <code>
+ struct wal_header
+ {
+ char name[32]; // name of the texture
+
+ uint32 width; // width (in pixels) of the largest mipmap level
+ uint32 height; // height (in pixels) of the largest mipmap level
+
+ int32 offset[4]; // byte offset of the start of each of the 4 mipmap levels
+
+ char next_name[32]; // name of the next texture in the animation
+
+ uint32 flags; // ?
+ uint32 contents; // ?
+ uint32 value; // ?
+ };
+ </code>
+ * The actual texture data is stored in an 8-bits-per-pixel raw format in a left-right, top-down order.
+ * <br>
+ * put the own ImageReaderSpi class name in the file (relative to classpath):
+ * <code>META-INF/services/javax.imageio.spi.ImageReaderSpi</code>
+ *
+ * */
+public class WALImageReaderSpi extends ImageReaderSpi {
+
+ static final String vendorName = "[email protected]";
+ static final String version = "1.0_beta";
+ static final String[] names = { "quake2 wal" };
+ static final String[] suffixes = { "wal" };
+ static final String[] MIMETypes = { "image/x-quake2-wal" };
+ static final String readerClassName = "jake2.imageio.WALImageReader";
+ static final String[] writerSpiNames = null; // { "jake2.imageio.WALImageWriterSpi" };
+
+ // Metadata formats, more information below
+ static final boolean supportsStandardStreamMetadataFormat = false;
+ static final String nativeStreamMetadataFormatName = null;
+
+ static final String nativeStreamMetadataFormatClassName = null;
+ static final String[] extraStreamMetadataFormatNames = null;
+ static final String[] extraStreamMetadataFormatClassNames = null;
+ static final boolean supportsStandardImageMetadataFormat = false;
+ static final String nativeImageMetadataFormatName =
+ "jake2.imageio.WALMetaData_1.0";
+ static final String nativeImageMetadataFormatClassName = null; // "jake2.imageio.WALMetadata";
+ static final String[] extraImageMetadataFormatNames = null;
+ static final String[] extraImageMetadataFormatClassNames = null;
+
+ public WALImageReaderSpi() {
+
+ super(
+ vendorName,
+ version,
+ names,
+ suffixes,
+ MIMETypes,
+ readerClassName,
+ ImageReaderSpi.STANDARD_INPUT_TYPE, // Accept ImageInputStreams
+ writerSpiNames,
+ supportsStandardStreamMetadataFormat,
+ nativeStreamMetadataFormatName,
+ nativeStreamMetadataFormatClassName,
+ extraStreamMetadataFormatNames,
+ extraStreamMetadataFormatClassNames,
+ supportsStandardImageMetadataFormat,
+ nativeImageMetadataFormatName,
+ nativeImageMetadataFormatClassName,
+ extraImageMetadataFormatNames,
+ extraImageMetadataFormatClassNames);
+ }
+
+ public boolean canDecodeInput(Object source) throws IOException {
+ if (!(source instanceof ImageInputStream)) {
+ return false;
+ }
+ ImageInputStream stream = (ImageInputStream)source;
+ byte[] buffer = new byte[WAL.HEADER_SIZE];
+ try {
+ stream.mark();
+ stream.readFully(buffer);
+ stream.reset();
+ // buffer will be converted to members and header checked
+ WAL.Header wal = new WAL.Header(buffer);
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * returns a WALImageReader
+ */
+ public ImageReader createReaderInstance(Object extension)
+ throws IOException {
+ return new WALImageReader(this);
+ }
+
+ public String getDescription(Locale locale) {
+ return "id-software's Quake2 wal format for textures";
+ }
+}