diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/DiscreteAlphaIndexColorModel.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/DiscreteAlphaIndexColorModel.java new file mode 100644 index 00000000..db3a1bd7 --- /dev/null +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/DiscreteAlphaIndexColorModel.java @@ -0,0 +1,134 @@ +package com.twelvemonkeys.imageio.color; + +import java.awt.*; +import java.awt.image.*; + +import static com.twelvemonkeys.lang.Validate.notNull; + +/** + * This class represents a hybrid between an {@link IndexColorModel} and a {@link ComponentColorModel}, + * having both a color map and a full, discrete alpha channel. + * The color map entries are assumed to be fully opaque and should have no transparent index. + *

+ * ColorSpace will always be the default sRGB color space (as with {@code IndexColorModel}). + *

+ * Component order is always P, A, where P is a palette index, and A is the alpha value. + * + * @see IndexColorModel + * @see ComponentColorModel + */ +public final class DiscreteAlphaIndexColorModel extends ColorModel { + // Our IndexColorModel delegate + private final IndexColorModel icm; + + /** + * Creates a {@code DiscreteAlphaIndexColorModel}, delegating color map look-ups + * to the given {@code IndexColorModel}. + * + * @param icm The {@code IndexColorModel} delegate. Color map entries are assumed to be + * fully opaque, any transparency or transparent index will be ignored. + */ + public DiscreteAlphaIndexColorModel(final IndexColorModel icm) { + super( + notNull(icm, "IndexColorModel").getPixelSize() * 2, + new int[] {icm.getPixelSize(), icm.getPixelSize(), icm.getPixelSize(), icm.getPixelSize()}, + icm.getColorSpace(), true, false, Transparency.TRANSLUCENT, icm.getTransferType() + ); + + this.icm = icm; + } + + @Override + public final int getRed(final int pixel) { + return icm.getRed(pixel); + } + + @Override + public final int getGreen(final int pixel) { + return icm.getGreen(pixel); + } + + @Override + public final int getBlue(final int pixel) { + return icm.getBlue(pixel); + } + + @Override + public final int getAlpha(final int pixel) { + return (int) ((((float) pixel) / ((1 << getComponentSize(3))-1)) * 255.0f + 0.5f); + } + + private int getSample(final Object inData, final int index) { + int pixel; + + switch (transferType) { + case DataBuffer.TYPE_BYTE: + byte bdata[] = (byte[]) inData; + pixel = bdata[index] & 0xff; + break; + case DataBuffer.TYPE_USHORT: + short sdata[] = (short[]) inData; + pixel = sdata[index] & 0xffff; + break; + case DataBuffer.TYPE_INT: + int idata[] = (int[]) inData; + pixel = idata[index]; + break; + default: + throw new UnsupportedOperationException("This method has not been implemented for transferType " + transferType); + } + + return pixel; + } + + @Override + public final int getRed(final Object inData) { + return getRed(getSample(inData, 0)); + } + + @Override + public final int getGreen(final Object inData) { + return getGreen(getSample(inData, 0)); + } + + @Override + public final int getBlue(final Object inData) { + return getBlue(getSample(inData, 0)); + } + + @Override + public final int getAlpha(final Object inData) { + return getAlpha(getSample(inData, 1)); + } + + @Override + public final SampleModel createCompatibleSampleModel(final int w, final int h) { + return new PixelInterleavedSampleModel(transferType, w, h, 2, w * 2, new int[] {0, 1}); + } + + @Override + public final boolean isCompatibleSampleModel(final SampleModel sm) { + return sm instanceof PixelInterleavedSampleModel && sm.getNumBands() == 2; + } + + @Override + public final WritableRaster createCompatibleWritableRaster(final int w, final int h) { + return Raster.createWritableRaster(createCompatibleSampleModel(w, h), new Point(0, 0)); + } + + @Override + public final boolean isCompatibleRaster(final Raster raster) { + int size = raster.getSampleModel().getSampleSize(0); + return ((raster.getTransferType() == transferType) && + (raster.getNumBands() == 2) && ((1 << size) >= icm.getMapSize())); + } + + public String toString() { + return "DiscreteAlphaIndexColorModel: #pixelBits = " + pixel_bits + + " numComponents = " + getNumComponents() + + " color space = " + getColorSpace() + + " transparency = " + getTransparency() + + " has alpha = " + hasAlpha() + + " isAlphaPre = " + isAlphaPremultiplied(); + } +} diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/ImageTypeSpecifiers.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/ImageTypeSpecifiers.java index 05cf4468..4c1d9308 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/ImageTypeSpecifiers.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/ImageTypeSpecifiers.java @@ -28,6 +28,7 @@ package com.twelvemonkeys.imageio.util; +import com.twelvemonkeys.imageio.color.DiscreteAlphaIndexColorModel; import com.twelvemonkeys.lang.Validate; import javax.imageio.ImageTypeSpecifier; @@ -167,4 +168,9 @@ public final class ImageTypeSpecifiers { public static ImageTypeSpecifier createFromIndexColorModel(final IndexColorModel pColorModel) { return new IndexedImageTypeSpecifier(pColorModel); } + + public static ImageTypeSpecifier createDiscreteAlphaIndexedFromIndexColorModel(final IndexColorModel pColorModel) { + ColorModel colorModel = new DiscreteAlphaIndexColorModel(pColorModel); + return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1)); + } } diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/DiscreteAlphaIndexColorModelTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/DiscreteAlphaIndexColorModelTest.java new file mode 100644 index 00000000..72d42186 --- /dev/null +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/DiscreteAlphaIndexColorModelTest.java @@ -0,0 +1,184 @@ +package com.twelvemonkeys.imageio.color; + +import org.hamcrest.CoreMatchers; +import org.junit.Test; + +import java.awt.image.*; + +import static org.junit.Assert.*; + +public class DiscreteAlphaIndexColorModelTest { + + @Test(expected = IllegalArgumentException.class) + public void testCreateNull() { + new DiscreteAlphaIndexColorModel(null); + } + + @Test + public void testCreateByte() { + int[] colors = createIntLut(1 << 8); + IndexColorModel colorModel = new IndexColorModel(8, colors.length, colors, 0, false, -1, DataBuffer.TYPE_BYTE); + + new DiscreteAlphaIndexColorModel(colorModel); + } + + @Test + public void testCreateUShort() { + int[] colors = createIntLut(1 << 16); + IndexColorModel colorModel = new IndexColorModel(16, colors.length, colors, 0, false, -1, DataBuffer.TYPE_USHORT); + + new DiscreteAlphaIndexColorModel(colorModel); + } + + @Test + public void testGetRed() { + int[] colors = createIntLut(1 << 8); + colors[0] = 0x336699; + IndexColorModel icm = new IndexColorModel(8, colors.length, colors, 0, false, -1, DataBuffer.TYPE_BYTE); + + DiscreteAlphaIndexColorModel colorModel = new DiscreteAlphaIndexColorModel(icm); + + assertEquals(0x33, colorModel.getRed(0)); + assertEquals(0x33, colorModel.getRed(new byte[] {0x00, 0x45})); + + for (int i = 1; i < colors.length; i++) { + assertEquals(i, colorModel.getRed(i)); + assertEquals(i, colorModel.getRed(new byte[] {(byte) i, (byte) 0xff})); + } + } + + @Test + public void testGetGreen() { + int[] colors = createIntLut(1 << 8); + colors[0] = 0x336699; + IndexColorModel icm = new IndexColorModel(8, colors.length, colors, 0, false, -1, DataBuffer.TYPE_BYTE); + + DiscreteAlphaIndexColorModel colorModel = new DiscreteAlphaIndexColorModel(icm); + + assertEquals(0x66, colorModel.getGreen(0)); + assertEquals(0x66, colorModel.getGreen(new byte[] {0x00, 0x45})); + + for (int i = 1; i < colors.length; i++) { + assertEquals(i, colorModel.getGreen(i)); + assertEquals(i, colorModel.getGreen(new byte[] {(byte) i, (byte) 0xff})); + } + } + + @Test + public void testGetBlue() { + int[] colors = createIntLut(1 << 8); + colors[0] = 0x336699; + IndexColorModel icm = new IndexColorModel(8, colors.length, colors, 0, false, -1, DataBuffer.TYPE_BYTE); + + DiscreteAlphaIndexColorModel colorModel = new DiscreteAlphaIndexColorModel(icm); + + assertEquals(0x99, colorModel.getBlue(0)); + assertEquals(0x99, colorModel.getBlue(new byte[] {0x00, 0x45})); + + for (int i = 1; i < colors.length; i++) { + assertEquals(i, colorModel.getBlue(i)); + assertEquals(i, colorModel.getBlue(new byte[] {(byte) i, (byte) 0xff})); + } + } + + @Test + public void testGetAlpha() { + int[] colors = createIntLut(1 << 8); + IndexColorModel icm = new IndexColorModel(8, colors.length, colors, 0, false, -1, DataBuffer.TYPE_BYTE); + + DiscreteAlphaIndexColorModel colorModel = new DiscreteAlphaIndexColorModel(icm); + + assertEquals(0x45, colorModel.getAlpha(0x45)); + assertEquals(0x45, colorModel.getAlpha(new byte[] {0x01, 0x45})); + + for (int i = 1; i < colors.length; i++) { + assertEquals(i, colorModel.getAlpha(i)); + assertEquals(i, colorModel.getAlpha(new byte[]{(byte) 0xff, (byte) i})); + } + } + + @Test + public void testGetAlphaUShort() { + int[] colors = createIntLut(1 << 16); + colors[1] = 0x336699; + IndexColorModel icm = new IndexColorModel(16, colors.length, colors, 0, false, -1, DataBuffer.TYPE_USHORT); + + DiscreteAlphaIndexColorModel colorModel = new DiscreteAlphaIndexColorModel(icm); + + assertEquals(0x45, colorModel.getAlpha(0x4500)); + assertEquals(0x45, colorModel.getAlpha(0x457F)); + + assertEquals(0x46, colorModel.getAlpha(0x45C6)); // Hmm.. This seems rather odd.. I would assume the limit should be 0x4580 + assertEquals(0x46, colorModel.getAlpha(0x45FF)); + + assertEquals(0x45, colorModel.getAlpha(new short[] {0x01, 0x4500})); + assertEquals(0x45, colorModel.getAlpha(new short[] {0x02, 0x457F})); + + assertEquals(0x46, colorModel.getAlpha(new short[] {0x03, 0x45C6})); + assertEquals(0x46, colorModel.getAlpha(new short[] {0x04, 0x45FF})); + } + + @Test + public void testCreateCompatibleSampleModel() { + int[] colors = createIntLut(1 << 8); + IndexColorModel icm = new IndexColorModel(8, colors.length, colors, 0, false, -1, DataBuffer.TYPE_BYTE); + + ColorModel colorModel = new DiscreteAlphaIndexColorModel(icm); + SampleModel sampleModel = colorModel.createCompatibleSampleModel(3, 2); + + assertNotNull(sampleModel); + + assertEquals(3, sampleModel.getWidth()); + assertEquals(2, sampleModel.getHeight()); + + assertTrue(colorModel.isCompatibleSampleModel(sampleModel)); + assertThat(sampleModel, CoreMatchers.is(PixelInterleavedSampleModel.class)); + assertThat(sampleModel.getDataType(), CoreMatchers.equalTo(DataBuffer.TYPE_BYTE)); + } + + @Test + public void testCreateCompatibleSampleModelUShort() { + int[] colors = createIntLut(1 << 8); + IndexColorModel icm = new IndexColorModel(8, colors.length, colors, 0, false, -1, DataBuffer.TYPE_USHORT); + + ColorModel colorModel = new DiscreteAlphaIndexColorModel(icm); + SampleModel sampleModel = colorModel.createCompatibleSampleModel(3, 2); + + assertNotNull(sampleModel); + + assertEquals(3, sampleModel.getWidth()); + assertEquals(2, sampleModel.getHeight()); + + assertTrue(colorModel.isCompatibleSampleModel(sampleModel)); + assertThat(sampleModel, CoreMatchers.is(PixelInterleavedSampleModel.class)); + assertThat(sampleModel.getDataType(), CoreMatchers.equalTo(DataBuffer.TYPE_USHORT)); + } + + @Test + public void testCreateCompatibleRaster() { + int[] colors = createIntLut(1 << 8); + IndexColorModel icm = new IndexColorModel(8, colors.length, colors, 0, false, -1, DataBuffer.TYPE_BYTE); + + ColorModel colorModel = new DiscreteAlphaIndexColorModel(icm); + WritableRaster raster = colorModel.createCompatibleWritableRaster(3, 2); + + assertNotNull(raster); + + assertEquals(3, raster.getWidth()); + assertEquals(2, raster.getHeight()); + + assertTrue(colorModel.isCompatibleRaster(raster)); + assertThat(raster, CoreMatchers.is(WritableRaster.class)); // Specific subclasses are in sun.awt package + assertThat(raster.getTransferType(), CoreMatchers.equalTo(DataBuffer.TYPE_BYTE)); + } + + private static int[] createIntLut(final int count) { + int[] lut = new int[count]; + + for (int i = 0; i < count; i++) { + lut[i] = 0xff000000 | i << 16 | i << 8 | i; + } + + return lut; + } +} \ No newline at end of file diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageTypeSpecifiersTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageTypeSpecifiersTest.java index 9a286bbf..d22e79ca 100644 --- a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageTypeSpecifiersTest.java +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageTypeSpecifiersTest.java @@ -609,7 +609,26 @@ public class ImageTypeSpecifiersTest { new IndexedImageTypeSpecifier(colorModel), ImageTypeSpecifiers.createFromIndexColorModel(colorModel) ); + } + @Test + public void testCreateDiscreteAlphaIndexedFromIndexColorModel8() { + int[] colors = createIntLut(1 << 8); + IndexColorModel colorModel = new IndexColorModel(8, colors.length, colors, 0, false, -1, DataBuffer.TYPE_BYTE); + assertEquals( + new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1)), + ImageTypeSpecifiers.createFromIndexColorModel(colorModel) + ); + } + + @Test + public void testCreateDiscreteAlphaIndexedFromIndexColorModel16() { + int[] colors = createIntLut(1 << 16); + IndexColorModel colorModel = new IndexColorModel(16, colors.length, colors, 0, false, -1, DataBuffer.TYPE_USHORT); + assertEquals( + new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1)), + ImageTypeSpecifiers.createFromIndexColorModel(colorModel) + ); } private static byte[] createByteLut(final int count) { diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java index 5961f6e5..068fd319 100755 --- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java +++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java @@ -52,7 +52,6 @@ import com.twelvemonkeys.io.FastByteArrayOutputStream; import com.twelvemonkeys.io.LittleEndianDataInputStream; import com.twelvemonkeys.io.enc.DecoderStream; import com.twelvemonkeys.io.enc.PackBitsDecoder; -import com.twelvemonkeys.xml.XMLSerializer; import org.w3c.dom.NodeList; import javax.imageio.*; @@ -459,7 +458,7 @@ public class TIFFImageReader extends ImageReaderBase { } case TIFFBaseline.PHOTOMETRIC_PALETTE: // Palette - if (samplesPerPixel != 1) { + if (samplesPerPixel != 1 && !(samplesPerPixel == 2 && extraSamples != null && extraSamples.length == 1)) { throw new IIOException("Bad SamplesPerPixel value for Palette TIFF (expected 1): " + samplesPerPixel); } else if (bitsPerSample <= 0 || bitsPerSample > 16) { @@ -474,6 +473,12 @@ public class TIFFImageReader extends ImageReaderBase { IndexColorModel icm = createIndexColorModel(bitsPerSample, dataType, (int[]) colorMap.getValue()); + if (extraSamples != null && extraSamples.length > 0 + && (extraSamples[0] == TIFFBaseline.EXTRASAMPLE_ASSOCIATED_ALPHA + || extraSamples[0] == TIFFBaseline.EXTRASAMPLE_UNASSOCIATED_ALPHA)) { + return ImageTypeSpecifiers.createDiscreteAlphaIndexedFromIndexColorModel(icm); + } + return ImageTypeSpecifiers.createFromIndexColorModel(icm); case TIFFExtension.PHOTOMETRIC_SEPARATED: @@ -808,8 +813,11 @@ public class TIFFImageReader extends ImageReaderBase { int tilesAcross = (width + stripTileWidth - 1) / stripTileWidth; int tilesDown = (height + stripTileHeight - 1) / stripTileHeight; + + // TODO: Get number of extra samples not part of the rawType spec... // TODO: If extrasamples, we might need to create a raster with more samples... WritableRaster rowRaster = rawType.createBufferedImage(stripTileWidth, 1).getRaster(); +// WritableRaster rowRaster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, stripTileWidth, 1, 2, null).createWritableChild(0, 0, stripTileWidth, 1, 0, 0, new int[]{0}); Rectangle clip = new Rectangle(srcRegion); int row = 0; Boolean needsCSConversion = null; diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java index 08bbf63d..095c0ddb 100644 --- a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java +++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java @@ -93,6 +93,7 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest