mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-05 04:25:29 -04:00
#287 Support for TIFF with color map + discrete alpha channel.
This commit is contained in:
parent
37b124c98a
commit
d4b3f3e04a
@ -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.
|
||||
* <p>
|
||||
* ColorSpace will always be the default sRGB color space (as with {@code IndexColorModel}).
|
||||
* <p>
|
||||
* 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();
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -93,6 +93,7 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest<TIFFImageReader
|
||||
new TestData(getClassLoaderResource("/tiff/lzw-colormap-iiobe.tif"), new Dimension(2550, 3300)), // RGBA, LZW compression, will throw IOOBE if small buffer
|
||||
new TestData(getClassLoaderResource("/tiff/scan-mono-iccgray.tif"), new Dimension(2408, 3436)), // B/W, PackBits w/gray ICC profile
|
||||
new TestData(getClassLoaderResource("/tiff/planar-striped-lzw.tif"), new Dimension(229, 229)), // RGB 8 bit/sample, planar, LZW compression
|
||||
new TestData(getClassLoaderResource("/tiff/colormap-with-extrasamples.tif"), new Dimension(10, 10)), // Palette, 8 bit/sample, 2 samples/pixel, extra samples, LZW
|
||||
// CCITT
|
||||
new TestData(getClassLoaderResource("/tiff/ccitt/group3_1d.tif"), new Dimension(6, 4)), // B/W, CCITT T4 1D
|
||||
new TestData(getClassLoaderResource("/tiff/ccitt/group3_1d_fill.tif"), new Dimension(6, 4)), // B/W, CCITT T4 1D
|
||||
|
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user