Better support for ImageTypeSpecifiers with IndexColorModel.

This commit is contained in:
Harald Kuhr 2023-09-22 11:52:11 +02:00
parent b55d4e8c1b
commit 4513b0c166
5 changed files with 106 additions and 16 deletions

View File

@ -257,18 +257,18 @@ public final class ImageTypeSpecifiers {
return createFromIndexColorModel(new IndexColorModel(bits, colors.length, colors, 0, hasAlpha, transIndex, dataType)); return createFromIndexColorModel(new IndexColorModel(bits, colors.length, colors, 0, hasAlpha, transIndex, dataType));
} }
public static ImageTypeSpecifier createFromIndexColorModel(final IndexColorModel pColorModel) { public static ImageTypeSpecifier createFromIndexColorModel(final IndexColorModel colorModel) {
return new IndexedImageTypeSpecifier(pColorModel); return new IndexedImageTypeSpecifier(colorModel);
} }
public static ImageTypeSpecifier createDiscreteAlphaIndexedFromIndexColorModel(final IndexColorModel pColorModel) { public static ImageTypeSpecifier createDiscreteAlphaIndexedFromIndexColorModel(final IndexColorModel colorModel) {
ColorModel colorModel = new DiscreteAlphaIndexColorModel(pColorModel); ColorModel discreteAlphaIndexColorModel = new DiscreteAlphaIndexColorModel(colorModel);
return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1)); return new ImageTypeSpecifier(discreteAlphaIndexColorModel, discreteAlphaIndexColorModel.createCompatibleSampleModel(1, 1));
} }
public static ImageTypeSpecifier createDiscreteExtraSamplesIndexedFromIndexColorModel(final IndexColorModel pColorModel, int extraSamples, boolean hasAlpha) { public static ImageTypeSpecifier createDiscreteExtraSamplesIndexedFromIndexColorModel(final IndexColorModel colorModel, int extraSamples, boolean hasAlpha) {
ColorModel colorModel = new DiscreteAlphaIndexColorModel(pColorModel, extraSamples, hasAlpha); ColorModel discreteAlphaIndexColorModel = new DiscreteAlphaIndexColorModel(colorModel, extraSamples, hasAlpha);
return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1)); return new ImageTypeSpecifier(discreteAlphaIndexColorModel, discreteAlphaIndexColorModel.createCompatibleSampleModel(1, 1));
} }
public static ImageTypeSpecifier createFromRenderedImage(RenderedImage image) { public static ImageTypeSpecifier createFromRenderedImage(RenderedImage image) {
@ -279,7 +279,9 @@ public final class ImageTypeSpecifiers {
if (image instanceof BufferedImage) { if (image instanceof BufferedImage) {
int bufferedImageType = ((BufferedImage) image).getType(); int bufferedImageType = ((BufferedImage) image).getType();
if (bufferedImageType != BufferedImage.TYPE_CUSTOM) { if (bufferedImageType != BufferedImage.TYPE_CUSTOM &&
// Need to retain the actual palette in the color model for IndexColorModel
bufferedImageType != BufferedImage.TYPE_BYTE_BINARY && bufferedImageType != BufferedImage.TYPE_BYTE_INDEXED) {
return createFromBufferedImageType(bufferedImageType); return createFromBufferedImageType(bufferedImageType);
} }
} }

View File

@ -52,12 +52,12 @@ final class IndexedImageTypeSpecifier extends ImageTypeSpecifier {
} }
@Override @Override
public final BufferedImage createBufferedImage(final int pWidth, final int pHeight) { public BufferedImage createBufferedImage(final int width, final int height) {
try { try {
// This is a fix for the super-method, that first creates a sample model, and then // This is a fix for the super-method, that first creates a sample model, and then
// creates a raster from it, using Raster.createWritableRaster. The problem with // creates a raster from it, using Raster.createWritableRaster. The problem with
// that approach, is that it always creates a TYPE_CUSTOM BufferedImage for indexed images. // that approach, is that it always creates a TYPE_CUSTOM BufferedImage for indexed images.
WritableRaster raster = colorModel.createCompatibleWritableRaster(pWidth, pHeight); WritableRaster raster = colorModel.createCompatibleWritableRaster(width, height);
return new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), null); return new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), null);
} }
catch (NegativeArraySizeException e) { catch (NegativeArraySizeException e) {

View File

@ -39,6 +39,7 @@ import java.awt.color.*;
import java.awt.image.*; import java.awt.image.*;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
public class ImageTypeSpecifiersTest { public class ImageTypeSpecifiersTest {
@ -736,6 +737,7 @@ public class ImageTypeSpecifiersTest {
ImageTypeSpecifier fromType = ImageTypeSpecifiers.createFromBufferedImageType(type); ImageTypeSpecifier fromType = ImageTypeSpecifiers.createFromBufferedImageType(type);
assertEquals(fromConstructor.getColorModel(), fromType.getColorModel()); assertEquals(fromConstructor.getColorModel(), fromType.getColorModel());
assertEquals(fromConstructor.getSampleModel(), fromType.getSampleModel());
} }
} }
@ -747,13 +749,43 @@ public class ImageTypeSpecifiersTest {
ImageTypeSpecifier fromImage = ImageTypeSpecifiers.createFromRenderedImage(image); ImageTypeSpecifier fromImage = ImageTypeSpecifiers.createFromRenderedImage(image);
assertEquals(fromConstructor.getColorModel(), fromImage.getColorModel()); assertEquals(fromConstructor.getColorModel(), fromImage.getColorModel());
assertEquals(fromConstructor.getSampleModel(), fromImage.getSampleModel());
} }
} }
@Test
public void testCreateFromRenderedImageIndexedBinaryShouldRetainPalette() {
IndexColorModel whiteIsZero = new IndexColorModel(1, 2, new int[]{0xFFFFFFFF, 0xFF000000}, 0, false, -1, DataBuffer.TYPE_BYTE);
BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_BINARY, whiteIsZero);
ImageTypeSpecifier fromImage = ImageTypeSpecifiers.createFromRenderedImage(image);
assertEquals(whiteIsZero, fromImage.getColorModel());
assertSame(whiteIsZero, fromImage.getColorModel()); // Note: This can be relaxed to asserting the LUTs are equal
assertEquals(image.getSampleModel(), fromImage.getSampleModel());
}
@Test
public void testCreateFromRenderedImageIndexedShouldRetainPalette() {
IndexColorModel palette = new IndexColorModel(4, 16, new int[]{
0xFFFFFFFF, 0xFF999999, 0xFF666666, 0xFF333333,
0xFF000000, 0xFF00202E, 0xFF003F5C, 0xFF2C4875,
0xFF8A508F, 0xFFBC5090, 0xFFFF6361, 0xFFFF8531,
0xFFFFA600, 0xFFFFD380, 0xFF74A892, 0xFF008585
}, 0, false, -1, DataBuffer.TYPE_BYTE);
BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_INDEXED, palette);
ImageTypeSpecifier fromImage = ImageTypeSpecifiers.createFromRenderedImage(image);
assertEquals(palette, fromImage.getColorModel());
assertSame(palette, fromImage.getColorModel()); // Note: This can be relaxed to asserting the LUTs are equal
assertEquals(image.getSampleModel(), fromImage.getSampleModel());
}
private static byte[] createByteLut(final int count) { private static byte[] createByteLut(final int count) {
byte[] lut = new byte[count]; byte[] lut = new byte[count];
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
lut[i] = (byte) count; lut[i] = (byte) (i * 255 / count);
} }
return lut; return lut;
} }
@ -762,7 +794,8 @@ public class ImageTypeSpecifiersTest {
int[] lut = new int[count]; int[] lut = new int[count];
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
lut[i] = 0xff000000 | count << 16 | count << 8 | count; int val = (i * 255 / count);
lut[i] = 0xff000000 | val << 16 | val << 8 | val;
} }
return lut; return lut;

