Engine directory for ticket #1

This commit is contained in:
DavidWyand-GG 2012-09-19 11:15:01 -04:00
parent 352279af7a
commit 7dbfe6994d
3795 changed files with 1363358 additions and 0 deletions

View file

@ -0,0 +1,284 @@
//-----------------------------------------------------------------------------
// 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 "gfx/bitmap/bitmapUtils.h"
#include "platform/platform.h"
void bitmapExtrude5551_c(const void *srcMip, void *mip, U32 srcHeight, U32 srcWidth)
{
const U16 *src = (const U16 *) srcMip;
U16 *dst = (U16 *) mip;
U32 stride = srcHeight != 1 ? srcWidth : 0;
U32 width = srcWidth >> 1;
U32 height = srcHeight >> 1;
if (width == 0) width = 1;
if (height == 0) height = 1;
if (srcWidth != 1)
{
for(U32 y = 0; y < height; y++)
{
for(U32 x = 0; x < width; x++)
{
U32 a = src[0];
U32 b = src[1];
U32 c = src[stride];
U32 d = src[stride+1];
#if defined(TORQUE_BIG_ENDIAN)
dst[x] = ((( (a >> 10) + (b >> 10) + (c >> 10) + (d >> 10)) >> 2) << 10) |
((( ((a >> 5) & 0x1F) + ((b >> 5) & 0x1F) + ((c >> 5) & 0x1F) + ((d >> 5) & 0x1F)) >> 2) << 5) |
((( ((a >> 0) & 0x1F) + ((b >> 0) & 0x1F) + ((c >> 0) & 0x1F) + ((d >> 0) & 0x1F)) >> 2) << 0);
#else
dst[x] = ((( (a >> 11) + (b >> 11) + (c >> 11) + (d >> 11)) >> 2) << 11) |
((( ((a >> 6) & 0x1F) + ((b >> 6) & 0x1F) + ((c >> 6) & 0x1F) + ((d >> 6) & 0x1F)) >> 2) << 6) |
((( ((a >> 1) & 0x1F) + ((b >> 1) & 0x1F) + ((c >> 1) & 0x1F) + ((d >> 1) & 0x1F)) >> 2) << 1);
#endif
src += 2;
}
src += stride;
dst += width;
}
}
else
{
for(U32 y = 0; y < height; y++)
{
U32 a = src[0];
U32 c = src[stride];
#if defined(TORQUE_OS_MAC)
dst[y] = ((( (a >> 10) + (c >> 10)) >> 1) << 10) |
((( ((a >> 5) & 0x1F) + ((c >> 5) & 0x1f)) >> 1) << 5) |
((( ((a >> 0) & 0x1F) + ((c >> 0) & 0x1f)) >> 1) << 0);
#else
dst[y] = ((( (a >> 11) + (c >> 11)) >> 1) << 11) |
((( ((a >> 6) & 0x1f) + ((c >> 6) & 0x1f)) >> 1) << 6) |
((( ((a >> 1) & 0x1F) + ((c >> 1) & 0x1f)) >> 1) << 1);
#endif
src += 1 + stride;
}
}
}
//--------------------------------------------------------------------------
void bitmapExtrudeRGB_c(const void *srcMip, void *mip, U32 srcHeight, U32 srcWidth)
{
const U8 *src = (const U8 *) srcMip;
U8 *dst = (U8 *) mip;
U32 stride = srcHeight != 1 ? (srcWidth) * 3 : 0;
U32 width = srcWidth >> 1;
U32 height = srcHeight >> 1;
if (width == 0) width = 1;
if (height == 0) height = 1;
if (srcWidth != 1)
{
for(U32 y = 0; y < height; y++)
{
for(U32 x = 0; x < width; x++)
{
*dst++ = (U32(*src) + U32(src[3]) + U32(src[stride]) + U32(src[stride+3]) + 2) >> 2;
src++;
*dst++ = (U32(*src) + U32(src[3]) + U32(src[stride]) + U32(src[stride+3]) + 2) >> 2;
src++;
*dst++ = (U32(*src) + U32(src[3]) + U32(src[stride]) + U32(src[stride+3]) + 2) >> 2;
src += 4;
}
src += stride; // skip
}
}
else
{
for(U32 y = 0; y < height; y++)
{
*dst++ = (U32(*src) + U32(src[stride]) + 1) >> 1;
src++;
*dst++ = (U32(*src) + U32(src[stride]) + 1) >> 1;
src++;
*dst++ = (U32(*src) + U32(src[stride]) + 1) >> 1;
src += 4;
src += stride; // skip
}
}
}
//--------------------------------------------------------------------------
void bitmapExtrudeRGBA_c(const void *srcMip, void *mip, U32 srcHeight, U32 srcWidth)
{
const U8 *src = (const U8 *) srcMip;
U8 *dst = (U8 *) mip;
U32 stride = srcHeight != 1 ? (srcWidth) * 4 : 0;
U32 width = srcWidth >> 1;
U32 height = srcHeight >> 1;
if (width == 0) width = 1;
if (height == 0) height = 1;
if (srcWidth != 1)
{
for(U32 y = 0; y < height; y++)
{
for(U32 x = 0; x < width; x++)
{
*dst++ = (U32(*src) + U32(src[4]) + U32(src[stride]) + U32(src[stride+4]) + 2) >> 2;
src++;
*dst++ = (U32(*src) + U32(src[4]) + U32(src[stride]) + U32(src[stride+4]) + 2) >> 2;
src++;
*dst++ = (U32(*src) + U32(src[4]) + U32(src[stride]) + U32(src[stride+4]) + 2) >> 2;
src++;
*dst++ = (U32(*src) + U32(src[4]) + U32(src[stride]) + U32(src[stride+4]) + 2) >> 2;
src += 5;
}
src += stride; // skip
}
}
else
{
for(U32 y = 0; y < height; y++)
{
*dst++ = (U32(*src) + U32(src[stride]) + 1) >> 1;
src++;
*dst++ = (U32(*src) + U32(src[stride]) + 1) >> 1;
src++;
*dst++ = (U32(*src) + U32(src[stride]) + 1) >> 1;
src++;
*dst++ = (U32(*src) + U32(src[stride]) + 1) >> 1;
src += 5;
src += stride; // skip
}
}
}
void (*bitmapExtrude5551)(const void *srcMip, void *mip, U32 height, U32 width) = bitmapExtrude5551_c;
void (*bitmapExtrudeRGB)(const void *srcMip, void *mip, U32 srcHeight, U32 srcWidth) = bitmapExtrudeRGB_c;
void (*bitmapExtrudeRGBA)(const void *srcMip, void *mip, U32 srcHeight, U32 srcWidth) = bitmapExtrudeRGBA_c;
//--------------------------------------------------------------------------
void bitmapConvertRGB_to_1555_c(U8 *src, U32 pixels)
{
U16 *dst = (U16 *)src;
for(U32 j = 0; j < pixels; j++)
{
U32 r = src[0] >> 3;
U32 g = src[1] >> 3;
U32 b = src[2] >> 3;
#if defined(TORQUE_OS_MAC)
*dst++ = 0x8000 | (b << 10) | (g << 5) | (r << 0);
#else
*dst++ = b | (g << 5) | (r << 10) | 0x8000;
#endif
src += 3;
}
}
void (*bitmapConvertRGB_to_1555)(U8 *src, U32 pixels) = bitmapConvertRGB_to_1555_c;
//------------------------------------------------------------------------------
void bitmapConvertRGB_to_5551_c(U8 *src, U32 pixels)
{
U16 *dst = (U16 *)src;
for(U32 j = 0; j < pixels; j++)
{
U32 r = src[0] >> 3;
U32 g = src[1] >> 3;
U32 b = src[2] >> 3;
#if defined(TORQUE_OS_MAC)
*dst++ = (1 << 15) | (b << 10) | (g << 5) | (r << 0);
#else
*dst++ = (b << 1) | (g << 6) | (r << 11) | 1;
#endif
src += 3;
}
}
void (*bitmapConvertRGB_to_5551)(U8 *src, U32 pixels) = bitmapConvertRGB_to_5551_c;
//------------------------------------------------------------------------------
void bitmapConvertRGB_to_RGBX_c( U8 **src, U32 pixels )
{
const U8 *oldBits = *src;
U8 *newBits = new U8[pixels * 4];
dMemset( newBits, 0xFF, pixels * 4 ); // This is done to set alpha values -patw
// Copy the bits over to the new memory
for( U32 i = 0; i < pixels; i++ )
dMemcpy( &newBits[i * 4], &oldBits[i * 3], sizeof(U8) * 3 );
// Now hose the old bits
delete [] *src;
*src = newBits;
}
void (*bitmapConvertRGB_to_RGBX)( U8 **src, U32 pixels ) = bitmapConvertRGB_to_RGBX_c;
//------------------------------------------------------------------------------
void bitmapConvertRGBX_to_RGB_c( U8 **src, U32 pixels )
{
const U8 *oldBits = *src;
U8 *newBits = new U8[pixels * 3];
// Copy the bits over to the new memory
for( U32 i = 0; i < pixels; i++ )
dMemcpy( &newBits[i * 3], &oldBits[i * 4], sizeof(U8) * 3 );
// Now hose the old bits
delete [] *src;
*src = newBits;
}
void (*bitmapConvertRGBX_to_RGB)( U8 **src, U32 pixels ) = bitmapConvertRGBX_to_RGB_c;
//------------------------------------------------------------------------------
void bitmapConvertA8_to_RGBA_c( U8 **src, U32 pixels )
{
const U8 *oldBits = *src;
U8 *newBits = new U8[pixels * 4];
// Zero new bits
dMemset( newBits, 0, pixels * 4 );
// Copy Alpha values
for( U32 i = 0; i < pixels; i++ )
newBits[i * 4 + 3] = oldBits[i];
// Now hose the old bits
delete [] *src;
*src = newBits;
}
void (*bitmapConvertA8_to_RGBA)( U8 **src, U32 pixels ) = bitmapConvertA8_to_RGBA_c;

