Initial source commit

This commit is contained in:
Tony Bark 2025-10-03 02:19:59 -04:00
commit f1384c11ee
335 changed files with 52715 additions and 0 deletions

View file

@ -0,0 +1,251 @@
/*
* Modification History
*
* 2001-February-19 Jason Rohrer
* Created.
*
* 2001-February-19 Jason Rohrer
* Fixed some bugs in the raster formatting code.
*
* 2001-May-19 Jason Rohrer
* Fixed a bug in zero padding for each line.
*
* 2001-May-21 Jason Rohrer
* Fixed another bug in zero padding for each line.
*
* 2001-September-22 Jason Rohrer
* Changed to subclass LittleEndianImageConverter.
*/
#ifndef BMP_IMAGE_CONVERTER_INCLUDED
#define BMP_IMAGE_CONVERTER_INCLUDED
#include "LittleEndianImageConverter.h"
/**
* Windows BMP implementation of the image conversion interface.
*
* Note that it only supports 24-bit BMP files (and thus only 3-channel
* Images).
*
* BMP format information taken from:
* http://www.daubnet.com/formats/BMP.html
* Some information from this site is incorrect:
* 24-bit pixels are _not_ stored in 32-bit blocks.
*
* @author Jason Rohrer
*/
class BMPImageConverter : public LittleEndianImageConverter {
public:
// implement the ImageConverter interface
virtual void formatImage( Image *inImage,
OutputStream *inStream );
virtual Image *deformatImage( InputStream *inStream );
};
inline void BMPImageConverter::formatImage( Image *inImage,
OutputStream *inStream ) {
// make sure the image is in the right format
if( inImage->getNumChannels() != 3 ) {
printf(
"Only 3-channel images can be converted to the BMP format.\n" );
return;
}
long width = inImage->getWidth();
long height = inImage->getHeight();
long numPixels = width * height;
// each line should be padded with zeros to
// end on a 4-byte boundary
int numZeroPaddingBytes = ( 4 - ( width * 3 ) % 4 ) % 4;
short bitCount = 24;
long rasterSize = numPixels * 3;
// zero padding bytes for each row
rasterSize += numZeroPaddingBytes * height;
// offset past two headers
long offsetToRaster = 14 + 40;
long fileSize = offsetToRaster + rasterSize;
long compressionType = 0;
long pixelsPerMeter = 2834;
// both are 0 since we have no color map
long colorsUsed = 0;
long colorsImportant = 0;
// write the header
unsigned char *signature = new unsigned char[2];
signature[0] = 'B';
signature[1] = 'M';
inStream->write( signature, 2 );
delete [] signature;
writeLittleEndianLong( fileSize, inStream );
writeLittleEndianLong( 0, inStream );
writeLittleEndianLong( offsetToRaster, inStream );
// write the info header
// header size
writeLittleEndianLong( 40, inStream );
writeLittleEndianLong( width, inStream );
writeLittleEndianLong( height, inStream );
// numPlanes
writeLittleEndianShort( 1, inStream );
writeLittleEndianShort( bitCount, inStream );
writeLittleEndianLong( compressionType, inStream );
writeLittleEndianLong( rasterSize, inStream );
writeLittleEndianLong( pixelsPerMeter, inStream );
writeLittleEndianLong( pixelsPerMeter, inStream );
writeLittleEndianLong( colorsUsed, inStream );
writeLittleEndianLong( colorsImportant, inStream );
// no color table...
// now write the raster
unsigned char *raster = new unsigned char[ rasterSize ];
double *red = inImage->getChannel( 0 );
double *green = inImage->getChannel( 1 );
double *blue = inImage->getChannel( 2 );
// pixels are stored bottom-up, left to right
// (row major order)
long rasterIndex = 0;
for( int y=height-1; y>=0; y-- ) {
for( int x=0; x<width; x++ ) {
int imageIndex = y * width + x;
raster[rasterIndex] =
(unsigned char)( 255 * blue[imageIndex] );
raster[rasterIndex + 1] =
(unsigned char)( 255 * green[imageIndex] );
raster[rasterIndex + 2] =
(unsigned char)( 255 * red[imageIndex] );
rasterIndex += 3;
}
for( int p=0; p<numZeroPaddingBytes; p++ ) {
// insert at the end of this line
raster[ rasterIndex ] = 0;
rasterIndex++;
}
}
inStream->write( raster, rasterSize );
delete [] raster;
}
inline Image *BMPImageConverter::deformatImage( InputStream *inStream ) {
// temp buffer used to skip data in the stream
unsigned char *temp = new unsigned char[ 100 ];
// skip signature
inStream->read( temp, 2 );
long fileSize = readLittleEndianLong( inStream );
// skip unused
inStream->read( temp, 4 );
long rasterOffset = readLittleEndianLong( inStream );
long rasterSize = fileSize - rasterOffset;
// skip size of header
inStream->read( temp, 4 );
long width = readLittleEndianLong( inStream );
long height = readLittleEndianLong( inStream );
// skip planes
inStream->read( temp, 2 );
short bitCount = readLittleEndianShort( inStream );
char failing = false;
if( bitCount != 24 ) {
printf( "Only 24-bit BMP file formats supported.\n" );
failing = true;
}
long compression = readLittleEndianLong( inStream );
if( compression != 0 ) {
printf( "Only uncompressed BMP file formats supported.\n" );
failing = true;
}
// skip imageSize, resolution, and color usage information
inStream->read( temp, 20 );
// now we're at the raster.
// each line should be padded with zeros to
// end on a 4-byte boundary
int numZeroPaddingBytes = ( 4 - ( width * 3 ) % 4 ) % 4;
unsigned char *raster = new unsigned char[ rasterSize ];
inStream->read( raster, rasterSize );
Image *returnImage;
if( failing ) {
return NULL;
}
else {
returnImage = new Image( width, height, 3 );
double *red = returnImage->getChannel( 0 );
double *green = returnImage->getChannel( 1 );
double *blue = returnImage->getChannel( 2 );
// pixels are stored bottom-up, left to right
// (row major order)
long rasterIndex = 0;
for( int y=height-1; y>=0; y-- ) {
for( int x=0; x<width; x++ ) {
int imageIndex = y * width + x;
blue[imageIndex] =
(double)( raster[rasterIndex] ) / 255.0;
green[imageIndex] =
(double)( raster[rasterIndex + 1] ) / 255.0;
red[imageIndex] =
(double)( raster[rasterIndex + 2] ) / 255.0;
rasterIndex += 3;
}
// skip the zero padding bytes at the end of this line
rasterIndex += numZeroPaddingBytes;
}
}
delete [] raster;
delete [] temp;
return returnImage;
}
#endif

View file

