package de.danielsenff.dropps.models;
/**
* Copyright (c) 2007-2009, JAGaToo Project Group all rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the 'Xith3D Project Group' 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) A
* RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE
*/
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.BufferedInputStream;
import java.io.IOException;
//import org.jagatoo.util.image.ImageUtility;
/**
* Reads TGA files from an InputStream.
*
* @author Marvin Froehlich (aka Qudus)
*/
public class TextureImageFormatLoaderTGA
{
private static final int HEADER_SIZE = 18;
private static final int HEADER_INVALID = 0;
private static final int HEADER_UNCOMPRESSED = 1;
private static final int HEADER_COMPRESSED = 2;
private static final short getUnsignedByte( byte[] bytes, int byteIndex )
{
return ( (short)( bytes[byteIndex] & 0xFF ) );
}
private static final int getUnsignedShort( byte[] bytes, int byteIndex )
{
return ( ( getUnsignedByte( bytes, byteIndex + 1 ) << 8 ) + getUnsignedByte( bytes, byteIndex + 0 ) );
}
private static void readBuffer( BufferedInputStream in, byte[] buffer ) throws IOException
{
int bytesRead = 0;
int bytesToRead = buffer.length;
while ( bytesToRead > 0 )
{
int read = in.read( buffer, bytesRead, bytesToRead );
bytesRead += read;
bytesToRead -= read;
}
}
private static final void skipBytes( BufferedInputStream in, long toSkip ) throws IOException
{
while ( toSkip > 0L )
{
long skipped = in.skip( toSkip );
if ( skipped > 0 )
toSkip -= skipped;
else if ( skipped < 0 )
toSkip = 0;
}
}
private static final int compareFormatHeader( BufferedInputStream in, byte[] header ) throws IOException
{
readBuffer( in, header );
boolean hasPalette = false;
int result = HEADER_INVALID;
/*
* 0: Length of Image-ID (usually 0)
*/
int imgIDSize = getUnsignedByte( header, 0 );
/*
* 1: boolean for palette
*
* possible values:
* 0 = no palette
* 1 = has palette
*/
if ( ( header[1] != (byte)0 ) && ( header[1] != (byte)1 ) )
return ( HEADER_INVALID );
/*
* 2: image type
*
* possible values:
* 0 = no image data
* 1 = indexed (palette), uncompressed
* 2 = RGB(A) (no palette), uncompressed
* 3 = monochrome (no palette), uncompressed
* 9 = indexed (palette), compressed (RLE)
* 10 = RGB(A) (no palette), compressed (RLE)
* 11 = monochrome, compressed (RLE)
*/
switch ( getUnsignedByte( header, 2 ) )
{
case 0:
result = HEADER_UNCOMPRESSED;
break;
case 1:
hasPalette = true;
result = HEADER_UNCOMPRESSED;
System.err.println( "Indexed TGA is not yet supported!" );
return ( HEADER_INVALID );
case 2:
result = HEADER_UNCOMPRESSED;
break;
case 3:
result = HEADER_UNCOMPRESSED;
break;
case 9:
hasPalette = true;
result = HEADER_COMPRESSED;
System.err.println( "Indexed TGA is not yet supported!" );
return ( HEADER_INVALID );
case 10:
result = HEADER_COMPRESSED;
break;
case 11:
result = HEADER_COMPRESSED;
break;
default:
return ( HEADER_INVALID );
}
/*
* 3/4: Begin of palette data (default: 0)
*/
if ( !hasPalette )
{
if ( getUnsignedShort( header, 3 ) != 0 )
{
// No palette data, but palette offset specified!
return ( HEADER_INVALID );
}
}
/*
* 5/6: Number of colors in the palette
*/
if ( !hasPalette )
{
if ( getUnsignedShort( header, 5 ) != 0 )
{
// No palette data, but palette size specified!
return ( HEADER_INVALID );
}
}
/*
* 7: Size of one palette entry in bits
*
* possible values:
* 0: this value is expected, if no palette is being used.
* 15, 16, 24, 32
*/
short paletteEntrySize = getUnsignedByte( header, 7 );
if ( !hasPalette )
{
if ( paletteEntrySize != 0 )
// No palette, but non-zero palette-entry-size!
return ( HEADER_INVALID );
}
else
{
if ( ( paletteEntrySize != 15 ) && ( paletteEntrySize != 16 ) && ( paletteEntrySize != 24 ) && ( paletteEntrySize != 32 ) )
// Invalid palette-entry-size!
return ( HEADER_INVALID );
}
/*
* 8/9: X-coordinate of the image origin
*
* Should always be 0
*/
if ( getUnsignedShort( header, 8 ) != 0 )
{
return ( HEADER_INVALID );
}
/*
* 10/11: Y-coordinate of the image origin
*
* Should always be 0
*/
if ( getUnsignedShort( header, 10 ) != 0 )
{
return ( HEADER_INVALID );
}
/*
* 12/13: Image width
*/
/*
* 14/15: Image height
*/
/*
* 16: its per pixel (BPP)
*/
switch ( getUnsignedByte( header, 16 ) )
{
case 1:
case 8:
case 15:
case 16:
System.err.println( "TGAs with non RGB or RGBA pixels are not yet supported." );
return ( HEADER_INVALID );
case 24:
case 32:
break;
default:
return ( HEADER_INVALID );
}
/*
* 17: attributes
*/
/*
* If 'imgIDSize' is non-zero, we need to read the image-ID.
*/
if ( imgIDSize != 0 )
{
// We don't need the image-ID. So we simply skip it.
skipBytes( in, imgIDSize );
}
return ( result );
}
private static final void writePixel( final byte red, final byte green, final byte blue, final byte alpha, final boolean hasAlpha, byte[] buffer, final int offset )
{
if ( hasAlpha )
{
buffer[offset + 0] = alpha;
buffer[offset + 1] = blue;
buffer[offset + 2] = green;
buffer[offset + 3] = red;
}
else
{
buffer[offset + 0] = blue;
buffer[offset + 1] = green;
buffer[offset + 2] = red;
}
}
private static void readBuffer( BufferedInputStream in, int width, int height, int srcBytesPerPixel, boolean acceptAlpha, boolean flipVertically, byte[] bb ) throws IOException
{
byte[] buffer = new byte[ srcBytesPerPixel ];
final boolean copyAlpha = ( srcBytesPerPixel == 4 ) && acceptAlpha;
final int dstBytesPerPixel = acceptAlpha ? srcBytesPerPixel : 3;
final int trgLineSize = width * dstBytesPerPixel;
int dstByteOffset = 0;
for ( int y = 0; y < height; y++ )
{
for ( int x = 0; x < width; x++ )
{
int read = in.read( buffer, 0, srcBytesPerPixel );
if ( read < srcBytesPerPixel )
return;
int actualByteOffset = dstByteOffset;
if ( !flipVertically )
actualByteOffset = ( ( height - y - 1 ) * trgLineSize ) + ( x * dstBytesPerPixel );
if ( copyAlpha ) // has alpha?
writePixel( buffer[2], buffer[1], buffer[0], buffer[3], true, bb, actualByteOffset );
else
writePixel( buffer[2], buffer[1], buffer[0], (byte)0, false, bb, actualByteOffset );
dstByteOffset += dstBytesPerPixel;
}
}
}
/**
* Loads an uncompressed TGA (note, much of this code is based on NeHe's)
*
* @param header
* @param in
* @param acceptAlpha
* @param flipVertically
* @param allowStreching
* @param texFactory
*
* @return the TextureImage or null.
*
* @throws IOException
*/
private BufferedImage loadUncompressedTGA( byte[] header, BufferedInputStream in, boolean acceptAlpha, boolean flipVertically ) throws IOException
{
// TGA Loading code nehe.gamedev.net)
// Determine The TGA width (highbyte * 256 + lowbyte)
int orgWidth = getUnsignedShort( header, 12 );
// Determine The TGA height (highbyte * 256 + lowbyte)
int orgHeight = getUnsignedShort( header, 14 );
// Determine the bits per pixel
int bpp = getUnsignedByte( header, 16 );
//boolean isOriginLeft = ( header[17] & 0x10 ) == 0;
boolean isOriginBottom = ( header[17] & 0x20 ) == 0;
//boolean isOriginLowerLeft = isOriginLeft && isOriginBottom;
if ( !isOriginBottom )
{
flipVertically = !flipVertically;
}
// Make sure all information is valid
if ( ( orgWidth <= 0 ) || ( orgHeight <= 0 ) || ( ( bpp != 24 ) && ( bpp != 32 ) ) )
{
throw new IOException( "Invalid texture information" );
}
// Compute the number of BYTES per pixel
int bytesPerPixel = ( bpp / 8 );
// Compute the total amout ofmemory needed to store data
//int imageSize = orgWidth * orgHeight * bytesPerPixel;
int width = orgWidth;
int height = orgHeight;
final int dstBytesPerPixel = ( acceptAlpha && ( bytesPerPixel == 4 ) ? 4 : 3 );
BufferedImage image = new BufferedImage( width, height, dstBytesPerPixel == 4 ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_3BYTE_BGR );
byte[] imageData = ((DataBufferByte)image.getRaster().getDataBuffer()).getData();
readBuffer( in, orgWidth, orgHeight, bytesPerPixel, acceptAlpha, flipVertically, imageData );
return ( image );
}
/**
* Loads COMPRESSED TGAs
*
* @param header
* @param in
* @param acceptAlpha
* @param flipVertically
* @param allowStreching
* @param texFactory
*
* @return the TextureImage or null.
*
* @throws IOException
*/
private BufferedImage loadCompressedTGA( byte[] header, BufferedInputStream in, boolean acceptAlpha, boolean flipVertically ) throws IOException
{
// Determine The TGA width (highbyte * 256 + lowbyte)
int orgWidth = getUnsignedShort( header, 12 );
// Determine The TGA height (highbyte * 256 + lowbyte)
int orgHeight = getUnsignedShort( header, 14 );
// Determine the bits per pixel
int bpp = getUnsignedByte( header, 16 );
//boolean isOriginLeft = ( header[17] & 0x10 ) == 0;
boolean isOriginBottom = ( header[17] & 0x20 ) == 0;
//boolean isOriginLowerLeft = isOriginLeft && isOriginBottom;
if ( !isOriginBottom )
{
flipVertically = !flipVertically;
}
// Make sure all information is valid
if ( ( orgWidth <= 0 ) || ( orgHeight <= 0 ) || ( ( bpp != 24 ) && ( bpp != 32 ) ) )
{
throw new IOException( "Invalid texture information" );
}
// Compute the number of BYTES per pixel
int bytesPerPixel = ( bpp / 8 );
// Compute the total amout ofmemory needed to store data
//int imageSize = ( bytesPerPixel * imageWidth * imageHeight );
// Allocate that much memory
//byte imageData[] = new byte[ imageSize ];
// Number of pixels in the image
int pixelCount = orgHeight * orgWidth;
// Current byte
// Current pixel being read
int currentPixel = 0;
// Storage for 1 pixel
byte[] colorBuffer = new byte[ bytesPerPixel ];
int width = orgWidth;
int height = orgHeight;
final int dstBytesPerPixel = ( acceptAlpha && ( bytesPerPixel == 4 ) ? 4 : 3 );
final int trgLineSize = orgWidth * dstBytesPerPixel;
BufferedImage image = new BufferedImage( width, height, dstBytesPerPixel == 4 ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_3BYTE_BGR );
byte[] imageData = ((DataBufferByte)image.getRaster().getDataBuffer()).getData();
int dstByteOffset = 0;
do
{
// Storage for "chunk" header
int chunkHeader = 0;
try
{
chunkHeader = (byte)in.read() & 0xFF;
}
catch ( IOException e )
{
throw new IOException( "Could not read RLE header" );
}
boolean repeatColor;
/*
* If the header is < 128, it means, the that is the number of RAW
* color packets minus 1.
*/
if ( chunkHeader < 128 )
{
// add 1 to get number of following color values
chunkHeader++;
repeatColor = false;
}
// chunkheader > 128 RLE data, next color repeated chunkheader - 127 times
else
{
// Subtract 127 to get rid of the ID bit
chunkHeader -= 127;
readBuffer( in, colorBuffer );
repeatColor = true;
}
for ( int counter = 0; counter < chunkHeader; counter++ )
{
if ( !repeatColor )
{
readBuffer( in, colorBuffer );
}
// write to memory
int x = currentPixel % orgWidth;
int y = currentPixel / orgWidth;
int actualByteOffset = dstByteOffset;
if ( !flipVertically )
actualByteOffset = ( ( height - y - 1 ) * trgLineSize ) + ( x * dstBytesPerPixel );
// Swap R and B, because TGA stores them swapped.
if ( dstBytesPerPixel == 4 ) // has alpha?
writePixel( colorBuffer[2], colorBuffer[1], colorBuffer[0], colorBuffer[3], true, imageData, actualByteOffset );
else
writePixel( colorBuffer[2], colorBuffer[1], colorBuffer[0], (byte)0, false, imageData, actualByteOffset );
dstByteOffset += dstBytesPerPixel;
// Increase current pixel by 1
currentPixel++;
// Make sure we havent read too many pixels
if ( currentPixel > pixelCount )
{
// if there is too many... Display an error!
throw new IOException( "Too many pixels read" );
}
}
}
while ( currentPixel < pixelCount ); // Loop while there are still pixels left...
return ( image );
}
/**
* can return null for invalid tga files
* {@inheritDoc}
*/
public BufferedImage loadTextureImage( BufferedInputStream in, boolean acceptAlpha, boolean flipVertically ) throws IOException
{
if ( in.available() < HEADER_SIZE )
{
return ( null );
}
byte[] header = new byte[ HEADER_SIZE ];
final int headerType = compareFormatHeader( in, header );
if ( headerType == HEADER_INVALID )
throw new IOException("headers are invalid");
//return ( null );
BufferedImage image = null;
if ( headerType == HEADER_UNCOMPRESSED )
{
// If so, jump to uncompressed TGA loading code
image = loadUncompressedTGA( header, in, acceptAlpha, flipVertically );
}
else if ( headerType == HEADER_COMPRESSED )
{
// If so, jump to compressed TGA loading code
image = loadCompressedTGA( header, in, acceptAlpha, flipVertically );
}
// If header matches neither type
else
{
throw new IOException("TGA file be type 2 or type 10 ");
// return ( null );
}
return ( image );
}
}