View file

@ -0,0 +1,41 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _BITMAPUTILS_H_
#define _BITMAPUTILS_H_
#ifndef _TORQUE_TYPES_H_
#include "platform/types.h"
#endif
extern void (*bitmapExtrude5551)(const void *srcMip, void *mip, U32 height, U32 width);
extern void (*bitmapExtrudeRGB)(const void *srcMip, void *mip, U32 height, U32 width);
extern void (*bitmapExtrudeRGBA)(const void *srcMip, void *mip, U32 height, U32 width);
extern void (*bitmapConvertRGB_to_5551)(U8 *src, U32 pixels);
extern void (*bitmapConvertRGB_to_1555)(U8 *src, U32 pixels);
extern void (*bitmapConvertRGB_to_RGBX)( U8 **src, U32 pixels );
extern void (*bitmapConvertRGBX_to_RGB)( U8 **src, U32 pixels );
extern void (*bitmapConvertA8_to_RGBA)( U8 **src, U32 pixels );
void bitmapExtrudeRGB_c(const void *srcMip, void *mip, U32 height, U32 width);
#endif //_BITMAPUTILS_H_

View file

@ -0,0 +1,205 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _DDSFILE_H_
#define _DDSFILE_H_
#ifndef _GFXSTRUCTS_H_
#include "gfx/gfxStructs.h"
#endif
#ifndef _BITSET_H_
#include "core/bitSet.h"
#endif
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
#ifndef __RESOURCE_H__
#include "core/resource.h"
#endif
class Stream;
class GBitmap;
struct DDSFile
{
enum DDSFlags
{
ComplexFlag = BIT(0), ///< Indicates this includes a mipchain, cubemap, or
/// volume texture, ie, isn't a plain old bitmap.
MipMapsFlag = BIT(1), ///< Indicates we have a mipmap chain in the file.
CubeMapFlag = BIT(2), ///< Indicates we are a cubemap. Requires all six faces.
VolumeFlag = BIT(3), ///< Indicates we are a volume texture.
PitchSizeFlag = BIT(4), ///< Cue as to how to interpret our pitchlinear value.
LinearSizeFlag = BIT(5), ///< Cue as to how to interpret our pitchlinear value.
RGBData = BIT(6), ///< Indicates that this is straight out RGBA data.
CompressedData = BIT(7), ///< Indicates that this is compressed or otherwise
/// exotic data.
/// These are the flags for which cubemap
/// surfaces are included in the file.
CubeMap_PosX_Flag = BIT(8),
CubeMap_NegX_Flag = BIT(9),
CubeMap_PosY_Flag = BIT(10),
CubeMap_NegY_Flag = BIT(11),
CubeMap_PosZ_Flag = BIT(12),
CubeMap_NegZ_Flag = BIT(13),
};
/// The index into mSurfaces for each
/// cubemap face.
enum
{
Cubemap_Surface_PosX,
Cubemap_Surface_NegX,
Cubemap_Surface_PosY,
Cubemap_Surface_NegY,
Cubemap_Surface_PosZ,
Cubemap_Surface_NegZ,
Cubemap_Surface_Count,
};
BitSet32 mFlags;
U32 mHeight;
U32 mWidth;
U32 mDepth;
U32 mPitchOrLinearSize;
U32 mMipMapCount;
GFXFormat mFormat;
U32 mBytesPerPixel; ///< Ignored if we're a compressed texture.
U32 mFourCC;
String mCacheString;
Torque::Path mSourcePath;
bool mHasTransparency;
// This is ugly... but it allows us to pass the number of
// mips to drop into the ResourceManager loading process.
static U32 smDropMipCount;
struct SurfaceData
{
SurfaceData()
{
VECTOR_SET_ASSOCIATION( mMips );
}
~SurfaceData()
{
// Free our mips!
for(S32 i=0; i<mMips.size(); i++)
delete[] mMips[i];
}
Vector<U8*> mMips;
// Helper function to read in a mipchain.
bool readMipChain();
void dumpImage(DDSFile *dds, U32 mip, const char *file);
/// Helper for reading a mip level.
void readNextMip(DDSFile *dds, Stream &s, U32 height, U32 width, U32 mipLevel, bool skip);
/// Helper for writing a mip level.
void writeNextMip(DDSFile *dds, Stream &s, U32 height, U32 width, U32 mipLevel);
};
Vector<SurfaceData*> mSurfaces;
/// Clear all our information; used before reading.
void clear();
/// Reads a DDS file from the stream.
bool read(Stream &s, U32 dropMipCount);
/// Called from read() to read in the DDS header.
bool readHeader(Stream &s);
/// Writes this DDS file to the stream.
bool write(Stream &s);
/// Called from write() to write the DDS header.
bool writeHeader(Stream &s);
/// For our current format etc., what is the size of a surface with the
/// given dimensions?
U32 getSurfaceSize( U32 mipLevel = 0 ) const { return getSurfaceSize( mHeight, mWidth, mipLevel ); }
U32 getSurfaceSize( U32 height, U32 width, U32 mipLevel = 0 ) const;
// Helper for getting the size in bytes of a compressed DDS texture.
static U32 getSizeInBytes( GFXFormat format, U32 height, U32 width, U32 mipLevels );
/// Returns the total video memory size of the texture
/// including all mipmaps and compression settings.
U32 getSizeInBytes() const;
U32 getWidth( U32 mipLevel = 0 ) const { return getMax( U32(1), mWidth >> mipLevel ); }
U32 getHeight( U32 mipLevel = 0 ) const { return getMax(U32(1), mHeight >> mipLevel); }
U32 getDepth( U32 mipLevel = 0 ) const { return getMax(U32(1), mDepth >> mipLevel); }
U32 getMipLevels() const { return mMipMapCount; }
bool getHasTransparency() const { return mHasTransparency; }
bool isCubemap() const { return mFlags.test( CubeMapFlag ); }
GFXFormat getFormat() const { return mFormat; }
U32 getSurfacePitch( U32 mipLevel = 0 ) const;
const Torque::Path &getSourcePath() const { return mSourcePath; }
const String &getTextureCacheString() const { return mCacheString; }
static Resource<DDSFile> load( const Torque::Path &path, U32 dropMipCount );
// For debugging fun!
static S32 smActiveCopies;
DDSFile()
{
VECTOR_SET_ASSOCIATION( mSurfaces );
smActiveCopies++;
mHasTransparency = false;
}
DDSFile( const DDSFile &dds );
~DDSFile()
{
smActiveCopies--;
// Free our surfaces!
for(S32 i=0; i<mSurfaces.size(); i++)
delete mSurfaces[i];
mSurfaces.clear();
}
static DDSFile *createDDSFileFromGBitmap( const GBitmap *gbmp );
};
#endif // _DDSFILE_H_

View file

