From c29f8953376f35fb310cc155fa1395c69bee06e1 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Thu, 26 May 2016 16:15:10 +0200 Subject: [PATCH] #256 ImageTypeSpecifiers fix --- .../color/Int16ComponentColorModel.java | 84 +++++++++++++++++++ .../imageio/util/ImageTypeSpecifiers.java | 39 ++++++++- .../imageio/util/Int16ImageTypeSpecifier.java | 58 +++++++++++++ .../imageio/util/ImageTypeSpecifiersTest.java | 83 +++++++++++------- 4 files changed, 232 insertions(+), 32 deletions(-) create mode 100644 imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/Int16ComponentColorModel.java create mode 100644 imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/Int16ImageTypeSpecifier.java diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/Int16ComponentColorModel.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/Int16ComponentColorModel.java new file mode 100644 index 00000000..3a38023f --- /dev/null +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/Int16ComponentColorModel.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2014, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name "TwelveMonkeys" nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.color; + +import java.awt.color.ColorSpace; +import java.awt.image.ComponentColorModel; +import java.awt.image.DataBuffer; + +/** + * ComponentColorModel subclass that correctly handles full 16 bit {@code TYPE_SHORT} signed integral samples. + ** + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: UInt32ColorModel.java,v 1.0 24.01.11 17.51 haraldk Exp$ + */ +public final class Int16ComponentColorModel extends ComponentColorModel { + private final ComponentColorModel delegate; + + public Int16ComponentColorModel(final ColorSpace cs, final boolean hasAlpha, boolean isAlphaPremultiplied) { + super(cs, hasAlpha, isAlphaPremultiplied, hasAlpha ? TRANSLUCENT : OPAQUE, DataBuffer.TYPE_SHORT); + + delegate = new ComponentColorModel(cs, hasAlpha, isAlphaPremultiplied, hasAlpha ? TRANSLUCENT : OPAQUE, DataBuffer.TYPE_USHORT); + } + + private void remap(final short[] s, final int i) { + // MIN ... -1 -> 0 ... MAX + // 0 ... MAX -> MIN ... -1 + short sample = s[i]; + + if (sample < 0) { + s[i] = (short) (sample - Short.MIN_VALUE); + } + else { + s[i] = (short) (sample + Short.MIN_VALUE); + } + } + + @Override + public int getRed(final Object inData) { + remap((short[]) inData, 0); + + return delegate.getRed(inData); + } + + @Override + public int getGreen(final Object inData) { + remap((short[]) inData, 1); + + return delegate.getGreen(inData); + } + + @Override + public int getBlue(final Object inData) { + remap((short[]) inData, 2); + + return delegate.getBlue(inData); + } +} 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 165e4152..372081ea 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,12 +30,15 @@ package com.twelvemonkeys.imageio.util; import javax.imageio.ImageTypeSpecifier; import java.awt.color.ColorSpace; +import java.awt.image.ColorModel; import java.awt.image.DataBuffer; +import java.awt.image.DirectColorModel; import java.awt.image.IndexColorModel; /** * Factory class for creating {@code ImageTypeSpecifier}s. - * In most cases, this class will delegate to the corresponding methods in {@link ImageTypeSpecifier}. + * Fixes some subtle bugs in {@code ImageTypeSpecifier}'s factory methods, but + * in most cases, this class will delegate to the corresponding methods in {@link ImageTypeSpecifier}. * * @see javax.imageio.ImageTypeSpecifier * @author Harald Kuhr @@ -54,6 +57,28 @@ 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) { + // ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround for BYTE/USHORT types + if (colorSpace == null) { + throw new IllegalArgumentException("colorSpace == null!"); + } + + if (colorSpace.getType() != ColorSpace.TYPE_RGB) { + throw new IllegalArgumentException("colorSpace is not of type TYPE_RGB!"); + } + + if (redMask == 0 && greenMask == 0 && blueMask == 0 && alphaMask == 0) { + throw new IllegalArgumentException("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); } @@ -79,7 +104,11 @@ public final class ImageTypeSpecifiers { } public static ImageTypeSpecifier createGrayscale(final int bits, final int dataType) { - if (bits == 32 && dataType == DataBuffer.TYPE_INT) { + if (bits == 16 && dataType == DataBuffer.TYPE_SHORT) { + // As the ComponentColorModel is broken for 16 bit signed int, we'll use our own version + return new Int16ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0}, false, false); + } + else if (bits == 32 && dataType == DataBuffer.TYPE_INT) { // As the ComponentColorModel is broken for 32 bit unsigned int, we'll use our own version return new UInt32ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0}, false, false); } @@ -89,7 +118,11 @@ public final class ImageTypeSpecifiers { } public static ImageTypeSpecifier createGrayscale(final int bits, final int dataType, final boolean isAlphaPremultiplied) { - if (bits == 32 && dataType == DataBuffer.TYPE_INT) { + if (bits == 16 && dataType == DataBuffer.TYPE_SHORT) { + // As the ComponentColorModel is broken for 16 bit signed int, we'll use our own version + return new Int16ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0, 1}, true, isAlphaPremultiplied); + } + else if (bits == 32 && dataType == DataBuffer.TYPE_INT) { // As the ComponentColorModel is broken for 32 bit unsigned int, we'll use our own version return new UInt32ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0, 1}, true, isAlphaPremultiplied); } diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/Int16ImageTypeSpecifier.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/Int16ImageTypeSpecifier.java new file mode 100644 index 00000000..e79ed3d3 --- /dev/null +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/Int16ImageTypeSpecifier.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2014, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name "TwelveMonkeys" nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.util; + +import com.twelvemonkeys.imageio.color.Int16ComponentColorModel; + +import javax.imageio.ImageTypeSpecifier; +import java.awt.color.ColorSpace; +import java.awt.image.DataBuffer; +import java.awt.image.PixelInterleavedSampleModel; + +/** + * ImageTypeSpecifier for interleaved 16 bit signed integral samples. + * + * @see com.twelvemonkeys.imageio.color.Int16ColorModel + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: Int16ImageTypeSpecifier.java,v 1.0 24.01.11 17.51 haraldk Exp$ + */ +final class Int16ImageTypeSpecifier extends ImageTypeSpecifier { + Int16ImageTypeSpecifier(final ColorSpace cs, int[] bandOffsets, final boolean hasAlpha, final boolean isAlphaPremultiplied) { + super( + new Int16ComponentColorModel(cs, hasAlpha, isAlphaPremultiplied), + new PixelInterleavedSampleModel( + DataBuffer.TYPE_SHORT, 1, 1, + cs.getNumComponents() + (hasAlpha ? 1 : 0), + cs.getNumComponents() + (hasAlpha ? 1 : 0), + bandOffsets + ) + ); + } +} 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 88451917..9ddfbf9b 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 @@ -1,12 +1,11 @@ package com.twelvemonkeys.imageio.util; +import com.twelvemonkeys.lang.Validate; import org.junit.Test; import javax.imageio.ImageTypeSpecifier; import java.awt.color.ColorSpace; -import java.awt.image.BufferedImage; -import java.awt.image.DataBuffer; -import java.awt.image.IndexColorModel; +import java.awt.image.*; import static org.junit.Assert.assertEquals; @@ -40,7 +39,7 @@ public class ImageTypeSpecifiersTest { } @Test - public void testCreatePacked() { + public void testCreatePacked32() { // TYPE_INT_RGB assertEquals( ImageTypeSpecifier.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, 0, DataBuffer.TYPE_INT, false), @@ -61,31 +60,70 @@ public class ImageTypeSpecifiersTest { 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() { // TYPE_USHORT_555_RGB assertEquals( - ImageTypeSpecifier.createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false), + 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 for some reason) -// assertEquals( -// ImageTypeSpecifier.createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_SHORT, false), -// ImageTypeSpecifiers.createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_SHORT, false) -// ); + // "SHORT 555 RGB" (impossible, only BYTE, USHORT, INT supported) + // TYPE_USHORT_565_RGB assertEquals( - ImageTypeSpecifier.createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false), + 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" assertEquals( - ImageTypeSpecifier.createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, false), + 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" assertEquals( - ImageTypeSpecifier.createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, true), + createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, true), ImageTypeSpecifiers.createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, true) ); + + // Extra: Make sure color models bits is actually 16 (ImageTypeSpecifier equivalent returns 32) + assertEquals(16, ImageTypeSpecifiers.createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false).getColorModel().getPixelSize()); + } + + @Test + public void testCreatePacked8() { + // "BYTE 332 RGB" + assertEquals( + createPacked(sRGB, 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), + 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), + ImageTypeSpecifiers.createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, true) + ); + + // Extra: Make sure color models bits is actually 8 (ImageTypeSpecifiers equivalent returns 32) + assertEquals(8, ImageTypeSpecifiers.createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, false).getColorModel().getPixelSize()); + } + + private ImageTypeSpecifier createPacked(final ColorSpace colorSpace, + 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"); + + 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)); } @Test @@ -337,11 +375,7 @@ public class ImageTypeSpecifiersTest { ); assertEquals( - ImageTypeSpecifier.createGrayscale(16, DataBuffer.TYPE_SHORT, false), // NOTE: Unsigned TYPE_SHORT makes no sense... - ImageTypeSpecifiers.createGrayscale(16, DataBuffer.TYPE_SHORT) - ); - assertEquals( - ImageTypeSpecifier.createGrayscale(16, DataBuffer.TYPE_SHORT, true), + new Int16ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0}, false, false), ImageTypeSpecifiers.createGrayscale(16, DataBuffer.TYPE_SHORT) ); } @@ -400,19 +434,11 @@ public class ImageTypeSpecifiersTest { ); assertEquals( - ImageTypeSpecifier.createGrayscale(16, DataBuffer.TYPE_SHORT, false, false), + new Int16ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0, 1}, true, false), ImageTypeSpecifiers.createGrayscale(16, DataBuffer.TYPE_SHORT, false) ); assertEquals( - ImageTypeSpecifier.createGrayscale(16, DataBuffer.TYPE_SHORT, false, true), - ImageTypeSpecifiers.createGrayscale(16, DataBuffer.TYPE_SHORT, true) - ); - assertEquals( - ImageTypeSpecifier.createGrayscale(16, DataBuffer.TYPE_SHORT, true, false), - ImageTypeSpecifiers.createGrayscale(16, DataBuffer.TYPE_SHORT, false) - ); - assertEquals( - ImageTypeSpecifier.createGrayscale(16, DataBuffer.TYPE_SHORT, true, true), + new Int16ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0, 1}, true, true), ImageTypeSpecifiers.createGrayscale(16, DataBuffer.TYPE_SHORT, true) ); } @@ -562,7 +588,6 @@ public class ImageTypeSpecifiersTest { } - private static byte[] createByteLut(final int count) { byte[] lut = new byte[count]; for (int i = 0; i < count; i++) {