@ -0,0 +1,142 @@
/*
* Modification History
*
* 2006-November-21 Jason Rohrer
* Created. Copied from LittleEndian version.
*/
#ifndef BIG_ENDIAN_IMAGE_CONVERTER_INCLUDED
#define BIG_ENDIAN_IMAGE_CONVERTER_INCLUDED
#include "minorGems/graphics/ImageConverter.h"
/**
* A base class for converters that have big endian file formats.
* Basically includes big endian reading and writing functions.
*
* @author Jason Rohrer
*/
class BigEndianImageConverter : public ImageConverter {
public:
// does not implement the ImageConverter interface,
// which makes this class abstract.
protected:
/**
* Writes a long value in big endian format.
*
* @param inLong the long value to write.
* @param inStream the stream to write inLong to.
*/
void writeBigEndianLong( long inLong,
OutputStream *inStream );
/**
* Writes a short value in big endian format.
*
* @param inShort the short value to write.
* @param inStream the stream to write inShort to.
*/
void writeBigEndianShort( short inShort,
OutputStream *inStream );
/**
* Reads a long value in big endian format.
*
* @param inStream the stream to read the long value from.
*
* @return the long value.
*/
long readBigEndianLong( InputStream *inStream );
/**
* Reads a short value in big endian format.
*
* @param inStream the stream to read the short value from.
*
* @return the short value.
*/
short readBigEndianShort( InputStream *inStream );
};
inline void BigEndianImageConverter::writeBigEndianLong( long inLong,
OutputStream *inStream ) {
unsigned char buffer[4];
buffer[0] = (unsigned char)( ( inLong >> 24 ) & 0xFF );
buffer[1] = (unsigned char)( ( inLong >> 16 ) & 0xFF );
buffer[2] = (unsigned char)( ( inLong >> 8 ) & 0xFF );
buffer[3] = (unsigned char)( inLong & 0xFF );
inStream->write( buffer, 4 );
}
inline long BigEndianImageConverter::readBigEndianLong(
InputStream *inStream ) {
unsigned char buffer[4];
inStream->read( buffer, 4 );
long outLong =
( buffer[0] << 24 ) |
( buffer[1] << 16 ) |
( buffer[2] << 8 ) |
buffer[3];
return outLong;
}
inline void BigEndianImageConverter::writeBigEndianShort( short inShort,
OutputStream *inStream ) {
unsigned char buffer[2];
buffer[0] = (unsigned char)( ( inShort >> 8 ) & 0xFF );
buffer[1] = (unsigned char)( inShort & 0xFF );
inStream->write( buffer, 2 );
}
inline short BigEndianImageConverter::readBigEndianShort(
InputStream *inStream ) {
unsigned char buffer[2];
inStream->read( buffer, 2 );
long outShort =
( buffer[0] << 8 ) |
buffer[1];
return outShort;
}
#endif

View file

@ -0,0 +1,95 @@
/*
* Modification History
*
* 2001-April-27 Jason Rohrer
* Created.
*/
#ifndef JPEG_IMAGE_CONVERTER_INCLUDED
#define JPEG_IMAGE_CONVERTER_INCLUDED
#include "minorGems/graphics/ImageConverter.h"
/**
* JPEG implementation of the image conversion interface.
*
* Implementations are platform dependent.
*
* @author Jason Rohrer
*/
class JPEGImageConverter : public ImageConverter {
public:
/**
* Constructs a JPEGImageConverter.
*
* @param inQuality a quality value in [0,100] for compression.
* 100 specifies highest quality.
*/
JPEGImageConverter( int inQuality );
/**
* Sets the compression quality.
*
* @param inQuality a quality value in [0,100]. 100
* specifies highest quality.
*/
void setQuality( int inQuality );
/**
* Gets the compression quality.
*
* @return a quality value in [0,100]. 100
* indicates highest quality.
*/
int getQuality();
// implement the ImageConverter interface
virtual void formatImage( Image *inImage,
OutputStream *inStream );
virtual Image *deformatImage( InputStream *inStream );
private:
int mQuality;
};
inline JPEGImageConverter::JPEGImageConverter( int inQuality )
: mQuality( inQuality ) {
if( mQuality > 100 || mQuality < 0 ) {
printf( "JPEG quality must be in [0,100]\n" );
mQuality = 50;
}
}
inline void JPEGImageConverter::setQuality( int inQuality ) {
mQuality = inQuality;
if( mQuality > 100 || mQuality < 0 ) {
printf( "JPEG quality must be in [0,100]\n" );
mQuality = 50;
}
}
inline int JPEGImageConverter::getQuality() {
return mQuality;
}
#endif

View file

@ -0,0 +1,137 @@
/*
* Modification History
*
* 2001-September-22 Jason Rohrer
* Created.
*/
#ifndef LITTLE_ENDIAN_IMAGE_CONVERTER_INCLUDED
#define LITTLE_ENDIAN_IMAGE_CONVERTER_INCLUDED
#include "minorGems/graphics/ImageConverter.h"
/**
* A base class for converters that have little endian file formats.
* Basically includes little endian reading and writing functions.
*
* @author Jason Rohrer
*/
class LittleEndianImageConverter : public ImageConverter {
public:
// does not implement the ImageConverter interface,
// which makes this class abstract.
protected:
/**
* Writes a long value in little endian format.
*
* @param inLong the long value to write.
* @param inStream the stream to write inLong to.
*/
void writeLittleEndianLong( long inLong,
OutputStream *inStream );
/**
* Writes a short value in little endian format.
*
* @param inShort the short value to write.
* @param inStream the stream to write inShort to.
*/
void writeLittleEndianShort( short inShort,
OutputStream *inStream );
/**
* Reads a long value in little endian format.
*
* @param inStream the stream to read the long value from.
*
* @return the long value.
*/
long readLittleEndianLong( InputStream *inStream );
/**
* Reads a short value in little endian format.
*
* @param inStream the stream to read the short value from.
*
* @return the short value.
*/
short readLittleEndianShort( InputStream *inStream );
};
inline void LittleEndianImageConverter::writeLittleEndianLong( long inLong,
OutputStream *inStream ) {
unsigned char buffer[4];
buffer[0] = (unsigned char)( inLong & 0xFF );
buffer[1] = (unsigned char)( ( inLong >> 8 ) & 0xFF );
buffer[2] = (unsigned char)( ( inLong >> 16 ) & 0xFF );
buffer[3] = (unsigned char)( ( inLong >> 24 ) & 0xFF );
inStream->write( buffer, 4 );
}
inline long LittleEndianImageConverter::readLittleEndianLong(
InputStream *inStream ) {
unsigned char buffer[4];
inStream->read( buffer, 4 );
long outLong = buffer[0] | ( buffer[1] << 8 ) | ( buffer[2] << 16 )
| ( buffer[3] << 24 );
return outLong;
}
inline void LittleEndianImageConverter::writeLittleEndianShort( short inShort,
OutputStream *inStream ) {
unsigned char buffer[2];
buffer[0] = (unsigned char)( inShort & 0xFF );
buffer[1] = (unsigned char)( ( inShort >> 8 ) & 0xFF );
inStream->write( buffer, 2 );
}
inline short LittleEndianImageConverter::readLittleEndianShort(
InputStream *inStream ) {
unsigned char buffer[2];
inStream->read( buffer, 2 );
long outShort = buffer[0] | ( buffer[1] << 8 );
return outShort;
}
#endif

View file