@ -0,0 +1,922 @@
//-----------------------------------------------------------------------------
// 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 "gfx/bitmap/ddsFile.h"
#include "gfx/gfxDevice.h"
#include "core/util/fourcc.h"
#include "console/console.h"
#include "core/resourceManager.h"
#include "core/stream/fileStream.h"
#include "gfx/bitmap/gBitmap.h"
#include "console/engineAPI.h"
S32 DDSFile::smActiveCopies = 0;
U32 DDSFile::smDropMipCount = 0;
// These were copied from the DX9 docs. The names are changed
// from the "real" defines since not all platforms have them.
enum DDSSurfaceDescFlags
{
DDSDCaps = 0x00000001l,
DDSDHeight = 0x00000002l,
DDSDWidth = 0x00000004l,
DDSDPitch = 0x00000008l,
DDSDPixelFormat = 0x00001000l,
DDSDMipMapCount = 0x00020000l,
DDSDLinearSize = 0x00080000l,
DDSDDepth = 0x00800000l,
};
enum DDSPixelFormatFlags
{
DDPFAlphaPixels = 0x00000001,
DDPFFourCC = 0x00000004,
DDPFRGB = 0x00000040,
DDPFLUMINANCE = 0x00020000
};
enum DDSCapFlags
{
DDSCAPSComplex = 0x00000008,
DDSCAPSTexture = 0x00001000,
DDSCAPSMipMap = 0x00400000,
DDSCAPS2Cubemap = 0x00000200,
DDSCAPS2Cubemap_POSITIVEX = 0x00000400,
DDSCAPS2Cubemap_NEGATIVEX = 0x00000800,
DDSCAPS2Cubemap_POSITIVEY = 0x00001000,
DDSCAPS2Cubemap_NEGATIVEY = 0x00002000,
DDSCAPS2Cubemap_POSITIVEZ = 0x00004000,
DDSCAPS2Cubemap_NEGATIVEZ = 0x00008000,
DDSCAPS2Volume = 0x00200000,
};
#define FOURCC_DXT1 (MakeFourCC('D','X','T','1'))
#define FOURCC_DXT2 (MakeFourCC('D','X','T','2'))
#define FOURCC_DXT3 (MakeFourCC('D','X','T','3'))
#define FOURCC_DXT4 (MakeFourCC('D','X','T','4'))
#define FOURCC_DXT5 (MakeFourCC('D','X','T','5'))
DDSFile::DDSFile( const DDSFile &dds )
: mFlags( dds.mFlags ),
mHeight( dds.mHeight ),
mWidth( dds.mWidth ),
mDepth( dds.mDepth ),
mPitchOrLinearSize( dds.mPitchOrLinearSize ),
mMipMapCount( dds.mMipMapCount ),
mFormat( dds.mFormat ),
mBytesPerPixel( dds.mBytesPerPixel ),
mFourCC( dds.mFourCC ),
mCacheString( dds.mCacheString ),
mSourcePath( dds.mSourcePath ),
mHasTransparency( dds.mHasTransparency )
{
VECTOR_SET_ASSOCIATION( mSurfaces );
smActiveCopies++;
for ( U32 i=0; i < dds.mSurfaces.size(); i++ )
{
SurfaceData *surface = NULL;
if ( dds.mSurfaces[i] )
surface = new SurfaceData;
mSurfaces.push_back( surface );
if ( !surface )
continue;
for ( U32 m=0; m < dds.mSurfaces[i]->mMips.size(); m++ )
{
U32 size = dds.getSurfaceSize( m );
surface->mMips.push_back(new U8[size]);
dMemcpy( surface->mMips.last(), dds.mSurfaces[i]->mMips[m], size );
}
}
}
void DDSFile::clear()
{
mFlags = 0;
mHeight = mWidth = mDepth = mPitchOrLinearSize = mMipMapCount = 0;
mFormat = GFXFormatR8G8B8;
}
U32 DDSFile::getSurfacePitch( U32 mipLevel ) const
{
if(mFlags.test(CompressedData))
{
U32 sizeMultiple = 0;
switch(mFormat)
{
case GFXFormatDXT1:
sizeMultiple = 8;
break;
case GFXFormatDXT2:
case GFXFormatDXT3:
case GFXFormatDXT4:
case GFXFormatDXT5:
sizeMultiple = 16;
break;
default:
AssertISV(false, "DDSFile::getPitch - invalid compressed texture format, we only support DXT1-5 right now.");
break;
}
// Maybe need to be DWORD aligned?
U32 align = getMax(U32(1), getWidth(mipLevel)/4) * sizeMultiple;
align += 3; align >>=2; align <<=2;
return align;
}
else
return getWidth(mipLevel) * mBytesPerPixel;
}
U32 DDSFile::getSurfaceSize( U32 height, U32 width, U32 mipLevel ) const
{
// Bump by the mip level.
height = getMax(U32(1), height >> mipLevel);
width = getMax(U32(1), width >> mipLevel);
if(mFlags.test(CompressedData))
{
// From the directX docs:
// max(1, width ÷ 4) x max(1, height ÷ 4) x 8(DXT1) or 16(DXT2-5)
U32 sizeMultiple = 0;
switch(mFormat)
{
case GFXFormatDXT1:
sizeMultiple = 8;
break;
case GFXFormatDXT2:
case GFXFormatDXT3:
case GFXFormatDXT4:
case GFXFormatDXT5:
sizeMultiple = 16;
break;
default:
AssertISV(false, "DDSFile::getSurfaceSize - invalid compressed texture format, we only support DXT1-5 right now.");
break;
}
return getMax(U32(1), width/4) * getMax(U32(1), height/4) * sizeMultiple;
}
else
{
return height * width* mBytesPerPixel;
}
}
U32 DDSFile::getSizeInBytes() const
{
// TODO: This doesn't take mDepth into account, so
// it doesn't work right for volume or cubemap textures!
U32 bytes = 0;
for ( U32 i=0; i < mMipMapCount; i++ )
bytes += getSurfaceSize( mHeight, mWidth, i );
return bytes;
}
U32 DDSFile::getSizeInBytes( GFXFormat format, U32 height, U32 width, U32 mipLevels )
{
AssertFatal( format >= GFXFormatDXT1 && format <= GFXFormatDXT5,
"DDSFile::getSizeInBytes - Must be a DXT format!" );
// From the directX docs:
// max(1, width ÷ 4) x max(1, height ÷ 4) x 8(DXT1) or 16(DXT2-5)
U32 sizeMultiple = 0;
if ( format == GFXFormatDXT1 )
sizeMultiple = 8;
else
sizeMultiple = 16;
U32 mipHeight, mipWidth;
U32 bytes = 0;
for ( U32 m=0; m < mipLevels; m++ )
{
mipHeight = getMax( U32(1), height >> m );
mipWidth = getMax( U32(1), width >> m );
bytes += getMax( U32(1), mipWidth / 4 ) *
getMax( U32(1), mipHeight / 4 ) * sizeMultiple;
}
return bytes;
}
bool DDSFile::readHeader(Stream &s)
{
U32 tmp;
// Read the FOURCC
s.read(&tmp);
if(tmp != MakeFourCC('D', 'D', 'S', ' '))
{
Con::errorf("DDSFile::readHeader - unexpected magic number, wanted 'DDS '!");
return false;
}
// Read the size of the header.
s.read(&tmp);
if(tmp != 124)
{
Con::errorf("DDSFile::readHeader - incorrect header size. Expected 124 bytes.");
return false;
}
// Read some flags...
U32 ddsdFlags;
s.read(&ddsdFlags);
// "Always include DDSD_CAPS, DDSD_PIXELFORMAT, DDSD_WIDTH, DDSD_HEIGHT."
if(!(ddsdFlags & (DDSDCaps | DDSDPixelFormat | DDSDWidth | DDSDHeight)))
{
Con::errorf("DDSFile::readHeader - incorrect surface description flags.");
return false;
}
// Read height and width (always present)
s.read(&mHeight);
s.read(&mWidth);
// Read pitch or linear size.
// First make sure we have valid flags (either linear size or pitch).
if((ddsdFlags & (DDSDLinearSize | DDSDPitch)) == (DDSDLinearSize | DDSDPitch))
{
// Both are invalid!
Con::errorf("DDSFile::readHeader - encountered both DDSD_LINEARSIZE and DDSD_PITCH!");
return false;
}
// Ok, some flags are set, so let's do some reading.
s.read(&mPitchOrLinearSize);
if(ddsdFlags & DDSDLinearSize)
{
mFlags.set(LinearSizeFlag); // ( mHeight / 4 ) * ( mWidth / 4 ) * DDSSIZE
}
else if (ddsdFlags & DDSDPitch)
{
mFlags.set(PitchSizeFlag); // ( mWidth / 4 ) * DDSSIZE ???
}
else
{
// Neither set! This appears to be depressingly common.
// Con::warnf("DDSFile::readHeader - encountered neither DDSD_LINEARSIZE nor DDSD_PITCH!");
}
// Do we need to read depth? If so, we are a volume texture!
s.read(&mDepth);
if(ddsdFlags & DDSDDepth)
{
mFlags.set(VolumeFlag);
}
else
{
// Wipe it if the flag wasn't set!
mDepth = 0;
}
// Deal with mips!
s.read(&mMipMapCount);
if(ddsdFlags & DDSDMipMapCount)
{
mFlags.set(MipMapsFlag);
}
else
{
// Wipe it if the flag wasn't set!
mMipMapCount = 1;
}
// Deal with 11 DWORDS of reserved space (this reserved space brought to
// you by DirectDraw and the letters F and U).
for(U32 i=0; i<11; i++)
s.read(&tmp);
// Now we're onto the pixel format!
s.read(&tmp);
if(tmp != 32)
{
Con::errorf("DDSFile::readHeader - pixel format chunk has unexpected size!");
return false;
}
U32 ddpfFlags;
s.read(&ddpfFlags);
// Read the next few values so we can deal with them all in one go.
U32 pfFourCC, pfBitCount, pfRMask, pfGMask, pfBMask, pfAlphaMask;
s.read(&pfFourCC);
s.read(&pfBitCount);
s.read(&pfRMask);
s.read(&pfGMask);
s.read(&pfBMask);
s.read(&pfAlphaMask);
// Sanity check flags...
if(!(ddpfFlags & (DDPFRGB | DDPFFourCC | DDPFLUMINANCE)))
{
Con::errorf("DDSFile::readHeader - incoherent pixel flags, neither RGB, FourCC, or Luminance!");
return false;
}
// For now let's just dump the header info.
if(ddpfFlags & DDPFLUMINANCE)
{
mFlags.set(RGBData);
mBytesPerPixel = pfBitCount / 8;
bool hasAlpha = ddpfFlags & DDPFAlphaPixels;
mHasTransparency = hasAlpha;
// Try to match a format.
if(hasAlpha)
{
// If it has alpha it is one of...
// GFXFormatA8L8
// GFXFormatA4L4
if(pfBitCount == 16)
mFormat = GFXFormatA8L8;
else if(pfBitCount == 8)
mFormat = GFXFormatA4L4;
else
{
Con::errorf("DDSFile::readHeader - unable to match alpha Luminance format!");
return false;
}
}
else
{
// Otherwise it is one of...
// GFXFormatL16
// GFXFormatL8
if(pfBitCount == 16)
mFormat = GFXFormatL16;
else if(pfBitCount == 8)
mFormat = GFXFormatL8;
else
{
Con::errorf("DDSFile::readHeader - unable to match non-alpha Luminance format!");
return false;
}
}
}
else if(ddpfFlags & DDPFRGB)
{
mFlags.set(RGBData);
//Con::printf("RGB Pixel format of DDS:");
//Con::printf(" bitcount = %d (16, 24, 32)", pfBitCount);
mBytesPerPixel = pfBitCount / 8;
//Con::printf(" red mask = %x", pfRMask);
//Con::printf(" green mask = %x", pfGMask);
//Con::printf(" blue mask = %x", pfBMask);
bool hasAlpha = false;
if(ddpfFlags & DDPFAlphaPixels)
{
hasAlpha = true;
//Con::printf(" alpha mask = %x", pfAlphaMask);
}
else
{
//Con::printf(" no alpha.");
}
mHasTransparency = hasAlpha;
// Try to match a format.
if(hasAlpha)
{
// If it has alpha it is one of...
// GFXFormatR8G8B8A8
// GFXFormatR5G5B5A1
// GFXFormatA8
if(pfBitCount == 32)
mFormat = GFXFormatR8G8B8A8;
else if(pfBitCount == 16)
mFormat = GFXFormatR5G5B5A1;
else if(pfBitCount == 8)
mFormat = GFXFormatA8;
else
{
Con::errorf("DDSFile::readHeader - unable to match alpha RGB format!");
return false;
}
}
else
{
// Otherwise it is one of...
// GFXFormatR8G8B8
// GFXFormatR8G8B8X8
// GFXFormatR5G6B5
// GFXFormatL8
if(pfBitCount == 24)
mFormat = GFXFormatR8G8B8;
else if(pfBitCount == 32)
mFormat = GFXFormatR8G8B8X8;
else if(pfBitCount == 16)
mFormat = GFXFormatR5G6B5;
else if(pfBitCount == 8)
{
// luminance
mFormat = GFXFormatL8;
}
else
{
Con::errorf("DDSFile::readHeader - unable to match non-alpha RGB format!");
return false;
}
}
// Sweet, all done.
}
else if (ddpfFlags & DDPFFourCC)
{
mHasTransparency = (ddpfFlags & DDPFAlphaPixels);
mFlags.set(CompressedData);
/* Con::printf("FourCC Pixel format of DDS:");
Con::printf(" fourcc = '%c%c%c%c'", ((U8*)&pfFourCC)[0], ((U8*)&pfFourCC)[1], ((U8*)&pfFourCC)[2], ((U8*)&pfFourCC)[3]); */
// Ok, make a format determination.
switch(pfFourCC)
{
case FOURCC_DXT1:
mFormat = GFXFormatDXT1;
break;
case FOURCC_DXT2:
mFormat = GFXFormatDXT2;
break;
case FOURCC_DXT3:
mFormat = GFXFormatDXT3;
break;
case FOURCC_DXT4:
mFormat = GFXFormatDXT4;
break;
case FOURCC_DXT5:
mFormat = GFXFormatDXT5;
break;
default:
Con::errorf("DDSFile::readHeader - unknown fourcc = '%c%c%c%c'", ((U8*)&pfFourCC)[0], ((U8*)&pfFourCC)[1], ((U8*)&pfFourCC)[2], ((U8*)&pfFourCC)[3]);
break;
}
}
// Deal with final caps bits... Is this really necessary?
U32 caps1, caps2;
s.read(&caps1);
s.read(&caps2);
s.read(&tmp);
s.read(&tmp); // More icky reserved space.
// Screw caps1.
// if(!(caps1 & DDSCAPS_TEXTURE)))
// {
// }
// Caps2 has cubemap/volume info. Care about that.
if(caps2 & DDSCAPS2Cubemap)
{
mFlags.set(CubeMapFlag);
// Store the face flags too.
if ( caps2 & DDSCAPS2Cubemap_POSITIVEX ) mFlags.set( CubeMap_PosX_Flag );
if ( caps2 & DDSCAPS2Cubemap_NEGATIVEX ) mFlags.set( CubeMap_NegX_Flag );
if ( caps2 & DDSCAPS2Cubemap_POSITIVEY ) mFlags.set( CubeMap_PosY_Flag );
if ( caps2 & DDSCAPS2Cubemap_NEGATIVEY ) mFlags.set( CubeMap_NegY_Flag );
if ( caps2 & DDSCAPS2Cubemap_POSITIVEZ ) mFlags.set( CubeMap_PosZ_Flag );
if ( caps2 & DDSCAPS2Cubemap_NEGATIVEZ ) mFlags.set( CubeMap_NegZ_Flag );
}
// MS has ANOTHER reserved word here. This one particularly sucks.
s.read(&tmp);
return true;
}
bool DDSFile::read(Stream &s, U32 dropMipCount)
{
if( !readHeader(s) || mMipMapCount == 0 )
{
Con::errorf("DDSFile::read - error reading header!");
return false;
}
// If we're droping mips then make sure we have enough.
dropMipCount = getMin( dropMipCount, mMipMapCount - 1 );
// At this point we know what sort of image we contain. So we should
// allocate some buffers, and read it in.
// How many surfaces are we talking about?
if(mFlags.test(CubeMapFlag))
{
mSurfaces.setSize( Cubemap_Surface_Count );
for ( U32 i=0; i < Cubemap_Surface_Count; i++ )
{
// Does the cubemap contain this surface?
if ( mFlags.test( CubeMap_PosX_Flag + ( i << 1 ) ) )
mSurfaces[i] = new SurfaceData();
else
{
mSurfaces[i] = NULL;
continue;
}
// Load all the mips.
for(S32 l=0; l<mMipMapCount; l++)
mSurfaces[i]->readNextMip(this, s, mHeight, mWidth, l, l < dropMipCount );
}
}
else if (mFlags.test(VolumeFlag))
{
// Do something with volume
}
else
{
// It's a plain old texture.
// First allocate a SurfaceData to stick this in.
mSurfaces.push_back(new SurfaceData());
// Load however many mips there are.
for(S32 i=0; i<mMipMapCount; i++)
mSurfaces.last()->readNextMip(this, s, mHeight, mWidth, i, i < dropMipCount);
// Ok, we're done.
}
// If we're dropping mips then fix up the stats.
if ( dropMipCount > 0 )
{
// Fix up the pitch and/or linear size.
if( mFlags.test( LinearSizeFlag ) )
mPitchOrLinearSize = getSurfaceSize( dropMipCount );
else if ( mFlags.test( PitchSizeFlag ) )
mPitchOrLinearSize = getSurfacePitch( dropMipCount );
else
mPitchOrLinearSize = mPitchOrLinearSize; // Do nothing?
// Now fix up the rest of the
mMipMapCount = getMax( (U32)1, mMipMapCount - dropMipCount );
mHeight = getHeight( dropMipCount );
mWidth = getWidth( dropMipCount );
}
return true;
}
bool DDSFile::writeHeader( Stream &s )
{
// Read the FOURCC
s.write( 4, "DDS " );
U32 tmp = 0;
// Read the size of the header.
s.write( 124 );
// Read some flags...
U32 ddsdFlags = DDSDCaps | DDSDPixelFormat | DDSDWidth | DDSDHeight;
if ( mFlags.test( CompressedData ) )
ddsdFlags |= DDSDLinearSize;
else
ddsdFlags |= DDSDPitch;
if ( mMipMapCount > 0 )
ddsdFlags |= DDSDMipMapCount;
s.write( ddsdFlags );
// Read height and width (always present)
s.write( mHeight );
s.write( mWidth );
// Ok, some flags are set, so let's do some reading.
s.write( mPitchOrLinearSize );
// Do we need to read depth? If so, we are a volume texture!
s.write( mDepth );
// Deal with mips!
s.write( mMipMapCount );
// Deal with 11 DWORDS of reserved space (this reserved space brought to
// you by DirectDraw and the letters F and U).
for(U32 i=0; i<11; i++)
s.write( tmp ); // is this right?
// Now we're onto the pixel format!
// This is the size, in bits,
// of the pixel format data.
tmp = 32;
s.write( tmp );
U32 ddpfFlags;
U32 fourCC = 0;
if ( mFlags.test( CompressedData ) )
{
ddpfFlags = DDPFFourCC;
if (mFormat == GFXFormatDXT1)
fourCC = FOURCC_DXT1;
if (mFormat == GFXFormatDXT3)
fourCC = FOURCC_DXT3;
if (mFormat == GFXFormatDXT5)
fourCC = FOURCC_DXT5;
}
else
ddpfFlags = mBytesPerPixel == 4 ? DDPFRGB | DDPFAlphaPixels : DDPFRGB;
s.write( ddpfFlags );
// Read the next few values so we can deal with them all in one go.
//U32 pfFourCC, pfBitCount, pfRMask, pfGMask, pfBMask, pfAlphaMask;
s.write( fourCC );
s.write( mBytesPerPixel * 8 );
s.write( 0x000000FF );
s.write( 0x00FF0000 );
s.write( 0x0000FF00 );
s.write( 0xFF000000 );
// Deal with final caps bits... Is this really necessary?
U32 caps1 = DDSCAPSTexture;
if ( mMipMapCount > 0 )
caps1 |= DDSCAPSComplex | DDSCAPSMipMap;
tmp = 0;
s.write( caps1 );
s.write( tmp );
s.write( tmp );
s.write( tmp );// More icky reserved space.
// MS has ANOTHER reserved word here. This one particularly sucks.
s.write( tmp );
return true;
}
bool DDSFile::write( Stream &s )
{
if(!writeHeader(s))
{
Con::errorf("DDSFile::write - error writing header!");
return false;
}
// At this point we know what sort of image we contain. So we should
// allocate some buffers, and read it in.
// How many surfaces are we talking about?
if(mFlags.test(CubeMapFlag))
{
// Do something with cubemaps.
}
else if (mFlags.test(VolumeFlag))
{
// Do something with volume
}
else
{
// It's a plain old texture.
// Load however many mips there are.
for ( S32 i = 0; i < mMipMapCount; i++ )
mSurfaces.last()->writeNextMip(this, s, mHeight, mWidth, i);
// Ok, we're done.
}
return true;
}
void DDSFile::SurfaceData::dumpImage(DDSFile *dds, U32 mip, const char *file)
{
GBitmap *foo = new GBitmap(dds->mWidth >> mip, dds->mHeight >> mip, false, dds->mFormat);
// Copy our data in.
dMemcpy(foo->getWritableBits(), mMips[mip], dds->getSurfaceSize(dds->mHeight, dds->mWidth, mip) );
FileStream stream;
stream.open( file, Torque::FS::File::Write );
if ( stream.getStatus() == Stream::Ok )
{
// Write it out.
foo->writeBitmap("png", stream);
}
// Clean up.
delete foo;
}
void DDSFile::SurfaceData::readNextMip(DDSFile *dds, Stream &s, U32 height, U32 width, U32 mipLevel, bool skip)
{
U32 size = dds->getSurfaceSize(height, width, mipLevel);
// If we're skipping this mip then seek forward.
if ( skip )
s.setPosition( s.getPosition() + size );
else
{
mMips.push_back(new U8[size]);
if(!s.read(size, mMips.last()))
Con::errorf("DDSFile::SurfaceData::addNextMip - failed to read mip!");
}
}
void DDSFile::SurfaceData::writeNextMip(DDSFile *dds, Stream &s, U32 height, U32 width, U32 mipLevel)
{
U32 size = dds->getSurfaceSize(height, width, mipLevel);
if(!s.write(size, mMips[mipLevel]))
Con::errorf("DDSFile::SurfaceData::writeNextMip - failed to write mip!");
}
//------------------------------------------------------------------------------
template<> void *Resource<DDSFile>::create( const Torque::Path &path )
{
#ifdef TORQUE_DEBUG_RES_MANAGER
Con::printf( "Resource<DDSFile>::create - [%s]", path.getFullPath().c_str() );
#endif
FileStream stream;
stream.open( path.getFullPath(), Torque::FS::File::Read );
if ( stream.getStatus() != Stream::Ok )
return NULL;
DDSFile *retDDS = new DDSFile;
if( !retDDS->read( stream, DDSFile::smDropMipCount ) )
{
delete retDDS;
return NULL;
}
else
{
// Set source file name
retDDS->mSourcePath = path;
retDDS->mCacheString = Torque::Path::Join( path.getRoot(), ':', path.getPath() );
retDDS->mCacheString = Torque::Path::Join( retDDS->mCacheString, '/', path.getFileName() );
}
return retDDS;
}
template<> ResourceBase::Signature Resource<DDSFile>::signature()
{
return MakeFourCC('D','D','S',' '); // Direct Draw Surface
}
Resource<DDSFile> DDSFile::load( const Torque::Path &path, U32 dropMipCount )
{
PROFILE_SCOPE( DDSFile_load );
// HACK: It sucks that we cannot pass parameters into
// the resource manager loading system.
DDSFile::smDropMipCount = dropMipCount;
Resource<DDSFile> ret = ResourceManager::get().load( path );
DDSFile::smDropMipCount = 0;
// Any kind of error checking or path stepping can happen here
return ret;
}
//------------------------------------------------------------------------------
DDSFile *DDSFile::createDDSFileFromGBitmap( const GBitmap *gbmp )
{
if( gbmp == NULL )
return NULL;
DDSFile *ret = new DDSFile;
// Set up the DDSFile properties that matter. Since this is a GBitmap, there
// are assumptions that can be made
ret->mHeight = gbmp->getHeight();
ret->mWidth = gbmp->getWidth();
ret->mDepth = 0;
ret->mFormat = gbmp->getFormat();
ret->mFlags.set(RGBData);
ret->mBytesPerPixel = gbmp->getBytesPerPixel();
ret->mMipMapCount = gbmp->getNumMipLevels();
ret->mHasTransparency = gbmp->getHasTransparency();
// ASSUMPTION!!!
// This _most likely_ does not belong here, but it is safe to assume that if
// a GBitmap is 24-bit, and it's being converted to a DDS, it is most likely
// going to be either:
// a) Uploaded as a 32-bit texture, and just needs to be padded to RGBX
// b) Uploaded as a compressed format, and needs to be padded to 32-bits anyway
if( ret->mFormat == GFXFormatR8G8B8 )
{
ret->mFormat = GFXFormatR8G8B8X8;
ret->mBytesPerPixel = 4;
}
if( ret->mMipMapCount > 1 )
ret->mFlags.set(MipMapsFlag);
// One surface per GBitmap
ret->mSurfaces.push_back( new SurfaceData() );
// Load the mips
for( int i = 0; i < ret->mMipMapCount; i++ )
{
const U32 mipSz = ret->getSurfaceSize(i);
ret->mSurfaces.last()->mMips.push_back( new U8[mipSz] );
U8 *mipMem = ret->mSurfaces.last()->mMips.last();
// If this is a straight copy, just do it, otherwise (ugh)
if( ret->mFormat == gbmp->getFormat() )
dMemcpy( mipMem, gbmp->getBits(i), mipSz );
else
{
// Assumption:
AssertFatal( gbmp->getBytesPerPixel() + 1 == ret->mBytesPerPixel, "Assumption failed, not 24->32 bit straight convert." );
for( int pxl = 0; pxl < gbmp->getWidth(i) * gbmp->getHeight(i); pxl++ )
{
U8 *dst = &mipMem[pxl * ret->mBytesPerPixel];
const U8 *src = &gbmp->getBits(i)[pxl * gbmp->getBytesPerPixel()];
dMemcpy( dst, src, gbmp->getBytesPerPixel() * sizeof(U8) );
dst[ret->mBytesPerPixel - 1] = 255;
}
}
// Uncomment to debug-dump each mip level
//ret->mSurfaces.last()->dumpImage( ret, i, avar( "%d_Gbmp_xmip%d", ret, i ) );
}
return ret;
}
DefineEngineFunction( getActiveDDSFiles, S32, (),,
"Returns the count of active DDSs files in memory.\n"
"@ingroup Rendering\n" )
{
return DDSFile::smActiveCopies;
}