View File

@ -40,6 +40,7 @@ import com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry;
import com.twelvemonkeys.imageio.metadata.tiff.TIFFWriter; import com.twelvemonkeys.imageio.metadata.tiff.TIFFWriter;
import com.twelvemonkeys.imageio.stream.SubImageOutputStream; import com.twelvemonkeys.imageio.stream.SubImageOutputStream;
import com.twelvemonkeys.imageio.util.IIOUtil; import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import com.twelvemonkeys.imageio.util.ProgressListenerBase; import com.twelvemonkeys.imageio.util.ProgressListenerBase;
import com.twelvemonkeys.io.enc.EncoderStream; import com.twelvemonkeys.io.enc.EncoderStream;
import com.twelvemonkeys.io.enc.PackBitsEncoder; import com.twelvemonkeys.io.enc.PackBitsEncoder;
@ -149,9 +150,9 @@ public final class TIFFImageWriter extends ImageWriterBase {
RenderedImage renderedImage = image.getRenderedImage(); RenderedImage renderedImage = image.getRenderedImage();
SampleModel sampleModel = renderedImage.getSampleModel(); SampleModel sampleModel = renderedImage.getSampleModel();
// Can't use createFromRenderedImage in this case, as it does not consider palette for TYPE_BYTE_BINARY... // Need ImageTypeSpecifiers.createFromRenderedImage in this case, as the JDK method does not consider
// TODO: Consider writing workaround in ImageTypeSpecifiers // palette for TYPE_BYTE_BINARY/TYPE_BYTE_INDEXED...
ImageTypeSpecifier spec = new ImageTypeSpecifier(renderedImage); ImageTypeSpecifier spec = ImageTypeSpecifiers.createFromRenderedImage(renderedImage);
// TODO: Handle case where convertImageMetadata returns null, due to unknown metadata format, or reconsider if that's a valid case... // TODO: Handle case where convertImageMetadata returns null, due to unknown metadata format, or reconsider if that's a valid case...
TIFFImageMetadata metadata = image.getMetadata() != null TIFFImageMetadata metadata = image.getMetadata() != null

View File

@ -1304,6 +1304,60 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTest<TIFFImageWriter
} }
} }
@Test
public void testWriteBinaryWhiteIsZero() throws IOException {
IndexColorModel whiteIsZero = new IndexColorModel(1, 2, new int[] {-1, 0}, 0, false, -1, DataBuffer.TYPE_BYTE);
BufferedImage image = new BufferedImage(10, 10, BufferedImage.TYPE_BYTE_BINARY, whiteIsZero);
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
try (ImageOutputStream output = ImageIO.createImageOutputStream(bytes)) {
ImageWriter imageWriter = createWriter();
imageWriter.setOutput(output);
imageWriter.write(image);
}
Directory directory = new TIFFReader().read(new ByteArrayImageInputStream(bytes.toByteArray()));
assertNotNull(directory.getEntryById(TIFF.TAG_PHOTOMETRIC_INTERPRETATION));
assertEquals(TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO, directory.getEntryById(TIFF.TAG_PHOTOMETRIC_INTERPRETATION).getValue());
}
@Test
public void testWriteBinaryBlackIsZero() throws IOException {
IndexColorModel blackIsZero = new IndexColorModel(1, 2, new int[] {0, -1}, 0, false, -1, DataBuffer.TYPE_BYTE);
BufferedImage image = new BufferedImage(10, 10, BufferedImage.TYPE_BYTE_BINARY, blackIsZero);
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
try (ImageOutputStream output = ImageIO.createImageOutputStream(bytes)) {
ImageWriter imageWriter = createWriter();
imageWriter.setOutput(output);
imageWriter.write(image);
}
Directory directory = new TIFFReader().read(new ByteArrayImageInputStream(bytes.toByteArray()));
assertNotNull(directory.getEntryById(TIFF.TAG_PHOTOMETRIC_INTERPRETATION));
assertEquals(TIFFBaseline.PHOTOMETRIC_BLACK_IS_ZERO, directory.getEntryById(TIFF.TAG_PHOTOMETRIC_INTERPRETATION).getValue());
}
@Test
public void testWriteBinaryPalette() throws IOException {
IndexColorModel redAndBluePalette = new IndexColorModel(1, 2, new int[] {0xFF00FF00, 0xFF0000FF}, 0, false, -1, DataBuffer.TYPE_BYTE);
BufferedImage image = new BufferedImage(10, 10, BufferedImage.TYPE_BYTE_BINARY, redAndBluePalette);
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
try (ImageOutputStream output = ImageIO.createImageOutputStream(bytes)) {
ImageWriter imageWriter = createWriter();
imageWriter.setOutput(output);
imageWriter.write(image);
}
Directory directory = new TIFFReader().read(new ByteArrayImageInputStream(bytes.toByteArray()));
assertNotNull(directory.getEntryById(TIFF.TAG_PHOTOMETRIC_INTERPRETATION));
assertEquals(TIFFBaseline.PHOTOMETRIC_PALETTE, directory.getEntryById(TIFF.TAG_PHOTOMETRIC_INTERPRETATION).getValue());
}
@Test @Test
public void testShortOverflowHuge() throws IOException { public void testShortOverflowHuge() throws IOException {
int width = 34769; int width = 34769;