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
index 81be60dd..0516560a 100755
--- 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
@@ -56,52 +56,36 @@ final class CGAColorModel {
// 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 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);
+ if (PCXImageReader.DEBUG) {
+ 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];
- }
+ if (paletteValue) {
+ if (intensityValue) {
+ cmap[1] = CGA_PALETTE[11];
+ cmap[2] = colorBurstEnable ? CGA_PALETTE[13] : CGA_PALETTE[12];
+ cmap[3] = CGA_PALETTE[15];
} 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];
- }
+ cmap[1] = CGA_PALETTE[3];
+ cmap[2] = colorBurstEnable ? CGA_PALETTE[5] : CGA_PALETTE[4];
+ cmap[3] = CGA_PALETTE[7];
}
} else {
if (intensityValue) {
- cmap[1] = CGA_PALETTE[11];
+ cmap[1] = CGA_PALETTE[10];
cmap[2] = CGA_PALETTE[12];
- cmap[3] = CGA_PALETTE[15];
+ cmap[3] = CGA_PALETTE[14];
} else {
- cmap[1] = CGA_PALETTE[4];
- cmap[2] = CGA_PALETTE[5];
- cmap[3] = CGA_PALETTE[7];
+ cmap[1] = CGA_PALETTE[2];
+ cmap[2] = CGA_PALETTE[4];
+ cmap[3] = CGA_PALETTE[6];
}
}
}
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
index 98e4ab5a..fd74169d 100755
--- 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
@@ -30,11 +30,14 @@ package com.twelvemonkeys.imageio.plugins.pcx;
import javax.imageio.IIOException;
import javax.imageio.stream.ImageInputStream;
+import java.awt.image.DataBuffer;
import java.awt.image.IndexColorModel;
import java.io.IOException;
import java.util.Arrays;
final class PCXHeader {
+ private static final IndexColorModel MONOCHROME = new IndexColorModel(1, 2, new int[] {0, -1}, 0, false, -1, DataBuffer.TYPE_BYTE);
+
private int version;
private int compression;
private int bitsPerPixel;
@@ -69,14 +72,6 @@ final class PCXHeader {
return height;
}
- public int getHdpi() {
- return hdpi;
- }
-
- public int getVdpi() {
- return vdpi;
- }
-
public int getChannels() {
return channels;
}
@@ -85,22 +80,63 @@ final class PCXHeader {
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);
-// }
+ // Test for CGA modes
+ if (isCGAVideoMode4() || isCGAVideoMode5() || isCGAVideoMode6()) {
+ return CGAColorModel.create(palette, bitsPerPixel);
+ }
+
+ // Test if we should use a default B/W palette
+ if (bitsPerPixel == 1 && channels == 1 && (version < PCX.VERSION_2_X_WINDOWS || isDummyPalette())) {
+ return MONOCHROME;
+ }
int bits = channels * bitsPerPixel;
return new IndexColorModel(bits, Math.min(16, 1 << bits), palette, 0, false);
}
+ private boolean isCGAVideoMode4() {
+ return bitsPerPixel * channels == 2 && width == 320 && hdpi == 320 && height == 200 && vdpi == 200;
+ }
+
+ private boolean isCGAVideoMode5() {
+ return bitsPerPixel == 1 && channels == 1 && width == 320 && hdpi == 320 && height == 200 && vdpi == 200;
+ }
+
+ private boolean isCGAVideoMode6() {
+ return bitsPerPixel == 1 && channels == 1 && width == 640 && hdpi == 640 && height == 200 && vdpi == 200;
+ }
+
+ private boolean isDummyPalette() {
+ return isEmptyPalette() || isPhotoshopPalette();
+ }
+
+ private boolean isEmptyPalette() {
+ // All black
+ for (int i = 0; i < 48; i++) {
+ if (palette[i] != 0) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private boolean isPhotoshopPalette() {
+ // Written by Photoshop: 15,15,15, 14,14,14, ... 0,0,0
+ for (int i = 0; i < 16; i++) {
+ int off = i * 3;
+
+ if (palette[off] != 15 - i || palette[off + 1] != 15 - i || palette[off + 2] != 15 - i) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
@Override public String toString() {
- return "PCXHeader{" +
+ return "PCXHeader[" +
"version=" + version +
", compression=" + compression +
", bitsPerPixel=" + bitsPerPixel +
@@ -114,7 +150,7 @@ final class PCXHeader {
", hScreenSize=" + hScreenSize +
", vScreenSize=" + vScreenSize +
", palette=" + Arrays.toString(palette) +
- '}';
+ ']';
}
public static PCXHeader read(final ImageInputStream imageInput) throws IOException {
@@ -142,7 +178,7 @@ final class PCXHeader {
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));
+ throw new IIOException(String.format("Not a PCX image. Expected PCX magic 0x%02x: 0x%02x", PCX.MAGIC, magic));
}
PCXHeader header = new PCXHeader();
@@ -165,10 +201,10 @@ final class PCXHeader {
imageInput.readUnsignedByte(); // Reserved, should be 0
- header.channels = imageInput.readUnsignedByte();
+ header.channels = imageInput.readUnsignedByte(); // Channels or Bit planes
header.bytesPerLine = imageInput.readUnsignedShort(); // Must be even!
- header.paletteInfo = imageInput.readUnsignedShort(); // 1 == Color/BW, 2 == Gray
+ header.paletteInfo = imageInput.readUnsignedShort() & 0x2; // 1 == Color/BW, 2 == Gray. Ignored
header.hScreenSize = imageInput.readUnsignedShort();
header.vScreenSize = imageInput.readUnsignedShort();
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
index 77b81fef..3cccb321 100755
--- 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
@@ -54,7 +54,15 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
+/**
+ * ImageReader for ZSoft PC Paintbrush (PCX) format.
+ *
+ * @see PCX Graphics
+ */
public final class PCXImageReader extends ImageReaderBase {
+
+ final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.pcx.debug"));
+
/** 8 bit ImageTypeSpecifer used for reading bitplane images. */
private static final ImageTypeSpecifier GRAYSCALE = ImageTypeSpecifiers.createGrayscale(8, DataBuffer.TYPE_BYTE);
@@ -93,7 +101,7 @@ public final class PCXImageReader extends ImageReaderBase {
public Iterator getImageTypes(final int imageIndex) throws IOException {
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
- List specifiers = new ArrayList();
+ List specifiers = new ArrayList<>();
// TODO: Implement
specifiers.add(rawType);
@@ -107,27 +115,30 @@ public final class PCXImageReader extends ImageReaderBase {
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:
+ // TODO: If there's a VGA palette here, use it?
+
return ImageTypeSpecifiers.createFromIndexColorModel(header.getEGAPalette());
case 8:
- // We may have IndexColorModel here for 1 channel images
- if (channels == 1 && paletteInfo != PCX.PALETTEINFO_GRAY) {
+ if (channels == 1) {
+ // We may have IndexColorModel here for 1 channel images
IndexColorModel palette = getVGAPalette();
- if (palette == null) {
- throw new IIOException("Expected VGA palette not found");
- }
- return ImageTypeSpecifiers.createFromIndexColorModel(palette);
+ if (palette != null) {
+ return ImageTypeSpecifiers.createFromIndexColorModel(palette);
+ }
+ else {
+ // PCX Gray has 1 channel and no palette
+ return ImageTypeSpecifiers.createGrayscale(8, DataBuffer.TYPE_BYTE);
+ }
}
- // PCX has 1 or 3 channels for 8 bit gray or 24 bit RGB, will be validated by ImageTypeSpecifier
- return ImageTypeSpecifiers.createBanded(cs, createIndices(channels, 1), createIndices(channels, 0), DataBuffer.TYPE_BYTE, false, false);
+ // PCX RGB has channels for 24 bit RGB, will be validated by ImageTypeSpecifier
+ return ImageTypeSpecifiers.createBanded(ColorSpace.getInstance(ColorSpace.CS_sRGB), createIndices(channels, 1), createIndices(channels, 0), DataBuffer.TYPE_BYTE, channels == 4, false);
case 24:
// Some sources says this is possible...
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
@@ -154,10 +165,6 @@ public final class PCXImageReader extends ImageReaderBase {
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);
@@ -174,8 +181,8 @@ public final class PCXImageReader extends ImageReaderBase {
// 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;
+ ? new DataInputStream(new DecoderStream(IIOUtil.createStreamAdapter(imageInput), new RLEDecoder()))
+ : imageInput;
int xSub = param != null ? param.getSourceXSubsampling() : 1;
int ySub = param != null ? param.getSourceYSubsampling() : 1;
@@ -191,8 +198,8 @@ public final class PCXImageReader extends ImageReaderBase {
// Clip to source region
Raster clippedRow = clipRowToRect(rowRaster, srcRegion,
- param != null ? param.getSourceBands() : null,
- param != null ? param.getSourceXSubsampling() : 1);
+ param != null ? param.getSourceBands() : null,
+ param != null ? param.getSourceXSubsampling() : 1);
byte[] planeData = new byte[rowWidth];
byte[] rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
@@ -257,8 +264,8 @@ public final class PCXImageReader extends ImageReaderBase {
// Clip to source region
Raster clippedRow = clipRowToRect(rowRaster, srcRegion,
- param != null ? param.getSourceBands() : null,
- param != null ? param.getSourceXSubsampling() : 1);
+ 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++) {
@@ -351,7 +358,11 @@ public final class PCXImageReader extends ImageReaderBase {
if (header == null) {
imageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
header = PCXHeader.read(imageInput);
-// System.err.println("header: " + header);
+
+ if (DEBUG) {
+ System.err.println("header: " + header);
+ }
+
imageInput.flushBefore(imageInput.getStreamPosition());
}
@@ -372,29 +383,27 @@ public final class PCXImageReader extends ImageReaderBase {
// 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
- skipToEOF(imageInput);
+ if (header.getVersion() >= PCX.VERSION_3 || header.getVersion() == PCX.VERSION_2_8_PALETTE) {
+ // We can't simply skip to an offset, as the RLE compression makes the file size unpredictable
+ skipToEOF(imageInput);
- // Seek backwards from EOF
- long paletteStart = imageInput.getStreamPosition() - 769;
- if (paletteStart <= imageInput.getFlushedPosition()) {
- return null;
+ int paletteSize = 256 * 3; // 256 * 3 for RGB
+
+ // Seek backwards from EOF
+ long paletteStart = imageInput.getStreamPosition() - paletteSize - 1;
+ if (paletteStart > imageInput.getFlushedPosition()) {
+ imageInput.seek(paletteStart);
+
+ byte val = imageInput.readByte();
+
+ if (val == PCX.VGA_PALETTE_MAGIC) {
+ byte[] palette = new byte[paletteSize];
+ imageInput.readFully(palette);
+
+ vgaPalette = new IndexColorModel(8, 256, palette, 0, false);
+ }
+ }
}
-
- 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;
@@ -414,7 +423,7 @@ public final class PCXImageReader extends ImageReaderBase {
long pos = stream.getStreamPosition();
// ...skip 1k blocks until we're passed EOF...
- while (stream.skipBytes(1024l) > 0) {
+ while (stream.skipBytes(1024L) > 0) {
if (stream.read() == -1) {
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
index bd3ab064..73a93bf5 100755
--- 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
@@ -87,7 +87,7 @@ public final class PCXImageReaderSpi extends ImageReaderSpiBase {
}
@Override public String getDescription(final Locale locale) {
- return "PC Paintbrush (PCX) image reader";
+ return "ZSoft PC Paintbrush (PCX) image reader";
}
}
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
index f662f4db..dfc758c5 100755
--- 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
@@ -31,13 +31,20 @@ package com.twelvemonkeys.imageio.plugins.pcx;
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import org.junit.Test;
+import javax.imageio.ImageIO;
+import javax.imageio.ImageReadParam;
import javax.imageio.spi.ImageReaderSpi;
+import javax.imageio.stream.ImageInputStream;
import java.awt.*;
+import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
/**
* PCXImageReaderTest
*
@@ -50,19 +57,18 @@ public class PCXImageReaderTest extends ImageReaderAbstractTest
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 damaged, 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/lena6.pcx"), new Dimension(512, 512)), // RLE encoded, 8 color indexed (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)
+ // See cga-pcx.txt, however, the text seems to be in error, the bits can not 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)
@@ -102,6 +108,29 @@ public class PCXImageReaderTest extends ImageReaderAbstractTest
);
}
+ @Test
+ public void testReadGray() throws IOException {
+ // Seems like the last scan lines have been overwritten by an unnecessary 768 byte palette + 1 byte magic...
+ try (ImageInputStream input = ImageIO.createImageInputStream(getClassLoaderResource("/pcx/GMARBLES.PCX"))) {
+ PCXImageReader reader = createReader();
+ reader.setInput(input);
+
+ assertEquals(1, reader.getNumImages(true));
+ assertEquals(1419, reader.getWidth(0));
+ assertEquals(1001, reader.getHeight(0));
+
+ ImageReadParam param = reader.getDefaultReadParam();
+ param.setSourceRegion(new Rectangle(1419, 1000)); // Ignore the last garbled line
+
+ BufferedImage image = reader.read(0, param);
+
+ assertNotNull(image);
+ assertEquals(BufferedImage.TYPE_BYTE_INDEXED, image.getType());
+ assertEquals(1419, image.getWidth());
+ assertEquals(1000, image.getHeight());
+ }
+ }
+
@Test
public void testReadWithSourceRegionParamEqualImage() throws IOException {
TestData data = getTestData().get(1);