@ -0,0 +1,643 @@
/*
* Modification History
*
* 2006-November-21 Jason Rohrer
* Created.
*
* 2010-May-18 Jason Rohrer
* String parameters as const to fix warnings.
*/
#include "PNGImageConverter.h"
#include "minorGems/util/SimpleVector.h"
#include "minorGems/graphics/RGBAImage.h"
//#include "lodepng.h"
//#include <zlib.h>
#include <png.h>
PNGImageConverter::PNGImageConverter( int inCompressionLevel )
: mCompressionLevel( inCompressionLevel ) {
// set up the CRC table
// code taken from the PNG spec:
// http://www.w3.org/TR/2003/REC-PNG-20031110/#D-CRCAppendix
unsigned long c;
int n, k;
for( n=0; n<256; n++ ) {
c = (unsigned long)n;
for( k=0; k<8; k++ ) {
if( c & 1 ) {
c = 0xedb88320L ^ (c >> 1);
}
else {
c = c >> 1;
}
}
mCRCTable[n] = c;
}
}
unsigned long PNGImageConverter::updateCRC(
unsigned long inCRC, unsigned char *inData, int inLength ) {
// code taken from the PNG spec:
// http://www.w3.org/TR/2003/REC-PNG-20031110/#D-CRCAppendix
unsigned long c = inCRC;
int n;
for( n=0; n<inLength; n++ ) {
c = mCRCTable[ (c ^ inData[n]) & 0xff ]
^
(c >> 8);
}
return c;
}
#define ADLER_BASE 65521 /* largest prime smaller than 65536 */
/**
* Updates an adler32 checksum.
* code found here http://www.ietf.org/rfc/rfc1950.txt
*
* New adlers should start with inAdler set to 1.
*
* @param inAdler the current state of the checksum.
* @param inData the data to add. Destroyed by caller.
* @param inLength the length of the data in bytes.
*
* @return the new checksum.
*/
unsigned long updateAdler32( unsigned long inAdler,
unsigned char *inData, int inLength ) {
unsigned long s1 = inAdler & 0xffff;
unsigned long s2 = (inAdler >> 16) & 0xffff;
int n;
for (n = 0; n < inLength; n++) {
s1 = (s1 + inData[n]) % ADLER_BASE;
s2 = (s2 + s1) % ADLER_BASE;
}
return (s2 << 16) + s1;
}
void PNGImageConverter::writeChunk(
const char inChunkType[4], unsigned char *inData,
unsigned long inNumBytes, OutputStream *inStream ) {
// chunk layout:
// 4-byte length
// 4-char type
// data
// 4-byte CRC (applied to type and data parts)
// write the length
writeBigEndianLong( inNumBytes, inStream );
inStream->write( (unsigned char *)inChunkType, 4 );
// start the crc
unsigned long crc = updateCRC( mStartCRC,
(unsigned char *)inChunkType, 4 );
if( inData != NULL ) {
// chunk has data
inStream->write( inData, inNumBytes );
crc = updateCRC( crc, inData, inNumBytes );
}
// final step: invert the CRC
crc = crc ^ 0xffffffffL;
// now write the CRC
writeBigEndianLong( crc, inStream );
}
// callbacks for libpng io
void libpngWriteCallback( png_structp png_ptr,
png_bytep data, png_size_t length ) {
// unpack our extra parameter
void *write_io_ptr = png_get_io_ptr( png_ptr );
OutputStream *inStream = (OutputStream *)write_io_ptr;
inStream->write( data, length );
}
void libpngFlushCallback( png_structp png_ptr ) {
// do nothing?
}
void PNGImageConverter::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 PNG format.\n" );
return;
}
int w = inImage->getWidth();
int h = inImage->getHeight();
//RGBAImage rgbaImage( inImage );
unsigned char *imageBytes = RGBAImage::getRGBABytes( inImage );
// libpng implementation
// adapted from this:
// http://zarb.org/~gc/html/libpng.html
// get pointers to rows
unsigned char **rows = new unsigned char *[h];
for( int y=0; y<h; y++ ) {
rows[y] = &( imageBytes[ y * (w * 4) ] );
}
png_structp png_ptr;
png_infop info_ptr;
// initialize structures
png_ptr = png_create_write_struct( PNG_LIBPNG_VER_STRING,
NULL, NULL, NULL );
if( !png_ptr ) {
delete [] imageBytes;
delete [] rows;
printf( "PNG Writing: png_create_write_struct failed\n" );
return;
}
png_set_compression_level( png_ptr, mCompressionLevel );
info_ptr = png_create_info_struct( png_ptr );
if( !info_ptr ) {
delete [] imageBytes;
delete [] rows;
png_destroy_write_struct( &png_ptr, NULL );
printf( "PNG Writing: png_create_info_struct failed\n" );
return;
}
// weird way that libpng handles errors with a jump
if( setjmp( png_jmpbuf( png_ptr ) ) ) {
delete [] imageBytes;
delete [] rows;
png_destroy_write_struct( &png_ptr, &info_ptr );
printf( "PNG Writing: error when setting writing funciton\n" );
return;
}
//png_init_io(png_ptr, fp);
// set our write callback (since we're not writing directly to file)
// pass the stream as the extra void* parameter
png_set_write_fn( png_ptr,
(void*)inStream,
libpngWriteCallback,
libpngFlushCallback );
// write header
if( setjmp( png_jmpbuf( png_ptr ) ) ) {
delete [] imageBytes;
delete [] rows;
png_destroy_write_struct( &png_ptr, &info_ptr );
printf( "PNG Writing: error writing header\n" );
return;
}
png_set_IHDR( png_ptr, info_ptr, w, h,
8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE );
png_write_info( png_ptr, info_ptr );
// write bytes
// write header
if( setjmp( png_jmpbuf( png_ptr ) ) ) {
delete [] imageBytes;
delete [] rows;
png_destroy_write_struct( &png_ptr, &info_ptr );
printf( "PNG Writing: error writing image bytes\n" );
return;
}
png_write_image( png_ptr, rows );
// end write
if( setjmp( png_jmpbuf( png_ptr ) ) ) {
delete [] imageBytes;
delete [] rows;
png_destroy_write_struct( &png_ptr, &info_ptr );
printf( "PNG Writing: error ending write\n" );
return;
}
png_write_end( png_ptr, NULL );
png_destroy_write_struct( &png_ptr, &info_ptr );
delete [] imageBytes;
delete [] rows;
if( true ) {
return;
}
/*
// LodePNG implementation
unsigned char *encodedBytes;
size_t encodedSize;
unsigned int error = LodePNG_encode32( &encodedBytes, &encodedSize,
imageBytes, w, h );
delete [] imageBytes;
if( error ) {
delete [] encodedBytes;
printf( "Error encoding PNG image\n" );
return;
}
inStream->write( encodedBytes, encodedSize );
delete [] encodedBytes;
if( true ) {
return;
}
*/
// same for all PNG images
// used to check for basic transmission errors, such as line-end flipping
unsigned char pngSignature[8] = { 0x89, 0x50, 0x4E, 0x47,
0x0D, 0x0A, 0x1A, 0x0A };
inStream->write( pngSignature, 8 );
// data for IHDR chunk
unsigned char headerData[13];
// width
headerData[0] = (w >> 24) & 0xff;
headerData[1] = (w >> 16) & 0xff;
headerData[2] = (w >> 8) & 0xff;
headerData[3] = w & 0xff;
// height
headerData[4] = (h >> 24) & 0xff;
headerData[5] = (h >> 16) & 0xff;
headerData[6] = (h >> 8) & 0xff;
headerData[7] = h & 0xff;
// bit depth
headerData[8] = 8;
// color type
// 2 = truecolor (RGB)
headerData[9] = 2;
// compression method
// method 0 (deflate)
headerData[10] = 0;
// filter method
// method 0 supports 5 filter types
headerData[11] = 0;
// no interlace
headerData[12] = 0;
writeChunk( "IHDR", headerData, 13, inStream );
int numRawBytes = w * h * 3;
// extra byte per scanline for filter type
numRawBytes += h;
unsigned char *rawScanlineBytes = new unsigned char[ numRawBytes ];
// ignore alpha channel
double *channels[3];
int i;
for( i=0; i<3; i++ ) {
channels[i] = inImage->getChannel( i );
}
int pixelNumber = 0;
for( int y=0; y<h; y++ ) {
// each scanline starts with filter type byte
// using filter type 0 (no filtering)
int index = y * ( w * 3 + 1 );
// filter type
rawScanlineBytes[ index ] = 0;
index++;
for( int x=0; x<w; x++ ) {
for( i=0; i<3; i++ ) {
rawScanlineBytes[ index ] =
(unsigned char)( 255 * channels[i][pixelNumber] );
index ++;
}
pixelNumber++;
}
}
// zlib block contains
// zlib compression method (1 byte)
// flags (1 byte)
// compressed data blocks
// check value (4 bytes) (not CRC)
SimpleVector<unsigned char> zlibBlock;
// compression method 8 (deflate)
// with a LZ77 window size parameter of w=7
// LZ77 window size is then 2^( w + 8 ), or in this case 32768
zlibBlock.push_back( 0x78 );
// flags
// compression level 0 (2 bytes = 00b)
// no preset dictionary (1 byte = 0b)
// check bits for compression method (5 bits)
// Should be such that if the 8-bit compression method, followed
// by the 8-bit flags field, is viewed as a 16-bit number,
// it is an even multiple of 31
// For our settings, check bits of 00001 works
//zlibBlock.push_back( 0x01 );
// hack: mimic zlib here
zlibBlock.push_back( 0xda );
// now ready for compressed data blocks
int rawDataIndex = 0;
// length field is 16 bits
int maxUncompressedBlockLength = 65535;
while( rawDataIndex < numRawBytes ) {
// push another deflate block
// first bit BFINAL
// only 1 for final block
// next two bits BTYPE
// BTYPE=00 is an uncompressed block
// remaining 5 bits ignored
// Thus, we put 0x00 for uncompressed blocks that are not final
// and 0x80 for final uncompressed block
int bytesLeft = numRawBytes - rawDataIndex;
int bytesInBlock;
if( bytesLeft <= maxUncompressedBlockLength ) {
// final
// hack: when comparing output with zlib, noticed that it doesn't
// set 0x80 for the final block, instead it uses 0x01
// For some reason, this was making the PNG unreadable
// zlibBlock.push_back( 0x80 );
zlibBlock.push_back( 0x01 );
bytesInBlock = bytesLeft;
}
else {
// not final
zlibBlock.push_back( 0x00 );
bytesInBlock = maxUncompressedBlockLength;
}
// length in least-significant-byte-first order
unsigned char firstLengthByte = bytesInBlock & 0xff;
unsigned char secondLengthByte = (bytesInBlock >> 8) & 0xff;
zlibBlock.push_back( firstLengthByte );
zlibBlock.push_back( secondLengthByte );
// those same length bytes inverted
// (called "one's compliment" in the spec
zlibBlock.push_back( firstLengthByte ^ 0xff);
zlibBlock.push_back( secondLengthByte ^ 0xff );
// now the uncompressed data
for( int b=0; b< bytesInBlock; b++ ) {
zlibBlock.push_back( rawScanlineBytes[ rawDataIndex ] );
rawDataIndex++;
}
}
// finally, adler32 of original data
unsigned long adler = updateAdler32( 1L, rawScanlineBytes, numRawBytes );
zlibBlock.push_back( (adler >> 24) & 0xff );
zlibBlock.push_back( (adler >> 16) & 0xff );
zlibBlock.push_back( (adler >> 8) & 0xff );
zlibBlock.push_back( adler & 0xff );
// the zlib block is now complete
/*
// check against real zlib implementation
z_stream zStream;
zStream.next_in = rawScanlineBytes;
zStream.avail_in = numRawBytes;
zStream.total_in = 0;
int outSize = 2 * numRawBytes + 100;
unsigned char *zlibOutBuffer = new unsigned char[ outSize ];
zStream.next_out = zlibOutBuffer;
zStream.avail_out = outSize;
zStream.total_out = 0;
zStream.data_type = Z_BINARY;
zStream.zalloc = Z_NULL;
zStream.zfree = Z_NULL;
// init the stream
// no compression
int result;
//result = deflateInit( &zStream, Z_DEFAULT_COMPRESSION);
result = deflateInit( &zStream, Z_NO_COMPRESSION);
if( result != Z_OK ) {
printf( "zlib deflateInit error: %s\n", zStream.msg );
}
// deflate and flush
result = deflate( &zStream, Z_FINISH );
if( result != Z_STREAM_END ) {
printf( "zlib deflate error (%d): %s\n", result, zStream.msg );
}
printf( "Total in = %d, total out = %d\n",
zStream.total_in, zStream.total_out );
printf( "Our raw bytes (%d):\n", numRawBytes );
int b;
for( b=0; b<numRawBytes; b++ ) {
printf( "%02x ", rawScanlineBytes[ b ] );
}
printf( "\n\nOur zlib bytes (%d) (adler = %d) :\n",
zlibBlock.size(), adler );
for( b=0; b<zlibBlock.size(); b++ ) {
printf( "%02x ", *( zlibBlock.getElement( b ) ) );
}
printf( "\n\nTheir zlib bytes (%d) (adler = %d) :\n",
(int)( zStream.total_out ), zStream.adler );
for( b=0; b<zStream.total_out; b++ ) {
printf( "%02x ", zlibOutBuffer[ b ] );
}
int minBytes = zStream.total_out;
if( minBytes > zlibBlock.size() ) {
minBytes = zlibBlock.size();
}
for( b=0; b<minBytes; b++ ) {
if( zlibOutBuffer[b] != *( zlibBlock.getElement( b ) ) ) {
printf( "mismatch at byte %d\n", b );
printf( "Theirs: %02x, ours: %02x\n", zlibOutBuffer[b],
*( zlibBlock.getElement( b ) ) );
}
}
printf( "\n\n" );
delete [] zlibOutBuffer;
result = deflateEnd( &zStream );
if( result != Z_OK ) {
printf( "zlib deflateEnd error (%d): %s\n", result, zStream.msg );
}
*/
// the zlib block is the data of an IDAT chunk
unsigned char *zlibBytes = zlibBlock.getElementArray();
writeChunk( "IDAT", zlibBytes, zlibBlock.size(), inStream );
delete [] zlibBytes;
// no data in end chunk
writeChunk( "IEND", NULL, 0, inStream );
delete [] rawScanlineBytes;
// done
}
Image *PNGImageConverter::deformatImage( InputStream *inStream ) {
printf( "ERROR: reading PNG images is not supported by "
"PNGImageConverter\n" );
return NULL;
}

