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 7a59d3de..1a4fef8a 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 @@ -30,21 +30,14 @@ package com.twelvemonkeys.imageio.util; -import static com.twelvemonkeys.lang.Validate.isTrue; -import static com.twelvemonkeys.lang.Validate.notNull; - -import java.awt.color.ColorSpace; -import java.awt.image.BufferedImage; -import java.awt.image.ColorModel; -import java.awt.image.DataBuffer; -import java.awt.image.DirectColorModel; -import java.awt.image.IndexColorModel; -import java.awt.image.MultiPixelPackedSampleModel; -import java.awt.image.SampleModel; +import com.twelvemonkeys.imageio.color.DiscreteAlphaIndexColorModel; import javax.imageio.ImageTypeSpecifier; +import java.awt.color.*; +import java.awt.image.*; -import com.twelvemonkeys.imageio.color.DiscreteAlphaIndexColorModel; +import static com.twelvemonkeys.lang.Validate.isTrue; +import static com.twelvemonkeys.lang.Validate.notNull; /** * Factory class for creating {@code ImageTypeSpecifier}s. @@ -58,28 +51,52 @@ import com.twelvemonkeys.imageio.color.DiscreteAlphaIndexColorModel; */ public final class ImageTypeSpecifiers { + private static final ImageTypeSpecifier TYPE_INT_RGB = createPackedOddBits(ColorSpace.getInstance(ColorSpace.CS_sRGB), 24, + 0xFF0000, + 0x00FF00, + 0x0000FF, + 0x0, + DataBuffer.TYPE_INT, + false); + private static final ImageTypeSpecifier TYPE_INT_BGR = createPackedOddBits(ColorSpace.getInstance(ColorSpace.CS_sRGB), 24, + 0x0000FF, + 0x00FF00, + 0xFF0000, + 0x0, + DataBuffer.TYPE_INT, + false); + private static final ImageTypeSpecifier TYPE_USHORT_565_RGB = createPackedOddBits(ColorSpace.getInstance(ColorSpace.CS_sRGB), 16, + 0xF800, + 0x07E0, + 0x001F, + 0x0, + DataBuffer.TYPE_USHORT, + false); + private static final ImageTypeSpecifier TYPE_USHORT_555_RGB = createPackedOddBits(ColorSpace.getInstance(ColorSpace.CS_sRGB), 15, + 0x7C00, + 0x03E0, + 0x001F, + 0x0, + DataBuffer.TYPE_USHORT, + false); + private ImageTypeSpecifiers() {} public static ImageTypeSpecifier createFromBufferedImageType(final int bufferedImageType) { switch (bufferedImageType) { - // ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround for the USHORT types + // ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround for the INT_RGB and USHORT types + case BufferedImage.TYPE_INT_RGB: + return TYPE_INT_RGB; + + case BufferedImage.TYPE_INT_BGR: + return TYPE_INT_BGR; + case BufferedImage.TYPE_USHORT_565_RGB: - return createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB), - 0xF800, - 0x07E0, - 0x001F, - 0x0, - DataBuffer.TYPE_USHORT, - false); + return TYPE_USHORT_565_RGB; case BufferedImage.TYPE_USHORT_555_RGB: - return createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB), - 0x7C00, - 0x03E0, - 0x001F, - 0x0, - DataBuffer.TYPE_USHORT, - false); + return TYPE_USHORT_555_RGB; + default: } @@ -90,23 +107,41 @@ public final class ImageTypeSpecifiers { final int redMask, final int greenMask, final int blueMask, final int alphaMask, final int transferType, boolean isAlphaPremultiplied) { - if (transferType == DataBuffer.TYPE_BYTE || transferType == DataBuffer.TYPE_USHORT) { + int bits = calculateRequiredBits(redMask | greenMask | blueMask | alphaMask); + if (bits != 32) { // ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround for BYTE/USHORT types - notNull(colorSpace, "colorSpace"); - isTrue(colorSpace.getType() == ColorSpace.TYPE_RGB, colorSpace, "ColorSpace must be TYPE_RGB"); - isTrue(redMask != 0 || greenMask != 0 || blueMask != 0 || alphaMask != 0, "No mask has at least 1 bit set"); - - int bits = transferType == DataBuffer.TYPE_BYTE ? 8 : 16; - - ColorModel colorModel = new DirectColorModel(colorSpace, bits, redMask, greenMask, blueMask, alphaMask, - isAlphaPremultiplied, transferType); - - return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1)); + return createPackedOddBits(colorSpace, bits, redMask, greenMask, blueMask, alphaMask, transferType, isAlphaPremultiplied); } return ImageTypeSpecifier.createPacked(colorSpace, redMask, greenMask, blueMask, alphaMask, transferType, isAlphaPremultiplied); } + private static int calculateRequiredBits(int mask) { + // See https://graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious + int r = 1; + + while ((mask >>>= 1) != 0) { + r++; + } + + return r; + } + + static ImageTypeSpecifier createPackedOddBits(final ColorSpace colorSpace, int bits, + final int redMask, final int greenMask, + final int blueMask, final int alphaMask, + final int transferType, boolean isAlphaPremultiplied) { + // ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround + notNull(colorSpace, "colorSpace"); + isTrue(colorSpace.getType() == ColorSpace.TYPE_RGB, colorSpace, "ColorSpace must be TYPE_RGB"); + isTrue(redMask != 0 || greenMask != 0 || blueMask != 0 || alphaMask != 0, "No mask has at least 1 bit set"); + + ColorModel colorModel = new DirectColorModel(colorSpace, bits, redMask, greenMask, blueMask, alphaMask, + isAlphaPremultiplied, transferType); + + return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1)); + } + public static ImageTypeSpecifier createInterleaved(final ColorSpace colorSpace, final int[] bandOffsets, final int dataType, @@ -235,4 +270,20 @@ public final class ImageTypeSpecifiers { ColorModel colorModel = new DiscreteAlphaIndexColorModel(pColorModel, extraSamples, hasAlpha); return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1)); } + + public static ImageTypeSpecifier createFromRenderedImage(RenderedImage image) { + if (image == null) { + throw new IllegalArgumentException("image == null!"); + } + + if (image instanceof BufferedImage) { + int bufferedImageType = ((BufferedImage) image).getType(); + + if (bufferedImageType != BufferedImage.TYPE_CUSTOM) { + return createFromBufferedImageType(bufferedImageType); + } + } + + return new ImageTypeSpecifier(image); + } } 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 21256c26..812a953e 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 @@ -30,20 +30,15 @@ package com.twelvemonkeys.imageio.util; -import static org.junit.Assert.assertEquals; - -import java.awt.color.ColorSpace; -import java.awt.image.BufferedImage; -import java.awt.image.ColorModel; -import java.awt.image.DataBuffer; -import java.awt.image.DirectColorModel; -import java.awt.image.IndexColorModel; - -import javax.imageio.ImageTypeSpecifier; +import com.twelvemonkeys.lang.Validate; import org.junit.Test; -import com.twelvemonkeys.lang.Validate; +import javax.imageio.ImageTypeSpecifier; +import java.awt.color.*; +import java.awt.image.*; + +import static org.junit.Assert.assertEquals; public class ImageTypeSpecifiersTest { @@ -70,12 +65,19 @@ public class ImageTypeSpecifiersTest { ImageTypeSpecifier expected; switch (type) { + // Special handling for INT_RGB and BGR, due to bug in ImageTypeSpecifier for these types (DirectColorModel is 32 bits) + case BufferedImage.TYPE_INT_RGB: + expected = createPacked(sRGB, 24, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, 0, DataBuffer.TYPE_INT, false); + break; + case BufferedImage.TYPE_INT_BGR: + expected = createPacked(sRGB, 24, DCM_BGR_RED_MASK, DCM_BGR_GRN_MASK, DCM_BGR_BLU_MASK, 0, DataBuffer.TYPE_INT, false); + break; // Special handling for USHORT_565 and 555, due to bug in ImageTypeSpecifier for these types (DirectColorModel is 32 bits) case BufferedImage.TYPE_USHORT_565_RGB: - expected = createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false); + expected = createPacked(sRGB, 16, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false); break; case BufferedImage.TYPE_USHORT_555_RGB: - expected = createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false); + expected = createPacked(sRGB, 15, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false); break; default: expected = ImageTypeSpecifier.createFromBufferedImageType(type); @@ -86,12 +88,24 @@ public class ImageTypeSpecifiersTest { } @Test - public void testCreatePacked32() { + public void testCreatePacked24() { // TYPE_INT_RGB assertEquals( - ImageTypeSpecifier.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, 0, DataBuffer.TYPE_INT, false), + createPacked(sRGB, 24, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, 0, DataBuffer.TYPE_INT, false), ImageTypeSpecifiers.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, 0, DataBuffer.TYPE_INT, false) ); + // TYPE_INT_BGR + assertEquals( + createPacked(sRGB, 24, DCM_BGR_RED_MASK, DCM_BGR_GRN_MASK, DCM_BGR_BLU_MASK, 0, DataBuffer.TYPE_INT, false), + ImageTypeSpecifiers.createPacked(sRGB, DCM_BGR_RED_MASK, DCM_BGR_GRN_MASK, DCM_BGR_BLU_MASK, 0, DataBuffer.TYPE_INT, false) + ); + + // Extra: Make sure color models bits is actually 24 (ImageTypeSpecifier equivalent returns 32) + assertEquals(24, ImageTypeSpecifiers.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, 0, DataBuffer.TYPE_INT, false).getColorModel().getPixelSize()); + } + + @Test + public void testCreatePacked32() { // TYPE_INT_ARGB assertEquals( ImageTypeSpecifier.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, DCM_ALPHA_MASK, DataBuffer.TYPE_INT, false), @@ -102,35 +116,36 @@ public class ImageTypeSpecifiersTest { ImageTypeSpecifier.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, DCM_ALPHA_MASK, DataBuffer.TYPE_INT, true), ImageTypeSpecifiers.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, DCM_ALPHA_MASK, DataBuffer.TYPE_INT, true) ); - // TYPE_INT_BGR - assertEquals( - ImageTypeSpecifier.createPacked(sRGB, DCM_BGR_RED_MASK, DCM_BGR_GRN_MASK, DCM_BGR_BLU_MASK, 0, DataBuffer.TYPE_INT, false), - ImageTypeSpecifiers.createPacked(sRGB, DCM_BGR_RED_MASK, DCM_BGR_GRN_MASK, DCM_BGR_BLU_MASK, 0, DataBuffer.TYPE_INT, false) - ); } @Test - public void testCreatePacked16() { + public void testCreatePacked15() { // TYPE_USHORT_555_RGB assertEquals( - createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false), + createPacked(sRGB, 15, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false), ImageTypeSpecifiers.createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false) ); // "SHORT 555 RGB" (impossible, only BYTE, USHORT, INT supported) + // Extra: Make sure color models bits is actually 15 (ImageTypeSpecifier equivalent returns 32) + assertEquals(15, ImageTypeSpecifiers.createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false).getColorModel().getPixelSize()); + } + + @Test + public void testCreatePacked16() { // TYPE_USHORT_565_RGB assertEquals( - createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false), + createPacked(sRGB, 16, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false), ImageTypeSpecifiers.createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false) ); // "USHORT 4444 ARGB" assertEquals( - createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, false), + createPacked(sRGB, 16,0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, false), ImageTypeSpecifiers.createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, false) ); // "USHORT 4444 ARGB PRE" assertEquals( - createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, true), + createPacked(sRGB, 16, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, true), ImageTypeSpecifiers.createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, true) ); @@ -142,17 +157,17 @@ public class ImageTypeSpecifiersTest { public void testCreatePacked8() { // "BYTE 332 RGB" assertEquals( - createPacked(sRGB, 0xe0, 0x1c, 0x03, 0x0, DataBuffer.TYPE_BYTE, false), + createPacked(sRGB, 8, 0xe0, 0x1c, 0x03, 0x0, DataBuffer.TYPE_BYTE, false), ImageTypeSpecifiers.createPacked(sRGB, 0xe0, 0x1c, 0x3, 0x0, DataBuffer.TYPE_BYTE, false) ); // "BYTE 2222 ARGB" assertEquals( - createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, false), + createPacked(sRGB, 8, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, false), ImageTypeSpecifiers.createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, false) ); // "BYTE 2222 ARGB PRE" assertEquals( - createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, true), + createPacked(sRGB, 8, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, true), ImageTypeSpecifiers.createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, true) ); @@ -160,15 +175,12 @@ public class ImageTypeSpecifiersTest { assertEquals(8, ImageTypeSpecifiers.createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, false).getColorModel().getPixelSize()); } - private ImageTypeSpecifier createPacked(final ColorSpace colorSpace, + private ImageTypeSpecifier createPacked(final ColorSpace colorSpace, final int bits, final int redMask, final int greenMask, final int blueMask, final int alphaMask, final int transferType, final boolean isAlphaPremultiplied) { - Validate.isTrue(transferType == DataBuffer.TYPE_BYTE || transferType == DataBuffer.TYPE_USHORT, transferType, "transferType: %s"); + Validate.isTrue(transferType == DataBuffer.TYPE_BYTE || transferType == DataBuffer.TYPE_USHORT || transferType == DataBuffer.TYPE_INT, transferType, "transferType: %s"); - int bits = transferType == DataBuffer.TYPE_BYTE ? 8 : 16; - - ColorModel colorModel = - new DirectColorModel(colorSpace, bits, redMask, greenMask, blueMask, alphaMask, isAlphaPremultiplied, transferType); + ColorModel colorModel = new DirectColorModel(colorSpace, bits, redMask, greenMask, blueMask, alphaMask, isAlphaPremultiplied, transferType); return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1)); } @@ -716,6 +728,28 @@ public class ImageTypeSpecifiersTest { ); } + @Test + public void testCreateFromBufferedImageTypeShouldEqualConstructor() { + for (int type = BufferedImage.TYPE_INT_RGB; type < BufferedImage.TYPE_BYTE_INDEXED; type++) { + BufferedImage image = new BufferedImage(1, 1, type); + ImageTypeSpecifier fromConstructor = new ImageTypeSpecifier(image); + ImageTypeSpecifier fromType = ImageTypeSpecifiers.createFromBufferedImageType(type); + + assertEquals(fromConstructor.getColorModel(), fromType.getColorModel()); + } + } + + @Test + public void testCreateFromRenderedImageShouldEqualConstructor() { + for (int type = BufferedImage.TYPE_INT_RGB; type < BufferedImage.TYPE_BYTE_INDEXED; type++) { + BufferedImage image = new BufferedImage(1, 1, type); + ImageTypeSpecifier fromConstructor = new ImageTypeSpecifier(image); + ImageTypeSpecifier fromImage = ImageTypeSpecifiers.createFromRenderedImage(image); + + assertEquals(fromConstructor.getColorModel(), fromImage.getColorModel()); + } + } + private static byte[] createByteLut(final int count) { byte[] lut = new byte[count]; for (int i = 0; i < count; i++) {