It all works

This commit is contained in:
Erlend Hamnaberg
2009-11-08 19:52:30 +01:00
parent b8faa6e36f
commit 0786949c1c
319 changed files with 0 additions and 0 deletions
@@ -0,0 +1,180 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.iff;
import javax.imageio.IIOException;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
/**
* BMHDChunk
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: BMHDChunk.java,v 1.0 28.feb.2006 00:04:32 haku Exp$
*/
class BMHDChunk extends IFFChunk {
//
// typedef UBYTE Masking; /* Choice of masking technique. */
//
// #define mskNone 0
// #define mskHasMask 1
// #define mskHasTransparentColor 2
// #define mskLasso 3
//
// typedef UBYTE Compression; /* Choice of compression algorithm
// applied to the rows of all source and mask planes. "cmpByteRun1"
// is the byte run encoding described in Appendix C. Do not compress
// across rows! */
// #define cmpNone 0
// #define cmpByteRun1 1
//
// typedef struct {
// UWORD w, h; /* raster width & height in pixels */
// WORD x, y; /* pixel position for this image */
// UBYTE nPlanes; /* # source bitplanes */
// Masking masking;
// Compression compression;
// UBYTE pad1; /* unused; ignore on read, write as 0 */
// UWORD transparentColor; /* transparent "color number" (sort of) */
// UBYTE xAspect, yAspect; /* pixel aspect, a ratio width : height */
// WORD pageWidth, pageHeight; /* source "page" size in pixels */
// } BitMapHeader;*/
static final int MASK_NONE = 0;
static final int MASK_HAS_MASK = 1;
static final int MASK_TRANSPARENT_COLOR = 2;
static final int MASK_LASSO = 3;
static final int COMPRESSION_NONE = 0;
// RLE
static final int COMPRESSION_BYTE_RUN = 1;
// 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)
// Dimensions of raster
int mWidth;
int mHeight;
// Source offsets
// Hmm.. Consider making these Image.properties?
int mXPos;
int mYPos;
// The number of source bitplanes in the BODY chunk (see below) is stored in
// nPlanes. An ILBM with a CMAP but no BODY and nPlanes = 0 is the
// recommended way to store a color map.
int mBitplanes;
int mMaskType;
int mCompressionType;
int mTransparentIndex;
// NOTE: Typical values are 10:11 (320 x 200)
int mXAspect;
int mYAspect;
// Source page dimension
// NOTE: The image may be larger than the page, probably ignore these
int mPageWidth;
int mPageHeight;
protected BMHDChunk(int pChunkLength) {
super(IFF.CHUNK_BMHD, pChunkLength);
}
protected BMHDChunk(int pWidth, int pHeight, int pBitplanes,
int pMaskType, int pCompressionType,
int pTransparentIndex) {
super(IFF.CHUNK_BMHD, 20);
mWidth = pWidth;
mHeight = pHeight;
mXPos = 0;
mYPos = 0;
mBitplanes = pBitplanes;
mMaskType = pMaskType;
mCompressionType = pCompressionType;
mTransparentIndex = pTransparentIndex;
mXAspect = 1;
mYAspect = 1;
mPageWidth = Math.min(pWidth, Short.MAX_VALUE); // For some reason, these are signed?
mPageHeight = Math.min(pHeight, Short.MAX_VALUE);
}
void readChunk(DataInput pInput) throws IOException {
if (mChunkLength != 20) {
throw new IIOException("Unknown BMHD chunk length: " + mChunkLength);
}
mWidth = pInput.readUnsignedShort();
mHeight = pInput.readUnsignedShort();
mXPos = pInput.readShort();
mYPos = pInput.readShort();
mBitplanes = pInput.readUnsignedByte();
mMaskType = pInput.readUnsignedByte();
mCompressionType = pInput.readUnsignedByte();
pInput.readByte(); // PAD
mTransparentIndex = pInput.readUnsignedShort();
mXAspect = pInput.readUnsignedByte();
mYAspect = pInput.readUnsignedByte();
mPageWidth = pInput.readShort();
mPageHeight = pInput.readShort();
}
void writeChunk(DataOutput pOutput) throws IOException {
pOutput.writeInt(mChunkId);
pOutput.writeInt(mChunkLength);
pOutput.writeShort(mWidth);
pOutput.writeShort(mHeight);
pOutput.writeShort(mXPos);
pOutput.writeShort(mYPos);
pOutput.writeByte(mBitplanes);
pOutput.writeByte(mMaskType);
pOutput.writeByte(mCompressionType);
pOutput.writeByte(0); // PAD
pOutput.writeShort(mTransparentIndex);
pOutput.writeByte(mXAspect);
pOutput.writeByte(mYAspect);
pOutput.writeShort(mPageWidth);
pOutput.writeShort(mPageHeight);
}
public String toString() {
return super.toString()
+ " {w=" + mWidth + ", h=" + mHeight
+ ", x=" + mXPos + ", y=" + mYPos
+ ", planes=" + mBitplanes + ", mask=" + mMaskType
+ ", compression=" + mCompressionType + ", trans=" + mTransparentIndex
+ ", xAspect=" + mXAspect + ", yAspect=" + mYAspect
+ ", pageWidth=" + mPageWidth + ", pageHeight=" + mPageHeight + "}";
}
}
@@ -0,0 +1,55 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.iff;
import java.io.DataInput;
import java.io.IOException;
import java.io.DataOutput;
/**
* BODYChunk
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: BODYChunk.java,v 1.0 28.feb.2006 01:25:49 haku Exp$
*/
class BODYChunk extends IFFChunk {
protected BODYChunk(int pChunkLength) {
super(IFF.CHUNK_BODY, pChunkLength);
}
void readChunk(DataInput pInput) throws IOException {
throw new InternalError("BODY chunk should only be read from IFFImageReader");
}
void writeChunk(DataOutput pOutput) throws IOException {
throw new InternalError("BODY chunk should only be written from IFFImageWriter");
}
}
@@ -0,0 +1,77 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.iff;
import javax.imageio.IIOException;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
/**
* CAMGChunk
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: CAMGChunk.java,v 1.0 28.feb.2006 02:10:07 haku Exp$
*/
class CAMGChunk extends IFFChunk {
// HIRES=0x8000, LACE=0x4
// #define CAMG_HAM 0x800 /* hold and modify */
// #define CAMG_EHB 0x80 /* extra halfbrite */
private int mCAMG;
public CAMGChunk(int pLength) {
super(IFF.CHUNK_CAMG, pLength);
}
void readChunk(DataInput pInput) throws IOException {
if (mChunkLength != 4) {
throw new IIOException("Unknown CAMG chunk length: " + mChunkLength);
}
mCAMG = pInput.readInt();
}
void writeChunk(DataOutput pOutput) throws IOException {
throw new InternalError("Not implemented: writeChunk()");
}
boolean isHAM() {
return (mCAMG & 0x800) != 0;
}
boolean isEHB() {
return (mCAMG & 0x80) != 0;
}
public String toString() {
return super.toString() + " {mode=" + (isHAM() ? "HAM" : isEHB() ? "EHB" : "Normal") + "}";
}
}
@@ -0,0 +1,170 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.iff;
import com.twelvemonkeys.image.InverseColorMapIndexColorModel;
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;
/**
* CMAPChunk
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: CMAPChunk.java,v 1.0 28.feb.2006 00:38:05 haku Exp$
*/
class CMAPChunk extends IFFChunk {
// typedef struct {
// UBYTE red, green, blue; /* color intensities 0..255 */
// } ColorRegister; /* size = 3 bytes */
//
// typedef ColorRegister ColorMap[n]; /* size = 3n bytes */
byte[] mReds;
byte[] mGreens;
byte[] mBlues;
boolean mEHB;
final private BMHDChunk mHeader;
final private CAMGChunk mCamg;
private IndexColorModel mModel;
protected CMAPChunk(int pChunkLength, BMHDChunk pHeader, CAMGChunk pCamg) {
super(IFF.CHUNK_CMAP, pChunkLength);
mHeader = pHeader;
mCamg = pCamg;
}
public CMAPChunk(IndexColorModel pModel) {
super(IFF.CHUNK_CMAP, pModel.getMapSize() * 3);
mModel = pModel;
mHeader = null;
mCamg = null;
}
void readChunk(DataInput pInput) throws IOException {
int numColors = mChunkLength / 3;
int paletteSize = numColors;
boolean isEHB = mCamg != null && mCamg.isEHB();
if (isEHB) {
if (numColors == 32) {
paletteSize = 64;
}
else {
throw new IIOException("Unknown number of colors for EHB: " + numColors);
}
}
mReds = new byte[paletteSize];
mGreens = mReds.clone();
mBlues = mReds.clone();
for (int i = 0; i < numColors; i++) {
mReds[i] = pInput.readByte();
mGreens[i] = pInput.readByte();
mBlues[i] = pInput.readByte();
}
if (isEHB) {
for (int i = 0; i < numColors; i++) {
mReds[i + numColors] = (byte) ((mReds[i] & 0xff) / 2);
mGreens[i + numColors] = (byte) ((mGreens[i] & 0xff) / 2);
mBlues[i + numColors] = (byte) ((mBlues[i] & 0xff) / 2);
}
}
// TODO: When reading in a CMAP for 8-bit-per-gun display or
// manipulation, you may want to assume that any CMAP which has 0 values
// for the low bits of all guns for all registers was stored shifted
// rather than scaled, and provide your own scaling.
// Use defaults if the color map is absent or has fewer color registers
// than you need. Ignore any extra color registers.
// R8 := (Rn x 255 ) / maxColor
// All chunks are WORD aligned (even sized), may need to read pad...
if (mChunkLength % 2 != 0) {
pInput.readByte();
}
// TODO: Bitmask transparency
// 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 trans = mHeader.mMaskType == BMHDChunk.MASK_TRANSPARENT_COLOR ? mHeader.mTransparentIndex : -1;
mModel = new InverseColorMapIndexColorModel(mHeader.mBitplanes, mReds.length, mReds, mGreens, mBlues, trans);
}
void writeChunk(DataOutput pOutput) throws IOException {
pOutput.writeInt(mChunkId);
pOutput.writeInt(mChunkLength);
final int length = mModel.getMapSize();
for (int i = 0; i < length; i++) {
pOutput.writeByte(mModel.getRed(i));
pOutput.writeByte(mModel.getGreen(i));
pOutput.writeByte(mModel.getBlue(i));
}
if (mChunkLength % 2 != 0) {
pOutput.writeByte(0); // PAD
}
}
public String toString() {
return super.toString() + " {colorMap=" + mModel + "}";
}
IndexColorModel getIndexColorModel() {
return mModel;
}
public BufferedImage createPaletteImage() {
// Create a 1 x colors.length image
IndexColorModel cm = getIndexColorModel();
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);
}
}
@@ -0,0 +1,76 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.iff;
import javax.imageio.IIOException;
import java.awt.*;
import java.awt.geom.Point2D;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
/**
* GRABChunk
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: GRABChunk.java,v 1.0 28.feb.2006 01:55:05 haku Exp$
*/
class GRABChunk extends IFFChunk {
// typedef struct {
// WORD x, y; /* relative coordinates (pixels) */
// } Point2D;
Point2D mPoint;
protected GRABChunk(int pChunkLength) {
super(IFF.CHUNK_GRAB, pChunkLength);
}
protected GRABChunk(Point2D pPoint) {
super(IFF.CHUNK_GRAB, 4);
mPoint = pPoint;
}
void readChunk(DataInput pInput) throws IOException {
if (mChunkLength != 4) {
throw new IIOException("Unknown GRAB chunk size: " + mChunkLength);
}
mPoint = new Point(pInput.readShort(), pInput.readShort());
}
void writeChunk(DataOutput pOutput) throws IOException {
pOutput.writeShort((int) mPoint.getX());
pOutput.writeShort((int) mPoint.getY());
}
public String toString() {
return super.toString() + " {point=" + mPoint + "}";
}
}
@@ -0,0 +1,85 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.iff;
import java.io.DataInput;
import java.io.IOException;
import java.io.DataOutput;
/**
* UnknownChunk
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: UnknownChunk.java,v 1.0 28.feb.2006 00:53:47 haku Exp$
*/
class GenericChunk extends IFFChunk {
byte[] mData;
protected GenericChunk(int pChunkId, int pChunkLength) {
super(pChunkId, pChunkLength);
mData = new byte[pChunkLength <= 50 ? pChunkLength : 47];
}
protected GenericChunk(int pChunkId, byte[] pChunkData) {
super(pChunkId, pChunkData.length);
mData = pChunkData;
}
void readChunk(DataInput pInput) throws IOException {
pInput.readFully(mData, 0, mData.length);
int toSkip = mChunkLength - mData.length;
while (toSkip > 0) {
toSkip -= pInput.skipBytes(toSkip);
}
// Read pad
if (mChunkLength % 2 != 0) {
pInput.readByte();
}
}
void writeChunk(DataOutput pOutput) throws IOException {
pOutput.writeInt(mChunkId);
pOutput.writeInt(mChunkLength);
pOutput.write(mData, 0, mData.length);
if (mData.length % 2 != 0) {
pOutput.writeByte(0); // PAD
}
}
public String toString() {
return super.toString() + " {value=\""
+ new String(mData, 0, mData.length <= 50 ? mData.length : 47)
+ (mChunkLength <= 50 ? "" : "...") + "\"}";
}
}
@@ -0,0 +1,68 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.iff;
/**
* IFF format constants.
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: IFF.java,v 1.0 07.mar.2006 15:31:48 haku Exp$
*/
interface IFF {
/** IFF FORM group chunk */
int CHUNK_FORM = ('F' << 24) + ('O' << 16) + ('R' << 8) + 'M';
/** IFF ILBM form type */
int TYPE_ILBM = ('I' << 24) + ('L' << 16) + ('B' << 8) + 'M';
/** IFF PBM form type */
int TYPE_PBM = ('P' << 24) + ('B' << 16) + ('M' << 8) + ' ';
/** Bitmap Header chunk */
int CHUNK_BMHD = ('B' << 24) + ('M' << 16) + ('H' << 8) + 'D';
/** Color map chunk */
int CHUNK_CMAP = ('C' << 24) + ('M' << 16) + ('A' << 8) + 'P';
/** Hotspot chunk (cursors, brushes) */
int CHUNK_GRAB = ('G' << 24) + ('R' << 16) + ('A' << 8) + 'B';
/** Destination merge data chunk */
int CHUNK_DEST = ('D' << 24) + ('E' << 16) + ('S' << 8) + 'T';
/** Sprite information chunk */
int CHUNK_SPRT = ('S' << 24) + ('P' << 16) + ('R' << 8) + 'T';
/** Commodore Amiga viewport mode chunk (used to determine HAM and EHB modes) */
int CHUNK_CAMG = ('C' << 24) + ('A' << 16) + ('M' << 8) + 'G';
/** Main data (body) chunk */
int CHUNK_BODY = ('B' << 24) + ('O' << 16) + ('D' << 8) + 'Y';
}
@@ -0,0 +1,58 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.iff;
import java.io.DataInput;
import java.io.IOException;
import java.io.DataOutput;
/**
* IFFChunk
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: IFFChunk.java,v 1.0 28.feb.2006 00:00:45 haku Exp$
*/
abstract class IFFChunk {
int mChunkId;
int mChunkLength;
protected IFFChunk(int pChunkId, int pChunkLength) {
mChunkId = pChunkId;
mChunkLength = pChunkLength;
}
abstract void readChunk(DataInput pInput) throws IOException;
abstract void writeChunk(DataOutput pOutput) throws IOException;
public String toString() {
return IFFUtil.toChunkStr(mChunkId) + " chunk (" + mChunkLength + " bytes)";
}
}
@@ -0,0 +1,716 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.iff;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.stream.BufferedImageInputStream;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.imageio.util.IndexedImageTypeSpecifier;
import com.twelvemonkeys.io.enc.DecoderStream;
import com.twelvemonkeys.io.enc.PackBitsDecoder;
import javax.imageio.*;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.image.*;
import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
/**
* Reader for Amiga (Electronic Arts) IFF ILBM (InterLeaved BitMap) and PBM
* format (Packed BitMap).
* The IFF format (Interchange File Format) is the standard file format
* supported by allmost all image software for the Amiga computer.
* <p/>
* This reader supports the original palette-based 1-8 bit formats, including
* EHB (Extra Halfbright), HAM (Hold and Modify), and the more recent "deep"
* formats, 8 bit gray, 24 bit RGB and 32 bit ARGB.
* Uncompressed and ByteRun1 compressed (run lenght encoding) files are
* supported.
* <p/>
* Palette based images are read as {@code BufferedImage} of
* {@link BufferedImage#TYPE_BYTE_INDEXED TYPE_BYTE_INDEXED} or
* {@link BufferedImage#TYPE_BYTE_BINARY BufferedImage#}
* depending on the bit depth.
* Gray images are read as
* {@link BufferedImage#TYPE_BYTE_GRAY TYPE_BYTE_GRAY}.
* 24 bit true-color images are read as
* {@link BufferedImage#TYPE_3BYTE_BGR TYPE_3BYTE_BGR}.
* 32 bit true-color images are read as
* {@link BufferedImage#TYPE_4BYTE_ABGR TYPE_4BYTE_ABGR}.
* <p/>
* Issues: HAM and HAM8 (Hold and Modify) formats are converted to RGB (24 bit),
* as it seems to be very hard to create an {@code IndexColorModel} subclass
* that would correctly describe these formats.
* These formats utilizes the special display hardware in the Amiga computers.
* HAM (6 bits) needs 12 bits storage/pixel, if unpacked to RGB (4 bits/gun).
* HAM8 (8 bits) needs 18 bits storage/pixel, if unpacked to RGB (6 bits/gun).
* See <a href="http://en.wikipedia.org/wiki/Hold_And_Modify">Wikipedia: HAM</a>
* for more information.
* <br/>
* EHB palette is expanded to an {@link IndexColorModel} with 64 entries.
* See <a href="http://en.wikipedia.org/wiki/Extra_Half-Brite">Wikipedia: EHB</a>
* for more information.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: IFFImageReader.java,v 1.0 29.aug.2004 20:26:58 haku Exp $
* @see <a href="http://en.wikipedia.org/wiki/Interchange_File_Format">Wikipedia: IFF</a>
* @see <a href="http://en.wikipedia.org/wiki/ILBM">Wikipedia: IFF ILBM</a>
*/
public class IFFImageReader extends ImageReaderBase {
// http://home.comcast.net/~erniew/lwsdk/docs/filefmts/ilbm.html
// http://www.fileformat.info/format/iff/spec/7866a9f0e53c42309af667c5da3bd426/view.htm
// - Contains definitions of some "new" chunks, as well as alternative FORM types
// TODO: One other existing deep bit ordering that you may encounter is the 21-bit
// NewTek format.
//
// NewTek deep ILBM bit ordering:
// saved first ------------------------------------------------------> saved last
// R7 G7 B7 R6 G6 B6 R5 G5 B5 R4 G4 B4 R3 G3 B3 R2 G2 B2 R1 G1 B1 R0 G0 B0
private BMHDChunk mHeader;
private CMAPChunk mColorMap;
private BODYChunk mBody;
private GRABChunk mGrab;
private CAMGChunk mViewPort;
private int mFormType;
private long mBodyStart;
private BufferedImage mImage;
private DataInputStream mByteRunStream;
public IFFImageReader() {
super(IFFImageReaderSpi.sharedProvider());
}
protected IFFImageReader(ImageReaderSpi pProvider) {
super(pProvider);
}
private void init(int pIndex) throws IOException {
checkBounds(pIndex);
if (mHeader == null) {
readMeta();
}
}
protected void resetMembers() {
mHeader = null;
mColorMap = null;
mBody = null;
mViewPort = null;
mFormType = 0;
mImage = null;
mByteRunStream = null;
}
private void readMeta() throws IOException {
if (mImageInput.readInt() != IFF.CHUNK_FORM) {
throw new IIOException("Unknown file format for IFFImageReader");
}
int remaining = mImageInput.readInt() - 4; // We'll read 4 more in a sec
mFormType = mImageInput.readInt();
if (mFormType != IFF.TYPE_ILBM && mFormType != IFF.TYPE_PBM) {
throw new IIOException("Only IFF (FORM) type ILBM and PBM supported: " + IFFUtil.toChunkStr(mFormType));
}
//System.out.println("IFF type FORM " + toChunkStr(type));
mGrab = null;
mViewPort = null;
while (remaining > 0) {
int chunkId = mImageInput.readInt();
int length = mImageInput.readInt();
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);
switch (chunkId) {
case IFF.CHUNK_BMHD:
if (mHeader != null) {
throw new IIOException("Multiple BMHD chunks not allowed");
}
mHeader = new BMHDChunk(length);
mHeader.readChunk(mImageInput);
//System.out.println(mHeader);
break;
case IFF.CHUNK_CMAP:
if (mColorMap != null) {
throw new IIOException("Multiple CMAP chunks not allowed");
}
mColorMap = new CMAPChunk(length, mHeader, mViewPort);
mColorMap.readChunk(mImageInput);
//System.out.println(mColorMap);
break;
case IFF.CHUNK_GRAB:
if (mGrab != null) {
throw new IIOException("Multiple GRAB chunks not allowed");
}
mGrab = new GRABChunk(length);
mGrab.readChunk(mImageInput);
//System.out.println(mGrab);
break;
case IFF.CHUNK_CAMG:
if (mViewPort != null) {
throw new IIOException("Multiple CAMG chunks not allowed");
}
mViewPort = new CAMGChunk(length);
mViewPort.readChunk(mImageInput);
//System.out.println(mViewPort);
break;
case IFF.CHUNK_BODY:
if (mBody != null) {
throw new IIOException("Multiple BODY chunks not allowed");
}
mBody = new BODYChunk(length);
mBodyStart = mImageInput.getStreamPosition();
// NOTE: We don't read the body here, it's done later in the read(int, ImageReadParam) method
// Done reading meta
return;
default:
// TODO: We probably want to store anno chunks as Metadata
// ANNO, DEST, SPRT and more
IFFChunk generic = new GenericChunk(chunkId, length);
generic.readChunk(mImageInput);
//System.out.println(generic);
break;
}
}
}
public BufferedImage read(int pIndex, ImageReadParam pParam) throws IOException {
init(pIndex);
processImageStarted(pIndex);
mImage = getDestination(pParam, getImageTypes(pIndex), mHeader.mWidth, mHeader.mHeight);
//System.out.println(mBody);
if (mBody != 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(mColorMap);
if (mColorMap != null) {
//System.out.println("Creating palette!");
mImage = mColorMap.createPaletteImage();
}
}
BufferedImage result = mImage;
processImageComplete();
return result;
}
public int getWidth(int pIndex) throws IOException {
init(pIndex);
return mHeader.mWidth;
}
public int getHeight(int pIndex) throws IOException {
init(pIndex);
return mHeader.mHeight;
}
public Iterator<ImageTypeSpecifier> getImageTypes(int pIndex) throws IOException {
init(pIndex);
List<ImageTypeSpecifier> types = Arrays.asList(
getRawImageType(pIndex),
ImageTypeSpecifier.createFromBufferedImageType(mHeader.mBitplanes == 32 ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_3BYTE_BGR)
// TODO: ImageTypeSpecifier.createFromBufferedImageType(mHeader.mBitplanes == 32 ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB),
// TODO: Allow 32 bit always. Allow RGB and discard alpha, if present?
);
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: mColorMap may be null for 8 bit (gray), 24 bit or 32 bit only
ImageTypeSpecifier specifier;
switch (mHeader.mBitplanes) {
case 1:
// 1 bit
case 2:
// 2 bit
case 3:
case 4:
// 4 bit
case 5:
case 6:
// May be HAM6
// May be EHB
case 7:
case 8:
// 8 bit
// May be HAM8
if (!isHAM()) {
if (mColorMap != null) {
IndexColorModel cm = mColorMap.getIndexColorModel();
specifier = IndexedImageTypeSpecifier.createFromIndexColorModel(cm);
break;
}
else {
specifier = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY);
break;
}
}
// NOTE: HAM modes falls through, as they are converted to RGB
case 24:
// 24 bit RGB
specifier = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
break;
case 32:
// 32 bit ARGB
specifier = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR);
break;
default:
throw new IIOException(String.format("Bit depth not implemented: %d", mHeader.mBitplanes));
}
return specifier;
}
private void readBody(final ImageReadParam pParam) throws IOException {
mImageInput.seek(mBodyStart);
mByteRunStream = null;
// NOTE: mColorMap may be null for 8 bit (gray), 24 bit or 32 bit only
if (mColorMap != null) {
IndexColorModel cm = mColorMap.getIndexColorModel();
readIndexed(pParam, mImageInput, cm);
}
else {
readTrueColor(pParam, mImageInput);
}
}
private void readIndexed(final ImageReadParam pParam, final ImageInputStream pInput, final IndexColorModel pModel) throws IOException {
final int width = mHeader.mWidth;
final int height = mHeader.mHeight;
final Rectangle aoi = getSourceRegion(pParam, width, height);
final Point offset = pParam == null ? new Point(0, 0) : pParam.getDestinationOffset();
// Set everything to default values
int sourceXSubsampling = 1;
int sourceYSubsampling = 1;
int[] sourceBands = null;
int[] destinationBands = null;
// Get values from the ImageReadParam, if any
if (pParam != null) {
sourceXSubsampling = pParam.getSourceXSubsampling();
sourceYSubsampling = pParam.getSourceYSubsampling();
sourceBands = pParam.getSourceBands();
destinationBands = pParam.getDestinationBands();
}
// Ensure band settings from param are compatible with images
checkReadParamBandSettings(pParam, isHAM() ? 3 : 1, mImage.getSampleModel().getNumBands());
WritableRaster destination = mImage.getRaster();
if (destinationBands != null || offset.x != 0 || offset.y != 0) {
destination = destination.createWritableChild(0, 0, destination.getWidth(), destination.getHeight(), offset.x, offset.y, destinationBands);
}
// 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];
ColorModel cm;
WritableRaster raster;
if (isHAM()) {
// TODO: If HAM6, use type USHORT_444_RGB or 2BYTE_444_RGB?
// Or create a HAMColorModel, if at all possible?
// TYPE_3BYTE_BGR
cm = new ComponentColorModel(
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
);
}
else {
// TYPE_BYTE_BINARY or TYPE_BYTE_INDEXED
cm = pModel;
raster = pModel.createCompatibleWritableRaster(width, 1);
}
Raster sourceRow = raster.createChild(aoi.x, 0, aoi.width, 1, 0, 0, sourceBands);
final byte[] row = new byte[width * 8];
//System.out.println("Data length: " + data.length);
//System.out.println("PlaneData length: " + planeData.length * planeData[0].length);
//System.out.println("Row length: " + row.length);
final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
final int planes = mHeader.mBitplanes;
Object dataElements = null;
Object outDataElements = null;
ColorConvertOp converter = null;
for (int srcY = 0; srcY < height; srcY++) {
for (int p = 0; p < planes; p++) {
readPlaneData(pInput, planeData, p * planeWidth, planeWidth);
}
// Skip rows outside AOI
if (srcY < aoi.y || (srcY - aoi.y) % sourceYSubsampling != 0) {
continue;
}
else if (srcY >= (aoi.y + aoi.height)) {
return;
}
if (mFormType == 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);
}
else {
raster.setDataElements(0, 0, width, 1, row);
}
}
else if (mFormType == IFF.TYPE_PBM) {
// TODO: Arraycopy might not be necessary, if it's okay with row larger than width
System.arraycopy(planeData, 0, row, 0, mHeader.mBitplanes * planeWidth);
raster.setDataElements(0, 0, width, 1, row);
}
else {
throw new AssertionError(String.format("Unsupported FORM type: %s", mFormType));
}
int dstY = (srcY - aoi.y) / sourceYSubsampling;
// Handle non-converting raster as special case for performance
if (cm.isCompatibleRaster(destination)) {
// Rasters are compatible, just write to destinaiton
if (sourceXSubsampling == 1) {
destination.setRect(offset.x, dstY, sourceRow);
// dataElements = raster.getDataElements(aoi.x, 0, aoi.width, 1, dataElements);
// destination.setDataElements(offset.x, offset.y + (srcY - aoi.y) / sourceYSubsampling, aoi.width, 1, dataElements);
}
else {
for (int srcX = 0; srcX < sourceRow.getWidth(); srcX += sourceXSubsampling) {
dataElements = sourceRow.getDataElements(srcX, 0, dataElements);
int dstX = /*offset.x +*/ srcX / sourceXSubsampling;
destination.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 = mImage.getColorModel().getDataElements(rgb, outDataElements);
int dstX = srcX / sourceXSubsampling;
destination.setDataElements(dstX, dstY, outDataElements);
}
}
else {
// TODO: This branch is never tested, and is probably "dead"
// ColorConvertOp
if (converter == null) {
converter = new ColorConvertOp(cm.getColorSpace(), mImage.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)
);
}
}
processImageProgress(srcY * 100f / mHeader.mWidth);
if (abortRequested()) {
processReadAborted();
break;
}
}
}
// One row from each of the 24 bitplanes is written before moving to the
// next scanline. For each scanline, the red bitplane rows are stored first,
// followed by green and blue. The first plane holds the least significant
// 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 = mHeader.mWidth;
final int height = mHeader.mHeight;
final Rectangle aoi = getSourceRegion(pParam, width, height);
final Point offset = pParam == null ? new Point(0, 0) : pParam.getDestinationOffset();
// Set everything to default values
int sourceXSubsampling = 1;
int sourceYSubsampling = 1;
int[] sourceBands = null;
int[] destinationBands = null;
// Get values from the ImageReadParam, if any
if (pParam != null) {
sourceXSubsampling = pParam.getSourceXSubsampling();
sourceYSubsampling = pParam.getSourceYSubsampling();
sourceBands = pParam.getSourceBands();
destinationBands = pParam.getDestinationBands();
}
// Ensure band settings from param are compatible with images
checkReadParamBandSettings(pParam, mHeader.mBitplanes / 8, mImage.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 = mImage.getRaster();
if (destinationBands != null || offset.x != 0 || offset.y != 0) {
destination = destination.createWritableChild(0, 0, destination.getWidth(), destination.getHeight(), offset.x, offset.y, destinationBands);
}
// WritableRaster raster = mImage.getRaster().createCompatibleWritableRaster(width, 1);
WritableRaster raster = mImage.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 = (mHeader.mBitplanes + 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);
}
// Skip rows outside AOI
if (srcY < aoi.y || (srcY - aoi.y) % sourceYSubsampling != 0) {
continue;
}
else if (srcY >= (aoi.y + aoi.height)) {
return;
}
if (mFormType == 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);
int pixelPos = 0;
for (int planePos = 0; planePos < planeWidth; planePos++) {
IFFUtil.bitRotateCW(planeData, planePos, planeWidth, data, off + pixelPos * channels, channels);
pixelPos += 8;
}
}
else if (mFormType == IFF.TYPE_PBM) {
System.arraycopy(planeData, 0, data, srcY * 8 * planeWidth, planeWidth);
}
else {
throw new AssertionError(String.format("Unsupported FORM type: %s", mFormType));
}
}
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);
}
else {
for (int srcX = 0; srcX < sourceRow.getWidth(); srcX += sourceXSubsampling) {
dataElements = sourceRow.getDataElements(srcX, 0, dataElements);
int dstX = srcX / sourceXSubsampling;
destination.setDataElements(dstX, dstY, dataElements);
}
}
processImageProgress(srcY * 100f / mHeader.mWidth);
if (abortRequested()) {
processReadAborted();
break;
}
}
}
private void readPlaneData(final ImageInputStream pInput, final byte[] pData, final int pOffset, final int pPlaneWidth)
throws IOException {
switch (mHeader.mCompressionType) {
case BMHDChunk.COMPRESSION_NONE:
pInput.readFully(pData, pOffset, pPlaneWidth);
// Uncompressed rows must have even number of bytes
if ((mHeader.mBitplanes * pPlaneWidth) % 2 != 0) {
pInput.readByte();
}
break;
case BMHDChunk.COMPRESSION_BYTE_RUN:
// 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,
// to make sure we don't decode anything we don't have to (shouldn't).
if (mByteRunStream == null) {
mByteRunStream = new DataInputStream(
new DecoderStream(
IIOUtil.createStreamAdapter(pInput, mBody.mChunkLength),
new PackBitsDecoder(true),
pPlaneWidth * mHeader.mBitplanes
)
);
}
mByteRunStream.readFully(pData, pOffset, pPlaneWidth);
break;
default:
throw new IIOException(String.format("Unknown compression type: %d", mHeader.mCompressionType));
}
}
private void hamToRGB(final byte[] pIndexed, final IndexColorModel pModel,
final byte[] pDest, final int pDestOffset) {
final int bits = mHeader.mBitplanes;
final int width = mHeader.mWidth;
int lastRed = 0;
int lastGreen = 0;
int lastBlue = 0;
for (int x = 0; x < width; x++) {
int pixel = pIndexed[x] & 0xff;
//System.out.println("--> ham" + bits);
int paletteIndex = bits == 6 ? pixel & 0x0f : pixel & 0x3f;
int indexShift = bits == 6 ? 4 : 2;
int colorMask = bits == 6 ? 0x0f : 0x03;
//System.out.println("palette index=" + paletteIndex);
// Get Hold and Modify bits
switch ((pixel >> (8 - indexShift)) & 0x03) {
case 0x00:// HOLD
lastRed = pModel.getRed(paletteIndex);
lastGreen = pModel.getGreen(paletteIndex);
lastBlue = pModel.getBlue(paletteIndex);
break;
case 0x01:// MODIFY BLUE
lastBlue = (lastBlue & colorMask) | (paletteIndex << indexShift);
break;
case 0x02:// MODIFY RED
lastRed = (lastRed & colorMask) | (paletteIndex << indexShift);
break;
case 0x03:// MODIFY GREEN
lastGreen = (lastGreen & colorMask) | (paletteIndex << indexShift);
break;
}
int offset = (x * 3) + pDestOffset;
pDest[2 + offset] = (byte) lastRed;
pDest[1 + offset] = (byte) lastGreen;
pDest[offset] = (byte) lastBlue;
}
}
private boolean isHAM() {
return mViewPort != null && mViewPort.isHAM();
}
public static void main(String[] pArgs) throws IOException {
ImageReader reader = new IFFImageReader();
// ImageInputStream input = ImageIO.createImageInputStream(new File(pArgs[0]));
ImageInputStream input = new BufferedImageInputStream(ImageIO.createImageInputStream(new File(pArgs[0])));
boolean canRead = reader.getOriginatingProvider().canDecodeInput(input);
System.out.println("Can read: " + canRead);
if (canRead) {
reader.setInput(input);
ImageReadParam param = reader.getDefaultReadParam();
// param.setSourceRegion(new Rectangle(0, 0, 160, 200));
// param.setSourceRegion(new Rectangle(160, 200, 160, 200));
// param.setSourceRegion(new Rectangle(80, 100, 160, 200));
// param.setDestinationOffset(new Point(80, 100));
// param.setSourceSubsampling(3, 3, 0, 0);
// param.setSourceBands(new int[]{0, 1, 2});
// param.setDestinationBands(new int[]{1, 0, 2});
BufferedImage image = reader.read(0, param);
System.out.println("image = " + image);
showIt(image, pArgs[0]);
}
}
}
@@ -0,0 +1,121 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.iff;
import com.twelvemonkeys.imageio.spi.ProviderInfo;
import com.twelvemonkeys.imageio.util.IIOUtil;
import javax.imageio.ImageReader;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.util.Locale;
/**
* IFFImageReaderSpi
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: IFFImageWriterSpi.java,v 1.0 28.feb.2006 19:21:05 haku Exp$
*/
public class IFFImageReaderSpi extends ImageReaderSpi {
static IFFImageReaderSpi mSharedInstance;
/**
* Creates an {@code IFFImageReaderSpi}.
*/
public IFFImageReaderSpi() {
this(IIOUtil.getProviderInfo(IFFImageReaderSpi.class));
}
private IFFImageReaderSpi(final ProviderInfo pProviderInfo) {
super(
pProviderInfo.getVendorName(),
pProviderInfo.getVersion(),
new String[]{"iff", "IFF"},
new String[]{"iff", "lbm", "ham", "ham8", "ilbm"},
new String[]{"image/iff", "image/x-iff"},
"com.twelvemonkeys.imageio.plugins.iff.IFFImageReader",
STANDARD_INPUT_TYPE,
new String[]{"com.twelvemonkeys.imageio.plugins.iff.IFFImageWriterSpi"},
true, null, null, null, null,
true, null, null, null, null
);
if (mSharedInstance == null) {
mSharedInstance = this;
}
}
public boolean canDecodeInput(Object pSource) throws IOException {
return pSource instanceof ImageInputStream && canDecode((ImageInputStream) pSource);
}
private static boolean canDecode(ImageInputStream pInput) throws IOException {
pInput.mark();
try {
// Is it IFF
if (pInput.readInt() == IFF.CHUNK_FORM) {
pInput.readInt();// Skip length field
int type = pInput.readInt();
// Is it ILBM or PBM
if (type == IFF.TYPE_ILBM || type == IFF.TYPE_PBM) {
return true;
}
}
}
finally {
pInput.reset();
}
return false;
}
public ImageReader createReaderInstance(Object pExtension) throws IOException {
return new IFFImageReader(this);
}
public String getDescription(Locale pLocale) {
return "Amiga (Electronic Arts) Image Interchange Format (IFF) image reader";
}
public static ImageReaderSpi sharedProvider() {
if (mSharedInstance == null) {
new IFFImageReaderSpi();
}
return mSharedInstance;
}
}
@@ -0,0 +1,271 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.iff;
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 Amiga (Electronic Arts) IFF ILBM (InterLeaved BitMap) format.
* The IFF format (Interchange File Format) is the standard file format
* supported by almost all image software for the Amiga computer.
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: IFFImageWriter.java,v 1.0 02.mar.2006 13:32:30 haku Exp$
*
* @see <a href="http://en.wikipedia.org/wiki/Interchange_File_Format">Wikipedia: IFF</a>
* @see <a href="http://en.wikipedia.org/wiki/ILBM">Wikipedia: IFF ILBM</a>
*/
public class IFFImageWriter extends ImageWriterBase {
public IFFImageWriter() {
this(null);
}
protected IFFImageWriter(ImageWriterSpi pProvider) {
super(pProvider);
}
public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, ImageWriteParam param) {
throw new UnsupportedOperationException("Method getDefaultImageMetadata not implemented");// TODO: Implement
}
public IIOMetadata convertImageMetadata(IIOMetadata inData, ImageTypeSpecifier imageType, ImageWriteParam param) {
throw new UnsupportedOperationException("Method convertImageMetadata not implemented");// TODO: Implement
}
public void write(IIOMetadata pStreamMetadata, IIOImage pImage, ImageWriteParam pParam) throws IOException {
assertOutput();
if (pImage.hasRaster()) {
throw new UnsupportedOperationException("Cannot write raster");
}
processImageStarted(0);
// Prepare image data to be written
ByteArrayOutputStream imageData = new FastByteArrayOutputStream(1024);
packImageData(imageData, pImage.getRenderedImage(), pParam);
//System.out.println("Image data: " + imageData.size());
// Write metadata
writeMeta(pImage.getRenderedImage(), imageData.size());
// Write image data
writeBody(imageData);
processImageComplete();
}
private void writeBody(ByteArrayOutputStream pImageData) throws IOException {
mImageOutput.writeInt(IFF.CHUNK_BODY);
mImageOutput.writeInt(pImageData.size());
// NOTE: This is much faster than mOutput.write(pImageData.toByteArray())
// as the data array is not duplicated
pImageData.writeTo(IIOUtil.createStreamAdapter(mImageOutput));
if (pImageData.size() % 2 == 0) {
mImageOutput.writeByte(0); // PAD
}
// NOTE: Most progress is done in packImageData, however, as we need to
// buffer, to write correct size, we defer the last 10 percent until now.
processImageProgress(100f);
mImageOutput.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?
// 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 int width = pImage.getWidth();
final int height = pImage.getHeight();
// Store each row of pixels
// 0. Loop pr channel
// 1. Convert to planar
// 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;
final int planesPerChannel = channels == 1 ? model.getPixelSize() : 8;
int[] pixels = new int[8 * planeWidth];
// TODO: The spec says "Do not compress across rows!".. I think we currently do.
// NOTE: I'm a little unsure if this is correct for 4 channel (RGBA)
// data, but it is at least consistent with the IFFImageReader for now...
for (int y = 0; y < height; y++) {
for (int c = 0; c < channels; c++) {
pixels = raster.getSamples(0, y, width, 1, c, pixels);
int pixelPos = 0;
int planePos = 0;
for (int i = 0; i < planeWidth; i++) {
IFFUtil.bitRotateCCW(pixels, pixelPos, 1,
planeData, planePos, planeWidth);
pixelPos += 8;
planePos++;
}
for (int p = 0; p < planesPerChannel; p++) {
output.write(planeData, p * planeWidth, planeWidth);
if (!compress && planeWidth % 2 != 0) {
output.write(0); // PAD
}
}
}
processImageProgress(y * 90f / height);
}
output.flush();
}
private void writeMeta(RenderedImage pImage, int pBodyLength) throws IOException {
// Annotation ANNO chunk, 8 + annoData.length bytes
String annotation = "Written by " + getOriginatingProvider().getDescription(null);
GenericChunk anno = new GenericChunk(IFFUtil.toInt("ANNO".getBytes()), annotation.getBytes());
ColorModel cm = pImage.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;
BMHDChunk header;
if (cm instanceof IndexColorModel) {
//System.out.println("IndexColorModel");
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(),
trans, compression, transPixel);
}
else {
//System.out.println(cm.getClass().getName());
header = new BMHDChunk(pImage.getWidth(), pImage.getHeight(), cm.getPixelSize(),
BMHDChunk.MASK_NONE, compression, 0);
}
// Colormap CMAP chunk, 8 + icm.getMapSize() * 3 bytes (+ 1 optional pad).
CMAPChunk cmap = null;
if (icm != null) {
//System.out.println("CMAP!");
cmap = new CMAPChunk(icm);
}
// ILBM(4) + anno(8+len) + header(8+20) + cmap(8+len)? + body(8+len);
int size = 4 + 8 + anno.mChunkLength + 28 + 8 + pBodyLength;
if (cmap != null) {
size += 8 + cmap.mChunkLength;
}
mImageOutput.writeInt(IFF.CHUNK_FORM);
mImageOutput.writeInt(size);
mImageOutput.writeInt(IFF.TYPE_ILBM);
anno.writeChunk(mImageOutput);
header.writeChunk(mImageOutput);
if (cmap != null) {
//System.out.println("CMAP written");
cmap.writeChunk(mImageOutput);
}
}
private boolean shouldCompress(RenderedImage pImage) {
return pImage.getWidth() >= 32;
}
public static void main(String[] pArgs) throws IOException {
BufferedImage image = ImageIO.read(new File(pArgs[0]));
ImageWriter writer = new IFFImageWriter(new IFFImageWriterSpi());
writer.setOutput(ImageIO.createImageOutputStream(new File(pArgs[1])));
//writer.addIIOWriteProgressListener(new ProgressListenerBase() {
// int mCurrPct = 0;
//
// public void imageComplete(ImageWriter pSource) {
// mCurrPct = 100;
// printProgress(mCurrPct);
// }
//
// public void imageProgress(ImageWriter pSource, float pPercentageDone) {
// if ((int) pPercentageDone > mCurrPct) {
// printProgress((int) pPercentageDone);
// mCurrPct = (int) pPercentageDone;
// }
// }
//
// private void printProgress(int pCurrPct) {
// if (mCurrPct == 0) {
// System.out.print("[");
// }
// for (int i = mCurrPct / 2; i < pCurrPct / 2; i++) {
// System.out.print(".");
// }
// if (mCurrPct == 100) {
// System.out.println("]");
// }
// }
//});
//image = com.twelvemonkeys.image.ImageUtil.toBuffered(image, BufferedImage.TYPE_INT_ARGB);
writer.write(image);
}
}
@@ -0,0 +1,85 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.iff;
import com.twelvemonkeys.imageio.spi.ProviderInfo;
import com.twelvemonkeys.imageio.util.IIOUtil;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriter;
import javax.imageio.spi.ImageWriterSpi;
import java.io.IOException;
import java.util.Locale;
/**
* IFFImageWriterSpi
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: IFFImageWriterSpi.java,v 1.0 02.mar.2006 19:21:05 haku Exp$
*/
public class IFFImageWriterSpi extends ImageWriterSpi {
/**
* Creates an {@code IFFImageWriterSpi}.
*/
public IFFImageWriterSpi() {
this(IIOUtil.getProviderInfo(IFFImageWriterSpi.class));
}
private IFFImageWriterSpi(final ProviderInfo pProviderInfo) {
super(
pProviderInfo.getVendorName(),
pProviderInfo.getVersion(),
new String[]{"iff", "IFF"},
new String[]{"iff", "lbm", "ham", "ham8", "ilbm"},
new String[]{"image/iff", "image/x-iff"},
"com.twelvemonkeys.imageio.plugins.iff.IFFImageWriter",
STANDARD_OUTPUT_TYPE,
new String[]{"com.twelvemonkeys.imageio.plugins.iff.IFFImageReaderSpi"},
true, null, null, null, null,
true, null, null, null, null
);
}
public boolean canEncodeImage(final ImageTypeSpecifier pType) {
// 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;
}
public ImageWriter createWriterInstance(Object pExtension) throws IOException {
return new IFFImageWriter(this);
}
public String getDescription(Locale pLocale) {
return "Amiga (Electronic Arts) IFF image writer";
}
}
@@ -0,0 +1,256 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Fast 90-degree bit rotation routines.
*
* Based on Sue-Ken Yap, "A Fast 90-Degree Bitmap Rotator," in GRAPHICS
* GEMS II, James Arvo ed., Academic Press, 1991, ISBN 0-12-064480-0.
*/
package com.twelvemonkeys.imageio.plugins.iff;
/**
* IFFUtil
* <p/>
* Bit rotate methods based on Sue-Ken Yap, "A Fast 90-Degree Bitmap Rotator,"
* in GRAPHICS GEMS II, James Arvo ed., Academic Press, 1991, ISBN 0-12-064480-0.
*
* @author Unascribed (C version)
* @author Harald Kuhr (Java port)
* @version $Id: IFFUtil.java,v 1.0 06.mar.2006 13:31:35 haku Exp$
*/
class IFFUtil {
/**
* Creates a rotation table
* @param n number of bits -1
*
* @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
};
}
static private final long[][] RTABLE = {
rtable(0), rtable(1), rtable(2), rtable(3),
rtable(4), rtable(5), rtable(6), rtable(7)
};
/**
* Rotate bits clockwise.
* The IFFImageReader uses this to convert pixel bits from planar to chunky.
* 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
*/
static void bitRotateCW(final byte[] pSrc, int pSrcPos, int pSrcStep,
final byte[] pDst, int pDstPos, int pDstStep) {
int idx = pSrcPos;
int lonyb;
int hinyb;
long lo = 0;
long hi = 0;
for (int i = 0; i < 8; i++) {
lonyb = pSrc[idx] & 0xF;
hinyb = (pSrc[idx] >> 4) & 0xF;
lo |= RTABLE[i][lonyb];
hi |= RTABLE[i][hinyb];
idx += pSrcStep;
}
idx = pDstPos;
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;
}
}
}
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);
}
}
}
}
}
/**
* 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
*/
static void bitRotateCCW(final int[] pSrc, int pSrcPos, int pSrcStep,
final byte[] pDst, int pDstPos, int pDstStep) {
int idx = pSrcPos;
int lonyb;
int hinyb;
long lo = 0;
long hi = 0;
for (int i = 7; i >= 0; i--) {
lonyb = pSrc[idx] & 0xF;
hinyb = (pSrc[idx] >> 4) & 0xF;
lo |= RTABLE[i][lonyb];
hi |= RTABLE[i][hinyb];
idx += pSrcStep;
}
idx = pDstPos;
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);
idx += pDstStep;
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);
}
/**
* 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
*/
static void bitRotateCCW(final byte[] pSrc, int pSrcPos, int pSrcStep,
final byte[] pDst, int pDstPos, int pDstStep) {
int idx = pSrcPos;
int lonyb;
int hinyb;
long lo = 0;
long hi = 0;
for (int i = 7; i >= 0; i--) {
lonyb = pSrc[idx] & 0xF;
hinyb = (pSrc[idx] >> 4) & 0xF;
lo |= RTABLE[i][lonyb];
hi |= IFFUtil.RTABLE[i][hinyb];
idx += pSrcStep;
}
idx = pDstPos;
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);
idx += pDstStep;
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);
}
/**
* Converts a byte array to an int.
*
* @param pBytes 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);
}
/**
* Converts an int to a four letter String.
*
* @param pChunkId 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))});
}
}
@@ -0,0 +1 @@
com.twelvemonkeys.imageio.plugins.iff.IFFImageReaderSpi
@@ -0,0 +1 @@
com.twelvemonkeys.imageio.plugins.iff.IFFImageWriterSpi
@@ -0,0 +1,85 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.iff;
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
import javax.imageio.spi.ImageReaderSpi;
import java.awt.*;
import java.util.Arrays;
import java.util.List;
/**
* IFFImageReaderTestCase
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: IFFImageReaderTestCase.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
*/
public class IFFImageReaderTestCase extends ImageReaderAbstractTestCase<IFFImageReader> {
// TODO: Need test for IFF PBM
protected List<TestData> getTestData() {
return Arrays.asList(
// 32 bit - Ok
new TestData(getClassLoaderResource("/iff/test.iff"), new Dimension(300, 200)), // 32 bit
// 24 bit - Ok
new TestData(getClassLoaderResource("/iff/survivor.iff"), new Dimension(800, 600)), // 24 bit
// HAM6 - Ok (a lot of visual "fringe", would be interesting to see on a real HAM display)
new TestData(getClassLoaderResource("/iff/A4000T_HAM6.IFF"), new Dimension(320, 512)), // ham6
// HAM8 - Ok (PackBits decoder chokes on padding byte)
new TestData(getClassLoaderResource("/iff/A4000T_HAM8.IFF"), new Dimension(628, 512)), // ham8
// 8 color indexed - Ok
new TestData(getClassLoaderResource("/iff/AmigaBig.iff"), new Dimension(300, 200)), // 8 color
// 8 color indexed - Ok
new TestData(getClassLoaderResource("/iff/AmigaAmiga.iff"), new Dimension(200, 150)), // 8 color
// Ok (PackBits decoder chokes on padding byte)
new TestData(getClassLoaderResource("/iff/Abyss.iff"), new Dimension(320, 400))
);
}
protected ImageReaderSpi createProvider() {
return new IFFImageReaderSpi();
}
protected Class<IFFImageReader> getReaderClass() {
return IFFImageReader.class;
}
protected List<String> getFormatNames() {
return Arrays.asList("iff");
}
protected List<String> getSuffixes() {
return Arrays.asList("iff", "ilbm", "ham", "ham8", "lbm");
}
protected List<String> getMIMETypes() {
return Arrays.asList("image/iff", "image/x-iff");
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.