TMI-PCX: Initial commit.

This commit is contained in:
Harald Kuhr 2014-10-01 14:34:51 +02:00
parent 74637105b8
commit 5313ffd6fc
37 changed files with 1758 additions and 0 deletions

25
imageio/imageio-pcx/license.txt Executable file
View File

@ -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.

28
imageio/imageio-pcx/pom.xml Executable file
View File

@ -0,0 +1,28 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.1-SNAPSHOT</version>
</parent>
<artifactId>imageio-pcx</artifactId>
<name>TwelveMonkeys :: ImageIO :: PCX plugin</name>
<description>
ImageIO plugin for ZSoft Paintbrush Format (PCX)
</description>
<dependencies>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-core</artifactId>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-core</artifactId>
<classifier>tests</classifier>
</dependency>
</dependencies>
</project>

View File

@ -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 <a href="http://www.fileformat.info/format/pcx/egff.htm#PCX-DMYID.3.8">[PCX] Related File Formats</a>
*/
interface DCX {
int MAGIC = 0x3ADE68B1;
}

View File

@ -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) +
'}';
}
}

View File

@ -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<ImageTypeSpecifier> 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;
}
}
}

View File

@ -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";
}}

View File

@ -0,0 +1,99 @@
package com.twelvemonkeys.imageio.plugins.pcx;
/**
* IFFUtil
* <p/>
* 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);
}
}
}
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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<ImageTypeSpecifier> getImageTypes(final int imageIndex) throws IOException {
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
List<ImageTypeSpecifier> specifiers = new ArrayList<ImageTypeSpecifier>();
// 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<ImageTypeSpecifier> 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;
}
}
}

View File

@ -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";
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -0,0 +1,2 @@
com.twelvemonkeys.imageio.plugins.pcx.PCXImageReaderSpi
com.twelvemonkeys.imageio.plugins.dcx.DCXImageReaderSpi

View File

@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @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<DCXImageReader> {
@Override
protected List<TestData> 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<DCXImageReader> getReaderClass() {
return DCXImageReader.class;
}
@Override
protected DCXImageReader createReader() {
return new DCXImageReader(createProvider());
}
@Override
protected List<String> getFormatNames() {
return Arrays.asList("DCX", "dcx");
}
@Override
protected List<String> getSuffixes() {
return Arrays.asList("dcx");
}
@Override
protected List<String> getMIMETypes() {
return Arrays.asList(
"image/dcx", "image/x-dcx"
);
}
}

View File

@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @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<PCXImageReader> {
@Override
protected List<TestData> 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<PCXImageReader> getReaderClass() {
return PCXImageReader.class;
}
@Override
protected PCXImageReader createReader() {
return new PCXImageReader(createProvider());
}
@Override
protected List<String> getFormatNames() {
return Arrays.asList("PCX", "pcx");
}
@Override
protected List<String> getSuffixes() {
return Arrays.asList("pcx");
}
@Override
protected List<String> getMIMETypes() {
return Arrays.asList(
"image/pcx", "image/x-pcx"
);
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -32,6 +32,7 @@
<module>imageio-icns</module>
<module>imageio-iff</module>
<module>imageio-jpeg</module>
<module>imageio-pcx</module>
<module>imageio-pdf</module>
<module>imageio-pict</module>
<module>imageio-pnm</module>