mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-03 19:45:28 -04:00
IFF: Thumbnail support for XS24 chunk.
(cherry picked from commit f5cfa0e619211b627d26417b13c8407b3063641b)
This commit is contained in:
parent
b7b2a61c93
commit
954dffd213
@ -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);
|
||||
|
@ -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';
|
||||
}
|
||||
|
@ -255,7 +255,6 @@ final class IFFImageMetadata extends AbstractMetadata {
|
||||
}
|
||||
|
||||
return text;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -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> <pixel data...>
|
||||
// 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);
|
||||
|
@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user