mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-04 20:15:28 -04:00
IFF: Read support for TVPaint DEEP and TVPP
+ Bonus: Massive code clean-up/refactor. (cherry picked from commit 73ad024833ed74618b81f44c95614845defc1282)
This commit is contained in:
parent
3cf6a4b836
commit
b7b2a61c93
@ -69,14 +69,14 @@ abstract class AbstractMultiPaletteChunk extends IFFChunk implements MultiPalett
|
||||
protected WeakReference<IndexColorModel> originalPalette;
|
||||
protected MutableIndexColorModel mutablePalette;
|
||||
|
||||
public AbstractMultiPaletteChunk(int pChunkId, int pChunkLength) {
|
||||
super(pChunkId, pChunkLength);
|
||||
public AbstractMultiPaletteChunk(int chunkId, int chunkLength) {
|
||||
super(chunkId, chunkLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
void readChunk(final DataInput pInput) throws IOException {
|
||||
void readChunk(final DataInput input) throws IOException {
|
||||
if (chunkId == IFF.CHUNK_SHAM) {
|
||||
pInput.readUnsignedShort(); // Version, typically 0, skipped
|
||||
input.readUnsignedShort(); // Version, typically 0, skipped
|
||||
}
|
||||
|
||||
int rows = chunkLength / 32; /* sizeof(word) * 16 */
|
||||
@ -91,7 +91,7 @@ abstract class AbstractMultiPaletteChunk extends IFFChunk implements MultiPalett
|
||||
}
|
||||
|
||||
for (int i = 0; i < 16; i++ ) {
|
||||
int data = pInput.readUnsignedShort();
|
||||
int data = input.readUnsignedShort();
|
||||
|
||||
changes[row][i].index = i;
|
||||
changes[row][i].r = (byte) (((data & 0x0f00) >> 8) * FACTOR_4BIT);
|
||||
@ -102,7 +102,7 @@ abstract class AbstractMultiPaletteChunk extends IFFChunk implements MultiPalett
|
||||
}
|
||||
|
||||
@Override
|
||||
void writeChunk(DataOutput pOutput) {
|
||||
void writeChunk(DataOutput output) {
|
||||
throw new UnsupportedOperationException("Method writeChunk not implemented");
|
||||
}
|
||||
|
||||
|
@ -109,64 +109,65 @@ final class BMHDChunk extends IFFChunk {
|
||||
int pageWidth;
|
||||
int pageHeight;
|
||||
|
||||
BMHDChunk(int pChunkLength) {
|
||||
super(IFF.CHUNK_BMHD, pChunkLength);
|
||||
BMHDChunk(int chunkLength) {
|
||||
super(IFF.CHUNK_BMHD, chunkLength);
|
||||
}
|
||||
|
||||
BMHDChunk(int pWidth, int pHeight, int pBitplanes, int pMaskType, int pCompressionType, int pTransparentIndex) {
|
||||
BMHDChunk(int width, int height, int bitplanes, int maskType, int compressionType, int transparentIndex) {
|
||||
super(IFF.CHUNK_BMHD, 20);
|
||||
width = pWidth;
|
||||
height = pHeight;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
xPos = 0;
|
||||
yPos = 0;
|
||||
bitplanes = pBitplanes;
|
||||
maskType = pMaskType;
|
||||
compressionType = pCompressionType;
|
||||
transparentIndex = pTransparentIndex;
|
||||
this.bitplanes = bitplanes;
|
||||
this.maskType = maskType;
|
||||
this.compressionType = compressionType;
|
||||
this.transparentIndex = transparentIndex;
|
||||
xAspect = 1;
|
||||
yAspect = 1;
|
||||
pageWidth = Math.min(pWidth, Short.MAX_VALUE); // For some reason, these are signed?
|
||||
pageHeight = Math.min(pHeight, Short.MAX_VALUE);
|
||||
pageWidth = Math.min(width, Short.MAX_VALUE); // For some reason, these are signed?
|
||||
pageHeight = Math.min(height, Short.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
void readChunk(final DataInput pInput) throws IOException {
|
||||
void readChunk(final DataInput input) throws IOException {
|
||||
if (chunkLength != 20) {
|
||||
throw new IIOException("Unknown BMHD chunk length: " + chunkLength);
|
||||
}
|
||||
width = pInput.readUnsignedShort();
|
||||
height = pInput.readUnsignedShort();
|
||||
xPos = pInput.readShort();
|
||||
yPos = pInput.readShort();
|
||||
bitplanes = pInput.readUnsignedByte();
|
||||
maskType = pInput.readUnsignedByte();
|
||||
compressionType = pInput.readUnsignedByte();
|
||||
pInput.readByte(); // PAD
|
||||
transparentIndex = pInput.readUnsignedShort();
|
||||
xAspect = pInput.readUnsignedByte();
|
||||
yAspect = pInput.readUnsignedByte();
|
||||
pageWidth = pInput.readShort();
|
||||
pageHeight = pInput.readShort();
|
||||
|
||||
width = input.readUnsignedShort();
|
||||
height = input.readUnsignedShort();
|
||||
xPos = input.readShort();
|
||||
yPos = input.readShort();
|
||||
bitplanes = input.readUnsignedByte();
|
||||
maskType = input.readUnsignedByte();
|
||||
compressionType = input.readUnsignedByte();
|
||||
input.readByte(); // PAD
|
||||
transparentIndex = input.readUnsignedShort();
|
||||
xAspect = input.readUnsignedByte();
|
||||
yAspect = input.readUnsignedByte();
|
||||
pageWidth = input.readShort();
|
||||
pageHeight = input.readShort();
|
||||
}
|
||||
|
||||
@Override
|
||||
void writeChunk(final DataOutput pOutput) throws IOException {
|
||||
pOutput.writeInt(chunkId);
|
||||
pOutput.writeInt(chunkLength);
|
||||
void writeChunk(final DataOutput output) throws IOException {
|
||||
output.writeInt(chunkId);
|
||||
output.writeInt(chunkLength);
|
||||
|
||||
pOutput.writeShort(width);
|
||||
pOutput.writeShort(height);
|
||||
pOutput.writeShort(xPos);
|
||||
pOutput.writeShort(yPos);
|
||||
pOutput.writeByte(bitplanes);
|
||||
pOutput.writeByte(maskType);
|
||||
pOutput.writeByte(compressionType);
|
||||
pOutput.writeByte(0); // PAD
|
||||
pOutput.writeShort(transparentIndex);
|
||||
pOutput.writeByte(xAspect);
|
||||
pOutput.writeByte(yAspect);
|
||||
pOutput.writeShort(pageWidth);
|
||||
pOutput.writeShort(pageHeight);
|
||||
output.writeShort(width);
|
||||
output.writeShort(height);
|
||||
output.writeShort(xPos);
|
||||
output.writeShort(yPos);
|
||||
output.writeByte(bitplanes);
|
||||
output.writeByte(maskType);
|
||||
output.writeByte(compressionType);
|
||||
output.writeByte(0); // PAD
|
||||
output.writeShort(transparentIndex);
|
||||
output.writeByte(xAspect);
|
||||
output.writeByte(yAspect);
|
||||
output.writeShort(pageWidth);
|
||||
output.writeShort(pageHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -33,6 +33,8 @@ package com.twelvemonkeys.imageio.plugins.iff;
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
|
||||
import static com.twelvemonkeys.lang.Validate.isTrue;
|
||||
|
||||
/**
|
||||
* BODYChunk
|
||||
*
|
||||
@ -40,17 +42,20 @@ import java.io.DataOutput;
|
||||
* @version $Id: BODYChunk.java,v 1.0 28.feb.2006 01:25:49 haku Exp$
|
||||
*/
|
||||
final class BODYChunk extends IFFChunk {
|
||||
BODYChunk(int pChunkLength) {
|
||||
super(IFF.CHUNK_BODY, pChunkLength);
|
||||
final long chunkOffset;
|
||||
|
||||
BODYChunk(int chunkId, int chunkLength, long chunkOffset) {
|
||||
super(isTrue(chunkId == IFF.CHUNK_BODY || chunkId == IFF.CHUNK_DBOD, chunkId, "Illegal body chunk: '%s'"), chunkLength);
|
||||
this.chunkOffset = chunkOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
void readChunk(final DataInput pInput) {
|
||||
void readChunk(final DataInput input) {
|
||||
throw new InternalError("BODY chunk should only be read from IFFImageReader");
|
||||
}
|
||||
|
||||
@Override
|
||||
void writeChunk(final DataOutput pOutput) {
|
||||
void writeChunk(final DataOutput output) {
|
||||
throw new InternalError("BODY chunk should only be written from IFFImageWriter");
|
||||
}
|
||||
}
|
||||
|
@ -48,21 +48,21 @@ final class CAMGChunk extends IFFChunk {
|
||||
|
||||
int camg;
|
||||
|
||||
CAMGChunk(int pLength) {
|
||||
super(IFF.CHUNK_CAMG, pLength);
|
||||
CAMGChunk(int chunkLength) {
|
||||
super(IFF.CHUNK_CAMG, chunkLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
void readChunk(final DataInput pInput) throws IOException {
|
||||
void readChunk(final DataInput input) throws IOException {
|
||||
if (chunkLength != 4) {
|
||||
throw new IIOException("Unknown CAMG chunk length: " + chunkLength);
|
||||
}
|
||||
|
||||
camg = pInput.readInt();
|
||||
camg = input.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
void writeChunk(final DataOutput pOutput) {
|
||||
void writeChunk(final DataOutput output) {
|
||||
throw new InternalError("Not implemented: writeChunk()");
|
||||
}
|
||||
|
||||
|
@ -31,9 +31,7 @@
|
||||
package com.twelvemonkeys.imageio.plugins.iff;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.IndexColorModel;
|
||||
import java.awt.image.WritableRaster;
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
@ -69,7 +67,7 @@ final class CMAPChunk extends IFFChunk {
|
||||
}
|
||||
|
||||
@Override
|
||||
void readChunk(final DataInput pInput) throws IOException {
|
||||
void readChunk(final DataInput input) throws IOException {
|
||||
int numColors = chunkLength / 3;
|
||||
|
||||
reds = new byte[numColors];
|
||||
@ -77,9 +75,9 @@ final class CMAPChunk extends IFFChunk {
|
||||
blues = reds.clone();
|
||||
|
||||
for (int i = 0; i < numColors; i++) {
|
||||
reds[i] = pInput.readByte();
|
||||
greens[i] = pInput.readByte();
|
||||
blues[i] = pInput.readByte();
|
||||
reds[i] = input.readByte();
|
||||
greens[i] = input.readByte();
|
||||
blues[i] = input.readByte();
|
||||
}
|
||||
|
||||
// TODO: When reading in a CMAP for 8-bit-per-gun display or
|
||||
@ -92,25 +90,25 @@ final class CMAPChunk extends IFFChunk {
|
||||
|
||||
// All chunks are WORD aligned (even sized), may need to read pad...
|
||||
if (chunkLength % 2 != 0) {
|
||||
pInput.readByte();
|
||||
input.readByte();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void writeChunk(final DataOutput pOutput) throws IOException {
|
||||
pOutput.writeInt(chunkId);
|
||||
pOutput.writeInt(chunkLength);
|
||||
void writeChunk(final DataOutput output) throws IOException {
|
||||
output.writeInt(chunkId);
|
||||
output.writeInt(chunkLength);
|
||||
|
||||
final int length = model.getMapSize();
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
pOutput.writeByte(model.getRed(i));
|
||||
pOutput.writeByte(model.getGreen(i));
|
||||
pOutput.writeByte(model.getBlue(i));
|
||||
output.writeByte(model.getRed(i));
|
||||
output.writeByte(model.getGreen(i));
|
||||
output.writeByte(model.getBlue(i));
|
||||
}
|
||||
|
||||
if (chunkLength % 2 != 0) {
|
||||
pOutput.writeByte(0); // PAD
|
||||
output.writeByte(0); // PAD
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,25 +117,11 @@ final class CMAPChunk extends IFFChunk {
|
||||
return super.toString() + " {colorMap=" + model + "}";
|
||||
}
|
||||
|
||||
BufferedImage createPaletteImage(final BMHDChunk header, boolean isEHB) throws IIOException {
|
||||
// Create a 1 x colors.length image
|
||||
IndexColorModel cm = getIndexColorModel(header, isEHB);
|
||||
WritableRaster raster = cm.createCompatibleWritableRaster(cm.getMapSize(), 1);
|
||||
byte[] pixel = null;
|
||||
|
||||
for (int x = 0; x < cm.getMapSize(); x++) {
|
||||
pixel = (byte[]) cm.getDataElements(cm.getRGB(x), pixel);
|
||||
raster.setDataElements(x, 0, pixel);
|
||||
}
|
||||
|
||||
return new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
||||
}
|
||||
|
||||
public IndexColorModel getIndexColorModel(final BMHDChunk header, boolean isEHB) throws IIOException {
|
||||
public IndexColorModel getIndexColorModel(final Form.ILBMForm header) throws IIOException {
|
||||
if (model == null) {
|
||||
int numColors = reds.length; // All arrays are same size
|
||||
|
||||
if (isEHB) {
|
||||
if (header.isEHB()) {
|
||||
if (numColors == 32) {
|
||||
reds = Arrays.copyOf(reds, numColors * 2);
|
||||
blues = Arrays.copyOf(blues, numColors * 2);
|
||||
@ -160,8 +144,9 @@ final class CMAPChunk extends IFFChunk {
|
||||
// Would it work to double to numbers of colors, and create an indexcolormodel,
|
||||
// with alpha, where all colors above the original color is all transparent?
|
||||
// This is a waste of time and space, of course...
|
||||
int transparent = header.maskType == BMHDChunk.MASK_TRANSPARENT_COLOR ? header.transparentIndex : -1;
|
||||
int bitplanes = header.bitplanes == 25 ? 8 : header.bitplanes;
|
||||
int transparent = header.transparentIndex();
|
||||
int bitplanes = header.bitplanes() == 25 ? 8 : header.bitplanes();
|
||||
|
||||
model = new IndexColorModel(bitplanes, reds.length, reds, greens, blues, transparent); // https://github.com/haraldk/TwelveMonkeys/issues/15
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,7 @@ package com.twelvemonkeys.imageio.plugins.iff;
|
||||
* @version $Id: CTBLChunk.java,v 1.0 30.03.12 14:53 haraldk Exp$
|
||||
*/
|
||||
final class CTBLChunk extends AbstractMultiPaletteChunk {
|
||||
CTBLChunk(int pChunkLength) {
|
||||
super(IFF.CHUNK_CTBL, pChunkLength);
|
||||
CTBLChunk(int chunkLength) {
|
||||
super(IFF.CHUNK_CTBL, chunkLength);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (c) 2022, 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 of the copyright holder 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 HOLDER 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.plugins.iff;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* DGBLChunk
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: DGBLChunk.java,v 1.0 28.feb.2006 02:10:07 haku Exp$
|
||||
*/
|
||||
final class DGBLChunk extends IFFChunk {
|
||||
|
||||
/*
|
||||
//
|
||||
struct DGBL = {
|
||||
//
|
||||
// Size of source display
|
||||
//
|
||||
UWORD DisplayWidth,DisplayHeight;
|
||||
//
|
||||
// Type of compression
|
||||
//
|
||||
UWORD Compression;
|
||||
//
|
||||
// Pixel aspect, a ration w:h
|
||||
//
|
||||
UBYTE xAspect,yAspect;
|
||||
};
|
||||
|
||||
*/
|
||||
int displayWidth;
|
||||
int displayHeight;
|
||||
int compressionType;
|
||||
int xAspect;
|
||||
int yAspect;
|
||||
|
||||
DGBLChunk(int chunkLength) {
|
||||
super(IFF.CHUNK_DGBL, chunkLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
void readChunk(final DataInput input) throws IOException {
|
||||
if (chunkLength != 8) {
|
||||
throw new IIOException("Unknown DBGL chunk length: " + chunkLength);
|
||||
}
|
||||
|
||||
displayWidth = input.readUnsignedShort();
|
||||
displayHeight = input.readUnsignedShort();
|
||||
compressionType = input.readUnsignedShort();
|
||||
xAspect = input.readUnsignedByte();
|
||||
yAspect = input.readUnsignedByte();
|
||||
}
|
||||
|
||||
@Override
|
||||
void writeChunk(final DataOutput output) {
|
||||
throw new InternalError("Not implemented: writeChunk()");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() +
|
||||
"{displayWidth=" + displayWidth +
|
||||
", displayHeight=" + displayHeight +
|
||||
", compression=" + compressionType +
|
||||
", xAspect=" + xAspect +
|
||||
", yAspect=" + yAspect +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright (c) 2022, 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 of the copyright holder 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 HOLDER 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.plugins.iff;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* DLOCChunk.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: DLOCChunk.java,v 1.0 31/01/2022 haraldk Exp$
|
||||
*/
|
||||
final class DLOCChunk extends IFFChunk {
|
||||
int width;
|
||||
int height;
|
||||
int x;
|
||||
int y;
|
||||
|
||||
DLOCChunk(final int chunkLength) {
|
||||
super(IFF.CHUNK_DLOC, chunkLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
void readChunk(final DataInput input) throws IOException {
|
||||
if (chunkLength != 8) {
|
||||
throw new IIOException("Unknown DLOC chunk length: " + chunkLength);
|
||||
}
|
||||
|
||||
width = input.readUnsignedShort();
|
||||
height = input.readUnsignedShort();
|
||||
x = input.readShort();
|
||||
y = input.readShort();
|
||||
}
|
||||
|
||||
@Override
|
||||
void writeChunk(final DataOutput output) {
|
||||
throw new InternalError("Not implemented: writeChunk()");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() +
|
||||
"{width=" + width +
|
||||
", height=" + height +
|
||||
", x=" + x +
|
||||
", y=" + y + '}';
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
package com.twelvemonkeys.imageio.plugins.iff;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* DPELChunk.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: DPELChunk.java,v 1.0 01/02/2022 haraldk Exp$
|
||||
*/
|
||||
final class DPELChunk extends IFFChunk {
|
||||
/*
|
||||
//
|
||||
// Chunk DPEL
|
||||
// ----------
|
||||
struct DPEL = {
|
||||
//
|
||||
// Number of pixel components
|
||||
//
|
||||
ULONG nElements;
|
||||
//
|
||||
// The TypeDepth structure is repeated nElement times to identify
|
||||
// the content of every pixel. Pixels will always be padded to
|
||||
// byte boundaries. The DBOD chunk will be padded to an even
|
||||
// longword boundary.
|
||||
//
|
||||
struct TypeDepth = {
|
||||
//
|
||||
// Type of data
|
||||
//
|
||||
UWORD cType;
|
||||
//
|
||||
// Bit depth of this type
|
||||
//
|
||||
UWORD cBitDepth;
|
||||
} typedepth[Nelements];
|
||||
};
|
||||
*/
|
||||
TypeDepth[] typeDepths;
|
||||
|
||||
DPELChunk(final int chunkLength) {
|
||||
super(IFF.CHUNK_DPEL, chunkLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
void readChunk(final DataInput input) throws IOException {
|
||||
int components = input.readInt(); // Strictly, it's unsigned, but that many components is unlikely...
|
||||
|
||||
if (chunkLength != 4 + components * 4) {
|
||||
throw new IIOException("Unsupported DPEL chunk length: " + chunkLength);
|
||||
}
|
||||
|
||||
typeDepths = new TypeDepth[components];
|
||||
|
||||
for (int i = 0; i < components; i++) {
|
||||
typeDepths[i] = new TypeDepth(input.readUnsignedShort(), input.readUnsignedShort());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void writeChunk(final DataOutput output) {
|
||||
throw new InternalError("Not implemented: writeChunk()");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString()
|
||||
+ "{typeDepths=" + Arrays.toString(typeDepths) + '}';
|
||||
}
|
||||
|
||||
public int bitsPerPixel() {
|
||||
int bitCount = 0;
|
||||
|
||||
for (TypeDepth typeDepth : typeDepths) {
|
||||
bitCount += typeDepth.bitDepth;
|
||||
}
|
||||
|
||||
return bitCount;
|
||||
}
|
||||
|
||||
static class TypeDepth {
|
||||
final int type;
|
||||
final int bitDepth;
|
||||
|
||||
TypeDepth(final int type, final int bitDepth) {
|
||||
this.type = type;
|
||||
this.bitDepth = bitDepth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TypeDepth{" +
|
||||
"type=" + type +
|
||||
", bits=" + bitDepth +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,364 @@
|
||||
package com.twelvemonkeys.imageio.plugins.iff;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import java.awt.image.ColorModel;
|
||||
import java.awt.image.IndexColorModel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.twelvemonkeys.imageio.plugins.iff.IFFUtil.toChunkStr;
|
||||
|
||||
/**
|
||||
* Form.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: Form.java,v 1.0 31/01/2022 haraldk Exp$
|
||||
*/
|
||||
abstract class Form {
|
||||
|
||||
final int formType;
|
||||
final List<GenericChunk> meta = new ArrayList<>();
|
||||
|
||||
Form(int formType) {
|
||||
this.formType = formType;
|
||||
}
|
||||
|
||||
abstract int width();
|
||||
abstract int height();
|
||||
abstract int xAspect();
|
||||
abstract int yAspect();
|
||||
abstract int bitplanes();
|
||||
abstract int compressionType();
|
||||
|
||||
boolean isMultiPalette() {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean isHAM() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean premultiplied() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public int sampleSize() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
public int transparentIndex() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
public IndexColorModel colorMap() throws IIOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
public ColorModel colorMapForRow(IndexColorModel colorModel, int row) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
abstract long bodyOffset();
|
||||
abstract long bodyLength();
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toChunkStr(formType);
|
||||
}
|
||||
|
||||
Form with(final IFFChunk chunk) throws IIOException {
|
||||
if (chunk instanceof GenericChunk) {
|
||||
// TODO: This feels kind of hackish, as it breaks the immutable design, perhaps we should just reconsider...
|
||||
meta.add((GenericChunk) chunk);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(chunk + " not supported in FORM type " + toChunkStr(formType));
|
||||
}
|
||||
|
||||
static Form ofType(int formType) {
|
||||
switch (formType) {
|
||||
case IFF.TYPE_ACBM:
|
||||
case IFF.TYPE_ILBM:
|
||||
case IFF.TYPE_PBM:
|
||||
case IFF.TYPE_RGB8:
|
||||
return new ILBMForm(formType);
|
||||
case IFF.TYPE_DEEP:
|
||||
case IFF.TYPE_TVPP:
|
||||
return new DEEPForm(formType);
|
||||
default:
|
||||
throw new IllegalArgumentException("FORM type " + toChunkStr(formType) + " not supported");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The set of chunks used in the "original" ILBM,
|
||||
* and also ACBM, PBM and RGB8 FORMs.
|
||||
*/
|
||||
static final class ILBMForm extends Form {
|
||||
private final BMHDChunk bitmapHeader;
|
||||
private final CAMGChunk viewMode;
|
||||
private final CMAPChunk colorMap;
|
||||
private final AbstractMultiPaletteChunk multiPalette;
|
||||
private final BODYChunk body;
|
||||
|
||||
ILBMForm(int formType) {
|
||||
this(formType, 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) {
|
||||
super(formType);
|
||||
this.bitmapHeader = bitmapHeader;
|
||||
this.viewMode = viewMode;
|
||||
this.colorMap = colorMap;
|
||||
this.multiPalette = multiPalette;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
@Override
|
||||
int width() {
|
||||
return bitmapHeader.width;
|
||||
}
|
||||
|
||||
@Override
|
||||
int height() {
|
||||
return bitmapHeader.height;
|
||||
}
|
||||
|
||||
@Override
|
||||
int bitplanes() {
|
||||
return bitmapHeader.bitplanes;
|
||||
}
|
||||
|
||||
@Override
|
||||
int compressionType() {
|
||||
return bitmapHeader.compressionType;
|
||||
}
|
||||
|
||||
@Override
|
||||
int xAspect() {
|
||||
return bitmapHeader.xAspect;
|
||||
}
|
||||
|
||||
@Override
|
||||
int yAspect() {
|
||||
return bitmapHeader.yAspect;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isMultiPalette() {
|
||||
return multiPalette != null;
|
||||
}
|
||||
|
||||
boolean isEHB() {
|
||||
return viewMode != null && viewMode.isEHB();
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isHAM() {
|
||||
return viewMode != null && viewMode.isHAM();
|
||||
}
|
||||
|
||||
boolean isLaced() {
|
||||
return viewMode != null && viewMode.isLaced();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int transparentIndex() {
|
||||
return bitmapHeader.maskType == BMHDChunk.MASK_TRANSPARENT_COLOR ? bitmapHeader.transparentIndex : -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IndexColorModel colorMap() throws IIOException {
|
||||
return colorMap != null ? colorMap.getIndexColorModel(this) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ColorModel colorMapForRow(final IndexColorModel colorModel, final int row) {
|
||||
return multiPalette != null ? multiPalette.getColorModel(colorModel, row, isLaced()) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
long bodyOffset() {
|
||||
return body.chunkOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
long bodyLength() {
|
||||
return body.chunkLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
ILBMForm with(final IFFChunk chunk) throws IIOException {
|
||||
if (chunk instanceof BMHDChunk) {
|
||||
if (bitmapHeader != null) {
|
||||
throw new IIOException("Multiple BMHD chunks not allowed");
|
||||
}
|
||||
|
||||
return new ILBMForm(formType, (BMHDChunk) chunk, null, colorMap, multiPalette, 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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
else if (chunk instanceof AbstractMultiPaletteChunk) {
|
||||
// NOTE: We prefer PHCG over SHAM/CTBL style palette changes, if both are present
|
||||
if (multiPalette instanceof PCHGChunk) {
|
||||
if (chunk instanceof PCHGChunk) {
|
||||
throw new IIOException("Multiple PCHG/SHAM/CTBL chunks not allowed");
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
return new ILBMForm(formType, bitmapHeader, viewMode, colorMap, (AbstractMultiPaletteChunk) 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);
|
||||
}
|
||||
else if (chunk instanceof GRABChunk) {
|
||||
// Ignored for now
|
||||
return this;
|
||||
}
|
||||
|
||||
return (ILBMForm) super.with(chunk);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + '{' + bitmapHeader +
|
||||
(viewMode != null ? ", " + viewMode : "" ) +
|
||||
(colorMap != null ? ", " + colorMap : "" ) +
|
||||
(multiPalette != null ? ", " + multiPalette : "" ) +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The set of chunks used in DEEP and TVPP FORMs.
|
||||
*/
|
||||
private static final class DEEPForm extends Form {
|
||||
private final DGBLChunk deepGlobal;
|
||||
private final DLOCChunk deepLocation;
|
||||
private final DPELChunk deepPixel;
|
||||
private final BODYChunk body;
|
||||
|
||||
DEEPForm(int formType) {
|
||||
this(formType, null, null, null, null);
|
||||
}
|
||||
|
||||
private DEEPForm(final int formType, final DGBLChunk deepGlobal, final DLOCChunk deepLocation, final DPELChunk deepPixel, final BODYChunk body) {
|
||||
super(formType);
|
||||
this.deepGlobal = deepGlobal;
|
||||
this.deepLocation = deepLocation;
|
||||
this.deepPixel = deepPixel;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
int width() {
|
||||
return deepLocation.width;
|
||||
}
|
||||
|
||||
@Override
|
||||
int height() {
|
||||
return deepLocation.height;
|
||||
}
|
||||
|
||||
@Override
|
||||
int bitplanes() {
|
||||
return deepPixel.bitsPerPixel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sampleSize() {
|
||||
return bitplanes() / 8;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean premultiplied() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
int compressionType() {
|
||||
return deepGlobal.compressionType;
|
||||
}
|
||||
|
||||
@Override
|
||||
int xAspect() {
|
||||
return deepGlobal.xAspect;
|
||||
}
|
||||
|
||||
@Override
|
||||
int yAspect() {
|
||||
return deepGlobal.yAspect;
|
||||
}
|
||||
|
||||
@Override
|
||||
long bodyOffset() {
|
||||
return body.chunkOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
long bodyLength() {
|
||||
return body.chunkLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
DEEPForm with(final IFFChunk chunk) throws IIOException {
|
||||
if (chunk instanceof DGBLChunk) {
|
||||
if (deepGlobal != null) {
|
||||
throw new IIOException("Multiple DGBL chunks not allowed");
|
||||
}
|
||||
|
||||
return new DEEPForm(formType, (DGBLChunk) chunk, null, null, 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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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 (DEEPForm) super.with(chunk);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + '{' + deepGlobal + ", " + deepLocation + ", " + deepPixel + '}';
|
||||
}
|
||||
}
|
||||
}
|
@ -50,25 +50,25 @@ final class GRABChunk extends IFFChunk {
|
||||
|
||||
Point2D point;
|
||||
|
||||
GRABChunk(int pChunkLength) {
|
||||
super(IFF.CHUNK_GRAB, pChunkLength);
|
||||
GRABChunk(int chunkLength) {
|
||||
super(IFF.CHUNK_GRAB, chunkLength);
|
||||
}
|
||||
|
||||
GRABChunk(Point2D pPoint) {
|
||||
GRABChunk(Point2D point) {
|
||||
super(IFF.CHUNK_GRAB, 4);
|
||||
point = pPoint;
|
||||
this.point = point;
|
||||
}
|
||||
|
||||
void readChunk(DataInput pInput) throws IOException {
|
||||
void readChunk(DataInput input) throws IOException {
|
||||
if (chunkLength != 4) {
|
||||
throw new IIOException("Unknown GRAB chunk size: " + chunkLength);
|
||||
}
|
||||
point = new Point(pInput.readShort(), pInput.readShort());
|
||||
point = new Point(input.readShort(), input.readShort());
|
||||
}
|
||||
|
||||
void writeChunk(DataOutput pOutput) throws IOException {
|
||||
pOutput.writeShort((int) point.getX());
|
||||
pOutput.writeShort((int) point.getY());
|
||||
void writeChunk(DataOutput output) throws IOException {
|
||||
output.writeShort((int) point.getX());
|
||||
output.writeShort((int) point.getY());
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
|
@ -44,31 +44,31 @@ final class GenericChunk extends IFFChunk {
|
||||
|
||||
byte[] data;
|
||||
|
||||
GenericChunk(int pChunkId, int pChunkLength) {
|
||||
super(pChunkId, pChunkLength);
|
||||
data = new byte[chunkLength];
|
||||
GenericChunk(int chunkId, int chunkLength) {
|
||||
super(chunkId, chunkLength);
|
||||
data = new byte[this.chunkLength];
|
||||
}
|
||||
|
||||
GenericChunk(int pChunkId, byte[] pChunkData) {
|
||||
super(pChunkId, pChunkData.length);
|
||||
data = pChunkData;
|
||||
GenericChunk(int chunkId, byte[] chunkData) {
|
||||
super(chunkId, chunkData.length);
|
||||
data = chunkData;
|
||||
}
|
||||
|
||||
@Override
|
||||
void readChunk(final DataInput pInput) throws IOException {
|
||||
pInput.readFully(data, 0, data.length);
|
||||
void readChunk(final DataInput input) throws IOException {
|
||||
input.readFully(data, 0, data.length);
|
||||
|
||||
skipData(pInput, chunkLength, data.length);
|
||||
skipData(input, chunkLength, data.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
void writeChunk(final DataOutput pOutput) throws IOException {
|
||||
pOutput.writeInt(chunkId);
|
||||
pOutput.writeInt(chunkLength);
|
||||
pOutput.write(data, 0, data.length);
|
||||
void writeChunk(final DataOutput output) throws IOException {
|
||||
output.writeInt(chunkId);
|
||||
output.writeInt(chunkLength);
|
||||
output.write(data, 0, data.length);
|
||||
|
||||
if (data.length % 2 != 0) {
|
||||
pOutput.writeByte(0); // PAD
|
||||
output.writeByte(0); // PAD
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,6 +49,8 @@ interface IFF {
|
||||
// TODO:
|
||||
/** IFF DEEP form type (TVPaint) */
|
||||
int TYPE_DEEP = ('D' << 24) + ('E' << 16) + ('E' << 8) + 'P';
|
||||
/** IFF TVPP form type (TVPaint Project) */
|
||||
int TYPE_TVPP = ('T' << 24) + ('V' << 16) + ('P' << 8) + 'P';
|
||||
/** IFF RGB8 form type (TurboSilver) */
|
||||
int TYPE_RGB8 = ('R' << 24) + ('G' << 16) + ('B' << 8) + '8';
|
||||
/** IFF RGBN form type (TurboSilver) */
|
||||
@ -92,7 +94,7 @@ interface IFF {
|
||||
int CHUNK_COPY = ('(' << 24) + ('c' << 16) + (')' << 8) + ' ';
|
||||
|
||||
/** EA IFF 85 Generic annotation chunk (usually used for Software) */
|
||||
int CHUNK_ANNO = ('A' << 24) + ('N' << 16) + ('N' << 8) + 'O';;
|
||||
int CHUNK_ANNO = ('A' << 24) + ('N' << 16) + ('N' << 8) + 'O';
|
||||
|
||||
/** Third-party defined UTF-8 text. */
|
||||
int CHUNK_UTF8 = ('U' << 24) + ('T' << 16) + ('F' << 8) + '8';
|
||||
@ -129,6 +131,16 @@ interface IFF {
|
||||
int CHUNK_SHAM = ('S' << 24) + ('H' << 16) + ('A' << 8) + 'M';
|
||||
/** ACBM body chunk */
|
||||
int CHUNK_ABIT = ('A' << 24) + ('B' << 16) + ('I' << 8) + 'T';
|
||||
/** unofficial direct color */
|
||||
/** Unofficial direct color */
|
||||
int CHUNK_DCOL = ('D' << 24) + ('C' << 16) + ('O' << 8) + 'L';
|
||||
/** TVPaint Deep GloBaL information */
|
||||
int CHUNK_DGBL = ('D' << 24) + ('G' << 16) + ('B' << 8) + 'L';
|
||||
/** TVPaint Deep Pixel ELements */
|
||||
int CHUNK_DPEL = ('D' << 24) + ('P' << 16) + ('E' << 8) + 'L';
|
||||
/** TVPaint Deep LOCation information */
|
||||
int CHUNK_DLOC = ('D' << 24) + ('L' << 16) + ('O' << 8) + 'C';
|
||||
/** TVPaint Deep BODy */
|
||||
int CHUNK_DBOD = ('D' << 24) + ('B' << 16) + ('O' << 8) + 'D';
|
||||
/** TVPaint Deep CHanGe buffer */
|
||||
int CHUNK_DCHG = ('D' << 24) + ('C' << 16) + ('H' << 8) + 'G';
|
||||
}
|
||||
|
@ -44,25 +44,25 @@ abstract class IFFChunk {
|
||||
int chunkId;
|
||||
int chunkLength;
|
||||
|
||||
protected IFFChunk(int pChunkId, int pChunkLength) {
|
||||
chunkId = pChunkId;
|
||||
chunkLength = pChunkLength;
|
||||
protected IFFChunk(int chunkId, int chunkLength) {
|
||||
this.chunkId = chunkId;
|
||||
this.chunkLength = chunkLength;
|
||||
}
|
||||
|
||||
abstract void readChunk(DataInput pInput) throws IOException;
|
||||
abstract void readChunk(DataInput input) throws IOException;
|
||||
|
||||
abstract void writeChunk(DataOutput pOutput) throws IOException;
|
||||
abstract void writeChunk(DataOutput output) throws IOException;
|
||||
|
||||
protected static void skipData(final DataInput pInput, final int chunkLength, final int dataReadSoFar) throws IOException {
|
||||
protected static void skipData(final DataInput input, final int chunkLength, final int dataReadSoFar) throws IOException {
|
||||
int toSkip = chunkLength - dataReadSoFar;
|
||||
|
||||
while (toSkip > 0) {
|
||||
toSkip -= pInput.skipBytes(toSkip);
|
||||
toSkip -= input.skipBytes(toSkip);
|
||||
}
|
||||
|
||||
// Read pad
|
||||
if (chunkLength % 2 != 0) {
|
||||
pInput.readByte();
|
||||
input.readByte();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,31 +13,29 @@ import static com.twelvemonkeys.lang.Validate.isTrue;
|
||||
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||
|
||||
final class IFFImageMetadata extends AbstractMetadata {
|
||||
private final int formType;
|
||||
private final BMHDChunk header;
|
||||
private final Form header;
|
||||
private final IndexColorModel colorMap;
|
||||
private final CAMGChunk viewPort;
|
||||
private final List<GenericChunk> meta;
|
||||
|
||||
IFFImageMetadata(int formType, BMHDChunk header, IndexColorModel colorMap, CAMGChunk viewPort, List<GenericChunk> meta) {
|
||||
this.formType = isTrue(validFormType(formType), formType, "Unknown IFF Form type: %s");
|
||||
IFFImageMetadata(Form header, IndexColorModel colorMap) {
|
||||
this.header = notNull(header, "header");
|
||||
isTrue(validFormType(header.formType), header.formType, "Unknown IFF Form type: %s");
|
||||
this.colorMap = colorMap;
|
||||
this.viewPort = viewPort;
|
||||
this.meta = meta;
|
||||
this.meta = header.meta;
|
||||
}
|
||||
|
||||
private boolean validFormType(int formType) {
|
||||
switch (formType) {
|
||||
default:
|
||||
return false;
|
||||
case TYPE_ACBM:
|
||||
case TYPE_DEEP:
|
||||
case TYPE_ILBM:
|
||||
case TYPE_PBM:
|
||||
case TYPE_RGB8:
|
||||
case TYPE_RGBN:
|
||||
case TYPE_TVPP:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,7 +46,7 @@ final class IFFImageMetadata extends AbstractMetadata {
|
||||
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
|
||||
chroma.appendChild(csType);
|
||||
|
||||
switch (header.bitplanes) {
|
||||
switch (header.bitplanes()) {
|
||||
case 8:
|
||||
if (colorMap == null) {
|
||||
csType.setAttribute("name", "GRAY");
|
||||
@ -73,10 +71,10 @@ final class IFFImageMetadata extends AbstractMetadata {
|
||||
// NOTE: Channels in chroma node reflects channels in color model (see data node, for channels in data)
|
||||
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
|
||||
chroma.appendChild(numChannels);
|
||||
if (colorMap == null && header.bitplanes == 8) {
|
||||
if (colorMap == null && header.bitplanes() == 8) {
|
||||
numChannels.setAttribute("value", Integer.toString(1));
|
||||
}
|
||||
else if (header.bitplanes == 32) {
|
||||
else if (header.bitplanes() == 25 || header.bitplanes() == 32) {
|
||||
numChannels.setAttribute("value", Integer.toString(4));
|
||||
}
|
||||
else {
|
||||
@ -103,9 +101,16 @@ final class IFFImageMetadata extends AbstractMetadata {
|
||||
paletteEntry.setAttribute("green", Integer.toString(colorMap.getGreen(i)));
|
||||
paletteEntry.setAttribute("blue", Integer.toString(colorMap.getBlue(i)));
|
||||
}
|
||||
|
||||
if (colorMap.getTransparentPixel() != -1) {
|
||||
IIOMetadataNode backgroundIndex = new IIOMetadataNode("BackgroundIndex");
|
||||
chroma.appendChild(backgroundIndex);
|
||||
backgroundIndex.setAttribute("value", Integer.toString(colorMap.getTransparentPixel()));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Background color is the color of the transparent index in the color model?
|
||||
// TODO: TVPP TVPaint Project files have a MIXR chunk with a background color
|
||||
// and also a BGP1 (background pen 1?) and BGP2 chunks
|
||||
// if (extensions != null && extensions.getBackgroundColor() != 0) {
|
||||
// Color background = new Color(extensions.getBackgroundColor(), true);
|
||||
//
|
||||
@ -122,7 +127,7 @@ final class IFFImageMetadata extends AbstractMetadata {
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardCompressionNode() {
|
||||
if (header.compressionType == BMHDChunk.COMPRESSION_NONE) {
|
||||
if (header.compressionType() == BMHDChunk.COMPRESSION_NONE) {
|
||||
return null; // All defaults
|
||||
}
|
||||
|
||||
@ -145,7 +150,9 @@ final class IFFImageMetadata extends AbstractMetadata {
|
||||
|
||||
// PlanarConfiguration
|
||||
IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration");
|
||||
switch (formType) {
|
||||
switch (header.formType) {
|
||||
case TYPE_DEEP:
|
||||
case TYPE_TVPP:
|
||||
case TYPE_RGB8:
|
||||
case TYPE_PBM:
|
||||
planarConfiguration.setAttribute("value", "PixelInterleaved");
|
||||
@ -154,7 +161,7 @@ final class IFFImageMetadata extends AbstractMetadata {
|
||||
planarConfiguration.setAttribute("value", "PlaneInterleaved");
|
||||
break;
|
||||
default:
|
||||
planarConfiguration.setAttribute("value", "Unknown " + IFFUtil.toChunkStr(formType));
|
||||
planarConfiguration.setAttribute("value", "Unknown " + IFFUtil.toChunkStr(header.formType));
|
||||
break;
|
||||
}
|
||||
data.appendChild(planarConfiguration);
|
||||
@ -165,7 +172,7 @@ final class IFFImageMetadata extends AbstractMetadata {
|
||||
|
||||
// BitsPerSample
|
||||
IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
|
||||
String value = bitsPerSampleValue(header.bitplanes);
|
||||
String value = bitsPerSampleValue(header.bitplanes());
|
||||
bitsPerSample.setAttribute("value", value);
|
||||
data.appendChild(bitsPerSample);
|
||||
|
||||
@ -173,7 +180,6 @@ final class IFFImageMetadata extends AbstractMetadata {
|
||||
// SampleMSB not in format
|
||||
|
||||
return data;
|
||||
|
||||
}
|
||||
|
||||
private String bitsPerSampleValue(int bitplanes) {
|
||||
@ -190,8 +196,8 @@ final class IFFImageMetadata extends AbstractMetadata {
|
||||
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)));
|
||||
if (header.formType != TYPE_RGB8) {
|
||||
throw new IllegalArgumentException(String.format("25 bit depth only supported for FORM type RGB8: %s", IFFUtil.toChunkStr(header.formType)));
|
||||
}
|
||||
|
||||
return "8 8 8 1";
|
||||
@ -204,7 +210,7 @@ final class IFFImageMetadata extends AbstractMetadata {
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDimensionNode() {
|
||||
if (viewPort == null) {
|
||||
if (header.xAspect() == 0 || header.yAspect() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -212,7 +218,7 @@ final class IFFImageMetadata extends AbstractMetadata {
|
||||
|
||||
// PixelAspectRatio
|
||||
IIOMetadataNode pixelAspectRatio = new IIOMetadataNode("PixelAspectRatio");
|
||||
pixelAspectRatio.setAttribute("value", String.valueOf((viewPort.isHires() ? 2f : 1f) / (viewPort.isLaced() ? 2f : 1f)));
|
||||
pixelAspectRatio.setAttribute("value", String.valueOf(header.xAspect() / (float) header.yAspect()));
|
||||
dimension.appendChild(pixelAspectRatio);
|
||||
|
||||
// TODO: HorizontalScreenSize?
|
||||
@ -254,16 +260,15 @@ final class IFFImageMetadata extends AbstractMetadata {
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardTransparencyNode() {
|
||||
// TODO: Make sure 25 bit is only RGB8...
|
||||
if ((colorMap == null || !colorMap.hasAlpha()) && header.bitplanes != 32 && header.bitplanes != 25) {
|
||||
if ((colorMap == null || !colorMap.hasAlpha()) && header.bitplanes() != 32 && header.bitplanes() != 25) {
|
||||
return null;
|
||||
}
|
||||
|
||||
IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
|
||||
|
||||
if (header.bitplanes == 25 || header.bitplanes == 32) {
|
||||
if (header.bitplanes() == 25 || header.bitplanes() == 32) {
|
||||
IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
|
||||
alpha.setAttribute("value", "nonpremultiplied");
|
||||
alpha.setAttribute("value", header.premultiplied() ? "premultiplied" : "nonpremultiplied");
|
||||
transparency.appendChild(alpha);
|
||||
}
|
||||
|
||||
|
@ -48,14 +48,15 @@ import java.awt.image.*;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import static com.twelvemonkeys.imageio.plugins.iff.IFFUtil.toChunkStr;
|
||||
|
||||
/**
|
||||
* Reader for Commodore Amiga (Electronic Arts) IFF ILBM (InterLeaved BitMap) and PBM
|
||||
* format (Packed BitMap).
|
||||
* format (Packed BitMap). Also supports IFF RGB8 (Impulse) and IFF DEEP (TVPaint).
|
||||
* The IFF format (Interchange File Format) is the standard file format
|
||||
* supported by allmost all image software for the Amiga computer.
|
||||
* <p>
|
||||
@ -104,20 +105,12 @@ 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.
|
||||
|
||||
private BMHDChunk header;
|
||||
private CMAPChunk colorMap;
|
||||
private BODYChunk body;
|
||||
@SuppressWarnings({"FieldCanBeLocal"})
|
||||
private GRABChunk grab;
|
||||
private CAMGChunk viewPort;
|
||||
private MultiPalette paletteChange;
|
||||
private final List<GenericChunk> meta = new ArrayList<>();
|
||||
private int formType;
|
||||
private long bodyStart;
|
||||
final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.iff.debug"));
|
||||
|
||||
private BufferedImage image;
|
||||
private Form header;
|
||||
private DataInputStream byteRunStream;
|
||||
|
||||
IFFImageReader(ImageReaderSpi pProvider) {
|
||||
@ -135,35 +128,30 @@ public final class IFFImageReader extends ImageReaderBase {
|
||||
@Override
|
||||
protected void resetMembers() {
|
||||
header = null;
|
||||
colorMap = null;
|
||||
paletteChange = null;
|
||||
body = null;
|
||||
viewPort = null;
|
||||
formType = 0;
|
||||
meta.clear();
|
||||
|
||||
image = null;
|
||||
byteRunStream = null;
|
||||
}
|
||||
|
||||
private void readMeta() throws IOException {
|
||||
int chunkType = imageInput.readInt();
|
||||
if (chunkType != IFF.CHUNK_FORM) {
|
||||
throw new IIOException(String.format("Unknown file format for IFFImageReader, expected 'FORM': %s", IFFUtil.toChunkStr(chunkType)));
|
||||
throw new IIOException(String.format("Unknown file format for IFFImageReader, expected 'FORM': %s", toChunkStr(chunkType)));
|
||||
}
|
||||
|
||||
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_RGB8 && formType != IFF.TYPE_DEEP) {
|
||||
throw new IIOException(String.format("Only IFF FORM types 'ILBM' and 'PBM ' supported: %s", IFFUtil.toChunkStr(formType)));
|
||||
int formType = imageInput.readInt();
|
||||
if (formType != IFF.TYPE_ILBM && formType != IFF.TYPE_PBM && formType != IFF.TYPE_RGB8 && formType != IFF.TYPE_DEEP && formType != IFF.TYPE_TVPP) {
|
||||
throw new IIOException(String.format("Only IFF FORM types 'ILBM' and 'PBM ' supported: %s", toChunkStr(formType)));
|
||||
}
|
||||
|
||||
//System.out.println("IFF type FORM " + toChunkStr(type));
|
||||
if (DEBUG) {
|
||||
System.out.println("IFF type FORM '" + toChunkStr(formType) + "', len: " + (remaining + 4));
|
||||
System.out.println("Reading Chunks...");
|
||||
}
|
||||
|
||||
grab = null;
|
||||
viewPort = null;
|
||||
header = Form.ofType(formType);
|
||||
|
||||
// TODO: Delegate the FORM reading to the Form class or a FormReader class?
|
||||
while (remaining > 0) {
|
||||
int chunkId = imageInput.readInt();
|
||||
int length = imageInput.readInt();
|
||||
@ -171,104 +159,82 @@ public final class IFFImageReader extends ImageReaderBase {
|
||||
remaining -= 8;
|
||||
remaining -= length % 2 == 0 ? length : length + 1;
|
||||
|
||||
//System.out.println("Next chunk: " + toChunkStr(chunkId) + " length: " + length);
|
||||
//System.out.println("Remaining bytes after chunk: " + remaining);
|
||||
if (DEBUG) {
|
||||
System.out.println("Next chunk: " + toChunkStr(chunkId) + " @ pos: " + (imageInput.getStreamPosition() - 8) + ", len: " + length);
|
||||
System.out.println("Remaining bytes after chunk: " + remaining);
|
||||
}
|
||||
|
||||
switch (chunkId) {
|
||||
case IFF.CHUNK_BMHD:
|
||||
if (header != null) {
|
||||
throw new IIOException("Multiple BMHD chunks not allowed");
|
||||
}
|
||||
|
||||
header = new BMHDChunk(length);
|
||||
header.readChunk(imageInput);
|
||||
|
||||
//System.out.println(header);
|
||||
BMHDChunk bitmapHeader = new BMHDChunk(length);
|
||||
bitmapHeader.readChunk(imageInput);
|
||||
header = header.with(bitmapHeader);
|
||||
break;
|
||||
|
||||
case IFF.CHUNK_DGBL:
|
||||
DGBLChunk deepGlobal = new DGBLChunk(length);
|
||||
deepGlobal.readChunk(imageInput);
|
||||
header = header.with(deepGlobal);
|
||||
break;
|
||||
|
||||
case IFF.CHUNK_DLOC:
|
||||
DLOCChunk deepLocation = new DLOCChunk(length);
|
||||
deepLocation.readChunk(imageInput);
|
||||
header = header.with(deepLocation);
|
||||
break;
|
||||
|
||||
case IFF.CHUNK_DPEL:
|
||||
DPELChunk deepPixel = new DPELChunk(length);
|
||||
deepPixel.readChunk(imageInput);
|
||||
header = header.with(deepPixel);
|
||||
break;
|
||||
|
||||
case IFF.CHUNK_CMAP:
|
||||
if (colorMap != null) {
|
||||
throw new IIOException("Multiple CMAP chunks not allowed");
|
||||
}
|
||||
|
||||
colorMap = new CMAPChunk(length);
|
||||
CMAPChunk colorMap = new CMAPChunk(length);
|
||||
colorMap.readChunk(imageInput);
|
||||
|
||||
//System.out.println(colorMap);
|
||||
header = header.with(colorMap);
|
||||
break;
|
||||
|
||||
case IFF.CHUNK_GRAB:
|
||||
if (grab != null) {
|
||||
throw new IIOException("Multiple GRAB chunks not allowed");
|
||||
}
|
||||
grab = new GRABChunk(length);
|
||||
GRABChunk grab = new GRABChunk(length);
|
||||
grab.readChunk(imageInput);
|
||||
|
||||
//System.out.println(grab);
|
||||
header = header.with(grab);
|
||||
break;
|
||||
|
||||
case IFF.CHUNK_CAMG:
|
||||
if (viewPort != null) {
|
||||
throw new IIOException("Multiple CAMG chunks not allowed");
|
||||
}
|
||||
viewPort = new CAMGChunk(length);
|
||||
viewPort.readChunk(imageInput);
|
||||
|
||||
// System.out.println(viewPort);
|
||||
CAMGChunk viewMode = new CAMGChunk(length);
|
||||
viewMode.readChunk(imageInput);
|
||||
header = header.with(viewMode);
|
||||
break;
|
||||
case IFF.CHUNK_PCHG:
|
||||
if (paletteChange instanceof PCHGChunk) {
|
||||
throw new IIOException("Multiple PCHG chunks not allowed");
|
||||
}
|
||||
|
||||
case IFF.CHUNK_PCHG:
|
||||
PCHGChunk pchg = new PCHGChunk(length);
|
||||
pchg.readChunk(imageInput);
|
||||
|
||||
// Always prefer PCHG style palette changes
|
||||
paletteChange = pchg;
|
||||
|
||||
// System.out.println(pchg);
|
||||
header = header.with(pchg);
|
||||
break;
|
||||
|
||||
case IFF.CHUNK_SHAM:
|
||||
if (paletteChange instanceof SHAMChunk) {
|
||||
throw new IIOException("Multiple SHAM chunks not allowed");
|
||||
}
|
||||
|
||||
SHAMChunk sham = new SHAMChunk(length);
|
||||
sham.readChunk(imageInput);
|
||||
|
||||
// NOTE: We prefer PHCG to SHAM style palette changes, if both are present
|
||||
if (paletteChange == null) {
|
||||
paletteChange = sham;
|
||||
}
|
||||
|
||||
// System.out.println(sham);
|
||||
header = header.with(sham);
|
||||
break;
|
||||
|
||||
case IFF.CHUNK_CTBL:
|
||||
if (paletteChange instanceof CTBLChunk) {
|
||||
throw new IIOException("Multiple CTBL chunks not allowed");
|
||||
}
|
||||
|
||||
CTBLChunk ctbl = new CTBLChunk(length);
|
||||
ctbl.readChunk(imageInput);
|
||||
|
||||
// NOTE: We prefer PHCG to CTBL style palette changes, if both are present
|
||||
if (paletteChange == null) {
|
||||
paletteChange = ctbl;
|
||||
}
|
||||
|
||||
// System.out.println(ctbl);
|
||||
header = header.with(ctbl);
|
||||
break;
|
||||
|
||||
case IFF.CHUNK_BODY:
|
||||
if (body != null) {
|
||||
throw new IIOException("Multiple BODY chunks not allowed");
|
||||
}
|
||||
|
||||
body = new BODYChunk(length);
|
||||
bodyStart = imageInput.getStreamPosition();
|
||||
|
||||
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
|
||||
header = header.with(body);
|
||||
|
||||
// Done reading meta
|
||||
if (DEBUG) {
|
||||
System.out.println("header = " + header);
|
||||
}
|
||||
return;
|
||||
|
||||
case IFF.CHUNK_ANNO:
|
||||
@ -279,45 +245,32 @@ public final class IFFImageReader extends ImageReaderBase {
|
||||
case IFF.CHUNK_UTF8:
|
||||
GenericChunk generic = new GenericChunk(chunkId, length);
|
||||
generic.readChunk(imageInput);
|
||||
meta.add(generic);
|
||||
|
||||
// System.out.println(generic);
|
||||
header = header.with(generic);
|
||||
break;
|
||||
|
||||
case IFF.CHUNK_JUNK:
|
||||
// Always skip junk chunks
|
||||
default:
|
||||
// TODO: SHAM, DEST, SPRT and more
|
||||
// TODO: DEST, SPRT and more
|
||||
// Everything else, we'll just skip
|
||||
IFFChunk.skipData(imageInput, length, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
System.out.println("header = " + header);
|
||||
System.out.println("No BODY chunk found...");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedImage read(int pIndex, ImageReadParam pParam) throws IOException {
|
||||
init(pIndex);
|
||||
public BufferedImage read(int imageIndex, ImageReadParam param) throws IOException {
|
||||
init(imageIndex);
|
||||
processImageStarted(imageIndex);
|
||||
|
||||
processImageStarted(pIndex);
|
||||
|
||||
image = getDestination(pParam, getImageTypes(pIndex), header.width, header.height);
|
||||
//System.out.println(body);
|
||||
if (body != null) {
|
||||
//System.out.println("Read body");
|
||||
readBody(pParam);
|
||||
}
|
||||
else {
|
||||
// TODO: Remove this hack when we have metadata
|
||||
// In the rare case of an ILBM containing nothing but a CMAP
|
||||
//System.out.println(colorMap);
|
||||
if (colorMap != null) {
|
||||
//System.out.println("Creating palette!");
|
||||
image = colorMap.createPaletteImage(header, isEHB());
|
||||
}
|
||||
}
|
||||
|
||||
BufferedImage result = image;
|
||||
BufferedImage result = getDestination(param, getImageTypes(imageIndex), getWidth(imageIndex), getHeight(imageIndex));
|
||||
readBody(param, result);
|
||||
|
||||
processImageComplete();
|
||||
|
||||
@ -325,77 +278,81 @@ public final class IFFImageReader extends ImageReaderBase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth(int pIndex) throws IOException {
|
||||
init(pIndex);
|
||||
return header.width;
|
||||
public int getWidth(int imageIndex) throws IOException {
|
||||
init(imageIndex);
|
||||
return header.width();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight(int pIndex) throws IOException {
|
||||
init(pIndex);
|
||||
return header.height;
|
||||
public int getHeight(int imageIndex) throws IOException {
|
||||
init(imageIndex);
|
||||
return header.height();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
|
||||
init(imageIndex);
|
||||
|
||||
return new IFFImageMetadata(formType, header, colorMap != null ? colorMap.getIndexColorModel(header, isEHB()) : null, viewPort, meta);
|
||||
return new IFFImageMetadata(header, header.colorMap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<ImageTypeSpecifier> getImageTypes(int pIndex) throws IOException {
|
||||
init(pIndex);
|
||||
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException {
|
||||
init(imageIndex);
|
||||
|
||||
List<ImageTypeSpecifier> types = Arrays.asList(
|
||||
getRawImageType(pIndex),
|
||||
ImageTypeSpecifiers.createFromBufferedImageType(header.bitplanes == 32 ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_3BYTE_BGR)
|
||||
// TODO: ImageTypeSpecifier.createFromBufferedImageType(header.bitplanes == 32 ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB),
|
||||
// TODO: Allow 32 bit always. Allow RGB and discard alpha, if present?
|
||||
);
|
||||
int bitplanes = header.bitplanes();
|
||||
List<ImageTypeSpecifier> types =
|
||||
header.formType == IFF.TYPE_DEEP || header.formType == IFF.TYPE_TVPP // TODO: Make a header attribute here
|
||||
? Arrays.asList(
|
||||
ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR_PRE),
|
||||
getRawImageType(imageIndex)
|
||||
)
|
||||
: Arrays.asList(
|
||||
getRawImageType(imageIndex),
|
||||
ImageTypeSpecifiers.createFromBufferedImageType(bitplanes == 32 ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_3BYTE_BGR)
|
||||
);
|
||||
// TODO: Allow 32 bit INT types?
|
||||
return types.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageTypeSpecifier getRawImageType(int pIndex) throws IOException {
|
||||
init(pIndex);
|
||||
// TODO: Stay DRY...
|
||||
// TODO: Use this for creating the Image/Buffer in the read code below...
|
||||
|
||||
// NOTE: colorMap may be null for 8 bit (gray), 24 bit or 32 bit only
|
||||
ImageTypeSpecifier specifier;
|
||||
switch (header.bitplanes) {
|
||||
switch (header.bitplanes()) {
|
||||
case 1:
|
||||
// 1 bit
|
||||
// -> 1 bit IndexColorModel
|
||||
case 2:
|
||||
// 2 bit
|
||||
// -> 2 bit IndexColorModel
|
||||
case 3:
|
||||
case 4:
|
||||
// 4 bit
|
||||
// -> 4 bit IndexColorModel
|
||||
case 5:
|
||||
case 6:
|
||||
// May be HAM6
|
||||
// May be EHB
|
||||
// May be EHB or HAM6
|
||||
case 7:
|
||||
case 8:
|
||||
// 8 bit
|
||||
// May be HAM8
|
||||
if (!isConvertToRGB()) {
|
||||
if (colorMap != null) {
|
||||
IndexColorModel cm = colorMap.getIndexColorModel(header, isEHB());
|
||||
return ImageTypeSpecifiers.createFromIndexColorModel(cm);
|
||||
}
|
||||
else {
|
||||
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY);
|
||||
// otherwise -> 8 bit IndexColorModel
|
||||
if (!needsConversionToRGB()) {
|
||||
IndexColorModel indexColorModel = header.colorMap();
|
||||
|
||||
if (indexColorModel != null) {
|
||||
return ImageTypeSpecifiers.createFromIndexColorModel(indexColorModel);
|
||||
}
|
||||
|
||||
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY);
|
||||
}
|
||||
// NOTE: HAM modes falls through, as they are converted to RGB
|
||||
case 24:
|
||||
// 24 bit RGB
|
||||
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)));
|
||||
case 25:
|
||||
// For TYPE_RGB8: 24 bit + 1 bit mask (we'll convert to full alpha during decoding)
|
||||
if (header.formType != IFF.TYPE_RGB8) {
|
||||
throw new IIOException(String.format("25 bit depth only supported for FORM type RGB8: %s", toChunkStr(header.formType)));
|
||||
}
|
||||
|
||||
return ImageTypeSpecifiers.createInterleaved(ColorSpaces.getColorSpace(ColorSpace.CS_sRGB),
|
||||
@ -403,40 +360,49 @@ public final class IFFImageReader extends ImageReaderBase {
|
||||
|
||||
case 32:
|
||||
// 32 bit ARGB
|
||||
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR);
|
||||
return header.formType == IFF.TYPE_DEEP || header.formType == IFF.TYPE_TVPP
|
||||
// R G B A
|
||||
? ImageTypeSpecifiers.createInterleaved(ColorSpaces.getColorSpace(ColorSpace.CS_sRGB), new int[] {1, 2, 3, 0}, DataBuffer.TYPE_BYTE, true, header.premultiplied()) // TODO: Create based on DPEL!
|
||||
: ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR);
|
||||
|
||||
default:
|
||||
throw new IIOException(String.format("Bit depth not implemented: %d", header.bitplanes));
|
||||
throw new IIOException(String.format("Bit depth not implemented: %d", header.bitplanes()));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isConvertToRGB() {
|
||||
return isHAM() || isPCHG() || isSHAM();
|
||||
private boolean needsConversionToRGB() {
|
||||
return header.isHAM() || header.isMultiPalette();
|
||||
}
|
||||
|
||||
private void readBody(final ImageReadParam pParam) throws IOException {
|
||||
imageInput.seek(bodyStart);
|
||||
private void readBody(final ImageReadParam param, final BufferedImage destination) throws IOException {
|
||||
if (DEBUG) {
|
||||
System.out.println("Reading body");
|
||||
System.out.println("pos: " + imageInput.getStreamPosition());
|
||||
System.out.println("body offset: " + header.bodyOffset());
|
||||
}
|
||||
|
||||
imageInput.seek(header.bodyOffset());
|
||||
byteRunStream = null;
|
||||
|
||||
if (formType == IFF.TYPE_RGB8) {
|
||||
readRGB8(pParam, imageInput);
|
||||
if (header.formType == IFF.TYPE_RGB8 || header.formType == IFF.TYPE_DEEP || header.formType == IFF.TYPE_TVPP) {
|
||||
readChunky(param, destination, 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 if (header.colorMap() != null) {
|
||||
// NOTE: For ILBM types, colorMap may be null for 8 bit (gray), 24 bit or 32 bit only
|
||||
IndexColorModel palette = header.colorMap();
|
||||
readInterleavedIndexed(param, destination, palette, imageInput);
|
||||
}
|
||||
else {
|
||||
readTrueColor(pParam, imageInput);
|
||||
readInterleaved(param, destination, imageInput);
|
||||
}
|
||||
}
|
||||
|
||||
private void readIndexed(final ImageReadParam pParam, final ImageInputStream pInput, final IndexColorModel pModel) throws IOException {
|
||||
final int width = header.width;
|
||||
final int height = header.height;
|
||||
private void readInterleavedIndexed(final ImageReadParam param, final BufferedImage destination, final IndexColorModel palette, final ImageInputStream input) 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();
|
||||
final Rectangle aoi = getSourceRegion(param, width, height);
|
||||
final Point offset = param == null ? new Point(0, 0) : param.getDestinationOffset();
|
||||
|
||||
// Set everything to default values
|
||||
int sourceXSubsampling = 1;
|
||||
@ -445,20 +411,20 @@ public final class IFFImageReader extends ImageReaderBase {
|
||||
int[] destinationBands = null;
|
||||
|
||||
// Get values from the ImageReadParam, if any
|
||||
if (pParam != null) {
|
||||
sourceXSubsampling = pParam.getSourceXSubsampling();
|
||||
sourceYSubsampling = pParam.getSourceYSubsampling();
|
||||
if (param != null) {
|
||||
sourceXSubsampling = param.getSourceXSubsampling();
|
||||
sourceYSubsampling = param.getSourceYSubsampling();
|
||||
|
||||
sourceBands = pParam.getSourceBands();
|
||||
destinationBands = pParam.getDestinationBands();
|
||||
sourceBands = param.getSourceBands();
|
||||
destinationBands = param.getDestinationBands();
|
||||
}
|
||||
|
||||
// Ensure band settings from param are compatible with images
|
||||
checkReadParamBandSettings(pParam, isConvertToRGB() ? 3 : 1, image.getSampleModel().getNumBands());
|
||||
checkReadParamBandSettings(param, needsConversionToRGB() ? 3 : 1, destination.getSampleModel().getNumBands());
|
||||
|
||||
WritableRaster destination = image.getRaster();
|
||||
WritableRaster destRaster = destination.getRaster();
|
||||
if (destinationBands != null || offset.x != 0 || offset.y != 0) {
|
||||
destination = destination.createWritableChild(0, 0, destination.getWidth(), destination.getHeight(), offset.x, offset.y, destinationBands);
|
||||
destRaster = destRaster.createWritableChild(0, 0, destRaster.getWidth(), destRaster.getHeight(), offset.x, offset.y, destinationBands);
|
||||
}
|
||||
|
||||
// NOTE: Each row of the image is stored in an integral number of 16 bit words.
|
||||
@ -467,31 +433,31 @@ public final class IFFImageReader extends ImageReaderBase {
|
||||
final byte[] planeData = new byte[8 * planeWidth];
|
||||
|
||||
ColorModel cm;
|
||||
WritableRaster raster;
|
||||
WritableRaster rowRaster;
|
||||
|
||||
if (isConvertToRGB()) {
|
||||
// TODO: If HAM6, use type USHORT_444_RGB or 2BYTE_444_RGB?
|
||||
// Or create a HAMColorModel, if at all possible?
|
||||
if (needsConversionToRGB()) {
|
||||
// TODO: Create a HAMColorModel, if at all possible?
|
||||
// TYPE_3BYTE_BGR
|
||||
cm = new ComponentColorModel(
|
||||
ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[]{8, 8, 8},
|
||||
ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] {8, 8, 8},
|
||||
false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE
|
||||
);
|
||||
// Create a byte raster with BGR order
|
||||
raster = Raster.createInterleavedRaster(
|
||||
DataBuffer.TYPE_BYTE, width, 1, width * 3, 3, new int[]{2, 1, 0}, null
|
||||
rowRaster = Raster.createInterleavedRaster(
|
||||
DataBuffer.TYPE_BYTE, width, 1, width * 3, 3, new int[] {2, 1, 0}, null
|
||||
);
|
||||
}
|
||||
else {
|
||||
// TYPE_BYTE_BINARY or TYPE_BYTE_INDEXED
|
||||
cm = pModel;
|
||||
raster = pModel.createCompatibleWritableRaster(width, 1);
|
||||
cm = palette;
|
||||
rowRaster = palette.createCompatibleWritableRaster(width, 1);
|
||||
}
|
||||
Raster sourceRow = raster.createChild(aoi.x, 0, aoi.width, 1, 0, 0, sourceBands);
|
||||
|
||||
Raster sourceRow = rowRaster.createChild(aoi.x, 0, aoi.width, 1, 0, 0, sourceBands);
|
||||
|
||||
final byte[] row = new byte[width * 8];
|
||||
final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
|
||||
final int planes = header.bitplanes;
|
||||
final byte[] data = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
|
||||
final int planes = header.bitplanes();
|
||||
|
||||
Object dataElements = null;
|
||||
Object outDataElements = null;
|
||||
@ -499,7 +465,7 @@ public final class IFFImageReader extends ImageReaderBase {
|
||||
|
||||
for (int srcY = 0; srcY < height; srcY++) {
|
||||
for (int p = 0; p < planes; p++) {
|
||||
readPlaneData(pInput, planeData, p * planeWidth, planeWidth);
|
||||
readPlaneData(planeData, p * planeWidth, planeWidth, input);
|
||||
}
|
||||
|
||||
// Skip rows outside AOI
|
||||
@ -510,72 +476,71 @@ public final class IFFImageReader extends ImageReaderBase {
|
||||
return;
|
||||
}
|
||||
|
||||
if (formType == IFF.TYPE_ILBM) {
|
||||
if (header.formType == IFF.TYPE_ILBM) {
|
||||
int pixelPos = 0;
|
||||
for (int planePos = 0; planePos < planeWidth; planePos++) {
|
||||
IFFUtil.bitRotateCW(planeData, planePos, planeWidth, row, pixelPos, 1);
|
||||
pixelPos += 8;
|
||||
}
|
||||
|
||||
if (isHAM()) {
|
||||
hamToRGB(row, pModel, data, 0);
|
||||
if (header.isHAM()) {
|
||||
hamToRGB(row, palette, data, 0);
|
||||
}
|
||||
else if (isConvertToRGB()) {
|
||||
multiPaletteToRGB(srcY, row, pModel, data, 0);
|
||||
else if (needsConversionToRGB()) {
|
||||
multiPaletteToRGB(srcY, row, palette, data, 0);
|
||||
}
|
||||
else {
|
||||
raster.setDataElements(0, 0, width, 1, row);
|
||||
rowRaster.setDataElements(0, 0, width, 1, row);
|
||||
}
|
||||
}
|
||||
else if (formType == IFF.TYPE_PBM) {
|
||||
raster.setDataElements(0, 0, width, 1, planeData);
|
||||
else if (header.formType == IFF.TYPE_PBM) {
|
||||
rowRaster.setDataElements(0, 0, width, 1, planeData);
|
||||
}
|
||||
else {
|
||||
throw new AssertionError(String.format("Unsupported FORM type: %s", formType));
|
||||
throw new AssertionError(String.format("Unsupported FORM type: %s", toChunkStr(header.formType)));
|
||||
}
|
||||
|
||||
int dstY = (srcY - aoi.y) / sourceYSubsampling;
|
||||
// Handle non-converting raster as special case for performance
|
||||
if (cm.isCompatibleRaster(destination)) {
|
||||
if (cm.isCompatibleRaster(destRaster)) {
|
||||
// Rasters are compatible, just write to destination
|
||||
if (sourceXSubsampling == 1) {
|
||||
destination.setRect(offset.x, dstY, sourceRow);
|
||||
destRaster.setRect(offset.x, dstY, sourceRow);
|
||||
}
|
||||
else {
|
||||
for (int srcX = 0; srcX < sourceRow.getWidth(); srcX += sourceXSubsampling) {
|
||||
dataElements = sourceRow.getDataElements(srcX, 0, dataElements);
|
||||
int dstX = /*offset.x +*/ srcX / sourceXSubsampling;
|
||||
destination.setDataElements(dstX, dstY, dataElements);
|
||||
destRaster.setDataElements(dstX, dstY, dataElements);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (cm instanceof IndexColorModel) {
|
||||
// TODO: Optimize this thing... Maybe it's faster to just get the data indexed, and use drawImage?
|
||||
IndexColorModel icm = (IndexColorModel) cm;
|
||||
|
||||
for (int srcX = 0; srcX < sourceRow.getWidth(); srcX += sourceXSubsampling) {
|
||||
dataElements = sourceRow.getDataElements(srcX, 0, dataElements);
|
||||
int rgb = icm.getRGB(dataElements);
|
||||
outDataElements = image.getColorModel().getDataElements(rgb, outDataElements);
|
||||
outDataElements = destination.getColorModel().getDataElements(rgb, outDataElements);
|
||||
int dstX = srcX / sourceXSubsampling;
|
||||
destination.setDataElements(dstX, dstY, outDataElements);
|
||||
destRaster.setDataElements(dstX, dstY, outDataElements);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// TODO: This branch is never tested, and is probably "dead"
|
||||
// ColorConvertOp
|
||||
if (converter == null) {
|
||||
converter = new ColorConvertOp(cm.getColorSpace(), image.getColorModel().getColorSpace(), null);
|
||||
converter = new ColorConvertOp(cm.getColorSpace(), destination.getColorModel().getColorSpace(), null);
|
||||
}
|
||||
converter.filter(
|
||||
raster.createChild(aoi.x, 0, aoi.width, 1, 0, 0, null),
|
||||
destination.createWritableChild(offset.x, offset.y + srcY - aoi.y, aoi.width, 1, 0, 0, null)
|
||||
rowRaster.createChild(aoi.x, 0, aoi.width, 1, 0, 0, null),
|
||||
destRaster.createWritableChild(offset.x, offset.y + srcY - aoi.y, aoi.width, 1, 0, 0, null)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
processImageProgress(srcY * 100f / header.width);
|
||||
processImageProgress(srcY * 100f / width);
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
break;
|
||||
@ -583,12 +548,12 @@ 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;
|
||||
private void readChunky(final ImageReadParam param, final BufferedImage destination, final ImageInputStream input) 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();
|
||||
final Rectangle aoi = getSourceRegion(param, width, height);
|
||||
final Point offset = param == null ? new Point(0, 0) : param.getDestinationOffset();
|
||||
|
||||
// Set everything to default values
|
||||
int sourceXSubsampling = 1;
|
||||
@ -597,50 +562,49 @@ public final class IFFImageReader extends ImageReaderBase {
|
||||
int[] destinationBands = null;
|
||||
|
||||
// Get values from the ImageReadParam, if any
|
||||
if (pParam != null) {
|
||||
sourceXSubsampling = pParam.getSourceXSubsampling();
|
||||
sourceYSubsampling = pParam.getSourceYSubsampling();
|
||||
if (param != null) {
|
||||
sourceXSubsampling = param.getSourceXSubsampling();
|
||||
sourceYSubsampling = param.getSourceYSubsampling();
|
||||
|
||||
sourceBands = pParam.getSourceBands();
|
||||
destinationBands = pParam.getDestinationBands();
|
||||
sourceBands = param.getSourceBands();
|
||||
destinationBands = param.getDestinationBands();
|
||||
}
|
||||
|
||||
// Ensure band settings from param are compatible with images
|
||||
checkReadParamBandSettings(pParam, 4, image.getSampleModel().getNumBands());
|
||||
checkReadParamBandSettings(param, 4, destination.getSampleModel().getNumBands());
|
||||
|
||||
WritableRaster destination = image.getRaster();
|
||||
WritableRaster destRaster = destination.getRaster();
|
||||
if (destinationBands != null || offset.x != 0 || offset.y != 0) {
|
||||
destination = destination.createWritableChild(0, 0, destination.getWidth(), destination.getHeight(), offset.x, offset.y, destinationBands);
|
||||
destRaster = destRaster.createWritableChild(0, 0, destRaster.getWidth(), destRaster.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);
|
||||
ImageTypeSpecifier rawType = getRawImageType(0);
|
||||
WritableRaster rowRaster = rawType.createBufferedImage(width, 1).getRaster();
|
||||
Raster sourceRow = rowRaster.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;
|
||||
|
||||
final byte[] data = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
|
||||
Object dataElements = null;
|
||||
|
||||
for (int srcY = 0; srcY < height; srcY++) {
|
||||
readPlaneData(pInput, data, 0, planeWidth);
|
||||
readPlaneData(data, 0, planeWidth, input);
|
||||
|
||||
if (srcY >= aoi.y && (srcY - aoi.y) % sourceYSubsampling == 0) {
|
||||
int dstY = (srcY - aoi.y) / sourceYSubsampling;
|
||||
if (sourceXSubsampling == 1) {
|
||||
destination.setRect(0, dstY, sourceRow);
|
||||
destRaster.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);
|
||||
destRaster.setDataElements(dstX, dstY, dataElements);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processImageProgress(srcY * 100f / header.width);
|
||||
processImageProgress(srcY * 100f / width);
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
break;
|
||||
@ -653,12 +617,12 @@ public final class IFFImageReader extends ImageReaderBase {
|
||||
// followed by green and blue. The first plane holds the least significant
|
||||
// bit of the red value for each pixel, and the last holds the most
|
||||
// significant bit of the blue value.
|
||||
private void readTrueColor(ImageReadParam pParam, final ImageInputStream pInput) throws IOException {
|
||||
final int width = header.width;
|
||||
final int height = header.height;
|
||||
private void readInterleaved(final ImageReadParam param, final BufferedImage destination, final ImageInputStream input) 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();
|
||||
final Rectangle aoi = getSourceRegion(param, width, height);
|
||||
final Point offset = param == null ? new Point(0, 0) : param.getDestinationOffset();
|
||||
|
||||
// Set everything to default values
|
||||
int sourceXSubsampling = 1;
|
||||
@ -667,39 +631,39 @@ public final class IFFImageReader extends ImageReaderBase {
|
||||
int[] destinationBands = null;
|
||||
|
||||
// Get values from the ImageReadParam, if any
|
||||
if (pParam != null) {
|
||||
sourceXSubsampling = pParam.getSourceXSubsampling();
|
||||
sourceYSubsampling = pParam.getSourceYSubsampling();
|
||||
if (param != null) {
|
||||
sourceXSubsampling = param.getSourceXSubsampling();
|
||||
sourceYSubsampling = param.getSourceYSubsampling();
|
||||
|
||||
sourceBands = pParam.getSourceBands();
|
||||
destinationBands = pParam.getDestinationBands();
|
||||
sourceBands = param.getSourceBands();
|
||||
destinationBands = param.getDestinationBands();
|
||||
}
|
||||
|
||||
// Ensure band settings from param are compatible with images
|
||||
checkReadParamBandSettings(pParam, header.bitplanes / 8, image.getSampleModel().getNumBands());
|
||||
checkReadParamBandSettings(param, header.bitplanes() / 8, destination.getSampleModel().getNumBands());
|
||||
|
||||
// NOTE: Each row of the image is stored in an integral number of 16 bit words.
|
||||
// The number of words per row is words=((w+15)/16)
|
||||
int planeWidth = 2 * ((width + 15) / 16);
|
||||
final byte[] planeData = new byte[8 * planeWidth];
|
||||
|
||||
WritableRaster destination = image.getRaster();
|
||||
WritableRaster destRaster = destination.getRaster();
|
||||
if (destinationBands != null || offset.x != 0 || offset.y != 0) {
|
||||
destination = destination.createWritableChild(0, 0, destination.getWidth(), destination.getHeight(), offset.x, offset.y, destinationBands);
|
||||
destRaster = destRaster.createWritableChild(0, 0, destRaster.getWidth(), destRaster.getHeight(), offset.x, offset.y, destinationBands);
|
||||
}
|
||||
// WritableRaster raster = image.getRaster().createCompatibleWritableRaster(width, 1);
|
||||
WritableRaster raster = image.getRaster().createCompatibleWritableRaster(8 * planeWidth, 1);
|
||||
Raster sourceRow = raster.createChild(aoi.x, 0, aoi.width, 1, 0, 0, sourceBands);
|
||||
|
||||
final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
|
||||
final int channels = (header.bitplanes + 7) / 8;
|
||||
WritableRaster rowRaster = destination.getRaster().createCompatibleWritableRaster(8 * planeWidth, 1);
|
||||
Raster sourceRow = rowRaster.createChild(aoi.x, 0, aoi.width, 1, 0, 0, sourceBands);
|
||||
|
||||
final byte[] data = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
|
||||
final int channels = (header.bitplanes() + 7) / 8;
|
||||
final int planesPerChannel = 8;
|
||||
Object dataElements = null;
|
||||
|
||||
for (int srcY = 0; srcY < height; srcY++) {
|
||||
for (int c = 0; c < channels; c++) {
|
||||
for (int p = 0; p < planesPerChannel; p++) {
|
||||
readPlaneData(pInput, planeData, p * planeWidth, planeWidth);
|
||||
readPlaneData(planeData, p * planeWidth, planeWidth, input);
|
||||
}
|
||||
|
||||
// Skip rows outside AOI
|
||||
@ -710,7 +674,7 @@ public final class IFFImageReader extends ImageReaderBase {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (formType == IFF.TYPE_ILBM) {
|
||||
if (header.formType == IFF.TYPE_ILBM) {
|
||||
// NOTE: Using (channels - c - 1) instead of just c,
|
||||
// effectively reverses the channel order from RGBA to ABGR
|
||||
int off = (channels - c - 1);
|
||||
@ -721,33 +685,30 @@ public final class IFFImageReader extends ImageReaderBase {
|
||||
pixelPos += 8;
|
||||
}
|
||||
}
|
||||
else if (formType == IFF.TYPE_PBM) {
|
||||
else if (header.formType == IFF.TYPE_PBM) {
|
||||
System.arraycopy(planeData, 0, data, srcY * 8 * planeWidth, planeWidth);
|
||||
}
|
||||
else {
|
||||
throw new AssertionError(String.format("Unsupported FORM type: %s", formType));
|
||||
throw new AssertionError(String.format("Unsupported FORM type: %s", toChunkStr(header.formType)));
|
||||
}
|
||||
}
|
||||
|
||||
if (srcY >= aoi.y && (srcY - aoi.y) % sourceYSubsampling == 0) {
|
||||
int dstY = (srcY - aoi.y) / sourceYSubsampling;
|
||||
// TODO: Support conversion to INT (A)RGB rasters (maybe using ColorConvertOp?)
|
||||
// TODO: Avoid createChild if no region?
|
||||
if (sourceXSubsampling == 1) {
|
||||
destination.setRect(0, 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);
|
||||
destRaster.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);
|
||||
destRaster.setDataElements(dstX, dstY, dataElements);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processImageProgress(srcY * 100f / header.width);
|
||||
processImageProgress(srcY * 100f / width);
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
break;
|
||||
@ -755,16 +716,15 @@ public final class IFFImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
private void readPlaneData(final ImageInputStream pInput, final byte[] pData, final int pOffset, final int pPlaneWidth)
|
||||
private void readPlaneData(final byte[] destination, final int offset, final int planeWidth, final ImageInputStream input)
|
||||
throws IOException {
|
||||
|
||||
switch (header.compressionType) {
|
||||
switch (header.compressionType()) {
|
||||
case BMHDChunk.COMPRESSION_NONE:
|
||||
pInput.readFully(pData, pOffset, pPlaneWidth);
|
||||
input.readFully(destination, offset, planeWidth);
|
||||
|
||||
// Uncompressed rows must have even number of bytes
|
||||
if ((header.bitplanes * pPlaneWidth) % 2 != 0) {
|
||||
pInput.readByte();
|
||||
// Uncompressed rows must have an even number of bytes
|
||||
if ((header.bitplanes() * planeWidth) % 2 != 0) {
|
||||
input.readByte();
|
||||
}
|
||||
|
||||
break;
|
||||
@ -773,48 +733,46 @@ public final class IFFImageReader extends ImageReaderBase {
|
||||
// TODO: How do we know if the last byte in the body is a pad byte or not?!
|
||||
// The body consists of byte-run (PackBits) compressed rows of bit plane data.
|
||||
// However, we don't know how long each compressed row is, without decoding it...
|
||||
// The workaround below, is to use a decode buffer size of pPlaneWidth,
|
||||
// The workaround below, is to use a decode buffer size of planeWidth,
|
||||
// to make sure we don't decode anything we don't have to (shouldn't).
|
||||
if (byteRunStream == null) {
|
||||
byteRunStream = new DataInputStream(
|
||||
new DecoderStream(
|
||||
IIOUtil.createStreamAdapter(pInput, body.chunkLength),
|
||||
new PackBitsDecoder(true),
|
||||
pPlaneWidth * header.bitplanes
|
||||
IIOUtil.createStreamAdapter(input, header.bodyLength()),
|
||||
new PackBitsDecoder(header.sampleSize(), true),
|
||||
planeWidth * (header.sampleSize() > 1 ? 1 : header.bitplanes())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
byteRunStream.readFully(pData, pOffset, pPlaneWidth);
|
||||
byteRunStream.readFully(destination, offset, planeWidth);
|
||||
break;
|
||||
|
||||
case 4: // Compression type 4 means different things for different FORM types... :-P
|
||||
if (formType == IFF.TYPE_RGB8) {
|
||||
if (header.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
|
||||
IIOUtil.createStreamAdapter(input, header.bodyLength()),
|
||||
new RGB8RLEDecoder(), 1024
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
byteRunStream.readFully(pData, pOffset, pPlaneWidth);
|
||||
|
||||
byteRunStream.readFully(destination, offset, planeWidth);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new IIOException(String.format("Unknown compression type: %d", header.compressionType));
|
||||
throw new IIOException(String.format("Unknown compression type: %d", header.compressionType()));
|
||||
}
|
||||
}
|
||||
|
||||
private void multiPaletteToRGB(final int row, final byte[] indexed, final IndexColorModel colorModel, final byte[] dest, final int destOffset) {
|
||||
final int width = header.width;
|
||||
private void multiPaletteToRGB(final int row, final byte[] indexed, final IndexColorModel colorModel, final byte[] dest, @SuppressWarnings("SameParameterValue") final int destOffset) {
|
||||
final int width = header.width();
|
||||
|
||||
ColorModel palette = paletteChange.getColorModel(colorModel, row, isLaced());
|
||||
ColorModel palette = header.colorMapForRow(colorModel, row);
|
||||
|
||||
for (int x = 0; x < width; x++) {
|
||||
int pixel = indexed[x] & 0xff;
|
||||
@ -828,12 +786,14 @@ public final class IFFImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
private void hamToRGB(final byte[] indexed, final IndexColorModel colorModel, final byte[] dest, final int destOffset) {
|
||||
final int bits = header.bitplanes;
|
||||
final int width = header.width;
|
||||
int lastRed = 0;
|
||||
int lastGreen = 0;
|
||||
int lastBlue = 0;
|
||||
private void hamToRGB(final byte[] indexed, final IndexColorModel colorModel, final byte[] dest, @SuppressWarnings("SameParameterValue") final int destOffset) {
|
||||
final int bits = header.bitplanes();
|
||||
final int width = header.width();
|
||||
|
||||
// Initialize to the "border color" (index 0)
|
||||
int lastRed = colorModel.getRed(0);
|
||||
int lastGreen = colorModel.getGreen(0);
|
||||
int lastBlue = colorModel.getBlue(0);
|
||||
|
||||
for (int x = 0; x < width; x++) {
|
||||
int pixel = indexed[x] & 0xff;
|
||||
@ -867,32 +827,11 @@ public final class IFFImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSHAM() {
|
||||
// TODO:
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isPCHG() {
|
||||
return paletteChange != null;
|
||||
}
|
||||
|
||||
private boolean isEHB() {
|
||||
return viewPort != null && viewPort.isEHB();
|
||||
}
|
||||
|
||||
private boolean isHAM() {
|
||||
return viewPort != null && viewPort.isHAM();
|
||||
}
|
||||
|
||||
public boolean isLaced() {
|
||||
return viewPort != null && viewPort.isLaced();
|
||||
}
|
||||
|
||||
public static void main(String[] pArgs) throws IOException {
|
||||
public static void main(String[] args) {
|
||||
ImageReader reader = new IFFImageReader(new IFFImageReaderSpi());
|
||||
|
||||
boolean scale = false;
|
||||
for (String arg : pArgs) {
|
||||
for (String arg : args) {
|
||||
if (arg.startsWith("-")) {
|
||||
scale = true;
|
||||
continue;
|
||||
|
@ -53,40 +53,41 @@ public final class IFFImageReaderSpi extends ImageReaderSpiBase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDecodeInput(Object pSource) throws IOException {
|
||||
return pSource instanceof ImageInputStream && canDecode((ImageInputStream) pSource);
|
||||
public boolean canDecodeInput(final Object source) throws IOException {
|
||||
return source instanceof ImageInputStream && canDecode((ImageInputStream) source);
|
||||
}
|
||||
|
||||
private static boolean canDecode(ImageInputStream pInput) throws IOException {
|
||||
pInput.mark();
|
||||
private static boolean canDecode(final ImageInputStream input) throws IOException {
|
||||
input.mark();
|
||||
|
||||
try {
|
||||
// Is it IFF
|
||||
if (pInput.readInt() == IFF.CHUNK_FORM) {
|
||||
pInput.readInt();// Skip length field
|
||||
if (input.readInt() == IFF.CHUNK_FORM) {
|
||||
input.readInt();// Skip length field
|
||||
|
||||
int type = pInput.readInt();
|
||||
int type = input.readInt();
|
||||
if (type == IFF.TYPE_ILBM || type == IFF.TYPE_PBM
|
||||
|| type == IFF.TYPE_RGB8) { // Impulse RGB8
|
||||
|| type == IFF.TYPE_RGB8 // Impulse RGB8 format
|
||||
|| type == IFF.TYPE_DEEP || type == IFF.TYPE_TVPP) { // TVPaint DEEP format
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
finally {
|
||||
pInput.reset();
|
||||
input.reset();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageReader createReaderInstance(Object pExtension) throws IOException {
|
||||
public ImageReader createReaderInstance(final Object extension) {
|
||||
return new IFFImageReader(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription(Locale pLocale) {
|
||||
public String getDescription(Locale locale) {
|
||||
return "Commodore Amiga/Electronic Arts Image Interchange Format (IFF) image reader";
|
||||
}
|
||||
}
|
||||
|
@ -30,31 +30,22 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.iff;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.ColorModel;
|
||||
import java.awt.image.IndexColorModel;
|
||||
import java.awt.image.Raster;
|
||||
import java.awt.image.RenderedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import javax.imageio.IIOImage;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.ImageWriteParam;
|
||||
import javax.imageio.ImageWriter;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.spi.ImageWriterSpi;
|
||||
|
||||
import com.twelvemonkeys.imageio.ImageWriterBase;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import com.twelvemonkeys.io.FastByteArrayOutputStream;
|
||||
import com.twelvemonkeys.io.enc.EncoderStream;
|
||||
import com.twelvemonkeys.io.enc.PackBitsEncoder;
|
||||
|
||||
import javax.imageio.*;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.spi.ImageWriterSpi;
|
||||
import java.awt.*;
|
||||
import java.awt.image.*;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Writer for Commodore Amiga (Electronic Arts) IFF ILBM (InterLeaved BitMap) format.
|
||||
* The IFF format (Interchange File Format) is the standard file format
|
||||
@ -68,8 +59,8 @@ import com.twelvemonkeys.io.enc.PackBitsEncoder;
|
||||
*/
|
||||
public final class IFFImageWriter extends ImageWriterBase {
|
||||
|
||||
IFFImageWriter(ImageWriterSpi pProvider) {
|
||||
super(pProvider);
|
||||
IFFImageWriter(ImageWriterSpi provider) {
|
||||
super(provider);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -83,23 +74,29 @@ public final class IFFImageWriter extends ImageWriterBase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(IIOMetadata pStreamMetadata, IIOImage pImage, ImageWriteParam pParam) throws IOException {
|
||||
public ImageWriteParam getDefaultWriteParam() {
|
||||
return new IFFWriteParam(getLocale());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(IIOMetadata streamMetadata, IIOImage image, ImageWriteParam param) throws IOException {
|
||||
assertOutput();
|
||||
|
||||
if (pImage.hasRaster()) {
|
||||
if (image.hasRaster()) {
|
||||
throw new UnsupportedOperationException("Cannot write raster");
|
||||
}
|
||||
|
||||
processImageStarted(0);
|
||||
|
||||
RenderedImage renderedImage = image.getRenderedImage();
|
||||
boolean compress = shouldCompress(renderedImage, param);
|
||||
|
||||
// Prepare image data to be written
|
||||
ByteArrayOutputStream imageData = new FastByteArrayOutputStream(1024);
|
||||
packImageData(imageData, pImage.getRenderedImage(), pParam);
|
||||
|
||||
//System.out.println("Image data: " + imageData.size());
|
||||
packImageData(imageData, renderedImage, compress);
|
||||
|
||||
// Write metadata
|
||||
writeMeta(pImage.getRenderedImage(), imageData.size());
|
||||
writeMeta(renderedImage, imageData.size(), compress);
|
||||
|
||||
// Write image data
|
||||
writeBody(imageData);
|
||||
@ -107,34 +104,31 @@ public final class IFFImageWriter extends ImageWriterBase {
|
||||
processImageComplete();
|
||||
}
|
||||
|
||||
private void writeBody(ByteArrayOutputStream pImageData) throws IOException {
|
||||
private void writeBody(ByteArrayOutputStream imageData) throws IOException {
|
||||
imageOutput.writeInt(IFF.CHUNK_BODY);
|
||||
imageOutput.writeInt(pImageData.size());
|
||||
imageOutput.writeInt(imageData.size());
|
||||
|
||||
// NOTE: This is much faster than imageOutput.write(pImageData.toByteArray())
|
||||
// NOTE: This is much faster than imageOutput.write(imageData.toByteArray())
|
||||
// as the data array is not duplicated
|
||||
try (OutputStream adapter = IIOUtil.createStreamAdapter(imageOutput)) {
|
||||
pImageData.writeTo(adapter);
|
||||
imageData.writeTo(adapter);
|
||||
}
|
||||
|
||||
if (pImageData.size() % 2 == 0) {
|
||||
if (imageData.size() % 2 == 0) {
|
||||
imageOutput.writeByte(0); // PAD
|
||||
}
|
||||
|
||||
imageOutput.flush();
|
||||
}
|
||||
|
||||
private void packImageData(OutputStream pOutput, RenderedImage pImage, ImageWriteParam pParam) throws IOException {
|
||||
// TODO: Allow param to dictate uncompressed
|
||||
// TODO: Allow param to dictate type PBM?
|
||||
private void packImageData(OutputStream outputStream, RenderedImage image, final boolean compress) throws IOException {
|
||||
// TODO: Subsample/AOI
|
||||
final boolean compress = shouldCompress(pImage);
|
||||
final OutputStream output = compress ? new EncoderStream(pOutput, new PackBitsEncoder(), true) : pOutput;
|
||||
final ColorModel model = pImage.getColorModel();
|
||||
final Raster raster = pImage.getData();
|
||||
final OutputStream output = compress ? new EncoderStream(outputStream, new PackBitsEncoder(), true) : outputStream;
|
||||
final ColorModel model = image.getColorModel();
|
||||
final Raster raster = image.getData();
|
||||
|
||||
final int width = pImage.getWidth();
|
||||
final int height = pImage.getHeight();
|
||||
final int width = image.getWidth();
|
||||
final int height = image.getHeight();
|
||||
|
||||
// Store each row of pixels
|
||||
// 0. Loop pr channel
|
||||
@ -142,7 +136,6 @@ public final class IFFImageWriter extends ImageWriterBase {
|
||||
// 2. Perform byteRun1 compression for each plane separately
|
||||
// 3. Write the plane data for each plane
|
||||
|
||||
//final int planeWidth = (width + 7) / 8;
|
||||
final int planeWidth = 2 * ((width + 15) / 16);
|
||||
final byte[] planeData = new byte[8 * planeWidth];
|
||||
final int channels = (model.getPixelSize() + 7) / 8;
|
||||
@ -167,10 +160,6 @@ public final class IFFImageWriter extends ImageWriterBase {
|
||||
|
||||
for (int p = 0; p < planesPerChannel; p++) {
|
||||
output.write(planeData, p * planeWidth, planeWidth);
|
||||
|
||||
if (!compress && planeWidth % 2 != 0) {
|
||||
output.write(0); // PAD
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -182,17 +171,16 @@ public final class IFFImageWriter extends ImageWriterBase {
|
||||
output.flush();
|
||||
}
|
||||
|
||||
private void writeMeta(RenderedImage pImage, int pBodyLength) throws IOException {
|
||||
private void writeMeta(RenderedImage image, int bodyLength, boolean compress) throws IOException {
|
||||
// Annotation ANNO chunk, 8 + annoData.length bytes
|
||||
String annotation = String.format("Written by %s IFFImageWriter %s", getOriginatingProvider().getVendorName(), getOriginatingProvider().getVersion());
|
||||
GenericChunk anno = new GenericChunk(IFFUtil.toInt("ANNO".getBytes()), annotation.getBytes());
|
||||
|
||||
ColorModel cm = pImage.getColorModel();
|
||||
ColorModel cm = image.getColorModel();
|
||||
IndexColorModel icm = null;
|
||||
|
||||
// Bitmap header BMHD chunk, 8 + 20 bytes
|
||||
// By default, don't compress narrow images
|
||||
int compression = shouldCompress(pImage) ? BMHDChunk.COMPRESSION_BYTE_RUN : BMHDChunk.COMPRESSION_NONE;
|
||||
int compression = compress ? BMHDChunk.COMPRESSION_BYTE_RUN : BMHDChunk.COMPRESSION_NONE;
|
||||
|
||||
BMHDChunk header;
|
||||
if (cm instanceof IndexColorModel) {
|
||||
@ -200,12 +188,12 @@ public final class IFFImageWriter extends ImageWriterBase {
|
||||
icm = (IndexColorModel) cm;
|
||||
int trans = icm.getTransparency() == Transparency.BITMASK ? BMHDChunk.MASK_TRANSPARENT_COLOR : BMHDChunk.MASK_NONE;
|
||||
int transPixel = icm.getTransparency() == Transparency.BITMASK ? icm.getTransparentPixel() : 0;
|
||||
header = new BMHDChunk(pImage.getWidth(), pImage.getHeight(), icm.getPixelSize(),
|
||||
header = new BMHDChunk(image.getWidth(), image.getHeight(), icm.getPixelSize(),
|
||||
trans, compression, transPixel);
|
||||
}
|
||||
else {
|
||||
//System.out.println(cm.getClass().getName());
|
||||
header = new BMHDChunk(pImage.getWidth(), pImage.getHeight(), cm.getPixelSize(),
|
||||
header = new BMHDChunk(image.getWidth(), image.getHeight(), cm.getPixelSize(),
|
||||
BMHDChunk.MASK_NONE, compression, 0);
|
||||
}
|
||||
|
||||
@ -217,7 +205,7 @@ public final class IFFImageWriter extends ImageWriterBase {
|
||||
}
|
||||
|
||||
// ILBM(4) + anno(8+len) + header(8+20) + cmap(8+len)? + body(8+len);
|
||||
int size = 4 + 8 + anno.chunkLength + 28 + 8 + pBodyLength;
|
||||
int size = 4 + 8 + anno.chunkLength + 28 + 8 + bodyLength;
|
||||
if (cmap != null) {
|
||||
size += 8 + cmap.chunkLength;
|
||||
}
|
||||
@ -231,21 +219,30 @@ public final class IFFImageWriter extends ImageWriterBase {
|
||||
header.writeChunk(imageOutput);
|
||||
|
||||
if (cmap != null) {
|
||||
//System.out.println("CMAP written");
|
||||
cmap.writeChunk(imageOutput);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private boolean shouldCompress(RenderedImage pImage) {
|
||||
return pImage.getWidth() >= 32;
|
||||
private boolean shouldCompress(final RenderedImage image, final ImageWriteParam param) {
|
||||
if (param != null && param.canWriteCompressed()) {
|
||||
switch (param.getCompressionMode()) {
|
||||
case ImageWriteParam.MODE_DISABLED:
|
||||
return false;
|
||||
case ImageWriteParam.MODE_EXPLICIT:
|
||||
return IFFWriteParam.COMPRESSION_TYPES[1].equals(param.getCompressionType());
|
||||
default:
|
||||
// Fall through
|
||||
}
|
||||
}
|
||||
|
||||
return image.getWidth() >= 32;
|
||||
}
|
||||
|
||||
public static void main(String[] pArgs) throws IOException {
|
||||
BufferedImage image = ImageIO.read(new File(pArgs[0]));
|
||||
public static void main(String[] args) throws IOException {
|
||||
BufferedImage image = ImageIO.read(new File(args[0]));
|
||||
|
||||
ImageWriter writer = new IFFImageWriter(new IFFImageWriterSpi());
|
||||
writer.setOutput(ImageIO.createImageOutputStream(new File(pArgs[1])));
|
||||
writer.setOutput(ImageIO.createImageOutputStream(new File(args[1])));
|
||||
//writer.addIIOWriteProgressListener(new ProgressListenerBase() {
|
||||
// int mCurrPct = 0;
|
||||
//
|
||||
|
@ -30,13 +30,11 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.iff;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
import com.twelvemonkeys.imageio.spi.ImageWriterSpiBase;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.ImageWriter;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ImageWriterSpiBase;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* IFFImageWriterSpi
|
||||
@ -53,19 +51,19 @@ public class IFFImageWriterSpi extends ImageWriterSpiBase {
|
||||
super(new IFFProviderInfo());
|
||||
}
|
||||
|
||||
public boolean canEncodeImage(final ImageTypeSpecifier pType) {
|
||||
public boolean canEncodeImage(final ImageTypeSpecifier type) {
|
||||
// TODO: Probably can't store 16 bit types etc...
|
||||
// TODO: Can't store CMYK (well.. it does, but they can't be read back)
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageWriter createWriterInstance(Object pExtension) throws IOException {
|
||||
public ImageWriter createWriterInstance(Object extension) {
|
||||
return new IFFImageWriter(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription(Locale pLocale) {
|
||||
public String getDescription(Locale locale) {
|
||||
return "Commodore Amiga/Electronic Arts Image Interchange Format (IFF) image writer";
|
||||
}
|
||||
}
|
||||
|
@ -40,11 +40,11 @@ import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
||||
* @version $Id: IFFProviderInfo.java,v 1.0 20/03/15 harald.kuhr Exp$
|
||||
*/
|
||||
final class IFFProviderInfo extends ReaderWriterProviderInfo {
|
||||
protected IFFProviderInfo() {
|
||||
IFFProviderInfo() {
|
||||
super(
|
||||
IFFProviderInfo.class,
|
||||
new String[] {"iff", "IFF"},
|
||||
new String[] {"iff", "lbm", "ham", "ham8", "ilbm"},
|
||||
new String[] {"iff", "lbm", "ham", "ham8", "ilbm", "rgb8", "deep"},
|
||||
new String[] {"image/iff", "image/x-iff"},
|
||||
"com.twelvemonkeys.imageio.plugins.iff.IFFImageReader",
|
||||
new String[]{"com.twelvemonkeys.imageio.plugins.iff.IFFImageReaderSpi"},
|
||||
|
@ -56,11 +56,11 @@ final class IFFUtil {
|
||||
* @return the rotation table
|
||||
*/
|
||||
static private long[] rtable(int n) {
|
||||
return new long[]{
|
||||
0x00000000l << n, 0x00000001l << n, 0x00000100l << n, 0x00000101l << n,
|
||||
0x00010000l << n, 0x00010001l << n, 0x00010100l << n, 0x00010101l << n,
|
||||
0x01000000l << n, 0x01000001l << n, 0x01000100l << n, 0x01000101l << n,
|
||||
0x01010000l << n, 0x01010001l << n, 0x01010100l << n, 0x01010101l << n
|
||||
return new long[] {
|
||||
0x00000000L , 0x00000001L << n, 0x00000100L << n, 0x00000101L << n,
|
||||
0x00010000L << n, 0x00010001L << n, 0x00010100L << n, 0x00010101L << n,
|
||||
0x01000000L << n, 0x01000001L << n, 0x01000100L << n, 0x01000101L << n,
|
||||
0x01010000L << n, 0x01010001L << n, 0x01010100L << n, 0x01010101L << n
|
||||
};
|
||||
}
|
||||
|
||||
@ -75,16 +75,16 @@ final class IFFUtil {
|
||||
* Bits from the source are rotated 90 degrees clockwise written to the
|
||||
* destination.
|
||||
*
|
||||
* @param pSrc source pixel data
|
||||
* @param pSrcPos starting index of 8 x 8 bit source tile
|
||||
* @param pSrcStep byte offset between adjacent rows in source
|
||||
* @param pDst destination pixel data
|
||||
* @param pDstPos starting index of 8 x 8 bit destination tile
|
||||
* @param pDstStep byte offset between adjacent rows in destination
|
||||
* @param src source pixel data
|
||||
* @param srcPos starting index of 8 x 8 bit source tile
|
||||
* @param srcStep byte offset between adjacent rows in source
|
||||
* @param dst destination pixel data
|
||||
* @param dstPos starting index of 8 x 8 bit destination tile
|
||||
* @param dstStep byte offset between adjacent rows in destination
|
||||
*/
|
||||
static void bitRotateCW(final byte[] pSrc, int pSrcPos, int pSrcStep,
|
||||
final byte[] pDst, int pDstPos, int pDstStep) {
|
||||
int idx = pSrcPos;
|
||||
static void bitRotateCW(final byte[] src, int srcPos, int srcStep,
|
||||
final byte[] dst, int dstPos, int dstStep) {
|
||||
int idx = srcPos;
|
||||
|
||||
int lonyb;
|
||||
int hinyb;
|
||||
@ -92,41 +92,41 @@ final class IFFUtil {
|
||||
long hi = 0;
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
lonyb = pSrc[idx] & 0xF;
|
||||
hinyb = (pSrc[idx] >> 4) & 0xF;
|
||||
lonyb = src[idx] & 0xF;
|
||||
hinyb = (src[idx] >> 4) & 0xF;
|
||||
lo |= RTABLE[i][lonyb];
|
||||
hi |= RTABLE[i][hinyb];
|
||||
idx += pSrcStep;
|
||||
idx += srcStep;
|
||||
}
|
||||
|
||||
idx = pDstPos;
|
||||
idx = dstPos;
|
||||
|
||||
pDst[idx] = (byte)((hi >> 24) & 0xFF);
|
||||
idx += pDstStep;
|
||||
if (idx < pDst.length) {
|
||||
pDst[idx] = (byte)((hi >> 16) & 0xFF);
|
||||
idx += pDstStep;
|
||||
if (idx < pDst.length) {
|
||||
pDst[idx] = (byte)((hi >> 8) & 0xFF);
|
||||
idx += pDstStep;
|
||||
if (idx < pDst.length) {
|
||||
pDst[idx] = (byte)(hi & 0xFF);
|
||||
idx += pDstStep;
|
||||
dst[idx] = (byte)((hi >> 24) & 0xFF);
|
||||
idx += dstStep;
|
||||
if (idx < dst.length) {
|
||||
dst[idx] = (byte)((hi >> 16) & 0xFF);
|
||||
idx += dstStep;
|
||||
if (idx < dst.length) {
|
||||
dst[idx] = (byte)((hi >> 8) & 0xFF);
|
||||
idx += dstStep;
|
||||
if (idx < dst.length) {
|
||||
dst[idx] = (byte)(hi & 0xFF);
|
||||
idx += dstStep;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (idx < pDst.length) {
|
||||
pDst[idx] = (byte)((lo >> 24) & 0xFF);
|
||||
idx += pDstStep;
|
||||
if (idx < pDst.length) {
|
||||
pDst[idx] = (byte)((lo >> 16) & 0xFF);
|
||||
idx += pDstStep;
|
||||
if (idx < pDst.length) {
|
||||
pDst[idx] = (byte)((lo >> 8) & 0xFF);
|
||||
idx += pDstStep;
|
||||
if (idx < pDst.length) {
|
||||
pDst[idx] = (byte)(lo & 0xFF);
|
||||
if (idx < dst.length) {
|
||||
dst[idx] = (byte)((lo >> 24) & 0xFF);
|
||||
idx += dstStep;
|
||||
if (idx < dst.length) {
|
||||
dst[idx] = (byte)((lo >> 16) & 0xFF);
|
||||
idx += dstStep;
|
||||
if (idx < dst.length) {
|
||||
dst[idx] = (byte)((lo >> 8) & 0xFF);
|
||||
idx += dstStep;
|
||||
if (idx < dst.length) {
|
||||
dst[idx] = (byte)(lo & 0xFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -137,16 +137,16 @@ final class IFFUtil {
|
||||
* Rotate bits counterclockwise.
|
||||
* The IFFImageWriter uses this to convert pixel bits from chunky to planar.
|
||||
*
|
||||
* @param pSrc source pixel data (only lower 8 bits used)
|
||||
* @param pSrcPos starting index of 8 x 8 bit source tile
|
||||
* @param pSrcStep byte offset between adjacent rows in source
|
||||
* @param pDst destination pixel data
|
||||
* @param pDstPos starting index of 8 x 8 bit destination tile
|
||||
* @param pDstStep byte offset between adjacent rows in destination
|
||||
* @param src source pixel data (only lower 8 bits used)
|
||||
* @param srcPos starting index of 8 x 8 bit source tile
|
||||
* @param srcStep byte offset between adjacent rows in source
|
||||
* @param dst destination pixel data
|
||||
* @param dstPos starting index of 8 x 8 bit destination tile
|
||||
* @param dstStep byte offset between adjacent rows in destination
|
||||
*/
|
||||
static void bitRotateCCW(final int[] pSrc, int pSrcPos, int pSrcStep,
|
||||
final byte[] pDst, int pDstPos, int pDstStep) {
|
||||
int idx = pSrcPos;
|
||||
static void bitRotateCCW(final int[] src, int srcPos, @SuppressWarnings("SameParameterValue") int srcStep,
|
||||
final byte[] dst, int dstPos, int dstStep) {
|
||||
int idx = srcPos;
|
||||
|
||||
int lonyb;
|
||||
int hinyb;
|
||||
@ -154,48 +154,49 @@ final class IFFUtil {
|
||||
long hi = 0;
|
||||
|
||||
for (int i = 7; i >= 0; i--) {
|
||||
lonyb = pSrc[idx] & 0xF;
|
||||
hinyb = (pSrc[idx] >> 4) & 0xF;
|
||||
lonyb = src[idx] & 0xF;
|
||||
hinyb = (src[idx] >> 4) & 0xF;
|
||||
lo |= RTABLE[i][lonyb];
|
||||
hi |= RTABLE[i][hinyb];
|
||||
idx += pSrcStep;
|
||||
idx += srcStep;
|
||||
}
|
||||
|
||||
idx = pDstPos;
|
||||
idx = dstPos;
|
||||
|
||||
pDst[idx] = (byte)(lo & 0xFF);
|
||||
idx += pDstStep;
|
||||
pDst[idx] = (byte)((lo >> 8) & 0xFF);
|
||||
idx += pDstStep;
|
||||
pDst[idx] = (byte)((lo >> 16) & 0xFF);
|
||||
idx += pDstStep;
|
||||
pDst[idx] = (byte)((lo >> 24) & 0xFF);
|
||||
dst[idx] = (byte)(lo & 0xFF);
|
||||
idx += dstStep;
|
||||
dst[idx] = (byte)((lo >> 8) & 0xFF);
|
||||
idx += dstStep;
|
||||
dst[idx] = (byte)((lo >> 16) & 0xFF);
|
||||
idx += dstStep;
|
||||
dst[idx] = (byte)((lo >> 24) & 0xFF);
|
||||
|
||||
idx += pDstStep;
|
||||
idx += dstStep;
|
||||
|
||||
pDst[idx] = (byte)(hi & 0xFF);
|
||||
idx += pDstStep;
|
||||
pDst[idx] = (byte)((hi >> 8) & 0xFF);
|
||||
idx += pDstStep;
|
||||
pDst[idx] = (byte)((hi >> 16) & 0xFF);
|
||||
idx += pDstStep;
|
||||
pDst[idx] = (byte)((hi >> 24) & 0xFF);
|
||||
dst[idx] = (byte)(hi & 0xFF);
|
||||
idx += dstStep;
|
||||
dst[idx] = (byte)((hi >> 8) & 0xFF);
|
||||
idx += dstStep;
|
||||
dst[idx] = (byte)((hi >> 16) & 0xFF);
|
||||
idx += dstStep;
|
||||
dst[idx] = (byte)((hi >> 24) & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotate bits counterclockwise.
|
||||
* The IFFImageWriter uses this to convert pixel bits from chunky to planar.
|
||||
*
|
||||
* @param pSrc source pixel data
|
||||
* @param pSrcPos starting index of 8 x 8 bit source tile
|
||||
* @param pSrcStep byte offset between adjacent rows in source
|
||||
* @param pDst destination pixel data
|
||||
* @param pDstPos starting index of 8 x 8 bit destination tile
|
||||
* @param pDstStep byte offset between adjacent rows in destination
|
||||
* @param src source pixel data
|
||||
* @param srcPos starting index of 8 x 8 bit source tile
|
||||
* @param srcStep byte offset between adjacent rows in source
|
||||
* @param dst destination pixel data
|
||||
* @param dstPos starting index of 8 x 8 bit destination tile
|
||||
* @param dstStep byte offset between adjacent rows in destination
|
||||
*/
|
||||
static void bitRotateCCW(final byte[] pSrc, int pSrcPos, int pSrcStep,
|
||||
final byte[] pDst, int pDstPos, int pDstStep) {
|
||||
int idx = pSrcPos;
|
||||
@SuppressWarnings("unused")
|
||||
static void bitRotateCCW(final byte[] src, int srcPos, int srcStep,
|
||||
final byte[] dst, int dstPos, int dstStep) {
|
||||
int idx = srcPos;
|
||||
|
||||
int lonyb;
|
||||
int hinyb;
|
||||
@ -203,57 +204,57 @@ final class IFFUtil {
|
||||
long hi = 0;
|
||||
|
||||
for (int i = 7; i >= 0; i--) {
|
||||
lonyb = pSrc[idx] & 0xF;
|
||||
hinyb = (pSrc[idx] >> 4) & 0xF;
|
||||
lonyb = src[idx] & 0xF;
|
||||
hinyb = (src[idx] >> 4) & 0xF;
|
||||
lo |= RTABLE[i][lonyb];
|
||||
hi |= IFFUtil.RTABLE[i][hinyb];
|
||||
idx += pSrcStep;
|
||||
idx += srcStep;
|
||||
}
|
||||
|
||||
idx = pDstPos;
|
||||
idx = dstPos;
|
||||
|
||||
pDst[idx] = (byte)(lo & 0xFF);
|
||||
idx += pDstStep;
|
||||
pDst[idx] = (byte)((lo >> 8) & 0xFF);
|
||||
idx += pDstStep;
|
||||
pDst[idx] = (byte)((lo >> 16) & 0xFF);
|
||||
idx += pDstStep;
|
||||
pDst[idx] = (byte)((lo >> 24) & 0xFF);
|
||||
dst[idx] = (byte)(lo & 0xFF);
|
||||
idx += dstStep;
|
||||
dst[idx] = (byte)((lo >> 8) & 0xFF);
|
||||
idx += dstStep;
|
||||
dst[idx] = (byte)((lo >> 16) & 0xFF);
|
||||
idx += dstStep;
|
||||
dst[idx] = (byte)((lo >> 24) & 0xFF);
|
||||
|
||||
idx += pDstStep;
|
||||
idx += dstStep;
|
||||
|
||||
pDst[idx] = (byte)(hi & 0xFF);
|
||||
idx += pDstStep;
|
||||
pDst[idx] = (byte)((hi >> 8) & 0xFF);
|
||||
idx += pDstStep;
|
||||
pDst[idx] = (byte)((hi >> 16) & 0xFF);
|
||||
idx += pDstStep;
|
||||
pDst[idx] = (byte)((hi >> 24) & 0xFF);
|
||||
dst[idx] = (byte)(hi & 0xFF);
|
||||
idx += dstStep;
|
||||
dst[idx] = (byte)((hi >> 8) & 0xFF);
|
||||
idx += dstStep;
|
||||
dst[idx] = (byte)((hi >> 16) & 0xFF);
|
||||
idx += dstStep;
|
||||
dst[idx] = (byte)((hi >> 24) & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a byte array to an int.
|
||||
*
|
||||
* @param pBytes a byte array of length 4
|
||||
* @param bytes a byte array of length 4
|
||||
* @return the bytes converted to an int
|
||||
*
|
||||
* @throws ArrayIndexOutOfBoundsException if length is < 4
|
||||
*/
|
||||
static int toInt(final byte[] pBytes) {
|
||||
return (pBytes[0] & 0xff) << 24 | (pBytes[1] & 0xff) << 16
|
||||
| (pBytes[2] & 0xff) << 8 | (pBytes[3] & 0xff);
|
||||
static int toInt(final byte[] bytes) {
|
||||
return (bytes[0] & 0xff) << 24 | (bytes[1] & 0xff) << 16
|
||||
| (bytes[2] & 0xff) << 8 | (bytes[3] & 0xff);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an int to a four letter String.
|
||||
*
|
||||
* @param pChunkId the chunk identifier
|
||||
* @param chunkId the chunk identifier
|
||||
* @return a String
|
||||
*/
|
||||
static String toChunkStr(int pChunkId) {
|
||||
return new String(new byte[] {(byte) ((pChunkId & 0xff000000) >> 24),
|
||||
(byte) ((pChunkId & 0x00ff0000) >> 16),
|
||||
(byte) ((pChunkId & 0x0000ff00) >> 8),
|
||||
(byte) ((pChunkId & 0x000000ff))});
|
||||
static String toChunkStr(int chunkId) {
|
||||
return new String(new byte[] {(byte) ((chunkId & 0xff000000) >> 24),
|
||||
(byte) ((chunkId & 0x00ff0000) >> 16),
|
||||
(byte) ((chunkId & 0x0000ff00) >> 8),
|
||||
(byte) ((chunkId & 0x000000ff))});
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,25 @@
|
||||
package com.twelvemonkeys.imageio.plugins.iff;
|
||||
|
||||
import javax.imageio.ImageWriteParam;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* IFFWriteParam.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: IFFWriteParam.java,v 1.0 03/02/2022 haraldk Exp$
|
||||
*/
|
||||
public final class IFFWriteParam extends ImageWriteParam {
|
||||
|
||||
static final String[] COMPRESSION_TYPES = {"NONE", "RLE"};
|
||||
|
||||
public IFFWriteParam(final Locale locale) {
|
||||
super(locale);
|
||||
|
||||
compressionTypes = COMPRESSION_TYPES;
|
||||
compressionType = compressionTypes[1];
|
||||
|
||||
canWriteCompressed = true;
|
||||
}
|
||||
}
|
@ -80,9 +80,9 @@ final class MutableIndexColorModel extends ColorModel {
|
||||
// TODO: Move validation to chunk (when reading)
|
||||
if (index >= rgbs.length) {
|
||||
// TODO: Issue IIO warning
|
||||
System.err.printf("warning - palette change register out of range\n");
|
||||
System.err.println("warning - palette change register out of range");
|
||||
System.err.printf(" change structure %d index=%d (max %d)\n", i, index, getMapSize() - 1);
|
||||
System.err.printf(" ignoring it... colors might get messed up from here\n");
|
||||
System.err.println(" ignoring it... colors might get messed up from here");
|
||||
}
|
||||
else if (index != MP_REG_IGNORE) {
|
||||
updateRGB(index, ((changes[i].r & 0xff) << 16) | ((changes[i].g & 0xff) << 8) | (changes[i].b & 0xff));
|
||||
|
@ -72,42 +72,43 @@ final class PCHGChunk extends AbstractMultiPaletteChunk {
|
||||
private int totalChanges;
|
||||
private int minReg;
|
||||
|
||||
PCHGChunk(int pChunkLength) {
|
||||
super(IFF.CHUNK_PCHG, pChunkLength);
|
||||
PCHGChunk(int chunkLength) {
|
||||
super(IFF.CHUNK_PCHG, chunkLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
void readChunk(final DataInput pInput) throws IOException {
|
||||
int compression = pInput.readUnsignedShort();
|
||||
int flags = pInput.readUnsignedShort();
|
||||
startLine = pInput.readShort();
|
||||
lineCount = pInput.readUnsignedShort();
|
||||
changedLines = pInput.readUnsignedShort();
|
||||
minReg = pInput.readUnsignedShort();
|
||||
int maxReg = pInput.readUnsignedShort();
|
||||
/*int maxChangesPerLine = */pInput.readUnsignedShort(); // We don't really care, as we're not limited by the Amiga display hardware
|
||||
totalChanges = pInput.readInt();
|
||||
void readChunk(final DataInput input) throws IOException {
|
||||
int compression = input.readUnsignedShort();
|
||||
int flags = input.readUnsignedShort();
|
||||
startLine = input.readShort();
|
||||
lineCount = input.readUnsignedShort();
|
||||
changedLines = input.readUnsignedShort();
|
||||
minReg = input.readUnsignedShort();
|
||||
int maxReg = input.readUnsignedShort();
|
||||
/*int maxChangesPerLine = */
|
||||
input.readUnsignedShort(); // We don't really care, as we're not limited by the Amiga display hardware
|
||||
totalChanges = input.readInt();
|
||||
|
||||
byte[] data;
|
||||
|
||||
switch (compression) {
|
||||
case PCHG_COMP_NONE:
|
||||
data = new byte[chunkLength - 20];
|
||||
pInput.readFully(data);
|
||||
input.readFully(data);
|
||||
|
||||
break;
|
||||
case PCHG_COMP_HUFFMAN:
|
||||
// NOTE: Huffman decompression is completely untested, due to lack of source data (read: Probably broken).
|
||||
int compInfoSize = pInput.readInt();
|
||||
int originalDataSize = pInput.readInt();
|
||||
int compInfoSize = input.readInt();
|
||||
int originalDataSize = input.readInt();
|
||||
|
||||
short[] compTree = new short[compInfoSize / 2];
|
||||
for (int i = 0; i < compTree.length; i++) {
|
||||
compTree[i] = pInput.readShort();
|
||||
compTree[i] = input.readShort();
|
||||
}
|
||||
|
||||
byte[] compData = new byte[chunkLength - 20 - 8 - compInfoSize];
|
||||
pInput.readFully(compData);
|
||||
input.readFully(compData);
|
||||
|
||||
data = new byte[originalDataSize];
|
||||
|
||||
|
@ -1,3 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2022, 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 of the copyright holder 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 HOLDER 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.plugins.iff;
|
||||
|
||||
import com.twelvemonkeys.io.enc.DecodeException;
|
||||
@ -13,7 +43,7 @@ import java.nio.ByteBuffer;
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: RGB8Stream.java,v 1.0 28/01/2022 haraldk Exp$
|
||||
* @version $Id: RGB8RLEDecoder.java,v 1.0 28/01/2022 haraldk Exp$
|
||||
*
|
||||
* @see <a href="https://wiki.amigaos.net/wiki/RGBN_and_RGB8_IFF_Image_Data">RGBN and RGB8 IFF Image Data</a>
|
||||
*/
|
||||
|
@ -38,8 +38,8 @@ package com.twelvemonkeys.imageio.plugins.iff;
|
||||
* @version $Id: SHAMChunk.java,v 1.0 30.03.12 14:53 haraldk Exp$
|
||||
*/
|
||||
final class SHAMChunk extends AbstractMultiPaletteChunk {
|
||||
SHAMChunk(int pChunkLength) {
|
||||
super(IFF.CHUNK_SHAM, pChunkLength);
|
||||
SHAMChunk(int chunkLength) {
|
||||
super(IFF.CHUNK_SHAM, chunkLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,26 +1,24 @@
|
||||
package com.twelvemonkeys.imageio.plugins.iff;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.awt.image.IndexColorModel;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.function.ThrowingRunnable;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
import java.awt.image.IndexColorModel;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class IFFImageMetadataTest {
|
||||
@Test
|
||||
public void testStandardFeatures() {
|
||||
BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
|
||||
public void testStandardFeatures() throws IIOException {
|
||||
Form header = Form.ofType(IFF.TYPE_ILBM)
|
||||
.with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
|
||||
|
||||
final IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
|
||||
final IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
// Standard metadata format
|
||||
assertTrue(metadata.isStandardMetadataFormatSupported());
|
||||
@ -49,10 +47,11 @@ public class IFFImageMetadataTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStandardChromaGray() {
|
||||
BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
|
||||
public void testStandardChromaGray() throws IIOException {
|
||||
Form header = Form.ofType(IFF.TYPE_ILBM)
|
||||
.with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
IIOMetadataNode chroma = metadata.getStandardChromaNode();
|
||||
assertNotNull(chroma);
|
||||
@ -75,10 +74,11 @@ public class IFFImageMetadataTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStandardChromaRGB() {
|
||||
BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
|
||||
public void testStandardChromaRGB() throws IIOException {
|
||||
Form header = Form.ofType(IFF.TYPE_ILBM)
|
||||
.with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
IIOMetadataNode chroma = metadata.getStandardChromaNode();
|
||||
assertNotNull(chroma);
|
||||
@ -101,16 +101,17 @@ public class IFFImageMetadataTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStandardChromaPalette() {
|
||||
BMHDChunk header = new BMHDChunk(300, 200, 1, BMHDChunk.MASK_TRANSPARENT_COLOR, BMHDChunk.COMPRESSION_BYTE_RUN, 1);
|
||||
public void testStandardChromaPalette() throws IIOException {
|
||||
Form header = Form.ofType(IFF.TYPE_ILBM)
|
||||
.with(new BMHDChunk(300, 200, 1, BMHDChunk.MASK_TRANSPARENT_COLOR, BMHDChunk.COMPRESSION_BYTE_RUN, 1));
|
||||
|
||||
byte[] bw = {0, (byte) 0xff};
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, new IndexColorModel(header.bitplanes, bw.length, bw, bw, bw, header.transparentIndex), null, Collections.<GenericChunk>emptyList());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, new IndexColorModel(header.bitplanes(), bw.length, bw, bw, bw, header.transparentIndex()));
|
||||
|
||||
IIOMetadataNode chroma = metadata.getStandardChromaNode();
|
||||
assertNotNull(chroma);
|
||||
assertEquals("Chroma", chroma.getNodeName());
|
||||
assertEquals(4, chroma.getLength());
|
||||
assertEquals(5, chroma.getLength());
|
||||
|
||||
IIOMetadataNode colorSpaceType = (IIOMetadataNode) chroma.getFirstChild();
|
||||
assertEquals("ColorSpaceType", colorSpaceType.getNodeName());
|
||||
@ -138,14 +139,21 @@ public class IFFImageMetadataTest {
|
||||
assertEquals(rgb, item0.getAttribute("blue"));
|
||||
}
|
||||
|
||||
// TODO: BackgroundIndex == 1??
|
||||
// BackgroundIndex == 1
|
||||
IIOMetadataNode backgroundIndex = (IIOMetadataNode) palette.getNextSibling();
|
||||
assertEquals("BackgroundIndex", backgroundIndex.getNodeName());
|
||||
assertEquals("1", backgroundIndex.getAttribute("value"));
|
||||
|
||||
// No more elements
|
||||
assertNull(backgroundIndex.getNextSibling());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStandardCompressionRLE() {
|
||||
BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
|
||||
public void testStandardCompressionRLE() throws IIOException {
|
||||
Form header = Form.ofType(IFF.TYPE_ILBM)
|
||||
.with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
IIOMetadataNode compression = metadata.getStandardCompressionNode();
|
||||
assertNotNull(compression);
|
||||
@ -164,19 +172,21 @@ public class IFFImageMetadataTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStandardCompressionNone() {
|
||||
BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_NONE, 0);
|
||||
public void testStandardCompressionNone() throws IIOException {
|
||||
Form header = Form.ofType(IFF.TYPE_ILBM)
|
||||
.with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_NONE, 0));
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
assertNull(metadata.getStandardCompressionNode()); // No compression, all default...
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStandardDataILBM_Gray() {
|
||||
BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
|
||||
public void testStandardDataILBM_Gray() throws IIOException {
|
||||
Form header = Form.ofType(IFF.TYPE_ILBM)
|
||||
.with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
IIOMetadataNode data = metadata.getStandardDataNode();
|
||||
assertNotNull(data);
|
||||
@ -199,10 +209,11 @@ public class IFFImageMetadataTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStandardDataILBM_RGB() {
|
||||
BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
|
||||
public void testStandardDataILBM_RGB() throws IIOException {
|
||||
Form header = Form.ofType(IFF.TYPE_ILBM)
|
||||
.with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
IIOMetadataNode data = metadata.getStandardDataNode();
|
||||
assertNotNull(data);
|
||||
@ -225,10 +236,11 @@ public class IFFImageMetadataTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStandardDataILBM_RGBA() {
|
||||
BMHDChunk header = new BMHDChunk(300, 200, 32, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
|
||||
public void testStandardDataILBM_RGBA() throws IIOException {
|
||||
Form header = Form.ofType(IFF.TYPE_ILBM)
|
||||
.with(new BMHDChunk(300, 200, 32, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
IIOMetadataNode data = metadata.getStandardDataNode();
|
||||
assertNotNull(data);
|
||||
@ -251,12 +263,13 @@ public class IFFImageMetadataTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStandardDataILBM_Palette() {
|
||||
public void testStandardDataILBM_Palette() throws IIOException {
|
||||
for (int i = 1; i <= 8; i++) {
|
||||
BMHDChunk header = new BMHDChunk(300, 200, i, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
|
||||
Form header = Form.ofType(IFF.TYPE_ILBM)
|
||||
.with(new BMHDChunk(300, 200, i, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
|
||||
|
||||
byte[] rgb = new byte[2 << i]; // Colors doesn't really matter here
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, new IndexColorModel(header.bitplanes, rgb.length, rgb, rgb, rgb, 0), null, Collections.<GenericChunk>emptyList());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, new IndexColorModel(header.bitplanes(), rgb.length, rgb, rgb, rgb, 0));
|
||||
|
||||
IIOMetadataNode data = metadata.getStandardDataNode();
|
||||
assertNotNull(data);
|
||||
@ -280,10 +293,11 @@ public class IFFImageMetadataTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStandardDataPBM_Gray() {
|
||||
BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
|
||||
public void testStandardDataPBM_Gray() throws IIOException {
|
||||
Form header = Form.ofType(IFF.TYPE_PBM)
|
||||
.with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_PBM, header, null, null, Collections.<GenericChunk>emptyList());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
IIOMetadataNode data = metadata.getStandardDataNode();
|
||||
assertNotNull(data);
|
||||
@ -306,10 +320,11 @@ public class IFFImageMetadataTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStandardDataPBM_RGB() {
|
||||
BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
|
||||
public void testStandardDataPBM_RGB() throws IIOException {
|
||||
Form header = Form.ofType(IFF.TYPE_PBM)
|
||||
.with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_PBM, header, null, null, Collections.<GenericChunk>emptyList());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
IIOMetadataNode data = metadata.getStandardDataNode();
|
||||
assertNotNull(data);
|
||||
@ -333,40 +348,57 @@ public class IFFImageMetadataTest {
|
||||
|
||||
|
||||
@Test
|
||||
public void testStandardDimensionNoViewport() {
|
||||
BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
|
||||
public void testStandardDimensionNoViewport() throws IIOException {
|
||||
BMHDChunk bitmapHeader = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
|
||||
bitmapHeader.xAspect = 0;
|
||||
bitmapHeader.yAspect = 0;
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
|
||||
Form header = Form.ofType(IFF.TYPE_ILBM)
|
||||
.with(bitmapHeader);
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
IIOMetadataNode dimension = metadata.getStandardDimensionNode();
|
||||
assertNull(dimension);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStandardDimensionNormal() {
|
||||
BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
|
||||
public void testStandardDimensionNormal() throws IIOException {
|
||||
Form header = Form.ofType(IFF.TYPE_ILBM)
|
||||
.with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0))
|
||||
.with(new CAMGChunk(4));
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, new CAMGChunk(4), Collections.<GenericChunk>emptyList());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
IIOMetadataNode dimension = metadata.getStandardDimensionNode();
|
||||
assertNotNull(dimension);
|
||||
assertEquals("Dimension", dimension.getNodeName());
|
||||
assertEquals(1, dimension.getLength());
|
||||
|
||||
IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild();
|
||||
assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName());
|
||||
assertEquals("1.0", pixelAspectRatio.getAttribute("value"));
|
||||
// No Dimension node is okay, or one with an aspect ratio of 1.0
|
||||
if (dimension != null) {
|
||||
assertEquals("Dimension", dimension.getNodeName());
|
||||
assertEquals(1, dimension.getLength());
|
||||
|
||||
assertNull(pixelAspectRatio.getNextSibling()); // No more children
|
||||
IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild();
|
||||
assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName());
|
||||
assertEquals("1.0", pixelAspectRatio.getAttribute("value"));
|
||||
|
||||
assertNull(pixelAspectRatio.getNextSibling()); // No more children
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStandardDimensionHires() {
|
||||
BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
|
||||
public void testStandardDimensionHires() throws IIOException {
|
||||
BMHDChunk bitmapHeader = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
|
||||
bitmapHeader.xAspect = 2;
|
||||
bitmapHeader.yAspect = 1;
|
||||
|
||||
CAMGChunk viewPort = new CAMGChunk(4);
|
||||
viewPort.camg = 0x8000;
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, viewPort, Collections.<GenericChunk>emptyList());
|
||||
Form header = Form.ofType(IFF.TYPE_ILBM)
|
||||
.with(bitmapHeader)
|
||||
.with(viewPort);
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
IIOMetadataNode dimension = metadata.getStandardDimensionNode();
|
||||
assertNotNull(dimension);
|
||||
@ -381,13 +413,19 @@ public class IFFImageMetadataTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStandardDimensionInterlaced() {
|
||||
BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
|
||||
public void testStandardDimensionInterlaced() throws IIOException {
|
||||
BMHDChunk bitmapHeader = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
|
||||
bitmapHeader.xAspect = 1;
|
||||
bitmapHeader.yAspect = 2;
|
||||
|
||||
CAMGChunk viewPort = new CAMGChunk(4);
|
||||
viewPort.camg = 0x4;
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, viewPort, Collections.<GenericChunk>emptyList());
|
||||
Form header = Form.ofType(IFF.TYPE_ILBM)
|
||||
.with(bitmapHeader)
|
||||
.with(viewPort);
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
IIOMetadataNode dimension = metadata.getStandardDimensionNode();
|
||||
assertNotNull(dimension);
|
||||
@ -402,13 +440,14 @@ public class IFFImageMetadataTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStandardDimensionHiresInterlaced() {
|
||||
BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
|
||||
|
||||
public void testStandardDimensionHiresInterlaced() throws IIOException {
|
||||
CAMGChunk viewPort = new CAMGChunk(4);
|
||||
viewPort.camg = 0x8004;
|
||||
Form header = Form.ofType(IFF.TYPE_ILBM)
|
||||
.with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0))
|
||||
.with(viewPort);
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, viewPort, Collections.<GenericChunk>emptyList());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
IIOMetadataNode dimension = metadata.getStandardDimensionNode();
|
||||
assertNotNull(dimension);
|
||||
@ -423,10 +462,11 @@ public class IFFImageMetadataTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStandardDocument() {
|
||||
BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
|
||||
public void testStandardDocument() throws IIOException {
|
||||
Form header = Form.ofType(IFF.TYPE_ILBM)
|
||||
.with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
IIOMetadataNode document = metadata.getStandardDocumentNode();
|
||||
assertNotNull(document);
|
||||
@ -441,13 +481,15 @@ public class IFFImageMetadataTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStandardText() {
|
||||
BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
|
||||
|
||||
public void testStandardText() throws IIOException {
|
||||
int[] chunks = {IFF.CHUNK_ANNO, IFF.CHUNK_UTF8};
|
||||
String[] texts = {"annotation", "äñnótâtïøñ"};
|
||||
List<GenericChunk> meta = Arrays.asList(new GenericChunk(IFF.CHUNK_ANNO, texts[0].getBytes(StandardCharsets.US_ASCII)),
|
||||
new GenericChunk(IFF.CHUNK_UTF8, texts[1].getBytes(StandardCharsets.UTF_8)));
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, meta);
|
||||
Form header = Form.ofType(IFF.TYPE_ILBM)
|
||||
.with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0))
|
||||
.with(new GenericChunk(chunks[0], texts[0].getBytes(StandardCharsets.US_ASCII)))
|
||||
.with(new GenericChunk(chunks[1], texts[1].getBytes(StandardCharsets.UTF_8)));
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
IIOMetadataNode text = metadata.getStandardTextNode();
|
||||
assertNotNull(text);
|
||||
@ -457,26 +499,28 @@ public class IFFImageMetadataTest {
|
||||
for (int i = 0; i < texts.length; i++) {
|
||||
IIOMetadataNode textEntry = (IIOMetadataNode) text.item(i);
|
||||
assertEquals("TextEntry", textEntry.getNodeName());
|
||||
assertEquals(IFFUtil.toChunkStr(meta.get(i).chunkId), textEntry.getAttribute("keyword"));
|
||||
assertEquals(IFFUtil.toChunkStr(chunks[i]), textEntry.getAttribute("keyword"));
|
||||
assertEquals(texts[i], textEntry.getAttribute("value"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStandardTransparencyRGB() {
|
||||
BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
|
||||
public void testStandardTransparencyRGB() throws IIOException {
|
||||
Form header = Form.ofType(IFF.TYPE_ILBM)
|
||||
.with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
IIOMetadataNode transparency = metadata.getStandardTransparencyNode();
|
||||
assertNull(transparency); // No transparency, just defaults
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStandardTransparencyRGBA() {
|
||||
BMHDChunk header = new BMHDChunk(300, 200, 32, BMHDChunk.MASK_HAS_MASK, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
|
||||
public void testStandardTransparencyRGBA() throws IIOException {
|
||||
Form header = Form.ofType(IFF.TYPE_ILBM)
|
||||
.with(new BMHDChunk(300, 200, 32, BMHDChunk.MASK_HAS_MASK, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
|
||||
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
IIOMetadataNode transparency = metadata.getStandardTransparencyNode();
|
||||
assertNotNull(transparency);
|
||||
@ -491,11 +535,12 @@ public class IFFImageMetadataTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStandardTransparencyPalette() {
|
||||
BMHDChunk header = new BMHDChunk(300, 200, 1, BMHDChunk.MASK_TRANSPARENT_COLOR, BMHDChunk.COMPRESSION_BYTE_RUN, 1);
|
||||
public void testStandardTransparencyPalette() throws IIOException {
|
||||
Form header = Form.ofType(IFF.TYPE_ILBM)
|
||||
.with(new BMHDChunk(300, 200, 1, BMHDChunk.MASK_TRANSPARENT_COLOR, BMHDChunk.COMPRESSION_BYTE_RUN, 1));
|
||||
|
||||
byte[] bw = {0, (byte) 0xff};
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, new IndexColorModel(header.bitplanes, bw.length, bw, bw, bw, header.transparentIndex), null, Collections.<GenericChunk>emptyList());
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, new IndexColorModel(header.bitplanes(), bw.length, bw, bw, bw, header.transparentIndex()));
|
||||
|
||||
IIOMetadataNode transparency = metadata.getStandardTransparencyNode();
|
||||
assertNotNull(transparency);
|
||||
@ -508,4 +553,131 @@ public class IFFImageMetadataTest {
|
||||
|
||||
assertNull(pixelAspectRatio.getNextSibling()); // No more children
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStandardRGB8() throws IIOException {
|
||||
Form header = Form.ofType(IFF.TYPE_RGB8)
|
||||
.with(new BMHDChunk(300, 200, 25, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
// Chroma
|
||||
IIOMetadataNode chroma = metadata.getStandardChromaNode();
|
||||
assertNotNull(chroma);
|
||||
assertEquals("Chroma", chroma.getNodeName());
|
||||
assertEquals(3, chroma.getLength());
|
||||
|
||||
IIOMetadataNode colorSpaceType = (IIOMetadataNode) chroma.getFirstChild();
|
||||
assertEquals("ColorSpaceType", colorSpaceType.getNodeName());
|
||||
assertEquals("RGB", colorSpaceType.getAttribute("name"));
|
||||
|
||||
IIOMetadataNode numChannels = (IIOMetadataNode) colorSpaceType.getNextSibling();
|
||||
assertEquals("NumChannels", numChannels.getNodeName());
|
||||
assertEquals("4", numChannels.getAttribute("value"));
|
||||
|
||||
IIOMetadataNode blackIsZero = (IIOMetadataNode) numChannels.getNextSibling();
|
||||
assertEquals("BlackIsZero", blackIsZero.getNodeName());
|
||||
assertEquals("TRUE", blackIsZero.getAttribute("value"));
|
||||
|
||||
assertNull(blackIsZero.getNextSibling()); // No more children
|
||||
|
||||
// Data
|
||||
IIOMetadataNode data = metadata.getStandardDataNode();
|
||||
assertNotNull(data);
|
||||
assertEquals("Data", data.getNodeName());
|
||||
assertEquals(3, data.getLength());
|
||||
|
||||
IIOMetadataNode planarConfiguration = (IIOMetadataNode) data.getFirstChild();
|
||||
assertEquals("PlanarConfiguration", planarConfiguration.getNodeName());
|
||||
assertEquals("PixelInterleaved", planarConfiguration.getAttribute("value"));
|
||||
|
||||
IIOMetadataNode sampleFormat = (IIOMetadataNode) planarConfiguration.getNextSibling();
|
||||
assertEquals("SampleFormat", sampleFormat.getNodeName());
|
||||
assertEquals("UnsignedIntegral", sampleFormat.getAttribute("value"));
|
||||
|
||||
IIOMetadataNode bitsPerSample = (IIOMetadataNode) sampleFormat.getNextSibling();
|
||||
assertEquals("BitsPerSample", bitsPerSample.getNodeName());
|
||||
assertEquals("8 8 8 1", bitsPerSample.getAttribute("value"));
|
||||
|
||||
assertNull(bitsPerSample.getNextSibling()); // No more children
|
||||
|
||||
// Transparency
|
||||
IIOMetadataNode transparency = metadata.getStandardTransparencyNode();
|
||||
assertNotNull(transparency);
|
||||
assertEquals("Transparency", transparency.getNodeName());
|
||||
assertEquals(1, transparency.getLength());
|
||||
|
||||
IIOMetadataNode alpha = (IIOMetadataNode) transparency.getFirstChild();
|
||||
assertEquals("Alpha", alpha.getNodeName());
|
||||
assertEquals("nonpremultiplied", alpha.getAttribute("value"));
|
||||
|
||||
assertNull(alpha.getNextSibling()); // No more children
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStandardDEEP() throws IIOException {
|
||||
DPELChunk dpel = new DPELChunk(20);
|
||||
dpel.typeDepths = new DPELChunk.TypeDepth[4];
|
||||
for (int i = 0; i < dpel.typeDepths.length; i++) {
|
||||
dpel.typeDepths[i] = new DPELChunk.TypeDepth(i == 0 ? 11 : i, 8);
|
||||
}
|
||||
|
||||
Form header = Form.ofType(IFF.TYPE_DEEP)
|
||||
.with(new DGBLChunk(8))
|
||||
.with(dpel);
|
||||
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
|
||||
|
||||
// Chroma
|
||||
IIOMetadataNode chroma = metadata.getStandardChromaNode();
|
||||
assertNotNull(chroma);
|
||||
assertEquals("Chroma", chroma.getNodeName());
|
||||
assertEquals(3, chroma.getLength());
|
||||
|
||||
IIOMetadataNode colorSpaceType = (IIOMetadataNode) chroma.getFirstChild();
|
||||
assertEquals("ColorSpaceType", colorSpaceType.getNodeName());
|
||||
assertEquals("RGB", colorSpaceType.getAttribute("name"));
|
||||
|
||||
IIOMetadataNode numChannels = (IIOMetadataNode) colorSpaceType.getNextSibling();
|
||||
assertEquals("NumChannels", numChannels.getNodeName());
|
||||
assertEquals("4", numChannels.getAttribute("value"));
|
||||
|
||||
IIOMetadataNode blackIsZero = (IIOMetadataNode) numChannels.getNextSibling();
|
||||
assertEquals("BlackIsZero", blackIsZero.getNodeName());
|
||||
assertEquals("TRUE", blackIsZero.getAttribute("value"));
|
||||
|
||||
// TODO: BackgroundColor = 0x666666
|
||||
|
||||
assertNull(blackIsZero.getNextSibling()); // No more children
|
||||
|
||||
// Data
|
||||
IIOMetadataNode data = metadata.getStandardDataNode();
|
||||
assertNotNull(data);
|
||||
assertEquals("Data", data.getNodeName());
|
||||
assertEquals(3, data.getLength());
|
||||
|
||||
IIOMetadataNode planarConfiguration = (IIOMetadataNode) data.getFirstChild();
|
||||
assertEquals("PlanarConfiguration", planarConfiguration.getNodeName());
|
||||
assertEquals("PixelInterleaved", planarConfiguration.getAttribute("value"));
|
||||
|
||||
IIOMetadataNode sampleFormat = (IIOMetadataNode) planarConfiguration.getNextSibling();
|
||||
assertEquals("SampleFormat", sampleFormat.getNodeName());
|
||||
assertEquals("UnsignedIntegral", sampleFormat.getAttribute("value"));
|
||||
|
||||
IIOMetadataNode bitsPerSample = (IIOMetadataNode) sampleFormat.getNextSibling();
|
||||
assertEquals("BitsPerSample", bitsPerSample.getNodeName());
|
||||
assertEquals("8 8 8 8", bitsPerSample.getAttribute("value"));
|
||||
|
||||
assertNull(bitsPerSample.getNextSibling()); // No more children
|
||||
|
||||
// Transparency
|
||||
IIOMetadataNode transparency = metadata.getStandardTransparencyNode();
|
||||
assertNotNull(transparency);
|
||||
assertEquals("Transparency", transparency.getNodeName());
|
||||
assertEquals(1, transparency.getLength());
|
||||
|
||||
IIOMetadataNode alpha = (IIOMetadataNode) transparency.getFirstChild();
|
||||
assertEquals("Alpha", alpha.getNodeName());
|
||||
assertEquals("premultiplied", alpha.getAttribute("value"));
|
||||
|
||||
assertNull(alpha.getNextSibling()); // No more children
|
||||
}
|
||||
}
|
@ -92,7 +92,12 @@ public class IFFImageReaderTest extends ImageReaderAbstractTest<IFFImageReader>
|
||||
// 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))
|
||||
new TestData(getClassLoaderResource("/iff/tunnel04-adpro-cross-boundary-runs.rgb8"), new Dimension(640, 480)),
|
||||
// TVPaint (TecSoft) DEEP format
|
||||
new TestData(getClassLoaderResource("/iff/arch.deep"), new Dimension(800, 600)),
|
||||
// TVPaint Project (TVPP is effectively same as the DEEP format, but multiple layers, background color etc.)
|
||||
// TODO: This file contains one more image/layer, second DBOD chunk @1868908, len: 1199144!
|
||||
new TestData(getClassLoaderResource("/iff/warm-and-bright.pro"), new Dimension(800, 600))
|
||||
);
|
||||
}
|
||||
|
||||
@ -103,7 +108,7 @@ public class IFFImageReaderTest extends ImageReaderAbstractTest<IFFImageReader>
|
||||
|
||||
@Override
|
||||
protected List<String> getSuffixes() {
|
||||
return Arrays.asList("iff", "ilbm", "ham", "ham8", "lbm");
|
||||
return Arrays.asList("iff", "ilbm", "ham", "ham8", "lbm", "rgb8", "deep");
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -138,9 +143,14 @@ public class IFFImageReaderTest extends ImageReaderAbstractTest<IFFImageReader>
|
||||
|
||||
for (int i = 0; i < 32; i++) {
|
||||
// Make sure the color model is really EHB
|
||||
assertEquals("red", (reds[i] & 0xff) / 2, reds[i + 32] & 0xff);
|
||||
assertEquals("blue", (blues[i] & 0xff) / 2, blues[i + 32] & 0xff);
|
||||
assertEquals("green", (greens[i] & 0xff) / 2, greens[i + 32] & 0xff);
|
||||
try {
|
||||
assertEquals("red", (reds[i] & 0xff) / 2, reds[i + 32] & 0xff);
|
||||
assertEquals("blue", (blues[i] & 0xff) / 2, blues[i + 32] & 0xff);
|
||||
assertEquals("green", (greens[i] & 0xff) / 2, greens[i + 32] & 0xff);
|
||||
}
|
||||
catch (AssertionError err) {
|
||||
throw new AssertionError("Color " + i + " " + err.getMessage(), err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
BIN
imageio/imageio-iff/src/test/resources/iff/arch.deep
Normal file
BIN
imageio/imageio-iff/src/test/resources/iff/arch.deep
Normal file
Binary file not shown.
BIN
imageio/imageio-iff/src/test/resources/iff/warm-and-bright.pro
Normal file
BIN
imageio/imageio-iff/src/test/resources/iff/warm-and-bright.pro
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user