diff --git a/imageio/imageio-pcx/license.txt b/imageio/imageio-pcx/license.txt
new file mode 100755
index 00000000..fe399516
--- /dev/null
+++ b/imageio/imageio-pcx/license.txt
@@ -0,0 +1,25 @@
+Copyright (c) 2014, Harald Kuhr
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions 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 "TwelveMonkeys" nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/imageio/imageio-pcx/pom.xml b/imageio/imageio-pcx/pom.xml
new file mode 100755
index 00000000..511724a1
--- /dev/null
+++ b/imageio/imageio-pcx/pom.xml
@@ -0,0 +1,28 @@
+
+
+ 4.0.0
+
+ com.twelvemonkeys.imageio
+ imageio
+ 3.1-SNAPSHOT
+
+ imageio-pcx
+ TwelveMonkeys :: ImageIO :: PCX plugin
+
+ ImageIO plugin for ZSoft Paintbrush Format (PCX)
+
+
+
+
+ com.twelvemonkeys.imageio
+ imageio-core
+
+
+ com.twelvemonkeys.imageio
+ imageio-core
+ tests
+
+
+
diff --git a/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/dcx/DCX.java b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/dcx/DCX.java
new file mode 100755
index 00000000..9428274d
--- /dev/null
+++ b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/dcx/DCX.java
@@ -0,0 +1,11 @@
+package com.twelvemonkeys.imageio.plugins.dcx;
+
+/**
+ * The DXC file format, is just a small header before a number of PCX streams.
+ * Mostly used as a FAX format.
+ *
+ * @see [PCX] Related File Formats
+ */
+interface DCX {
+ int MAGIC = 0x3ADE68B1;
+}
diff --git a/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/dcx/DCXHeader.java b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/dcx/DCXHeader.java
new file mode 100755
index 00000000..3d3a1dd1
--- /dev/null
+++ b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/dcx/DCXHeader.java
@@ -0,0 +1,53 @@
+package com.twelvemonkeys.imageio.plugins.dcx;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import javax.imageio.IIOException;
+import javax.imageio.stream.ImageInputStream;
+
+final class DCXHeader {
+ private final int[] offsetTable;
+
+ DCXHeader(final int[] offsetTable) {
+ this.offsetTable = offsetTable;
+ }
+
+ public int getCount() {
+ return offsetTable.length;
+ }
+
+ public long getOffset(final int index) {
+ return (0xffffffffL & offsetTable[index]);
+ }
+
+ public static DCXHeader read(final ImageInputStream imageInput) throws IOException {
+ // typedef struct _DcxHeader
+ // {
+ // DWORD Id; /* DCX Id number */
+ // DWORD PageTable[1024]; /* Image offsets */
+ // } DCXHEAD;
+
+ int magic = imageInput.readInt();
+ if (magic != DCX.MAGIC) {
+ throw new IIOException(String.format("Not a DCX file. Expected DCX magic %02x, read %02x", DCX.MAGIC, magic));
+ }
+
+ int[] offsets = new int[1024];
+
+ int count = 0;
+ do {
+ offsets[count] = imageInput.readInt();
+ count++;
+ }
+ while (offsets[count - 1] != 0 && count < offsets.length);
+
+ return new DCXHeader(count == offsets.length ? offsets : Arrays.copyOf(offsets, count));
+ }
+
+ @Override public String toString() {
+ return "DCXHeader{" +
+ "offsetTable=" + Arrays.toString(offsetTable) +
+ '}';
+ }
+}
diff --git a/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/dcx/DCXImageReader.java b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/dcx/DCXImageReader.java
new file mode 100755
index 00000000..e3eee236
--- /dev/null
+++ b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/dcx/DCXImageReader.java
@@ -0,0 +1,205 @@
+package com.twelvemonkeys.imageio.plugins.dcx;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteOrder;
+import java.util.Iterator;
+
+import javax.imageio.ImageIO;
+import javax.imageio.ImageReadParam;
+import javax.imageio.ImageReader;
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.event.IIOReadWarningListener;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.metadata.IIOMetadataFormatImpl;
+import javax.imageio.spi.ImageReaderSpi;
+
+import com.twelvemonkeys.imageio.ImageReaderBase;
+import com.twelvemonkeys.imageio.plugins.pcx.PCXImageReader;
+import com.twelvemonkeys.imageio.util.ProgressListenerBase;
+import com.twelvemonkeys.xml.XMLSerializer;
+
+public final class DCXImageReader extends ImageReaderBase {
+ // TODO: Delegate listeners with correct index!
+
+ private DCXHeader header;
+
+ private PCXImageReader readerDelegate;
+ private ProgressDelegator progressDelegator;
+
+ public DCXImageReader(final ImageReaderSpi provider) {
+ super(provider);
+ readerDelegate = new PCXImageReader(provider);
+
+ progressDelegator = new ProgressDelegator();
+ installListeners();
+ }
+
+ private void installListeners() {
+ readerDelegate.addIIOReadProgressListener(progressDelegator);
+ readerDelegate.addIIOReadWarningListener(progressDelegator);
+ }
+
+ @Override protected void resetMembers() {
+ header = null;
+
+ readerDelegate.reset();
+ installListeners();
+ }
+
+ @Override public void dispose() {
+ super.dispose();
+
+ readerDelegate.dispose();
+ readerDelegate = null;
+ }
+
+ @Override public int getWidth(final int imageIndex) throws IOException {
+ initIndex(imageIndex);
+
+ return readerDelegate.getWidth(0);
+ }
+
+ @Override public int getHeight(final int imageIndex) throws IOException {
+ initIndex(imageIndex);
+
+ return readerDelegate.getHeight(0);
+ }
+
+ @Override public ImageTypeSpecifier getRawImageType(final int imageIndex) throws IOException {
+ initIndex(imageIndex);
+
+ return readerDelegate.getRawImageType(0);
+ }
+
+ @Override public Iterator getImageTypes(final int imageIndex) throws IOException {
+ initIndex(imageIndex);
+
+ return readerDelegate.getImageTypes(0);
+ }
+
+ @Override public BufferedImage read(final int imageIndex, final ImageReadParam param) throws IOException {
+ initIndex(imageIndex);
+
+ return readerDelegate.read(imageIndex, param);
+ }
+
+ @Override public IIOMetadata getImageMetadata(final int imageIndex) throws IOException {
+ initIndex(imageIndex);
+
+ return readerDelegate.getImageMetadata(0);
+ }
+
+ @Override public synchronized void abort() {
+ super.abort();
+ readerDelegate.abort();
+ }
+
+ @Override public int getNumImages(final boolean allowSearch) throws IOException {
+ readHeader();
+
+ return header.getCount();
+ }
+
+ private void initIndex(final int imageIndex) throws IOException {
+ checkBounds(imageIndex);
+
+ imageInput.seek(header.getOffset(imageIndex));
+ progressDelegator.index = imageIndex;
+ readerDelegate.setInput(imageInput);
+ }
+
+ private void readHeader() throws IOException {
+ assertInput();
+
+ if (header == null) {
+ imageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+ header = DCXHeader.read(imageInput);
+// System.err.println("header: " + header);
+ imageInput.flushBefore(imageInput.getStreamPosition());
+ }
+
+ imageInput.seek(imageInput.getFlushedPosition());
+ }
+
+ private class ProgressDelegator extends ProgressListenerBase implements IIOReadWarningListener {
+ private int index;
+
+ @Override
+ public void imageComplete(ImageReader source) {
+ processImageComplete();
+ }
+
+ @Override
+ public void imageProgress(ImageReader source, float percentageDone) {
+ processImageProgress(percentageDone);
+ }
+
+ @Override
+ public void imageStarted(ImageReader source, int imageIndex) {
+ processImageStarted(index);
+ }
+
+ @Override
+ public void readAborted(ImageReader source) {
+ processReadAborted();
+ }
+
+ @Override
+ public void sequenceComplete(ImageReader source) {
+ processSequenceComplete();
+ }
+
+ @Override
+ public void sequenceStarted(ImageReader source, int minIndex) {
+ processSequenceStarted(index);
+ }
+
+ public void warningOccurred(ImageReader source, String warning) {
+ processWarningOccurred(warning);
+ }
+ }
+
+
+ public static void main(String[] args) throws IOException {
+ DCXImageReader reader = new DCXImageReader(null);
+
+ for (String arg : args) {
+ File in = new File(arg);
+ reader.setInput(ImageIO.createImageInputStream(in));
+
+ ImageReadParam param = reader.getDefaultReadParam();
+ param.setDestinationType(reader.getImageTypes(0).next());
+// param.setSourceSubsampling(2, 3, 0, 0);
+// param.setSourceSubsampling(2, 1, 0, 0);
+//
+// int width = reader.getHdpi(0);
+// int height = reader.getVdpi(0);
+//
+// param.setSourceRegion(new Rectangle(width / 4, height / 4, width / 2, height / 2));
+// param.setSourceRegion(new Rectangle(width / 2, height / 2));
+// param.setSourceRegion(new Rectangle(width / 2, height / 2, width / 2, height / 2));
+
+ System.err.println("header: " + reader.header);
+
+ BufferedImage image = reader.read(0, param);
+
+ System.err.println("image: " + image);
+
+ showIt(image, in.getName());
+
+ new XMLSerializer(System.out, System.getProperty("file.encoding"))
+ .serialize(reader.getImageMetadata(0).getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName), false);
+
+// File reference = new File(in.getParent() + "/../reference", in.getName().replaceAll("\\.p(a|b|g|p)m", ".png"));
+// if (reference.exists()) {
+// System.err.println("reference.getAbsolutePath(): " + reference.getAbsolutePath());
+// showIt(ImageIO.read(reference), reference.getName());
+// }
+
+// break;
+ }
+
+ }
+}
diff --git a/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/dcx/DCXImageReaderSpi.java b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/dcx/DCXImageReaderSpi.java
new file mode 100755
index 00000000..cd7902ad
--- /dev/null
+++ b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/dcx/DCXImageReaderSpi.java
@@ -0,0 +1,79 @@
+package com.twelvemonkeys.imageio.plugins.dcx;
+
+import java.io.IOException;
+import java.nio.ByteOrder;
+import java.util.Locale;
+
+import javax.imageio.ImageReader;
+import javax.imageio.spi.ImageReaderSpi;
+import javax.imageio.stream.ImageInputStream;
+
+import com.twelvemonkeys.imageio.spi.ProviderInfo;
+import com.twelvemonkeys.imageio.util.IIOUtil;
+
+public final class DCXImageReaderSpi extends ImageReaderSpi {
+
+ /**
+ * Creates a {@code DCXImageReaderSpi}.
+ */
+ public DCXImageReaderSpi() {
+ this(IIOUtil.getProviderInfo(DCXImageReaderSpi.class));
+ }
+
+ private DCXImageReaderSpi(final ProviderInfo providerInfo) {
+ super(
+ providerInfo.getVendorName(),
+ providerInfo.getVersion(),
+ new String[]{
+ "dcx",
+ "DCX"
+ },
+ new String[]{"dcx"},
+ new String[]{
+ // No official IANA record exists
+ "image/dcx",
+ "image/x-dcx",
+ },
+ "com.twelvemkonkeys.imageio.plugins.dcx.DCXImageReader",
+ new Class[] {ImageInputStream.class},
+ null,
+ true, // supports standard stream metadata
+ null, null, // native stream format name and class
+ null, null, // extra stream formats
+ true, // supports standard image metadata
+ null, null,
+ null, null // extra image metadata formats
+ );
+ }
+
+ @Override public boolean canDecodeInput(final Object source) throws IOException {
+ if (!(source instanceof ImageInputStream)) {
+ return false;
+ }
+
+ ImageInputStream stream = (ImageInputStream) source;
+ stream.mark();
+
+ try {
+ ByteOrder originalByteOrder = stream.getByteOrder();
+ stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+
+ try {
+ return stream.readInt() == DCX.MAGIC;
+ }
+ finally {
+ stream.setByteOrder(originalByteOrder);
+ }
+ }
+ finally{
+ stream.reset();
+ }
+ }
+
+ @Override public ImageReader createReaderInstance(final Object extension) throws IOException {
+ return new DCXImageReader(this);
+ }
+
+ @Override public String getDescription(final Locale locale) {
+ return "Multi-page PCX fax document (DCX) image reader";
+ }}
diff --git a/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/BitRotator.java b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/BitRotator.java
new file mode 100755
index 00000000..3a6702d0
--- /dev/null
+++ b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/BitRotator.java
@@ -0,0 +1,99 @@
+package com.twelvemonkeys.imageio.plugins.pcx;
+
+/**
+ * IFFUtil
+ *
+ * Bit rotate methods based on Sue-Ken Yap, "A Fast 90-Degree Bitmap Rotator,"
+ * in GRAPHICS GEMS II, James Arvo ed., Academic Press, 1991, ISBN 0-12-064480-0.
+ *
+ * @author Unascribed (C version)
+ * @author Harald Kuhr (Java port)
+ * @version $Id: IFFUtil.java,v 1.0 06.mar.2006 13:31:35 haku Exp$
+ */
+final class BitRotator {
+ // TODO: Extract and merge with IFFUtil
+
+ /**
+ * Creates a rotation table
+ * @param n number of bits -1
+ *
+ * @return the rotation table
+ */
+ private static long[] rtable(int n) {
+ return new long[]{
+ 0x00000000l << n, 0x00000001l << n, 0x00000100l << n, 0x00000101l << n,
+ 0x00010000l << n, 0x00010001l << n, 0x00010100l << n, 0x00010101l << n,
+ 0x01000000l << n, 0x01000001l << n, 0x01000100l << n, 0x01000101l << n,
+ 0x01010000l << n, 0x01010001l << n, 0x01010100l << n, 0x01010101l << n
+ };
+ }
+
+ private static final long[][] RTABLE = {
+ rtable(0), rtable(1), rtable(2), rtable(3),
+ rtable(4), rtable(5), rtable(6), rtable(7)
+ };
+
+ /**
+ * Rotate bits clockwise.
+ * The IFFImageReader uses this to convert pixel bits from planar to chunky.
+ * Bits from the source are rotated 90 degrees clockwise written to the
+ * destination.
+ *
+ * @param pSrc source pixel data
+ * @param pSrcPos starting index of 8 x 8 bit source tile
+ * @param pSrcStep byte offset between adjacent rows in source
+ * @param pDst destination pixel data
+ * @param pDstPos starting index of 8 x 8 bit destination tile
+ * @param pDstStep byte offset between adjacent rows in destination
+ */
+ static void bitRotateCW(final byte[] pSrc, int pSrcPos, int pSrcStep,
+ final byte[] pDst, int pDstPos, int pDstStep) {
+ int idx = pSrcPos;
+
+ int lonyb;
+ int hinyb;
+ long lo = 0;
+ long hi = 0;
+
+ for (int i = 0; i < 8; i++) {
+ lonyb = pSrc[idx] & 0xF;
+ hinyb = (pSrc[idx] >> 4) & 0xF;
+ lo |= RTABLE[i][lonyb];
+ hi |= RTABLE[i][hinyb];
+ idx += pSrcStep;
+ }
+
+ idx = pDstPos;
+
+ pDst[idx] = (byte)((hi >> 24) & 0xFF);
+ idx += pDstStep;
+ if (idx < pDst.length) {
+ pDst[idx] = (byte)((hi >> 16) & 0xFF);
+ idx += pDstStep;
+ if (idx < pDst.length) {
+ pDst[idx] = (byte)((hi >> 8) & 0xFF);
+ idx += pDstStep;
+ if (idx < pDst.length) {
+ pDst[idx] = (byte)(hi & 0xFF);
+ idx += pDstStep;
+ }
+ }
+ }
+
+ if (idx < pDst.length) {
+ pDst[idx] = (byte)((lo >> 24) & 0xFF);
+ idx += pDstStep;
+ if (idx < pDst.length) {
+ pDst[idx] = (byte)((lo >> 16) & 0xFF);
+ idx += pDstStep;
+ if (idx < pDst.length) {
+ pDst[idx] = (byte)((lo >> 8) & 0xFF);
+ idx += pDstStep;
+ if (idx < pDst.length) {
+ pDst[idx] = (byte)(lo & 0xFF);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/CGAColorModel.java b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/CGAColorModel.java
new file mode 100755
index 00000000..9977c5d6
--- /dev/null
+++ b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/CGAColorModel.java
@@ -0,0 +1,83 @@
+package com.twelvemonkeys.imageio.plugins.pcx;
+
+import java.awt.image.DataBuffer;
+import java.awt.image.IndexColorModel;
+
+final class CGAColorModel {
+
+ // http://en.wikipedia.org/wiki/Color_Graphics_Adapter#Color_palette
+ private static final int[] CGA_PALETTE = {
+ // black, blue, green, cyan, red, magenta, brown, light gray
+ 0x000000, 0x0000aa, 0x00aa00, 0x00aaaa, 0xaa0000, 0xaa00aa, 0xaa5500, 0xaaaaaa,
+ // gray, light b, light g, light c, light r, light m, yellow, white
+ 0x555555, 0x5555ff, 0x55ff55, 0x55ffff, 0xff5555, 0xff55ff, 0xffff55, 0xffffff
+ };
+
+ static IndexColorModel create(final byte[] cgaMode, final int bitsPerPixel) {
+ int[] cmap = new int[1 << bitsPerPixel];
+
+ byte byte0 = cgaMode[0];
+ int background = (byte0 & 0xf0) >> 4;
+ cmap[0] = CGA_PALETTE[background];
+
+ if (bitsPerPixel == 1) {
+ // Monochrome
+ cmap[1] = CGA_PALETTE[0];
+ }
+ else {
+ // Configured palette
+ byte byte3 = cgaMode[3];
+
+ System.err.printf("background: %d\n", background);
+ System.err.printf("cgaMode: %02x\n", (byte3 & 0xff));
+ System.err.printf("cgaMode: %d\n", (byte3 & 0x80) >> 7);
+ System.err.printf("cgaMode: %d\n", (byte3 & 0x40) >> 6);
+ System.err.printf("cgaMode: %d\n", (byte3 & 0x20) >> 5);
+
+ boolean colorBurstEnable = (byte3 & 0x80) == 0;
+ boolean paletteValue = (byte3 & 0x40) != 0;
+ boolean intensityValue = (byte3 & 0x20) != 0;
+
+ System.err.println("colorBurstEnable: " + colorBurstEnable);
+ System.err.println("paletteValue: " + paletteValue);
+ System.err.println("intensityValue: " + intensityValue);
+
+ // Set up the fixed part of the palette
+ if (colorBurstEnable) {
+ if (paletteValue) {
+ if (intensityValue) {
+ cmap[1] = CGA_PALETTE[11];
+ cmap[2] = CGA_PALETTE[13];
+ cmap[3] = CGA_PALETTE[15];
+ } else {
+ cmap[1] = CGA_PALETTE[3];
+ cmap[2] = CGA_PALETTE[5];
+ cmap[3] = CGA_PALETTE[7];
+ }
+ } else {
+ if (intensityValue) {
+ cmap[1] = CGA_PALETTE[10];
+ cmap[2] = CGA_PALETTE[12];
+ cmap[3] = CGA_PALETTE[14];
+ } else {
+ cmap[1] = CGA_PALETTE[2];
+ cmap[2] = CGA_PALETTE[4];
+ cmap[3] = CGA_PALETTE[6];
+ }
+ }
+ } else {
+ if (intensityValue) {
+ cmap[1] = CGA_PALETTE[11];
+ cmap[2] = CGA_PALETTE[12];
+ cmap[3] = CGA_PALETTE[15];
+ } else {
+ cmap[1] = CGA_PALETTE[4];
+ cmap[2] = CGA_PALETTE[5];
+ cmap[3] = CGA_PALETTE[7];
+ }
+ }
+ }
+
+ return new IndexColorModel(bitsPerPixel, cmap.length, cmap, 0, false, -1, DataBuffer.TYPE_BYTE);
+ }
+}
diff --git a/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCX.java b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCX.java
new file mode 100755
index 00000000..3199b2e0
--- /dev/null
+++ b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCX.java
@@ -0,0 +1,27 @@
+package com.twelvemonkeys.imageio.plugins.pcx;
+
+interface PCX {
+ byte MAGIC = 0x0A;
+
+ int HEADER_SIZE = 128;
+
+ byte VERSION_2_5 = 0;
+ byte VERSION_2_8_PALETTE = 2;
+ byte VERSION_2_8_NO_PALETTE = 3;
+ byte VERSION_2_X_WINDOWS = 4;
+ byte VERSION_3 = 5;
+
+ /** No compression, channels stored verbatim. */
+ byte COMPRESSION_NONE = 0;
+ /** Runlength encoed compression,
+ * channels are prepended by one offset and length tables (one entry in each per scanline). */
+ byte COMPRESSION_RLE = 1;
+
+ /** Color or BW. */
+ int PALETTEINFO_COLOR = 1;
+ /** Gray. */
+ int PALETTEINFO_GRAY = 2;
+
+ /** Magic identifier for VGA palette. */
+ byte VGA_PALETTE_MAGIC = 0x0c;
+}
diff --git a/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXHeader.java b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXHeader.java
new file mode 100755
index 00000000..f2c3eb52
--- /dev/null
+++ b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXHeader.java
@@ -0,0 +1,155 @@
+package com.twelvemonkeys.imageio.plugins.pcx;
+
+import javax.imageio.IIOException;
+import javax.imageio.stream.ImageInputStream;
+
+import java.awt.Rectangle;
+import java.awt.image.ColorModel;
+import java.awt.image.IndexColorModel;
+import java.io.IOException;
+import java.util.Arrays;
+
+final class PCXHeader {
+ private int version;
+ private int compression;
+ private int bitsPerPixel;
+ private int width;
+ private int height;
+ private int hdpi;
+ private int vdpi;
+ private byte[] palette;
+ private int channels;
+ private int bytesPerLine;
+ private int paletteInfo;
+ private int hScreenSize;
+ private int vScreenSize;
+
+ public int getVersion() {
+ return version;
+ }
+
+ public int getCompression() {
+ return compression;
+ }
+
+ public int getBitsPerPixel() {
+ return bitsPerPixel;
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public int getHeight() {
+ return height;
+ }
+
+ public int getHdpi() {
+ return hdpi;
+ }
+
+ public int getVdpi() {
+ return vdpi;
+ }
+
+ public int getChannels() {
+ return channels;
+ }
+
+ public int getBytesPerLine() {
+ return bytesPerLine;
+ }
+
+ public int getPaletteInfo() {
+ return paletteInfo;
+ }
+
+ public IndexColorModel getEGAPalette() {
+ // TODO: Figure out when/how to enable CGA palette... The test below isn't good enough.
+// if (channels == 1 && (bitsPerPixel == 1 || bitsPerPixel == 2)) {
+// return CGAColorModel.create(palette, bitsPerPixel);
+// }
+
+ int bits = channels * bitsPerPixel;
+ return new IndexColorModel(bits, Math.min(16, 1 << bits), palette, 0, false);
+ }
+
+ @Override public String toString() {
+ return "PCXHeader{" +
+ "version=" + version +
+ ", compression=" + compression +
+ ", bitsPerPixel=" + bitsPerPixel +
+ ", width=" + width +
+ ", height=" + height +
+ ", hdpi=" + hdpi +
+ ", vdpi=" + vdpi +
+ ", channels=" + channels +
+ ", bytesPerLine=" + bytesPerLine +
+ ", paletteInfo=" + paletteInfo +
+ ", hScreenSize=" + hScreenSize +
+ ", vScreenSize=" + vScreenSize +
+ ", palette=" + Arrays.toString(palette) +
+ '}';
+ }
+
+ public static PCXHeader read(final ImageInputStream imageInput) throws IOException {
+// typedef struct _PcxHeader
+// {
+// BYTE Identifier; /* PCX Id Number (Always 0x0A) */
+// BYTE Version; /* Version Number */
+// BYTE Encoding; /* Encoding Format */
+// BYTE BitsPerPixel; /* Bits per Pixel */
+// WORD XStart; /* Left of image */
+// WORD YStart; /* Top of Image */
+// WORD XEnd; /* Right of Image
+// WORD YEnd; /* Bottom of image */
+// WORD HorzRes; /* Horizontal Resolution */
+// WORD VertRes; /* Vertical Resolution */
+// BYTE Palette[48]; /* 16-Color EGA Palette */
+// BYTE Reserved1; /* Reserved (Always 0) */
+// BYTE NumBitPlanes; /* Number of Bit Planes */
+// WORD BytesPerLine; /* Bytes per Scan-line */
+// WORD PaletteType; /* Palette Type */
+// WORD HorzScreenSize; /* Horizontal Screen Size */
+// WORD VertScreenSize; /* Vertical Screen Size */
+// BYTE Reserved2[54]; /* Reserved (Always 0) */
+// } PCXHEAD;
+
+ byte magic = imageInput.readByte();
+ if (magic != PCX.MAGIC) {
+ throw new IIOException(String.format("Not a PCX image. Expected PCX magic %02x, read %02x", PCX.MAGIC, magic));
+ }
+
+ PCXHeader header = new PCXHeader();
+
+ header.version = imageInput.readUnsignedByte();
+ header.compression = imageInput.readUnsignedByte();
+ header.bitsPerPixel = imageInput.readUnsignedByte();
+
+ int xStart = imageInput.readUnsignedShort();
+ int yStart = imageInput.readUnsignedShort();
+ header.width = imageInput.readUnsignedShort() - xStart + 1;
+ header.height = imageInput.readUnsignedShort() - yStart + 1;
+
+ header.hdpi = imageInput.readUnsignedShort();
+ header.vdpi = imageInput.readUnsignedShort();
+
+ byte[] palette = new byte[48];
+ imageInput.readFully(palette); // 16 RGB triplets
+ header.palette = palette;
+
+ imageInput.readUnsignedByte(); // Reserved, should be 0
+
+ header.channels = imageInput.readUnsignedByte();
+ header.bytesPerLine = imageInput.readUnsignedShort(); // Must be even!
+
+ header.paletteInfo = imageInput.readUnsignedShort(); // 1 == Color/BW, 2 == Gray
+
+ header.hScreenSize = imageInput.readUnsignedShort();
+ header.vScreenSize = imageInput.readUnsignedShort();
+
+ imageInput.skipBytes(PCX.HEADER_SIZE - imageInput.getStreamPosition());
+
+ return header;
+ }
+}
diff --git a/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXImageReader.java b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXImageReader.java
new file mode 100755
index 00000000..dfcca950
--- /dev/null
+++ b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXImageReader.java
@@ -0,0 +1,425 @@
+package com.twelvemonkeys.imageio.plugins.pcx;
+
+import java.awt.Rectangle;
+import java.awt.color.ColorSpace;
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferByte;
+import java.awt.image.IndexColorModel;
+import java.awt.image.Raster;
+import java.awt.image.WritableRaster;
+import java.io.DataInput;
+import java.io.DataInputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.imageio.IIOException;
+import javax.imageio.ImageIO;
+import javax.imageio.ImageReadParam;
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.metadata.IIOMetadataFormatImpl;
+import javax.imageio.spi.ImageReaderSpi;
+import javax.imageio.stream.ImageInputStream;
+
+import com.twelvemonkeys.imageio.ImageReaderBase;
+import com.twelvemonkeys.imageio.util.IIOUtil;
+import com.twelvemonkeys.imageio.util.IndexedImageTypeSpecifier;
+import com.twelvemonkeys.io.enc.DecoderStream;
+import com.twelvemonkeys.xml.XMLSerializer;
+
+public final class PCXImageReader extends ImageReaderBase {
+ /** 8 bit ImageTypeSpecifer used for reading bitplane images. */
+ private static final ImageTypeSpecifier GRAYSCALE = ImageTypeSpecifier.createGrayscale(8, DataBuffer.TYPE_BYTE, false);
+
+ private PCXHeader header;
+ private boolean readPalette;
+ private IndexColorModel vgaPalette;
+
+ public PCXImageReader(final ImageReaderSpi provider) {
+ super(provider);
+ }
+
+ @Override
+ protected void resetMembers() {
+ header = null;
+ readPalette = false;
+ vgaPalette = null;
+ }
+
+ @Override
+ public int getWidth(final int imageIndex) throws IOException {
+ checkBounds(imageIndex);
+ readHeader();
+
+ return header.getWidth();
+ }
+
+ @Override
+ public int getHeight(final int imageIndex) throws IOException {
+ checkBounds(imageIndex);
+ readHeader();
+
+ return header.getHeight();
+ }
+
+ @Override
+ public Iterator getImageTypes(final int imageIndex) throws IOException {
+ ImageTypeSpecifier rawType = getRawImageType(imageIndex);
+
+ List specifiers = new ArrayList();
+
+ // TODO: Implement
+ specifiers.add(rawType);
+
+ return specifiers.iterator();
+ }
+
+ @Override
+ public ImageTypeSpecifier getRawImageType(final int imageIndex) throws IOException {
+ checkBounds(imageIndex);
+ readHeader();
+
+ int channels = header.getChannels();
+ int paletteInfo = header.getPaletteInfo();
+ ColorSpace cs = paletteInfo == PCX.PALETTEINFO_GRAY ? ColorSpace.getInstance(ColorSpace.CS_GRAY) : ColorSpace.getInstance(ColorSpace.CS_sRGB);
+
+ switch (header.getBitsPerPixel()) {
+ case 1:
+ case 2:
+ case 4:
+ return IndexedImageTypeSpecifier.createFromIndexColorModel(header.getEGAPalette());
+ case 8:
+ // We may have IndexColorModel here for 1 channel images
+ if (channels == 1 && paletteInfo != PCX.PALETTEINFO_GRAY) {
+ IndexColorModel palette = getVGAPalette();
+ if (palette == null) {
+ throw new IIOException("Expected VGA palette not found");
+ }
+
+ return IndexedImageTypeSpecifier.createFromIndexColorModel(palette);
+ }
+
+ // PCX has 1 or 3 channels for 8 bit gray or 24 bit RGB, will be validated by ImageTypeSpecifier
+ return ImageTypeSpecifier.createBanded(cs, createIndices(channels, 1), createIndices(channels, 0), DataBuffer.TYPE_BYTE, false, false);
+ case 24:
+ // Some sources says this is possible... Untested.
+ return ImageTypeSpecifier.createInterleaved(cs, createIndices(channels, 0), DataBuffer.TYPE_BYTE, false, false);
+ default:
+ throw new IIOException("Unknown number of bytes per pixel: " + header.getBitsPerPixel());
+ }
+ }
+
+ private int[] createIndices(final int bands, int increment) {
+ int[] indices = new int[bands];
+
+ for (int i = 0; i < bands; i++) {
+ indices[i] = i * increment;
+ }
+
+ return indices;
+ }
+
+ @Override
+ public BufferedImage read(final int imageIndex, final ImageReadParam param) throws IOException {
+ Iterator imageTypes = getImageTypes(imageIndex);
+ ImageTypeSpecifier rawType = getRawImageType(imageIndex);
+
+ if (header.getPaletteInfo() != PCX.PALETTEINFO_COLOR && header.getPaletteInfo() != PCX.PALETTEINFO_GRAY) {
+ processWarningOccurred(String.format("Unsupported color mode: %d, colors may look incorrect", header.getPaletteInfo()));
+ }
+
+ int width = getWidth(imageIndex);
+ int height = getHeight(imageIndex);
+
+ BufferedImage destination = getDestination(param, imageTypes, width, height);
+
+ Rectangle srcRegion = new Rectangle();
+ Rectangle destRegion = new Rectangle();
+ computeRegions(param, width, height, destination, srcRegion, destRegion);
+
+ WritableRaster destRaster = clipToRect(destination.getRaster(), destRegion, param != null ? param.getDestinationBands() : null);
+ checkReadParamBandSettings(param, rawType.getNumBands(), destRaster.getNumBands());
+
+ int compression = header.getCompression();
+
+ // Wrap input (COMPRESSION_RLE is really the only value allowed)
+ DataInput input = compression == PCX.COMPRESSION_RLE
+ ? new DataInputStream(new DecoderStream(IIOUtil.createStreamAdapter(imageInput), new RLEDecoder()))
+ : imageInput;
+
+ int xSub = param != null ? param.getSourceXSubsampling() : 1;
+ int ySub = param != null ? param.getSourceYSubsampling() : 1;
+
+ processImageStarted(imageIndex);
+
+ if (rawType.getColorModel() instanceof IndexColorModel && header.getChannels() > 1) {
+ // Bit planes!
+ // Create raster from a default 8 bit layout
+ WritableRaster rowRaster = GRAYSCALE.createBufferedImage(header.getWidth(), 1).getRaster();
+
+ // Clip to source region
+ Raster clippedRow = clipRowToRect(rowRaster, srcRegion,
+ param != null ? param.getSourceBands() : null,
+ param != null ? param.getSourceXSubsampling() : 1);
+
+ int planeWidth = header.getBytesPerLine();
+ byte[] planeData = new byte[planeWidth * 8];
+
+ byte[] rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
+
+ for (int y = 0; y < height; y++) {
+ switch (header.getBitsPerPixel()) {
+ case 1:
+ readRowByte(input, srcRegion, xSub, ySub, planeData, 0, planeWidth * header.getChannels(), destRaster, clippedRow, y);
+ break;
+ default:
+ throw new AssertionError();
+ }
+
+ int pixelPos = 0;
+ for (int planePos = 0; planePos < planeWidth; planePos++) {
+ BitRotator.bitRotateCW(planeData, planePos, planeWidth, rowDataByte, pixelPos, 1);
+ pixelPos += 8;
+ }
+
+ processImageProgress(100f * y / height);
+
+ if (y < srcRegion.y) {
+ break;
+ }
+
+ if (abortRequested()) {
+ processReadAborted();
+ break;
+ }
+ }
+ }
+ else {
+ // Can't use width here, as we need to take bytesPerLine into account, and re-create a width based on this
+ int rowWidth = (header.getBytesPerLine() * 8) / header.getBitsPerPixel();
+ WritableRaster rowRaster = rawType.createBufferedImage(rowWidth, 1).getRaster();
+
+ // Clip to source region
+ Raster clippedRow = clipRowToRect(rowRaster, srcRegion,
+ param != null ? param.getSourceBands() : null,
+ param != null ? param.getSourceXSubsampling() : 1);
+
+ for (int y = 0; y < height; y++) {
+ for (int c = 0; c < header.getChannels(); c++) {
+ WritableRaster destChannel = destRaster.createWritableChild(destRaster.getMinX(), destRaster.getMinY(), destRaster.getWidth(), destRaster.getHeight(), 0, 0, new int[] {c});
+ Raster srcChannel = clippedRow.createChild(clippedRow.getMinX(), 0, clippedRow.getWidth(), 1, 0, 0, new int[] {c});
+
+ switch (header.getBitsPerPixel()) {
+ case 1:
+ case 2:
+ case 4:
+ case 8:
+ byte[] rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData(c);
+ readRowByte(input, srcRegion, xSub, ySub, rowDataByte, 0, rowDataByte.length, destChannel, srcChannel, y);
+ break;
+ default:
+ throw new AssertionError();
+ }
+
+ processImageProgress(100f * y / height * c / header.getChannels());
+
+ if (y < srcRegion.y) {
+ break;
+ }
+
+ if (abortRequested()) {
+ break;
+ }
+ }
+
+ if (abortRequested()) {
+ processReadAborted();
+ break;
+ }
+ }
+ }
+
+ processImageComplete();
+
+ return destination;
+ }
+
+ private void readRowByte(final DataInput input,
+ Rectangle srcRegion,
+ int xSub,
+ int ySub,
+ byte[] rowDataByte, final int off, final int length,
+ WritableRaster destChannel,
+ Raster srcChannel,
+ int y) throws IOException {
+ // If subsampled or outside source region, skip entire row
+ if (y % ySub != 0 || y < srcRegion.y || y >= srcRegion.y + srcRegion.height) {
+ input.skipBytes(length);
+
+ return;
+ }
+
+ input.readFully(rowDataByte, off, length);
+
+ // Subsample horizontal
+ if (xSub != 1) {
+ for (int x = 0; x < srcRegion.width / xSub; x++) {
+ rowDataByte[srcRegion.x + x] = rowDataByte[srcRegion.x + x * xSub];
+ }
+ }
+
+ int dstY = (y - srcRegion.y) / ySub;
+ destChannel.setDataElements(0, dstY, srcChannel);
+ }
+
+ private Raster clipRowToRect(final Raster raster, final Rectangle rect, final int[] bands, final int xSub) {
+ if (rect.contains(raster.getMinX(), 0, raster.getWidth(), 1)
+ && xSub == 1
+ && bands == null /* TODO: Compare bands with that of raster */) {
+ return raster;
+ }
+
+ return raster.createChild(rect.x / xSub, 0, rect.width / xSub, 1, 0, 0, bands);
+ }
+
+ private WritableRaster clipToRect(final WritableRaster raster, final Rectangle rect, final int[] bands) {
+ if (rect.contains(raster.getMinX(), raster.getMinY(), raster.getWidth(), raster.getHeight())
+ && bands == null /* TODO: Compare bands with that of raster */) {
+ return raster;
+ }
+
+ return raster.createWritableChild(rect.x, rect.y, rect.width, rect.height, 0, 0, bands);
+ }
+
+ private void readHeader() throws IOException {
+ if (header == null) {
+ imageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+ header = PCXHeader.read(imageInput);
+ imageInput.flushBefore(imageInput.getStreamPosition());
+ }
+
+ imageInput.seek(imageInput.getFlushedPosition());
+ }
+
+ @Override public IIOMetadata getImageMetadata(final int imageIndex) throws IOException {
+ checkBounds(imageIndex);
+ readHeader();
+
+ return new PCXMetadata(header, getVGAPalette());
+ }
+
+ private IndexColorModel getVGAPalette() throws IOException {
+ if (!readPalette) {
+ readHeader();
+
+ // Mark palette as read, to avoid further attempts
+ readPalette = true;
+
+ // Wee can't simply skip to an offset, as the RLE compression makes the file size unpredictable
+ skiptToEOF(imageInput);
+
+ // Seek backwards from EOF
+ long paletteStart = imageInput.getStreamPosition() - 769;
+ if (paletteStart <= imageInput.getFlushedPosition()) {
+ return null;
+ }
+
+ imageInput.seek(paletteStart);
+
+ byte val = imageInput.readByte();
+
+ if (val == PCX.VGA_PALETTE_MAGIC) {
+ byte[] palette = new byte[768]; // 256 * 3 for RGB
+ imageInput.readFully(palette);
+
+ vgaPalette = new IndexColorModel(8, 256, palette, 0, false);
+
+ return vgaPalette;
+ }
+
+ return null;
+ }
+
+ return vgaPalette;
+ }
+
+ // TODO: Candidate util method
+ private static long skiptToEOF(final ImageInputStream stream) throws IOException {
+ long length = stream.length();
+
+ if (length > 0) {
+ // Known length, skip there and we're done.
+ stream.seek(length);
+ }
+ else {
+ // Otherwise, seek to EOF the hard way.
+ // First, store stream position...
+ long pos = stream.getStreamPosition();
+
+ // ...skip 1k blocks until we're passed EOF...
+ while (stream.skipBytes(1024l) > 0) {
+ if (stream.read() == -1) {
+ break;
+ }
+
+ pos = stream.getStreamPosition();
+ }
+
+ // ...go back to last known pos...
+ stream.seek(pos);
+
+ // ...finally seek until EOF one byte at a time. Done.
+ while (stream.read() != -1) {
+ }
+ }
+
+ return stream.getStreamPosition();
+ }
+
+ public static void main(String[] args) throws IOException {
+ PCXImageReader reader = new PCXImageReader(null);
+
+ for (String arg : args) {
+ File in = new File(arg);
+ reader.setInput(ImageIO.createImageInputStream(in));
+
+ ImageReadParam param = reader.getDefaultReadParam();
+ param.setDestinationType(reader.getImageTypes(0).next());
+// param.setSourceSubsampling(2, 3, 0, 0);
+// param.setSourceSubsampling(2, 1, 0, 0);
+//
+// int width = reader.getHdpi(0);
+// int height = reader.getVdpi(0);
+//
+// param.setSourceRegion(new Rectangle(width / 4, height / 4, width / 2, height / 2));
+// param.setSourceRegion(new Rectangle(width / 2, height / 2));
+// param.setSourceRegion(new Rectangle(width / 2, height / 2, width / 2, height / 2));
+
+ System.err.println("header: " + reader.header);
+
+ BufferedImage image = reader.read(0, param);
+
+ System.err.println("image: " + image);
+
+ showIt(image, in.getName());
+
+ new XMLSerializer(System.out, System.getProperty("file.encoding"))
+ .serialize(reader.getImageMetadata(0).getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName), false);
+
+// File reference = new File(in.getParent() + "/../reference", in.getName().replaceAll("\\.p(a|b|g|p)m", ".png"));
+// if (reference.exists()) {
+// System.err.println("reference.getAbsolutePath(): " + reference.getAbsolutePath());
+// showIt(ImageIO.read(reference), reference.getName());
+// }
+
+// break;
+ }
+ }
+}
diff --git a/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXImageReaderSpi.java b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXImageReaderSpi.java
new file mode 100755
index 00000000..57b1a8eb
--- /dev/null
+++ b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXImageReaderSpi.java
@@ -0,0 +1,93 @@
+package com.twelvemonkeys.imageio.plugins.pcx;
+
+import com.twelvemonkeys.imageio.spi.ProviderInfo;
+import com.twelvemonkeys.imageio.util.IIOUtil;
+
+import javax.imageio.ImageReader;
+import javax.imageio.spi.ImageReaderSpi;
+import javax.imageio.stream.ImageInputStream;
+import java.io.IOException;
+import java.util.Locale;
+
+public final class PCXImageReaderSpi extends ImageReaderSpi {
+
+ /**
+ * Creates a {@code PCXImageReaderSpi}.
+ */
+ public PCXImageReaderSpi() {
+ this(IIOUtil.getProviderInfo(PCXImageReaderSpi.class));
+ }
+
+ private PCXImageReaderSpi(final ProviderInfo providerInfo) {
+ super(
+ providerInfo.getVendorName(),
+ providerInfo.getVersion(),
+ new String[]{
+ "pcx",
+ "PCX"
+ },
+ new String[]{"pcx"},
+ new String[]{
+ // No official IANA record exists
+ "image/pcx",
+ "image/x-pcx",
+ },
+ "com.twelvemkonkeys.imageio.plugins.pcx.PCXImageReader",
+ new Class[] {ImageInputStream.class},
+ null,
+ true, // supports standard stream metadata
+ null, null, // native stream format name and class
+ null, null, // extra stream formats
+ true, // supports standard image metadata
+ null, null,
+ null, null // extra image metadata formats
+ );
+ }
+
+ @Override public boolean canDecodeInput(final Object source) throws IOException {
+ if (!(source instanceof ImageInputStream)) {
+ return false;
+ }
+
+ ImageInputStream stream = (ImageInputStream) source;
+
+ stream.mark();
+
+ try {
+ byte magic = stream.readByte();
+
+ switch (magic) {
+ case PCX.MAGIC:
+ byte version = stream.readByte();
+
+ switch (version) {
+ case PCX.VERSION_2_5:
+ case PCX.VERSION_2_8_PALETTE:
+ case PCX.VERSION_2_8_NO_PALETTE:
+ case PCX.VERSION_2_X_WINDOWS:
+ case PCX.VERSION_3:
+ byte compression = stream.readByte();
+ byte bpp = stream.readByte();
+
+ return (compression == PCX.COMPRESSION_NONE || compression == PCX.COMPRESSION_RLE) && (bpp == 1 || bpp == 2 || bpp == 4 || bpp == 8);
+ default:
+ return false;
+ }
+ default:
+ return false;
+ }
+ }
+ finally {
+ stream.reset();
+ }
+ }
+
+ @Override public ImageReader createReaderInstance(final Object extension) throws IOException {
+ return new PCXImageReader(this);
+ }
+
+ @Override public String getDescription(final Locale locale) {
+ return "PC Paintbrush (PCX) image reader";
+ }
+}
+
diff --git a/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXMetadata.java b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXMetadata.java
new file mode 100755
index 00000000..6144a9c9
--- /dev/null
+++ b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXMetadata.java
@@ -0,0 +1,210 @@
+package com.twelvemonkeys.imageio.plugins.pcx;
+
+import java.awt.image.DataBuffer;
+import java.awt.image.IndexColorModel;
+
+import javax.imageio.IIOException;
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.metadata.IIOMetadataFormatImpl;
+import javax.imageio.metadata.IIOMetadataNode;
+
+import com.twelvemonkeys.imageio.util.IndexedImageTypeSpecifier;
+import org.w3c.dom.Node;
+
+final class PCXMetadata extends IIOMetadata {
+ // TODO: Clean up & extend AbstractMetadata (after moving from PSD -> Core)
+
+ private final PCXHeader header;
+ private final IndexColorModel vgaPalette;
+
+ PCXMetadata(final PCXHeader header, final IndexColorModel vgaPalette) {
+ this.header = header;
+ this.vgaPalette = vgaPalette;
+
+ standardFormatSupported = true;
+ }
+
+ @Override public boolean isReadOnly() {
+ return true;
+ }
+
+ @Override public Node getAsTree(final String formatName) {
+ if (formatName.equals(IIOMetadataFormatImpl.standardMetadataFormatName)) {
+ return getStandardTree();
+ }
+ else {
+ throw new IllegalArgumentException("Unsupported metadata format: " + formatName);
+ }
+ }
+
+ @Override public void mergeTree(final String formatName, final Node root) {
+ if (isReadOnly()) {
+ throw new IllegalStateException("Metadata is read-only");
+ }
+ }
+
+ @Override public void reset() {
+ if (isReadOnly()) {
+ throw new IllegalStateException("Metadata is read-only");
+ }
+ }
+
+ @Override protected IIOMetadataNode getStandardChromaNode() {
+ IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
+
+ IndexColorModel palette = null;
+
+ IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
+ switch (header.getBitsPerPixel()) {
+ case 1:
+ case 2:
+ case 4:
+ palette = header.getEGAPalette();
+ csType.setAttribute("name", "RGB");
+ break;
+ case 8:
+ // We may have IndexColorModel here for 1 channel images
+ if (header.getChannels() == 1 && header.getPaletteInfo() != PCX.PALETTEINFO_GRAY) {
+ palette = vgaPalette;
+ csType.setAttribute("name", "RGB");
+ break;
+ }
+ if (header.getChannels() == 1) {
+ csType.setAttribute("name", "GRAY");
+ break;
+ }
+ csType.setAttribute("name", "RGB");
+ break;
+
+ case 24:
+ // Some sources says this is possible... Untested.
+ csType.setAttribute("name", "RGB");
+ break;
+
+ default:
+ csType.setAttribute("name", "Unknown");
+ }
+
+ chroma.appendChild(csType);
+
+ if (palette != null) {
+ IIOMetadataNode paletteNode = new IIOMetadataNode("Palette");
+ chroma.appendChild(paletteNode);
+
+ for (int i = 0; i < palette.getMapSize(); i++) {
+ IIOMetadataNode paletteEntry = new IIOMetadataNode("PaletteEntry");
+ paletteEntry.setAttribute("index", Integer.toString(i));
+
+ paletteEntry.setAttribute("red", Integer.toString(palette.getRed(i)));
+ paletteEntry.setAttribute("green", Integer.toString(palette.getGreen(i)));
+ paletteEntry.setAttribute("blue", Integer.toString(palette.getBlue(i)));
+
+ paletteNode.appendChild(paletteEntry);
+ }
+ }
+
+ // TODO: Channels in chroma node should reflect channels in color model, not data! (see data node)
+ IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
+ numChannels.setAttribute("value", Integer.toString(header.getChannels()));
+ chroma.appendChild(numChannels);
+
+ IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
+ blackIsZero.setAttribute("value", "TRUE");
+ chroma.appendChild(blackIsZero);
+
+ return chroma;
+ }
+
+ // No compression
+
+ @Override protected IIOMetadataNode getStandardCompressionNode() {
+ if (header.getCompression() != PCX.COMPRESSION_NONE) {
+ IIOMetadataNode node = new IIOMetadataNode("Compression");
+
+ IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
+ compressionTypeName.setAttribute("value", header.getCompression() == PCX.COMPRESSION_RLE ? "RLE" : "Uknown");
+ node.appendChild(compressionTypeName);
+
+ IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
+ lossless.setAttribute("value", "TRUE");
+ node.appendChild(lossless);
+
+ return node;
+ }
+
+ return null;
+ }
+
+ @Override protected IIOMetadataNode getStandardDataNode() {
+ IIOMetadataNode node = new IIOMetadataNode("Data");
+
+ // Planar configuration only makes sense for multi-channel images
+ if (header.getChannels() > 1) {
+ IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration");
+ planarConfiguration.setAttribute("value", "LineInterleaved");
+ node.appendChild(planarConfiguration);
+ }
+
+ // TODO: SampleFormat value = Index if colormapped/palette data
+ IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
+ sampleFormat.setAttribute("value", "UnsignedIntegral");
+ node.appendChild(sampleFormat);
+
+ IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
+ bitsPerSample.setAttribute("value", createListValue(header.getChannels(), Integer.toString(header.getBitsPerPixel())));
+ node.appendChild(bitsPerSample);
+
+ IIOMetadataNode significantBitsPerSample = new IIOMetadataNode("SignificantBitsPerSample");
+ significantBitsPerSample.setAttribute("value", createListValue(header.getChannels(), Integer.toString(header.getBitsPerPixel())));
+ node.appendChild(significantBitsPerSample);
+
+ IIOMetadataNode sampleMSB = new IIOMetadataNode("SampleMSB");
+ sampleMSB.setAttribute("value", createListValue(header.getChannels(), "0"));
+
+ return node;
+ }
+
+ private String createListValue(final int itemCount, final String... values) {
+ StringBuilder buffer = new StringBuilder();
+
+ for (int i = 0; i < itemCount; i++) {
+ if (buffer.length() > 0) {
+ buffer.append(' ');
+ }
+
+ buffer.append(values[i % values.length]);
+ }
+
+ return buffer.toString();
+ }
+
+ @Override protected IIOMetadataNode getStandardDimensionNode() {
+ IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
+
+ IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
+ imageOrientation.setAttribute("value", "Normal");
+ dimension.appendChild(imageOrientation);
+
+ return dimension;
+ }
+
+ // TODO: document node with version
+
+ // No text node
+
+ // No tiling
+
+ @Override protected IIOMetadataNode getStandardTransparencyNode() {
+ // NOTE: There doesn't seem to be any god way to determine transparency, other than by convention
+ // 1 channel: Gray, 2 channel: Gray + Alpha, 3 channel: RGB, 4 channel: RGBA (hopefully never CMYK...)
+
+ IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
+
+ IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
+ alpha.setAttribute("value", header.getChannels() == 1 || header.getChannels() == 3 ? "none" : "nonpremultiplied");
+ transparency.appendChild(alpha);
+
+ return transparency;
+ }
+}
diff --git a/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/RLEDecoder.java b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/RLEDecoder.java
new file mode 100755
index 00000000..9f6608b8
--- /dev/null
+++ b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/RLEDecoder.java
@@ -0,0 +1,43 @@
+package com.twelvemonkeys.imageio.plugins.pcx;
+
+import com.twelvemonkeys.io.enc.Decoder;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+final class RLEDecoder implements Decoder {
+
+ static final int COMPRESSED_RUN_MASK = 0xc0;
+
+ // A rather strange and inefficient RLE encoding, but it probably made sense at the time...
+ // Uses the upper two bits to flag if the next values are to be treated as a compressed run.
+ // This means that any value above 0b11000000/0xc0/192 must be encoded as a compressed run,
+ // even if this will make the output larger.
+ public int decode(final InputStream stream, final ByteBuffer buffer) throws IOException {
+ while (buffer.remaining() >= 64) {
+ int val = stream.read();
+ if (val < 0) {
+ break; // EOF
+ }
+
+ if ((val & COMPRESSED_RUN_MASK) == COMPRESSED_RUN_MASK) {
+ int count = val & ~COMPRESSED_RUN_MASK;
+
+ int pixel = stream.read();
+ if (pixel < 0) {
+ break; // EOF
+ }
+
+ for (int i = 0; i < count; i++) {
+ buffer.put((byte) pixel);
+ }
+ }
+ else {
+ buffer.put((byte) val);
+ }
+ }
+
+ return buffer.position();
+ }
+}
diff --git a/imageio/imageio-pcx/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi b/imageio/imageio-pcx/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi
new file mode 100755
index 00000000..49b60604
--- /dev/null
+++ b/imageio/imageio-pcx/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi
@@ -0,0 +1,2 @@
+com.twelvemonkeys.imageio.plugins.pcx.PCXImageReaderSpi
+com.twelvemonkeys.imageio.plugins.dcx.DCXImageReaderSpi
diff --git a/imageio/imageio-pcx/src/test/java/com/twelvemonkeys/imageio/plugins/dcx/DCXImageReaderTest.java b/imageio/imageio-pcx/src/test/java/com/twelvemonkeys/imageio/plugins/dcx/DCXImageReaderTest.java
new file mode 100755
index 00000000..2aead130
--- /dev/null
+++ b/imageio/imageio-pcx/src/test/java/com/twelvemonkeys/imageio/plugins/dcx/DCXImageReaderTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2014, Harald Kuhr
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions 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 "TwelveMonkeys" nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.twelvemonkeys.imageio.plugins.dcx;
+
+import java.awt.Dimension;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.imageio.spi.ImageReaderSpi;
+
+import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
+
+/**
+ * DCXImageReaderTest
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: DCXImageReaderTest.java,v 1.0 03.07.14 22:28 haraldk Exp$
+ */
+public class DCXImageReaderTest extends ImageReaderAbstractTestCase {
+ @Override
+ protected List getTestData() {
+ return Arrays.asList(
+ new TestData(getClassLoaderResource("/dcx/input.dcx"), new Dimension(70, 46)) // RLE encoded RGB (the only sample I've found)
+ );
+ }
+
+ @Override
+ protected ImageReaderSpi createProvider() {
+ return new DCXImageReaderSpi();
+ }
+
+ @Override
+ protected Class getReaderClass() {
+ return DCXImageReader.class;
+ }
+
+ @Override
+ protected DCXImageReader createReader() {
+ return new DCXImageReader(createProvider());
+ }
+
+ @Override
+ protected List getFormatNames() {
+ return Arrays.asList("DCX", "dcx");
+ }
+
+ @Override
+ protected List getSuffixes() {
+ return Arrays.asList("dcx");
+ }
+
+ @Override
+ protected List getMIMETypes() {
+ return Arrays.asList(
+ "image/dcx", "image/x-dcx"
+ );
+ }
+}
diff --git a/imageio/imageio-pcx/src/test/java/com/twelvemonkeys/imageio/plugins/pcx/PCXImageReaderTest.java b/imageio/imageio-pcx/src/test/java/com/twelvemonkeys/imageio/plugins/pcx/PCXImageReaderTest.java
new file mode 100755
index 00000000..7fc1e107
--- /dev/null
+++ b/imageio/imageio-pcx/src/test/java/com/twelvemonkeys/imageio/plugins/pcx/PCXImageReaderTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2014, Harald Kuhr
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions 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 "TwelveMonkeys" nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.twelvemonkeys.imageio.plugins.pcx;
+
+import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
+
+import javax.imageio.spi.ImageReaderSpi;
+import java.awt.*;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * PCXImageReaderTest
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: PCXImageReaderTest.java,v 1.0 03.07.14 22:28 haraldk Exp$
+ */
+public class PCXImageReaderTest extends ImageReaderAbstractTestCase {
+ @Override
+ protected List getTestData() {
+ return Arrays.asList(
+ new TestData(getClassLoaderResource("/pcx/MARBLES.PCX"), new Dimension(1419, 1001)), // RLE encoded RGB
+// new TestData(getClassLoaderResource("/pcx/GMARBLES.PCX"), new Dimension(1419, 1001)) // RLE encoded gray (seems to be damanged, missing the last few scan lines)
+ new TestData(getClassLoaderResource("/pcx/lena.pcx"), new Dimension(512, 512)), // RLE encoded RGB
+ new TestData(getClassLoaderResource("/pcx/lena2.pcx"), new Dimension(512, 512)), // RLE encoded, 256 color indexed (8 bps/1 channel)
+ new TestData(getClassLoaderResource("/pcx/lena3.pcx"), new Dimension(512, 512)), // RLE encoded, 16 color indexed (4 bps/1 channel)
+ new TestData(getClassLoaderResource("/pcx/lena4.pcx"), new Dimension(512, 512)), // RLE encoded, 16 color indexed (1 bps/4 channels)
+ new TestData(getClassLoaderResource("/pcx/lena5.pcx"), new Dimension(512, 512)), // RLE encoded, 256 color indexed (8 bps/1 channel)
+ new TestData(getClassLoaderResource("/pcx/lena6.pcx"), new Dimension(512, 512)), // RLE encoded, 8 colorindexed (1 bps/3 channels)
+ new TestData(getClassLoaderResource("/pcx/lena7.pcx"), new Dimension(512, 512)), // RLE encoded, 4 color indexed (1 bps/2 channels)
+ new TestData(getClassLoaderResource("/pcx/lena8.pcx"), new Dimension(512, 512)), // RLE encoded, 4 color indexed (2 bps/1 channel)
+ new TestData(getClassLoaderResource("/pcx/lena9.pcx"), new Dimension(512, 512)), // RLE encoded, 2 color indexed (1 bps/1 channel)
+ new TestData(getClassLoaderResource("/pcx/lena10.pcx"), new Dimension(512, 512)), // RLE encoded, 16 color indexed (4 bps/1 channel) (uses only 8 colors)
+ new TestData(getClassLoaderResource("/pcx/DARKSTAR.PCX"), new Dimension(88, 52)), // RLE encoded monochrome (1 bps/1 channel)
+ // TODO: Get correct colors for CGA mode, see cga-pcx.txt (however, the text seems to be in error, the bits are not as described)
+ new TestData(getClassLoaderResource("/pcx/CGA_BW.PCX"), new Dimension(640, 200)), // RLE encoded indexed (CGA mode)
+ new TestData(getClassLoaderResource("/pcx/CGA_FSD.PCX"), new Dimension(320, 200)), // RLE encoded indexed (CGA mode)
+ new TestData(getClassLoaderResource("/pcx/CGA_RGBI.PCX"), new Dimension(320, 200)), // RLE encoded indexed (CGA mode)
+ new TestData(getClassLoaderResource("/pcx/CGA_TST1.PCX"), new Dimension(320, 200)) // RLE encoded indexed (CGA mode)
+ );
+ }
+
+ @Override
+ protected ImageReaderSpi createProvider() {
+ return new PCXImageReaderSpi();
+ }
+
+ @Override
+ protected Class getReaderClass() {
+ return PCXImageReader.class;
+ }
+
+ @Override
+ protected PCXImageReader createReader() {
+ return new PCXImageReader(createProvider());
+ }
+
+ @Override
+ protected List getFormatNames() {
+ return Arrays.asList("PCX", "pcx");
+ }
+
+ @Override
+ protected List getSuffixes() {
+ return Arrays.asList("pcx");
+ }
+
+ @Override
+ protected List getMIMETypes() {
+ return Arrays.asList(
+ "image/pcx", "image/x-pcx"
+ );
+ }
+}
diff --git a/imageio/imageio-pcx/src/test/resources/dcx/input.dcx b/imageio/imageio-pcx/src/test/resources/dcx/input.dcx
new file mode 100755
index 00000000..15897a6a
Binary files /dev/null and b/imageio/imageio-pcx/src/test/resources/dcx/input.dcx differ
diff --git a/imageio/imageio-pcx/src/test/resources/pcx/CGA_BW.PCX b/imageio/imageio-pcx/src/test/resources/pcx/CGA_BW.PCX
new file mode 100755
index 00000000..d135810d
Binary files /dev/null and b/imageio/imageio-pcx/src/test/resources/pcx/CGA_BW.PCX differ
diff --git a/imageio/imageio-pcx/src/test/resources/pcx/CGA_FSD.PCX b/imageio/imageio-pcx/src/test/resources/pcx/CGA_FSD.PCX
new file mode 100755
index 00000000..242429a6
Binary files /dev/null and b/imageio/imageio-pcx/src/test/resources/pcx/CGA_FSD.PCX differ
diff --git a/imageio/imageio-pcx/src/test/resources/pcx/CGA_RGBI.PCX b/imageio/imageio-pcx/src/test/resources/pcx/CGA_RGBI.PCX
new file mode 100755
index 00000000..e341f804
Binary files /dev/null and b/imageio/imageio-pcx/src/test/resources/pcx/CGA_RGBI.PCX differ
diff --git a/imageio/imageio-pcx/src/test/resources/pcx/CGA_TST1.PCX b/imageio/imageio-pcx/src/test/resources/pcx/CGA_TST1.PCX
new file mode 100755
index 00000000..6f492d91
Binary files /dev/null and b/imageio/imageio-pcx/src/test/resources/pcx/CGA_TST1.PCX differ
diff --git a/imageio/imageio-pcx/src/test/resources/pcx/DARKSTAR.PCX b/imageio/imageio-pcx/src/test/resources/pcx/DARKSTAR.PCX
new file mode 100755
index 00000000..292cd6d3
Binary files /dev/null and b/imageio/imageio-pcx/src/test/resources/pcx/DARKSTAR.PCX differ
diff --git a/imageio/imageio-pcx/src/test/resources/pcx/GMARBLES.PCX b/imageio/imageio-pcx/src/test/resources/pcx/GMARBLES.PCX
new file mode 100755
index 00000000..d15658ab
Binary files /dev/null and b/imageio/imageio-pcx/src/test/resources/pcx/GMARBLES.PCX differ
diff --git a/imageio/imageio-pcx/src/test/resources/pcx/MARBLES.PCX b/imageio/imageio-pcx/src/test/resources/pcx/MARBLES.PCX
new file mode 100755
index 00000000..8ddaae4c
Binary files /dev/null and b/imageio/imageio-pcx/src/test/resources/pcx/MARBLES.PCX differ
diff --git a/imageio/imageio-pcx/src/test/resources/pcx/cga-pcx.txt b/imageio/imageio-pcx/src/test/resources/pcx/cga-pcx.txt
new file mode 100755
index 00000000..03f357bd
--- /dev/null
+++ b/imageio/imageio-pcx/src/test/resources/pcx/cga-pcx.txt
@@ -0,0 +1,33 @@
+From Jim Leonard (trixter -at- oldskool.org)
+
+Here you go. I made these myself, and made sure to flex all of the
+PCX format's CGA attributes. These all load perfectly under *REAL*
+PC Paintbrush 4.0 in DOS on a CGA card, so if your decoder screws it
+up, it's the fault of your decoder!
+
+They are:
+
+CGA_FSD.PCX Standard lightcyan-lightmagenta-white palette (intensity
+bit set) with black background. When most people think of CGA, they're
+thinking of this.
+
+0x00
+000 0 0000
+
+CGA_TST1.PCX Different palette (intensite bit NOT set) and different
+background color. If you can read the text inside the box at the bottom,
+your decoder is shite :-)
+
+0xf0
+111 1 0000
+
+
+CGA_RGBI.PCX Less commonly-used lightred-lightgreen-yellow palette
+(intensity bit set) with a blue background.
+
+0xa4
+101 0 0100
+
+
+CGA_BW.PCX 640x200 B&W pic in case he didn't have a sample.
+Most PCX decoders handle this just fine.
diff --git a/imageio/imageio-pcx/src/test/resources/pcx/lena.pcx b/imageio/imageio-pcx/src/test/resources/pcx/lena.pcx
new file mode 100755
index 00000000..7a00c7f2
Binary files /dev/null and b/imageio/imageio-pcx/src/test/resources/pcx/lena.pcx differ
diff --git a/imageio/imageio-pcx/src/test/resources/pcx/lena10.pcx b/imageio/imageio-pcx/src/test/resources/pcx/lena10.pcx
new file mode 100755
index 00000000..91447bc3
Binary files /dev/null and b/imageio/imageio-pcx/src/test/resources/pcx/lena10.pcx differ
diff --git a/imageio/imageio-pcx/src/test/resources/pcx/lena2.pcx b/imageio/imageio-pcx/src/test/resources/pcx/lena2.pcx
new file mode 100755
index 00000000..00ce7fd9
Binary files /dev/null and b/imageio/imageio-pcx/src/test/resources/pcx/lena2.pcx differ
diff --git a/imageio/imageio-pcx/src/test/resources/pcx/lena3.pcx b/imageio/imageio-pcx/src/test/resources/pcx/lena3.pcx
new file mode 100755
index 00000000..22499655
Binary files /dev/null and b/imageio/imageio-pcx/src/test/resources/pcx/lena3.pcx differ
diff --git a/imageio/imageio-pcx/src/test/resources/pcx/lena4.pcx b/imageio/imageio-pcx/src/test/resources/pcx/lena4.pcx
new file mode 100755
index 00000000..30a625c7
Binary files /dev/null and b/imageio/imageio-pcx/src/test/resources/pcx/lena4.pcx differ
diff --git a/imageio/imageio-pcx/src/test/resources/pcx/lena5.pcx b/imageio/imageio-pcx/src/test/resources/pcx/lena5.pcx
new file mode 100755
index 00000000..00ce7fd9
Binary files /dev/null and b/imageio/imageio-pcx/src/test/resources/pcx/lena5.pcx differ
diff --git a/imageio/imageio-pcx/src/test/resources/pcx/lena6.pcx b/imageio/imageio-pcx/src/test/resources/pcx/lena6.pcx
new file mode 100755
index 00000000..397c84bf
Binary files /dev/null and b/imageio/imageio-pcx/src/test/resources/pcx/lena6.pcx differ
diff --git a/imageio/imageio-pcx/src/test/resources/pcx/lena7.pcx b/imageio/imageio-pcx/src/test/resources/pcx/lena7.pcx
new file mode 100755
index 00000000..bb209100
Binary files /dev/null and b/imageio/imageio-pcx/src/test/resources/pcx/lena7.pcx differ
diff --git a/imageio/imageio-pcx/src/test/resources/pcx/lena8.pcx b/imageio/imageio-pcx/src/test/resources/pcx/lena8.pcx
new file mode 100755
index 00000000..efa89d4e
Binary files /dev/null and b/imageio/imageio-pcx/src/test/resources/pcx/lena8.pcx differ
diff --git a/imageio/imageio-pcx/src/test/resources/pcx/lena9.pcx b/imageio/imageio-pcx/src/test/resources/pcx/lena9.pcx
new file mode 100755
index 00000000..064028cd
Binary files /dev/null and b/imageio/imageio-pcx/src/test/resources/pcx/lena9.pcx differ
diff --git a/imageio/pom.xml b/imageio/pom.xml
index ab2d7b0c..391574bf 100644
--- a/imageio/pom.xml
+++ b/imageio/pom.xml
@@ -32,6 +32,7 @@
imageio-icns
imageio-iff
imageio-jpeg
+ imageio-pcx
imageio-pdf
imageio-pict
imageio-pnm