diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageMetadata.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageMetadata.java
index 7ded0a4c..9634084a 100644
--- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageMetadata.java
+++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageMetadata.java
@@ -62,6 +62,7 @@ final class IFFImageMetadata extends AbstractMetadata {
case 6:
case 7:
case 24:
+ case 25:
case 32:
csType.setAttribute("name", "RGB");
break;
@@ -145,6 +146,7 @@ final class IFFImageMetadata extends AbstractMetadata {
// PlanarConfiguration
IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration");
switch (formType) {
+ case TYPE_RGB8:
case TYPE_PBM:
planarConfiguration.setAttribute("value", "PixelInterleaved");
break;
@@ -187,10 +189,16 @@ final class IFFImageMetadata extends AbstractMetadata {
return Integer.toString(bitplanes);
case 24:
return "8 8 8";
+ case 25:
+ if (formType != TYPE_RGB8) {
+ throw new IllegalArgumentException(String.format("25 bit depth only supported for FORM type RGB8: %s", IFFUtil.toChunkStr(formType)));
+ }
+
+ return "8 8 8 1";
case 32:
return "8 8 8 8";
default:
- throw new IllegalArgumentException("Ubknown bit count: " + bitplanes);
+ throw new IllegalArgumentException("Unknown bit count: " + bitplanes);
}
}
@@ -246,13 +254,14 @@ final class IFFImageMetadata extends AbstractMetadata {
@Override
protected IIOMetadataNode getStandardTransparencyNode() {
- if ((colorMap == null || !colorMap.hasAlpha()) && header.bitplanes != 32) {
+ // TODO: Make sure 25 bit is only RGB8...
+ if ((colorMap == null || !colorMap.hasAlpha()) && header.bitplanes != 32 && header.bitplanes != 25) {
return null;
}
IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
- if (header.bitplanes == 32) {
+ if (header.bitplanes == 25 || header.bitplanes == 32) {
IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
alpha.setAttribute("value", "nonpremultiplied");
transparency.appendChild(alpha);
diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReader.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReader.java
index d3c8f6ef..6c752711 100755
--- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReader.java
+++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReader.java
@@ -32,6 +32,7 @@ package com.twelvemonkeys.imageio.plugins.iff;
import com.twelvemonkeys.image.ResampleOp;
import com.twelvemonkeys.imageio.ImageReaderBase;
+import com.twelvemonkeys.imageio.color.ColorSpaces;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import com.twelvemonkeys.io.enc.DecoderStream;
@@ -154,7 +155,7 @@ public final class IFFImageReader extends ImageReaderBase {
int remaining = imageInput.readInt() - 4; // We'll read 4 more in a sec
formType = imageInput.readInt();
- if (formType != IFF.TYPE_ILBM && formType != IFF.TYPE_PBM/* && formType != IFF.TYPE_DEEP*/) {
+ if (formType != IFF.TYPE_ILBM && formType != IFF.TYPE_PBM && formType != IFF.TYPE_RGB8 && formType != IFF.TYPE_DEEP) {
throw new IIOException(String.format("Only IFF FORM types 'ILBM' and 'PBM ' supported: %s", IFFUtil.toChunkStr(formType)));
}
@@ -381,26 +382,32 @@ public final class IFFImageReader extends ImageReaderBase {
if (!isConvertToRGB()) {
if (colorMap != null) {
IndexColorModel cm = colorMap.getIndexColorModel(header, isEHB());
- specifier = ImageTypeSpecifiers.createFromIndexColorModel(cm);
+ return ImageTypeSpecifiers.createFromIndexColorModel(cm);
}
else {
- specifier = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY);
+ return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY);
}
- break;
}
// NOTE: HAM modes falls through, as they are converted to RGB
case 24:
// 24 bit RGB
- specifier = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
- break;
+ return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
+
+ case 25: // TYPE_RGB8: 24 bit + 1 bit mask (we'll convert to full alpha during decoding)
+ if (formType != IFF.TYPE_RGB8) {
+ throw new IIOException(String.format("25 bit depth only supported for FORM type RGB8: %s", IFFUtil.toChunkStr(formType)));
+ }
+
+ return ImageTypeSpecifiers.createInterleaved(ColorSpaces.getColorSpace(ColorSpace.CS_sRGB),
+ new int[] {0, 1, 2, 3}, DataBuffer.TYPE_BYTE, true, false);
+
case 32:
// 32 bit ARGB
- specifier = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR);
- break;
+ return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR);
+
default:
throw new IIOException(String.format("Bit depth not implemented: %d", header.bitplanes));
}
- return specifier;
}
private boolean isConvertToRGB() {
@@ -411,15 +418,17 @@ public final class IFFImageReader extends ImageReaderBase {
imageInput.seek(bodyStart);
byteRunStream = null;
- // NOTE: colorMap may be null for 8 bit (gray), 24 bit or 32 bit only
- if (colorMap != null) {
+ if (formType == IFF.TYPE_RGB8) {
+ readRGB8(pParam, imageInput);
+ }
+ else if (colorMap != null) {
+ // NOTE: colorMap may be null for 8 bit (gray), 24 bit or 32 bit only
IndexColorModel cm = colorMap.getIndexColorModel(header, isEHB());
readIndexed(pParam, imageInput, cm);
}
else {
readTrueColor(pParam, imageInput);
}
-
}
private void readIndexed(final ImageReadParam pParam, final ImageInputStream pInput, final IndexColorModel pModel) throws IOException {
@@ -481,12 +490,7 @@ public final class IFFImageReader extends ImageReaderBase {
Raster sourceRow = raster.createChild(aoi.x, 0, aoi.width, 1, 0, 0, sourceBands);
final byte[] row = new byte[width * 8];
-
-// System.out.println("PlaneData length: " + planeData.length);
-// System.out.println("Row length: " + row.length);
-
final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
-
final int planes = header.bitplanes;
Object dataElements = null;
@@ -536,8 +540,6 @@ public final class IFFImageReader extends ImageReaderBase {
// Rasters are compatible, just write to destination
if (sourceXSubsampling == 1) {
destination.setRect(offset.x, dstY, sourceRow);
-// dataElements = raster.getDataElements(aoi.x, 0, aoi.width, 1, dataElements);
-// destination.setDataElements(offset.x, offset.y + (srcY - aoi.y) / sourceYSubsampling, aoi.width, 1, dataElements);
}
else {
for (int srcX = 0; srcX < sourceRow.getWidth(); srcX += sourceXSubsampling) {
@@ -581,6 +583,71 @@ public final class IFFImageReader extends ImageReaderBase {
}
}
+ private void readRGB8(ImageReadParam pParam, ImageInputStream pInput) throws IOException {
+ final int width = header.width;
+ final int height = header.height;
+
+ final Rectangle aoi = getSourceRegion(pParam, width, height);
+ final Point offset = pParam == null ? new Point(0, 0) : pParam.getDestinationOffset();
+
+ // Set everything to default values
+ int sourceXSubsampling = 1;
+ int sourceYSubsampling = 1;
+ int[] sourceBands = null;
+ int[] destinationBands = null;
+
+ // Get values from the ImageReadParam, if any
+ if (pParam != null) {
+ sourceXSubsampling = pParam.getSourceXSubsampling();
+ sourceYSubsampling = pParam.getSourceYSubsampling();
+
+ sourceBands = pParam.getSourceBands();
+ destinationBands = pParam.getDestinationBands();
+ }
+
+ // Ensure band settings from param are compatible with images
+ checkReadParamBandSettings(pParam, 4, image.getSampleModel().getNumBands());
+
+ WritableRaster destination = image.getRaster();
+ if (destinationBands != null || offset.x != 0 || offset.y != 0) {
+ destination = destination.createWritableChild(0, 0, destination.getWidth(), destination.getHeight(), offset.x, offset.y, destinationBands);
+ }
+
+ WritableRaster raster = image.getRaster().createCompatibleWritableRaster(width, 1);
+ Raster sourceRow = raster.createChild(aoi.x, 0, aoi.width, 1, 0, 0, sourceBands);
+
+ int planeWidth = width * 4;
+
+ final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
+ final int channels = (header.bitplanes + 7) / 8;
+
+ Object dataElements = null;
+
+ for (int srcY = 0; srcY < height; srcY++) {
+ readPlaneData(pInput, data, 0, planeWidth);
+
+ if (srcY >= aoi.y && (srcY - aoi.y) % sourceYSubsampling == 0) {
+ int dstY = (srcY - aoi.y) / sourceYSubsampling;
+ if (sourceXSubsampling == 1) {
+ destination.setRect(0, dstY, sourceRow);
+ }
+ else {
+ for (int srcX = 0; srcX < sourceRow.getWidth(); srcX += sourceXSubsampling) {
+ dataElements = sourceRow.getDataElements(srcX, 0, dataElements);
+ int dstX = srcX / sourceXSubsampling;
+ destination.setDataElements(dstX, dstY, dataElements);
+ }
+ }
+ }
+
+ processImageProgress(srcY * 100f / header.width);
+ if (abortRequested()) {
+ processReadAborted();
+ break;
+ }
+ }
+ }
+
// One row from each of the 24 bitplanes is written before moving to the
// next scanline. For each scanline, the red bitplane rows are stored first,
// followed by green and blue. The first plane holds the least significant
@@ -721,6 +788,24 @@ public final class IFFImageReader extends ImageReaderBase {
byteRunStream.readFully(pData, pOffset, pPlaneWidth);
break;
+ case 4: // Compression type 4 means different things for different FORM types... :-P
+ if (formType == IFF.TYPE_RGB8) {
+ // Impulse RGB8 RLE compression: 24 bit RGB + 1 bit mask + 7 bit run count
+ if (byteRunStream == null) {
+ byteRunStream = new DataInputStream(
+ new DecoderStream(
+ IIOUtil.createStreamAdapter(pInput, body.chunkLength),
+ new RGB8RLEDecoder(),
+ pPlaneWidth * 4
+ )
+ );
+ }
+
+ byteRunStream.readFully(pData, pOffset, pPlaneWidth);
+
+ break;
+ }
+
default:
throw new IIOException(String.format("Unknown compression type: %d", header.compressionType));
}
diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReaderSpi.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReaderSpi.java
index 92b2ec67..36e196a5 100755
--- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReaderSpi.java
+++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReaderSpi.java
@@ -30,13 +30,12 @@
package com.twelvemonkeys.imageio.plugins.iff;
-import java.io.IOException;
-import java.util.Locale;
+import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
-
-import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
+import java.io.IOException;
+import java.util.Locale;
/**
* IFFImageReaderSpi
@@ -67,9 +66,8 @@ public final class IFFImageReaderSpi extends ImageReaderSpiBase {
pInput.readInt();// Skip length field
int type = pInput.readInt();
-
- // Is it ILBM or PBM
- if (type == IFF.TYPE_ILBM || type == IFF.TYPE_PBM) {
+ if (type == IFF.TYPE_ILBM || type == IFF.TYPE_PBM
+ || type == IFF.TYPE_RGB8) { // Impulse RGB8
return true;
}
diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/RGB8RLEDecoder.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/RGB8RLEDecoder.java
new file mode 100644
index 00000000..461022b9
--- /dev/null
+++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/RGB8RLEDecoder.java
@@ -0,0 +1,57 @@
+package com.twelvemonkeys.imageio.plugins.iff;
+
+import com.twelvemonkeys.io.enc.DecodeException;
+import com.twelvemonkeys.io.enc.Decoder;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+/**
+ * Decoder implementation for Impulse FORM RGB8 RLE compression (type 4).
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: RGB8Stream.java,v 1.0 28/01/2022 haraldk Exp$
+ *
+ * @see RGBN and RGB8 IFF Image Data
+ */
+final class RGB8RLEDecoder implements Decoder {
+ public int decode(final InputStream stream, final ByteBuffer buffer) throws IOException {
+ while (buffer.remaining() >= 127 * 4) {
+ int r = stream.read();
+ int g = stream.read();
+ int b = stream.read();
+ int a = stream.read();
+
+ if (a < 0) {
+ // Normal EOF
+ if (r == -1) {
+ break;
+ }
+
+ // Partial pixel read...
+ throw new EOFException();
+ }
+
+ // Get "genlock" (transparency) bit + count
+ boolean alpha = (a & 0x80) != 0;
+ int count = a & 0x7f;
+ a = alpha ? 0 : (byte) 0xff; // convert to full transparent/opaque;
+
+ if (count == 0) {
+ throw new DecodeException("Multi-byte counts not supported");
+ }
+
+ for (int i = 0; i < count; i++) {
+ buffer.put((byte) r);
+ buffer.put((byte) g);
+ buffer.put((byte) b);
+ buffer.put((byte) a);
+ }
+ }
+
+ return buffer.position();
+ }
+}
diff --git a/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReaderTest.java b/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReaderTest.java
index db1c1993..1ca3102f 100755
--- a/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReaderTest.java
+++ b/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReaderTest.java
@@ -88,7 +88,11 @@ public class IFFImageReaderTest extends ImageReaderAbstractTest
// 16 color indexed, multi palette (PCHG) - Ok
new TestData(getClassLoaderResource("/iff/Manhattan.PCHG"), new Dimension(704, 440)),
// 16 color indexed, multi palette (PCHG + SHAM) - Ok
- new TestData(getClassLoaderResource("/iff/Somnambulist-2.SHAM"), new Dimension(704, 440))
+ new TestData(getClassLoaderResource("/iff/Somnambulist-2.SHAM"), new Dimension(704, 440)),
+ // Impulse RGB8 format straight from Imagine 2.0
+ new TestData(getClassLoaderResource("/iff/glowsphere2.rgb8"), new Dimension(640, 480)),
+ // Impulse RGB8 format written by ASDG ADPro, with cross boundary runs, which is probably not as per spec...
+ new TestData(getClassLoaderResource("/iff/tunnel04-adpro-cross-boundary-runs.rgb8"), new Dimension(640, 480))
);
}
diff --git a/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/RGB8RLEDecoderTest.java b/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/RGB8RLEDecoderTest.java
new file mode 100644
index 00000000..c45d357f
--- /dev/null
+++ b/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/RGB8RLEDecoderTest.java
@@ -0,0 +1,114 @@
+package com.twelvemonkeys.imageio.plugins.iff;
+
+import com.twelvemonkeys.io.enc.DecodeException;
+import com.twelvemonkeys.io.enc.Decoder;
+import com.twelvemonkeys.io.enc.DecoderAbstractTest;
+import com.twelvemonkeys.io.enc.Encoder;
+
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * RGB8RLEDecoderTest.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: RGB8RLEDecoderTest.java,v 1.0 28/01/2022 haraldk Exp$
+ */
+public class RGB8RLEDecoderTest extends DecoderAbstractTest {
+
+ public static final int BUFFER_SIZE = 1024;
+
+ @Override
+ public Decoder createDecoder() {
+ return new RGB8RLEDecoder();
+ }
+
+ @Override
+ public Encoder createCompatibleEncoder() {
+ return null;
+ }
+
+ @Test
+ public final void testDecodeEmpty() throws IOException {
+ Decoder decoder = createDecoder();
+ ByteArrayInputStream bytes = new ByteArrayInputStream(new byte[0]);
+
+ int count = decoder.decode(bytes, ByteBuffer.allocate(BUFFER_SIZE));
+ assertEquals("Should not be able to read any bytes", 0, count);
+ }
+
+ @Test(expected = EOFException.class)
+ public final void testDecodePartial() throws IOException {
+ Decoder decoder = createDecoder();
+ ByteArrayInputStream bytes = new ByteArrayInputStream(new byte[] {0});
+
+ decoder.decode(bytes, ByteBuffer.allocate(BUFFER_SIZE));
+ fail("Should not be able to read any bytes");
+ }
+
+ @Test(expected = EOFException.class)
+ public final void testDecodePartialToo() throws IOException {
+ Decoder decoder = createDecoder();
+ ByteArrayInputStream bytes = new ByteArrayInputStream(new byte[] {0, 0, 0, 1, 0, 0});
+
+ decoder.decode(bytes, ByteBuffer.allocate(BUFFER_SIZE));
+ fail("Should not be able to read any bytes");
+ }
+
+ @Test(expected = DecodeException.class)
+ public final void testDecodeZeroRun() throws IOException {
+ // The spec says that 0-runs should be used to signal that the run is > 127,
+ // and contained in the next byte, however, this is not used in practise and not supported.
+ Decoder decoder = createDecoder();
+ ByteArrayInputStream bytes = new ByteArrayInputStream(new byte[] {0, 0, 0, 0});
+
+ decoder.decode(bytes, ByteBuffer.allocate(BUFFER_SIZE));
+ fail("Should not be able to read any bytes");
+ }
+
+ @Test
+ public final void testDecodeSingleOpaque() throws IOException {
+ Decoder decoder = createDecoder();
+ ByteArrayInputStream bytes = new ByteArrayInputStream(new byte[] {0, 0, 0, 1});
+ ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
+
+ int count = decoder.decode(bytes, buffer);
+
+ assertEquals(4, count);
+ assertEquals(0x000000FF, buffer.getInt(0));
+ }
+
+ @Test
+ public final void testDecodeSingleTransparent() throws IOException {
+ Decoder decoder = createDecoder();
+ ByteArrayInputStream bytes = new ByteArrayInputStream(new byte[] {0, 0, 0, (byte) 0x81});
+ ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
+
+ int count = decoder.decode(bytes, buffer);
+
+ assertEquals(4, count);
+ assertEquals(0x00000000, buffer.getInt(0));
+ }
+
+ @Test
+ public final void testDecodeMaxRun() throws IOException {
+ Decoder decoder = createDecoder();
+ ByteArrayInputStream bytes = new ByteArrayInputStream(new byte[] {(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x7F});
+ ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
+
+ int count = decoder.decode(bytes, buffer);
+
+ assertEquals(127 * 4, count);
+ for (int i = 0; i < 127; i++) {
+ assertEquals(0xFFFFFFFF, buffer.getInt(i));
+ }
+ }
+}
\ No newline at end of file
diff --git a/imageio/imageio-iff/src/test/resources/iff/glowsphere2.rgb8 b/imageio/imageio-iff/src/test/resources/iff/glowsphere2.rgb8
new file mode 100644
index 00000000..c6a08e50
Binary files /dev/null and b/imageio/imageio-iff/src/test/resources/iff/glowsphere2.rgb8 differ
diff --git a/imageio/imageio-iff/src/test/resources/iff/tunnel04-adpro-cross-boundary-runs.rgb8 b/imageio/imageio-iff/src/test/resources/iff/tunnel04-adpro-cross-boundary-runs.rgb8
new file mode 100644
index 00000000..22bdefea
Binary files /dev/null and b/imageio/imageio-iff/src/test/resources/iff/tunnel04-adpro-cross-boundary-runs.rgb8 differ