View file

@ -0,0 +1,116 @@
//-----------------------------------------------------------------------------
// 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 "squish/squish.h"
#include "gfx/bitmap/ddsFile.h"
#include "gfx/bitmap/ddsUtils.h"
//------------------------------------------------------------------------------
// If false is returned, from this method, the source DDS is not modified
bool DDSUtil::squishDDS( DDSFile *srcDDS, const GFXFormat dxtFormat )
{
// Sanity check
if( srcDDS->mBytesPerPixel != 4 )
{
AssertFatal( false, "Squish wants 32-bit source data" );
return false;
}
// Build flags, start with fast compress
U32 squishFlags = squish::kColourRangeFit;
// Flag which format we are using
switch( dxtFormat )
{
case GFXFormatDXT1:
squishFlags |= squish::kDxt1;
break;
case GFXFormatDXT2:
case GFXFormatDXT3:
squishFlags |= squish::kDxt3;
break;
case GFXFormatDXT4:
case GFXFormatDXT5:
squishFlags |= squish::kDxt5;
break;
default:
AssertFatal( false, "Assumption failed" );
return false;
break;
}
// We got this far, so assume we can finish (gosh I hope so)
srcDDS->mFormat = dxtFormat;
srcDDS->mFlags.set( DDSFile::CompressedData );
// If this has alpha, set the flag
if( srcDDS->mFormat == GFXFormatR8G8B8A8 )
squishFlags |= squish::kWeightColourByAlpha;
// The source surface is the original surface of the file
DDSFile::SurfaceData *srcSurface = srcDDS->mSurfaces.last();
// Create a new surface, this will be the DXT compressed surface. Once we
// are done, we can discard the old surface, and replace it with this one.
DDSFile::SurfaceData *newSurface = new DDSFile::SurfaceData();
for( int i = 0; i < srcDDS->mMipMapCount; i++ )
{
const U8 *srcBits = srcSurface->mMips[i];
const U32 mipSz = srcDDS->getSurfaceSize(i);
U8 *dstBits = new U8[mipSz];
newSurface->mMips.push_back( dstBits );
PROFILE_START(SQUISH_DXT_COMPRESS);
// Compress with Squish
//
// squish::CompressImageOMP will call squish::CompressImage if OpenMP is
// not enabled.
squish::CompressImageOMP( srcBits, srcDDS->getWidth(i), srcDDS->getHeight(i),
dstBits, squishFlags );
PROFILE_END();
}
// Now delete the source surface, and return.
srcDDS->mSurfaces.pop_back();
delete srcSurface;
srcDDS->mSurfaces.push_back( newSurface );
return true;
}
//------------------------------------------------------------------------------
void DDSUtil::swizzleDDS( DDSFile *srcDDS, const Swizzle<U8, 4> &swizzle )
{
for( int i = 0; i < srcDDS->mMipMapCount; i++ )
{
swizzle.InPlace( srcDDS->mSurfaces.last()->mMips[i], srcDDS->getSurfaceSize( i ) );
}
}

