STB used for loading and writing images.
This commit is contained in:
marauder2k7 2023-11-30 09:09:13 +00:00
parent d671f2611e
commit 108ba34c08
21 changed files with 20355 additions and 1737 deletions

View file

@ -573,7 +573,7 @@ void DDSFile::SurfaceData::dumpImage(DDSFile *dds, U32 mip, const char *file)
if ( stream.getStatus() == Stream::Ok )
{
// Write it out.
foo->writeBitmap("png", stream);
foo->writeBitmap("png", file);
}
// Clean up.

View file

@ -1192,7 +1192,7 @@ bool GBitmap::write(Stream& io_rStream) const
//-------------------------------------- Persistent I/O
//
bool GBitmap::readBitmap( const String &bmType, Stream &ioStream )
bool GBitmap::readBitmap(const String& bmType, const Torque::Path& path)
{
PROFILE_SCOPE(ResourceGBitmap_readBitmap);
const GBitmap::Registration *regInfo = GBitmap::sFindRegInfo( bmType );
@ -1203,10 +1203,10 @@ bool GBitmap::readBitmap( const String &bmType, Stream &ioStream )
return false;
}
return regInfo->readFunc( ioStream, this );
return regInfo->readFunc(path, this);
}
bool GBitmap::writeBitmap( const String &bmType, Stream &ioStream, U32 compressionLevel )
bool GBitmap::writeBitmap( const String &bmType, const Torque::Path& path, U32 compressionLevel )
{
const GBitmap::Registration *regInfo = GBitmap::sFindRegInfo( bmType );
@ -1216,7 +1216,7 @@ bool GBitmap::writeBitmap( const String &bmType, Stream &ioStream, U32 compress
return false;
}
return regInfo->writeFunc( this, ioStream, (compressionLevel == U32_MAX) ? regInfo->defaultCompression : compressionLevel );
return regInfo->writeFunc(path, this, (compressionLevel == U32_MAX) ? regInfo->defaultCompression : compressionLevel );
}
template<> void *Resource<GBitmap>::create(const Torque::Path &path)
@ -1239,7 +1239,7 @@ template<> void *Resource<GBitmap>::create(const Torque::Path &path)
GBitmap *bmp = new GBitmap;
const String extension = path.getExtension();
if( !bmp->readBitmap( extension, stream ) )
if( !bmp->readBitmap( extension, path ) )
{
Con::errorf( "Resource<GBitmap>::create - error reading '%s'", path.getFullPath().c_str() );
delete bmp;
@ -1441,7 +1441,7 @@ DefineEngineFunction(saveScaledImage, bool, (const char* bitmapSource, const cha
}
else
{
image->writeBitmap("png", fs);
image->writeBitmap("png", destinationPath.getFullPath());
fs.close();
delete image;

View file

@ -71,10 +71,10 @@ public:
struct Registration
{
/// The read function prototype.
typedef bool(*ReadFunc)(Stream &stream, GBitmap *bitmap);
typedef bool(*ReadFunc)(const Torque::Path& path, GBitmap* bitmap);
/// The write function prototype. Compression levels are image-specific - see their registration declaration for details.
typedef bool(*WriteFunc)(GBitmap *bitmap, Stream &stream, U32 compressionLevel);
typedef bool(*WriteFunc)(const Torque::Path& path, GBitmap* bitmap, U32 compressionLevel);
/// Used to sort the registrations so that
/// lookups occur in a fixed order.
@ -241,13 +241,13 @@ public:
/// Read a bitmap from a stream
/// @param bmType This is a file extension to describe the type of the data [i.e. "png" for PNG file, etc]
/// @param ioStream The stream to read from
bool readBitmap( const String &bmType, Stream &ioStream );
bool readBitmap(const String& bmType, const Torque::Path& path);
/// Write a bitmap to a stream
/// @param bmType This is a file extension to describe the type of the data [i.e. "png" for PNG file, etc]
/// @param ioStream The stream to read from
/// @param compressionLevel Image format-specific compression level. If set to U32_MAX, we use the default compression defined when the format was registered.
bool writeBitmap( const String &bmType, Stream &ioStream, U32 compressionLevel = U32_MAX );
bool writeBitmap( const String &bmType, const Torque::Path& path, U32 compressionLevel = U32_MAX );
bool readMNG(Stream& io_rStream); // located in bitmapMng.cc
bool writeMNG(Stream& io_rStream) const;

View file

@ -1,246 +0,0 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "core/stream/stream.h"
#include "gfx/bitmap/gBitmap.h"
static bool sReadBMP(Stream &stream, GBitmap *bitmap);
static bool sWriteBMP(GBitmap *bitmap, Stream &stream, U32 compressionLevel);
static struct _privateRegisterBMP
{
_privateRegisterBMP()
{
GBitmap::Registration reg;
reg.extensions.push_back( "bmp" );
reg.readFunc = sReadBMP;
reg.writeFunc = sWriteBMP;
GBitmap::sRegisterFormat( reg );
}
} sStaticRegisterBMP;
// structures mirror those defined by the win32 API
struct RGBQUAD
{
U8 rgbBlue;
U8 rgbGreen;
U8 rgbRed;
U8 rgbReserved;
};
struct BITMAPFILEHEADER
{
U16 bfType;
U32 bfSize;
U16 bfReserved1;
U16 bfReserved2;
U32 bfOffBits;
};
struct BITMAPINFOHEADER
{
U32 biSize;
S32 biWidth;
S32 biHeight;
U16 biPlanes;
U16 biBitCount;
U32 biCompression;
U32 biSizeImage;
S32 biXPelsPerMeter;
S32 biYPelsPerMeter;
U32 biClrUsed;
U32 biClrImportant;
};
// constants for the biCompression field
#define BI_RGB 0L
#define BI_RLE8 1L
#define BI_RLE4 2L
#define BI_BITFIELDS 3L
//------------------------------------------------------------------------------
//-------------------------------------- Supplementary I/O (Partially located in
// bitmapPng.cc)
//
static bool sReadBMP(Stream &stream, GBitmap *bitmap)
{
PROFILE_SCOPE(sReadBMP);
BITMAPINFOHEADER bi;
BITMAPFILEHEADER bf;
RGBQUAD rgb[256];
stream.read(&bf.bfType);
stream.read(&bf.bfSize);
stream.read(&bf.bfReserved1);
stream.read(&bf.bfReserved2);
stream.read(&bf.bfOffBits);
stream.read(&bi.biSize);
stream.read(&bi.biWidth);
stream.read(&bi.biHeight);
stream.read(&bi.biPlanes);
stream.read(&bi.biBitCount);
stream.read(&bi.biCompression);
stream.read(&bi.biSizeImage);
stream.read(&bi.biXPelsPerMeter);
stream.read(&bi.biYPelsPerMeter);
stream.read(&bi.biClrUsed);
stream.read(&bi.biClrImportant);
GFXFormat fmt = GFXFormatR8G8B8;
if(bi.biBitCount == 8)
{
// read in texture palette
if(!bi.biClrUsed)
bi.biClrUsed = 256;
stream.read(sizeof(RGBQUAD) * bi.biClrUsed, rgb);
}
bitmap->allocateBitmap(bi.biWidth, bi.biHeight, false, fmt);
U32 width = bitmap->getWidth();
U32 height = bitmap->getHeight();
U32 bytesPerPixel = bitmap->getBytesPerPixel();
for(U32 i = 0; i < bi.biHeight; i++)
{
U8 *rowDest = bitmap->getAddress(0, height - i - 1);
if (bi.biBitCount == 8)
{
// use palette...don't worry about being slow
for (S32 j=0; j<width; j++)
{
U8 palIdx;
stream.read(&palIdx);
U8 * pixelLocation = &rowDest[j*bytesPerPixel];
pixelLocation[0] = rgb[palIdx].rgbRed;
pixelLocation[1] = rgb[palIdx].rgbGreen;
pixelLocation[2] = rgb[palIdx].rgbBlue;
if (bytesPerPixel==3)
pixelLocation[3] = 255;
}
}
else
stream.read(bytesPerPixel * width, rowDest);
}
if(bytesPerPixel == 3 && bi.biBitCount != 8) // do BGR swap
{
U8 *ptr = bitmap->getAddress(0,0);
for(S32 i = 0; i < width * height; i++)
{
U8 tmp = ptr[0];
ptr[0] = ptr[2];
ptr[2] = tmp;
ptr += 3;
}
}
// We know BMP's don't have any transparency
bitmap->setHasTransparency(false);
return true;
}
static bool sWriteBMP(GBitmap *bitmap, Stream &stream, U32 compressionLevel)
{
TORQUE_UNUSED( compressionLevel ); // BMP does not use compression
BITMAPINFOHEADER bi;
BITMAPFILEHEADER bf;
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = bitmap->getWidth();
bi.biHeight = bitmap->getHeight(); //our data is top-down
bi.biPlanes = 1;
if(bitmap->getFormat() == GFXFormatR8G8B8)
{
bi.biBitCount = 24;
bi.biCompression = BI_RGB;
bi.biClrUsed = 0;
}
else
{
bi.biBitCount = 0;
bi.biCompression = BI_RGB; // Removes warning C4701 on line
AssertISV(false, "GBitmap::writeMSBmp - only support R8G8B8 formats!");
}
U32 width = bitmap->getWidth();
U32 height = bitmap->getHeight();
U32 bytesPP = bi.biBitCount >> 3;
bi.biSizeImage = width * height * bytesPP;
bi.biXPelsPerMeter = 0;
bi.biYPelsPerMeter = 0;
bi.biClrUsed = 0;
bi.biClrImportant = 0;
bf.bfType = makeFourCCTag('B','M',0,0); //Type of file 'BM'
bf.bfOffBits= sizeof(BITMAPINFOHEADER)
+ sizeof(BITMAPFILEHEADER)
+ (sizeof(RGBQUAD)*bi.biClrUsed);
bf.bfSize = bf.bfOffBits + bi.biSizeImage;
bf.bfReserved1 = 0;
bf.bfReserved2 = 0;
stream.write(bf.bfType);
stream.write(bf.bfSize);
stream.write(bf.bfReserved1);
stream.write(bf.bfReserved2);
stream.write(bf.bfOffBits);
stream.write(bi.biSize);
stream.write(bi.biWidth);
stream.write(bi.biHeight);
stream.write(bi.biPlanes);
stream.write(bi.biBitCount);
stream.write(bi.biCompression);
stream.write(bi.biSizeImage);
stream.write(bi.biXPelsPerMeter);
stream.write(bi.biYPelsPerMeter);
stream.write(bi.biClrUsed);
stream.write(bi.biClrImportant);
//write the bitmap bits
U8* pMSUpsideDownBits = new U8[bi.biSizeImage];
for (U32 i = 0; i < height; i++)
{
const U8* pSrc = bitmap->getAddress(0, i);
U8* pDst = pMSUpsideDownBits + (height - i - 1) * width * bytesPP;
dMemcpy(pDst, pSrc, width * bytesPP);
}
stream.write(bi.biSizeImage, pMSUpsideDownBits);
delete [] pMSUpsideDownBits;
return stream.getStatus() == Stream::Ok;
}

View file

@ -1,251 +0,0 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "ljpeg/jpeglib.h"
#include "core/stream/stream.h"
#include "gfx/bitmap/gBitmap.h"
static bool sReadJPG(Stream &stream, GBitmap *bitmap);
static bool sWriteJPG(GBitmap *bitmap, Stream &stream, U32 compressionLevel);
static struct _privateRegisterJPG
{
_privateRegisterJPG()
{
GBitmap::Registration reg;
reg.priority = 50;
reg.extensions.push_back( "jpeg" );
reg.extensions.push_back( "jpg" );
reg.readFunc = sReadJPG;
reg.writeFunc = sWriteJPG;
GBitmap::sRegisterFormat( reg );
}
} sStaticRegisterJPG;
//-------------------------------------- Replacement I/O for standard LIBjpeg
// functions. we don't wanna use
// FILE*'s...
static S32 jpegReadDataFn(void *client_data, U8 *data, S32 length)
{
Stream *stream = (Stream*)client_data;
AssertFatal(stream != NULL, "jpegReadDataFn::No stream.");
S32 pos = stream->getPosition();
if (stream->read(length, data))
return length;
if (stream->getStatus() == Stream::EOS)
return (stream->getPosition()-pos);
else
return 0;
}
//--------------------------------------
static S32 jpegWriteDataFn(void *client_data, U8 *data, S32 length)
{
Stream *stream = (Stream*)client_data;
AssertFatal(stream != NULL, "jpegWriteDataFn::No stream.");
if (stream->write(length, data))
return length;
else
return 0;
}
//--------------------------------------
static S32 jpegFlushDataFn(void *)
{
// do nothing since we can't flush the stream object
return 0;
}
//--------------------------------------
static S32 jpegErrorFn(void *client_data)
{
Stream *stream = (Stream*)client_data;
AssertFatal(stream != NULL, "jpegErrorFn::No stream.");
return (stream->getStatus() != Stream::Ok);
}
//--------------------------------------
static bool sReadJPG(Stream &stream, GBitmap *bitmap)
{
PROFILE_SCOPE(sReadJPG);
JFREAD = jpegReadDataFn;
JFERROR = jpegErrorFn;
jpeg_decompress_struct cinfo;
jpeg_error_mgr jerr;
// 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;
// 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);
// return false;
// }
cinfo.err = jpeg_std_error(&jerr); // set up the normal JPEG error routines.
cinfo.client_data = (void*)&stream; // set the stream into the client_data
// Now we can initialize the JPEG decompression object.
jpeg_create_decompress(&cinfo);
jpeg_stdio_src(&cinfo);
// Read file header, set default decompression parameters
jpeg_read_header(&cinfo, true);
GFXFormat format;
switch (cinfo.out_color_space)
{
case JCS_GRAYSCALE: format = GFXFormatA8; break;
case JCS_RGB: format = GFXFormatR8G8B8; break;
default:
jpeg_destroy_decompress(&cinfo);
return false;
}
// Start decompressor
jpeg_start_decompress(&cinfo);
// allocate the bitmap space and init internal variables...
bitmap->allocateBitmap(cinfo.output_width, cinfo.output_height, false, format);
// Set up the row pointers...
U32 rowBytes = cinfo.output_width * cinfo.output_components;
U8* pBase = (U8*)bitmap->getBits();
for (U32 i = 0; i < bitmap->getHeight(); i++)
{
JSAMPROW rowPointer = pBase + (U64)(i * rowBytes);
jpeg_read_scanlines(&cinfo, &rowPointer, 1);
}
// Finish decompression
jpeg_finish_decompress(&cinfo);
// Release JPEG decompression object
// This is an important step since it will release a good deal of memory.
jpeg_destroy_decompress(&cinfo);
// We know JPEG's don't have any transparency
bitmap->setHasTransparency(false);
return true;
}
//--------------------------------------------------------------------------
static bool sWriteJPG(GBitmap *bitmap, Stream &stream, U32 compressionLevel)
{
TORQUE_UNUSED(compressionLevel); // compression level not currently hooked up
GFXFormat format = bitmap->getFormat();
// JPEG format does not support transparency so any image
// in Alpha format should be saved as a grayscale which coincides
// with how the readJPEG function will read-in a JPEG. So the
// only formats supported are RGB and Alpha, not RGBA.
AssertFatal(format == GFXFormatR8G8B8 || format == GFXFormatA8,
"GBitmap::writeJPEG: ONLY RGB bitmap writing supported at this time.");
if (format != GFXFormatR8G8B8 && format != GFXFormatA8)
return false;
// maximum image size allowed
#define MAX_HEIGHT 4096
if (bitmap->getHeight() > MAX_HEIGHT)
return false;
// Bind our own stream writing, error, and memory flush functions
// to the jpeg library interface
JFWRITE = jpegWriteDataFn;
JFFLUSH = jpegFlushDataFn;
JFERROR = jpegErrorFn;
// Allocate and initialize our jpeg compression structure and error manager
jpeg_compress_struct cinfo;
jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr); // set up the normal JPEG error routines.
cinfo.client_data = (void*)&stream; // set the stream into the client_data
jpeg_create_compress(&cinfo); // allocates a small amount of memory
// specify the destination for the compressed data(our stream)
jpeg_stdio_dest(&cinfo);
// set the image properties
cinfo.image_width = bitmap->getWidth(); // image width
cinfo.image_height = bitmap->getHeight(); // image height
cinfo.input_components = bitmap->getBytesPerPixel(); // samples per pixel(RGB:3, Alpha:1)
switch (format)
{
case GFXFormatA8: // no alpha support in JPEG format, so turn it into a grayscale
cinfo.in_color_space = JCS_GRAYSCALE;
break;
case GFXFormatR8G8B8: // otherwise we are writing in RGB format
cinfo.in_color_space = JCS_RGB;
break;
default:
AssertFatal( false, "Format not handled in GBitmap::writeJPEG() switch" );
break;
}
// use default compression params(75% compression)
jpeg_set_defaults(&cinfo);
// begin JPEG compression cycle
jpeg_start_compress(&cinfo, true);
// Set up the row pointers...
U32 rowBytes = cinfo.image_width * cinfo.input_components;
U8* pBase = (U8*)bitmap->getBits();
for (U32 i = 0; i < bitmap->getHeight(); i++)
{
// write the image data
JSAMPROW rowPointer = pBase + (i * rowBytes);
jpeg_write_scanlines(&cinfo, &rowPointer, 1);
}
// complete the compression cycle
jpeg_finish_compress(&cinfo);
// release the JPEG compression object
jpeg_destroy_compress(&cinfo);
// return success
return true;
}

View file

@ -1,645 +0,0 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "core/stream/fileStream.h"
#include "core/stream/memStream.h"
#include "core/strings/stringFunctions.h"
#include "gfx/bitmap/gBitmap.h"
#include "gfx/bitmap/pngUtils.h"
#define PNG_INTERNAL 1
#include <time.h>
#include "lpng/png.h"
#include "zlib/zlib.h"
#ifdef NULL
#undef NULL
#define NULL 0
#endif
static bool sReadPNG(Stream &stream, GBitmap *bitmap);
/// Compression levels for PNGs range from 0-9.
/// A value outside that range will cause the write routine to look for the best compression for a given PNG. This can be slow.
static bool sWritePNG(GBitmap *bitmap, Stream &stream, U32 compressionLevel);
static bool _writePNG(GBitmap *bitmap, Stream &stream, U32 compressionLevel, U32 strategy, U32 filter);
static struct _privateRegisterPNG
{
_privateRegisterPNG()
{
GBitmap::Registration reg;
reg.priority = 100;
reg.extensions.push_back( "png" );
reg.readFunc = sReadPNG;
reg.writeFunc = sWritePNG;
reg.defaultCompression = 6;
GBitmap::sRegisterFormat( reg );
}
} sStaticRegisterPNG;
//-------------------------------------- Replacement I/O for standard LIBPng
// functions. we don't wanna use
// FILE*'s...
static void pngReadDataFn(png_structp png_ptr,
png_bytep data,
png_size_t length)
{
AssertFatal(png_get_io_ptr(png_ptr) != NULL, "No stream?");
Stream *strm = (Stream*)png_get_io_ptr(png_ptr);
bool success = strm->read((U32)length, data);
AssertFatal(success, "pngReadDataFn - failed to read from stream!");
}
//--------------------------------------
static void pngWriteDataFn(png_structp png_ptr,
png_bytep data,
png_size_t length)
{
AssertFatal(png_get_io_ptr(png_ptr) != NULL, "No stream?");
Stream *strm = (Stream*)png_get_io_ptr(png_ptr);
bool success = strm->write((U32)length, data);
AssertFatal(success, "pngWriteDataFn - failed to write to stream!");
}
//--------------------------------------
static void pngFlushDataFn(png_structp /*png_ptr*/)
{
//
}
static png_voidp pngMallocFn(png_structp /*png_ptr*/, png_size_t size)
{
return FrameAllocator::alloc((U32)size);
}
static void pngFreeFn(png_structp /*png_ptr*/, png_voidp /*mem*/)
{
}
static png_voidp pngRealMallocFn(png_structp /*png_ptr*/, png_size_t size)
{
return (png_voidp)dMalloc(size);
}
static void pngRealFreeFn(png_structp /*png_ptr*/, png_voidp mem)
{
dFree(mem);
}
//--------------------------------------
static void pngFatalErrorFn(png_structp /*png_ptr*/,
png_const_charp pMessage)
{
AssertISV(false, avar("Error reading PNG file:\n %s", pMessage));
}
//--------------------------------------
static void pngWarningFn(png_structp, png_const_charp /*pMessage*/)
{
// AssertWarn(false, avar("Warning reading PNG file:\n %s", pMessage));
}
//--------------------------------------
static bool sReadPNG(Stream &stream, GBitmap *bitmap)
{
PROFILE_SCOPE(sReadPNG);
static const U32 cs_headerBytesChecked = 8;
U8 header[cs_headerBytesChecked];
stream.read(cs_headerBytesChecked, header);
bool isPng = png_check_sig(header, cs_headerBytesChecked) != 0;
if (!isPng)
{
AssertWarn(false, "GBitmap::readPNG: stream doesn't contain a PNG");
return false;
}
U32 prevWaterMark = FrameAllocator::getWaterMark();
png_structp png_ptr = png_create_read_struct_2(PNG_LIBPNG_VER_STRING,
NULL,
pngFatalErrorFn,
pngWarningFn,
NULL,
pngRealMallocFn,
pngRealFreeFn);
if (png_ptr == NULL)
{
FrameAllocator::setWaterMark(prevWaterMark);
return false;
}
png_infop info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == NULL)
{
png_destroy_read_struct(&png_ptr,
(png_infopp)NULL,
(png_infopp)NULL);
FrameAllocator::setWaterMark(prevWaterMark);
return false;
}
png_infop end_info = png_create_info_struct(png_ptr);
if (end_info == NULL)
{
png_destroy_read_struct(&png_ptr,
&info_ptr,
(png_infopp)NULL);
FrameAllocator::setWaterMark(prevWaterMark);
return false;
}
png_set_read_fn(png_ptr, &stream, pngReadDataFn);
// Read off the info on the image.
png_set_sig_bytes(png_ptr, cs_headerBytesChecked);
png_read_info(png_ptr, info_ptr);
// OK, at this point, if we have reached it ok, then we can reset the
// image to accept the new data...
//
bitmap->deleteImage();
png_uint_32 width;
png_uint_32 height;
S32 bit_depth;
S32 color_type;
png_get_IHDR(png_ptr, info_ptr,
&width, &height, // obv.
&bit_depth, &color_type, // obv.
NULL, // interlace
NULL, // compression_type
NULL); // filter_type
// First, handle the color transformations. We need this to read in the
// data as RGB or RGBA, _always_, with a maximal channel width of 8 bits.
//
bool transAlpha = false;
GFXFormat format = GFXFormatR8G8B8;
// Strip off any 16 bit info
//
if (bit_depth == 16 && color_type != PNG_COLOR_TYPE_GRAY)
{
png_set_strip_16(png_ptr);
}
// Expand a transparency channel into a full alpha channel...
//
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
{
png_set_expand(png_ptr);
transAlpha = true;
}
if (color_type == PNG_COLOR_TYPE_PALETTE)
{
png_set_expand(png_ptr);
format = transAlpha ? GFXFormatR8G8B8A8 : GFXFormatR8G8B8;
}
else if (color_type == PNG_COLOR_TYPE_GRAY)
{
png_set_expand(png_ptr);
if (bit_depth == 16)
format = GFXFormatL16;
else
format = GFXFormatA8;
}
else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
{
png_set_expand(png_ptr);
png_set_gray_to_rgb(png_ptr);
format = GFXFormatR8G8B8A8;
}
else if (color_type == PNG_COLOR_TYPE_RGB)
{
format = transAlpha ? GFXFormatR8G8B8A8 : GFXFormatR8G8B8;
png_set_expand(png_ptr);
}
else if (color_type == PNG_COLOR_TYPE_RGB_ALPHA)
{
png_set_expand(png_ptr);
format = GFXFormatR8G8B8A8;
}
// Update the info pointer with the result of the transformations
// above...
png_read_update_info(png_ptr, info_ptr);
png_uint_32 rowBytes = (png_uint_32)png_get_rowbytes(png_ptr, info_ptr);
if (format == GFXFormatR8G8B8)
{
AssertFatal(rowBytes == width * 3,
"Error, our rowbytes are incorrect for this transform... (3)");
}
else if (format == GFXFormatR8G8B8A8)
{
AssertFatal(rowBytes == width * 4,
"Error, our rowbytes are incorrect for this transform... (4)");
}
else if (format == GFXFormatL16)
{
AssertFatal(rowBytes == width * 2,
"Error, our rowbytes are incorrect for this transform... (2)");
}
// actually allocate the bitmap space...
bitmap->allocateBitmap(width, height,
false, // don't extrude miplevels...
format); // use determined format...
// Set up the row pointers...
png_bytep* rowPointers = new png_bytep[ height ];
U8* pBase = (U8*)bitmap->getBits();
for (U32 i = 0; i < height; i++)
rowPointers[i] = pBase + (i * rowBytes);
// And actually read the image!
png_read_image(png_ptr, rowPointers);
// We're outta here, destroy the png structs, and release the lock
// as quickly as possible...
//png_read_end(png_ptr, end_info);
delete [] rowPointers;
png_read_end(png_ptr, NULL);
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
// Ok, the image is read in, now we need to finish up the initialization,
// which means: setting up the detailing members, init'ing the palette
// key, etc...
//
// actually, all of that was handled by allocateBitmap, so we're outta here
//
// Check this bitmap for transparency
bitmap->checkForTransparency();
FrameAllocator::setWaterMark(prevWaterMark);
return true;
}
//--------------------------------------------------------------------------
static bool _writePNG(GBitmap *bitmap, Stream &stream, U32 compressionLevel, U32 strategy, U32 filter)
{
GFXFormat format = bitmap->getFormat();
// ONLY RGB bitmap writing supported at this time!
AssertFatal( format == GFXFormatR8G8B8 ||
format == GFXFormatR8G8B8A8 ||
format == GFXFormatR8G8B8X8 ||
format == GFXFormatA8 ||
format == GFXFormatR5G6B5 ||
format == GFXFormatR8G8B8A8_LINEAR_FORCE, "_writePNG: ONLY RGB bitmap writing supported at this time.");
if ( format != GFXFormatR8G8B8 &&
format != GFXFormatR8G8B8A8 &&
format != GFXFormatR8G8B8X8 &&
format != GFXFormatA8 &&
format != GFXFormatR5G6B5 && format != GFXFormatR8G8B8A8_LINEAR_FORCE)
return false;
png_structp png_ptr = png_create_write_struct_2(PNG_LIBPNG_VER_STRING,
NULL,
pngFatalErrorFn,
pngWarningFn,
NULL,
pngMallocFn,
pngFreeFn);
if (png_ptr == NULL)
return (false);
png_infop info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == NULL)
{
png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
return false;
}
png_set_write_fn(png_ptr, &stream, pngWriteDataFn, pngFlushDataFn);
// Set the compression level and image filters
png_set_compression_window_bits(png_ptr, 15);
png_set_compression_level(png_ptr, compressionLevel);
png_set_filter(png_ptr, 0, filter);
// Set the image information here. Width and height are up to 2^31,
// bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
// the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
// PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
// or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
// PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
// currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
U32 width = bitmap->getWidth();
U32 height = bitmap->getHeight();
if (format == GFXFormatR8G8B8)
{
png_set_IHDR(png_ptr, info_ptr,
width, height, // the width & height
8, PNG_COLOR_TYPE_RGB, // bit_depth, color_type,
NULL, // no interlace
NULL, // compression type
NULL); // filter type
}
else if (format == GFXFormatR8G8B8A8 || format == GFXFormatR8G8B8X8 || format == GFXFormatR8G8B8A8_LINEAR_FORCE)
{
png_set_IHDR(png_ptr, info_ptr,
width, height, // the width & height
8, PNG_COLOR_TYPE_RGB_ALPHA, // bit_depth, color_type,
NULL, // no interlace
NULL, // compression type
NULL); // filter type
}
else if (format == GFXFormatA8)
{
png_set_IHDR(png_ptr, info_ptr,
width, height, // the width & height
8, PNG_COLOR_TYPE_GRAY, // bit_depth, color_type,
NULL, // no interlace
NULL, // compression type
NULL); // filter type
}
else if (format == GFXFormatR5G6B5)
{
png_set_IHDR(png_ptr, info_ptr,
width, height, // the width & height
16, PNG_COLOR_TYPE_GRAY, // bit_depth, color_type,
PNG_INTERLACE_NONE, // no interlace
PNG_COMPRESSION_TYPE_DEFAULT, // compression type
PNG_FILTER_TYPE_DEFAULT); // filter type
png_color_8_struct sigBit = { 0 };
sigBit.gray = 16;
png_set_sBIT(png_ptr, info_ptr, &sigBit );
png_set_swap( png_ptr );
}
png_write_info(png_ptr, info_ptr);
FrameAllocatorMarker marker;
png_bytep* row_pointers = (png_bytep*)marker.alloc( height * sizeof( png_bytep ) );
for (U32 i=0; i<height; i++)
row_pointers[i] = const_cast<png_bytep>(bitmap->getAddress(0, i));
png_write_image(png_ptr, row_pointers);
// Write S3TC data if present...
// Write FXT1 data if present...
png_write_end(png_ptr, info_ptr);
png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
return true;
}
//--------------------------------------------------------------------------
static bool sWritePNG(GBitmap *bitmap, Stream &stream, U32 compressionLevel)
{
U32 waterMark = FrameAllocator::getWaterMark();
if ( compressionLevel < 10 )
{
bool retVal = _writePNG(bitmap, stream, compressionLevel, 0, PNG_ALL_FILTERS);
FrameAllocator::setWaterMark(waterMark);
return retVal;
}
// check all our methods of compression to find the best one and use it
U8* buffer = new U8[1 << 22]; // 4 Megs. Should be enough...
MemStream* pMemStream = new MemStream(1 << 22, buffer, false, true);
const U32 zStrategies[] = { Z_DEFAULT_STRATEGY,
Z_FILTERED };
const U32 pngFilters[] = { PNG_FILTER_NONE,
PNG_FILTER_SUB,
PNG_FILTER_UP,
PNG_FILTER_AVG,
PNG_FILTER_PAETH,
PNG_ALL_FILTERS };
U32 minSize = 0xFFFFFFFF;
U32 bestStrategy = 0xFFFFFFFF;
U32 bestFilter = 0xFFFFFFFF;
U32 bestCLevel = 0xFFFFFFFF;
for (U32 cl = 0; cl <=9; cl++)
{
for (U32 zs = 0; zs < 2; zs++)
{
for (U32 pf = 0; pf < 6; pf++)
{
pMemStream->setPosition(0);
U32 waterMarkInner = FrameAllocator::getWaterMark();
if (_writePNG(bitmap, *pMemStream, cl, zStrategies[zs], pngFilters[pf]) == false)
AssertFatal(false, "Handle this error!");
FrameAllocator::setWaterMark(waterMarkInner);
if (pMemStream->getPosition() < minSize)
{
minSize = pMemStream->getPosition();
bestStrategy = zs;
bestFilter = pf;
bestCLevel = cl;
}
}
}
}
AssertFatal(minSize != 0xFFFFFFFF, "Error, no best found?");
delete pMemStream;
delete [] buffer;
bool retVal = _writePNG(bitmap, stream,
bestCLevel,
zStrategies[bestStrategy],
pngFilters[bestFilter]);
FrameAllocator::setWaterMark(waterMark);
return retVal;
}
//--------------------------------------------------------------------------
// Stores PNG stream data
struct DeferredPNGWriterData {
png_structp png_ptr;
png_infop info_ptr;
U32 width;
U32 height;
};
DeferredPNGWriter::DeferredPNGWriter() :
mData( NULL ),
mActive(false)
{
mData = new DeferredPNGWriterData();
}
DeferredPNGWriter::~DeferredPNGWriter()
{
delete mData;
}
bool DeferredPNGWriter::begin( GFXFormat format, S32 width, S32 height, Stream &stream, U32 compressionLevel )
{
// ONLY RGB bitmap writing supported at this time!
AssertFatal( format == GFXFormatR8G8B8 ||
format == GFXFormatR8G8B8A8 ||
format == GFXFormatR8G8B8X8 ||
format == GFXFormatA8 ||
format == GFXFormatR5G6B5, "_writePNG: ONLY RGB bitmap writing supported at this time.");
if ( format != GFXFormatR8G8B8 &&
format != GFXFormatR8G8B8A8 &&
format != GFXFormatR8G8B8X8 &&
format != GFXFormatA8 &&
format != GFXFormatR5G6B5 )
return false;
mData->png_ptr = png_create_write_struct_2(PNG_LIBPNG_VER_STRING,
NULL,
pngFatalErrorFn,
pngWarningFn,
NULL,
pngRealMallocFn,
pngRealFreeFn);
if (mData->png_ptr == NULL)
return (false);
mData->info_ptr = png_create_info_struct(mData->png_ptr);
if (mData->info_ptr == NULL)
{
png_destroy_write_struct(&mData->png_ptr, (png_infopp)NULL);
return false;
}
png_set_write_fn(mData->png_ptr, &stream, pngWriteDataFn, pngFlushDataFn);
// Set the compression level and image filters
png_set_compression_window_bits(mData->png_ptr, 15);
png_set_compression_level(mData->png_ptr, compressionLevel);
png_set_filter(mData->png_ptr, 0, PNG_ALL_FILTERS);
// Set the image information here. Width and height are up to 2^31,
// bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
// the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
// PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
// or PNG_COLOR_TYPE_RGB_ALPHA. interlace is either PNG_INTERLACE_NONE or
// PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
// currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
if (format == GFXFormatR8G8B8)
{
png_set_IHDR(mData->png_ptr, mData->info_ptr,
width, height, // the width & height
8, PNG_COLOR_TYPE_RGB, // bit_depth, color_type,
NULL, // no interlace
NULL, // compression type
NULL); // filter type
}
else if (format == GFXFormatR8G8B8A8 || format == GFXFormatR8G8B8X8)
{
png_set_IHDR(mData->png_ptr, mData->info_ptr,
width, height, // the width & height
8, PNG_COLOR_TYPE_RGB_ALPHA, // bit_depth, color_type,
NULL, // no interlace
NULL, // compression type
NULL); // filter type
}
else if (format == GFXFormatA8)
{
png_set_IHDR(mData->png_ptr, mData->info_ptr,
width, height, // the width & height
8, PNG_COLOR_TYPE_GRAY, // bit_depth, color_type,
NULL, // no interlace
NULL, // compression type
NULL); // filter type
}
else if (format == GFXFormatR5G6B5)
{
png_set_IHDR(mData->png_ptr, mData->info_ptr,
width, height, // the width & height
16, PNG_COLOR_TYPE_GRAY, // bit_depth, color_type,
PNG_INTERLACE_NONE, // no interlace
PNG_COMPRESSION_TYPE_DEFAULT, // compression type
PNG_FILTER_TYPE_DEFAULT); // filter type
png_color_8_struct sigBit = { 0 };
sigBit.gray = 16;
png_set_sBIT(mData->png_ptr, mData->info_ptr, &sigBit );
png_set_swap( mData->png_ptr );
}
png_write_info(mData->png_ptr, mData->info_ptr);
mActive = true;
return true;
}
void DeferredPNGWriter::append( GBitmap* bitmap, U32 rows)
{
AssertFatal(mActive, "Cannot append to an inactive DeferredPNGWriter!");
U32 height = getMin( bitmap->getHeight(), rows);
FrameAllocatorMarker marker;
png_bytep* row_pointers = (png_bytep*)marker.alloc( height * sizeof( png_bytep ) );
for (U32 i=0; i<height; i++)
row_pointers[i] = const_cast<png_bytep>(bitmap->getAddress(0, i));
png_write_rows(mData->png_ptr, row_pointers, height);
}
void DeferredPNGWriter::end()
{
AssertFatal(mActive, "Cannot end an inactive DeferredPNGWriter!");
png_write_end(mData->png_ptr, mData->info_ptr);
png_destroy_write_struct(&mData->png_ptr, (png_infopp)NULL);
mActive = false;
}

View file

@ -0,0 +1,230 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "core/stream/fileStream.h"
#include "core/stream/memStream.h"
#include "core/strings/stringFunctions.h"
#include "gfx/bitmap/gBitmap.h"
#ifdef __clang__
#define STBIWDEF static inline
#endif
#ifndef STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_STATIC
#include "stb_image.h"
#endif
#ifndef STB_IMAGE_WRITE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#define STB_IMAGE_WRITE_STATIC
#include "stb_image_write.h"
#endif
static bool sReadSTB(const Torque::Path& path, GBitmap* bitmap);
static bool sWriteSTB(const Torque::Path& path, GBitmap* bitmap, U32 compressionLevel);
static struct _privateRegisterSTB
{
_privateRegisterSTB()
{
GBitmap::Registration reg;
reg.priority = 100;
reg.extensions.push_back("png");
reg.extensions.push_back("bmp");
reg.extensions.push_back("jpg");
reg.extensions.push_back("jpeg");
reg.extensions.push_back("psd");
reg.extensions.push_back("hdr");
reg.extensions.push_back("tga");
reg.readFunc = sReadSTB;
reg.writeFunc = sWriteSTB;
// for png only.
reg.defaultCompression = 6;
GBitmap::sRegisterFormat(reg);
}
} sStaticRegisterSTB;
bool sReadSTB(const Torque::Path& path, GBitmap* bitmap)
{
PROFILE_SCOPE(sReadSTB);
S32 x, y, n, channels;
bool is16 = false;
bool isHdr = false;
U32 prevWaterMark = FrameAllocator::getWaterMark();
if (!stbi_info(path.getFullPath().c_str(), &x, &y, &channels))
{
FrameAllocator::setWaterMark(prevWaterMark);
return false;
}
// do this to map one channel to 3 and 2 channels to 4
if (channels == 2)
channels = 4;
if (stbi_is_16_bit(path.getFullPath().c_str()))
{
is16 = true;
channels = 4;
}
// stbi treats hdrs as 32f (all float are 32f from stbi so no 16f =/ )
if (stbi_is_hdr(path.getFullPath().c_str()))
{
isHdr = true;
is16 = false;
channels = 4;
}
unsigned char* data = stbi_load(path.getFullPath().c_str(), &x, &y, &n, channels);
bitmap->deleteImage();
GFXFormat format;
switch (n) {
case 1:
if (is16)
{
format = GFXFormatL16;
break;
}
format = GFXFormatA8;
break;
case 2:
format = GFXFormatA8L8;
break;
case 3:
format = GFXFormatR8G8B8;
break;
case 4:
if (isHdr)
{
format = GFXFormatR32G32B32A32F;
break;
}
if (is16)
{
format = GFXFormatR16G16B16A16;
break;
}
format = GFXFormatR8G8B8A8;
break;
default:
FrameAllocator::setWaterMark(prevWaterMark);
return false;
}
// actually allocate the bitmap space...
bitmap->allocateBitmap(x, y,
false, // don't extrude miplevels...
format); // use determined format...
U8* pBase = (U8*)bitmap->getBits();
U32 rowBytes = y * x * n;
dMemcpy(pBase, data, rowBytes);
stbi_image_free(data);
// Check this bitmap for transparency
if (channels == 4 && !is16)
bitmap->checkForTransparency();
FrameAllocator::setWaterMark(prevWaterMark);
return true;
}
bool sWriteSTB(const Torque::Path& path, GBitmap* bitmap, U32 compressionLevel)
{
PROFILE_SCOPE(sWriteSTB);
// get our data to be saved.
U32 width = bitmap->getWidth();
U32 height = bitmap->getHeight();
U32 bytes = bitmap->getBytesPerPixel();
GFXFormat format = bitmap->getFormat();
String ext = path.getExtension();
U32 stride = width * bytes;
// we always have at least 1
U32 comp = 1;
if (format == GFXFormatR8G8B8)
{
comp = 3;
}
else if (format == GFXFormatR8G8B8A8 || format == GFXFormatR8G8B8X8 || format == GFXFormatR8G8B8A8_LINEAR_FORCE)
{
comp = 4;
}
if (ext.equal("png"))
{
stbi_write_png_compression_level = compressionLevel;
if (stbi_write_png(path.getFullPath().c_str(), width, height, comp, bitmap->getWritableBits(), 0))
return true;
}
if (ext.equal("tga"))
{
if (stbi_write_tga(path.getFullPath().c_str(), width, height, comp, bitmap->getWritableBits()))
return true;
}
if (ext.equal("bmp"))
{
if (stbi_write_bmp(path.getFullPath().c_str(), width, height, comp, bitmap->getWritableBits()))
return true;
}
if (ext.equal("jpg") || ext.equal("jpeg"))
{
if (stbi_write_jpg(path.getFullPath().c_str(), width, height, comp, bitmap->getWritableBits(), 90))
return true;
}
if (ext.equal("hdr"))
{
if (stbi_write_hdr(path.getFullPath().c_str(), width, height, comp, (const F32*)bitmap->getWritableBits()))
return true;
}
return false;
}

View file

@ -1,491 +0,0 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "core/stream/stream.h"
#include "gfx/bitmap/gBitmap.h"
static bool sReadTGA(Stream &stream, GBitmap *bitmap);
static bool sWriteTGA(GBitmap *bitmap, Stream &stream, U32 compressionLevel);
static struct _privateRegisterTGA
{
_privateRegisterTGA()
{
GBitmap::Registration reg;
reg.extensions.push_back( "tga" );
reg.readFunc = sReadTGA;
reg.writeFunc = sWriteTGA;
GBitmap::sRegisterFormat( reg );
}
} sStaticRegisterTGA;
//------------------------------------------------------------------------------
//-------------------------------------- Supplementary I/O
//
enum eImageType
{
TypeNoData = 0,
TypeUncPaletted = 1,
TypeUncTruecolor = 2,
TypeUncGrayscale = 3,
TypeRlePaletted = 9,
TypeRleTruecolor = 10,
TypeRleGrayscale = 11
};
enum ePixelMap
{
MapLowerLeft = 0,
MapLowerRight = 1,
MapUpperLeft = 2,
MapUpperRight = 3,
};
static void tga_write_pixel_to_mem( U8 * dat, U8 img_spec, U32 number,
U32 w, U32 h, U32 pixel, U32 bppOut )
{
// write the pixel to the data regarding how the
// header says the data is ordered.
U32 x, y;
switch( (img_spec & 0x30) >> 4 )
{
case MapLowerRight:
x = w - 1 - (number % w);
y = h - 1 - (number / w);
break;
case MapUpperLeft:
x = number % w;
y = number / w;
break;
case MapUpperRight:
x = w - 1 - (number % w);
y = number / w;
break;
case MapLowerLeft:
default:
x = number % w;
y = h - 1 - (number / w);
break;
}
U32 addy = (y * w + x) * bppOut;
for ( U32 j = 0; j < bppOut; j++ )
dat[addy + j] = (U8)((pixel >> (j * 8)) & 0xFF);
}
static U32 tga_get_pixel( Stream& stream, U8 bppIn,
U8 * colormap, U8 cmapBytesEntry )
{
/* get the image data value out */
U32 tmp_int32 = 0;
for ( U32 j = 0; j < bppIn; j++ )
{
U8 tmp_byte;
if ( !stream.read( &tmp_byte ) )
tmp_int32 = 0;
else
tmp_int32 += tmp_byte << (j * 8);
}
/* byte-order correct the thing */
switch( bppIn )
{
case 2:
tmp_int32 = convertLEndianToHost( (U16)tmp_int32 );
break;
case 3: /* intentional fall-thru */
case 4:
tmp_int32 = convertLEndianToHost( tmp_int32 );
break;
}
U32 tmp_col;
if ( colormap )
{
/* need to look up value to get real color */
tmp_col = 0;
for ( U32 j = 0; j < cmapBytesEntry; j++ )
tmp_col += colormap[cmapBytesEntry * tmp_int32 + j] << (8 * j);
}
else
{
tmp_col = tmp_int32;
}
return tmp_col;
}
static U32 tga_convert_color( U32 pixel, U32 bppIn, U8 alphabits, U32 bppOut )
{
// this is not only responsible for converting from different depths
// to other depths, it also switches BGR to RGB.
// this thing will also premultiply alpha, on a pixel by pixel basis.
U8 r, g, b, a;
switch( bppIn )
{
case 32:
if ( alphabits == 0 )
goto is_24_bit_in_disguise;
// 32-bit to 32-bit -- nop.
break;
case 24:
is_24_bit_in_disguise:
// 24-bit to 32-bit; (only force alpha to full)
pixel |= 0xFF000000;
break;
case 15:
is_15_bit_in_disguise:
r = (U8)(((F32)((pixel & 0x7C00) >> 10)) * 8.2258f);
g = (U8)(((F32)((pixel & 0x03E0) >> 5 )) * 8.2258f);
b = (U8)(((F32)(pixel & 0x001F)) * 8.2258f);
// 15-bit to 32-bit; (force alpha to full)
pixel = 0xFF000000 + (r << 16) + (g << 8) + b;
break;
case 16:
if ( alphabits == 1 )
goto is_15_bit_in_disguise;
// 16-bit to 32-bit; (force alpha to full)
r = (U8)(((F32)((pixel & 0xF800) >> 11)) * 8.2258f);
g = (U8)(((F32)((pixel & 0x07E0) >> 5 )) * 4.0476f);
b = (U8)(((F32)(pixel & 0x001F)) * 8.2258f);
pixel = 0xFF000000 + (r << 16) + (g << 8) + b;
break;
}
// convert the 32-bit pixel from BGR to RGB.
pixel = (pixel & 0xFF00FF00) + ((pixel & 0xFF) << 16) + ((pixel & 0xFF0000) >> 16);
r = pixel & 0x000000FF;
g = (pixel & 0x0000FF00) >> 8;
b = (pixel & 0x00FF0000) >> 16;
a = (pixel & 0xFF000000) >> 24;
// not premultiplied alpha -- multiply.
r = (U8)(((F32)r / 255.0f) * ((F32)a / 255.0f) * 255.0f);
g = (U8)(((F32)g / 255.0f) * ((F32)a / 255.0f) * 255.0f);
b = (U8)(((F32)b / 255.0f) * ((F32)a / 255.0f) * 255.0f);
pixel = r + (g << 8) + (b << 16) + (a << 24);
/* now convert from 32-bit to whatever they want. */
switch( bppOut )
{
case 4:
// 32 to 32 -- nop.
break;
case 3:
// 32 to 24 -- discard alpha.
pixel &= 0x00FFFFFF;
break;
}
return pixel;
}
static bool sReadTGA(Stream &stream, GBitmap *bitmap)
{
PROFILE_SCOPE(sReadTGA);
struct Header
{
U8 idLength; // length of the image_id string below.
U8 cmapType; // paletted image <=> cmapType
U8 imageType; // can be any of the IMG_TYPE constants above.
U16 cmapFirst; //
U16 cmapLength; // how long the colormap is
U8 cmapEntrySize; // how big a palette entry is.
U16 xOrigin; // the x origin of the image in the image data.
U16 yOrigin; // the y origin of the image in the image data.
U16 width; // the width of the image.
U16 height; // the height of the image.
U8 pixelDepth; // the depth of a pixel in the image.
U8 imageDesc; // the image descriptor.
};
// Read header
Header header;
stream.read( &header.idLength );
stream.read( &header.cmapType );
stream.read( &header.imageType );
stream.read( &header.cmapFirst );
stream.read( &header.cmapLength );
stream.read( &header.cmapEntrySize );
stream.read( &header.xOrigin );
stream.read( &header.yOrigin );
stream.read( &header.width );
stream.read( &header.height );
stream.read( &header.pixelDepth );
stream.read( &header.imageDesc );
U32 numPixels = header.width * header.height;
if ( numPixels == 0 )
{
//Con::errorf( "Texture has width and/or height set to 0" );
return false;
}
U8 alphabits = header.imageDesc & 0x0F;
/* seek past the image id, if there is one */
if ( header.idLength )
{
if ( !stream.setPosition( stream.getPosition() + header.idLength ) )
{
//Con::errorf( "Unexpected end of stream encountered" );
return false;
}
}
/* if this is a 'nodata' image, just jump out. */
if ( header.imageType == TypeNoData )
{
//Con::errorf( "Texture contains no data" );
return false;
}
/* deal with the colormap, if there is one. */
U8* colormap = NULL;
U32 cmapBytes = 0;
U8 cmapBytesEntry = 0;
if ( header.cmapType )
{
switch( header.imageType )
{
case TypeUncPaletted:
case TypeRlePaletted:
break;
case TypeUncTruecolor:
case TypeRleTruecolor:
// this should really be an error, but some really old
// crusty targas might actually be like this (created by TrueVision, no less!)
// so, we'll hack our way through it.
break;
case TypeUncGrayscale:
case TypeRleGrayscale:
//Con::errorf( "Found colormap for a grayscale image" );
return false;
}
/* ensure colormap entry size is something we support */
if ( !(header.cmapEntrySize == 15 ||
header.cmapEntrySize == 16 ||
header.cmapEntrySize == 24 ||
header.cmapEntrySize == 32) )
{
//Con::errorf( "Unsupported colormap entry size" );
return false;
}
/* allocate memory for a colormap */
if ( header.cmapEntrySize & 0x07 )
cmapBytesEntry = (((8 - (header.cmapEntrySize & 0x07)) + header.cmapEntrySize) >> 3);
else
cmapBytesEntry = (header.cmapEntrySize >> 3);
cmapBytes = cmapBytesEntry * header.cmapLength;
colormap = new U8[ cmapBytes ];
for ( U32 i = 0; i < header.cmapLength; i++ )
{
/* seek ahead to first entry used */
if ( header.cmapFirst != 0 )
stream.setPosition( stream.getPosition() + header.cmapFirst * cmapBytesEntry );
U32 tmp_int32 = 0;
for ( U32 j = 0; j < cmapBytesEntry; j++ )
{
U8 tmp_byte;
if ( !stream.read( &tmp_byte ) )
{
delete [] colormap;
//Con::errorf( "Bad colormap" );
return false;
}
tmp_int32 += tmp_byte << (j * 8);
}
// byte order correct.
tmp_int32 = convertLEndianToHost( tmp_int32 );
for ( U32 j = 0; j < cmapBytesEntry; j++ )
colormap[i * cmapBytesEntry + j] = (tmp_int32 >> (8 * j)) & 0xFF;
}
}
// compute number of bytes in an image data unit (either index or BGR triple)
U8 inBytesPerPixel = 0;
if ( header.pixelDepth & 0x07 )
inBytesPerPixel = (((8 - (header.pixelDepth & 0x07)) + header.pixelDepth) >> 3);
else
inBytesPerPixel = (header.pixelDepth >> 3);
/* assume that there's one byte per pixel */
if ( inBytesPerPixel == 0 )
inBytesPerPixel = 1;
GFXFormat gfxFmt;
U32 outBytesPerPixel;
switch ( header.pixelDepth )
{
case 32:
gfxFmt = GFXFormatR8G8B8A8;
outBytesPerPixel = 4;
break;
case 24:
default:
gfxFmt = GFXFormatR8G8B8;
outBytesPerPixel = 3;
break;
}
bitmap->allocateBitmap( header.width, header.height, false, gfxFmt );
// compute the true number of bits per pixel
U8 trueBitsPerPixel = header.cmapType ? header.cmapEntrySize : header.pixelDepth;
// Override the number of alpha bits if necessary
// Some apps generate transparent TGAs with alphabits set to 0 in the image descriptor
if ( ( trueBitsPerPixel == 32 ) && ( alphabits == 0 ) )
alphabits = 8;
switch( header.imageType )
{
case TypeUncTruecolor:
case TypeUncGrayscale:
case TypeUncPaletted:
/* FIXME: support grayscale */
for ( U32 i = 0; i < numPixels; i++ )
{
// get the color value.
U32 tmp_col = tga_get_pixel( stream, inBytesPerPixel, colormap, cmapBytesEntry );
tmp_col = tga_convert_color( tmp_col, trueBitsPerPixel, alphabits, outBytesPerPixel );
// now write the data out.
tga_write_pixel_to_mem( bitmap->getAddress( 0, 0 ), header.imageDesc,
i, header.width, header.height, tmp_col, outBytesPerPixel );
}
break;
case TypeRleTruecolor:
case TypeRleGrayscale:
case TypeRlePaletted:
// FIXME: handle grayscale..
for ( U32 i = 0; i < numPixels; )
{
/* a bit of work to do to read the data.. */
U8 packet_header;
if ( !stream.read( 1, &packet_header ) )
{
// well, just let them fill the rest with null pixels then...
packet_header = 1;
}
if ( packet_header & 0x80 )
{
/* run length packet */
U32 tmp_col = tga_get_pixel( stream, inBytesPerPixel, colormap, cmapBytesEntry );
tmp_col = tga_convert_color( tmp_col, trueBitsPerPixel, alphabits, outBytesPerPixel );
U8 repcount = (packet_header & 0x7F) + 1;
/* write all the data out */
for ( U32 j = 0; j < repcount; j++ )
{
tga_write_pixel_to_mem( bitmap->getAddress( 0, 0 ), header.imageDesc,
i + j, header.width, header.height, tmp_col, outBytesPerPixel );
}
i += repcount;
}
else
{
/* raw packet */
/* get pixel from file */
U8 repcount = (packet_header & 0x7F) + 1;
for ( U32 j = 0; j < repcount; j++ )
{
U32 tmp_col = tga_get_pixel( stream, inBytesPerPixel, colormap, cmapBytesEntry );
tmp_col = tga_convert_color( tmp_col, trueBitsPerPixel, alphabits, outBytesPerPixel );
tga_write_pixel_to_mem( bitmap->getAddress( 0, 0 ), header.imageDesc,
i + j, header.width, header.height, tmp_col, outBytesPerPixel );
}
i += repcount;
}
}
break;
default:
//Con::errorf( "Unknown image type" );
delete[] colormap;
return false;
}
delete [] colormap;
// 32-bit tgas have an alpha channel
bitmap->setHasTransparency( header.pixelDepth == 32 );
return true;
}
static bool sWriteTGA(GBitmap *bitmap, Stream &stream, U32 compressionLevel)
{
AssertISV(false, "GBitmap::writeTGA - doesn't support writing tga files!");
return false;
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff