Jaysyn904 5914ed2ab5 Updated Release Archive
Updated Release Archive.  Fixed Mage-killer prereqs.  Removed old LETO & ConvoCC related files.  Added organized spell scroll store.  Fixed Gloura spellbook. Various TLK fixes.  Reorganized Repo.  Removed invalid user folders. Added DocGen back in.
2023-08-22 10:00:21 -04:00

648 lines
26 KiB
Java

package com.realityinteractive.imageio.tga;
/*
* TGAImageReader.java
* Copyright (c) 2003 Reality Interactive, Inc.
* See bottom of file for license and warranty information.
* Created on Sep 26, 2003
*/
import java.awt.Rectangle;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferInt;
import java.awt.image.WritableRaster;
import java.io.IOException;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
/**
* <p>The {@link javax.imageio.ImageReader} that exposes the TGA image reading.
* 8, 15, 16, 24 and 32 bit true color or color mapped (RLE compressed or
* uncompressed) are supported. Monochrome images are not supported.</p>
*
* <p>Great care should be employed with {@link javax.imageio.ImageReadParam}s.
* Little to no effort has been made to correctly handle sub-sampling or
* specified bands.</p>
*
* <p>{@link javax.imageio.ImageIO#setUseCache(boolean)} should be set to <code>false</code>
* when using this reader. Also, {@link javax.imageio.ImageIO#read(java.io.InputStream)}
* is the preferred read method if used against a buffered array (for performance
* reasons).</p>
*
* @author Rob Grzywinski <a href="mailto:rgrzywinski@realityinteractive.com">rgrzywinski@realityinteractive.com</a>
* @version $Id: TGAImageReader.java,v 1.1 2005/04/12 11:23:53 ornedan Exp $
* @since 1.0
*/
// TODO: incorporate the x and y origins
public class TGAImageReader extends ImageReader
{
/**
* <p>The {@link javax.imageio.stream.ImageInputStream} from which the TGA
* is read. This may be <code>null</code> if {@link javax.imageio.ImageReader#setInput(java.lang.Object)}
* (or the other forms of <code>setInput()</code>) has not been called. The
* stream will be set litle-endian when it is set.</p>
*/
private ImageInputStream inputStream;
/**
* <p>The {@link com.realityinteractive.imageio.tga.TGAHeader}. If <code>null</code>
* then the header has not been read since <code>inputStream</code> was
* last set. This is created lazily.</p>
*/
private TGAHeader header;
// =========================================================================
/**
* @see javax.imageio.ImageReader#ImageReader(javax.imageio.spi.ImageReaderSpi)
*/
public TGAImageReader(final ImageReaderSpi originatingProvider)
{
super(originatingProvider);
}
/**
* <p>Store the input if it is an {@link javax.imageio.stream.ImageInputStream}.
* Otherwise {@link java.lang.IllegalArgumentException} is thrown. The
* stream is set to little-endian byte ordering.</p>
*
* @see javax.imageio.ImageReader#setInput(java.lang.Object, boolean, boolean)
*/
// NOTE: can't read the header in here as there would be no place for
// exceptions to go. It must be read lazily.
public void setInput(final Object input, final boolean seekForwardOnly,
final boolean ignoreMetadata)
{
// delegate to the partent
super.setInput(input, seekForwardOnly, ignoreMetadata);
// if the input is null clear the inputStream and header
if(input == null)
{
inputStream = null;
header = null;
} /* else -- the input is non-null */
// only ImageInputStream are allowed. If other throw IllegalArgumentException
if(input instanceof ImageInputStream)
{
// set the inputStream
inputStream = (ImageInputStream)input;
// put the ImageInputStream into little-endian ("Intel byte ordering")
// byte ordering
inputStream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
} else /* input is not an instance of ImageInputStream */
{
throw new IllegalArgumentException("Only ImageInputStreams are accepted."); // FIXME: localize
}
}
/**
* <p>Create and read the {@link com.realityinteractive.imageio.tga.TGAHeader}
* only if there is not one already.</p>
*
* @return the <code>TGAHeader</code> (for convenience)
* @throws IOException if there is an I/O error while reading the header
*/
private synchronized TGAHeader getHeader()
throws IOException
{
// if there is already a header (non-null) then there is nothing to be
// done
if(header != null)
return header;
/* else -- there is no header */
// ensure that there is an ImageInputStream from which the header is
// read
if(inputStream == null)
throw new IllegalStateException("There is no ImageInputStream from which the header can be read."); // FIXME: localize
/* else -- there is an input stream */
header = new TGAHeader(inputStream);
return header;
}
/**
* <p>Only a single image can be read by this reader. Validate the
* specified image index and if not <code>0</code> then {@link java.lang.IndexOutOfBoundsException}
* is thrown.</p>
*
* @param imageIndex the index of the image to validate
* @throws IndexOutOfBoundsException if the <code>imageIndex</code> is not
* <code>0</code>
*/
private void checkImageIndex(final int imageIndex)
{
// if the imageIndex is not 0 then throw an exception
if(imageIndex != 0)
throw new IndexOutOfBoundsException("Image index out of bounds (" + imageIndex + " != 0)."); // FIXME: localize
/* else -- the index is in bounds */
}
// =========================================================================
// Required ImageReader methods
/**
* @see javax.imageio.ImageReader#getImageTypes(int)
*/
public Iterator/*<ImageTypeSpecifier>*/ getImageTypes(final int imageIndex)
throws IOException
{
// validate the imageIndex (this will throw if invalid)
checkImageIndex(imageIndex);
// read / get the header
final TGAHeader header = getHeader();
// get the ImageTypeSpecifier for the image type
// FIXME: finish
final ImageTypeSpecifier imageTypeSpecifier;
switch(header.getImageType())
{
case TGAConstants.COLOR_MAP:
case TGAConstants.RLE_COLOR_MAP:
case TGAConstants.TRUE_COLOR:
case TGAConstants.RLE_TRUE_COLOR:
{
// determine if there is an alpha mask based on the number of
// samples per pixel
final int alphaMask;
if(header.getSamplesPerPixel() == 4)
alphaMask = 0xFF000000;
else /* no alpha channel (less than 32 bits or 4 samples) */
alphaMask = 0;
// packed RGB(A) pixel data (more specifically (A)BGR)
// TODO: split on 16, 24, and 32 bit images otherwise there
// will be wasted space
final ColorSpace rgb = ColorSpace.getInstance(ColorSpace.CS_sRGB);
imageTypeSpecifier = ImageTypeSpecifier.createPacked(rgb,
0x000000FF,
0x0000FF00,
0x00FF0000,
alphaMask,
DataBuffer.TYPE_INT,
false /*not pre-multiplied by an alpha*/);
break;
}
case TGAConstants.MONO:
case TGAConstants.RLE_MONO:
throw new IllegalArgumentException("Monochrome image type not supported.");
case TGAConstants.NO_IMAGE:
default:
throw new IllegalArgumentException("The image type is not known."); // FIXME: localize
}
// create a list and add the ImageTypeSpecifier to it
final List/*<ImageTypeSpecifier>*/ imageSpecifiers = new ArrayList/*<ImageTypeSpecifier>*/();
imageSpecifiers.add(imageTypeSpecifier);
return imageSpecifiers.iterator();
}
/**
* <p>Only a single image is supported.</p>
*
* @see javax.imageio.ImageReader#getNumImages(boolean)
*/
public int getNumImages(final boolean allowSearch)
throws IOException
{
// see javadoc
// NOTE: 1 is returned regardless if a search is allowed or not
return 1;
}
/**
* <p>There is no stream metadata (i.e. <code>null</code> is returned).</p>
*
* @see javax.imageio.ImageReader#getStreamMetadata()
*/
public IIOMetadata getStreamMetadata()
throws IOException
{
// see javadoc
return null;
}
/**
* <p>There is no image metadata (i.e. <code>null</code> is returned).</p>
*
* @see javax.imageio.ImageReader#getImageMetadata(int)
*/
public IIOMetadata getImageMetadata(final int imageIndex)
throws IOException
{
// see javadoc
return null;
}
/**
* @see javax.imageio.ImageReader#getHeight(int)
*/
public int getHeight(final int imageIndex)
throws IOException
{
// validate the imageIndex (this will throw if invalid)
checkImageIndex(imageIndex);
// get the header and return the height
return getHeader().getHeight();
}
/**
* @see javax.imageio.ImageReader#getWidth(int)
*/
public int getWidth(final int imageIndex)
throws IOException
{
// validate the imageIndex (this will throw if invalid)
checkImageIndex(imageIndex);
// get the header and return the width
return getHeader().getHeight();
}
/**
* @see javax.imageio.ImageReader#read(int, javax.imageio.ImageReadParam)
*/
public BufferedImage read(final int imageIndex, final ImageReadParam param)
throws IOException
{
// ensure that the image is of a supported type
// NOTE: this will implicitly ensure that the imageIndex is valid
final Iterator imageTypes = getImageTypes(imageIndex);
if(!imageTypes.hasNext())
{
throw new IOException("Unsupported Image Type");
}
// read and get the header
final TGAHeader header = getHeader();
// ensure that the ImageReadParam hasn't been set to other than the
// defaults (this will throw if not acceptible)
checkImageReadParam(param, header);
// get the height and width from the header for convenience
final int width = header.getWidth();
final int height = header.getHeight();
// read the color map data. If the image does not contain a color map
// then null will be returned.
final int[] colorMap = readColorMap(header);
// seek to the pixel data offset
// TODO: read the color map
inputStream.seek(header.getPixelDataOffset());
// get the destination image and WritableRaster for the image type and
// size
final BufferedImage image = getDestination(param, imageTypes,
width, height);
final WritableRaster imageRaster = image.getRaster();
// get and validate the number of image bands
final int numberOfImageBands = image.getSampleModel().getNumBands();
checkReadParamBandSettings(param, header.getSamplesPerPixel(),
numberOfImageBands);
// get the destination bands
final int[] destinationBands;
if(param != null)
{
// there is an ImageReadParam -- use its destination bands
destinationBands = param.getDestinationBands();
} else
{
// there are no destination bands
destinationBands = null;
}
// create the destination WritableRaster
final WritableRaster raster = imageRaster.createWritableChild(0, 0,
width,
height,
0, 0,
destinationBands);
// set up to read the data
final int[] intData = ((DataBufferInt)raster.getDataBuffer()).getData(); // CHECK: is this valid / acceptible?
int index = 0; // the index in the intData array
int runLength = 0; // the number of pixels in a run length
boolean readPixel = true; // if true then a raw pixel is read. Used by the RLE.
boolean isRaw = false; // if true then the next pixels should be read. Used by the RLE.
int pixel = 0; // the current pixel data
// TODO: break out the case of 32 bit non-RLE as it can be read
// directly and 24 bit non-RLE as it can be read simply. If
// subsampling and ROI's are implemented then selection must be
// done per pixel for RLE otherwise it's possible to miss the
// repetition count field.
// TODO: account for TGAHeader.firstColorMapEntryIndex
// loop over the rows
// TODO: this should be destinationROI.height (right?)
for(int y=0; y<height; y++)
{
// if the image is flipped top-to-bottom then set the index in
// intData appropriately
if(header.isBottomToTop())
//index = y;
index = (height - y) - 1;
else /* is top-to-bottom */
index = y;
//index = (height - y) - 1;
// account for the width
// TODO: this doesn't take into account the destination size or bands
index *= width;
// loop over the columns
// TODO: this should be destinationROI.width (right?)
// NOTE: *if* destinations are used the RLE will break as this will
// cause the repetition count field to be missed.
for(int x=0; x<width; x++)
{
// if the image is compressed (run length encoded) then determine
// if a pixel should be read or if the current one should be
// used (using the current one is part of the RLE'ing).
if(header.isCompressed())
{
// if there is a non-zero run length then there are still
// compressed pixels
if(runLength > 0)
{
// decrement the run length and flag that a pixel should
// not be read
// NOTE: a pixel is only read from the input if the
// packet was raw. If it was a run length packet
// then the previous (current) pixel is used.
runLength--;
readPixel = isRaw;
} else /* non-positive run length */
{
// read the repetition count field
runLength = inputStream.readByte() & 0xFF; // unsigned
// determine which packet type: raw or runlength
isRaw = ( (runLength & 0x80) == 0); // bit 7 == 0 -> raw; bit 7 == 1 -> runlength
// if a run length packet then shift to get the number
if(!isRaw)
runLength -= 0x80;
/* else -- is raw so there's no need to shift */
// the next field is always read (it's the pixel data)
readPixel = true;
}
}
// read the next pixel
// NOTE: only don't read when in a run length packet
if(readPixel)
{
// NOTE: the alpha must hav a default value since it is
// not guaranteed to be present for each pixel read
int red = 0, green = 0, blue = 0, alpha = 0xFF;
// read based on the number of bits per pixel
switch(header.getBitsPerPixel())
{
// grey scale (R = G = B)
case 8:
default:
{
// read the data -- it is either the color map index
// or the color for each pixel
final int data = inputStream.readByte() & 0xFF; // unsigned
// if the image is a color mapped image then the
// resulting pixel is pulled from the color map,
// otherwise each pixel gets the data
if(header.hasColorMap())
{
// the pixel is pulled from the color map
// CHECK: do sanity bounds check?
pixel = colorMap[data];
} else /* no color map */
{
// each color component is set to the color
red = green = blue = data;
// combine each component into the result
pixel = (red << 0) | (green << 8) | (blue << 16);
}
break;
}
// 5-5-5 (RGB)
case 15:
case 16:
{
// read the two bytes
final int data = inputStream.readShort() & 0xFFFF; // unsigned
// get each color component -- each is 5 bits
red = ((data >> 10) & 0x1F) << 3;
green = ((data >> 5) & 0x1F) << 3;
blue = (data & 0x1F) << 3;
// combine each component into the result
pixel = (red << 0) | (green << 8) | (blue << 16);
break;
}
// true color RGB(A) (8 bits per pixel)
case 24:
case 32:
// read each color component -- the alpha is only
// read if there are 32 bits per pixel
blue = inputStream.readByte() & 0xFF; // unsigned
green = inputStream.readByte() & 0xFF; // unsigned
red = inputStream.readByte() & 0xFF; // unsigned
if(header.getBitsPerPixel() == 32)
alpha = (inputStream.readByte() & 0xFF); // unsigned
/* else -- 24 bits per pixel (i.e. no alpha) */
// combine each component into the result
pixel = (red << 0) | (green << 8) | (blue << 16) | (alpha << 24);
break;
}
}
// put the pixel in the data array
intData[index] = pixel;
// advance to the next pixel
// TODO: the right-to-left switch
index++;
}
}
return image;
}
/**
* <p>Reads and returns an array of color mapped values. If the image does
* not contain a color map <code>null</code> will be returned</p>
*
* @param header the <code>TGAHeader</code> for the image
* @return the array of <code>int</code> color map values or <code>null</code>
* if the image does not contain a color map
* @throws IOException if there is an I/O error while reading the color map
*/
private int[] readColorMap(final TGAHeader header)
throws IOException
{
// determine if the image contains a color map. If not, return null
if(!header.hasColorMap())
return null;
/* else -- there is a color map */
// seek to the start of the color map in the input stream
inputStream.seek(header.getColorMapDataOffset());
// get the number of colros in the color map and the number of bits
// per color map entry
final int numberOfColors = header.getColorMapLength();
final int bitsPerEntry = header.getBitsPerColorMapEntry();
// create the array that will contain the color map data
// CHECK: why is tge explicit +1 needed here ?!?
final int[] colorMap = new int[numberOfColors + 1];
// read each color map entry
for(int i=0; i<numberOfColors; i++)
{
int red = 0, green = 0, blue = 0;
// read based on the number of bits per color map entry
switch(bitsPerEntry)
{
// grey scale (R = G = B)
case 8:
default:
{
final int data = inputStream.readByte() & 0xFF; // unsigned
red = green = blue = data;
break;
}
// 5-5-5 (RGB)
case 15:
case 16:
{
// read the two bytes
final int data = inputStream.readShort() & 0xFFFF; // unsigned
// get each color component -- each is 5 bits
red = ((data >> 10) & 0x1F) << 3;
green = ((data >> 5) & 0x1F) << 3;
blue = (data & 0x1F) << 3;
break;
}
// true color RGB(A) (8 bits per pixel)
case 24:
case 32:
// read each color component
// CHECK: is there an alpha?!?
blue = inputStream.readByte() & 0xFF; // unsigned
green = inputStream.readByte() & 0xFF; // unsigned
red = inputStream.readByte() & 0xFF; // unsigned
break;
}
// combine each component into the result
colorMap[i] = (red << 0) | (green << 8) | (blue << 16);
}
return colorMap;
}
/**
* <p>Validate that the specified {@link javax.imageio.ImageReadParam}
* contains only the default values. If non-default values are present,
* {@link java.io.IOException} is thrown.</p>
*
* @param param the <code>ImageReadParam</code> to be validated
* @param head the <code>TGAHeader</code> that contains information about
* the source image
* @throws IOException if the <code>ImageReadParam</code> contains non-default
* values
*/
private void checkImageReadParam(final ImageReadParam param,
final TGAHeader header)
throws IOException
{
if(param != null)
{
// get the image height and width from the header for convenience
final int width = header.getWidth();
final int height = header.getHeight();
// ensure that the param contains only the defaults
final Rectangle sourceROI = param.getSourceRegion();
if( (sourceROI != null) &&
( (sourceROI.x != 0) || (sourceROI.y != 0) ||
(sourceROI.width != width) || (sourceROI.height != height) ) )
{
throw new IOException("The source region of interest is not the default."); // FIXME: localize
} /* else -- the source ROI is the default */
final Rectangle destinationROI = param.getSourceRegion();
if( (destinationROI != null) &&
( (destinationROI.x != 0) || (destinationROI.y != 0) ||
(destinationROI.width != width) || (destinationROI.height != height) ) )
{
throw new IOException("The destination region of interest is not the default."); // FIXME: localize
} /* else -- the destination ROI is the default */
if( (param.getSourceXSubsampling() != 1) ||
(param.getSourceYSubsampling() != 1) )
{
throw new IOException("Source sub-sampling is not supported."); // FIXME: localize
} /* else -- sub-sampling is the default */
} /* else -- the ImageReadParam is null so the defaults *are* used */
}
}
// =============================================================================
/*
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/