376 lines
9.2 KiB
C++
376 lines
9.2 KiB
C++
/*
|
|
* Modification History
|
|
*
|
|
* 2001-September-22 Jason Rohrer
|
|
* Created.
|
|
*
|
|
* 2001-October-13 Jason Rohrer
|
|
* Added support for 4-channel images.
|
|
*
|
|
* 2006-November-18 Jason Rohrer
|
|
* Fixed two unused variable warnings.
|
|
*/
|
|
|
|
|
|
#ifndef TGA_IMAGE_CONVERTER_INCLUDED
|
|
#define TGA_IMAGE_CONVERTER_INCLUDED
|
|
|
|
|
|
#include "LittleEndianImageConverter.h"
|
|
|
|
|
|
/**
|
|
* TGA (Targa) implementation of the image conversion interface.
|
|
*
|
|
* Note that it only supports 24- and 32-bit TGA files
|
|
* (and thus only 3- and 4-channel Images).
|
|
*
|
|
* TGA format information taken from:
|
|
* http://www.cubic.org/source/archive/fileform/graphic/tga/targa.txt
|
|
*
|
|
* @author Jason Rohrer
|
|
*/
|
|
class TGAImageConverter : public LittleEndianImageConverter {
|
|
|
|
public:
|
|
|
|
// implement the ImageConverter interface
|
|
virtual void formatImage( Image *inImage,
|
|
OutputStream *inStream );
|
|
|
|
virtual Image *deformatImage( InputStream *inStream );
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
inline void TGAImageConverter::formatImage( Image *inImage,
|
|
OutputStream *inStream ) {
|
|
|
|
int numChannels = inImage->getNumChannels();
|
|
|
|
// make sure the image is in the right format
|
|
if( numChannels != 3 &&
|
|
numChannels != 4 ) {
|
|
printf( "Only 3- and 4-channel images can be converted to " );
|
|
printf( "the TGA format.\n" );
|
|
return;
|
|
}
|
|
|
|
long width = inImage->getWidth();
|
|
long height = inImage->getHeight();
|
|
|
|
long numPixels = width * height;
|
|
|
|
|
|
// a buffer for writing single bytes
|
|
unsigned char *byteBuffer = new unsigned char[1];
|
|
|
|
// write the identification field size
|
|
// (an empty identification field)
|
|
byteBuffer[0] = 0;
|
|
inStream->write( byteBuffer, 1 );
|
|
|
|
// write the color map type
|
|
// (no color map)
|
|
byteBuffer[0] = 0;
|
|
inStream->write( byteBuffer, 1 );
|
|
|
|
// write the image type code
|
|
// (type 2: unmapped RGB image)
|
|
byteBuffer[0] = 2;
|
|
inStream->write( byteBuffer, 1 );
|
|
|
|
// no color map spec
|
|
// (set to 0, though it will be ignored)
|
|
unsigned char *colorMapSpec = new unsigned char[5];
|
|
int i;
|
|
for( i=0; i<5; i++ ) {
|
|
colorMapSpec[i] = 0;
|
|
}
|
|
inStream->write( colorMapSpec, 5 );
|
|
delete [] colorMapSpec;
|
|
|
|
// now for the image specification
|
|
|
|
// x origin coordinate
|
|
writeLittleEndianShort( 0, inStream );
|
|
// y origin coordinate
|
|
writeLittleEndianShort( 0, inStream );
|
|
|
|
writeLittleEndianShort( width, inStream );
|
|
writeLittleEndianShort( height, inStream );
|
|
|
|
// number of bits in pixels
|
|
if( numChannels == 3 ) {
|
|
byteBuffer[0] = 24;
|
|
}
|
|
else {
|
|
byteBuffer[0] = 32;
|
|
}
|
|
inStream->write( byteBuffer, 1 );
|
|
|
|
|
|
// image descriptor byte
|
|
if( numChannels == 3 ) {
|
|
// setting to 0 specifies:
|
|
// -- no attributes per pixel (for 24-bit)
|
|
// -- screen origin in lower left corner
|
|
// -- non-interleaved data storage
|
|
byteBuffer[0] = 0;
|
|
}
|
|
else {
|
|
// setting to 8 specifies:
|
|
// -- 8 attributes per pixel (for 32-bit) (attributes are alpha bits)
|
|
// -- screen origin in lower left corner
|
|
// -- non-interleaved data storage
|
|
byteBuffer[0] = 8;
|
|
}
|
|
|
|
// set bit 5 to 1 to specify screen origin in upper left corner
|
|
byteBuffer[0] = byteBuffer[0] | ( 1 << 5 );
|
|
inStream->write( byteBuffer, 1 );
|
|
|
|
|
|
// We skip the image identification field,
|
|
// since we set its length to 0 above.
|
|
|
|
// We also skip the color map data,
|
|
// since we have none (as specified above).
|
|
|
|
|
|
// now we write the pixels, in BGR(A) order
|
|
unsigned char *raster = new unsigned char[ numPixels * numChannels ];
|
|
double *red = inImage->getChannel( 0 );
|
|
double *green = inImage->getChannel( 1 );
|
|
double *blue = inImage->getChannel( 2 );
|
|
|
|
long rasterIndex = 0;
|
|
|
|
if( numChannels == 3 ) {
|
|
for( int i=0; i<numPixels; i++ ) {
|
|
raster[rasterIndex] =
|
|
(unsigned char)( 255 * blue[i] );
|
|
raster[rasterIndex + 1] =
|
|
(unsigned char)( 255 * green[i] );
|
|
raster[rasterIndex + 2] =
|
|
(unsigned char)( 255 * red[i] );
|
|
|
|
rasterIndex += 3;
|
|
}
|
|
inStream->write( raster, numPixels * 3 );
|
|
}
|
|
else { // numChannels == 4
|
|
double *alpha = inImage->getChannel( 3 );
|
|
|
|
for( int i=0; i<numPixels; i++ ) {
|
|
raster[rasterIndex] =
|
|
(unsigned char)( 255 * blue[i] );
|
|
raster[rasterIndex + 1] =
|
|
(unsigned char)( 255 * green[i] );
|
|
raster[rasterIndex + 2] =
|
|
(unsigned char)( 255 * red[i] );
|
|
raster[rasterIndex + 3] =
|
|
(unsigned char)( 255 * alpha[i] );
|
|
|
|
rasterIndex += 4;
|
|
}
|
|
}
|
|
|
|
inStream->write( raster, numPixels * numChannels );
|
|
|
|
delete [] raster;
|
|
delete [] byteBuffer;
|
|
}
|
|
|
|
|
|
|
|
inline Image *TGAImageConverter::deformatImage( InputStream *inStream ) {
|
|
|
|
// a buffer for reading single bytes
|
|
unsigned char *byteBuffer = new unsigned char[1];
|
|
|
|
// read the identification field size
|
|
inStream->read( byteBuffer, 1 );
|
|
|
|
int identificationFieldSize = byteBuffer[0];
|
|
|
|
// read the color map type
|
|
// (only 0, or no color map, is supported)
|
|
inStream->read( byteBuffer, 1 );
|
|
if( byteBuffer[0] != 0 ) {
|
|
printf( "Only TGA files without colormaps can be read.\n" );
|
|
delete [] byteBuffer;
|
|
return NULL;
|
|
}
|
|
|
|
// read the image type code
|
|
// (only type 2, unmapped RGB image, is supported)
|
|
inStream->read( byteBuffer, 1 );
|
|
if( byteBuffer[0] != 2 ) {
|
|
printf(
|
|
"Only TGA files containing unmapped RGB images can be read.\n" );
|
|
delete [] byteBuffer;
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// ignore color map spec
|
|
// (skip all 5 bytes of it)
|
|
unsigned char *colorMapSpec = new unsigned char[5];
|
|
inStream->read( colorMapSpec, 5 );
|
|
delete [] colorMapSpec;
|
|
|
|
|
|
// now for the image specification
|
|
|
|
// don't need either of these
|
|
// don't set to a variable for now to avoid unused variable warnings
|
|
// x origin coordinate
|
|
readLittleEndianShort( inStream );
|
|
// y origin coordinate
|
|
readLittleEndianShort( inStream );
|
|
|
|
long width = readLittleEndianShort( inStream );
|
|
long height = readLittleEndianShort( inStream );
|
|
|
|
long numPixels = width * height;
|
|
|
|
|
|
// number of bits in pixels
|
|
// only 24 bits per pixel supported
|
|
|
|
inStream->read( byteBuffer, 1 );
|
|
if( byteBuffer[0] != 24 && byteBuffer[0] != 32 ) {
|
|
printf( "Only 24- and 32-bit TGA files can be read.\n" );
|
|
delete [] byteBuffer;
|
|
return NULL;
|
|
}
|
|
|
|
int numChannels = 0;
|
|
if( byteBuffer[0] == 24 ) {
|
|
numChannels = 3;
|
|
}
|
|
else {
|
|
numChannels = 4;
|
|
}
|
|
|
|
|
|
// image descriptor byte
|
|
// setting to 0 specifies:
|
|
// -- no attributes per pixel (for 24-bit)
|
|
// -- screen origin in lower left corner
|
|
// -- non-interleaved data storage
|
|
// set bit 5 to 1 to specify screen origin in upper left corner
|
|
inStream->read( byteBuffer, 1 );
|
|
char originAtTop = byteBuffer[0] & ( 1 << 5 );
|
|
|
|
|
|
if( identificationFieldSize > 0 ) {
|
|
// We skip the image identification field
|
|
unsigned char *identificationField =
|
|
new unsigned char[ identificationFieldSize ];
|
|
inStream->read( identificationField, identificationFieldSize );
|
|
delete [] identificationField;
|
|
}
|
|
|
|
|
|
// We also skip the color map data,
|
|
// since we have none (as specified above).
|
|
|
|
|
|
|
|
|
|
// now we read the pixels, in BGR(A) order
|
|
unsigned char *raster = new unsigned char[ numPixels * numChannels ];
|
|
inStream->read( raster, numPixels * numChannels );
|
|
|
|
// optimization: don't init channels to black (found with profiler)
|
|
Image *image = new Image( width, height, numChannels, false );
|
|
|
|
double *red = image->getChannel( 0 );
|
|
double *green = image->getChannel( 1 );
|
|
double *blue = image->getChannel( 2 );
|
|
|
|
long rasterIndex = 0;
|
|
double inv255 = 1.0 / 255.0;
|
|
|
|
if( numChannels == 3 ) {
|
|
if( originAtTop ) {
|
|
for( int i=0; i<numPixels; i++ ) {
|
|
blue[i] = inv255 * raster[ rasterIndex ];
|
|
green[i] = inv255 * raster[ rasterIndex + 1 ];
|
|
red[i] = inv255 * raster[ rasterIndex + 2 ];
|
|
|
|
rasterIndex += 3;
|
|
}
|
|
}
|
|
else {
|
|
// we need to flip the raster vertically as we
|
|
// copy it into our return image
|
|
for( int y=height-1; y>=0; y-- ) {
|
|
for( int x=0; x<width; x++ ) {
|
|
int imageIndex = y * width + x;
|
|
|
|
blue[ imageIndex ] = inv255 * raster[ rasterIndex ];
|
|
green[imageIndex] = inv255 * raster[ rasterIndex + 1 ];
|
|
red[imageIndex] = inv255 * raster[ rasterIndex + 2 ];
|
|
|
|
rasterIndex += 3;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else { // numChannels == 4
|
|
double *alpha = image->getChannel( 3 );
|
|
|
|
if( originAtTop ) {
|
|
for( int i=0; i<numPixels; i++ ) {
|
|
// optimization: use postfix increment operators in
|
|
// array index
|
|
// (found with profiler)
|
|
blue[i] = inv255 * raster[ rasterIndex ++ ];
|
|
green[i] = inv255 * raster[ rasterIndex ++ ];
|
|
red[i] = inv255 * raster[ rasterIndex ++ ];
|
|
alpha[i] = inv255 * raster[ rasterIndex ++ ];
|
|
|
|
// optimization
|
|
// rasterIndex += 4;
|
|
}
|
|
}
|
|
else {
|
|
// we need to flip the raster vertically as we
|
|
// copy it into our return image
|
|
for( int y=height-1; y>=0; y-- ) {
|
|
int yOffset = y * width;
|
|
|
|
for( int x=0; x<width; x++ ) {
|
|
int imageIndex = yOffset + x;
|
|
|
|
// optimization: use postfix increment operators in
|
|
// array index
|
|
// (found with profiler)
|
|
|
|
blue[ imageIndex ] = inv255 * raster[ rasterIndex ++ ];
|
|
green[imageIndex] = inv255 * raster[ rasterIndex ++ ];
|
|
red[imageIndex] = inv255 * raster[ rasterIndex ++ ];
|
|
alpha[imageIndex] = inv255 * raster[ rasterIndex ++ ];
|
|
|
|
// optimization
|
|
// rasterIndex += 4;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
delete [] raster;
|
|
delete [] byteBuffer;
|
|
|
|
return image;
|
|
}
|
|
|
|
|
|
|
|
#endif
|