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 eeb8d19c..ed79b5bf 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 @@ -37,6 +37,7 @@ import java.awt.image.WritableRaster; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; +import java.util.Arrays; /** * CMAPChunk @@ -53,45 +54,25 @@ final class CMAPChunk extends IFFChunk { // // typedef ColorRegister ColorMap[n]; /* size = 3n bytes */ - byte[] reds; byte[] greens; byte[] blues; - boolean ehb; - - final private BMHDChunk header; - final private CAMGChunk camg; private IndexColorModel model; - protected CMAPChunk(int pChunkLength, BMHDChunk pHeader, CAMGChunk pCamg) { + protected CMAPChunk(final int pChunkLength) { super(IFF.CHUNK_CMAP, pChunkLength); - header = pHeader; - camg = pCamg; } - public CMAPChunk(IndexColorModel pModel) { + public CMAPChunk(final IndexColorModel pModel) { super(IFF.CHUNK_CMAP, pModel.getMapSize() * 3); model = pModel; - header = null; - camg = null; } - void readChunk(DataInput pInput) throws IOException { + void readChunk(final DataInput pInput) throws IOException { int numColors = chunkLength / 3; - int paletteSize = numColors; - boolean isEHB = camg != null && camg.isEHB(); - if (isEHB) { - if (numColors == 32) { - paletteSize = 64; - } - else if (numColors != 64) { - throw new IIOException("Unknown number of colors for EHB: " + numColors); - } - } - - reds = new byte[paletteSize]; + reds = new byte[numColors]; greens = reds.clone(); blues = reds.clone(); @@ -101,38 +82,21 @@ final class CMAPChunk extends IFFChunk { blues[i] = pInput.readByte(); } - if (isEHB && numColors == 32) { - // Create the half-brite colors - for (int i = 0; i < numColors; i++) { - reds[i + numColors] = (byte) ((reds[i] & 0xff) / 2); - greens[i + numColors] = (byte) ((greens[i] & 0xff) / 2); - blues[i + numColors] = (byte) ((blues[i] & 0xff) / 2); - } - } - // TODO: When reading in a CMAP for 8-bit-per-gun display or // manipulation, you may want to assume that any CMAP which has 0 values // for the low bits of all guns for all registers was stored shifted // rather than scaled, and provide your own scaling. // Use defaults if the color map is absent or has fewer color registers // than you need. Ignore any extra color registers. - // R8 := (Rn x 255 ) / maxColor // All chunks are WORD aligned (even sized), may need to read pad... if (chunkLength % 2 != 0) { pInput.readByte(); } - - // TODO: Bitmask transparency - // 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 trans = header.maskType == BMHDChunk.MASK_TRANSPARENT_COLOR ? header.transparentIndex : -1; - model = new InverseColorMapIndexColorModel(header.bitplanes, reds.length, reds, greens, blues, trans); } - void writeChunk(DataOutput pOutput) throws IOException { + void writeChunk(final DataOutput pOutput) throws IOException { pOutput.writeInt(chunkId); pOutput.writeInt(chunkLength); @@ -153,15 +117,12 @@ final class CMAPChunk extends IFFChunk { return super.toString() + " {colorMap=" + model + "}"; } - IndexColorModel getIndexColorModel() { - return model; - } - - public BufferedImage createPaletteImage() { + BufferedImage createPaletteImage(final BMHDChunk header, boolean isEHB) throws IIOException { // Create a 1 x colors.length image - IndexColorModel cm = getIndexColorModel(); + 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); @@ -169,4 +130,38 @@ final class CMAPChunk extends IFFChunk { return new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null); } + + public IndexColorModel getIndexColorModel(final BMHDChunk header, boolean isEHB) throws IIOException { + if (model == null) { + int numColors = reds.length; // All arrays are same size + + if (isEHB) { + if (numColors == 32) { + reds = Arrays.copyOf(reds, numColors * 2); + blues = Arrays.copyOf(blues, numColors * 2); + greens = Arrays.copyOf(greens, numColors * 2); + } + else if (numColors != 64) { + throw new IIOException("Unknown number of colors for EHB: " + numColors); + } + + // Create the half-brite colors + // We do this regardless of the colors read, as the color map may contain trash values + for (int i = 0; i < 32; i++) { + reds[i + 32] = (byte) ((reds[i] & 0xff) / 2); + greens[i + 32] = (byte) ((greens[i] & 0xff) / 2); + blues[i + 32] = (byte) ((blues[i] & 0xff) / 2); + } + } + + // TODO: Bitmask transparency + // 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; + model = new InverseColorMapIndexColorModel(header.bitplanes, reds.length, reds, greens, blues, transparent); + } + + return model; + } } 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 06347ac2..3a15cd67 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 @@ -183,7 +183,8 @@ public class IFFImageReader extends ImageReaderBase { if (colorMap != null) { throw new IIOException("Multiple CMAP chunks not allowed"); } - colorMap = new CMAPChunk(length, header, viewPort); + + colorMap = new CMAPChunk(length); colorMap.readChunk(imageInput); //System.out.println(colorMap); @@ -204,7 +205,7 @@ public class IFFImageReader extends ImageReaderBase { viewPort = new CAMGChunk(length); viewPort.readChunk(imageInput); - //System.out.println(viewPort); +// System.out.println(viewPort); break; case IFF.CHUNK_PCHG: if (paletteChange instanceof PCHGChunk) { @@ -217,7 +218,7 @@ public class IFFImageReader extends ImageReaderBase { // Always prefer PCHG style palette changes paletteChange = pchg; -// System.out.println(paletteChange); +// System.out.println(pchg); break; case IFF.CHUNK_SHAM: @@ -233,7 +234,7 @@ public class IFFImageReader extends ImageReaderBase { paletteChange = sham; } -// System.out.println(paletteChange); +// System.out.println(sham); break; case IFF.CHUNK_CTBL: @@ -249,7 +250,7 @@ public class IFFImageReader extends ImageReaderBase { paletteChange = ctbl; } -// System.out.println(paletteChange); +// System.out.println(ctbl); break; case IFF.CHUNK_JUNK: @@ -297,7 +298,7 @@ public class IFFImageReader extends ImageReaderBase { //System.out.println(colorMap); if (colorMap != null) { //System.out.println("Creating palette!"); - image = colorMap.createPaletteImage(); + image = colorMap.createPaletteImage(header, isEHB()); } } @@ -355,7 +356,7 @@ public class IFFImageReader extends ImageReaderBase { // May be HAM8 if (!isConvertToRGB()) { if (colorMap != null) { - IndexColorModel cm = colorMap.getIndexColorModel(); + IndexColorModel cm = colorMap.getIndexColorModel(header, isEHB()); specifier = IndexedImageTypeSpecifier.createFromIndexColorModel(cm); break; } @@ -389,7 +390,7 @@ public class IFFImageReader extends ImageReaderBase { // NOTE: colorMap may be null for 8 bit (gray), 24 bit or 32 bit only if (colorMap != null) { - IndexColorModel cm = colorMap.getIndexColorModel(); + IndexColorModel cm = colorMap.getIndexColorModel(header, isEHB()); readIndexed(pParam, imageInput, cm); } else { @@ -765,6 +766,10 @@ public class IFFImageReader extends ImageReaderBase { return paletteChange != null; } + private boolean isEHB() { + return viewPort != null && viewPort.isEHB(); + } + private boolean isHAM() { return viewPort != null && viewPort.isHAM(); } @@ -782,32 +787,41 @@ public class IFFImageReader extends ImageReaderBase { scale = true; continue; } - ImageInputStream input = new BufferedImageInputStream(ImageIO.createImageInputStream(new File(arg))); - boolean canRead = reader.getOriginatingProvider().canDecodeInput(input); - System.out.println("Can read: " + canRead); - - if (canRead) { - reader.setInput(input); - ImageReadParam param = reader.getDefaultReadParam(); -// param.setSourceRegion(new Rectangle(0, 0, 160, 200)); -// param.setSourceRegion(new Rectangle(160, 200, 160, 200)); -// param.setSourceRegion(new Rectangle(80, 100, 160, 200)); -// param.setDestinationOffset(new Point(80, 100)); -// param.setSourceSubsampling(3, 3, 0, 0); -// param.setSourceBands(new int[]{0, 1, 2}); -// param.setDestinationBands(new int[]{1, 0, 2}); - BufferedImage image = reader.read(0, param); - System.out.println("image = " + image); - - if (scale) { - image = new ResampleOp(image.getWidth() / 2, image.getHeight(), ResampleOp.FILTER_LANCZOS).filter(image, null); -// image = ImageUtil.createResampled(image, image.getWidth(), image.getHeight() * 2, Image.SCALE_FAST); - } - - showIt(image, arg); + File file = new File(arg); + if (!file.isFile()) { + continue; } + try { + ImageInputStream input = new BufferedImageInputStream(ImageIO.createImageInputStream(file)); + boolean canRead = reader.getOriginatingProvider().canDecodeInput(input); + + if (canRead) { + reader.setInput(input); + ImageReadParam param = reader.getDefaultReadParam(); + // param.setSourceRegion(new Rectangle(0, 0, 160, 200)); + // param.setSourceRegion(new Rectangle(160, 200, 160, 200)); + // param.setSourceRegion(new Rectangle(80, 100, 160, 200)); + // param.setDestinationOffset(new Point(80, 100)); + // param.setSourceSubsampling(3, 3, 0, 0); + // param.setSourceBands(new int[]{0, 1, 2}); + // param.setDestinationBands(new int[]{1, 0, 2}); + BufferedImage image = reader.read(0, param); + System.out.println("image = " + image); + + if (scale) { + image = new ResampleOp(image.getWidth() / 2, image.getHeight(), ResampleOp.FILTER_LANCZOS).filter(image, null); + // image = ImageUtil.createResampled(image, image.getWidth(), image.getHeight() * 2, Image.SCALE_FAST); + } + + showIt(image, arg); + } + } + catch (IOException e) { + System.err.println("Error reading file: " + file); + e.printStackTrace(); + } } } } 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 9526c9f4..1f611f14 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 @@ -29,12 +29,22 @@ package com.twelvemonkeys.imageio.plugins.iff; import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase; +import org.junit.Test; +import javax.imageio.ImageIO; import javax.imageio.spi.ImageReaderSpi; import java.awt.*; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.IndexColorModel; +import java.io.IOException; import java.util.Arrays; import java.util.List; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + /** * IFFImageReaderTestCase * @@ -46,21 +56,25 @@ public class IFFImageReaderTest extends ImageReaderAbstractTestCase getTestData() { return Arrays.asList( // 32 bit - Ok - new TestData(getClassLoaderResource("/iff/test.iff"), new Dimension(300, 200)), // 32 bit + new TestData(getClassLoaderResource("/iff/test.iff"), new Dimension(300, 200)), // 24 bit - Ok - new TestData(getClassLoaderResource("/iff/survivor.iff"), new Dimension(800, 600)), // 24 bit + new TestData(getClassLoaderResource("/iff/survivor.iff"), new Dimension(800, 600)), // HAM6 - Ok (a lot of visual "fringe", would be interesting to see on a real HAM display) - new TestData(getClassLoaderResource("/iff/A4000T_HAM6.IFF"), new Dimension(320, 512)), // ham6 + new TestData(getClassLoaderResource("/iff/A4000T_HAM6.IFF"), new Dimension(320, 512)), // HAM8 - Ok - new TestData(getClassLoaderResource("/iff/A4000T_HAM8.IFF"), new Dimension(628, 512)), // ham8 + new TestData(getClassLoaderResource("/iff/A4000T_HAM8.IFF"), new Dimension(628, 512)), // 8 color indexed - Ok - new TestData(getClassLoaderResource("/iff/AmigaAmiga.iff"), new Dimension(200, 150)), // 8 color - // HAM6 - Ok - new TestData(getClassLoaderResource("/iff/Abyss.iff"), new Dimension(320, 400)), - // PBM, indexed - Ok - new TestData(getClassLoaderResource("/iff/ASH.PBM"), new Dimension(320, 240)), + new TestData(getClassLoaderResource("/iff/AmigaAmiga.iff"), new Dimension(200, 150)), + // 16 color indexed - Ok + new TestData(getClassLoaderResource("/iff/Lion.iff"), new Dimension(704, 480)), + // 32 color indexed - Ok + new TestData(getClassLoaderResource("/iff/GoldPorsche.iff"), new Dimension(320, 200)), + // 64 color indexed EHB - Ok + new TestData(getClassLoaderResource("/iff/Bryce.iff"), new Dimension(320, 200)), // 256 color indexed - Ok new TestData(getClassLoaderResource("/iff/IKKEGOD.iff"), new Dimension(640, 256)), + // PBM, indexed - Ok + new TestData(getClassLoaderResource("/iff/ASH.PBM"), new Dimension(320, 240)), // 16 color indexed, multi palette (PCHG) - Ok new TestData(getClassLoaderResource("/iff/Manhattan.PCHG"), new Dimension(704, 440)), // 16 color indexed, multi palette (PCHG + SHAM) - Ok @@ -87,4 +101,37 @@ public class IFFImageReaderTest extends ImageReaderAbstractTestCase getMIMETypes() { return Arrays.asList("image/iff", "image/x-iff"); } + + // Regression tests + + @Test + public void testEHBColors() throws IOException { + IFFImageReader reader = createReader(); + reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/iff/Bryce.iff"))); + + BufferedImage image = reader.read(0); + assertEquals(BufferedImage.TYPE_BYTE_INDEXED, image.getType()); + + ColorModel colorModel = image.getColorModel(); + assertNotNull(colorModel); + assertTrue(colorModel instanceof IndexColorModel); + + IndexColorModel indexColorModel = (IndexColorModel) colorModel; + + assertEquals(64, indexColorModel.getMapSize()); + + byte[] reds = new byte[indexColorModel.getMapSize()]; + indexColorModel.getReds(reds); + byte[] blues = new byte[indexColorModel.getMapSize()]; + indexColorModel.getBlues(blues); + byte[] greens = new byte[indexColorModel.getMapSize()]; + indexColorModel.getGreens(greens); + + 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); + } + } } diff --git a/imageio/imageio-iff/src/test/resources/iff/Abyss.iff b/imageio/imageio-iff/src/test/resources/iff/Abyss.iff deleted file mode 100755 index 4540de22..00000000 Binary files a/imageio/imageio-iff/src/test/resources/iff/Abyss.iff and /dev/null differ diff --git a/imageio/imageio-iff/src/test/resources/iff/AmigaBig.iff b/imageio/imageio-iff/src/test/resources/iff/AmigaBig.iff deleted file mode 100755 index 0ee086e5..00000000 Binary files a/imageio/imageio-iff/src/test/resources/iff/AmigaBig.iff and /dev/null differ diff --git a/imageio/imageio-iff/src/test/resources/iff/Bryce.iff b/imageio/imageio-iff/src/test/resources/iff/Bryce.iff new file mode 100644 index 00000000..0be90771 Binary files /dev/null and b/imageio/imageio-iff/src/test/resources/iff/Bryce.iff differ diff --git a/imageio/imageio-iff/src/test/resources/iff/GoldPorsche.iff b/imageio/imageio-iff/src/test/resources/iff/GoldPorsche.iff new file mode 100644 index 00000000..01c77b18 Binary files /dev/null and b/imageio/imageio-iff/src/test/resources/iff/GoldPorsche.iff differ diff --git a/imageio/imageio-iff/src/test/resources/iff/Lion.iff b/imageio/imageio-iff/src/test/resources/iff/Lion.iff new file mode 100644 index 00000000..2a0bfef2 Binary files /dev/null and b/imageio/imageio-iff/src/test/resources/iff/Lion.iff differ