#703: Workaround for 32 bit issue in ImageTypeSpecifier

This commit is contained in:
Harald Kuhr 2022-10-06 16:00:43 +02:00
parent 0160fb70f8
commit 9375bfda9a
2 changed files with 157 additions and 72 deletions

View File

@ -30,21 +30,14 @@
package com.twelvemonkeys.imageio.util; package com.twelvemonkeys.imageio.util;
import static com.twelvemonkeys.lang.Validate.isTrue; import com.twelvemonkeys.imageio.color.DiscreteAlphaIndexColorModel;
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 javax.imageio.ImageTypeSpecifier; 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. * Factory class for creating {@code ImageTypeSpecifier}s.
@ -58,28 +51,52 @@ import com.twelvemonkeys.imageio.color.DiscreteAlphaIndexColorModel;
*/ */
public final class ImageTypeSpecifiers { 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() {} private ImageTypeSpecifiers() {}
public static ImageTypeSpecifier createFromBufferedImageType(final int bufferedImageType) { public static ImageTypeSpecifier createFromBufferedImageType(final int bufferedImageType) {
switch (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: case BufferedImage.TYPE_USHORT_565_RGB:
return createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB), return TYPE_USHORT_565_RGB;
0xF800,
0x07E0,
0x001F,
0x0,
DataBuffer.TYPE_USHORT,
false);
case BufferedImage.TYPE_USHORT_555_RGB: case BufferedImage.TYPE_USHORT_555_RGB:
return createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB), return TYPE_USHORT_555_RGB;
0x7C00,
0x03E0,
0x001F,
0x0,
DataBuffer.TYPE_USHORT,
false);
default: default:
} }
@ -90,23 +107,41 @@ public final class ImageTypeSpecifiers {
final int redMask, final int greenMask, final int redMask, final int greenMask,
final int blueMask, final int alphaMask, final int blueMask, final int alphaMask,
final int transferType, boolean isAlphaPremultiplied) { 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 // ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround for BYTE/USHORT types
notNull(colorSpace, "colorSpace"); return createPackedOddBits(colorSpace, bits, redMask, greenMask, blueMask, alphaMask, transferType, isAlphaPremultiplied);
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 ImageTypeSpecifier.createPacked(colorSpace, 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, public static ImageTypeSpecifier createInterleaved(final ColorSpace colorSpace,
final int[] bandOffsets, final int[] bandOffsets,
final int dataType, final int dataType,
@ -235,4 +270,20 @@ public final class ImageTypeSpecifiers {
ColorModel colorModel = new DiscreteAlphaIndexColorModel(pColorModel, extraSamples, hasAlpha); ColorModel colorModel = new DiscreteAlphaIndexColorModel(pColorModel, extraSamples, hasAlpha);
return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1)); 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);
}
} }

View File

@ -30,20 +30,15 @@
package com.twelvemonkeys.imageio.util; package com.twelvemonkeys.imageio.util;
import static org.junit.Assert.assertEquals; import com.twelvemonkeys.lang.Validate;
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 org.junit.Test; 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 { public class ImageTypeSpecifiersTest {
@ -70,12 +65,19 @@ public class ImageTypeSpecifiersTest {
ImageTypeSpecifier expected; ImageTypeSpecifier expected;
switch (type) { 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) // 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: 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; break;
case BufferedImage.TYPE_USHORT_555_RGB: 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; break;
default: default:
expected = ImageTypeSpecifier.createFromBufferedImageType(type); expected = ImageTypeSpecifier.createFromBufferedImageType(type);
@ -86,12 +88,24 @@ public class ImageTypeSpecifiersTest {
} }
@Test @Test
public void testCreatePacked32() { public void testCreatePacked24() {
// TYPE_INT_RGB // TYPE_INT_RGB
assertEquals( 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) 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 // TYPE_INT_ARGB
assertEquals( assertEquals(
ImageTypeSpecifier.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, DCM_ALPHA_MASK, DataBuffer.TYPE_INT, false), 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), 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) 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 @Test
public void testCreatePacked16() { public void testCreatePacked15() {
// TYPE_USHORT_555_RGB // TYPE_USHORT_555_RGB
assertEquals( 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) 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) // "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 // TYPE_USHORT_565_RGB
assertEquals( 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) ImageTypeSpecifiers.createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false)
); );
// "USHORT 4444 ARGB" // "USHORT 4444 ARGB"
assertEquals( 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) ImageTypeSpecifiers.createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, false)
); );
// "USHORT 4444 ARGB PRE" // "USHORT 4444 ARGB PRE"
assertEquals( 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) ImageTypeSpecifiers.createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, true)
); );
@ -142,17 +157,17 @@ public class ImageTypeSpecifiersTest {
public void testCreatePacked8() { public void testCreatePacked8() {
// "BYTE 332 RGB" // "BYTE 332 RGB"
assertEquals( 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) ImageTypeSpecifiers.createPacked(sRGB, 0xe0, 0x1c, 0x3, 0x0, DataBuffer.TYPE_BYTE, false)
); );
// "BYTE 2222 ARGB" // "BYTE 2222 ARGB"
assertEquals( 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) ImageTypeSpecifiers.createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, false)
); );
// "BYTE 2222 ARGB PRE" // "BYTE 2222 ARGB PRE"
assertEquals( 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) 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()); 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 redMask, final int greenMask, final int blueMask, final int alphaMask,
final int transferType, final boolean isAlphaPremultiplied) { 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)); 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) { 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++) {