diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/Form.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/Form.java index 781ea308..fcc79056 100644 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/Form.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/Form.java @@ -1,6 +1,7 @@ package com.twelvemonkeys.imageio.plugins.iff; import javax.imageio.IIOException; +import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.IndexColorModel; import java.util.ArrayList; @@ -59,6 +60,14 @@ abstract class Form { throw new UnsupportedOperationException(); } + public abstract boolean hasThumbnail(); + + public abstract int thumbnailWidth(); + + public abstract int thumbnailHeight(); + + public abstract BufferedImage thumbnail(); + abstract long bodyOffset(); abstract long bodyLength(); @@ -102,18 +111,20 @@ abstract class Form { private final CAMGChunk viewMode; private final CMAPChunk colorMap; private final AbstractMultiPaletteChunk multiPalette; + private final XS24Chunk thumbnail; private final BODYChunk body; ILBMForm(int formType) { - this(formType, null, null, null, null, null); + this(formType, null, null, null, null, null, null); } - private ILBMForm(final int formType, final BMHDChunk bitmapHeader, final CAMGChunk viewMode, final CMAPChunk colorMap, final AbstractMultiPaletteChunk multiPalette, final BODYChunk body) { + private ILBMForm(final int formType, final BMHDChunk bitmapHeader, final CAMGChunk viewMode, final CMAPChunk colorMap, final AbstractMultiPaletteChunk multiPalette, final XS24Chunk thumbnail, final BODYChunk body) { super(formType); this.bitmapHeader = bitmapHeader; this.viewMode = viewMode; this.colorMap = colorMap; this.multiPalette = multiPalette; + this.thumbnail = thumbnail; this.body = body; } @@ -180,6 +191,26 @@ abstract class Form { return multiPalette != null ? multiPalette.getColorModel(colorModel, row, isLaced()) : null; } + @Override + public boolean hasThumbnail() { + return thumbnail != null; + } + + @Override + public int thumbnailWidth() { + return thumbnail != null ? thumbnail.width : -1; + } + + @Override + public int thumbnailHeight() { + return thumbnail != null ? thumbnail.height : -1; + } + + @Override + public BufferedImage thumbnail() { + return thumbnail != null ? thumbnail.thumbnail() : null; + } + @Override long bodyOffset() { return body.chunkOffset; @@ -197,21 +228,21 @@ abstract class Form { throw new IIOException("Multiple BMHD chunks not allowed"); } - return new ILBMForm(formType, (BMHDChunk) chunk, null, colorMap, multiPalette, body); + return new ILBMForm(formType, (BMHDChunk) chunk, null, colorMap, multiPalette, thumbnail, body); } else if (chunk instanceof CAMGChunk) { if (viewMode != null) { throw new IIOException("Multiple CAMG chunks not allowed"); } - return new ILBMForm(formType, bitmapHeader, (CAMGChunk) chunk, colorMap, multiPalette, body); + return new ILBMForm(formType, bitmapHeader, (CAMGChunk) chunk, colorMap, multiPalette, thumbnail, body); } else if (chunk instanceof CMAPChunk) { if (colorMap != null) { throw new IIOException("Multiple CMAP chunks not allowed"); } - return new ILBMForm(formType, bitmapHeader, viewMode, (CMAPChunk) chunk, multiPalette, body); + return new ILBMForm(formType, bitmapHeader, viewMode, (CMAPChunk) chunk, multiPalette, thumbnail, body); } else if (chunk instanceof AbstractMultiPaletteChunk) { // NOTE: We prefer PHCG over SHAM/CTBL style palette changes, if both are present @@ -223,14 +254,21 @@ abstract class Form { return this; } - return new ILBMForm(formType, bitmapHeader, viewMode, colorMap, (AbstractMultiPaletteChunk) chunk, body); + return new ILBMForm(formType, bitmapHeader, viewMode, colorMap, (AbstractMultiPaletteChunk) chunk, thumbnail, body); + } + else if (chunk instanceof XS24Chunk) { + if (thumbnail != null) { + throw new IIOException("Multiple XS24 chunks not allowed"); + } + + return new ILBMForm(formType, bitmapHeader, viewMode, colorMap, multiPalette, (XS24Chunk) chunk, body); } else if (chunk instanceof BODYChunk) { if (body != null) { throw new IIOException("Multiple " + toChunkStr(chunk.chunkId) + " chunks not allowed"); } - return new ILBMForm(formType, bitmapHeader, viewMode, colorMap, multiPalette, (BODYChunk) chunk); + return new ILBMForm(formType, bitmapHeader, viewMode, colorMap, multiPalette, thumbnail, (BODYChunk) chunk); } else if (chunk instanceof GRABChunk) { // Ignored for now @@ -257,17 +295,19 @@ abstract class Form { private final DGBLChunk deepGlobal; private final DLOCChunk deepLocation; private final DPELChunk deepPixel; + private final XS24Chunk thumbnail; private final BODYChunk body; DEEPForm(int formType) { - this(formType, null, null, null, null); + this(formType, null, null, null, null, null); } - private DEEPForm(final int formType, final DGBLChunk deepGlobal, final DLOCChunk deepLocation, final DPELChunk deepPixel, final BODYChunk body) { + private DEEPForm(final int formType, final DGBLChunk deepGlobal, final DLOCChunk deepLocation, final DPELChunk deepPixel, final XS24Chunk thumbnail, final BODYChunk body) { super(formType); this.deepGlobal = deepGlobal; this.deepLocation = deepLocation; this.deepPixel = deepPixel; + this.thumbnail = thumbnail; this.body = body; } @@ -312,6 +352,26 @@ abstract class Form { return deepGlobal.yAspect; } + @Override + public boolean hasThumbnail() { + return thumbnail != null; + } + + @Override + public int thumbnailWidth() { + return thumbnail != null ? thumbnail.width : -1; + } + + @Override + public int thumbnailHeight() { + return thumbnail != null ? thumbnail.height : -1; + } + + @Override + public BufferedImage thumbnail() { + return thumbnail != null ? thumbnail.thumbnail() : null; + } + @Override long bodyOffset() { return body.chunkOffset; @@ -329,28 +389,35 @@ abstract class Form { throw new IIOException("Multiple DGBL chunks not allowed"); } - return new DEEPForm(formType, (DGBLChunk) chunk, null, null, body); + return new DEEPForm(formType, (DGBLChunk) chunk, null, null, thumbnail, body); } else if (chunk instanceof DLOCChunk) { if (deepLocation != null) { throw new IIOException("Multiple DLOC chunks not allowed"); } - return new DEEPForm(formType, deepGlobal, (DLOCChunk) chunk, deepPixel, body); + return new DEEPForm(formType, deepGlobal, (DLOCChunk) chunk, deepPixel, thumbnail, body); } else if (chunk instanceof DPELChunk) { if (deepPixel != null) { throw new IIOException("Multiple DPEL chunks not allowed"); } - return new DEEPForm(formType, deepGlobal, deepLocation, (DPELChunk) chunk, body); + return new DEEPForm(formType, deepGlobal, deepLocation, (DPELChunk) chunk, thumbnail, body); + } + else if (chunk instanceof XS24Chunk) { + if (thumbnail != null) { + throw new IIOException("Multiple XS24 chunks not allowed"); + } + + return new DEEPForm(formType, deepGlobal, deepLocation, deepPixel, (XS24Chunk) chunk, body); } else if (chunk instanceof BODYChunk) { if (body != null) { throw new IIOException("Multiple " + toChunkStr(chunk.chunkId) + " chunks not allowed"); } - return new DEEPForm(formType, deepGlobal, deepLocation, deepPixel, (BODYChunk) chunk); + return new DEEPForm(formType, deepGlobal, deepLocation, deepPixel, thumbnail, (BODYChunk) chunk); } return (DEEPForm) super.with(chunk); diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFF.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFF.java index 2276bb19..a6292302 100755 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFF.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFF.java @@ -143,4 +143,6 @@ interface IFF { int CHUNK_DBOD = ('D' << 24) + ('B' << 16) + ('O' << 8) + 'D'; /** TVPaint Deep CHanGe buffer */ int CHUNK_DCHG = ('D' << 24) + ('C' << 16) + ('H' << 8) + 'G'; + /** TVPaint 24 bit thumbnail */ + int CHUNK_XS24 = ('X' << 24) + ('S' << 16) + ('2' << 8) + '4'; } 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 90af52bf..82b9e158 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 @@ -255,7 +255,6 @@ final class IFFImageMetadata extends AbstractMetadata { } return text; - } @Override 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 d6406e28..6705de12 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 @@ -105,7 +105,6 @@ public final class IFFImageReader extends ImageReaderBase { // - Contains definitions of some "new" chunks, as well as alternative FORM types // http://amigan.1emu.net/index/iff.html - // TODO: XS24 chunk seems to be a raw 24 bit thumbnail for TVPaint images: XS24 <4 byte len> <2 byte width> <2 byte height> // TODO: Allow reading rasters for HAM6/HAM8 and multipalette images that are expanded to RGB (24 bit) during read. final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.iff.debug")); @@ -189,6 +188,12 @@ public final class IFFImageReader extends ImageReaderBase { header = header.with(deepPixel); break; + case IFF.CHUNK_XS24: + XS24Chunk thumbnail = new XS24Chunk(length); + thumbnail.readChunk(imageInput); + header = header.with(thumbnail); + break; + case IFF.CHUNK_CMAP: CMAPChunk colorMap = new CMAPChunk(length); colorMap.readChunk(imageInput); @@ -227,8 +232,8 @@ public final class IFFImageReader extends ImageReaderBase { case IFF.CHUNK_BODY: case IFF.CHUNK_DBOD: - BODYChunk body = new BODYChunk(chunkId, length, imageInput.getStreamPosition()); // NOTE: We don't read the body here, it's done later in the read(int, ImageReadParam) method + BODYChunk body = new BODYChunk(chunkId, length, imageInput.getStreamPosition()); header = header.with(body); // Done reading meta @@ -277,6 +282,60 @@ public final class IFFImageReader extends ImageReaderBase { return result; } + @Override + public boolean readerSupportsThumbnails() { + return true; + } + + @Override + public boolean hasThumbnails(final int imageIndex) throws IOException { + init(imageIndex); + return header.hasThumbnail(); + } + + @Override + public int getNumThumbnails(final int imageIndex) throws IOException { + init(imageIndex); + return header.hasThumbnail() ? 1 : 0; + } + + @Override + public int getThumbnailWidth(final int imageIndex, final int thumbnailIndex) throws IOException { + init(imageIndex); + if (!header.hasThumbnail() || thumbnailIndex > 1) { + throw new IndexOutOfBoundsException("thumbnailIndex out of bounds: " + thumbnailIndex); + } + + return header.thumbnailWidth(); + } + + @Override + public int getThumbnailHeight(final int imageIndex, final int thumbnailIndex) throws IOException { + init(imageIndex); + if (!header.hasThumbnail() || thumbnailIndex > 1) { + throw new IndexOutOfBoundsException("thumbnailIndex out of bounds: " + thumbnailIndex); + } + + return header.thumbnailHeight(); + } + + @Override + public BufferedImage readThumbnail(final int imageIndex, final int thumbnailIndex) throws IOException { + init(imageIndex); + if (!header.hasThumbnail() || thumbnailIndex > 1) { + throw new IndexOutOfBoundsException("thumbnailIndex out of bounds: " + thumbnailIndex); + } + + processThumbnailStarted(imageIndex, thumbnailIndex); + + BufferedImage thumbnail = header.thumbnail(); + + processThumbnailProgress(100f); + processThumbnailComplete(); + + return thumbnail; + } + @Override public int getWidth(int imageIndex) throws IOException { init(imageIndex); diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/XS24Chunk.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/XS24Chunk.java new file mode 100644 index 00000000..b7098d71 --- /dev/null +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/XS24Chunk.java @@ -0,0 +1,78 @@ +package com.twelvemonkeys.imageio.plugins.iff; + +import com.twelvemonkeys.imageio.color.ColorSpaces; + +import javax.imageio.IIOException; +import java.awt.*; +import java.awt.color.ColorSpace; +import java.awt.image.*; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +/** + * XS24Chunk. + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: XS24Chunk.java,v 1.0 01/02/2022 haraldk Exp$ + */ +final class XS24Chunk extends IFFChunk { + private byte[] data; + int width; + int height; + + XS24Chunk(final int chunkLength) { + super(IFF.CHUNK_XS24, chunkLength); + } + + @Override + void readChunk(final DataInput input) throws IOException { + width = input.readUnsignedShort(); + height = input.readUnsignedShort(); + input.readShort(); // Not sure what this is? + + int dataLength = width * height * 3; + if (dataLength > chunkLength - 6) { + throw new IIOException("Bad XS24 chunk: " + width + " * " + height + " * 3 > chunk length (" + chunkLength + ")"); + } + + System.err.println("chunkLength: " + chunkLength); + System.err.println("dataLength: " + dataLength); + + data = new byte[dataLength]; + + input.readFully(data); + + // Skip pad + for (int i = 0; i < chunkLength - dataLength - 6; i++) { + input.readByte(); + } + } + + @Override + void writeChunk(final DataOutput output) { + throw new InternalError("Not implemented: writeChunk()"); + } + + @Override + public String toString() { + return super.toString() + + "{thumbnail=" + data.length + '}'; + } + + public BufferedImage thumbnail() { + WritableRaster raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, width, height, width * 3, 3, new int[] {0, 1, 2}, null); +// WritableRaster raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, width, height, width * 3, 3, new int[] {2, 1, 0}, null); + ColorModel colorModel = new ComponentColorModel(ColorSpaces.getColorSpace(ColorSpace.CS_sRGB), false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE); +// raster.setDataElements(0, 0, width, height, data); + System.arraycopy(data, 0, ((DataBufferByte) raster.getDataBuffer()).getData(), 0, data.length); + +// BufferedImage thumbnail = ImageTypeSpecifiers.createInterleaved(ColorSpaces.getColorSpace(ColorSpace.CS_sRGB), new int[] {1, 2, 0}, DataBuffer.TYPE_BYTE, false, false) +// // BufferedImage thumbnail = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR) +// .createBufferedImage(width, height); +// thumbnail.getRaster().setDataElements(0, 0, width, height, data); +// return thumbnail; + return new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), null); + } +}