View file

@ -0,0 +1,100 @@
/*
* Modification History
*
* 2006-November-21 Jason Rohrer
* Created.
*
* 2010-May-18 Jason Rohrer
* String parameters as const to fix warnings.
*/
#ifndef PNG_IMAGE_CONVERTER_INCLUDED
#define PNG_IMAGE_CONVERTER_INCLUDED
#include "BigEndianImageConverter.h"
/**
* PNG implementation of the image conversion interface.
*
* Note that it only supports 32-bit PNG files
* (3-channel Images are given a solid alpha channel).
*
* @author Jason Rohrer
*/
class PNGImageConverter : public BigEndianImageConverter {
public:
// compression level can be from 0 (none, raw data) to
// 9 (most, slowest).
// 1 is supposedly the "fastest" setting that actually does some
// compression
// 3 to 6 are supposed to be "nearly as good" as 7-9, but "much faster"
//
// Defaults to 5.
PNGImageConverter( int inCompressionLevel=5 );
// implement the ImageConverter interface
virtual void formatImage( Image *inImage,
OutputStream *inStream );
virtual Image *deformatImage( InputStream *inStream );
protected:
int mCompressionLevel;
/**
* Writes a chunk to a stream.
*
* @param inChunkType the 4-char type of the chunk.
* @param inData the data for the chunk. Can be NULL if no data
* in chunk. Destroyed by caller.
* @param inNumBytes the length of inData, or 0 if inData is NULL.
* @param inStream the stream to write the chunk to. Destroyed by
* caller.
*/
void writeChunk( const char inChunkType[4], unsigned char *inData,
unsigned long inNumBytes, OutputStream *inStream );
// precomputed CRCs for all 8-bit messages
unsigned long mCRCTable[256];
const static unsigned long mStartCRC = 0xffffffffL;
/**
* Updates a crc with new data.
*
* Note that starting state for a CRC (before it is updated with data)
* must be mStartCRC.
* After all data has been added to the CRC, the resulting value
* must be inverted (crc ^ 0xffffffffL).
*
* @param inCRC the current crc value.
* @param inData the data. Destroyed by caller.
* @param inLength the length of the data.
*
* @return the new CRC.
*/
unsigned long updateCRC( unsigned long inCRC, unsigned char *inData,
int inLength );
};
#endif

