diff --git a/README.md b/README.md index 41f5df6d..264d3d55 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ The goal is to create a set of efficient and robust ImageIO plug-ins, that can b | | ICO | MS Windows Icon Format | ✔ | ✔ | - | | [HDR](https://github.com/haraldk/TwelveMonkeys/wiki/HDR-Plugin) | HDR | Radiance High Dynamic Range RGBE Format | ✔ | - | Standard | | [ICNS](https://github.com/haraldk/TwelveMonkeys/wiki/ICNS-Plugin) | ICNS | Apple Icon Image | ✔ | ✔ | - | -| [IFF](https://github.com/haraldk/TwelveMonkeys/wiki/IFF-Plugin) | IFF | Commodore Amiga/Electronic Arts Interchange File Format | ✔ | ✔ | - | +| [IFF](https://github.com/haraldk/TwelveMonkeys/wiki/IFF-Plugin) | IFF | Commodore Amiga/Electronic Arts Interchange File Format | ✔ | ✔ | Standard | | [JPEG](https://github.com/haraldk/TwelveMonkeys/wiki/JPEG-Plugin) | **JPEG** | Joint Photographers Expert Group | ✔ | ✔ | Native & Standard | | [PCX](https://github.com/haraldk/TwelveMonkeys/wiki/PCX-Plugin) | PCX | ZSoft Paintbrush Format | ✔ | - | Standard | | | DCX | Multi-page PCX fax document | ✔ | - | Standard | 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 291dd35b..420c05fb 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 @@ -102,14 +102,15 @@ abstract class AbstractMultiPaletteChunk extends IFFChunk implements MultiPalett } @Override - void writeChunk(DataOutput pOutput) throws IOException { + void writeChunk(DataOutput pOutput) { throw new UnsupportedOperationException("Method writeChunk not implemented"); } + @Override public ColorModel getColorModel(final IndexColorModel colorModel, final int rowIndex, final boolean laced) { if (rowIndex < lastRow || mutablePalette == null || originalPalette != null && originalPalette.get() != colorModel) { - originalPalette = new WeakReference(colorModel); + originalPalette = new WeakReference<>(colorModel); mutablePalette = new MutableIndexColorModel(colorModel); if (initialChanges != null) { 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 03186c27..36d4b48d 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 @@ -30,11 +30,12 @@ package com.twelvemonkeys.imageio.plugins.iff; -import javax.imageio.IIOException; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; +import javax.imageio.IIOException; + /** * BMHDChunk * @@ -129,7 +130,8 @@ final class BMHDChunk extends IFFChunk { pageHeight = Math.min(pHeight, Short.MAX_VALUE); } - void readChunk(DataInput pInput) throws IOException { + @Override + void readChunk(final DataInput pInput) throws IOException { if (chunkLength != 20) { throw new IIOException("Unknown BMHD chunk length: " + chunkLength); } @@ -148,7 +150,8 @@ final class BMHDChunk extends IFFChunk { pageHeight = pInput.readShort(); } - void writeChunk(DataOutput pOutput) throws IOException { + @Override + void writeChunk(final DataOutput pOutput) throws IOException { pOutput.writeInt(chunkId); pOutput.writeInt(chunkLength); @@ -167,6 +170,7 @@ final class BMHDChunk extends IFFChunk { pOutput.writeShort(pageHeight); } + @Override public String toString() { return super.toString() + " {w=" + width + ", h=" + height 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 fc583a4d..e5b5aec6 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 @@ -32,7 +32,6 @@ package com.twelvemonkeys.imageio.plugins.iff; import java.io.DataInput; import java.io.DataOutput; -import java.io.IOException; /** * BODYChunk @@ -45,11 +44,13 @@ final class BODYChunk extends IFFChunk { super(IFF.CHUNK_BODY, pChunkLength); } - void readChunk(DataInput pInput) throws IOException { + @Override + void readChunk(final DataInput pInput) { throw new InternalError("BODY chunk should only be read from IFFImageReader"); } - void writeChunk(DataOutput pOutput) throws IOException { + @Override + void writeChunk(final DataOutput pOutput) { 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 54d09e94..05137874 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 @@ -30,11 +30,12 @@ package com.twelvemonkeys.imageio.plugins.iff; -import javax.imageio.IIOException; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; +import javax.imageio.IIOException; + /** * CAMGChunk * @@ -46,13 +47,14 @@ final class CAMGChunk extends IFFChunk { // #define CAMG_HAM 0x800 /* hold and modify */ // #define CAMG_EHB 0x80 /* extra halfbrite */ - private int camg; + int camg; public CAMGChunk(int pLength) { super(IFF.CHUNK_CAMG, pLength); } - void readChunk(DataInput pInput) throws IOException { + @Override + void readChunk(final DataInput pInput) throws IOException { if (chunkLength != 4) { throw new IIOException("Unknown CAMG chunk length: " + chunkLength); } @@ -60,7 +62,8 @@ final class CAMGChunk extends IFFChunk { camg = pInput.readInt(); } - void writeChunk(DataOutput pOutput) throws IOException { + @Override + void writeChunk(final DataOutput pOutput) { throw new InternalError("Not implemented: writeChunk()"); } @@ -80,6 +83,7 @@ final class CAMGChunk extends IFFChunk { return (camg & 0x80) != 0; } + @Override public String toString() { return super.toString() + " {mode=" + (isHAM() ? "HAM" : isEHB() ? "EHB" : "Normal") + "}"; } 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 ba17fdb5..82215afb 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 @@ -30,7 +30,6 @@ 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; @@ -39,6 +38,8 @@ import java.io.DataOutput; import java.io.IOException; import java.util.Arrays; +import javax.imageio.IIOException; + /** * CMAPChunk * @@ -68,6 +69,7 @@ final class CMAPChunk extends IFFChunk { model = pModel; } + @Override void readChunk(final DataInput pInput) throws IOException { int numColors = chunkLength / 3; @@ -95,6 +97,7 @@ final class CMAPChunk extends IFFChunk { } } + @Override void writeChunk(final DataOutput pOutput) throws IOException { pOutput.writeInt(chunkId); pOutput.writeInt(chunkLength); @@ -112,6 +115,7 @@ final class CMAPChunk extends IFFChunk { } } + @Override public String toString() { return super.toString() + " {colorMap=" + model + "}"; } 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 9a46e153..ef595fe8 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 @@ -35,10 +35,10 @@ import java.io.DataOutput; import java.io.IOException; /** - * UnknownChunk + * GenericChunk * * @author Harald Kuhr - * @version $Id: UnknownChunk.java,v 1.0 28.feb.2006 00:53:47 haku Exp$ + * @version $Id: GenericChunk.java,v 1.0 28.feb.2006 00:53:47 haku Exp$ */ final class GenericChunk extends IFFChunk { @@ -46,7 +46,7 @@ final class GenericChunk extends IFFChunk { protected GenericChunk(int pChunkId, int pChunkLength) { super(pChunkId, pChunkLength); - data = new byte[pChunkLength <= 50 ? pChunkLength : 47]; + data = new byte[chunkLength]; } protected GenericChunk(int pChunkId, byte[] pChunkData) { @@ -54,13 +54,15 @@ final class GenericChunk extends IFFChunk { data = pChunkData; } - void readChunk(DataInput pInput) throws IOException { + @Override + void readChunk(final DataInput pInput) throws IOException { pInput.readFully(data, 0, data.length); skipData(pInput, chunkLength, data.length); } - void writeChunk(DataOutput pOutput) throws IOException { + @Override + void writeChunk(final DataOutput pOutput) throws IOException { pOutput.writeInt(chunkId); pOutput.writeInt(chunkLength); pOutput.write(data, 0, data.length); @@ -70,6 +72,7 @@ final class GenericChunk extends IFFChunk { } } + @Override public String toString() { return super.toString() + " {value=\"" + new String(data, 0, data.length <= 50 ? data.length : 47) 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 1f032c30..d5d194d8 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 @@ -91,6 +91,12 @@ interface IFF { /** EA IFF 85 Generic Copyright text chunk */ 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';; + + /** Third-party defined UTF-8 text. */ + int CHUNK_UTF8 = ('U' << 24) + ('T' << 16) + ('F' << 8) + '8'; + /** color cycling */ int CHUNK_CRNG = ('C' << 24) + ('R' << 16) + ('N' << 8) + 'G'; /** color cycling */ 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 new file mode 100644 index 00000000..846c0f15 --- /dev/null +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageMetadata.java @@ -0,0 +1,252 @@ +package com.twelvemonkeys.imageio.plugins.iff; + +import java.awt.*; +import java.awt.image.IndexColorModel; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import javax.imageio.metadata.IIOMetadataNode; + +import com.twelvemonkeys.imageio.AbstractMetadata; + +final class IFFImageMetadata extends AbstractMetadata { + private final int formType; + private final BMHDChunk 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 = formType; + this.header = header; + this.colorMap = colorMap; + this.viewPort = viewPort; + this.meta = meta; + } + + @Override + protected IIOMetadataNode getStandardChromaNode() { + IIOMetadataNode chroma = new IIOMetadataNode("Chroma"); + + IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType"); + chroma.appendChild(csType); + + switch (header.bitplanes) { + case 8: + if (colorMap == null) { + csType.setAttribute("name", "GRAY"); + break; + } + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 24: + case 32: + csType.setAttribute("name", "RGB"); + break; + default: + csType.setAttribute("name", "Unknown"); + } + + // 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) { + numChannels.setAttribute("value", Integer.toString(1)); + } + else if (header.bitplanes == 32) { + numChannels.setAttribute("value", Integer.toString(4)); + } + else { + numChannels.setAttribute("value", Integer.toString(3)); + } + + IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero"); + chroma.appendChild(blackIsZero); + blackIsZero.setAttribute("value", "TRUE"); + + // NOTE: TGA files may contain a color map, even if true color... + // Not sure if this is a good idea to expose to the meta data, + // as it might be unexpected... Then again... + if (colorMap != null) { + IIOMetadataNode palette = new IIOMetadataNode("Palette"); + chroma.appendChild(palette); + + for (int i = 0; i < colorMap.getMapSize(); i++) { + IIOMetadataNode paletteEntry = new IIOMetadataNode("PaletteEntry"); + palette.appendChild(paletteEntry); + paletteEntry.setAttribute("index", Integer.toString(i)); + + paletteEntry.setAttribute("red", Integer.toString(colorMap.getRed(i))); + paletteEntry.setAttribute("green", Integer.toString(colorMap.getGreen(i))); + paletteEntry.setAttribute("blue", Integer.toString(colorMap.getBlue(i))); + } + } + + // TODO: Background color is the color of the transparent index in the color model? +// if (extensions != null && extensions.getBackgroundColor() != 0) { +// Color background = new Color(extensions.getBackgroundColor(), true); +// +// IIOMetadataNode backgroundColor = new IIOMetadataNode("BackgroundColor"); +// chroma.appendChild(backgroundColor); +// +// backgroundColor.setAttribute("red", Integer.toString(background.getRed())); +// backgroundColor.setAttribute("green", Integer.toString(background.getGreen())); +// backgroundColor.setAttribute("blue", Integer.toString(background.getBlue())); +// } + + return chroma; + } + + @Override + protected IIOMetadataNode getStandardCompressionNode() { + if (header.compressionType == BMHDChunk.COMPRESSION_NONE) { + return null; // All defaults + } + + IIOMetadataNode node = new IIOMetadataNode("Compression"); + + IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName"); + compressionTypeName.setAttribute("value", "RLE"); + node.appendChild(compressionTypeName); + + IIOMetadataNode lossless = new IIOMetadataNode("Lossless"); + lossless.setAttribute("value", "TRUE"); + node.appendChild(lossless); + + return node; + } + + @Override + protected IIOMetadataNode getStandardDataNode() { + IIOMetadataNode data = new IIOMetadataNode("Data"); + + // PlanarConfiguration + IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration"); + switch (formType) { + case IFF.TYPE_PBM: + planarConfiguration.setAttribute("value", "PixelInterleaved"); + break; + case IFF.TYPE_ILBM: + planarConfiguration.setAttribute("value", "PlaneInterleaved"); + break; + default: + planarConfiguration.setAttribute("value", "Unknown " + IFFUtil.toChunkStr(formType)); + break; + } + data.appendChild(planarConfiguration); + + IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat"); + sampleFormat.setAttribute("value", colorMap != null ? "Index" : "UnsignedIntegral"); + data.appendChild(sampleFormat); + + // BitsPerSample + IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample"); + String value = bitsPerSampleValue(header.bitplanes); + bitsPerSample.setAttribute("value", value); + data.appendChild(bitsPerSample); + + // SignificantBitsPerSample not in format + // SampleMSB not in format + + return data; + + } + + private String bitsPerSampleValue(int bitplanes) { + switch (bitplanes) { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + return Integer.toString(bitplanes); + case 24: + return "8 8 8"; + case 32: + return "8 8 8 8"; + default: + throw new IllegalArgumentException("Ubknown bit count: " + bitplanes); + } + } + + @Override + protected IIOMetadataNode getStandardDimensionNode() { + if (viewPort == null) { + return null; + } + + IIOMetadataNode dimension = new IIOMetadataNode("Dimension"); + + // PixelAspectRatio + IIOMetadataNode pixelAspectRatio = new IIOMetadataNode("PixelAspectRatio"); + pixelAspectRatio.setAttribute("value", String.valueOf((viewPort.isHires() ? 2f : 1f) / (viewPort.isLaced() ? 2f : 1f))); + dimension.appendChild(pixelAspectRatio); + + // TODO: HorizontalScreenSize? + // TODO: VerticalScreenSize? + + return dimension; + } + + @Override + protected IIOMetadataNode getStandardDocumentNode() { + IIOMetadataNode document = new IIOMetadataNode("Document"); + + IIOMetadataNode formatVersion = new IIOMetadataNode("FormatVersion"); + document.appendChild(formatVersion); + formatVersion.setAttribute("value", "1.0"); + + return document; + } + + @Override + protected IIOMetadataNode getStandardTextNode() { + if (meta.isEmpty()) { + return null; + } + + IIOMetadataNode text = new IIOMetadataNode("Text"); + + // /Text/TextEntry@keyword = field name, /Text/TextEntry@value = field value. + for (GenericChunk chunk : meta) { + IIOMetadataNode node = new IIOMetadataNode("TextEntry"); + node.setAttribute("keyword", IFFUtil.toChunkStr(chunk.chunkId)); + node.setAttribute("value", new String(chunk.data, chunk.chunkId == IFF.CHUNK_UTF8 ? StandardCharsets.UTF_8 : StandardCharsets.US_ASCII)); + text.appendChild(node); + } + + return text; + + } + + @Override + protected IIOMetadataNode getStandardTransparencyNode() { + if ((colorMap == null || !colorMap.hasAlpha()) && header.bitplanes != 32) { + return null; + } + + IIOMetadataNode transparency = new IIOMetadataNode("Transparency"); + + if (header.bitplanes == 32) { + IIOMetadataNode alpha = new IIOMetadataNode("Alpha"); + alpha.setAttribute("value", "nonpremultiplied"); + transparency.appendChild(alpha); + } + + if (colorMap != null && colorMap.getTransparency() == Transparency.BITMASK) { + IIOMetadataNode transparentIndex = new IIOMetadataNode("TransparentIndex"); + transparentIndex.setAttribute("value", Integer.toString(colorMap.getTransparentPixel())); + transparency.appendChild(transparentIndex); + } + + return transparency; + } +} 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 b378f48d..cc2f1439 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 @@ -30,6 +30,26 @@ package com.twelvemonkeys.imageio.plugins.iff; +import java.awt.*; +import java.awt.color.ColorSpace; +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 javax.imageio.IIOException; +import javax.imageio.ImageIO; +import javax.imageio.ImageReadParam; +import javax.imageio.ImageReader; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.stream.ImageInputStream; + import com.twelvemonkeys.image.ResampleOp; import com.twelvemonkeys.imageio.ImageReaderBase; import com.twelvemonkeys.imageio.stream.BufferedImageInputStream; @@ -38,19 +58,6 @@ import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers; import com.twelvemonkeys.io.enc.DecoderStream; import com.twelvemonkeys.io.enc.PackBitsDecoder; -import javax.imageio.*; -import javax.imageio.spi.ImageReaderSpi; -import javax.imageio.stream.ImageInputStream; -import java.awt.*; -import java.awt.color.ColorSpace; -import java.awt.image.*; -import java.io.DataInputStream; -import java.io.File; -import java.io.IOException; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; - /** * Reader for Commodore Amiga (Electronic Arts) IFF ILBM (InterLeaved BitMap) and PBM * format (Packed BitMap). @@ -96,7 +103,7 @@ import java.util.List; * @see Wikipedia: IFF * @see Wikipedia: IFF ILBM */ -public class IFFImageReader extends ImageReaderBase { +public final class IFFImageReader extends ImageReaderBase { // http://home.comcast.net/~erniew/lwsdk/docs/filefmts/ilbm.html // http://www.fileformat.info/format/iff/spec/7866a9f0e53c42309af667c5da3bd426/view.htm // - Contains definitions of some "new" chunks, as well as alternative FORM types @@ -111,17 +118,14 @@ public class IFFImageReader extends ImageReaderBase { private GRABChunk grab; private CAMGChunk viewPort; private MultiPalette paletteChange; + private final List meta = new ArrayList<>(); private int formType; private long bodyStart; private BufferedImage image; private DataInputStream byteRunStream; - public IFFImageReader() { - super(new IFFImageReaderSpi()); - } - - protected IFFImageReader(ImageReaderSpi pProvider) { + IFFImageReader(ImageReaderSpi pProvider) { super(pProvider); } @@ -133,6 +137,7 @@ public class IFFImageReader extends ImageReaderBase { } } + @Override protected void resetMembers() { header = null; colorMap = null; @@ -140,6 +145,7 @@ public class IFFImageReader extends ImageReaderBase { body = null; viewPort = null; formType = 0; + meta.clear(); image = null; byteRunStream = null; @@ -258,11 +264,6 @@ public class IFFImageReader extends ImageReaderBase { // System.out.println(ctbl); break; - case IFF.CHUNK_JUNK: - // Always skip junk chunks - IFFChunk.skipData(imageInput, length, 0); - break; - case IFF.CHUNK_BODY: if (body != null) { throw new IIOException("Multiple BODY chunks not allowed"); @@ -274,18 +275,32 @@ public class IFFImageReader extends ImageReaderBase { // NOTE: We don't read the body here, it's done later in the read(int, ImageReadParam) method // Done reading meta return; - default: - // TODO: We probably want to store ANNO, TEXT, AUTH, COPY etc chunks as Metadata - // SHAM, ANNO, DEST, SPRT and more - IFFChunk generic = new GenericChunk(chunkId, length); + + case IFF.CHUNK_ANNO: + case IFF.CHUNK_AUTH: + case IFF.CHUNK_COPY: + case IFF.CHUNK_NAME: + case IFF.CHUNK_TEXT: + case IFF.CHUNK_UTF8: + GenericChunk generic = new GenericChunk(chunkId, length); generic.readChunk(imageInput); + meta.add(generic); // System.out.println(generic); break; + + case IFF.CHUNK_JUNK: + // Always skip junk chunks + default: + // TODO: SHAM, DEST, SPRT and more + // Everything else, we'll just skip + IFFChunk.skipData(imageInput, length, 0); + break; } } } + @Override public BufferedImage read(int pIndex, ImageReadParam pParam) throws IOException { init(pIndex); @@ -314,16 +329,26 @@ public class IFFImageReader extends ImageReaderBase { return result; } + @Override public int getWidth(int pIndex) throws IOException { init(pIndex); return header.width; } + @Override public int getHeight(int pIndex) throws IOException { init(pIndex); 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); + } + + @Override public Iterator getImageTypes(int pIndex) throws IOException { init(pIndex); @@ -363,12 +388,11 @@ public class IFFImageReader extends ImageReaderBase { if (colorMap != null) { IndexColorModel cm = colorMap.getIndexColorModel(header, isEHB()); specifier = ImageTypeSpecifiers.createFromIndexColorModel(cm); - break; } else { specifier = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY); - break; } + break; } // NOTE: HAM modes falls through, as they are converted to RGB case 24: @@ -786,7 +810,7 @@ public class IFFImageReader extends ImageReaderBase { } public static void main(String[] pArgs) throws IOException { - ImageReader reader = new IFFImageReader(); + ImageReader reader = new IFFImageReader(new IFFImageReaderSpi()); boolean scale = false; for (String arg : pArgs) { 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 ea581be9..92b2ec67 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 @@ -30,12 +30,13 @@ package com.twelvemonkeys.imageio.plugins.iff; -import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase; +import java.io.IOException; +import java.util.Locale; import javax.imageio.ImageReader; import javax.imageio.stream.ImageInputStream; -import java.io.IOException; -import java.util.Locale; + +import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase; /** * IFFImageReaderSpi @@ -52,6 +53,7 @@ public final class IFFImageReaderSpi extends ImageReaderSpiBase { super(new IFFProviderInfo()); } + @Override public boolean canDecodeInput(Object pSource) throws IOException { return pSource instanceof ImageInputStream && canDecode((ImageInputStream) pSource); } @@ -80,10 +82,12 @@ public final class IFFImageReaderSpi extends ImageReaderSpiBase { return false; } + @Override public ImageReader createReaderInstance(Object pExtension) throws IOException { return new IFFImageReader(this); } + @Override public String getDescription(Locale pLocale) { 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 beb0b38b..212520ce 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,22 +30,31 @@ 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 @@ -57,24 +66,23 @@ import java.io.OutputStream; * @see Wikipedia: IFF * @see Wikipedia: IFF ILBM */ -public class IFFImageWriter extends ImageWriterBase { +public final class IFFImageWriter extends ImageWriterBase { - public IFFImageWriter() { - this(null); - } - - protected IFFImageWriter(ImageWriterSpi pProvider) { + IFFImageWriter(ImageWriterSpi pProvider) { super(pProvider); } + @Override public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, ImageWriteParam param) { throw new UnsupportedOperationException("Method getDefaultImageMetadata not implemented");// TODO: Implement } + @Override public IIOMetadata convertImageMetadata(IIOMetadata inData, ImageTypeSpecifier imageType, ImageWriteParam param) { throw new UnsupportedOperationException("Method convertImageMetadata not implemented");// TODO: Implement } + @Override public void write(IIOMetadata pStreamMetadata, IIOImage pImage, ImageWriteParam pParam) throws IOException { assertOutput(); @@ -105,13 +113,9 @@ public class IFFImageWriter extends ImageWriterBase { // NOTE: This is much faster than imageOutput.write(pImageData.toByteArray()) // as the data array is not duplicated - OutputStream adapter = IIOUtil.createStreamAdapter(imageOutput); - try { + try (OutputStream adapter = IIOUtil.createStreamAdapter(imageOutput)) { pImageData.writeTo(adapter); } - finally { - adapter.close(); - } if (pImageData.size() % 2 == 0) { imageOutput.writeByte(0); // PAD @@ -180,7 +184,7 @@ public class IFFImageWriter extends ImageWriterBase { private void writeMeta(RenderedImage pImage, int pBodyLength) throws IOException { // Annotation ANNO chunk, 8 + annoData.length bytes - String annotation = "Written by " + getOriginatingProvider().getDescription(null) + " by " + getOriginatingProvider().getVendorName(); + 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(); 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 02d712fc..22e11630 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,12 +30,13 @@ package com.twelvemonkeys.imageio.plugins.iff; -import com.twelvemonkeys.imageio.spi.ImageWriterSpiBase; +import java.io.IOException; +import java.util.Locale; import javax.imageio.ImageTypeSpecifier; import javax.imageio.ImageWriter; -import java.io.IOException; -import java.util.Locale; + +import com.twelvemonkeys.imageio.spi.ImageWriterSpiBase; /** * IFFImageWriterSpi @@ -58,10 +59,12 @@ public class IFFImageWriterSpi extends ImageWriterSpiBase { return true; } + @Override public ImageWriter createWriterInstance(Object pExtension) throws IOException { return new IFFImageWriter(this); } + @Override public String getDescription(Locale pLocale) { 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/IFFUtil.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFUtil.java index bad6b4d8..ffa62a5f 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 @@ -47,7 +47,7 @@ package com.twelvemonkeys.imageio.plugins.iff; * @author Harald Kuhr (Java port) * @version $Id: IFFUtil.java,v 1.0 06.mar.2006 13:31:35 haku Exp$ */ -class IFFUtil { +final class IFFUtil { /** * Creates a rotation table 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 new file mode 100644 index 00000000..035a9cb6 --- /dev/null +++ b/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageMetadataTest.java @@ -0,0 +1,511 @@ +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; + +public class IFFImageMetadataTest { + @Test + public void testStandardFeatures() { + BMHDChunk header = 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()); + + // Standard metadata format + assertTrue(metadata.isStandardMetadataFormatSupported()); + Node root = metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName); + assertNotNull(root); + assertTrue(root instanceof IIOMetadataNode); + + // Other formats + assertNull(metadata.getNativeMetadataFormatName()); + assertNull(metadata.getExtraMetadataFormatNames()); + assertThrows(IllegalArgumentException.class, new ThrowingRunnable() { + @Override + public void run() { + metadata.getAsTree("com_foo_bar_1.0"); + } + }); + + // Read-only + assertTrue(metadata.isReadOnly()); + assertThrows(IllegalStateException.class, new ThrowingRunnable() { + @Override + public void run() throws Throwable { + metadata.mergeTree(IIOMetadataFormatImpl.standardMetadataFormatName, new IIOMetadataNode(IIOMetadataFormatImpl.standardMetadataFormatName)); + } + }); + } + + @Test + public void testStandardChromaGray() { + BMHDChunk header = 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()); + + IIOMetadataNode chroma = metadata.getStandardChromaNode(); + assertNotNull(chroma); + assertEquals("Chroma", chroma.getNodeName()); + assertEquals(3, chroma.getLength()); + + IIOMetadataNode colorSpaceType = (IIOMetadataNode) chroma.getFirstChild(); + assertEquals("ColorSpaceType", colorSpaceType.getNodeName()); + assertEquals("GRAY", colorSpaceType.getAttribute("name")); + + IIOMetadataNode numChannels = (IIOMetadataNode) colorSpaceType.getNextSibling(); + assertEquals("NumChannels", numChannels.getNodeName()); + assertEquals("1", numChannels.getAttribute("value")); + + IIOMetadataNode blackIsZero = (IIOMetadataNode) numChannels.getNextSibling(); + assertEquals("BlackIsZero", blackIsZero.getNodeName()); + assertEquals("TRUE", blackIsZero.getAttribute("value")); + + assertNull(blackIsZero.getNextSibling()); // No more children + } + + @Test + public void testStandardChromaRGB() { + BMHDChunk header = 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()); + + 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("3", numChannels.getAttribute("value")); + + IIOMetadataNode blackIsZero = (IIOMetadataNode) numChannels.getNextSibling(); + assertEquals("BlackIsZero", blackIsZero.getNodeName()); + assertEquals("TRUE", blackIsZero.getAttribute("value")); + + assertNull(blackIsZero.getNextSibling()); // No more children + } + + @Test + public void testStandardChromaPalette() { + BMHDChunk header = 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()); + + IIOMetadataNode chroma = metadata.getStandardChromaNode(); + assertNotNull(chroma); + assertEquals("Chroma", chroma.getNodeName()); + assertEquals(4, 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("3", numChannels.getAttribute("value")); + + IIOMetadataNode blackIsZero = (IIOMetadataNode) numChannels.getNextSibling(); + assertEquals("BlackIsZero", blackIsZero.getNodeName()); + assertEquals("TRUE", blackIsZero.getAttribute("value")); + + IIOMetadataNode palette = (IIOMetadataNode) blackIsZero.getNextSibling(); + assertEquals("Palette", palette.getNodeName()); + assertEquals(bw.length, palette.getLength()); + + for (int i = 0; i < palette.getLength(); i++) { + IIOMetadataNode item0 = (IIOMetadataNode) palette.item(i); + assertEquals("PaletteEntry", item0.getNodeName()); + assertEquals(String.valueOf(i), item0.getAttribute("index")); + String rgb = String.valueOf(bw[i] & 0xff); + assertEquals(rgb, item0.getAttribute("red")); + assertEquals(rgb, item0.getAttribute("green")); + assertEquals(rgb, item0.getAttribute("blue")); + } + + // TODO: BackgroundIndex == 1?? + } + + @Test + public void testStandardCompressionRLE() { + BMHDChunk header = 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()); + + IIOMetadataNode compression = metadata.getStandardCompressionNode(); + assertNotNull(compression); + assertEquals("Compression", compression.getNodeName()); + assertEquals(2, compression.getLength()); + + IIOMetadataNode compressionTypeName = (IIOMetadataNode) compression.getFirstChild(); + assertEquals("CompressionTypeName", compressionTypeName.getNodeName()); + assertEquals("RLE", compressionTypeName.getAttribute("value")); + + IIOMetadataNode lossless = (IIOMetadataNode) compressionTypeName.getNextSibling(); + assertEquals("Lossless", lossless.getNodeName()); + assertEquals("TRUE", lossless.getAttribute("value")); + + assertNull(lossless.getNextSibling()); // No more children + } + + @Test + public void testStandardCompressionNone() { + BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_NONE, 0); + + IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.emptyList()); + + 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); + + IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.emptyList()); + + IIOMetadataNode data = metadata.getStandardDataNode(); + assertNotNull(data); + assertEquals("Data", data.getNodeName()); + assertEquals(3, data.getLength()); + + IIOMetadataNode planarConfiguration = (IIOMetadataNode) data.getFirstChild(); + assertEquals("PlanarConfiguration", planarConfiguration.getNodeName()); + assertEquals("PlaneInterleaved", planarConfiguration.getAttribute("value")); + + IIOMetadataNode sampleFomat = (IIOMetadataNode) planarConfiguration.getNextSibling(); + assertEquals("SampleFormat", sampleFomat.getNodeName()); + assertEquals("UnsignedIntegral", sampleFomat.getAttribute("value")); + + IIOMetadataNode bitsPerSample = (IIOMetadataNode) sampleFomat.getNextSibling(); + assertEquals("BitsPerSample", bitsPerSample.getNodeName()); + assertEquals("8", bitsPerSample.getAttribute("value")); + + assertNull(bitsPerSample.getNextSibling()); // No more children + } + + @Test + public void testStandardDataILBM_RGB() { + BMHDChunk header = 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()); + + IIOMetadataNode data = metadata.getStandardDataNode(); + assertNotNull(data); + assertEquals("Data", data.getNodeName()); + assertEquals(3, data.getLength()); + + IIOMetadataNode planarConfiguration = (IIOMetadataNode) data.getFirstChild(); + assertEquals("PlanarConfiguration", planarConfiguration.getNodeName()); + assertEquals("PlaneInterleaved", planarConfiguration.getAttribute("value")); + + IIOMetadataNode sampleFomat = (IIOMetadataNode) planarConfiguration.getNextSibling(); + assertEquals("SampleFormat", sampleFomat.getNodeName()); + assertEquals("UnsignedIntegral", sampleFomat.getAttribute("value")); + + IIOMetadataNode bitsPerSample = (IIOMetadataNode) sampleFomat.getNextSibling(); + assertEquals("BitsPerSample", bitsPerSample.getNodeName()); + assertEquals("8 8 8", bitsPerSample.getAttribute("value")); + + assertNull(bitsPerSample.getNextSibling()); // No more children + } + + @Test + public void testStandardDataILBM_RGBA() { + BMHDChunk header = 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()); + + IIOMetadataNode data = metadata.getStandardDataNode(); + assertNotNull(data); + assertEquals("Data", data.getNodeName()); + assertEquals(3, data.getLength()); + + IIOMetadataNode planarConfiguration = (IIOMetadataNode) data.getFirstChild(); + assertEquals("PlanarConfiguration", planarConfiguration.getNodeName()); + assertEquals("PlaneInterleaved", planarConfiguration.getAttribute("value")); + + IIOMetadataNode sampleFomat = (IIOMetadataNode) planarConfiguration.getNextSibling(); + assertEquals("SampleFormat", sampleFomat.getNodeName()); + assertEquals("UnsignedIntegral", sampleFomat.getAttribute("value")); + + IIOMetadataNode bitsPerSample = (IIOMetadataNode) sampleFomat.getNextSibling(); + assertEquals("BitsPerSample", bitsPerSample.getNodeName()); + assertEquals("8 8 8 8", bitsPerSample.getAttribute("value")); + + assertNull(bitsPerSample.getNextSibling()); // No more children + } + + @Test + public void testStandardDataILBM_Palette() { + for (int i = 1; i <= 8; i++) { + BMHDChunk header = 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()); + + IIOMetadataNode data = metadata.getStandardDataNode(); + assertNotNull(data); + assertEquals("Data", data.getNodeName()); + assertEquals(3, data.getLength()); + + IIOMetadataNode planarConfiguration = (IIOMetadataNode) data.getFirstChild(); + assertEquals("PlanarConfiguration", planarConfiguration.getNodeName()); + assertEquals("PlaneInterleaved", planarConfiguration.getAttribute("value")); + + IIOMetadataNode sampleFomat = (IIOMetadataNode) planarConfiguration.getNextSibling(); + assertEquals("SampleFormat", sampleFomat.getNodeName()); + assertEquals("Index", sampleFomat.getAttribute("value")); + + IIOMetadataNode bitsPerSample = (IIOMetadataNode) sampleFomat.getNextSibling(); + assertEquals("BitsPerSample", bitsPerSample.getNodeName()); + assertEquals(String.valueOf(i), bitsPerSample.getAttribute("value")); + + assertNull(bitsPerSample.getNextSibling()); // No more children + } + } + + @Test + public void testStandardDataPBM_Gray() { + BMHDChunk header = 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()); + + 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 sampleFomat = (IIOMetadataNode) planarConfiguration.getNextSibling(); + assertEquals("SampleFormat", sampleFomat.getNodeName()); + assertEquals("UnsignedIntegral", sampleFomat.getAttribute("value")); + + IIOMetadataNode bitsPerSample = (IIOMetadataNode) sampleFomat.getNextSibling(); + assertEquals("BitsPerSample", bitsPerSample.getNodeName()); + assertEquals("8", bitsPerSample.getAttribute("value")); + + assertNull(bitsPerSample.getNextSibling()); // No more children + } + + @Test + public void testStandardDataPBM_RGB() { + BMHDChunk header = 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()); + + 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 sampleFomat = (IIOMetadataNode) planarConfiguration.getNextSibling(); + assertEquals("SampleFormat", sampleFomat.getNodeName()); + assertEquals("UnsignedIntegral", sampleFomat.getAttribute("value")); + + IIOMetadataNode bitsPerSample = (IIOMetadataNode) sampleFomat.getNextSibling(); + assertEquals("BitsPerSample", bitsPerSample.getNodeName()); + assertEquals("8 8 8", bitsPerSample.getAttribute("value")); + + assertNull(bitsPerSample.getNextSibling()); // No more children + } + + + @Test + public void testStandardDimensionNoViewport() { + BMHDChunk header = 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()); + + 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); + + IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, new CAMGChunk(4), Collections.emptyList()); + + 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")); + + 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); + CAMGChunk viewPort = new CAMGChunk(4); + viewPort.camg = 0x8000; + + IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, viewPort, Collections.emptyList()); + + IIOMetadataNode dimension = metadata.getStandardDimensionNode(); + assertNotNull(dimension); + assertEquals("Dimension", dimension.getNodeName()); + assertEquals(1, dimension.getLength()); + + IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild(); + assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName()); + assertEquals("2.0", pixelAspectRatio.getAttribute("value")); + + assertNull(pixelAspectRatio.getNextSibling()); // No more children + } + + @Test + public void testStandardDimensionInterlaced() { + BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + + CAMGChunk viewPort = new CAMGChunk(4); + viewPort.camg = 0x4; + + IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, viewPort, Collections.emptyList()); + + IIOMetadataNode dimension = metadata.getStandardDimensionNode(); + assertNotNull(dimension); + assertEquals("Dimension", dimension.getNodeName()); + assertEquals(1, dimension.getLength()); + + IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild(); + assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName()); + assertEquals("0.5", pixelAspectRatio.getAttribute("value")); + + assertNull(pixelAspectRatio.getNextSibling()); // No more children + } + + @Test + public void testStandardDimensionHiresInterlaced() { + BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + + CAMGChunk viewPort = new CAMGChunk(4); + viewPort.camg = 0x8004; + + IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, viewPort, Collections.emptyList()); + + 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")); + + assertNull(pixelAspectRatio.getNextSibling()); // No more children + } + + @Test + public void testStandardDocument() { + BMHDChunk header = 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()); + + IIOMetadataNode document = metadata.getStandardDocumentNode(); + assertNotNull(document); + assertEquals("Document", document.getNodeName()); + assertEquals(1, document.getLength()); + + IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) document.getFirstChild(); + assertEquals("FormatVersion", pixelAspectRatio.getNodeName()); + assertEquals("1.0", pixelAspectRatio.getAttribute("value")); + + assertNull(pixelAspectRatio.getNextSibling()); // No more children + } + + @Test + public void testStandardText() { + BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0); + + 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); + + IIOMetadataNode text = metadata.getStandardTextNode(); + assertNotNull(text); + assertEquals("Text", text.getNodeName()); + assertEquals(texts.length, text.getLength()); + + 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(texts[i], textEntry.getAttribute("value")); + } + } + + @Test + public void testStandardTransparencyRGB() { + BMHDChunk header = 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()); + + 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); + + IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.emptyList()); + + IIOMetadataNode transparency = metadata.getStandardTransparencyNode(); + assertNotNull(transparency); + assertEquals("Transparency", transparency.getNodeName()); + assertEquals(1, transparency.getLength()); + + IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) transparency.getFirstChild(); + assertEquals("Alpha", pixelAspectRatio.getNodeName()); + assertEquals("nonpremultiplied", pixelAspectRatio.getAttribute("value")); + + assertNull(pixelAspectRatio.getNextSibling()); // No more children + } + + @Test + public void testStandardTransparencyPalette() { + BMHDChunk header = 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()); + + IIOMetadataNode transparency = metadata.getStandardTransparencyNode(); + assertNotNull(transparency); + assertEquals("Transparency", transparency.getNodeName()); + assertEquals(1, transparency.getLength()); + + IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) transparency.getFirstChild(); + assertEquals("TransparentIndex", pixelAspectRatio.getNodeName()); + assertEquals("1", pixelAspectRatio.getAttribute("value")); + + assertNull(pixelAspectRatio.getNextSibling()); // No more children + } +} \ No newline at end of file