View file

@ -0,0 +1,34 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _DDS_UTILS_H_
#define _DDS_UTILS_H_
struct DDSFile;
namespace DDSUtil
{
bool squishDDS( DDSFile *srcDDS, const GFXFormat dxtFormat );
void swizzleDDS( DDSFile *srcDDS, const Swizzle<U8, 4> &swizzle );
};
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,327 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _GBITMAP_H_
#define _GBITMAP_H_
#ifndef __RESOURCE_H__
#include "core/resource.h"
#endif
#ifndef _SWIZZLE_H_
#include "core/util/swizzle.h"
#endif
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
#ifndef _GFXENUMS_H_
#include "gfx/gfxEnums.h" // For the format
#endif
//-------------------------------------- Forward decls.
class Stream;
class RectI;
class Point2I;
class ColorI;
class ColorF;
//------------------------------------------------------------------------------
//-------------------------------------- GBitmap
class GBitmap
{
public:
enum Constants
{
/// The maximum mipmap levels we support. The current
/// value lets us support up to 4096 x 4096 images.
c_maxMipLevels = 13
};
struct Registration
{
/// The read function prototype.
typedef bool(*ReadFunc)(Stream &stream, 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);
/// Used to sort the registrations so that
/// lookups occur in a fixed order.
U32 priority;
Vector<String> extensions; ///< the list of file extensions for this bitmap type [these should be lower case]
ReadFunc readFunc; ///< the read function to call for this bitmap type
WriteFunc writeFunc; ///< the write function to call for this bitmap type
U32 defaultCompression; ///< the default compression level [levels are image-specific - see their registration declaration for details]
Registration()
{
priority = 0;
VECTOR_SET_ASSOCIATION( extensions );
}
};
/// Load the given bitmap file. It will try known file
/// extensions if one is not specified. If all else fails
/// it will look up the folder hierarchy for a match.
///
/// Important: Don't do something like this...
///
/// @code
/// GBitmap* bitmap; // WRONG TYPE!
/// bitmap = GBitmap::load( filename );
/// @endcode
///
/// Resources are reference-counted and the smart pointer conversion will
/// release the bitmap and thus render the resulting bitmap pointer invalid!
/// The right way is like this:
///
/// @code
/// Resource<GBitmap> bitmap; // Correct!
/// bitmap = GBitmap::load( filename );
/// @endcode
///
static Resource<GBitmap> load(const Torque::Path &path);
protected:
static Resource<GBitmap> _load(const Torque::Path &path);
static Resource<GBitmap> _search(const Torque::Path &path);
public:
GBitmap();
GBitmap(const GBitmap&);
GBitmap(const U32 in_width,
const U32 in_height,
const bool in_extrudeMipLevels = false,
const GFXFormat in_format = GFXFormatR8G8B8 );
// This builds a GBitmap with the R8G8B8A8 format using the passed in
// data (assumes that there is width * height * 4 U8's in data)
GBitmap(const U32 in_width,
const U32 in_height,
const U8* data );
virtual ~GBitmap();
static void sRegisterFormat( const Registration &reg );
static const Registration* sFindRegInfo( const String &extension );
/// Find the first file matching the registered extensions
/// skipping the original.
static bool sFindFile( const Torque::Path &path, Torque::Path *outPath );
/// Given a path to a file, try all known extensions. If the file exists on disk, fill in path
/// with the correct extension and return true. Otherwise, return false.
static bool sFindFiles( const Torque::Path &path, Vector<Torque::Path> *outFoundPaths );
/// Returns a space separated string of all registered extensions.
static String sGetExtensionList();
void allocateBitmap(const U32 in_width,
const U32 in_height,
const bool in_extrudeMipLevels = false,
const GFXFormat in_format = GFXFormatR8G8B8 );
void extrudeMipLevels(bool clearBorders = false);
void extrudeMipLevelsDetail();
U32 getNumMipLevels() const { return mNumMipLevels; }
GBitmap *createPaddedBitmap() const;
GBitmap *createPow2Bitmap() const;
/// Copies a color channel by index into the first channel
/// of the output bitmap. The output bitmap must be the same
/// dimensions as the source.
void copyChannel( U32 index, GBitmap *outBitmap ) const;
void copyRect(const GBitmap *in, const RectI &srcRect, const Point2I &dstPoint, const U32 srcMipLevel = 0, const U32 dstMipLevel = 0);
GFXFormat getFormat() const { return mInternalFormat; }
bool setFormat(GFXFormat fmt);
U32 getWidth(const U32 in_mipLevel = 0) const;
U32 getHeight(const U32 in_mipLevel = 0) const;
U32 getDepth(const U32 in_mipLevel = 0) const;
U8* getAddress(const S32 in_x, const S32 in_y, const U32 mipLevel = 0);
const U8* getAddress(const S32 in_x, const S32 in_y, const U32 mipLevel = 0) const;
const U8* getBits(const U32 in_mipLevel = 0) const;
U8* getWritableBits(const U32 in_mipLevel = 0);
U32 getByteSize() const { return mByteSize; }
U32 getBytesPerPixel() const { return mBytesPerPixel; }
/// Use these functions to set and get the mHasTransparency value
/// This is used to indicate that this bitmap has pixels that have
/// an alpha value less than 255 (used by the auto-Material mapper)
bool getHasTransparency() const { return mHasTransparency; }
void setHasTransparency(bool hasTransparency) { mHasTransparency = hasTransparency; }
/// In general you will want to use this function if there is not a
/// good spot in the bitmap loader(s) to check the alpha value of
/// the pixels. This function uses the texture format to loop over
/// the bitmap bits and to check for alpha values less than 255
bool checkForTransparency();
ColorF sampleTexel(F32 u, F32 v) const;
bool getColor(const U32 x, const U32 y, ColorI& rColor) const;
bool setColor(const U32 x, const U32 y, const ColorI& rColor);
/// This method will combine bitmapA and bitmapB using the operation specified
/// by combineOp. The result will be stored in the bitmap that this method is
/// called on. The size of the resulting bitmap will be the larger of A and B.
/// The format of the resulting bitmap will be the format of A or B, whichever
/// has a larger byte size.
///
/// @note There are some restrictions on ops and formats that will probably change
/// based on how we use this function.
bool combine( const GBitmap *bitmapA, const GBitmap *bitmapB, const GFXTextureOp combineOp );
/// Fills the first mip level of the bitmap with the specified color.
void fill( const ColorI &rColor );
/// An optimized version of fill().
void fillWhite();
//-------------------------------------- Internal data/operators
void deleteImage();
//-------------------------------------- Input/Output interface
/// 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 );
/// 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 readMNG(Stream& io_rStream); // located in bitmapMng.cc
bool writeMNG(Stream& io_rStream) const;
bool read(Stream& io_rStream);
bool write(Stream& io_rStream) const;
template<class T, dsize_t mapLength>
void swizzle(const Swizzle<T,mapLength> *s);
static Vector<Registration> sRegistrations;
private:
GFXFormat mInternalFormat;
U8* mBits; // Master bytes
U32 mByteSize;
U32 mWidth;
U32 mHeight;
U32 mBytesPerPixel;
U32 mNumMipLevels;
U32 mMipLevelOffsets[c_maxMipLevels];
bool mHasTransparency;
static const U32 csFileVersion;
};
//------------------------------------------------------------------------------
//-------------------------------------- Inlines
//
inline U32 GBitmap::getWidth(const U32 in_mipLevel) const
{
AssertFatal(in_mipLevel < mNumMipLevels,
avar("GBitmap::getWidth: mip level out of range: (%d, %d)",
in_mipLevel, mNumMipLevels));
U32 retVal = mWidth >> in_mipLevel;
return (retVal != 0) ? retVal : 1;
}
inline U32 GBitmap::getHeight(const U32 in_mipLevel) const
{
AssertFatal(in_mipLevel < mNumMipLevels,
avar("Bitmap::getHeight: mip level out of range: (%d, %d)",
in_mipLevel, mNumMipLevels));
U32 retVal = mHeight >> in_mipLevel;
return (retVal != 0) ? retVal : 1;
}
inline const U8* GBitmap::getBits(const U32 in_mipLevel) const
{
AssertFatal(in_mipLevel < mNumMipLevels,
avar("GBitmap::getBits: mip level out of range: (%d, %d)",
in_mipLevel, mNumMipLevels));
return &mBits[mMipLevelOffsets[in_mipLevel]];
}
inline U8* GBitmap::getWritableBits(const U32 in_mipLevel)
{
AssertFatal(in_mipLevel < mNumMipLevels,
avar("GBitmap::getWritableBits: mip level out of range: (%d, %d)",
in_mipLevel, mNumMipLevels));
return &mBits[mMipLevelOffsets[in_mipLevel]];
}
inline U8* GBitmap::getAddress(const S32 in_x, const S32 in_y, const U32 mipLevel)
{
return (getWritableBits(mipLevel) + ((in_y * getWidth(mipLevel)) + in_x) * mBytesPerPixel);
}
inline const U8* GBitmap::getAddress(const S32 in_x, const S32 in_y, const U32 mipLevel) const
{
return (getBits(mipLevel) + ((in_y * getWidth(mipLevel)) + in_x) * mBytesPerPixel);
}
template<class T, dsize_t mapLength>
void GBitmap::swizzle(const Swizzle<T,mapLength> *s )
{
const U32 memSize = getWidth() * getHeight() * mBytesPerPixel;
void *b = dMalloc(memSize);
s->ToBuffer(b, getWritableBits(), memSize);
dMemcpy(getWritableBits(), b, memSize);
dFree(b);
}
#endif //_GBITMAP_H_