View file

@ -0,0 +1,376 @@
/*
* 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

View file

@ -0,0 +1,90 @@
/*
* Modification History
*
* 2001-February-19 Jason Rohrer
* Created.
*
* 2001-February-24 Jason Rohrer
* Fixed incorrect delete usage.
*
* 2001-April-12 Jason Rohrer
* Changed to comply with new FileInput/OutputStream interface
* (underlying File must be destroyed explicitly).
*/
#include <stdio.h>
#include "minorGems/graphics/Image.h"
#include "BMPImageConverter.h"
#include "minorGems/io/file/File.h"
#include "minorGems/io/file/FileOutputStream.h"
#include "minorGems/io/file/FileInputStream.h"
// test function for the BMPImageConverter class
int main( char inNumArgs, char**inArgs ) {
if( inNumArgs != 2 ) {
printf( "must pass in a file name to write to\n" );
return 1;
}
int length = 0;
while( inArgs[1][length] != '\0' ) {
length++;
}
File *file = new File( NULL, inArgs[1], length );
// read image in
FileInputStream *stream = new FileInputStream( file );
BMPImageConverter *converter = new BMPImageConverter();
Image *image = converter->deformatImage( stream );
if( image != NULL ) {
// write image back out
File *fileOut = new File( NULL, "testOut.bmp", 11 );
FileOutputStream *outStream = new FileOutputStream( fileOut );
converter->formatImage( image, outStream );
delete outStream;
delete image;
}
delete stream;
delete file;
delete converter;
/*
FileOutputStream *stream = new FileOutputStream( file );
BMPImageConverter *converter = new BMPImageConverter();
Image *image = new Image( 256, 256, 3 );
double *red = image->getChannel( 0 );
double *green = image->getChannel( 1 );
for( int y=0; y<image->getHeight(); y++ ) {
for( int x=0; x<image->getWidth(); x++ ) {
long index = y * image->getWidth() + x;
red[index] = (double)y / (double)( image->getHeight() );
green[index] = (double)x / (double)( image->getWidth() );
//red[index] = 1.0;
}
}
converter->formatImage( image, stream );
delete stream;
// delete file explicitly
delete file;
delete converter;
delete image;
*/
return 0;
}

View file

@ -0,0 +1,87 @@
Size
Description
Header
14 bytes
Windows Structure: BITMAPFILEHEADER
Signature
2 bytes
'BM'
FileSize
4 bytes
File size in bytes
reserved
4 bytes
unused (=0)
DataOffset
4 bytes
File offset to Raster Data
InfoHeader
40 bytes
Windows Structure: BITMAPINFOHEADER
Size
4 bytes
Size of InfoHeader =40
Width
4 bytes
Bitmap Width
Height
4 bytes
Bitmap Height
Planes
2 bytes
Number of Planes (=1)
BitCount
2 bytes
Bits per Pixel
1 = monochrome palette. NumColors = 1
4 = 4bit palletized. NumColors = 16
8 = 8bit palletized. NumColors = 256
16 = 16bit RGB. NumColors = 65536 (?)
24 = 24bit RGB. NumColors = 16M
Compression
4 bytes
Type of Compression
0 = BI_RGB no compression
1 = BI_RLE8 8bit RLE encoding
2 = BI_RLE4 4bit RLE encoding
ImageSize
4 bytes
(compressed) Size of Image
It is valid to set this =0 if Compression = 0
XpixelsPerM
4 bytes
horizontal resolution: Pixels/meter
YpixelsPerM
4 bytes
vertical resolution: Pixels/meter
ColorsUsed
4 bytes
Number of actually used colors
ColorsImportant
4 bytes
Number of important colors
0 = all
ColorTable
4 * NumColors bytes
present only if Info.BitsPerPixel <= 8
colors should be ordered by importance
Red
1 byte
Red intensity
Green
1 byte
Green intensity
Blue
1 byte
Blue intensity
reserved
1 byte
unused (=0)
repeated NumColors times
Raster Data
Info.ImageSize bytes
The pixel data

View file

@ -0,0 +1 @@
g++ -g -Wall -o testPNG -I../../.. testPNG.cpp PNGImageConverter.cpp ../../io/file/linux/PathLinux.cpp ../../system/unix/TimeUnix.cpp -lz -lpng

