Fixed numerous long-standing bugs in the IFFImageReader and IFFImageWriter.

- Fixed EOF bug for RLE compressed BODY chunks with padding
 - Fixed alignment bug for rows (now 16 bit aligned/padded).
 - Added a couple of TODOs for more known bugs...
 - Some general clean-up.
This commit is contained in:
Harald Kuhr 2009-11-04 01:27:56 +01:00
parent c801926a02
commit 96a74e0b81
5 changed files with 62 additions and 45 deletions

View File

@ -154,7 +154,7 @@ public class IFFImageReader extends ImageReaderBase {
remaining -= 8;
remaining -= length % 2 == 0 ? length : length + 1;
//System.out.println("Next chunk: " + toChunkStr(chunkId) + " lenght: " + length);
//System.out.println("Next chunk: " + toChunkStr(chunkId) + " length: " + length);
//System.out.println("Remaining bytes after chunk: " + remaining);
switch (chunkId) {
@ -232,6 +232,7 @@ public class IFFImageReader extends ImageReaderBase {
readBody(pParam);
}
else {
// TODO: Remove this hack when we have metadata
// In the rare case of an ILBM containing nothing but a CMAP
//System.out.println(mColorMap);
if (mColorMap != null) {
@ -244,7 +245,6 @@ public class IFFImageReader extends ImageReaderBase {
processImageComplete();
return result;
}
@ -294,6 +294,7 @@ public class IFFImageReader extends ImageReaderBase {
// 8 bit
// May be HAM8
if (!isHAM()) {
// TODO: This is probably a bug, as mColorMap is null in case of gray..
IndexColorModel cm = mColorMap.getIndexColorModel();
if (cm != null) {
specifier = IndexedImageTypeSpecifier.createFromIndexColorModel(cm);
@ -304,6 +305,7 @@ public class IFFImageReader extends ImageReaderBase {
break;
}
}
// NOTE: HAM modes falls through, as they are converted to RGB
case 24:
// 24 bit RGB
specifier = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
@ -313,12 +315,12 @@ public class IFFImageReader extends ImageReaderBase {
specifier = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR);
break;
default:
throw new IIOException("Bit depth not implemented: " + mHeader.mBitplanes);
throw new IIOException(String.format("Bit depth not implemented: %d", mHeader.mBitplanes));
}
return specifier;
}
private void readBody(ImageReadParam pParam) throws IOException {
private void readBody(final ImageReadParam pParam) throws IOException {
mImageInput.seek(mBodyStart); // 8 for the header before length in stream
mByteRunStream = null;
@ -333,7 +335,7 @@ public class IFFImageReader extends ImageReaderBase {
}
private void readIndexed(ImageReadParam pParam, final ImageInputStream pInput, final IndexColorModel pModel) throws IOException {
private void readIndexed(final ImageReadParam pParam, final ImageInputStream pInput, final IndexColorModel pModel) throws IOException {
final int width = mHeader.mWidth;
final int height = mHeader.mHeight;
@ -363,7 +365,9 @@ public class IFFImageReader extends ImageReaderBase {
destination = destination.createWritableChild(0, 0, destination.getWidth(), destination.getHeight(), offset.x, offset.y, destinationBands);
}
int planeWidth = (width + 7) / 8;
// NOTE: Each row of the image is stored in an integral number of 16 bit words.
// The number of words per row is words=((w+15)/16)
int planeWidth = 2 * ((width + 15) / 16);
final byte[] planeData = new byte[8 * planeWidth];
ColorModel cm;
@ -373,10 +377,14 @@ public class IFFImageReader extends ImageReaderBase {
// TODO: If HAM6, use type USHORT_444_RGB or 2BYTE_444_RGB?
// Or create a HAMColorModel, if at all possible?
// TYPE_3BYTE_BGR
cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[]{8, 8, 8},
false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
cm = new ComponentColorModel(
ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[]{8, 8, 8},
false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE
);
// Create a byte raster with BGR order
raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, width, 1, width * 3, 3, new int[]{2, 1, 0}, null);
raster = Raster.createInterleavedRaster(
DataBuffer.TYPE_BYTE, width, 1, width * 3, 3, new int[]{2, 1, 0}, null
);
}
else {
// TYPE_BYTE_BINARY or TYPE_BYTE_INDEXED
@ -400,15 +408,9 @@ public class IFFImageReader extends ImageReaderBase {
ColorConvertOp converter = null;
for (int srcY = 0; srcY < height; srcY++) {
for (int p = 0; p < planes; p++) {
try {
readPlaneData(pInput, planeData, p * planeWidth, planeWidth);
}
catch (IOException e) {
// TODO: Add warning? Probbably a bug somewhere, should not catch!
}
}
// Skip rows outside AOI
if (srcY < aoi.y || (srcY - aoi.y) % sourceYSubsampling != 0) {
@ -420,11 +422,9 @@ public class IFFImageReader extends ImageReaderBase {
if (mFormType == IFF.TYPE_ILBM) {
int pixelPos = 0;
int planePos = 0;
for (int i = 0; i < planeWidth; i++) {
for (int planePos = 0; planePos < planeWidth; planePos++) {
IFFUtil.bitRotateCW(planeData, planePos, planeWidth, row, pixelPos, 1);
pixelPos += 8;
planePos++;
}
if (isHAM()) {
@ -434,11 +434,14 @@ public class IFFImageReader extends ImageReaderBase {
raster.setDataElements(0, 0, width, 1, row);
}
}
else /*if (mType == IFFImageReader.TYPE_PBM)*/ {
// TODO: Arraycopy might not be neccessary, if it's okay with row larger than width
else if (mFormType == IFF.TYPE_PBM) {
// TODO: Arraycopy might not be necessary, if it's okay with row larger than width
System.arraycopy(planeData, 0, row, 0, mHeader.mBitplanes * planeWidth);
raster.setDataElements(0, 0, width, 1, row);
}
else {
throw new AssertionError(String.format("Unsupported FORM type: %s", mFormType));
}
int dstY = (srcY - aoi.y) / sourceYSubsampling;
// Handle non-converting raster as special case for performance
@ -521,8 +524,9 @@ public class IFFImageReader extends ImageReaderBase {
// Ensure band settings from param are compatible with images
checkReadParamBandSettings(pParam, mHeader.mBitplanes / 8, mImage.getSampleModel().getNumBands());
int planeWidth = (width + 7) / 8;
// NOTE: Each row of the image is stored in an integral number of 16 bit words.
// The number of words per row is words=((w+15)/16)
int planeWidth = 2 * ((width + 15) / 16);
final byte[] planeData = new byte[8 * planeWidth];
WritableRaster destination = mImage.getRaster();
@ -558,16 +562,17 @@ public class IFFImageReader extends ImageReaderBase {
int off = (channels - c - 1);
int pixelPos = 0;
int planePos = 0;
for (int i = 0; i < planeWidth; i++) {
for (int planePos = 0; planePos < planeWidth; planePos++) {
IFFUtil.bitRotateCW(planeData, planePos, planeWidth, data, off + pixelPos * channels, channels);
pixelPos += 8;
planePos++;
}
}
else /*if (mType == IFFImageReader.TYPE_PBM)*/ {
else if (mFormType == IFF.TYPE_PBM) {
System.arraycopy(planeData, 0, data, srcY * 8 * planeWidth, planeWidth);
}
else {
throw new AssertionError(String.format("Unsupported FORM type: %s", mFormType));
}
}
int dstY = (srcY - aoi.y) / sourceYSubsampling;
@ -607,16 +612,25 @@ public class IFFImageReader extends ImageReaderBase {
break;
case BMHDChunk.COMPRESSION_BYTE_RUN:
// TODO: How do we know if the last byte in the body is a pad byte or not?!
// The body consists of byte-run (PackBits) compressed rows of bit plane data.
// However, we don't know how long each compressed row is, without decoding it...
// The workaround below, is to use a decode buffer size of pPlaneWidth,
// to make sure we don't decode anything we don't have to (shouldn't).
if (mByteRunStream == null) {
mByteRunStream = new DataInputStream(new DecoderStream(
mByteRunStream = new DataInputStream(
new DecoderStream(
IIOUtil.createStreamAdapter(pInput, mBody.mChunkLength),
new PackBitsDecoder(true)
));
new PackBitsDecoder(true),
pPlaneWidth * mHeader.mBitplanes
)
);
}
mByteRunStream.readFully(pData, pOffset, pPlaneWidth);
break;
default:
throw new IIOException("Unknown compression type: " + mHeader.mCompressionType);
throw new IIOException(String.format("Unknown compression type: %d", mHeader.mCompressionType));
}
}
@ -666,7 +680,7 @@ public class IFFImageReader extends ImageReaderBase {
}
public static void main(String[] pArgs) throws IOException {
ImageReader reader = new IFFImageReader(new IFFImageReaderSpi());
ImageReader reader = new IFFImageReader();
// ImageInputStream input = ImageIO.createImageInputStream(new File(pArgs[0]));
ImageInputStream input = new BufferedImageInputStream(ImageIO.createImageInputStream(new File(pArgs[0])));
@ -687,7 +701,7 @@ public class IFFImageReader extends ImageReaderBase {
BufferedImage image = reader.read(0, param);
System.out.println("image = " + image);
showIt(image, "");
showIt(image, pArgs[0]);
}
}
}

View File

@ -47,7 +47,7 @@ import java.io.OutputStream;
/**
* Writer for Amiga (Electronic Arts) IFF ILBM (InterLeaved BitMap) format.
* The IFF format (Interchange File Format) is the standard file format
* supported by allmost all image software for the Amiga computer.
* supported by almost all image software for the Amiga computer.
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
@ -58,8 +58,6 @@ import java.io.OutputStream;
*/
public class IFFImageWriter extends ImageWriterBase {
private static final byte[] ANNO_DATA = "Written by TwelveMonkeys IFFImageWriter 1.0 for Java (javax.imageio).".getBytes();
public IFFImageWriter() {
this(null);
}
@ -121,6 +119,7 @@ public class IFFImageWriter extends ImageWriterBase {
private void packImageData(OutputStream pOutput, RenderedImage pImage, ImageWriteParam pParam) throws IOException {
// TODO: Allow param to dictate uncompressed
// TODO: Allow param to dictate type PBM?
// TODO: Subsample/AOI
final boolean compress = shouldCompress(pImage);
final OutputStream output = compress ? new EncoderStream(pOutput, new PackBitsEncoder(), true) : pOutput;
@ -136,12 +135,14 @@ public class IFFImageWriter extends ImageWriterBase {
// 2. Perform byteRun1 compression for each plane separately
// 3. Write the plane data for each plane
final int planeWidth = (width + 7) / 8;
//final int planeWidth = (width + 7) / 8;
final int planeWidth = 2 * ((width + 15) / 16);
final byte[] planeData = new byte[8 * planeWidth];
final int channels = (model.getPixelSize() + 7) / 8;
final int planesPerChannel = channels == 1 ? model.getPixelSize() : 8;
int[] pixels = new int[8 * planeWidth];
// TODO: The spec says "Do not compress across rows!".. I think we currently do.
// NOTE: I'm a little unsure if this is correct for 4 channel (RGBA)
// data, but it is at least consistent with the IFFImageReader for now...
for (int y = 0; y < height; y++) {
@ -174,7 +175,8 @@ public class IFFImageWriter extends ImageWriterBase {
private void writeMeta(RenderedImage pImage, int pBodyLength) throws IOException {
// Annotation ANNO chunk, 8 + annoData.length bytes
GenericChunk anno = new GenericChunk(IFFUtil.toInt("ANNO".getBytes()), ANNO_DATA);
String annotation = "Written by " + getOriginatingProvider().getDescription(null);
GenericChunk anno = new GenericChunk(IFFUtil.toInt("ANNO".getBytes()), annotation.getBytes());
ColorModel cm = pImage.getColorModel();
IndexColorModel icm = null;

View File

@ -69,8 +69,9 @@ public class IFFImageWriterSpi extends ImageWriterSpi {
);
}
public boolean canEncodeImage(ImageTypeSpecifier pType) {
public boolean canEncodeImage(final ImageTypeSpecifier pType) {
// TODO: Probably can't store 16 bit types etc...
// TODO: Can't store CMYK (well.. it does, but they can't be read back)
return true;
}

View File

@ -48,7 +48,7 @@ class IFFUtil {
/**
* Creates a rotation table
* @param n
* @param n number of bits -1
*
* @return the rotation table
*/
@ -243,7 +243,7 @@ class IFFUtil {
/**
* Converts an int to a four letter String.
*
* @param pChunkId
* @param pChunkId the chunk identifier
* @return a String
*/
static String toChunkStr(int pChunkId) {

View File

@ -52,13 +52,13 @@ public class IFFImageReaderTestCase extends ImageReaderAbstractTestCase<IFFImage
new TestData(getClassLoaderResource("/iff/survivor.iff"), new Dimension(800, 600)), // 24 bit
// HAM6 - Ok (a lot of visual "fringe", would be interesting to see on a real HAM display)
new TestData(getClassLoaderResource("/iff/A4000T_HAM6.IFF"), new Dimension(320, 512)), // ham6
// HAM8 - Passes tests, but visuals are trashed. Have other HAM8 files that are ok
// HAM8 - Ok (PackBits decoder chokes on padding byte)
new TestData(getClassLoaderResource("/iff/A4000T_HAM8.IFF"), new Dimension(628, 512)), // ham8
// 8 color indexed - Passes tests, but trashed. Must be something special with these images
// 8 color indexed - Ok
new TestData(getClassLoaderResource("/iff/AmigaBig.iff"), new Dimension(300, 200)), // 8 color
// 8 color indexed - Ok
new TestData(getClassLoaderResource("/iff/AmigaAmiga.iff"), new Dimension(200, 150)), // 8 color
// Breaks completely... Could be bug in the packbits decoder?
// Ok (PackBits decoder chokes on padding byte)
new TestData(getClassLoaderResource("/iff/Abyss.iff"), new Dimension(320, 400))
);
}