diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/AbstractMultiPaletteChunk.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/AbstractMultiPaletteChunk.java index 420c05fb..d12611a2 100644 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/AbstractMultiPaletteChunk.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/AbstractMultiPaletteChunk.java @@ -69,14 +69,14 @@ abstract class AbstractMultiPaletteChunk extends IFFChunk implements MultiPalett protected WeakReference originalPalette; protected MutableIndexColorModel mutablePalette; - public AbstractMultiPaletteChunk(int pChunkId, int pChunkLength) { - super(pChunkId, pChunkLength); + public AbstractMultiPaletteChunk(int chunkId, int chunkLength) { + super(chunkId, chunkLength); } @Override - void readChunk(final DataInput pInput) throws IOException { + void readChunk(final DataInput input) throws IOException { if (chunkId == IFF.CHUNK_SHAM) { - pInput.readUnsignedShort(); // Version, typically 0, skipped + input.readUnsignedShort(); // Version, typically 0, skipped } int rows = chunkLength / 32; /* sizeof(word) * 16 */ @@ -91,7 +91,7 @@ abstract class AbstractMultiPaletteChunk extends IFFChunk implements MultiPalett } for (int i = 0; i < 16; i++ ) { - int data = pInput.readUnsignedShort(); + int data = input.readUnsignedShort(); changes[row][i].index = i; changes[row][i].r = (byte) (((data & 0x0f00) >> 8) * FACTOR_4BIT); @@ -102,7 +102,7 @@ abstract class AbstractMultiPaletteChunk extends IFFChunk implements MultiPalett } @Override - void writeChunk(DataOutput pOutput) { + void writeChunk(DataOutput output) { throw new UnsupportedOperationException("Method writeChunk not implemented"); } diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/BMHDChunk.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/BMHDChunk.java index 3c25013d..aac4fac3 100755 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/BMHDChunk.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/BMHDChunk.java @@ -109,64 +109,65 @@ final class BMHDChunk extends IFFChunk { int pageWidth; int pageHeight; - BMHDChunk(int pChunkLength) { - super(IFF.CHUNK_BMHD, pChunkLength); + BMHDChunk(int chunkLength) { + super(IFF.CHUNK_BMHD, chunkLength); } - BMHDChunk(int pWidth, int pHeight, int pBitplanes, int pMaskType, int pCompressionType, int pTransparentIndex) { + BMHDChunk(int width, int height, int bitplanes, int maskType, int compressionType, int transparentIndex) { super(IFF.CHUNK_BMHD, 20); - width = pWidth; - height = pHeight; + this.width = width; + this.height = height; xPos = 0; yPos = 0; - bitplanes = pBitplanes; - maskType = pMaskType; - compressionType = pCompressionType; - transparentIndex = pTransparentIndex; + this.bitplanes = bitplanes; + this.maskType = maskType; + this.compressionType = compressionType; + this.transparentIndex = transparentIndex; xAspect = 1; yAspect = 1; - pageWidth = Math.min(pWidth, Short.MAX_VALUE); // For some reason, these are signed? - pageHeight = Math.min(pHeight, Short.MAX_VALUE); + pageWidth = Math.min(width, Short.MAX_VALUE); // For some reason, these are signed? + pageHeight = Math.min(height, Short.MAX_VALUE); } @Override - void readChunk(final DataInput pInput) throws IOException { + void readChunk(final DataInput input) throws IOException { if (chunkLength != 20) { throw new IIOException("Unknown BMHD chunk length: " + chunkLength); } - width = pInput.readUnsignedShort(); - height = pInput.readUnsignedShort(); - xPos = pInput.readShort(); - yPos = pInput.readShort(); - bitplanes = pInput.readUnsignedByte(); - maskType = pInput.readUnsignedByte(); - compressionType = pInput.readUnsignedByte(); - pInput.readByte(); // PAD - transparentIndex = pInput.readUnsignedShort(); - xAspect = pInput.readUnsignedByte(); - yAspect = pInput.readUnsignedByte(); - pageWidth = pInput.readShort(); - pageHeight = pInput.readShort(); + + width = input.readUnsignedShort(); + height = input.readUnsignedShort(); + xPos = input.readShort(); + yPos = input.readShort(); + bitplanes = input.readUnsignedByte(); + maskType = input.readUnsignedByte(); + compressionType = input.readUnsignedByte(); + input.readByte(); // PAD + transparentIndex = input.readUnsignedShort(); + xAspect = input.readUnsignedByte(); + yAspect = input.readUnsignedByte(); + pageWidth = input.readShort(); + pageHeight = input.readShort(); } @Override - void writeChunk(final DataOutput pOutput) throws IOException { - pOutput.writeInt(chunkId); - pOutput.writeInt(chunkLength); + void writeChunk(final DataOutput output) throws IOException { + output.writeInt(chunkId); + output.writeInt(chunkLength); - pOutput.writeShort(width); - pOutput.writeShort(height); - pOutput.writeShort(xPos); - pOutput.writeShort(yPos); - pOutput.writeByte(bitplanes); - pOutput.writeByte(maskType); - pOutput.writeByte(compressionType); - pOutput.writeByte(0); // PAD - pOutput.writeShort(transparentIndex); - pOutput.writeByte(xAspect); - pOutput.writeByte(yAspect); - pOutput.writeShort(pageWidth); - pOutput.writeShort(pageHeight); + output.writeShort(width); + output.writeShort(height); + output.writeShort(xPos); + output.writeShort(yPos); + output.writeByte(bitplanes); + output.writeByte(maskType); + output.writeByte(compressionType); + output.writeByte(0); // PAD + output.writeShort(transparentIndex); + output.writeByte(xAspect); + output.writeByte(yAspect); + output.writeShort(pageWidth); + output.writeShort(pageHeight); } @Override diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/BODYChunk.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/BODYChunk.java index 3febed79..00a1240f 100755 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/BODYChunk.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/BODYChunk.java @@ -33,6 +33,8 @@ package com.twelvemonkeys.imageio.plugins.iff; import java.io.DataInput; import java.io.DataOutput; +import static com.twelvemonkeys.lang.Validate.isTrue; + /** * BODYChunk * @@ -40,17 +42,20 @@ import java.io.DataOutput; * @version $Id: BODYChunk.java,v 1.0 28.feb.2006 01:25:49 haku Exp$ */ final class BODYChunk extends IFFChunk { - BODYChunk(int pChunkLength) { - super(IFF.CHUNK_BODY, pChunkLength); + final long chunkOffset; + + BODYChunk(int chunkId, int chunkLength, long chunkOffset) { + super(isTrue(chunkId == IFF.CHUNK_BODY || chunkId == IFF.CHUNK_DBOD, chunkId, "Illegal body chunk: '%s'"), chunkLength); + this.chunkOffset = chunkOffset; } @Override - void readChunk(final DataInput pInput) { + void readChunk(final DataInput input) { throw new InternalError("BODY chunk should only be read from IFFImageReader"); } @Override - void writeChunk(final DataOutput pOutput) { + void writeChunk(final DataOutput output) { throw new InternalError("BODY chunk should only be written from IFFImageWriter"); } } diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/CAMGChunk.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/CAMGChunk.java index 12ce0766..d1758225 100755 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/CAMGChunk.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/CAMGChunk.java @@ -48,21 +48,21 @@ final class CAMGChunk extends IFFChunk { int camg; - CAMGChunk(int pLength) { - super(IFF.CHUNK_CAMG, pLength); + CAMGChunk(int chunkLength) { + super(IFF.CHUNK_CAMG, chunkLength); } @Override - void readChunk(final DataInput pInput) throws IOException { + void readChunk(final DataInput input) throws IOException { if (chunkLength != 4) { throw new IIOException("Unknown CAMG chunk length: " + chunkLength); } - camg = pInput.readInt(); + camg = input.readInt(); } @Override - void writeChunk(final DataOutput pOutput) { + void writeChunk(final DataOutput output) { throw new InternalError("Not implemented: writeChunk()"); } diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/CMAPChunk.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/CMAPChunk.java index b015c71e..bacdc2aa 100755 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/CMAPChunk.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/CMAPChunk.java @@ -31,9 +31,7 @@ package com.twelvemonkeys.imageio.plugins.iff; import javax.imageio.IIOException; -import java.awt.image.BufferedImage; import java.awt.image.IndexColorModel; -import java.awt.image.WritableRaster; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; @@ -69,7 +67,7 @@ final class CMAPChunk extends IFFChunk { } @Override - void readChunk(final DataInput pInput) throws IOException { + void readChunk(final DataInput input) throws IOException { int numColors = chunkLength / 3; reds = new byte[numColors]; @@ -77,9 +75,9 @@ final class CMAPChunk extends IFFChunk { blues = reds.clone(); for (int i = 0; i < numColors; i++) { - reds[i] = pInput.readByte(); - greens[i] = pInput.readByte(); - blues[i] = pInput.readByte(); + reds[i] = input.readByte(); + greens[i] = input.readByte(); + blues[i] = input.readByte(); } // TODO: When reading in a CMAP for 8-bit-per-gun display or @@ -92,25 +90,25 @@ final class CMAPChunk extends IFFChunk { // All chunks are WORD aligned (even sized), may need to read pad... if (chunkLength % 2 != 0) { - pInput.readByte(); + input.readByte(); } } @Override - void writeChunk(final DataOutput pOutput) throws IOException { - pOutput.writeInt(chunkId); - pOutput.writeInt(chunkLength); + void writeChunk(final DataOutput output) throws IOException { + output.writeInt(chunkId); + output.writeInt(chunkLength); final int length = model.getMapSize(); for (int i = 0; i < length; i++) { - pOutput.writeByte(model.getRed(i)); - pOutput.writeByte(model.getGreen(i)); - pOutput.writeByte(model.getBlue(i)); + output.writeByte(model.getRed(i)); + output.writeByte(model.getGreen(i)); + output.writeByte(model.getBlue(i)); } if (chunkLength % 2 != 0) { - pOutput.writeByte(0); // PAD + output.writeByte(0); // PAD } } @@ -119,25 +117,11 @@ final class CMAPChunk extends IFFChunk { return super.toString() + " {colorMap=" + model + "}"; } - BufferedImage createPaletteImage(final BMHDChunk header, boolean isEHB) throws IIOException { - // Create a 1 x colors.length image - IndexColorModel cm = getIndexColorModel(header, isEHB); - WritableRaster raster = cm.createCompatibleWritableRaster(cm.getMapSize(), 1); - byte[] pixel = null; - - for (int x = 0; x < cm.getMapSize(); x++) { - pixel = (byte[]) cm.getDataElements(cm.getRGB(x), pixel); - raster.setDataElements(x, 0, pixel); - } - - return new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null); - } - - public IndexColorModel getIndexColorModel(final BMHDChunk header, boolean isEHB) throws IIOException { + public IndexColorModel getIndexColorModel(final Form.ILBMForm header) throws IIOException { if (model == null) { int numColors = reds.length; // All arrays are same size - if (isEHB) { + if (header.isEHB()) { if (numColors == 32) { reds = Arrays.copyOf(reds, numColors * 2); blues = Arrays.copyOf(blues, numColors * 2); @@ -160,8 +144,9 @@ final class CMAPChunk extends IFFChunk { // Would it work to double to numbers of colors, and create an indexcolormodel, // with alpha, where all colors above the original color is all transparent? // This is a waste of time and space, of course... - int transparent = header.maskType == BMHDChunk.MASK_TRANSPARENT_COLOR ? header.transparentIndex : -1; - int bitplanes = header.bitplanes == 25 ? 8 : header.bitplanes; + int transparent = header.transparentIndex(); + int bitplanes = header.bitplanes() == 25 ? 8 : header.bitplanes(); + model = new IndexColorModel(bitplanes, reds.length, reds, greens, blues, transparent); // https://github.com/haraldk/TwelveMonkeys/issues/15 } diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/CTBLChunk.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/CTBLChunk.java index 74e917d4..e4e35f30 100644 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/CTBLChunk.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/CTBLChunk.java @@ -38,7 +38,7 @@ package com.twelvemonkeys.imageio.plugins.iff; * @version $Id: CTBLChunk.java,v 1.0 30.03.12 14:53 haraldk Exp$ */ final class CTBLChunk extends AbstractMultiPaletteChunk { - CTBLChunk(int pChunkLength) { - super(IFF.CHUNK_CTBL, pChunkLength); + CTBLChunk(int chunkLength) { + super(IFF.CHUNK_CTBL, chunkLength); } } diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/DGBLChunk.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/DGBLChunk.java new file mode 100755 index 00000000..0100e58a --- /dev/null +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/DGBLChunk.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2022, 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 of the copyright holder 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 HOLDER 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.iff; + +import javax.imageio.IIOException; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +/** + * DGBLChunk + * + * @author Harald Kuhr + * @version $Id: DGBLChunk.java,v 1.0 28.feb.2006 02:10:07 haku Exp$ + */ +final class DGBLChunk extends IFFChunk { + + /* + // + struct DGBL = { +// +// Size of source display +// + UWORD DisplayWidth,DisplayHeight; +// +// Type of compression +// + UWORD Compression; +// +// Pixel aspect, a ration w:h +// + UBYTE xAspect,yAspect; + }; + + */ + int displayWidth; + int displayHeight; + int compressionType; + int xAspect; + int yAspect; + + DGBLChunk(int chunkLength) { + super(IFF.CHUNK_DGBL, chunkLength); + } + + @Override + void readChunk(final DataInput input) throws IOException { + if (chunkLength != 8) { + throw new IIOException("Unknown DBGL chunk length: " + chunkLength); + } + + displayWidth = input.readUnsignedShort(); + displayHeight = input.readUnsignedShort(); + compressionType = input.readUnsignedShort(); + xAspect = input.readUnsignedByte(); + yAspect = input.readUnsignedByte(); + } + + @Override + void writeChunk(final DataOutput output) { + throw new InternalError("Not implemented: writeChunk()"); + } + + @Override + public String toString() { + return super.toString() + + "{displayWidth=" + displayWidth + + ", displayHeight=" + displayHeight + + ", compression=" + compressionType + + ", xAspect=" + xAspect + + ", yAspect=" + yAspect + + '}'; + } +} diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/DLOCChunk.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/DLOCChunk.java new file mode 100644 index 00000000..81927b12 --- /dev/null +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/DLOCChunk.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2022, 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 of the copyright holder 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 HOLDER 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.iff; + +import javax.imageio.IIOException; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +/** + * DLOCChunk. + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: DLOCChunk.java,v 1.0 31/01/2022 haraldk Exp$ + */ +final class DLOCChunk extends IFFChunk { + int width; + int height; + int x; + int y; + + DLOCChunk(final int chunkLength) { + super(IFF.CHUNK_DLOC, chunkLength); + } + + @Override + void readChunk(final DataInput input) throws IOException { + if (chunkLength != 8) { + throw new IIOException("Unknown DLOC chunk length: " + chunkLength); + } + + width = input.readUnsignedShort(); + height = input.readUnsignedShort(); + x = input.readShort(); + y = input.readShort(); + } + + @Override + void writeChunk(final DataOutput output) { + throw new InternalError("Not implemented: writeChunk()"); + } + + @Override + public String toString() { + return super.toString() + + "{width=" + width + + ", height=" + height + + ", x=" + x + + ", y=" + y + '}'; + } +} diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/DPELChunk.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/DPELChunk.java new file mode 100644 index 00000000..b19f1a42 --- /dev/null +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/DPELChunk.java @@ -0,0 +1,103 @@ +package com.twelvemonkeys.imageio.plugins.iff; + +import javax.imageio.IIOException; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.Arrays; + +/** + * DPELChunk. + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: DPELChunk.java,v 1.0 01/02/2022 haraldk Exp$ + */ +final class DPELChunk extends IFFChunk { + /* +// +// Chunk DPEL +// ---------- + struct DPEL = { +// +// Number of pixel components +// +ULONG nElements; + // +// The TypeDepth structure is repeated nElement times to identify +// the content of every pixel. Pixels will always be padded to +// byte boundaries. The DBOD chunk will be padded to an even +// longword boundary. +// + struct TypeDepth = { +// +// Type of data +// +UWORD cType; + // +// Bit depth of this type +// + UWORD cBitDepth; +} typedepth[Nelements]; + }; + */ + TypeDepth[] typeDepths; + + DPELChunk(final int chunkLength) { + super(IFF.CHUNK_DPEL, chunkLength); + } + + @Override + void readChunk(final DataInput input) throws IOException { + int components = input.readInt(); // Strictly, it's unsigned, but that many components is unlikely... + + if (chunkLength != 4 + components * 4) { + throw new IIOException("Unsupported DPEL chunk length: " + chunkLength); + } + + typeDepths = new TypeDepth[components]; + + for (int i = 0; i < components; i++) { + typeDepths[i] = new TypeDepth(input.readUnsignedShort(), input.readUnsignedShort()); + } + } + + @Override + void writeChunk(final DataOutput output) { + throw new InternalError("Not implemented: writeChunk()"); + } + + @Override + public String toString() { + return super.toString() + + "{typeDepths=" + Arrays.toString(typeDepths) + '}'; + } + + public int bitsPerPixel() { + int bitCount = 0; + + for (TypeDepth typeDepth : typeDepths) { + bitCount += typeDepth.bitDepth; + } + + return bitCount; + } + + static class TypeDepth { + final int type; + final int bitDepth; + + TypeDepth(final int type, final int bitDepth) { + this.type = type; + this.bitDepth = bitDepth; + } + + @Override + public String toString() { + return "TypeDepth{" + + "type=" + type + + ", bits=" + bitDepth + + '}'; + } + } +} diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/Form.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/Form.java new file mode 100644 index 00000000..781ea308 --- /dev/null +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/Form.java @@ -0,0 +1,364 @@ +package com.twelvemonkeys.imageio.plugins.iff; + +import javax.imageio.IIOException; +import java.awt.image.ColorModel; +import java.awt.image.IndexColorModel; +import java.util.ArrayList; +import java.util.List; + +import static com.twelvemonkeys.imageio.plugins.iff.IFFUtil.toChunkStr; + +/** + * Form. + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: Form.java,v 1.0 31/01/2022 haraldk Exp$ + */ +abstract class Form { + + final int formType; + final List meta = new ArrayList<>(); + + Form(int formType) { + this.formType = formType; + } + + abstract int width(); + abstract int height(); + abstract int xAspect(); + abstract int yAspect(); + abstract int bitplanes(); + abstract int compressionType(); + + boolean isMultiPalette() { + return false; + } + + boolean isHAM() { + return false; + } + + public boolean premultiplied() { + return false; + } + + public int sampleSize() { + return 1; + } + + public int transparentIndex() { + return -1; + } + + public IndexColorModel colorMap() throws IIOException { + return null; + } + + public ColorModel colorMapForRow(IndexColorModel colorModel, int row) { + throw new UnsupportedOperationException(); + } + + abstract long bodyOffset(); + abstract long bodyLength(); + + @Override + public String toString() { + return toChunkStr(formType); + } + + Form with(final IFFChunk chunk) throws IIOException { + if (chunk instanceof GenericChunk) { + // TODO: This feels kind of hackish, as it breaks the immutable design, perhaps we should just reconsider... + meta.add((GenericChunk) chunk); + + return this; + } + + throw new IllegalArgumentException(chunk + " not supported in FORM type " + toChunkStr(formType)); + } + + static Form ofType(int formType) { + switch (formType) { + case IFF.TYPE_ACBM: + case IFF.TYPE_ILBM: + case IFF.TYPE_PBM: + case IFF.TYPE_RGB8: + return new ILBMForm(formType); + case IFF.TYPE_DEEP: + case IFF.TYPE_TVPP: + return new DEEPForm(formType); + default: + throw new IllegalArgumentException("FORM type " + toChunkStr(formType) + " not supported"); + } + } + + /** + * The set of chunks used in the "original" ILBM, + * and also ACBM, PBM and RGB8 FORMs. + */ + static final class ILBMForm extends Form { + private final BMHDChunk bitmapHeader; + private final CAMGChunk viewMode; + private final CMAPChunk colorMap; + private final AbstractMultiPaletteChunk multiPalette; + private final BODYChunk body; + + ILBMForm(int formType) { + this(formType, null, null, null, null, null); + } + + private ILBMForm(final int formType, final BMHDChunk bitmapHeader, final CAMGChunk viewMode, final CMAPChunk colorMap, final AbstractMultiPaletteChunk multiPalette, final BODYChunk body) { + super(formType); + this.bitmapHeader = bitmapHeader; + this.viewMode = viewMode; + this.colorMap = colorMap; + this.multiPalette = multiPalette; + this.body = body; + } + + @Override + int width() { + return bitmapHeader.width; + } + + @Override + int height() { + return bitmapHeader.height; + } + + @Override + int bitplanes() { + return bitmapHeader.bitplanes; + } + + @Override + int compressionType() { + return bitmapHeader.compressionType; + } + + @Override + int xAspect() { + return bitmapHeader.xAspect; + } + + @Override + int yAspect() { + return bitmapHeader.yAspect; + } + + @Override + boolean isMultiPalette() { + return multiPalette != null; + } + + boolean isEHB() { + return viewMode != null && viewMode.isEHB(); + } + + @Override + boolean isHAM() { + return viewMode != null && viewMode.isHAM(); + } + + boolean isLaced() { + return viewMode != null && viewMode.isLaced(); + } + + @Override + public int transparentIndex() { + return bitmapHeader.maskType == BMHDChunk.MASK_TRANSPARENT_COLOR ? bitmapHeader.transparentIndex : -1; + } + + @Override + public IndexColorModel colorMap() throws IIOException { + return colorMap != null ? colorMap.getIndexColorModel(this) : null; + } + + @Override + public ColorModel colorMapForRow(final IndexColorModel colorModel, final int row) { + return multiPalette != null ? multiPalette.getColorModel(colorModel, row, isLaced()) : null; + } + + @Override + long bodyOffset() { + return body.chunkOffset; + } + + @Override + long bodyLength() { + return body.chunkLength; + } + + @Override + ILBMForm with(final IFFChunk chunk) throws IIOException { + if (chunk instanceof BMHDChunk) { + if (bitmapHeader != null) { + throw new IIOException("Multiple BMHD chunks not allowed"); + } + + return new ILBMForm(formType, (BMHDChunk) chunk, null, colorMap, multiPalette, body); + } + else if (chunk instanceof CAMGChunk) { + if (viewMode != null) { + throw new IIOException("Multiple CAMG chunks not allowed"); + } + + return new ILBMForm(formType, bitmapHeader, (CAMGChunk) chunk, colorMap, multiPalette, body); + } + else if (chunk instanceof CMAPChunk) { + if (colorMap != null) { + throw new IIOException("Multiple CMAP chunks not allowed"); + } + + return new ILBMForm(formType, bitmapHeader, viewMode, (CMAPChunk) chunk, multiPalette, body); + } + else if (chunk instanceof AbstractMultiPaletteChunk) { + // NOTE: We prefer PHCG over SHAM/CTBL style palette changes, if both are present + if (multiPalette instanceof PCHGChunk) { + if (chunk instanceof PCHGChunk) { + throw new IIOException("Multiple PCHG/SHAM/CTBL chunks not allowed"); + } + + return this; + } + + return new ILBMForm(formType, bitmapHeader, viewMode, colorMap, (AbstractMultiPaletteChunk) chunk, body); + } + else if (chunk instanceof BODYChunk) { + if (body != null) { + throw new IIOException("Multiple " + toChunkStr(chunk.chunkId) + " chunks not allowed"); + } + + return new ILBMForm(formType, bitmapHeader, viewMode, colorMap, multiPalette, (BODYChunk) chunk); + } + else if (chunk instanceof GRABChunk) { + // Ignored for now + return this; + } + + return (ILBMForm) super.with(chunk); + } + + @Override + public String toString() { + return super.toString() + '{' + bitmapHeader + + (viewMode != null ? ", " + viewMode : "" ) + + (colorMap != null ? ", " + colorMap : "" ) + + (multiPalette != null ? ", " + multiPalette : "" ) + + '}'; + } + } + + /** + * The set of chunks used in DEEP and TVPP FORMs. + */ + private static final class DEEPForm extends Form { + private final DGBLChunk deepGlobal; + private final DLOCChunk deepLocation; + private final DPELChunk deepPixel; + private final BODYChunk body; + + DEEPForm(int formType) { + this(formType, null, null, null, null); + } + + private DEEPForm(final int formType, final DGBLChunk deepGlobal, final DLOCChunk deepLocation, final DPELChunk deepPixel, final BODYChunk body) { + super(formType); + this.deepGlobal = deepGlobal; + this.deepLocation = deepLocation; + this.deepPixel = deepPixel; + this.body = body; + } + + + @Override + int width() { + return deepLocation.width; + } + + @Override + int height() { + return deepLocation.height; + } + + @Override + int bitplanes() { + return deepPixel.bitsPerPixel(); + } + + @Override + public int sampleSize() { + return bitplanes() / 8; + } + + @Override + public boolean premultiplied() { + return true; + } + + @Override + int compressionType() { + return deepGlobal.compressionType; + } + + @Override + int xAspect() { + return deepGlobal.xAspect; + } + + @Override + int yAspect() { + return deepGlobal.yAspect; + } + + @Override + long bodyOffset() { + return body.chunkOffset; + } + + @Override + long bodyLength() { + return body.chunkLength; + } + + @Override + DEEPForm with(final IFFChunk chunk) throws IIOException { + if (chunk instanceof DGBLChunk) { + if (deepGlobal != null) { + throw new IIOException("Multiple DGBL chunks not allowed"); + } + + return new DEEPForm(formType, (DGBLChunk) chunk, null, null, body); + } + else if (chunk instanceof DLOCChunk) { + if (deepLocation != null) { + throw new IIOException("Multiple DLOC chunks not allowed"); + } + + return new DEEPForm(formType, deepGlobal, (DLOCChunk) chunk, deepPixel, body); + } + else if (chunk instanceof DPELChunk) { + if (deepPixel != null) { + throw new IIOException("Multiple DPEL chunks not allowed"); + } + + return new DEEPForm(formType, deepGlobal, deepLocation, (DPELChunk) chunk, body); + } + else if (chunk instanceof BODYChunk) { + if (body != null) { + throw new IIOException("Multiple " + toChunkStr(chunk.chunkId) + " chunks not allowed"); + } + + return new DEEPForm(formType, deepGlobal, deepLocation, deepPixel, (BODYChunk) chunk); + } + + return (DEEPForm) super.with(chunk); + } + + @Override + public String toString() { + return super.toString() + '{' + deepGlobal + ", " + deepLocation + ", " + deepPixel + '}'; + } + } +} diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/GRABChunk.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/GRABChunk.java index dab83a5d..20c4d7c6 100755 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/GRABChunk.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/GRABChunk.java @@ -50,25 +50,25 @@ final class GRABChunk extends IFFChunk { Point2D point; - GRABChunk(int pChunkLength) { - super(IFF.CHUNK_GRAB, pChunkLength); + GRABChunk(int chunkLength) { + super(IFF.CHUNK_GRAB, chunkLength); } - GRABChunk(Point2D pPoint) { + GRABChunk(Point2D point) { super(IFF.CHUNK_GRAB, 4); - point = pPoint; + this.point = point; } - void readChunk(DataInput pInput) throws IOException { + void readChunk(DataInput input) throws IOException { if (chunkLength != 4) { throw new IIOException("Unknown GRAB chunk size: " + chunkLength); } - point = new Point(pInput.readShort(), pInput.readShort()); + point = new Point(input.readShort(), input.readShort()); } - void writeChunk(DataOutput pOutput) throws IOException { - pOutput.writeShort((int) point.getX()); - pOutput.writeShort((int) point.getY()); + void writeChunk(DataOutput output) throws IOException { + output.writeShort((int) point.getX()); + output.writeShort((int) point.getY()); } public String toString() { diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/GenericChunk.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/GenericChunk.java index 5915d9bb..55da8401 100755 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/GenericChunk.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/GenericChunk.java @@ -44,31 +44,31 @@ final class GenericChunk extends IFFChunk { byte[] data; - GenericChunk(int pChunkId, int pChunkLength) { - super(pChunkId, pChunkLength); - data = new byte[chunkLength]; + GenericChunk(int chunkId, int chunkLength) { + super(chunkId, chunkLength); + data = new byte[this.chunkLength]; } - GenericChunk(int pChunkId, byte[] pChunkData) { - super(pChunkId, pChunkData.length); - data = pChunkData; + GenericChunk(int chunkId, byte[] chunkData) { + super(chunkId, chunkData.length); + data = chunkData; } @Override - void readChunk(final DataInput pInput) throws IOException { - pInput.readFully(data, 0, data.length); + void readChunk(final DataInput input) throws IOException { + input.readFully(data, 0, data.length); - skipData(pInput, chunkLength, data.length); + skipData(input, chunkLength, data.length); } @Override - void writeChunk(final DataOutput pOutput) throws IOException { - pOutput.writeInt(chunkId); - pOutput.writeInt(chunkLength); - pOutput.write(data, 0, data.length); + void writeChunk(final DataOutput output) throws IOException { + output.writeInt(chunkId); + output.writeInt(chunkLength); + output.write(data, 0, data.length); if (data.length % 2 != 0) { - pOutput.writeByte(0); // PAD + output.writeByte(0); // PAD } } diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFF.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFF.java index d5d194d8..2276bb19 100755 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFF.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFF.java @@ -49,6 +49,8 @@ interface IFF { // TODO: /** IFF DEEP form type (TVPaint) */ int TYPE_DEEP = ('D' << 24) + ('E' << 16) + ('E' << 8) + 'P'; + /** IFF TVPP form type (TVPaint Project) */ + int TYPE_TVPP = ('T' << 24) + ('V' << 16) + ('P' << 8) + 'P'; /** IFF RGB8 form type (TurboSilver) */ int TYPE_RGB8 = ('R' << 24) + ('G' << 16) + ('B' << 8) + '8'; /** IFF RGBN form type (TurboSilver) */ @@ -92,7 +94,7 @@ interface IFF { int CHUNK_COPY = ('(' << 24) + ('c' << 16) + (')' << 8) + ' '; /** EA IFF 85 Generic annotation chunk (usually used for Software) */ - int CHUNK_ANNO = ('A' << 24) + ('N' << 16) + ('N' << 8) + 'O';; + int CHUNK_ANNO = ('A' << 24) + ('N' << 16) + ('N' << 8) + 'O'; /** Third-party defined UTF-8 text. */ int CHUNK_UTF8 = ('U' << 24) + ('T' << 16) + ('F' << 8) + '8'; @@ -129,6 +131,16 @@ interface IFF { int CHUNK_SHAM = ('S' << 24) + ('H' << 16) + ('A' << 8) + 'M'; /** ACBM body chunk */ int CHUNK_ABIT = ('A' << 24) + ('B' << 16) + ('I' << 8) + 'T'; - /** unofficial direct color */ + /** Unofficial direct color */ int CHUNK_DCOL = ('D' << 24) + ('C' << 16) + ('O' << 8) + 'L'; + /** TVPaint Deep GloBaL information */ + int CHUNK_DGBL = ('D' << 24) + ('G' << 16) + ('B' << 8) + 'L'; + /** TVPaint Deep Pixel ELements */ + int CHUNK_DPEL = ('D' << 24) + ('P' << 16) + ('E' << 8) + 'L'; + /** TVPaint Deep LOCation information */ + int CHUNK_DLOC = ('D' << 24) + ('L' << 16) + ('O' << 8) + 'C'; + /** TVPaint Deep BODy */ + int CHUNK_DBOD = ('D' << 24) + ('B' << 16) + ('O' << 8) + 'D'; + /** TVPaint Deep CHanGe buffer */ + int CHUNK_DCHG = ('D' << 24) + ('C' << 16) + ('H' << 8) + 'G'; } diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFChunk.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFChunk.java index 5f27a1de..3096ab85 100755 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFChunk.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFChunk.java @@ -44,25 +44,25 @@ abstract class IFFChunk { int chunkId; int chunkLength; - protected IFFChunk(int pChunkId, int pChunkLength) { - chunkId = pChunkId; - chunkLength = pChunkLength; + protected IFFChunk(int chunkId, int chunkLength) { + this.chunkId = chunkId; + this.chunkLength = chunkLength; } - abstract void readChunk(DataInput pInput) throws IOException; + abstract void readChunk(DataInput input) throws IOException; - abstract void writeChunk(DataOutput pOutput) throws IOException; + abstract void writeChunk(DataOutput output) throws IOException; - protected static void skipData(final DataInput pInput, final int chunkLength, final int dataReadSoFar) throws IOException { + protected static void skipData(final DataInput input, final int chunkLength, final int dataReadSoFar) throws IOException { int toSkip = chunkLength - dataReadSoFar; while (toSkip > 0) { - toSkip -= pInput.skipBytes(toSkip); + toSkip -= input.skipBytes(toSkip); } // Read pad if (chunkLength % 2 != 0) { - pInput.readByte(); + input.readByte(); } } diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageMetadata.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageMetadata.java index 9634084a..90af52bf 100644 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageMetadata.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageMetadata.java @@ -13,31 +13,29 @@ import static com.twelvemonkeys.lang.Validate.isTrue; import static com.twelvemonkeys.lang.Validate.notNull; final class IFFImageMetadata extends AbstractMetadata { - private final int formType; - private final BMHDChunk header; + private final Form header; private final IndexColorModel colorMap; - private final CAMGChunk viewPort; private final List meta; - IFFImageMetadata(int formType, BMHDChunk header, IndexColorModel colorMap, CAMGChunk viewPort, List meta) { - this.formType = isTrue(validFormType(formType), formType, "Unknown IFF Form type: %s"); + IFFImageMetadata(Form header, IndexColorModel colorMap) { this.header = notNull(header, "header"); + isTrue(validFormType(header.formType), header.formType, "Unknown IFF Form type: %s"); this.colorMap = colorMap; - this.viewPort = viewPort; - this.meta = meta; + this.meta = header.meta; } private boolean validFormType(int formType) { switch (formType) { + default: + return false; case TYPE_ACBM: case TYPE_DEEP: case TYPE_ILBM: case TYPE_PBM: case TYPE_RGB8: case TYPE_RGBN: + case TYPE_TVPP: return true; - default: - return false; } } @@ -48,7 +46,7 @@ final class IFFImageMetadata extends AbstractMetadata { IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType"); chroma.appendChild(csType); - switch (header.bitplanes) { + switch (header.bitplanes()) { case 8: if (colorMap == null) { csType.setAttribute("name", "GRAY"); @@ -73,10 +71,10 @@ final class IFFImageMetadata extends AbstractMetadata { // NOTE: Channels in chroma node reflects channels in color model (see data node, for channels in data) IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels"); chroma.appendChild(numChannels); - if (colorMap == null && header.bitplanes == 8) { + if (colorMap == null && header.bitplanes() == 8) { numChannels.setAttribute("value", Integer.toString(1)); } - else if (header.bitplanes == 32) { + else if (header.bitplanes() == 25 || header.bitplanes() == 32) { numChannels.setAttribute("value", Integer.toString(4)); } else { @@ -103,9 +101,16 @@ final class IFFImageMetadata extends AbstractMetadata { paletteEntry.setAttribute("green", Integer.toString(colorMap.getGreen(i))); paletteEntry.setAttribute("blue", Integer.toString(colorMap.getBlue(i))); } + + if (colorMap.getTransparentPixel() != -1) { + IIOMetadataNode backgroundIndex = new IIOMetadataNode("BackgroundIndex"); + chroma.appendChild(backgroundIndex); + backgroundIndex.setAttribute("value", Integer.toString(colorMap.getTransparentPixel())); + } } - // TODO: Background color is the color of the transparent index in the color model? + // TODO: TVPP TVPaint Project files have a MIXR chunk with a background color + // and also a BGP1 (background pen 1?) and BGP2 chunks // if (extensions != null && extensions.getBackgroundColor() != 0) { // Color background = new Color(extensions.getBackgroundColor(), true); // @@ -122,7 +127,7 @@ final class IFFImageMetadata extends AbstractMetadata { @Override protected IIOMetadataNode getStandardCompressionNode() { - if (header.compressionType == BMHDChunk.COMPRESSION_NONE) { + if (header.compressionType() == BMHDChunk.COMPRESSION_NONE) { return null; // All defaults } @@ -145,7 +150,9 @@ final class IFFImageMetadata extends AbstractMetadata { // PlanarConfiguration IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration"); - switch (formType) { + switch (header.formType) { + case TYPE_DEEP: + case TYPE_TVPP: case TYPE_RGB8: case TYPE_PBM: planarConfiguration.setAttribute("value", "PixelInterleaved"); @@ -154,7 +161,7 @@ final class IFFImageMetadata extends AbstractMetadata { planarConfiguration.setAttribute("value", "PlaneInterleaved"); break; default: - planarConfiguration.setAttribute("value", "Unknown " + IFFUtil.toChunkStr(formType)); + planarConfiguration.setAttribute("value", "Unknown " + IFFUtil.toChunkStr(header.formType)); break; } data.appendChild(planarConfiguration); @@ -165,7 +172,7 @@ final class IFFImageMetadata extends AbstractMetadata { // BitsPerSample IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample"); - String value = bitsPerSampleValue(header.bitplanes); + String value = bitsPerSampleValue(header.bitplanes()); bitsPerSample.setAttribute("value", value); data.appendChild(bitsPerSample); @@ -173,7 +180,6 @@ final class IFFImageMetadata extends AbstractMetadata { // SampleMSB not in format return data; - } private String bitsPerSampleValue(int bitplanes) { @@ -190,8 +196,8 @@ final class IFFImageMetadata extends AbstractMetadata { case 24: return "8 8 8"; case 25: - if (formType != TYPE_RGB8) { - throw new IllegalArgumentException(String.format("25 bit depth only supported for FORM type RGB8: %s", IFFUtil.toChunkStr(formType))); + if (header.formType != TYPE_RGB8) { + throw new IllegalArgumentException(String.format("25 bit depth only supported for FORM type RGB8: %s", IFFUtil.toChunkStr(header.formType))); } return "8 8 8 1"; @@ -204,7 +210,7 @@ final class IFFImageMetadata extends AbstractMetadata { @Override protected IIOMetadataNode getStandardDimensionNode() { - if (viewPort == null) { + if (header.xAspect() == 0 || header.yAspect() == 0) { return null; } @@ -212,7 +218,7 @@ final class IFFImageMetadata extends AbstractMetadata { // PixelAspectRatio IIOMetadataNode pixelAspectRatio = new IIOMetadataNode("PixelAspectRatio"); - pixelAspectRatio.setAttribute("value", String.valueOf((viewPort.isHires() ? 2f : 1f) / (viewPort.isLaced() ? 2f : 1f))); + pixelAspectRatio.setAttribute("value", String.valueOf(header.xAspect() / (float) header.yAspect())); dimension.appendChild(pixelAspectRatio); // TODO: HorizontalScreenSize? @@ -254,16 +260,15 @@ final class IFFImageMetadata extends AbstractMetadata { @Override protected IIOMetadataNode getStandardTransparencyNode() { - // TODO: Make sure 25 bit is only RGB8... - if ((colorMap == null || !colorMap.hasAlpha()) && header.bitplanes != 32 && header.bitplanes != 25) { + if ((colorMap == null || !colorMap.hasAlpha()) && header.bitplanes() != 32 && header.bitplanes() != 25) { return null; } IIOMetadataNode transparency = new IIOMetadataNode("Transparency"); - if (header.bitplanes == 25 || header.bitplanes == 32) { + if (header.bitplanes() == 25 || header.bitplanes() == 32) { IIOMetadataNode alpha = new IIOMetadataNode("Alpha"); - alpha.setAttribute("value", "nonpremultiplied"); + alpha.setAttribute("value", header.premultiplied() ? "premultiplied" : "nonpremultiplied"); transparency.appendChild(alpha); } diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReader.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReader.java index 6c752711..d6406e28 100755 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReader.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReader.java @@ -48,14 +48,15 @@ import java.awt.image.*; import java.io.DataInputStream; import java.io.File; import java.io.IOException; -import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; +import static com.twelvemonkeys.imageio.plugins.iff.IFFUtil.toChunkStr; + /** * Reader for Commodore Amiga (Electronic Arts) IFF ILBM (InterLeaved BitMap) and PBM - * format (Packed BitMap). + * format (Packed BitMap). Also supports IFF RGB8 (Impulse) and IFF DEEP (TVPaint). * The IFF format (Interchange File Format) is the standard file format * supported by allmost all image software for the Amiga computer. *

@@ -104,20 +105,12 @@ public final class IFFImageReader extends ImageReaderBase { // - Contains definitions of some "new" chunks, as well as alternative FORM types // http://amigan.1emu.net/index/iff.html + // TODO: XS24 chunk seems to be a raw 24 bit thumbnail for TVPaint images: XS24 <4 byte len> <2 byte width> <2 byte height> // TODO: Allow reading rasters for HAM6/HAM8 and multipalette images that are expanded to RGB (24 bit) during read. - private BMHDChunk header; - private CMAPChunk colorMap; - private BODYChunk body; - @SuppressWarnings({"FieldCanBeLocal"}) - private GRABChunk grab; - private CAMGChunk viewPort; - private MultiPalette paletteChange; - private final List meta = new ArrayList<>(); - private int formType; - private long bodyStart; + final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.iff.debug")); - private BufferedImage image; + private Form header; private DataInputStream byteRunStream; IFFImageReader(ImageReaderSpi pProvider) { @@ -135,35 +128,30 @@ public final class IFFImageReader extends ImageReaderBase { @Override protected void resetMembers() { header = null; - colorMap = null; - paletteChange = null; - body = null; - viewPort = null; - formType = 0; - meta.clear(); - - image = null; byteRunStream = null; } private void readMeta() throws IOException { int chunkType = imageInput.readInt(); if (chunkType != IFF.CHUNK_FORM) { - throw new IIOException(String.format("Unknown file format for IFFImageReader, expected 'FORM': %s", IFFUtil.toChunkStr(chunkType))); + throw new IIOException(String.format("Unknown file format for IFFImageReader, expected 'FORM': %s", toChunkStr(chunkType))); } int remaining = imageInput.readInt() - 4; // We'll read 4 more in a sec - formType = imageInput.readInt(); - if (formType != IFF.TYPE_ILBM && formType != IFF.TYPE_PBM && formType != IFF.TYPE_RGB8 && formType != IFF.TYPE_DEEP) { - throw new IIOException(String.format("Only IFF FORM types 'ILBM' and 'PBM ' supported: %s", IFFUtil.toChunkStr(formType))); + int formType = imageInput.readInt(); + if (formType != IFF.TYPE_ILBM && formType != IFF.TYPE_PBM && formType != IFF.TYPE_RGB8 && formType != IFF.TYPE_DEEP && formType != IFF.TYPE_TVPP) { + throw new IIOException(String.format("Only IFF FORM types 'ILBM' and 'PBM ' supported: %s", toChunkStr(formType))); } - //System.out.println("IFF type FORM " + toChunkStr(type)); + if (DEBUG) { + System.out.println("IFF type FORM '" + toChunkStr(formType) + "', len: " + (remaining + 4)); + System.out.println("Reading Chunks..."); + } - grab = null; - viewPort = null; + header = Form.ofType(formType); + // TODO: Delegate the FORM reading to the Form class or a FormReader class? while (remaining > 0) { int chunkId = imageInput.readInt(); int length = imageInput.readInt(); @@ -171,104 +159,82 @@ public final class IFFImageReader extends ImageReaderBase { remaining -= 8; remaining -= length % 2 == 0 ? length : length + 1; - //System.out.println("Next chunk: " + toChunkStr(chunkId) + " length: " + length); - //System.out.println("Remaining bytes after chunk: " + remaining); + if (DEBUG) { + System.out.println("Next chunk: " + toChunkStr(chunkId) + " @ pos: " + (imageInput.getStreamPosition() - 8) + ", len: " + length); + System.out.println("Remaining bytes after chunk: " + remaining); + } switch (chunkId) { case IFF.CHUNK_BMHD: - if (header != null) { - throw new IIOException("Multiple BMHD chunks not allowed"); - } - - header = new BMHDChunk(length); - header.readChunk(imageInput); - - //System.out.println(header); + BMHDChunk bitmapHeader = new BMHDChunk(length); + bitmapHeader.readChunk(imageInput); + header = header.with(bitmapHeader); break; + + case IFF.CHUNK_DGBL: + DGBLChunk deepGlobal = new DGBLChunk(length); + deepGlobal.readChunk(imageInput); + header = header.with(deepGlobal); + break; + + case IFF.CHUNK_DLOC: + DLOCChunk deepLocation = new DLOCChunk(length); + deepLocation.readChunk(imageInput); + header = header.with(deepLocation); + break; + + case IFF.CHUNK_DPEL: + DPELChunk deepPixel = new DPELChunk(length); + deepPixel.readChunk(imageInput); + header = header.with(deepPixel); + break; + case IFF.CHUNK_CMAP: - if (colorMap != null) { - throw new IIOException("Multiple CMAP chunks not allowed"); - } - - colorMap = new CMAPChunk(length); + CMAPChunk colorMap = new CMAPChunk(length); colorMap.readChunk(imageInput); - - //System.out.println(colorMap); + header = header.with(colorMap); break; + case IFF.CHUNK_GRAB: - if (grab != null) { - throw new IIOException("Multiple GRAB chunks not allowed"); - } - grab = new GRABChunk(length); + GRABChunk grab = new GRABChunk(length); grab.readChunk(imageInput); - - //System.out.println(grab); + header = header.with(grab); break; + case IFF.CHUNK_CAMG: - if (viewPort != null) { - throw new IIOException("Multiple CAMG chunks not allowed"); - } - viewPort = new CAMGChunk(length); - viewPort.readChunk(imageInput); - -// System.out.println(viewPort); + CAMGChunk viewMode = new CAMGChunk(length); + viewMode.readChunk(imageInput); + header = header.with(viewMode); break; - case IFF.CHUNK_PCHG: - if (paletteChange instanceof PCHGChunk) { - throw new IIOException("Multiple PCHG chunks not allowed"); - } + case IFF.CHUNK_PCHG: PCHGChunk pchg = new PCHGChunk(length); pchg.readChunk(imageInput); - - // Always prefer PCHG style palette changes - paletteChange = pchg; - -// System.out.println(pchg); + header = header.with(pchg); break; case IFF.CHUNK_SHAM: - if (paletteChange instanceof SHAMChunk) { - throw new IIOException("Multiple SHAM chunks not allowed"); - } - SHAMChunk sham = new SHAMChunk(length); sham.readChunk(imageInput); - - // NOTE: We prefer PHCG to SHAM style palette changes, if both are present - if (paletteChange == null) { - paletteChange = sham; - } - -// System.out.println(sham); + header = header.with(sham); break; case IFF.CHUNK_CTBL: - if (paletteChange instanceof CTBLChunk) { - throw new IIOException("Multiple CTBL chunks not allowed"); - } - CTBLChunk ctbl = new CTBLChunk(length); ctbl.readChunk(imageInput); - - // NOTE: We prefer PHCG to CTBL style palette changes, if both are present - if (paletteChange == null) { - paletteChange = ctbl; - } - -// System.out.println(ctbl); + header = header.with(ctbl); break; case IFF.CHUNK_BODY: - if (body != null) { - throw new IIOException("Multiple BODY chunks not allowed"); - } - - body = new BODYChunk(length); - bodyStart = imageInput.getStreamPosition(); - + case IFF.CHUNK_DBOD: + BODYChunk body = new BODYChunk(chunkId, length, imageInput.getStreamPosition()); // NOTE: We don't read the body here, it's done later in the read(int, ImageReadParam) method + header = header.with(body); + // Done reading meta + if (DEBUG) { + System.out.println("header = " + header); + } return; case IFF.CHUNK_ANNO: @@ -279,45 +245,32 @@ public final class IFFImageReader extends ImageReaderBase { case IFF.CHUNK_UTF8: GenericChunk generic = new GenericChunk(chunkId, length); generic.readChunk(imageInput); - meta.add(generic); - -// System.out.println(generic); + header = header.with(generic); break; case IFF.CHUNK_JUNK: // Always skip junk chunks default: - // TODO: SHAM, DEST, SPRT and more + // TODO: DEST, SPRT and more // Everything else, we'll just skip IFFChunk.skipData(imageInput, length, 0); break; } } + + if (DEBUG) { + System.out.println("header = " + header); + System.out.println("No BODY chunk found..."); + } } @Override - public BufferedImage read(int pIndex, ImageReadParam pParam) throws IOException { - init(pIndex); + public BufferedImage read(int imageIndex, ImageReadParam param) throws IOException { + init(imageIndex); + processImageStarted(imageIndex); - processImageStarted(pIndex); - - image = getDestination(pParam, getImageTypes(pIndex), header.width, header.height); - //System.out.println(body); - if (body != null) { - //System.out.println("Read body"); - 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(colorMap); - if (colorMap != null) { - //System.out.println("Creating palette!"); - image = colorMap.createPaletteImage(header, isEHB()); - } - } - - BufferedImage result = image; + BufferedImage result = getDestination(param, getImageTypes(imageIndex), getWidth(imageIndex), getHeight(imageIndex)); + readBody(param, result); processImageComplete(); @@ -325,77 +278,81 @@ public final class IFFImageReader extends ImageReaderBase { } @Override - public int getWidth(int pIndex) throws IOException { - init(pIndex); - return header.width; + public int getWidth(int imageIndex) throws IOException { + init(imageIndex); + return header.width(); } @Override - public int getHeight(int pIndex) throws IOException { - init(pIndex); - return header.height; + public int getHeight(int imageIndex) throws IOException { + init(imageIndex); + return header.height(); } @Override public IIOMetadata getImageMetadata(int imageIndex) throws IOException { init(imageIndex); - return new IFFImageMetadata(formType, header, colorMap != null ? colorMap.getIndexColorModel(header, isEHB()) : null, viewPort, meta); + return new IFFImageMetadata(header, header.colorMap()); } @Override - public Iterator getImageTypes(int pIndex) throws IOException { - init(pIndex); + public Iterator getImageTypes(int imageIndex) throws IOException { + init(imageIndex); - List types = Arrays.asList( - getRawImageType(pIndex), - ImageTypeSpecifiers.createFromBufferedImageType(header.bitplanes == 32 ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_3BYTE_BGR) - // TODO: ImageTypeSpecifier.createFromBufferedImageType(header.bitplanes == 32 ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB), - // TODO: Allow 32 bit always. Allow RGB and discard alpha, if present? - ); + int bitplanes = header.bitplanes(); + List types = + header.formType == IFF.TYPE_DEEP || header.formType == IFF.TYPE_TVPP // TODO: Make a header attribute here + ? Arrays.asList( + ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR_PRE), + getRawImageType(imageIndex) + ) + : Arrays.asList( + getRawImageType(imageIndex), + ImageTypeSpecifiers.createFromBufferedImageType(bitplanes == 32 ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_3BYTE_BGR) + ); + // TODO: Allow 32 bit INT types? return types.iterator(); } @Override public ImageTypeSpecifier getRawImageType(int pIndex) throws IOException { init(pIndex); - // TODO: Stay DRY... - // TODO: Use this for creating the Image/Buffer in the read code below... + // NOTE: colorMap may be null for 8 bit (gray), 24 bit or 32 bit only - ImageTypeSpecifier specifier; - switch (header.bitplanes) { + switch (header.bitplanes()) { case 1: - // 1 bit + // -> 1 bit IndexColorModel case 2: - // 2 bit + // -> 2 bit IndexColorModel case 3: case 4: - // 4 bit + // -> 4 bit IndexColorModel case 5: case 6: - // May be HAM6 - // May be EHB + // May be EHB or HAM6 case 7: case 8: - // 8 bit // May be HAM8 - if (!isConvertToRGB()) { - if (colorMap != null) { - IndexColorModel cm = colorMap.getIndexColorModel(header, isEHB()); - return ImageTypeSpecifiers.createFromIndexColorModel(cm); - } - else { - return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY); + // otherwise -> 8 bit IndexColorModel + if (!needsConversionToRGB()) { + IndexColorModel indexColorModel = header.colorMap(); + + if (indexColorModel != null) { + return ImageTypeSpecifiers.createFromIndexColorModel(indexColorModel); } + + return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY); } // NOTE: HAM modes falls through, as they are converted to RGB case 24: // 24 bit RGB return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR); - case 25: // TYPE_RGB8: 24 bit + 1 bit mask (we'll convert to full alpha during decoding) - if (formType != IFF.TYPE_RGB8) { - throw new IIOException(String.format("25 bit depth only supported for FORM type RGB8: %s", IFFUtil.toChunkStr(formType))); + case 25: + // For TYPE_RGB8: 24 bit + 1 bit mask (we'll convert to full alpha during decoding) + if (header.formType != IFF.TYPE_RGB8) { + throw new IIOException(String.format("25 bit depth only supported for FORM type RGB8: %s", toChunkStr(header.formType))); } return ImageTypeSpecifiers.createInterleaved(ColorSpaces.getColorSpace(ColorSpace.CS_sRGB), @@ -403,40 +360,49 @@ public final class IFFImageReader extends ImageReaderBase { case 32: // 32 bit ARGB - return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR); + return header.formType == IFF.TYPE_DEEP || header.formType == IFF.TYPE_TVPP + // R G B A + ? ImageTypeSpecifiers.createInterleaved(ColorSpaces.getColorSpace(ColorSpace.CS_sRGB), new int[] {1, 2, 3, 0}, DataBuffer.TYPE_BYTE, true, header.premultiplied()) // TODO: Create based on DPEL! + : ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR); default: - throw new IIOException(String.format("Bit depth not implemented: %d", header.bitplanes)); + throw new IIOException(String.format("Bit depth not implemented: %d", header.bitplanes())); } } - private boolean isConvertToRGB() { - return isHAM() || isPCHG() || isSHAM(); + private boolean needsConversionToRGB() { + return header.isHAM() || header.isMultiPalette(); } - private void readBody(final ImageReadParam pParam) throws IOException { - imageInput.seek(bodyStart); + private void readBody(final ImageReadParam param, final BufferedImage destination) throws IOException { + if (DEBUG) { + System.out.println("Reading body"); + System.out.println("pos: " + imageInput.getStreamPosition()); + System.out.println("body offset: " + header.bodyOffset()); + } + + imageInput.seek(header.bodyOffset()); byteRunStream = null; - if (formType == IFF.TYPE_RGB8) { - readRGB8(pParam, imageInput); + if (header.formType == IFF.TYPE_RGB8 || header.formType == IFF.TYPE_DEEP || header.formType == IFF.TYPE_TVPP) { + readChunky(param, destination, imageInput); } - else if (colorMap != null) { - // NOTE: colorMap may be null for 8 bit (gray), 24 bit or 32 bit only - IndexColorModel cm = colorMap.getIndexColorModel(header, isEHB()); - readIndexed(pParam, imageInput, cm); + else if (header.colorMap() != null) { + // NOTE: For ILBM types, colorMap may be null for 8 bit (gray), 24 bit or 32 bit only + IndexColorModel palette = header.colorMap(); + readInterleavedIndexed(param, destination, palette, imageInput); } else { - readTrueColor(pParam, imageInput); + readInterleaved(param, destination, imageInput); } } - private void readIndexed(final ImageReadParam pParam, final ImageInputStream pInput, final IndexColorModel pModel) throws IOException { - final int width = header.width; - final int height = header.height; + private void readInterleavedIndexed(final ImageReadParam param, final BufferedImage destination, final IndexColorModel palette, final ImageInputStream input) throws IOException { + final int width = header.width(); + final int height = header.height(); - final Rectangle aoi = getSourceRegion(pParam, width, height); - final Point offset = pParam == null ? new Point(0, 0) : pParam.getDestinationOffset(); + final Rectangle aoi = getSourceRegion(param, width, height); + final Point offset = param == null ? new Point(0, 0) : param.getDestinationOffset(); // Set everything to default values int sourceXSubsampling = 1; @@ -445,20 +411,20 @@ public final class IFFImageReader extends ImageReaderBase { int[] destinationBands = null; // Get values from the ImageReadParam, if any - if (pParam != null) { - sourceXSubsampling = pParam.getSourceXSubsampling(); - sourceYSubsampling = pParam.getSourceYSubsampling(); + if (param != null) { + sourceXSubsampling = param.getSourceXSubsampling(); + sourceYSubsampling = param.getSourceYSubsampling(); - sourceBands = pParam.getSourceBands(); - destinationBands = pParam.getDestinationBands(); + sourceBands = param.getSourceBands(); + destinationBands = param.getDestinationBands(); } // Ensure band settings from param are compatible with images - checkReadParamBandSettings(pParam, isConvertToRGB() ? 3 : 1, image.getSampleModel().getNumBands()); + checkReadParamBandSettings(param, needsConversionToRGB() ? 3 : 1, destination.getSampleModel().getNumBands()); - WritableRaster destination = image.getRaster(); + WritableRaster destRaster = destination.getRaster(); if (destinationBands != null || offset.x != 0 || offset.y != 0) { - destination = destination.createWritableChild(0, 0, destination.getWidth(), destination.getHeight(), offset.x, offset.y, destinationBands); + destRaster = destRaster.createWritableChild(0, 0, destRaster.getWidth(), destRaster.getHeight(), offset.x, offset.y, destinationBands); } // NOTE: Each row of the image is stored in an integral number of 16 bit words. @@ -467,31 +433,31 @@ public final class IFFImageReader extends ImageReaderBase { final byte[] planeData = new byte[8 * planeWidth]; ColorModel cm; - WritableRaster raster; + WritableRaster rowRaster; - if (isConvertToRGB()) { - // TODO: If HAM6, use type USHORT_444_RGB or 2BYTE_444_RGB? - // Or create a HAMColorModel, if at all possible? + if (needsConversionToRGB()) { + // TODO: Create a HAMColorModel, if at all possible? // TYPE_3BYTE_BGR cm = new ComponentColorModel( - ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[]{8, 8, 8}, + 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 + rowRaster = Raster.createInterleavedRaster( + DataBuffer.TYPE_BYTE, width, 1, width * 3, 3, new int[] {2, 1, 0}, null ); } else { // TYPE_BYTE_BINARY or TYPE_BYTE_INDEXED - cm = pModel; - raster = pModel.createCompatibleWritableRaster(width, 1); + cm = palette; + rowRaster = palette.createCompatibleWritableRaster(width, 1); } - Raster sourceRow = raster.createChild(aoi.x, 0, aoi.width, 1, 0, 0, sourceBands); + + Raster sourceRow = rowRaster.createChild(aoi.x, 0, aoi.width, 1, 0, 0, sourceBands); final byte[] row = new byte[width * 8]; - final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData(); - final int planes = header.bitplanes; + final byte[] data = ((DataBufferByte) rowRaster.getDataBuffer()).getData(); + final int planes = header.bitplanes(); Object dataElements = null; Object outDataElements = null; @@ -499,7 +465,7 @@ public final class IFFImageReader extends ImageReaderBase { for (int srcY = 0; srcY < height; srcY++) { for (int p = 0; p < planes; p++) { - readPlaneData(pInput, planeData, p * planeWidth, planeWidth); + readPlaneData(planeData, p * planeWidth, planeWidth, input); } // Skip rows outside AOI @@ -510,72 +476,71 @@ public final class IFFImageReader extends ImageReaderBase { return; } - if (formType == IFF.TYPE_ILBM) { + if (header.formType == IFF.TYPE_ILBM) { int pixelPos = 0; for (int planePos = 0; planePos < planeWidth; planePos++) { IFFUtil.bitRotateCW(planeData, planePos, planeWidth, row, pixelPos, 1); pixelPos += 8; } - if (isHAM()) { - hamToRGB(row, pModel, data, 0); + if (header.isHAM()) { + hamToRGB(row, palette, data, 0); } - else if (isConvertToRGB()) { - multiPaletteToRGB(srcY, row, pModel, data, 0); + else if (needsConversionToRGB()) { + multiPaletteToRGB(srcY, row, palette, data, 0); } else { - raster.setDataElements(0, 0, width, 1, row); + rowRaster.setDataElements(0, 0, width, 1, row); } } - else if (formType == IFF.TYPE_PBM) { - raster.setDataElements(0, 0, width, 1, planeData); + else if (header.formType == IFF.TYPE_PBM) { + rowRaster.setDataElements(0, 0, width, 1, planeData); } else { - throw new AssertionError(String.format("Unsupported FORM type: %s", formType)); + throw new AssertionError(String.format("Unsupported FORM type: %s", toChunkStr(header.formType))); } int dstY = (srcY - aoi.y) / sourceYSubsampling; // Handle non-converting raster as special case for performance - if (cm.isCompatibleRaster(destination)) { + if (cm.isCompatibleRaster(destRaster)) { // Rasters are compatible, just write to destination if (sourceXSubsampling == 1) { - destination.setRect(offset.x, dstY, sourceRow); + destRaster.setRect(offset.x, dstY, sourceRow); } else { for (int srcX = 0; srcX < sourceRow.getWidth(); srcX += sourceXSubsampling) { dataElements = sourceRow.getDataElements(srcX, 0, dataElements); int dstX = /*offset.x +*/ srcX / sourceXSubsampling; - destination.setDataElements(dstX, dstY, dataElements); + destRaster.setDataElements(dstX, dstY, dataElements); } } } else { if (cm instanceof IndexColorModel) { - // TODO: Optimize this thing... Maybe it's faster to just get the data indexed, and use drawImage? IndexColorModel icm = (IndexColorModel) cm; for (int srcX = 0; srcX < sourceRow.getWidth(); srcX += sourceXSubsampling) { dataElements = sourceRow.getDataElements(srcX, 0, dataElements); int rgb = icm.getRGB(dataElements); - outDataElements = image.getColorModel().getDataElements(rgb, outDataElements); + outDataElements = destination.getColorModel().getDataElements(rgb, outDataElements); int dstX = srcX / sourceXSubsampling; - destination.setDataElements(dstX, dstY, outDataElements); + destRaster.setDataElements(dstX, dstY, outDataElements); } } else { // TODO: This branch is never tested, and is probably "dead" // ColorConvertOp if (converter == null) { - converter = new ColorConvertOp(cm.getColorSpace(), image.getColorModel().getColorSpace(), null); + converter = new ColorConvertOp(cm.getColorSpace(), destination.getColorModel().getColorSpace(), null); } converter.filter( - raster.createChild(aoi.x, 0, aoi.width, 1, 0, 0, null), - destination.createWritableChild(offset.x, offset.y + srcY - aoi.y, aoi.width, 1, 0, 0, null) + rowRaster.createChild(aoi.x, 0, aoi.width, 1, 0, 0, null), + destRaster.createWritableChild(offset.x, offset.y + srcY - aoi.y, aoi.width, 1, 0, 0, null) ); } } - processImageProgress(srcY * 100f / header.width); + processImageProgress(srcY * 100f / width); if (abortRequested()) { processReadAborted(); break; @@ -583,12 +548,12 @@ public final class IFFImageReader extends ImageReaderBase { } } - private void readRGB8(ImageReadParam pParam, ImageInputStream pInput) throws IOException { - final int width = header.width; - final int height = header.height; + private void readChunky(final ImageReadParam param, final BufferedImage destination, final ImageInputStream input) throws IOException { + final int width = header.width(); + final int height = header.height(); - final Rectangle aoi = getSourceRegion(pParam, width, height); - final Point offset = pParam == null ? new Point(0, 0) : pParam.getDestinationOffset(); + final Rectangle aoi = getSourceRegion(param, width, height); + final Point offset = param == null ? new Point(0, 0) : param.getDestinationOffset(); // Set everything to default values int sourceXSubsampling = 1; @@ -597,50 +562,49 @@ public final class IFFImageReader extends ImageReaderBase { int[] destinationBands = null; // Get values from the ImageReadParam, if any - if (pParam != null) { - sourceXSubsampling = pParam.getSourceXSubsampling(); - sourceYSubsampling = pParam.getSourceYSubsampling(); + if (param != null) { + sourceXSubsampling = param.getSourceXSubsampling(); + sourceYSubsampling = param.getSourceYSubsampling(); - sourceBands = pParam.getSourceBands(); - destinationBands = pParam.getDestinationBands(); + sourceBands = param.getSourceBands(); + destinationBands = param.getDestinationBands(); } // Ensure band settings from param are compatible with images - checkReadParamBandSettings(pParam, 4, image.getSampleModel().getNumBands()); + checkReadParamBandSettings(param, 4, destination.getSampleModel().getNumBands()); - WritableRaster destination = image.getRaster(); + WritableRaster destRaster = destination.getRaster(); if (destinationBands != null || offset.x != 0 || offset.y != 0) { - destination = destination.createWritableChild(0, 0, destination.getWidth(), destination.getHeight(), offset.x, offset.y, destinationBands); + destRaster = destRaster.createWritableChild(0, 0, destRaster.getWidth(), destRaster.getHeight(), offset.x, offset.y, destinationBands); } - WritableRaster raster = image.getRaster().createCompatibleWritableRaster(width, 1); - Raster sourceRow = raster.createChild(aoi.x, 0, aoi.width, 1, 0, 0, sourceBands); + ImageTypeSpecifier rawType = getRawImageType(0); + WritableRaster rowRaster = rawType.createBufferedImage(width, 1).getRaster(); + Raster sourceRow = rowRaster.createChild(aoi.x, 0, aoi.width, 1, 0, 0, sourceBands); int planeWidth = width * 4; - final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData(); - final int channels = (header.bitplanes + 7) / 8; - + final byte[] data = ((DataBufferByte) rowRaster.getDataBuffer()).getData(); Object dataElements = null; for (int srcY = 0; srcY < height; srcY++) { - readPlaneData(pInput, data, 0, planeWidth); + readPlaneData(data, 0, planeWidth, input); if (srcY >= aoi.y && (srcY - aoi.y) % sourceYSubsampling == 0) { int dstY = (srcY - aoi.y) / sourceYSubsampling; if (sourceXSubsampling == 1) { - destination.setRect(0, dstY, sourceRow); + destRaster.setRect(0, dstY, sourceRow); } else { for (int srcX = 0; srcX < sourceRow.getWidth(); srcX += sourceXSubsampling) { dataElements = sourceRow.getDataElements(srcX, 0, dataElements); int dstX = srcX / sourceXSubsampling; - destination.setDataElements(dstX, dstY, dataElements); + destRaster.setDataElements(dstX, dstY, dataElements); } } } - processImageProgress(srcY * 100f / header.width); + processImageProgress(srcY * 100f / width); if (abortRequested()) { processReadAborted(); break; @@ -653,12 +617,12 @@ public final class IFFImageReader extends ImageReaderBase { // followed by green and blue. The first plane holds the least significant // bit of the red value for each pixel, and the last holds the most // significant bit of the blue value. - private void readTrueColor(ImageReadParam pParam, final ImageInputStream pInput) throws IOException { - final int width = header.width; - final int height = header.height; + private void readInterleaved(final ImageReadParam param, final BufferedImage destination, final ImageInputStream input) throws IOException { + final int width = header.width(); + final int height = header.height(); - final Rectangle aoi = getSourceRegion(pParam, width, height); - final Point offset = pParam == null ? new Point(0, 0) : pParam.getDestinationOffset(); + final Rectangle aoi = getSourceRegion(param, width, height); + final Point offset = param == null ? new Point(0, 0) : param.getDestinationOffset(); // Set everything to default values int sourceXSubsampling = 1; @@ -667,39 +631,39 @@ public final class IFFImageReader extends ImageReaderBase { int[] destinationBands = null; // Get values from the ImageReadParam, if any - if (pParam != null) { - sourceXSubsampling = pParam.getSourceXSubsampling(); - sourceYSubsampling = pParam.getSourceYSubsampling(); + if (param != null) { + sourceXSubsampling = param.getSourceXSubsampling(); + sourceYSubsampling = param.getSourceYSubsampling(); - sourceBands = pParam.getSourceBands(); - destinationBands = pParam.getDestinationBands(); + sourceBands = param.getSourceBands(); + destinationBands = param.getDestinationBands(); } // Ensure band settings from param are compatible with images - checkReadParamBandSettings(pParam, header.bitplanes / 8, image.getSampleModel().getNumBands()); + checkReadParamBandSettings(param, header.bitplanes() / 8, destination.getSampleModel().getNumBands()); // 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 = image.getRaster(); + WritableRaster destRaster = destination.getRaster(); if (destinationBands != null || offset.x != 0 || offset.y != 0) { - destination = destination.createWritableChild(0, 0, destination.getWidth(), destination.getHeight(), offset.x, offset.y, destinationBands); + destRaster = destRaster.createWritableChild(0, 0, destRaster.getWidth(), destRaster.getHeight(), offset.x, offset.y, destinationBands); } -// WritableRaster raster = image.getRaster().createCompatibleWritableRaster(width, 1); - WritableRaster raster = image.getRaster().createCompatibleWritableRaster(8 * planeWidth, 1); - Raster sourceRow = raster.createChild(aoi.x, 0, aoi.width, 1, 0, 0, sourceBands); - final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData(); - final int channels = (header.bitplanes + 7) / 8; + WritableRaster rowRaster = destination.getRaster().createCompatibleWritableRaster(8 * planeWidth, 1); + Raster sourceRow = rowRaster.createChild(aoi.x, 0, aoi.width, 1, 0, 0, sourceBands); + + final byte[] data = ((DataBufferByte) rowRaster.getDataBuffer()).getData(); + final int channels = (header.bitplanes() + 7) / 8; final int planesPerChannel = 8; Object dataElements = null; for (int srcY = 0; srcY < height; srcY++) { for (int c = 0; c < channels; c++) { for (int p = 0; p < planesPerChannel; p++) { - readPlaneData(pInput, planeData, p * planeWidth, planeWidth); + readPlaneData(planeData, p * planeWidth, planeWidth, input); } // Skip rows outside AOI @@ -710,7 +674,7 @@ public final class IFFImageReader extends ImageReaderBase { continue; } - if (formType == IFF.TYPE_ILBM) { + if (header.formType == IFF.TYPE_ILBM) { // NOTE: Using (channels - c - 1) instead of just c, // effectively reverses the channel order from RGBA to ABGR int off = (channels - c - 1); @@ -721,33 +685,30 @@ public final class IFFImageReader extends ImageReaderBase { pixelPos += 8; } } - else if (formType == IFF.TYPE_PBM) { + else if (header.formType == IFF.TYPE_PBM) { System.arraycopy(planeData, 0, data, srcY * 8 * planeWidth, planeWidth); } else { - throw new AssertionError(String.format("Unsupported FORM type: %s", formType)); + throw new AssertionError(String.format("Unsupported FORM type: %s", toChunkStr(header.formType))); } } if (srcY >= aoi.y && (srcY - aoi.y) % sourceYSubsampling == 0) { int dstY = (srcY - aoi.y) / sourceYSubsampling; - // TODO: Support conversion to INT (A)RGB rasters (maybe using ColorConvertOp?) // TODO: Avoid createChild if no region? if (sourceXSubsampling == 1) { - destination.setRect(0, dstY, sourceRow); -// dataElements = raster.getDataElements(aoi.x, 0, aoi.width, 1, dataElements); -// destination.setDataElements(offset.x, offset.y + (srcY - aoi.y) / sourceYSubsampling, aoi.width, 1, dataElements); + destRaster.setRect(0, dstY, sourceRow); } else { for (int srcX = 0; srcX < sourceRow.getWidth(); srcX += sourceXSubsampling) { dataElements = sourceRow.getDataElements(srcX, 0, dataElements); int dstX = srcX / sourceXSubsampling; - destination.setDataElements(dstX, dstY, dataElements); + destRaster.setDataElements(dstX, dstY, dataElements); } } } - processImageProgress(srcY * 100f / header.width); + processImageProgress(srcY * 100f / width); if (abortRequested()) { processReadAborted(); break; @@ -755,16 +716,15 @@ public final class IFFImageReader extends ImageReaderBase { } } - private void readPlaneData(final ImageInputStream pInput, final byte[] pData, final int pOffset, final int pPlaneWidth) + private void readPlaneData(final byte[] destination, final int offset, final int planeWidth, final ImageInputStream input) throws IOException { - - switch (header.compressionType) { + switch (header.compressionType()) { case BMHDChunk.COMPRESSION_NONE: - pInput.readFully(pData, pOffset, pPlaneWidth); + input.readFully(destination, offset, planeWidth); - // Uncompressed rows must have even number of bytes - if ((header.bitplanes * pPlaneWidth) % 2 != 0) { - pInput.readByte(); + // Uncompressed rows must have an even number of bytes + if ((header.bitplanes() * planeWidth) % 2 != 0) { + input.readByte(); } break; @@ -773,48 +733,46 @@ public final class IFFImageReader extends ImageReaderBase { // 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, + // The workaround below, is to use a decode buffer size of planeWidth, // to make sure we don't decode anything we don't have to (shouldn't). if (byteRunStream == null) { byteRunStream = new DataInputStream( new DecoderStream( - IIOUtil.createStreamAdapter(pInput, body.chunkLength), - new PackBitsDecoder(true), - pPlaneWidth * header.bitplanes + IIOUtil.createStreamAdapter(input, header.bodyLength()), + new PackBitsDecoder(header.sampleSize(), true), + planeWidth * (header.sampleSize() > 1 ? 1 : header.bitplanes()) ) ); } - byteRunStream.readFully(pData, pOffset, pPlaneWidth); + byteRunStream.readFully(destination, offset, planeWidth); break; case 4: // Compression type 4 means different things for different FORM types... :-P - if (formType == IFF.TYPE_RGB8) { + if (header.formType == IFF.TYPE_RGB8) { // Impulse RGB8 RLE compression: 24 bit RGB + 1 bit mask + 7 bit run count if (byteRunStream == null) { byteRunStream = new DataInputStream( new DecoderStream( - IIOUtil.createStreamAdapter(pInput, body.chunkLength), - new RGB8RLEDecoder(), - pPlaneWidth * 4 + IIOUtil.createStreamAdapter(input, header.bodyLength()), + new RGB8RLEDecoder(), 1024 ) ); } - byteRunStream.readFully(pData, pOffset, pPlaneWidth); - + byteRunStream.readFully(destination, offset, planeWidth); break; } default: - throw new IIOException(String.format("Unknown compression type: %d", header.compressionType)); + throw new IIOException(String.format("Unknown compression type: %d", header.compressionType())); } } - private void multiPaletteToRGB(final int row, final byte[] indexed, final IndexColorModel colorModel, final byte[] dest, final int destOffset) { - final int width = header.width; + private void multiPaletteToRGB(final int row, final byte[] indexed, final IndexColorModel colorModel, final byte[] dest, @SuppressWarnings("SameParameterValue") final int destOffset) { + final int width = header.width(); - ColorModel palette = paletteChange.getColorModel(colorModel, row, isLaced()); + ColorModel palette = header.colorMapForRow(colorModel, row); for (int x = 0; x < width; x++) { int pixel = indexed[x] & 0xff; @@ -828,12 +786,14 @@ public final class IFFImageReader extends ImageReaderBase { } } - private void hamToRGB(final byte[] indexed, final IndexColorModel colorModel, final byte[] dest, final int destOffset) { - final int bits = header.bitplanes; - final int width = header.width; - int lastRed = 0; - int lastGreen = 0; - int lastBlue = 0; + private void hamToRGB(final byte[] indexed, final IndexColorModel colorModel, final byte[] dest, @SuppressWarnings("SameParameterValue") final int destOffset) { + final int bits = header.bitplanes(); + final int width = header.width(); + + // Initialize to the "border color" (index 0) + int lastRed = colorModel.getRed(0); + int lastGreen = colorModel.getGreen(0); + int lastBlue = colorModel.getBlue(0); for (int x = 0; x < width; x++) { int pixel = indexed[x] & 0xff; @@ -867,32 +827,11 @@ public final class IFFImageReader extends ImageReaderBase { } } - private boolean isSHAM() { - // TODO: - return false; - } - - private boolean isPCHG() { - return paletteChange != null; - } - - private boolean isEHB() { - return viewPort != null && viewPort.isEHB(); - } - - private boolean isHAM() { - return viewPort != null && viewPort.isHAM(); - } - - public boolean isLaced() { - return viewPort != null && viewPort.isLaced(); - } - - public static void main(String[] pArgs) throws IOException { + public static void main(String[] args) { ImageReader reader = new IFFImageReader(new IFFImageReaderSpi()); boolean scale = false; - for (String arg : pArgs) { + for (String arg : args) { if (arg.startsWith("-")) { scale = true; continue; diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReaderSpi.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReaderSpi.java index 36e196a5..c7f002d1 100755 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReaderSpi.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReaderSpi.java @@ -53,40 +53,41 @@ public final class IFFImageReaderSpi extends ImageReaderSpiBase { } @Override - public boolean canDecodeInput(Object pSource) throws IOException { - return pSource instanceof ImageInputStream && canDecode((ImageInputStream) pSource); + public boolean canDecodeInput(final Object source) throws IOException { + return source instanceof ImageInputStream && canDecode((ImageInputStream) source); } - private static boolean canDecode(ImageInputStream pInput) throws IOException { - pInput.mark(); + private static boolean canDecode(final ImageInputStream input) throws IOException { + input.mark(); try { // Is it IFF - if (pInput.readInt() == IFF.CHUNK_FORM) { - pInput.readInt();// Skip length field + if (input.readInt() == IFF.CHUNK_FORM) { + input.readInt();// Skip length field - int type = pInput.readInt(); + int type = input.readInt(); if (type == IFF.TYPE_ILBM || type == IFF.TYPE_PBM - || type == IFF.TYPE_RGB8) { // Impulse RGB8 + || type == IFF.TYPE_RGB8 // Impulse RGB8 format + || type == IFF.TYPE_DEEP || type == IFF.TYPE_TVPP) { // TVPaint DEEP format return true; } } } finally { - pInput.reset(); + input.reset(); } return false; } @Override - public ImageReader createReaderInstance(Object pExtension) throws IOException { + public ImageReader createReaderInstance(final Object extension) { return new IFFImageReader(this); } @Override - public String getDescription(Locale pLocale) { + public String getDescription(Locale locale) { return "Commodore Amiga/Electronic Arts Image Interchange Format (IFF) image reader"; } } diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageWriter.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageWriter.java index 212520ce..7796d8a7 100755 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageWriter.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageWriter.java @@ -30,31 +30,22 @@ package com.twelvemonkeys.imageio.plugins.iff; -import java.awt.*; -import java.awt.image.BufferedImage; -import java.awt.image.ColorModel; -import java.awt.image.IndexColorModel; -import java.awt.image.Raster; -import java.awt.image.RenderedImage; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.OutputStream; - -import javax.imageio.IIOImage; -import javax.imageio.ImageIO; -import javax.imageio.ImageTypeSpecifier; -import javax.imageio.ImageWriteParam; -import javax.imageio.ImageWriter; -import javax.imageio.metadata.IIOMetadata; -import javax.imageio.spi.ImageWriterSpi; - import com.twelvemonkeys.imageio.ImageWriterBase; import com.twelvemonkeys.imageio.util.IIOUtil; import com.twelvemonkeys.io.FastByteArrayOutputStream; import com.twelvemonkeys.io.enc.EncoderStream; import com.twelvemonkeys.io.enc.PackBitsEncoder; +import javax.imageio.*; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.spi.ImageWriterSpi; +import java.awt.*; +import java.awt.image.*; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; + /** * Writer for Commodore Amiga (Electronic Arts) IFF ILBM (InterLeaved BitMap) format. * The IFF format (Interchange File Format) is the standard file format @@ -68,8 +59,8 @@ import com.twelvemonkeys.io.enc.PackBitsEncoder; */ public final class IFFImageWriter extends ImageWriterBase { - IFFImageWriter(ImageWriterSpi pProvider) { - super(pProvider); + IFFImageWriter(ImageWriterSpi provider) { + super(provider); } @Override @@ -83,23 +74,29 @@ public final class IFFImageWriter extends ImageWriterBase { } @Override - public void write(IIOMetadata pStreamMetadata, IIOImage pImage, ImageWriteParam pParam) throws IOException { + public ImageWriteParam getDefaultWriteParam() { + return new IFFWriteParam(getLocale()); + } + + @Override + public void write(IIOMetadata streamMetadata, IIOImage image, ImageWriteParam param) throws IOException { assertOutput(); - if (pImage.hasRaster()) { + if (image.hasRaster()) { throw new UnsupportedOperationException("Cannot write raster"); } processImageStarted(0); + RenderedImage renderedImage = image.getRenderedImage(); + boolean compress = shouldCompress(renderedImage, param); + // Prepare image data to be written ByteArrayOutputStream imageData = new FastByteArrayOutputStream(1024); - packImageData(imageData, pImage.getRenderedImage(), pParam); - - //System.out.println("Image data: " + imageData.size()); + packImageData(imageData, renderedImage, compress); // Write metadata - writeMeta(pImage.getRenderedImage(), imageData.size()); + writeMeta(renderedImage, imageData.size(), compress); // Write image data writeBody(imageData); @@ -107,34 +104,31 @@ public final class IFFImageWriter extends ImageWriterBase { processImageComplete(); } - private void writeBody(ByteArrayOutputStream pImageData) throws IOException { + private void writeBody(ByteArrayOutputStream imageData) throws IOException { imageOutput.writeInt(IFF.CHUNK_BODY); - imageOutput.writeInt(pImageData.size()); + imageOutput.writeInt(imageData.size()); - // NOTE: This is much faster than imageOutput.write(pImageData.toByteArray()) + // NOTE: This is much faster than imageOutput.write(imageData.toByteArray()) // as the data array is not duplicated try (OutputStream adapter = IIOUtil.createStreamAdapter(imageOutput)) { - pImageData.writeTo(adapter); + imageData.writeTo(adapter); } - if (pImageData.size() % 2 == 0) { + if (imageData.size() % 2 == 0) { imageOutput.writeByte(0); // PAD } imageOutput.flush(); } - private void packImageData(OutputStream pOutput, RenderedImage pImage, ImageWriteParam pParam) throws IOException { - // TODO: Allow param to dictate uncompressed - // TODO: Allow param to dictate type PBM? + private void packImageData(OutputStream outputStream, RenderedImage image, final boolean compress) throws IOException { // TODO: Subsample/AOI - final boolean compress = shouldCompress(pImage); - final OutputStream output = compress ? new EncoderStream(pOutput, new PackBitsEncoder(), true) : pOutput; - final ColorModel model = pImage.getColorModel(); - final Raster raster = pImage.getData(); + final OutputStream output = compress ? new EncoderStream(outputStream, new PackBitsEncoder(), true) : outputStream; + final ColorModel model = image.getColorModel(); + final Raster raster = image.getData(); - final int width = pImage.getWidth(); - final int height = pImage.getHeight(); + final int width = image.getWidth(); + final int height = image.getHeight(); // Store each row of pixels // 0. Loop pr channel @@ -142,7 +136,6 @@ public final 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 = 2 * ((width + 15) / 16); final byte[] planeData = new byte[8 * planeWidth]; final int channels = (model.getPixelSize() + 7) / 8; @@ -167,10 +160,6 @@ public final class IFFImageWriter extends ImageWriterBase { for (int p = 0; p < planesPerChannel; p++) { output.write(planeData, p * planeWidth, planeWidth); - - if (!compress && planeWidth % 2 != 0) { - output.write(0); // PAD - } } } @@ -182,17 +171,16 @@ public final class IFFImageWriter extends ImageWriterBase { output.flush(); } - private void writeMeta(RenderedImage pImage, int pBodyLength) throws IOException { + private void writeMeta(RenderedImage image, int bodyLength, boolean compress) throws IOException { // Annotation ANNO chunk, 8 + annoData.length bytes String annotation = String.format("Written by %s IFFImageWriter %s", getOriginatingProvider().getVendorName(), getOriginatingProvider().getVersion()); GenericChunk anno = new GenericChunk(IFFUtil.toInt("ANNO".getBytes()), annotation.getBytes()); - ColorModel cm = pImage.getColorModel(); + ColorModel cm = image.getColorModel(); IndexColorModel icm = null; // Bitmap header BMHD chunk, 8 + 20 bytes - // By default, don't compress narrow images - int compression = shouldCompress(pImage) ? BMHDChunk.COMPRESSION_BYTE_RUN : BMHDChunk.COMPRESSION_NONE; + int compression = compress ? BMHDChunk.COMPRESSION_BYTE_RUN : BMHDChunk.COMPRESSION_NONE; BMHDChunk header; if (cm instanceof IndexColorModel) { @@ -200,12 +188,12 @@ public final class IFFImageWriter extends ImageWriterBase { icm = (IndexColorModel) cm; int trans = icm.getTransparency() == Transparency.BITMASK ? BMHDChunk.MASK_TRANSPARENT_COLOR : BMHDChunk.MASK_NONE; int transPixel = icm.getTransparency() == Transparency.BITMASK ? icm.getTransparentPixel() : 0; - header = new BMHDChunk(pImage.getWidth(), pImage.getHeight(), icm.getPixelSize(), + header = new BMHDChunk(image.getWidth(), image.getHeight(), icm.getPixelSize(), trans, compression, transPixel); } else { //System.out.println(cm.getClass().getName()); - header = new BMHDChunk(pImage.getWidth(), pImage.getHeight(), cm.getPixelSize(), + header = new BMHDChunk(image.getWidth(), image.getHeight(), cm.getPixelSize(), BMHDChunk.MASK_NONE, compression, 0); } @@ -217,7 +205,7 @@ public final class IFFImageWriter extends ImageWriterBase { } // ILBM(4) + anno(8+len) + header(8+20) + cmap(8+len)? + body(8+len); - int size = 4 + 8 + anno.chunkLength + 28 + 8 + pBodyLength; + int size = 4 + 8 + anno.chunkLength + 28 + 8 + bodyLength; if (cmap != null) { size += 8 + cmap.chunkLength; } @@ -231,21 +219,30 @@ public final class IFFImageWriter extends ImageWriterBase { header.writeChunk(imageOutput); if (cmap != null) { - //System.out.println("CMAP written"); cmap.writeChunk(imageOutput); } - } - private boolean shouldCompress(RenderedImage pImage) { - return pImage.getWidth() >= 32; + private boolean shouldCompress(final RenderedImage image, final ImageWriteParam param) { + if (param != null && param.canWriteCompressed()) { + switch (param.getCompressionMode()) { + case ImageWriteParam.MODE_DISABLED: + return false; + case ImageWriteParam.MODE_EXPLICIT: + return IFFWriteParam.COMPRESSION_TYPES[1].equals(param.getCompressionType()); + default: + // Fall through + } + } + + return image.getWidth() >= 32; } - public static void main(String[] pArgs) throws IOException { - BufferedImage image = ImageIO.read(new File(pArgs[0])); + public static void main(String[] args) throws IOException { + BufferedImage image = ImageIO.read(new File(args[0])); ImageWriter writer = new IFFImageWriter(new IFFImageWriterSpi()); - writer.setOutput(ImageIO.createImageOutputStream(new File(pArgs[1]))); + writer.setOutput(ImageIO.createImageOutputStream(new File(args[1]))); //writer.addIIOWriteProgressListener(new ProgressListenerBase() { // int mCurrPct = 0; // diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageWriterSpi.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageWriterSpi.java index 22e11630..533694d0 100755 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageWriterSpi.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageWriterSpi.java @@ -30,13 +30,11 @@ package com.twelvemonkeys.imageio.plugins.iff; -import java.io.IOException; -import java.util.Locale; +import com.twelvemonkeys.imageio.spi.ImageWriterSpiBase; import javax.imageio.ImageTypeSpecifier; import javax.imageio.ImageWriter; - -import com.twelvemonkeys.imageio.spi.ImageWriterSpiBase; +import java.util.Locale; /** * IFFImageWriterSpi @@ -53,19 +51,19 @@ public class IFFImageWriterSpi extends ImageWriterSpiBase { super(new IFFProviderInfo()); } - public boolean canEncodeImage(final ImageTypeSpecifier pType) { + public boolean canEncodeImage(final ImageTypeSpecifier type) { // 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; } @Override - public ImageWriter createWriterInstance(Object pExtension) throws IOException { + public ImageWriter createWriterInstance(Object extension) { return new IFFImageWriter(this); } @Override - public String getDescription(Locale pLocale) { + public String getDescription(Locale locale) { return "Commodore Amiga/Electronic Arts Image Interchange Format (IFF) image writer"; } } diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFProviderInfo.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFProviderInfo.java index 92270714..6aeaf725 100644 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFProviderInfo.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFProviderInfo.java @@ -40,11 +40,11 @@ import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo; * @version $Id: IFFProviderInfo.java,v 1.0 20/03/15 harald.kuhr Exp$ */ final class IFFProviderInfo extends ReaderWriterProviderInfo { - protected IFFProviderInfo() { + IFFProviderInfo() { super( IFFProviderInfo.class, new String[] {"iff", "IFF"}, - new String[] {"iff", "lbm", "ham", "ham8", "ilbm"}, + new String[] {"iff", "lbm", "ham", "ham8", "ilbm", "rgb8", "deep"}, new String[] {"image/iff", "image/x-iff"}, "com.twelvemonkeys.imageio.plugins.iff.IFFImageReader", new String[]{"com.twelvemonkeys.imageio.plugins.iff.IFFImageReaderSpi"}, diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFUtil.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFUtil.java index ffa62a5f..2f7555a5 100755 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFUtil.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFUtil.java @@ -56,11 +56,11 @@ final class IFFUtil { * @return the rotation table */ static private 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 + return new long[] { + 0x00000000L , 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 }; } @@ -75,16 +75,16 @@ final class IFFUtil { * 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 + * @param src source pixel data + * @param srcPos starting index of 8 x 8 bit source tile + * @param srcStep byte offset between adjacent rows in source + * @param dst destination pixel data + * @param dstPos starting index of 8 x 8 bit destination tile + * @param dstStep 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; + static void bitRotateCW(final byte[] src, int srcPos, int srcStep, + final byte[] dst, int dstPos, int dstStep) { + int idx = srcPos; int lonyb; int hinyb; @@ -92,41 +92,41 @@ final class IFFUtil { long hi = 0; for (int i = 0; i < 8; i++) { - lonyb = pSrc[idx] & 0xF; - hinyb = (pSrc[idx] >> 4) & 0xF; + lonyb = src[idx] & 0xF; + hinyb = (src[idx] >> 4) & 0xF; lo |= RTABLE[i][lonyb]; hi |= RTABLE[i][hinyb]; - idx += pSrcStep; + idx += srcStep; } - idx = pDstPos; + idx = dstPos; - 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; + dst[idx] = (byte)((hi >> 24) & 0xFF); + idx += dstStep; + if (idx < dst.length) { + dst[idx] = (byte)((hi >> 16) & 0xFF); + idx += dstStep; + if (idx < dst.length) { + dst[idx] = (byte)((hi >> 8) & 0xFF); + idx += dstStep; + if (idx < dst.length) { + dst[idx] = (byte)(hi & 0xFF); + idx += dstStep; } } } - 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); + if (idx < dst.length) { + dst[idx] = (byte)((lo >> 24) & 0xFF); + idx += dstStep; + if (idx < dst.length) { + dst[idx] = (byte)((lo >> 16) & 0xFF); + idx += dstStep; + if (idx < dst.length) { + dst[idx] = (byte)((lo >> 8) & 0xFF); + idx += dstStep; + if (idx < dst.length) { + dst[idx] = (byte)(lo & 0xFF); } } } @@ -137,16 +137,16 @@ final class IFFUtil { * Rotate bits counterclockwise. * The IFFImageWriter uses this to convert pixel bits from chunky to planar. * - * @param pSrc source pixel data (only lower 8 bits used) - * @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 + * @param src source pixel data (only lower 8 bits used) + * @param srcPos starting index of 8 x 8 bit source tile + * @param srcStep byte offset between adjacent rows in source + * @param dst destination pixel data + * @param dstPos starting index of 8 x 8 bit destination tile + * @param dstStep byte offset between adjacent rows in destination */ - static void bitRotateCCW(final int[] pSrc, int pSrcPos, int pSrcStep, - final byte[] pDst, int pDstPos, int pDstStep) { - int idx = pSrcPos; + static void bitRotateCCW(final int[] src, int srcPos, @SuppressWarnings("SameParameterValue") int srcStep, + final byte[] dst, int dstPos, int dstStep) { + int idx = srcPos; int lonyb; int hinyb; @@ -154,48 +154,49 @@ final class IFFUtil { long hi = 0; for (int i = 7; i >= 0; i--) { - lonyb = pSrc[idx] & 0xF; - hinyb = (pSrc[idx] >> 4) & 0xF; + lonyb = src[idx] & 0xF; + hinyb = (src[idx] >> 4) & 0xF; lo |= RTABLE[i][lonyb]; hi |= RTABLE[i][hinyb]; - idx += pSrcStep; + idx += srcStep; } - idx = pDstPos; + idx = dstPos; - pDst[idx] = (byte)(lo & 0xFF); - idx += pDstStep; - pDst[idx] = (byte)((lo >> 8) & 0xFF); - idx += pDstStep; - pDst[idx] = (byte)((lo >> 16) & 0xFF); - idx += pDstStep; - pDst[idx] = (byte)((lo >> 24) & 0xFF); + dst[idx] = (byte)(lo & 0xFF); + idx += dstStep; + dst[idx] = (byte)((lo >> 8) & 0xFF); + idx += dstStep; + dst[idx] = (byte)((lo >> 16) & 0xFF); + idx += dstStep; + dst[idx] = (byte)((lo >> 24) & 0xFF); - idx += pDstStep; + idx += dstStep; - pDst[idx] = (byte)(hi & 0xFF); - idx += pDstStep; - pDst[idx] = (byte)((hi >> 8) & 0xFF); - idx += pDstStep; - pDst[idx] = (byte)((hi >> 16) & 0xFF); - idx += pDstStep; - pDst[idx] = (byte)((hi >> 24) & 0xFF); + dst[idx] = (byte)(hi & 0xFF); + idx += dstStep; + dst[idx] = (byte)((hi >> 8) & 0xFF); + idx += dstStep; + dst[idx] = (byte)((hi >> 16) & 0xFF); + idx += dstStep; + dst[idx] = (byte)((hi >> 24) & 0xFF); } /** * Rotate bits counterclockwise. * The IFFImageWriter uses this to convert pixel bits from chunky to planar. * - * @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 + * @param src source pixel data + * @param srcPos starting index of 8 x 8 bit source tile + * @param srcStep byte offset between adjacent rows in source + * @param dst destination pixel data + * @param dstPos starting index of 8 x 8 bit destination tile + * @param dstStep byte offset between adjacent rows in destination */ - static void bitRotateCCW(final byte[] pSrc, int pSrcPos, int pSrcStep, - final byte[] pDst, int pDstPos, int pDstStep) { - int idx = pSrcPos; + @SuppressWarnings("unused") + static void bitRotateCCW(final byte[] src, int srcPos, int srcStep, + final byte[] dst, int dstPos, int dstStep) { + int idx = srcPos; int lonyb; int hinyb; @@ -203,57 +204,57 @@ final class IFFUtil { long hi = 0; for (int i = 7; i >= 0; i--) { - lonyb = pSrc[idx] & 0xF; - hinyb = (pSrc[idx] >> 4) & 0xF; + lonyb = src[idx] & 0xF; + hinyb = (src[idx] >> 4) & 0xF; lo |= RTABLE[i][lonyb]; hi |= IFFUtil.RTABLE[i][hinyb]; - idx += pSrcStep; + idx += srcStep; } - idx = pDstPos; + idx = dstPos; - pDst[idx] = (byte)(lo & 0xFF); - idx += pDstStep; - pDst[idx] = (byte)((lo >> 8) & 0xFF); - idx += pDstStep; - pDst[idx] = (byte)((lo >> 16) & 0xFF); - idx += pDstStep; - pDst[idx] = (byte)((lo >> 24) & 0xFF); + dst[idx] = (byte)(lo & 0xFF); + idx += dstStep; + dst[idx] = (byte)((lo >> 8) & 0xFF); + idx += dstStep; + dst[idx] = (byte)((lo >> 16) & 0xFF); + idx += dstStep; + dst[idx] = (byte)((lo >> 24) & 0xFF); - idx += pDstStep; + idx += dstStep; - pDst[idx] = (byte)(hi & 0xFF); - idx += pDstStep; - pDst[idx] = (byte)((hi >> 8) & 0xFF); - idx += pDstStep; - pDst[idx] = (byte)((hi >> 16) & 0xFF); - idx += pDstStep; - pDst[idx] = (byte)((hi >> 24) & 0xFF); + dst[idx] = (byte)(hi & 0xFF); + idx += dstStep; + dst[idx] = (byte)((hi >> 8) & 0xFF); + idx += dstStep; + dst[idx] = (byte)((hi >> 16) & 0xFF); + idx += dstStep; + dst[idx] = (byte)((hi >> 24) & 0xFF); } /** * Converts a byte array to an int. * - * @param pBytes a byte array of length 4 + * @param bytes a byte array of length 4 * @return the bytes converted to an int * * @throws ArrayIndexOutOfBoundsException if length is < 4 */ - static int toInt(final byte[] pBytes) { - return (pBytes[0] & 0xff) << 24 | (pBytes[1] & 0xff) << 16 - | (pBytes[2] & 0xff) << 8 | (pBytes[3] & 0xff); + static int toInt(final byte[] bytes) { + return (bytes[0] & 0xff) << 24 | (bytes[1] & 0xff) << 16 + | (bytes[2] & 0xff) << 8 | (bytes[3] & 0xff); } /** * Converts an int to a four letter String. * - * @param pChunkId the chunk identifier + * @param chunkId the chunk identifier * @return a String */ - static String toChunkStr(int pChunkId) { - return new String(new byte[] {(byte) ((pChunkId & 0xff000000) >> 24), - (byte) ((pChunkId & 0x00ff0000) >> 16), - (byte) ((pChunkId & 0x0000ff00) >> 8), - (byte) ((pChunkId & 0x000000ff))}); + static String toChunkStr(int chunkId) { + return new String(new byte[] {(byte) ((chunkId & 0xff000000) >> 24), + (byte) ((chunkId & 0x00ff0000) >> 16), + (byte) ((chunkId & 0x0000ff00) >> 8), + (byte) ((chunkId & 0x000000ff))}); } } diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFWriteParam.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFWriteParam.java new file mode 100644 index 00000000..9f96490c --- /dev/null +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFWriteParam.java @@ -0,0 +1,25 @@ +package com.twelvemonkeys.imageio.plugins.iff; + +import javax.imageio.ImageWriteParam; +import java.util.Locale; + +/** + * IFFWriteParam. + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: IFFWriteParam.java,v 1.0 03/02/2022 haraldk Exp$ + */ +public final class IFFWriteParam extends ImageWriteParam { + + static final String[] COMPRESSION_TYPES = {"NONE", "RLE"}; + + public IFFWriteParam(final Locale locale) { + super(locale); + + compressionTypes = COMPRESSION_TYPES; + compressionType = compressionTypes[1]; + + canWriteCompressed = true; + } +} diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/MutableIndexColorModel.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/MutableIndexColorModel.java index dc381aa9..65459a9c 100644 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/MutableIndexColorModel.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/MutableIndexColorModel.java @@ -80,9 +80,9 @@ final class MutableIndexColorModel extends ColorModel { // TODO: Move validation to chunk (when reading) if (index >= rgbs.length) { // TODO: Issue IIO warning - System.err.printf("warning - palette change register out of range\n"); + System.err.println("warning - palette change register out of range"); System.err.printf(" change structure %d index=%d (max %d)\n", i, index, getMapSize() - 1); - System.err.printf(" ignoring it... colors might get messed up from here\n"); + System.err.println(" ignoring it... colors might get messed up from here"); } else if (index != MP_REG_IGNORE) { updateRGB(index, ((changes[i].r & 0xff) << 16) | ((changes[i].g & 0xff) << 8) | (changes[i].b & 0xff)); diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/PCHGChunk.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/PCHGChunk.java index fe8d52f3..f7246baa 100644 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/PCHGChunk.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/PCHGChunk.java @@ -72,42 +72,43 @@ final class PCHGChunk extends AbstractMultiPaletteChunk { private int totalChanges; private int minReg; - PCHGChunk(int pChunkLength) { - super(IFF.CHUNK_PCHG, pChunkLength); + PCHGChunk(int chunkLength) { + super(IFF.CHUNK_PCHG, chunkLength); } @Override - void readChunk(final DataInput pInput) throws IOException { - int compression = pInput.readUnsignedShort(); - int flags = pInput.readUnsignedShort(); - startLine = pInput.readShort(); - lineCount = pInput.readUnsignedShort(); - changedLines = pInput.readUnsignedShort(); - minReg = pInput.readUnsignedShort(); - int maxReg = pInput.readUnsignedShort(); - /*int maxChangesPerLine = */pInput.readUnsignedShort(); // We don't really care, as we're not limited by the Amiga display hardware - totalChanges = pInput.readInt(); + void readChunk(final DataInput input) throws IOException { + int compression = input.readUnsignedShort(); + int flags = input.readUnsignedShort(); + startLine = input.readShort(); + lineCount = input.readUnsignedShort(); + changedLines = input.readUnsignedShort(); + minReg = input.readUnsignedShort(); + int maxReg = input.readUnsignedShort(); + /*int maxChangesPerLine = */ + input.readUnsignedShort(); // We don't really care, as we're not limited by the Amiga display hardware + totalChanges = input.readInt(); byte[] data; switch (compression) { case PCHG_COMP_NONE: data = new byte[chunkLength - 20]; - pInput.readFully(data); + input.readFully(data); break; case PCHG_COMP_HUFFMAN: // NOTE: Huffman decompression is completely untested, due to lack of source data (read: Probably broken). - int compInfoSize = pInput.readInt(); - int originalDataSize = pInput.readInt(); + int compInfoSize = input.readInt(); + int originalDataSize = input.readInt(); short[] compTree = new short[compInfoSize / 2]; for (int i = 0; i < compTree.length; i++) { - compTree[i] = pInput.readShort(); + compTree[i] = input.readShort(); } byte[] compData = new byte[chunkLength - 20 - 8 - compInfoSize]; - pInput.readFully(compData); + input.readFully(compData); data = new byte[originalDataSize]; diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/RGB8RLEDecoder.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/RGB8RLEDecoder.java index 461022b9..17a78832 100644 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/RGB8RLEDecoder.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/RGB8RLEDecoder.java @@ -1,3 +1,33 @@ +/* + * Copyright (c) 2022, 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 of the copyright holder 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 HOLDER 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.iff; import com.twelvemonkeys.io.enc.DecodeException; @@ -13,7 +43,7 @@ import java.nio.ByteBuffer; * * @author Harald Kuhr * @author last modified by $Author: haraldk$ - * @version $Id: RGB8Stream.java,v 1.0 28/01/2022 haraldk Exp$ + * @version $Id: RGB8RLEDecoder.java,v 1.0 28/01/2022 haraldk Exp$ * * @see RGBN and RGB8 IFF Image Data */ diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/SHAMChunk.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/SHAMChunk.java index 471c7009..7b0a3140 100644 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/SHAMChunk.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/SHAMChunk.java @@ -38,8 +38,8 @@ package com.twelvemonkeys.imageio.plugins.iff; * @version $Id: SHAMChunk.java,v 1.0 30.03.12 14:53 haraldk Exp$ */ final class SHAMChunk extends AbstractMultiPaletteChunk { - SHAMChunk(int pChunkLength) { - super(IFF.CHUNK_SHAM, pChunkLength); + SHAMChunk(int chunkLength) { + super(IFF.CHUNK_SHAM, chunkLength); } @Override diff --git a/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageMetadataTest.java b/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageMetadataTest.java index 035a9cb6..151857e1 100644 --- a/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageMetadataTest.java +++ b/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageMetadataTest.java @@ -1,26 +1,24 @@ package com.twelvemonkeys.imageio.plugins.iff; -import static org.junit.Assert.*; - -import java.awt.image.IndexColorModel; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import javax.imageio.metadata.IIOMetadataFormatImpl; -import javax.imageio.metadata.IIOMetadataNode; - import org.junit.Test; import org.junit.function.ThrowingRunnable; import org.w3c.dom.Node; +import javax.imageio.IIOException; +import javax.imageio.metadata.IIOMetadataFormatImpl; +import javax.imageio.metadata.IIOMetadataNode; +import java.awt.image.IndexColorModel; +import java.nio.charset.StandardCharsets; + +import static org.junit.Assert.*; + public class IFFImageMetadataTest { @Test - public void testStandardFeatures() { - BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + public void testStandardFeatures() throws IIOException { + Form header = Form.ofType(IFF.TYPE_ILBM) + .with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); - final IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.emptyList()); + final IFFImageMetadata metadata = new IFFImageMetadata(header, null); // Standard metadata format assertTrue(metadata.isStandardMetadataFormatSupported()); @@ -49,10 +47,11 @@ public class IFFImageMetadataTest { } @Test - public void testStandardChromaGray() { - BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + public void testStandardChromaGray() throws IIOException { + Form header = Form.ofType(IFF.TYPE_ILBM) + .with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); - IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.emptyList()); + IFFImageMetadata metadata = new IFFImageMetadata(header, null); IIOMetadataNode chroma = metadata.getStandardChromaNode(); assertNotNull(chroma); @@ -75,10 +74,11 @@ public class IFFImageMetadataTest { } @Test - public void testStandardChromaRGB() { - BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + public void testStandardChromaRGB() throws IIOException { + Form header = Form.ofType(IFF.TYPE_ILBM) + .with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); - IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.emptyList()); + IFFImageMetadata metadata = new IFFImageMetadata(header, null); IIOMetadataNode chroma = metadata.getStandardChromaNode(); assertNotNull(chroma); @@ -101,16 +101,17 @@ public class IFFImageMetadataTest { } @Test - public void testStandardChromaPalette() { - BMHDChunk header = new BMHDChunk(300, 200, 1, BMHDChunk.MASK_TRANSPARENT_COLOR, BMHDChunk.COMPRESSION_BYTE_RUN, 1); + public void testStandardChromaPalette() throws IIOException { + Form header = Form.ofType(IFF.TYPE_ILBM) + .with(new BMHDChunk(300, 200, 1, BMHDChunk.MASK_TRANSPARENT_COLOR, BMHDChunk.COMPRESSION_BYTE_RUN, 1)); byte[] bw = {0, (byte) 0xff}; - IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, new IndexColorModel(header.bitplanes, bw.length, bw, bw, bw, header.transparentIndex), null, Collections.emptyList()); + IFFImageMetadata metadata = new IFFImageMetadata(header, new IndexColorModel(header.bitplanes(), bw.length, bw, bw, bw, header.transparentIndex())); IIOMetadataNode chroma = metadata.getStandardChromaNode(); assertNotNull(chroma); assertEquals("Chroma", chroma.getNodeName()); - assertEquals(4, chroma.getLength()); + assertEquals(5, chroma.getLength()); IIOMetadataNode colorSpaceType = (IIOMetadataNode) chroma.getFirstChild(); assertEquals("ColorSpaceType", colorSpaceType.getNodeName()); @@ -138,14 +139,21 @@ public class IFFImageMetadataTest { assertEquals(rgb, item0.getAttribute("blue")); } - // TODO: BackgroundIndex == 1?? + // BackgroundIndex == 1 + IIOMetadataNode backgroundIndex = (IIOMetadataNode) palette.getNextSibling(); + assertEquals("BackgroundIndex", backgroundIndex.getNodeName()); + assertEquals("1", backgroundIndex.getAttribute("value")); + + // No more elements + assertNull(backgroundIndex.getNextSibling()); } @Test - public void testStandardCompressionRLE() { - BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + public void testStandardCompressionRLE() throws IIOException { + Form header = Form.ofType(IFF.TYPE_ILBM) + .with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); - IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.emptyList()); + IFFImageMetadata metadata = new IFFImageMetadata(header, null); IIOMetadataNode compression = metadata.getStandardCompressionNode(); assertNotNull(compression); @@ -164,19 +172,21 @@ public class IFFImageMetadataTest { } @Test - public void testStandardCompressionNone() { - BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_NONE, 0); + public void testStandardCompressionNone() throws IIOException { + Form header = Form.ofType(IFF.TYPE_ILBM) + .with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_NONE, 0)); - IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.emptyList()); + IFFImageMetadata metadata = new IFFImageMetadata(header, null); assertNull(metadata.getStandardCompressionNode()); // No compression, all default... } @Test - public void testStandardDataILBM_Gray() { - BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + public void testStandardDataILBM_Gray() throws IIOException { + Form header = Form.ofType(IFF.TYPE_ILBM) + .with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); - IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.emptyList()); + IFFImageMetadata metadata = new IFFImageMetadata(header, null); IIOMetadataNode data = metadata.getStandardDataNode(); assertNotNull(data); @@ -199,10 +209,11 @@ public class IFFImageMetadataTest { } @Test - public void testStandardDataILBM_RGB() { - BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + public void testStandardDataILBM_RGB() throws IIOException { + Form header = Form.ofType(IFF.TYPE_ILBM) + .with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); - IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.emptyList()); + IFFImageMetadata metadata = new IFFImageMetadata(header, null); IIOMetadataNode data = metadata.getStandardDataNode(); assertNotNull(data); @@ -225,10 +236,11 @@ public class IFFImageMetadataTest { } @Test - public void testStandardDataILBM_RGBA() { - BMHDChunk header = new BMHDChunk(300, 200, 32, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + public void testStandardDataILBM_RGBA() throws IIOException { + Form header = Form.ofType(IFF.TYPE_ILBM) + .with(new BMHDChunk(300, 200, 32, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); - IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.emptyList()); + IFFImageMetadata metadata = new IFFImageMetadata(header, null); IIOMetadataNode data = metadata.getStandardDataNode(); assertNotNull(data); @@ -251,12 +263,13 @@ public class IFFImageMetadataTest { } @Test - public void testStandardDataILBM_Palette() { + public void testStandardDataILBM_Palette() throws IIOException { for (int i = 1; i <= 8; i++) { - BMHDChunk header = new BMHDChunk(300, 200, i, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + Form header = Form.ofType(IFF.TYPE_ILBM) + .with(new BMHDChunk(300, 200, i, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); byte[] rgb = new byte[2 << i]; // Colors doesn't really matter here - IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, new IndexColorModel(header.bitplanes, rgb.length, rgb, rgb, rgb, 0), null, Collections.emptyList()); + IFFImageMetadata metadata = new IFFImageMetadata(header, new IndexColorModel(header.bitplanes(), rgb.length, rgb, rgb, rgb, 0)); IIOMetadataNode data = metadata.getStandardDataNode(); assertNotNull(data); @@ -280,10 +293,11 @@ public class IFFImageMetadataTest { } @Test - public void testStandardDataPBM_Gray() { - BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + public void testStandardDataPBM_Gray() throws IIOException { + Form header = Form.ofType(IFF.TYPE_PBM) + .with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); - IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_PBM, header, null, null, Collections.emptyList()); + IFFImageMetadata metadata = new IFFImageMetadata(header, null); IIOMetadataNode data = metadata.getStandardDataNode(); assertNotNull(data); @@ -306,10 +320,11 @@ public class IFFImageMetadataTest { } @Test - public void testStandardDataPBM_RGB() { - BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + public void testStandardDataPBM_RGB() throws IIOException { + Form header = Form.ofType(IFF.TYPE_PBM) + .with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); - IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_PBM, header, null, null, Collections.emptyList()); + IFFImageMetadata metadata = new IFFImageMetadata(header, null); IIOMetadataNode data = metadata.getStandardDataNode(); assertNotNull(data); @@ -333,40 +348,57 @@ public class IFFImageMetadataTest { @Test - public void testStandardDimensionNoViewport() { - BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + public void testStandardDimensionNoViewport() throws IIOException { + BMHDChunk bitmapHeader = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + bitmapHeader.xAspect = 0; + bitmapHeader.yAspect = 0; - IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.emptyList()); + Form header = Form.ofType(IFF.TYPE_ILBM) + .with(bitmapHeader); + + IFFImageMetadata metadata = new IFFImageMetadata(header, null); IIOMetadataNode dimension = metadata.getStandardDimensionNode(); assertNull(dimension); } @Test - public void testStandardDimensionNormal() { - BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + public void testStandardDimensionNormal() throws IIOException { + Form header = Form.ofType(IFF.TYPE_ILBM) + .with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)) + .with(new CAMGChunk(4)); - IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, new CAMGChunk(4), Collections.emptyList()); + IFFImageMetadata metadata = new IFFImageMetadata(header, null); IIOMetadataNode dimension = metadata.getStandardDimensionNode(); - assertNotNull(dimension); - assertEquals("Dimension", dimension.getNodeName()); - assertEquals(1, dimension.getLength()); - IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild(); - assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName()); - assertEquals("1.0", pixelAspectRatio.getAttribute("value")); + // No Dimension node is okay, or one with an aspect ratio of 1.0 + if (dimension != null) { + assertEquals("Dimension", dimension.getNodeName()); + assertEquals(1, dimension.getLength()); - assertNull(pixelAspectRatio.getNextSibling()); // No more children + IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild(); + assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName()); + assertEquals("1.0", pixelAspectRatio.getAttribute("value")); + + assertNull(pixelAspectRatio.getNextSibling()); // No more children + } } @Test - public void testStandardDimensionHires() { - BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + public void testStandardDimensionHires() throws IIOException { + BMHDChunk bitmapHeader = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + bitmapHeader.xAspect = 2; + bitmapHeader.yAspect = 1; + CAMGChunk viewPort = new CAMGChunk(4); viewPort.camg = 0x8000; - IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, viewPort, Collections.emptyList()); + Form header = Form.ofType(IFF.TYPE_ILBM) + .with(bitmapHeader) + .with(viewPort); + + IFFImageMetadata metadata = new IFFImageMetadata(header, null); IIOMetadataNode dimension = metadata.getStandardDimensionNode(); assertNotNull(dimension); @@ -381,13 +413,19 @@ public class IFFImageMetadataTest { } @Test - public void testStandardDimensionInterlaced() { - BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + public void testStandardDimensionInterlaced() throws IIOException { + BMHDChunk bitmapHeader = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + bitmapHeader.xAspect = 1; + bitmapHeader.yAspect = 2; CAMGChunk viewPort = new CAMGChunk(4); viewPort.camg = 0x4; - IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, viewPort, Collections.emptyList()); + Form header = Form.ofType(IFF.TYPE_ILBM) + .with(bitmapHeader) + .with(viewPort); + + IFFImageMetadata metadata = new IFFImageMetadata(header, null); IIOMetadataNode dimension = metadata.getStandardDimensionNode(); assertNotNull(dimension); @@ -402,13 +440,14 @@ public class IFFImageMetadataTest { } @Test - public void testStandardDimensionHiresInterlaced() { - BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); - + public void testStandardDimensionHiresInterlaced() throws IIOException { CAMGChunk viewPort = new CAMGChunk(4); viewPort.camg = 0x8004; + Form header = Form.ofType(IFF.TYPE_ILBM) + .with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)) + .with(viewPort); - IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, viewPort, Collections.emptyList()); + IFFImageMetadata metadata = new IFFImageMetadata(header, null); IIOMetadataNode dimension = metadata.getStandardDimensionNode(); assertNotNull(dimension); @@ -423,10 +462,11 @@ public class IFFImageMetadataTest { } @Test - public void testStandardDocument() { - BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + public void testStandardDocument() throws IIOException { + Form header = Form.ofType(IFF.TYPE_ILBM) + .with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); - IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.emptyList()); + IFFImageMetadata metadata = new IFFImageMetadata(header, null); IIOMetadataNode document = metadata.getStandardDocumentNode(); assertNotNull(document); @@ -441,13 +481,15 @@ public class IFFImageMetadataTest { } @Test - public void testStandardText() { - BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); - + public void testStandardText() throws IIOException { + int[] chunks = {IFF.CHUNK_ANNO, IFF.CHUNK_UTF8}; String[] texts = {"annotation", "äñnótâtïøñ"}; - List meta = Arrays.asList(new GenericChunk(IFF.CHUNK_ANNO, texts[0].getBytes(StandardCharsets.US_ASCII)), - new GenericChunk(IFF.CHUNK_UTF8, texts[1].getBytes(StandardCharsets.UTF_8))); - IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, meta); + Form header = Form.ofType(IFF.TYPE_ILBM) + .with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)) + .with(new GenericChunk(chunks[0], texts[0].getBytes(StandardCharsets.US_ASCII))) + .with(new GenericChunk(chunks[1], texts[1].getBytes(StandardCharsets.UTF_8))); + + IFFImageMetadata metadata = new IFFImageMetadata(header, null); IIOMetadataNode text = metadata.getStandardTextNode(); assertNotNull(text); @@ -457,26 +499,28 @@ public class IFFImageMetadataTest { for (int i = 0; i < texts.length; i++) { IIOMetadataNode textEntry = (IIOMetadataNode) text.item(i); assertEquals("TextEntry", textEntry.getNodeName()); - assertEquals(IFFUtil.toChunkStr(meta.get(i).chunkId), textEntry.getAttribute("keyword")); + assertEquals(IFFUtil.toChunkStr(chunks[i]), textEntry.getAttribute("keyword")); assertEquals(texts[i], textEntry.getAttribute("value")); } } @Test - public void testStandardTransparencyRGB() { - BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + public void testStandardTransparencyRGB() throws IIOException { + Form header = Form.ofType(IFF.TYPE_ILBM) + .with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); - IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.emptyList()); + IFFImageMetadata metadata = new IFFImageMetadata(header, null); IIOMetadataNode transparency = metadata.getStandardTransparencyNode(); assertNull(transparency); // No transparency, just defaults } @Test - public void testStandardTransparencyRGBA() { - BMHDChunk header = new BMHDChunk(300, 200, 32, BMHDChunk.MASK_HAS_MASK, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + public void testStandardTransparencyRGBA() throws IIOException { + Form header = Form.ofType(IFF.TYPE_ILBM) + .with(new BMHDChunk(300, 200, 32, BMHDChunk.MASK_HAS_MASK, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); - IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.emptyList()); + IFFImageMetadata metadata = new IFFImageMetadata(header, null); IIOMetadataNode transparency = metadata.getStandardTransparencyNode(); assertNotNull(transparency); @@ -491,11 +535,12 @@ public class IFFImageMetadataTest { } @Test - public void testStandardTransparencyPalette() { - BMHDChunk header = new BMHDChunk(300, 200, 1, BMHDChunk.MASK_TRANSPARENT_COLOR, BMHDChunk.COMPRESSION_BYTE_RUN, 1); + public void testStandardTransparencyPalette() throws IIOException { + Form header = Form.ofType(IFF.TYPE_ILBM) + .with(new BMHDChunk(300, 200, 1, BMHDChunk.MASK_TRANSPARENT_COLOR, BMHDChunk.COMPRESSION_BYTE_RUN, 1)); byte[] bw = {0, (byte) 0xff}; - IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, new IndexColorModel(header.bitplanes, bw.length, bw, bw, bw, header.transparentIndex), null, Collections.emptyList()); + IFFImageMetadata metadata = new IFFImageMetadata(header, new IndexColorModel(header.bitplanes(), bw.length, bw, bw, bw, header.transparentIndex())); IIOMetadataNode transparency = metadata.getStandardTransparencyNode(); assertNotNull(transparency); @@ -508,4 +553,131 @@ public class IFFImageMetadataTest { assertNull(pixelAspectRatio.getNextSibling()); // No more children } + + @Test + public void testStandardRGB8() throws IIOException { + Form header = Form.ofType(IFF.TYPE_RGB8) + .with(new BMHDChunk(300, 200, 25, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); + IFFImageMetadata metadata = new IFFImageMetadata(header, null); + + // Chroma + IIOMetadataNode chroma = metadata.getStandardChromaNode(); + assertNotNull(chroma); + assertEquals("Chroma", chroma.getNodeName()); + assertEquals(3, chroma.getLength()); + + IIOMetadataNode colorSpaceType = (IIOMetadataNode) chroma.getFirstChild(); + assertEquals("ColorSpaceType", colorSpaceType.getNodeName()); + assertEquals("RGB", colorSpaceType.getAttribute("name")); + + IIOMetadataNode numChannels = (IIOMetadataNode) colorSpaceType.getNextSibling(); + assertEquals("NumChannels", numChannels.getNodeName()); + assertEquals("4", numChannels.getAttribute("value")); + + IIOMetadataNode blackIsZero = (IIOMetadataNode) numChannels.getNextSibling(); + assertEquals("BlackIsZero", blackIsZero.getNodeName()); + assertEquals("TRUE", blackIsZero.getAttribute("value")); + + assertNull(blackIsZero.getNextSibling()); // No more children + + // Data + IIOMetadataNode data = metadata.getStandardDataNode(); + assertNotNull(data); + assertEquals("Data", data.getNodeName()); + assertEquals(3, data.getLength()); + + IIOMetadataNode planarConfiguration = (IIOMetadataNode) data.getFirstChild(); + assertEquals("PlanarConfiguration", planarConfiguration.getNodeName()); + assertEquals("PixelInterleaved", planarConfiguration.getAttribute("value")); + + IIOMetadataNode sampleFormat = (IIOMetadataNode) planarConfiguration.getNextSibling(); + assertEquals("SampleFormat", sampleFormat.getNodeName()); + assertEquals("UnsignedIntegral", sampleFormat.getAttribute("value")); + + IIOMetadataNode bitsPerSample = (IIOMetadataNode) sampleFormat.getNextSibling(); + assertEquals("BitsPerSample", bitsPerSample.getNodeName()); + assertEquals("8 8 8 1", bitsPerSample.getAttribute("value")); + + assertNull(bitsPerSample.getNextSibling()); // No more children + + // Transparency + IIOMetadataNode transparency = metadata.getStandardTransparencyNode(); + assertNotNull(transparency); + assertEquals("Transparency", transparency.getNodeName()); + assertEquals(1, transparency.getLength()); + + IIOMetadataNode alpha = (IIOMetadataNode) transparency.getFirstChild(); + assertEquals("Alpha", alpha.getNodeName()); + assertEquals("nonpremultiplied", alpha.getAttribute("value")); + + assertNull(alpha.getNextSibling()); // No more children + } + + @Test + public void testStandardDEEP() throws IIOException { + DPELChunk dpel = new DPELChunk(20); + dpel.typeDepths = new DPELChunk.TypeDepth[4]; + for (int i = 0; i < dpel.typeDepths.length; i++) { + dpel.typeDepths[i] = new DPELChunk.TypeDepth(i == 0 ? 11 : i, 8); + } + + Form header = Form.ofType(IFF.TYPE_DEEP) + .with(new DGBLChunk(8)) + .with(dpel); + IFFImageMetadata metadata = new IFFImageMetadata(header, null); + + // Chroma + IIOMetadataNode chroma = metadata.getStandardChromaNode(); + assertNotNull(chroma); + assertEquals("Chroma", chroma.getNodeName()); + assertEquals(3, chroma.getLength()); + + IIOMetadataNode colorSpaceType = (IIOMetadataNode) chroma.getFirstChild(); + assertEquals("ColorSpaceType", colorSpaceType.getNodeName()); + assertEquals("RGB", colorSpaceType.getAttribute("name")); + + IIOMetadataNode numChannels = (IIOMetadataNode) colorSpaceType.getNextSibling(); + assertEquals("NumChannels", numChannels.getNodeName()); + assertEquals("4", numChannels.getAttribute("value")); + + IIOMetadataNode blackIsZero = (IIOMetadataNode) numChannels.getNextSibling(); + assertEquals("BlackIsZero", blackIsZero.getNodeName()); + assertEquals("TRUE", blackIsZero.getAttribute("value")); + + // TODO: BackgroundColor = 0x666666 + + assertNull(blackIsZero.getNextSibling()); // No more children + + // Data + IIOMetadataNode data = metadata.getStandardDataNode(); + assertNotNull(data); + assertEquals("Data", data.getNodeName()); + assertEquals(3, data.getLength()); + + IIOMetadataNode planarConfiguration = (IIOMetadataNode) data.getFirstChild(); + assertEquals("PlanarConfiguration", planarConfiguration.getNodeName()); + assertEquals("PixelInterleaved", planarConfiguration.getAttribute("value")); + + IIOMetadataNode sampleFormat = (IIOMetadataNode) planarConfiguration.getNextSibling(); + assertEquals("SampleFormat", sampleFormat.getNodeName()); + assertEquals("UnsignedIntegral", sampleFormat.getAttribute("value")); + + IIOMetadataNode bitsPerSample = (IIOMetadataNode) sampleFormat.getNextSibling(); + assertEquals("BitsPerSample", bitsPerSample.getNodeName()); + assertEquals("8 8 8 8", bitsPerSample.getAttribute("value")); + + assertNull(bitsPerSample.getNextSibling()); // No more children + + // Transparency + IIOMetadataNode transparency = metadata.getStandardTransparencyNode(); + assertNotNull(transparency); + assertEquals("Transparency", transparency.getNodeName()); + assertEquals(1, transparency.getLength()); + + IIOMetadataNode alpha = (IIOMetadataNode) transparency.getFirstChild(); + assertEquals("Alpha", alpha.getNodeName()); + assertEquals("premultiplied", alpha.getAttribute("value")); + + assertNull(alpha.getNextSibling()); // No more children + } } \ No newline at end of file diff --git a/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReaderTest.java b/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReaderTest.java index 1ca3102f..65588c75 100755 --- a/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReaderTest.java +++ b/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReaderTest.java @@ -92,7 +92,12 @@ public class IFFImageReaderTest extends ImageReaderAbstractTest // Impulse RGB8 format straight from Imagine 2.0 new TestData(getClassLoaderResource("/iff/glowsphere2.rgb8"), new Dimension(640, 480)), // Impulse RGB8 format written by ASDG ADPro, with cross boundary runs, which is probably not as per spec... - new TestData(getClassLoaderResource("/iff/tunnel04-adpro-cross-boundary-runs.rgb8"), new Dimension(640, 480)) + new TestData(getClassLoaderResource("/iff/tunnel04-adpro-cross-boundary-runs.rgb8"), new Dimension(640, 480)), + // TVPaint (TecSoft) DEEP format + new TestData(getClassLoaderResource("/iff/arch.deep"), new Dimension(800, 600)), + // TVPaint Project (TVPP is effectively same as the DEEP format, but multiple layers, background color etc.) + // TODO: This file contains one more image/layer, second DBOD chunk @1868908, len: 1199144! + new TestData(getClassLoaderResource("/iff/warm-and-bright.pro"), new Dimension(800, 600)) ); } @@ -103,7 +108,7 @@ public class IFFImageReaderTest extends ImageReaderAbstractTest @Override protected List getSuffixes() { - return Arrays.asList("iff", "ilbm", "ham", "ham8", "lbm"); + return Arrays.asList("iff", "ilbm", "ham", "ham8", "lbm", "rgb8", "deep"); } @Override @@ -138,9 +143,14 @@ public class IFFImageReaderTest extends ImageReaderAbstractTest for (int i = 0; i < 32; i++) { // Make sure the color model is really EHB - assertEquals("red", (reds[i] & 0xff) / 2, reds[i + 32] & 0xff); - assertEquals("blue", (blues[i] & 0xff) / 2, blues[i + 32] & 0xff); - assertEquals("green", (greens[i] & 0xff) / 2, greens[i + 32] & 0xff); + try { + assertEquals("red", (reds[i] & 0xff) / 2, reds[i + 32] & 0xff); + assertEquals("blue", (blues[i] & 0xff) / 2, blues[i + 32] & 0xff); + assertEquals("green", (greens[i] & 0xff) / 2, greens[i + 32] & 0xff); + } + catch (AssertionError err) { + throw new AssertionError("Color " + i + " " + err.getMessage(), err); + } } } } diff --git a/imageio/imageio-iff/src/test/resources/iff/arch.deep b/imageio/imageio-iff/src/test/resources/iff/arch.deep new file mode 100644 index 00000000..4a5b885b Binary files /dev/null and b/imageio/imageio-iff/src/test/resources/iff/arch.deep differ diff --git a/imageio/imageio-iff/src/test/resources/iff/warm-and-bright.pro b/imageio/imageio-iff/src/test/resources/iff/warm-and-bright.pro new file mode 100644 index 00000000..6f1a2783 Binary files /dev/null and b/imageio/imageio-iff/src/test/resources/iff/warm-and-bright.pro differ