View file

@ -0,0 +1,90 @@
/*
* Modification History
*
* 2001-April-27 Jason Rohrer
* Created.
*
* 2001-April-29 Jason Rohrer
* Completed initial version and used to test JPEGImageConverter
* successfully.
*/
#include <stdio.h>
#include "minorGems/graphics/Image.h"
#include "JPEGImageConverter.h"
#include "minorGems/io/file/File.h"
#include "minorGems/io/file/FileOutputStream.h"
#include "minorGems/io/file/FileInputStream.h"
// test function for the BMPImageConverter class
int main( char inNumArgs, char**inArgs ) {
if( inNumArgs != 2 ) {
printf( "must pass in a file name to write to\n" );
return 1;
}
int length = 0;
while( inArgs[1][length] != '\0' ) {
length++;
}
File *file = new File( NULL, inArgs[1], length );
// read image in
FileInputStream *stream = new FileInputStream( file );
JPEGImageConverter *converter = new JPEGImageConverter( 50 );
Image *image = converter->deformatImage( stream );
if( image != NULL ) {
// write image back out
File *fileOut = new File( NULL, "testOut.jpg", 11 );
FileOutputStream *outStream = new FileOutputStream( fileOut );
converter->formatImage( image, outStream );
delete outStream;
delete fileOut;
delete image;
}
delete stream;
delete converter;
delete file;
/*
FileOutputStream *stream = new FileOutputStream( file );
JPEGImageConverter *converter = new JPEGImageConverteonverter( 50 );
Image *image = new Image( 256, 256, 3 );
double *red = image->getChannel( 0 );
double *green = image->getChannel( 1 );
for( int y=0; y<image->getHeight(); y++ ) {
for( int x=0; x<image->getWidth(); x++ ) {
long index = y * image->getWidth() + x;
red[index] = (double)y / (double)( image->getHeight() );
green[index] = (double)x / (double)( image->getWidth() );
//red[index] = 1.0;
}
}
converter->formatImage( image, stream );
delete stream;
// delete file explicitly
delete file;
delete converter;
delete image;
*/
return 0;
}

View file

@ -0,0 +1 @@
g++ -I../../.. -ljpeg -o jpegConverterTest jpegConverterTest.cpp unix/JPEGImageConverterUnix.cpp ../../io/file/linux/PathLinux.cpp

View file