View file

@ -0,0 +1,245 @@
//-----------------------------------------------------------------------------
// 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)
{
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(int 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

@ -0,0 +1,231 @@
//-----------------------------------------------------------------------------
// 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/util/path.h"
#include "gfx/bitmap/gBitmap.h"
// This must come after our headers due to a conflicting definition of VoidPtr
#include "lungif/gif_lib.h"
using namespace Torque;
static bool sReadGIF(Stream &stream, GBitmap *bitmap);
static bool sWriteGIF(GBitmap *bitmap, Stream &stream, U32 compressionLevel);
static struct _privateRegisterGIF
{
_privateRegisterGIF()
{
GBitmap::Registration reg;
reg.extensions.push_back( "gif" );
reg.readFunc = sReadGIF;
reg.writeFunc = sWriteGIF;
GBitmap::sRegisterFormat( reg );
}
} sStaticRegisterGIF;
//-------------------------------------- Replacement I/O for standard LIBjpeg
// functions. we don't wanna use
// FILE*'s...
static int gifReadDataFn(GifFileType *gifinfo, GifByteType *data, int length)
{
Stream *stream = (Stream*)gifinfo->UserData;
AssertFatal(stream != NULL, "gifReadDataFn::No stream.");
int pos = stream->getPosition();
if (stream->read(length, data))
return length;
if (stream->getStatus() == Stream::EOS)
return (stream->getPosition()-pos);
else
return 0;
}
//--------------------------------------
#if 0
// CodeReview - until we can write these, get rid of warning by disabling method.
static int gifWriteDataFn(GifFileType *gifinfo, GifByteType *data, int length)
{
Stream *stream = (Stream*)gifinfo->UserData;
AssertFatal(stream != NULL, "gifWriteDataFn::No stream.");
if (stream->write(length, data))
return length;
else
return 0;
}
#endif
//--------------------------------------
static bool sReadGIF( Stream &stream, GBitmap *bitmap )
{
GifFileType *gifinfo = DGifOpen( (void*)&stream, gifReadDataFn);
if (!gifinfo)
return false;
GifRecordType recordType;
do
{
if (DGifGetRecordType(gifinfo, &recordType) == GIF_ERROR)
break;
if (recordType == IMAGE_DESC_RECORD_TYPE)
{
if (DGifGetImageDesc(gifinfo) == GIF_ERROR)
break;
GFXFormat format = (gifinfo->SBackGroundColor == 0 ) ? GFXFormatR8G8B8 : GFXFormatR8G8B8A8;
bitmap->allocateBitmap(gifinfo->SWidth, gifinfo->SHeight, false, format);
// Assume no transparency until proven otherwise
bitmap->setHasTransparency(false);
U32 gwidth = gifinfo->Image.Width ? gifinfo->Image.Width : bitmap->getWidth();
U32 gheight= gifinfo->Image.Height ? gifinfo->Image.Height : bitmap->getHeight();
U32 gifSize = gwidth * gheight;
U8 *data = new U8[gifSize];
if (DGifGetLine(gifinfo, data, gifSize) != GIF_ERROR)
{
// use the global or local color table ?
GifColorType *color = NULL;
if (gifinfo->Image.ColorMap)
color = gifinfo->Image.ColorMap->Colors;
else if (gifinfo->SColorMap)
color = gifinfo->SColorMap->Colors;
if (color)
{
U8 *dst = bitmap->getAddress(gifinfo->Image.Left, gifinfo->Image.Top);
U8 *src = data;
U32 right = gifinfo->Image.Left + gwidth;
U32 bottom = gifinfo->Image.Top + gheight;
U32 next = (bitmap->getWidth() - gwidth) * bitmap->getBytesPerPixel();
if (format == GFXFormatR8G8B8A8)
{
for (U32 y=gifinfo->Image.Top; y<bottom; y++)
{
for (U32 x=gifinfo->Image.Left; x<right; x++, src++)
{
if (*src == gifinfo->SBackGroundColor)
{
// this is a transparent pixel
dst[0] = 0; // red
dst[1] = 0; // green
dst[2] = 0; // blue
dst[3] = 0; // alpha
bitmap->setHasTransparency(true);
}
else
{
dst[0] = color[*src].Red;
dst[1] = color[*src].Green;
dst[2] = color[*src].Blue;
dst[3] = 0; // alpha
}
dst += bitmap->getBytesPerPixel();
}
dst += next;
}
}
else
{
for (U32 y=gifinfo->Image.Top; y<bottom; y++)
{
for (U32 x=gifinfo->Image.Left; x<right; x++, src++)
{
dst[0] = color[*src].Red;
dst[1] = color[*src].Green;
dst[2] = color[*src].Blue;
dst += bitmap->getBytesPerPixel();
}
dst += next;
}
}
delete [] data;
DGifCloseFile(gifinfo);
return true;
}
}
// failure
delete [] data;
break;
}
else if (recordType == EXTENSION_RECORD_TYPE)
{
GifByteType *extension;
S32 extCode;
// Skip any extension blocks in file
if (DGifGetExtension(gifinfo, &extCode, &extension) != GIF_ERROR)
{
while (extension != NULL)
{
if (DGifGetExtensionNext(gifinfo, &extension) == GIF_ERROR)
{
return false;
}
}
}
else
{
return false;
}
}
// There used to be a break right here. This caused the while condition to
// never get processed, and so it never looped through all the records in
// the GIF. I took a quick peek back at TGB and TGE histories and I am not
// sure where this change got made, but I can't figure out why the loading
// worked at all, ever, with that break in there. The only case I can think
// of is if the first record in the GIF was the bitmap data.
// [6/6/2007 Pat]
}while (recordType != TERMINATE_RECORD_TYPE);
DGifCloseFile(gifinfo);
return true;
}
//--------------------------------------------------------------------------
static bool sWriteGIF(GBitmap *bitmap, Stream &stream, U32 compressionLevel)
{
TORQUE_UNUSED( bitmap );
TORQUE_UNUSED( stream );
TORQUE_UNUSED( compressionLevel );
return false;
}

View file

@ -0,0 +1,250 @@
//-----------------------------------------------------------------------------
// 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 int jpegReadDataFn(void *client_data, unsigned char *data, int length)
{
Stream *stream = (Stream*)client_data;
AssertFatal(stream != NULL, "jpegReadDataFn::No stream.");
int pos = stream->getPosition();
if (stream->read(length, data))
return length;
if (stream->getStatus() == Stream::EOS)
return (stream->getPosition()-pos);
else
return 0;
}
//--------------------------------------
static int jpegWriteDataFn(void *client_data, unsigned char *data, int length)
{
Stream *stream = (Stream*)client_data;
AssertFatal(stream != NULL, "jpegWriteDataFn::No stream.");
if (stream->write(length, data))
return length;
else
return 0;
}
//--------------------------------------
static int jpegFlushDataFn(void *)
{
// do nothing since we can't flush the stream object
return 0;
}
//--------------------------------------
static int 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)
{
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 + (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

@ -0,0 +1,421 @@
//-----------------------------------------------------------------------------
// 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"
#include "core/color.h"
#define MNG_NO_CMS
#define MNG_SUPPORT_READ
#define MNG_SUPPORT_WRITE
#define MNG_SUPPORT_DISPLAY
#define MNG_STORE_CHUNKS
#define MNG_ACCESS_CHUNKS
#include "lmng/libmng.h"
static bool sReadMNG(Stream &stream, GBitmap *bitmap);
static bool sWriteMNG(GBitmap *bitmap, Stream &stream, U32 compressionLevel);
static struct _privateRegisterMNG
{
_privateRegisterMNG()
{
GBitmap::Registration reg;
reg.extensions.push_back( "jng" );
reg.extensions.push_back( "mng" );
reg.readFunc = sReadMNG;
reg.writeFunc = sWriteMNG;
GBitmap::sRegisterFormat( reg );
}
} sStaticRegisterMNG;
typedef struct
{
GBitmap* image;
Stream* stream;
} mngstuff;
static mng_ptr mngMallocFn(mng_size_t size)
{
mng_ptr data = dMalloc(size);
return dMemset(data, 0, size);
}
static void mngFreeFn(mng_ptr p, mng_size_t size)
{
dFree(p);
}
static mng_bool mngOpenDataFn(mng_handle mng)
{
return MNG_TRUE;
}
static mng_bool mngCloseDataFn(mng_handle mng)
{
return MNG_TRUE;
}
static mng_bool mngReadDataFn(mng_handle mng, mng_ptr data, mng_uint32 length, mng_uint32 *bytesread)
{
mngstuff *mymng = (mngstuff *)mng_get_userdata(mng);
AssertFatal(mymng->stream != NULL, "No stream?");
bool success = mymng->stream->read(length, data);
*bytesread = length; // stupid hack
AssertFatal(success, "MNG read catastrophic error!");
if(success)
return MNG_TRUE;
else
return MNG_FALSE;
}
#if 0
// CodeReview - until we can write these, get rid of warning by disabling method.
static mng_bool mngWriteDataFn(mng_handle mng, mng_ptr data, mng_uint32 length, mng_uint32 *iWritten)
{
mngstuff *mymng = (mngstuff *)mng_get_userdata(mng);
AssertFatal(mymng->stream != NULL, "No stream?");
bool success = mymng->stream->write(length, data);
*iWritten = length; // stupid hack
AssertFatal(success, "MNG write catastrophic error!");
if(success)
return MNG_TRUE;
else
return MNG_FALSE;
}
#endif
static mng_bool mngProcessHeaderFn(mng_handle mng, mng_uint32 width, mng_uint32 height)
{
mngstuff *mymng = (mngstuff *)mng_get_userdata(mng);
GFXFormat format;
mng_uint8 colorType = mng_get_colortype(mng);
mng_uint8 alphaDepth = mng_get_alphadepth(mng);
switch(colorType)
{
case MNG_COLORTYPE_GRAY:
case MNG_COLORTYPE_JPEGGRAY:
format = GFXFormatR8G8B8;
mng_set_canvasstyle(mng, MNG_CANVAS_RGB8);
break;
case MNG_COLORTYPE_INDEXED:
if(alphaDepth >= 1)
{
format = GFXFormatR8G8B8A8;
mng_set_canvasstyle(mng, MNG_CANVAS_RGBA8);
}
else
{
format = GFXFormatR8G8B8;
mng_set_canvasstyle(mng, MNG_CANVAS_RGB8);
}
case MNG_COLORTYPE_RGB:
case MNG_COLORTYPE_JPEGCOLOR:
if(alphaDepth >= 1)
{
format = GFXFormatR8G8B8A8;
mng_set_canvasstyle(mng, MNG_CANVAS_RGBA8);
}
else
{
format = GFXFormatR8G8B8;
mng_set_canvasstyle(mng, MNG_CANVAS_RGB8);
}
break;
case MNG_COLORTYPE_RGBA:
case MNG_COLORTYPE_JPEGCOLORA:
format = GFXFormatR8G8B8A8;
mng_set_canvasstyle(mng, MNG_CANVAS_RGBA8);
break;
default:
// This case should never get hit, however it resolves a compiler
// warning
format = GFXFormat_FIRST;
AssertISV( false, "Unknown color format in bitmap MNG Loading" );
}
mymng->image->allocateBitmap(width, height, false, format);
return MNG_TRUE;
}
static mng_ptr mngCanvasLineFn(mng_handle mng, mng_uint32 line)
{
mngstuff *mymng = (mngstuff *)mng_get_userdata(mng);
return (mng_ptr) mymng->image->getAddress(0, line);
}
static mng_bool mngRefreshFn(mng_handle mng, mng_uint32 x, mng_uint32 y, mng_uint32 w, mng_uint32 h)
{
return MNG_TRUE;
}
static mng_uint32 mngGetTicksFn(mng_handle mng)
{
return 0;
}
static mng_bool mngSetTimerFn(mng_handle mng, mng_uint32 msecs)
{
return MNG_TRUE;
}
static mng_bool mngFatalErrorFn(mng_handle mng, mng_int32 code, mng_int8 severity, mng_chunkid chunktype, mng_uint32 chunkseq, mng_int32 extra1, mng_int32 extra2, mng_pchar text)
{
mng_cleanup(&mng);
AssertISV(false, avar("Error reading MNG file:\n %s", (const char*)text));
return MNG_FALSE;
}
static bool sReadMNG(Stream &stream, GBitmap *bitmap)
{
mngstuff mnginfo;
dMemset(&mnginfo, 0, sizeof(mngstuff));
mng_handle mng = mng_initialize(&mnginfo, mngMallocFn, mngFreeFn, MNG_NULL);
if(mng == NULL)
return false;
// setup the callbacks
mng_setcb_errorproc(mng, mngFatalErrorFn);
mng_setcb_openstream(mng, mngOpenDataFn);
mng_setcb_closestream(mng, mngCloseDataFn);
mng_setcb_readdata(mng, mngReadDataFn);
mng_setcb_processheader(mng, mngProcessHeaderFn);
mng_setcb_getcanvasline(mng, mngCanvasLineFn);
mng_setcb_refresh(mng, mngRefreshFn);
mng_setcb_gettickcount(mng, mngGetTicksFn);
mng_setcb_settimer(mng, mngSetTimerFn);
mnginfo.image = bitmap;
mnginfo.stream = &stream;
mng_read(mng);
mng_display(mng);
// hacks :(
// libmng doesn't support returning data in gray/gray alpha format,
// so we grab as RGB/RGBA and just cut off the g and b
mng_uint8 colorType = mng_get_colortype(mng);
switch(colorType)
{
case MNG_COLORTYPE_GRAY:
case MNG_COLORTYPE_JPEGGRAY:
{
GBitmap temp(*bitmap);
bitmap->deleteImage();
bitmap->allocateBitmap(temp.getWidth(), temp.getHeight(), false, GFXFormatA8);
// force getColor to read in in the same color value for each channel
// since the gray colortype has the real alpha in the first channel
temp.setFormat( GFXFormatA8 );
ColorI color;
for(U32 row = 0; row < bitmap->getHeight(); row++)
{
for(U32 col = 0; col < bitmap->getWidth(); col++)
{
temp.getColor(col, row, color);
bitmap->setColor(col, row, color);
}
}
}
break;
}
mng_cleanup(&mng);
// Check this bitmap for transparency
bitmap->checkForTransparency();
return true;
}
static bool sWriteMNG(GBitmap *bitmap, Stream &stream, U32 compressionLevel)
{
TORQUE_UNUSED( bitmap );
TORQUE_UNUSED( stream );
TORQUE_UNUSED( compressionLevel );
return false;
#if 0
// ONLY RGB bitmap writing supported at this time!
AssertFatal(getFormat() == GFXFormatR8G8B8 || getFormat() == GFXFormatR8G8B8A8 || getFormat() == GFXFormatA8, "GBitmap::writeMNG: ONLY RGB bitmap writing supported at this time.");
if(getFormat() != GFXFormatR8G8B8 && getFormat() != GFXFormatR8G8B8A8 && getFormat() != GFXFormatA8)
return (false);
// maximum image size allowed
#define MAX_HEIGHT 4096
if(getHeight() >= MAX_HEIGHT)
return false;
mngstuff mnginfo;
dMemset(&mnginfo, 0, sizeof(mngstuff));
mng_handle mng = mng_initialize(&mnginfo, mngMallocFn, mngFreeFn, MNG_NULL);
if(mng == NULL) {
return false;
}
// setup the callbacks
mng_setcb_openstream(mng, mngOpenDataFn);
mng_setcb_closestream(mng, mngCloseDataFn);
mng_setcb_writedata(mng, mngWriteDataFn);
// create the file in memory
mng_create(mng);
mng_putchunk_defi(mng, 0, 0, 0, MNG_FALSE, 0, 0, MNG_FALSE, 0, getWidth(), 0, getHeight());
mnginfo.image = (GBitmap*)this;
mnginfo.stream = &stream;
switch(getFormat()) {
case GFXFormatA8:
mng_putchunk_ihdr(mng, getWidth(), getHeight(),
MNG_BITDEPTH_8,
MNG_COLORTYPE_GRAY,
MNG_COMPRESSION_DEFLATE,
MNG_FILTER_ADAPTIVE,
MNG_INTERLACE_NONE);
// not implemented in lib yet
//mng_putimgdata_ihdr(mng, getWidth(), getHeight(),
// MNG_COLORTYPE_GRAY,
// MNG_BITDEPTH_8,
// MNG_COMPRESSION_DEFLATE,
// MNG_FILTER_ADAPTIVE,
// MNG_INTERLACE_NONE,
// MNG_CANVAS_GRAY8, mngCanvasLineFn);
break;
case GFXFormatR8G8B8:
mng_putchunk_ihdr(mng, getWidth(), getHeight(),
MNG_BITDEPTH_8,
MNG_COLORTYPE_RGB,
MNG_COMPRESSION_DEFLATE,
MNG_FILTER_ADAPTIVE,
MNG_INTERLACE_NONE);
// not implemented in lib yet
//mng_putimgdata_ihdr(mng, getWidth(), getHeight(),
// MNG_COLORTYPE_RGB,
// MNG_BITDEPTH_8,
// MNG_COMPRESSION_DEFLATE,
// MNG_FILTER_ADAPTIVE,
// MNG_INTERLACE_NONE,
// MNG_CANVAS_RGB8, mngCanvasLineFn);
break;
case GFXFormatR8G8B8A8:
mng_putchunk_ihdr(mng, getWidth(), getHeight(),
MNG_BITDEPTH_8,
MNG_COLORTYPE_RGBA,
MNG_COMPRESSION_DEFLATE,
MNG_FILTER_ADAPTIVE,
MNG_INTERLACE_NONE);
// not implemented in lib yet
//mng_putimgdata_ihdr(mng, getWidth(), getHeight(),
// MNG_COLORTYPE_RGBA,
// MNG_BITDEPTH_8,
// MNG_COMPRESSION_DEFLATE,
// MNG_FILTER_ADAPTIVE,
// MNG_INTERLACE_NONE,
// MNG_CANVAS_RGBA8, mngCanvasLineFn);
break;
}
// below is a hack until libmng is mature enough to handle this itself
//-----------------------------------------------------------------------------
U8 *tmpbuffer = new U8[this->byteSize + getHeight()];
if(tmpbuffer == 0)
{
mng_cleanup(&mng);
return false;
}
// transfer data, add filterbyte
U32 effwdt = getWidth() * this->bytesPerPixel;
for(U32 Row = 0; Row < getHeight(); Row++)
{
// first Byte in each scanline is filterbyte: currently 0 -> no filter
tmpbuffer[Row * (effwdt + 1)] = 0;
// copy the scanline
dMemcpy(tmpbuffer + Row * (effwdt + 1) + 1, getAddress(0, Row), effwdt);
}
// compress data with zlib
U8 *dstbuffer = new U8[this->byteSize + getHeight()];
if(dstbuffer == 0)
{
delete [] tmpbuffer;
mng_cleanup(&mng);
return false;
}
U32 dstbufferSize = this->byteSize + getHeight();
if(Z_OK != compress2((Bytef*)dstbuffer,(uLongf*)&dstbufferSize, (const Bytef*)tmpbuffer, dstbufferSize, 9))
{
delete [] tmpbuffer;
delete [] dstbuffer;
mng_cleanup(&mng);
return false;
}
mng_putchunk_idat(mng, dstbufferSize, (mng_ptr*)dstbuffer);
//-----------------------------------------------------------------------------
mng_putchunk_iend(mng);
delete [] tmpbuffer;
delete [] dstbuffer;
mng_write(mng);
mng_cleanup(&mng);
return true;
#endif
}

View file

@ -0,0 +1,647 @@
//-----------------------------------------------------------------------------
// 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_ptr->io_ptr != NULL, "No stream?");
Stream *strm = (Stream*)png_ptr->io_ptr;
bool success = strm->read(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_ptr->io_ptr != NULL, "No stream?");
Stream *strm = (Stream*)png_ptr->io_ptr;
bool success = strm->write(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(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)
{
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 == false)
{
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 = GFXFormatR5G6B5;
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_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 == GFXFormatR5G6B5)
{
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, "_writePNG: ONLY RGB bitmap writing supported at this time.");
if ( format != GFXFormatR8G8B8 &&
format != GFXFormatR8G8B8A8 &&
format != GFXFormatR8G8B8X8 &&
format != GFXFormatA8 &&
format != GFXFormatR5G6B5 )
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, image filters, and compression strategy...
png_ptr->flags |= PNG_FLAG_ZLIB_CUSTOM_STRATEGY;
png_ptr->zlib_strategy = strategy;
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)
{
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, image filters, and compression strategy...
mData->png_ptr->flags |= PNG_FLAG_ZLIB_CUSTOM_STRATEGY;
mData->png_ptr->zlib_strategy = 0;
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,489 @@
//-----------------------------------------------------------------------------
// 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)
{
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" );
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;
}

View file

@ -0,0 +1,49 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _PNG_UTILS_H_
#define _PNG_UTILS_H_
#ifndef _GFXENUMS_H_
#include "gfx/gfxEnums.h"
#endif
struct DeferredPNGWriterData; // This is used to avoid including png.h in this header
class GBitmap;
class Stream;
/// This class is used to write PNGs in row batches
class DeferredPNGWriter {
protected:
DeferredPNGWriterData *mData;
bool mActive;
public:
DeferredPNGWriter();
~DeferredPNGWriter();
bool begin( GFXFormat format, S32 width, S32 height, Stream &stream, U32 compressionLevel );
void append( GBitmap* bitmap, U32 rows );
void end();
};
#endif