@ -0,0 +1,108 @@
#include <png.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
void abort_(const char * s, ...)
{
va_list args;
va_start(args, s);
vfprintf(stderr, s, args);
fprintf(stderr, "\n");
va_end(args);
abort();
}
int main() {
int w = 100;
int h = 100;
unsigned int *data = new unsigned int[ w * h ];
// red fades toward bottom
// green fades toward right
unsigned int **rows = new unsigned int *[ h ];
for( int y=0; y<h; y++ ) {
for( int x=0; x<w; x++ ) {
unsigned char red = 255 - ( 255 * y / h );
unsigned char green = 255 - ( 255 * x / h );
data[ y * w + x ] = red | green << 8 | 255 << 24;
}
rows[y] = &( data[ y * w ] );
}
/* create file */
FILE *fp = fopen( "test.png", "wb");
if (!fp)
abort_("[write_png_file] File %s could not be opened for writing",
"test.png" );
png_structp png_ptr;
png_infop info_ptr;
/* initialize stuff */
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png_ptr)
abort_("[write_png_file] png_create_write_struct failed");
info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr)
abort_("[write_png_file] png_create_info_struct failed");
if (setjmp(png_jmpbuf(png_ptr)))
abort_("[write_png_file] Error during init_io");
png_init_io(png_ptr, fp);
/* write header */
if (setjmp(png_jmpbuf(png_ptr)))
abort_("[write_png_file] Error during writing header");
png_set_IHDR(png_ptr, info_ptr, w, h,
8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
png_write_info(png_ptr, info_ptr);
/* write bytes */
if (setjmp(png_jmpbuf(png_ptr)))
abort_("[write_png_file] Error during writing bytes");
png_write_image(png_ptr, (png_byte**)rows);
/* end write */
if (setjmp(png_jmpbuf(png_ptr)))
abort_("[write_png_file] Error during end of write");
png_write_end(png_ptr, NULL);
/* cleanup heap allocation */
/*for (y=0; y<height; y++)
free(row_pointers[y]);
free(row_pointers);
*/
fclose(fp);
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,44 @@
#include "PNGImageConverter.h"
#include "minorGems/graphics/Image.h"
#include "minorGems/io/file/FileOutputStream.h"
#include "minorGems/system/Time.h"
int main() {
int imageSize = 640;
Image testImage( imageSize, imageSize, 3, false );
// red fades toward bottom
// green fades toward right
double *red = testImage.getChannel( 0 );
double *green = testImage.getChannel( 1 );
for( int y=0; y<imageSize; y++ ) {
for( int x=0; x<imageSize; x++ ) {
red[y*imageSize+x] = 1.0 - ( y / (double)imageSize );
green[y*imageSize+x] = 1.0 - ( x / (double)imageSize );
green[y*imageSize+x] = 0;
}
}
PNGImageConverter png;
File outFileB( NULL, "test.png" );
FileOutputStream outStreamB( &outFileB );
double t = Time::getCurrentTime();
png.formatImage( &testImage, &outStreamB );
printf( "Converter took %f seconds\n",
Time::getCurrentTime() - t );
return 0;
}

View file

@ -0,0 +1,563 @@
/*
* Modification History
*
* 2001-April-27 Jason Rohrer
* Created.
*
* 2001-April-29 Jason Rohrer
* Finished implementation.
* Added an optimization to formatImage, but it did not improve
* performance, so it has been commented out.
*/
/**
* Unix-specific JPEGImageConverter implementation
*
* Code for compression and decompression modeled after IJG's
* libjpeg example code.
*
* For now, it use libjpeg to write converted data out to
* file, and then reads it back in.
*/
#include "minorGems/graphics/converters/JPEGImageConverter.h"
#include "minorGems/io/file/File.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// include the jpeg library as a C file.
// (yuk... spent way too much time trying to figure this one out!)
extern "C" {
#include<jpeglib.h>
}
/*
* <setjmp.h> is used for the decompression
* error recovery mechanism.
*/
#include <setjmp.h>
void JPEGImageConverter::formatImage( Image *inImage,
OutputStream *inStream ) {
if( inImage->getNumChannels() != 3 ) {
printf( "JPEGImageConverter only works on 3-channel images.\n" );
return;
}
// most of this code was copied without modification from
// IJG's example.c
// This struct contains the JPEG compression parameters and pointers to
// working space (which is allocated as needed by the JPEG library).
// It is possible to have several such structures, representing multiple
// compression/decompression processes, in existence at once. We refer
// to any one struct (and its associated working data) as a "JPEG object".
struct jpeg_compress_struct cinfo;
// This struct represents a JPEG error handler. It is declared separately
// because applications often want to supply a specialized error handler
// (see the second half of this file for an example). But here we just
// take the easy way out and use the standard error handler, which will
// print a message on stderr and call exit() if compression fails.
// Note that this struct must live as long as the main JPEG parameter
// struct, to avoid dangling-pointer problems.
struct jpeg_error_mgr jerr;
// More stuff
FILE * outfile; // target file
JSAMPROW row_pointer[1]; // pointer to JSAMPLE row[s]
int row_stride; // physical row width in image buffer
// Step 1: allocate and initialize JPEG compression object
// We have to set up the error handler first, in case the initialization
// step fails. (Unlikely, but it could happen if you are out of memory.)
// This routine fills in the contents of struct jerr, and returns jerr's
// address which we place into the link field in cinfo.
cinfo.err = jpeg_std_error( &jerr );
// Now we can initialize the JPEG compression object.
jpeg_create_compress( &cinfo );
// Step 2: specify data destination (eg, a file)
// Note: steps 2 and 3 can be done in either order.
// use a temp file with a random name to make this more
// thread-safe
char *fileName = new char[99];
sprintf( fileName, "temp%d.dat", rand() );
// Here we use the library-supplied code to send compressed data to a
// stdio stream. You can also write your own code to do something else.
// VERY IMPORTANT: use "b" option to fopen() if you are on a machine that
// requires it in order to write binary files.
if( ( outfile = fopen( fileName, "wb" ) ) == NULL ) {
printf( "can't open jpeg conversion temp file %s\n", fileName );
return;
}
jpeg_stdio_dest( &cinfo, outfile );
// Step 3: set parameters for compression
// First we supply a description of the input image.
// Four fields of the cinfo struct must be filled in:
// image width and height, in pixels
cinfo.image_width = inImage->getWidth();
cinfo.image_height = inImage->getHeight();
cinfo.input_components = 3; // # of color components per pixel
cinfo.in_color_space = JCS_RGB; // colorspace of input image
// Now use the library's routine to set default compression parameters.
// (You must set at least cinfo.in_color_space before calling this,
// since the defaults depend on the source color space.)
jpeg_set_defaults( &cinfo );
// Now you can set any non-default parameters you wish to.
// Here we just illustrate the use of
// quality (quantization table) scaling:
jpeg_set_quality( &cinfo, mQuality,
TRUE ); // limit to baseline-JPEG values
// Step 4: Start compressor
// TRUE ensures that we will write a complete interchange-JPEG file.
// Pass TRUE unless you are very sure of what you're doing.
jpeg_start_compress( &cinfo, TRUE );
// Step 5: while (scan lines remain to be written)
// jpeg_write_scanlines(...);
// Here we use the library's state variable cinfo.next_scanline as the
// loop counter, so that we don't have to keep track ourselves.
// To keep things simple, we pass one scanline per call; you can pass
// more if you wish, though.
// JSAMPLEs per row in image_buffer
row_stride = cinfo.image_width * 3;
// channels of inImage, which we will need to pull pixel values out of
double *redChannel = inImage->getChannel(0);
double *greenChannel = inImage->getChannel(1);
double *blueChannel = inImage->getChannel(2);
// array that we will copy inImage pixels into
// one scanline at a time
row_pointer[0] = new JSAMPLE[ row_stride ];
//int rowNumber = 0;
while( cinfo.next_scanline < cinfo.image_height ) {
// jpeg_write_scanlines expects an array of pointers to scanlines.
// Here the array is only one element long, but you could pass
// more than one scanline at a time if that's more convenient.
// make a scanline
int yOffset = cinfo.next_scanline * cinfo.image_width;
// for each pixel in the row
for( int p=0; p<cinfo.image_width; p++ ) {
// index into inImage
int pixelIndex = p + yOffset;
// index into this row
int startRowIndex = p * 3;
// red
row_pointer[0][ startRowIndex ] =
(JSAMPLE)( redChannel[ pixelIndex ] * 255 );
// green
row_pointer[0][ startRowIndex + 1 ] =
(JSAMPLE)( greenChannel[ pixelIndex ] * 255 );
// blue
row_pointer[0][ startRowIndex + 2 ] =
(JSAMPLE)( blueChannel[ pixelIndex ] * 255 );
}
// now pass the scanline into libjpeg
(void) jpeg_write_scanlines( &cinfo, row_pointer, 1 );
//rowNumber++;
}
delete [] ( row_pointer[0] );
// Step 6: Finish compression
jpeg_finish_compress( &cinfo );
// After finish_compress, we can close the output file.
fclose( outfile );
// Step 7: release JPEG compression object
// This is an important step since it
// will release a good deal of memory.
jpeg_destroy_compress( &cinfo );
// now read the compressed data back in from file
File *file = new File( NULL, fileName, strlen( fileName ) );
FILE *inFile = fopen( fileName, "rb" );
if( inFile == NULL ) {
printf( "can't open jpeg conversion temp file %s\n", fileName );
return;
}
// read entire file into memory
int fileLength = file->getLength();
unsigned char *fileBuffer = new unsigned char[ fileLength ];
fread( fileBuffer, 1, fileLength, inFile );
// now write the entire buffer to our output stream
inStream->write( fileBuffer,
fileLength );
delete [] fileBuffer;
delete file;
fclose( inFile );
// delete this temporary file
remove( fileName );
delete [] fileName;
// And we're done!
}
// copied this directly from IJG's example.c
//extern "C" {
/*
* ERROR HANDLING:
*
* The JPEG library's standard error handler (jerror.c) is divided into
* several "methods" which you can override individually. This lets you
* adjust the behavior without duplicating a lot of code, which you might
* have to update with each future release.
*
* Our example here shows how to override the "error_exit" method so that
* control is returned to the library's caller when a fatal error occurs,
* rather than calling exit() as the standard error_exit method does.
*
* We use C's setjmp/longjmp facility to return
* control. This means that the
* routine which calls the JPEG library must
* first execute a setjmp() call to
* establish the return point. We want the replacement error_exit to do a
* longjmp(). But we need to make the setjmp buffer accessible to the
* error_exit routine. To do this, we make a private extension of the
* standard JPEG error handler object. (If we were using C++, we'd say we
* were making a subclass of the regular error handler.)
*
* Here's the extended error handler struct:
*/
struct my_error_mgr {
struct jpeg_error_mgr pub; /* "public" fields */
jmp_buf setjmp_buffer; /* for return to caller */
};
typedef struct my_error_mgr * my_error_ptr;
/*
* Here's the routine that will replace the standard error_exit method:
*/
METHODDEF(void) my_error_exit( j_common_ptr cinfo ) {
/* cinfo->err really points to a my_error_mgr struct,
so coerce pointer
*/
my_error_ptr myerr = (my_error_ptr)( cinfo->err );
/* Always display the message. */
/* We could postpone this until after returning, if we chose. */
(*cinfo->err->output_message)( cinfo );
/* Return control to the setjmp point */
longjmp( myerr->setjmp_buffer, 1 );
}
// }
Image *JPEGImageConverter::deformatImage( InputStream *inStream ) {
// use a temp file with a random name to make this more
// thread-safe
char *fileName = new char[99];
sprintf( fileName, "temp%d.dat", rand() );
FILE *tempFile = fopen( fileName, "wb" );
if( tempFile == NULL ) {
printf( "can't open jpeg conversion temp file %s\n", fileName );
return NULL;
}
// buffer for dumping stream to temp file
unsigned char *tempBuffer = new unsigned char[1];
unsigned char previousByte = 0;
// dump the JPEG stream from the input stream into tempFile
// so that we can pass this file to libjpeg
/*
// optimization: use a buffer to prevent too many fwrite calls
int bufferLength = 5000;
unsigned char *fileBuffer = new unsigned char[ bufferLength ];
int currentBufferPosition = 0;
while( !( tempBuffer[0] == 0xD9 && previousByte == 0xFF ) ) {
previousByte = tempBuffer[0];
inStream->read( tempBuffer, 1 );
fileBuffer[currentBufferPosition] = tempBuffer[0];
if( currentBufferPosition == bufferLength - 1 ) {
// at the end of the file buffer
fwrite( fileBuffer, 1, bufferLength, tempFile );
currentBufferPosition = 0;
}
else {
// keep filling the fileBuffer
currentBufferPosition++;
}
}
// now write remaining fileBuffer data to file
fwrite( fileBuffer, 1, currentBufferPosition + 1, tempFile );
delete [] fileBuffer;
*/
// write until EOI sequence seen (0xFFD9)
while( !( tempBuffer[0] == 0xD9 && previousByte == 0xFF ) ) {
previousByte = tempBuffer[0];
inStream->read( tempBuffer, 1 );
fwrite( tempBuffer, 1, 1, tempFile );
}
// end of jpeg stream reached.
fclose( tempFile );
delete [] tempBuffer;
// the remainder of this method was mostly copied from
// IJG's example.c
/* This struct contains the JPEG decompression parameters and pointers to
* working space (which is allocated as needed by the JPEG library).
*/
struct jpeg_decompress_struct cinfo;
/* We use our private extension JPEG error handler.
* Note that this struct must live as long as the main JPEG parameter
* struct, to avoid dangling-pointer problems.
*/
struct my_error_mgr jerr;
/* More stuff */
FILE * infile; /* source file */
JSAMPARRAY buffer; /* Output row buffer */
int row_stride; /* physical row width in output buffer */
/* In this example we want to open the input
* file before doing anything else,
* so that the setjmp() error recovery below can assume the file is open.
* VERY IMPORTANT: use "b" option to fopen() if you are on a machine that
* requires it in order to read binary files.
*/
if( ( infile = fopen( fileName, "rb" ) ) == NULL ) {
printf( "can't open jpeg conversion temp file %s\n", fileName );
return NULL;
}
/* Step 1: allocate and initialize JPEG decompression object */
/* We set up the normal JPEG error routines, then override error_exit. */
cinfo.err = jpeg_std_error(&jerr.pub);
jerr.pub.error_exit = my_error_exit;
/* Establish the setjmp return context for my_error_exit to use. */
if( setjmp( jerr.setjmp_buffer ) ) {
/* If we get here, the JPEG code has signaled an error.
* We need to clean up the JPEG object,
* close the input file, and return.
*/
jpeg_destroy_decompress( &cinfo );
fclose( infile );
printf( "error in decompressing jpeg from stream.\n" );
return NULL;
}
/* Now we can initialize the JPEG decompression object. */
jpeg_create_decompress( &cinfo );
/* Step 2: specify data source (eg, a file) */
jpeg_stdio_src( &cinfo, infile );
/* Step 3: read file parameters with jpeg_read_header() */
(void) jpeg_read_header( &cinfo, TRUE );
/* We can ignore the return value from jpeg_read_header since
* (a) suspension is not possible with the stdio data source, and
* (b) we passed TRUE to reject a tables-only JPEG file as an error.
* See libjpeg.doc for more info.
*/
/* Step 4: set parameters for decompression */
/* In this example, we don't need to change any of the defaults set by
* jpeg_read_header(), so we do nothing here.
*/
/* Step 5: Start decompressor */
(void) jpeg_start_decompress( &cinfo );
/* We can ignore the return value since suspension is not possible
* with the stdio data source.
*/
/* We may need to do some setup of our own at this point before reading
* the data. After jpeg_start_decompress() we have the correct scaled
* output image dimensions available, as well as the output colormap
* if we asked for color quantization.
* In this example, we need to make an output work buffer of the right size.
*/
/* JSAMPLEs per row in output buffer */
int imageWidth = cinfo.output_width;
int imageHeight = cinfo.output_height;
// the return image with 3 channels
Image *returnImage = new Image( imageWidth, imageHeight, 3, false );
// channels of returnImage,
// which we will need to put pixel values into of
double *redChannel = returnImage->getChannel(0);
double *greenChannel = returnImage->getChannel(1);
double *blueChannel = returnImage->getChannel(2);
int currentIndex = 0;
row_stride = cinfo.output_width * cinfo.output_components;
/* Make a one-row-high sample array that
* will go away when done with image
*/
buffer = ( *cinfo.mem->alloc_sarray )
((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1 );
/* Step 6: while (scan lines remain to be read) */
/* jpeg_read_scanlines(...); */
/* Here we use the library's state variable cinfo.output_scanline as the
* loop counter, so that we don't have to keep track ourselves.
*/
int rowNumber = 0;
double inv255 = 1.0 / 255.0;
while( cinfo.output_scanline < cinfo.output_height ) {
/* jpeg_read_scanlines expects an array of pointers to scanlines.
* Here the array is only one element long, but you could ask for
* more than one scanline at a time if that's more convenient.
*/
(void) jpeg_read_scanlines( &cinfo, buffer, 1 );
// write the scanline into returnImage
int yOffset = rowNumber * cinfo.output_width;
// for each pixel in the row
// copy it into the return image channels
for( int p=0; p<cinfo.output_width; p++ ) {
// index into inImage
int pixelIndex = p + yOffset;
// index into this row
int startRowIndex = p * 3;
// red
redChannel[ pixelIndex ] =
buffer[0][ startRowIndex ] * inv255;
// green
greenChannel[ pixelIndex ] =
buffer[0][ startRowIndex + 1 ] * inv255;
// blue
blueChannel[ pixelIndex ] =
buffer[0][ startRowIndex + 2 ] * inv255;
}
rowNumber++;
}
/* Step 7: Finish decompression */
(void) jpeg_finish_decompress( &cinfo );
/* We can ignore the return value since suspension is not possible
* with the stdio data source.
*/
/* Step 8: Release JPEG decompression object */
/* This is an important step since it will
* release a good deal of memory.
*/
jpeg_destroy_decompress( &cinfo );
/* After finish_decompress, we can close the input file.
* Here we postpone it until after no more JPEG errors are possible,
* so as to simplify the setjmp error logic above. (Actually, I don't
* think that jpeg_destroy can do an error exit,
* but why assume anything...)
*/
fclose( infile );
/* At this point you may want to check to see whether any corrupt-data
* warnings occurred (test whether jerr.pub.num_warnings is nonzero).
*/
// delete this temporary file
remove( fileName );
delete [] fileName;
/* And we're done! */
return returnImage;
}