You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

2255 lines
61 KiB

/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Additional permissions under GNU GPL version 3 section 7
If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
///////////////////////////////////////////////////////////////////////////////
// //
// plMipmap Class Functions //
// Derived bitmap class representing a single mipmap. //
// Cyan, Inc. //
// //
//// Version History //////////////////////////////////////////////////////////
// //
// 6.7.2001 mcn - Created. //
// //
///////////////////////////////////////////////////////////////////////////////
#include "hsTypes.h"
#include "plMipmap.h"
#include "hsStream.h"
#include "hsExceptions.h"
#include "hsUtils.h"
#include "hsColorRGBA.h"
#include "../plPipeline/hsGDeviceRef.h"
#include "plProfile.h"
#include "../plJPEG/plJPEG.h"
#include "plPNG.h"
plProfile_CreateMemCounter("Mipmaps", "Memory", MemMipmaps);
//// Constructor & Destructor /////////////////////////////////////////////////
plMipmap::plMipmap() : fImage( nil ), fLevelSizes( nil ), fCurrLevelPtr( nil ), fCurrLevel( 0 ), fTotalSize( 0 )
{
SetConfig( kARGB32Config );
fCompressionType = kUncompressed;
fUncompressedInfo.fType = UncompressedInfo::kRGB8888;
#ifdef MEMORY_LEAK_TRACER
fNumMipmaps++;
#endif
}
plMipmap::~plMipmap()
{
Reset();
#ifdef MEMORY_LEAK_TRACER
fNumMipmaps--;
if( fNumMipmaps == 0 )
IReportLeaks();
#endif
}
plMipmap::plMipmap( UInt32 width, UInt32 height, unsigned config, UInt8 numLevels, UInt8 compType, UInt8 format )
{
Create( width, height, config, numLevels, compType, format );
#ifdef MEMORY_LEAK_TRACER
fNumMipmaps++;
#endif
}
//// Create ///////////////////////////////////////////////////////////////////
void plMipmap::Create( UInt32 width, UInt32 height, unsigned config, UInt8 numLevels, UInt8 compType, UInt8 format )
{
int i;
SetConfig( config );
fWidth = width;
fHeight = height;
fRowBytes = fWidth * fPixelSize >> 3;
if( numLevels > 0 )
fNumLevels = numLevels;
else
{
for( fNumLevels = 1; width > 1 || height > 1; fNumLevels++ )
{
if( width > 1 )
width >>= 1;
if( height > 1 )
height >>= 1;
}
}
fCompressionType = compType;
if( compType == kUncompressed || compType == kJPEGCompression ||
compType == kPNGCompression )
{
fUncompressedInfo.fType = format;
}
else
{
fDirectXInfo.fBlockSize = ( format == DirectXInfo::kDXT1 ) ? 8 : 16;
fDirectXInfo.fCompressionType = format;
if( format == DirectXInfo::kDXT1 )
{
// Has an alpha bit, but no channel
fFlags |= kAlphaBitFlag;
fFlags &= ~kAlphaChannelFlag;
}
else // All other formats have an actual alpha channel
{
fFlags &= ~kAlphaBitFlag;
fFlags |= kAlphaChannelFlag;
}
}
fLevelSizes = nil;
IBuildLevelSizes();
fTotalSize = 0;
for( i = 0; i < fNumLevels; i++ )
fTotalSize += fLevelSizes[ i ];
fImage = (void *)TRACKED_NEW UInt8[ fTotalSize ];
memset(fImage, 0, fTotalSize);
SetCurrLevel( 0 );
plProfile_NewMem(MemMipmaps, fTotalSize);
#ifdef MEMORY_LEAK_TRACER
IAddToMemRecord( this, plRecord::kViaCreate );
#endif
}
//// Reset ////////////////////////////////////////////////////////////////////
void plMipmap::Reset()
{
delete [] fLevelSizes;
fLevelSizes = nil;
if( !( fFlags & kUserOwnsBitmap ) )
{
#ifdef MEMORY_LEAK_TRACER
if( fImage != nil )
IRemoveFromMemRecord( (UInt8 *)fImage );
#endif
delete [] fImage;
plProfile_DelMem(MemMipmaps, fTotalSize);
}
fImage = nil;
}
///////////////////////////////////////////////////////////////////////////////
//// Virtual Functions ////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//// GetTotalSize /////////////////////////////////////////////////////////////
// Get the total size in bytes
UInt32 plMipmap::GetTotalSize() const
{
return fTotalSize;
}
//// Read /////////////////////////////////////////////////////////////////////
UInt32 plMipmap::Read( hsStream *s )
{
UInt32 totalRead = plBitmap::Read( s );
// Decide to clamp if we were told to
int clampBy = fGlobalNumLevelsToChop;
const int kMaxSkipLevels = 1;
if( clampBy > kMaxSkipLevels )
clampBy = kMaxSkipLevels;
if( fFlags & kNoMaxSize )
clampBy = 0;
else if( ( fFlags & kHalfSize ) && clampBy > 1 )
clampBy = 1;
UInt32 amtToSkip = 0;
fWidth = s->ReadSwap32();
fHeight = s->ReadSwap32();
fRowBytes = s->ReadSwap32();
fTotalSize = s->ReadSwap32();
fNumLevels = s->ReadByte();
totalRead += 4 * 4 + 1;
if( fTotalSize == 0 )
fImage = nil;
else
{
IBuildLevelSizes();
if (fCompressionType != kJPEGCompression) // JPEGs don't play nicely with quality settings the way they are written, so we ignore them
{
if( clampBy > 0 )
{
int i;
for( i = 0; i < clampBy && fNumLevels > 1 && fWidth > 4 && fHeight > 4; i++ )
{
amtToSkip += fLevelSizes[ i ];
fWidth >>= 1;
fHeight >>= 1;
fRowBytes >>= 1;
fNumLevels--;
}
fTotalSize -= amtToSkip;
IBuildLevelSizes();
}
}
fImage = (void *)TRACKED_NEW UInt8[ fTotalSize ];
#ifdef MEMORY_LEAK_TRACER
IAddToMemRecord( this, plRecord::kViaRead );
#endif
plProfile_NewMem(MemMipmaps, fTotalSize);
switch( fCompressionType )
{
case kDirectXCompression:
s->Skip( amtToSkip );
s->Read( fTotalSize, fImage );
break;
case kUncompressed:
s->Skip( amtToSkip );
IReadRawImage( s );
break;
case kJPEGCompression:
IReadJPEGImage( s );
break;
case kPNGCompression:
IReadPNGImage( s );
break;
default:
hsAssert( false, "Unknown compression type in plMipmap::Read()" );
return totalRead;
}
totalRead += fTotalSize;
}
return totalRead;
}
//// Write ////////////////////////////////////////////////////////////////////
UInt32 plMipmap::Write( hsStream *s )
{
UInt32 totalWritten = plBitmap::Write( s );
s->WriteSwap32( fWidth );
s->WriteSwap32( fHeight );
s->WriteSwap32( fRowBytes );
s->WriteSwap32( fTotalSize );
s->WriteByte( fNumLevels );
totalWritten += 4 * 4 + 1;
if( fTotalSize > 0 )
{
switch( fCompressionType )
{
case kDirectXCompression:
s->Write( fTotalSize, fImage );
break;
case kUncompressed:
IWriteRawImage( s );
break;
case kJPEGCompression:
IWriteJPEGImage( s );
break;
case kPNGCompression:
IWritePNGImage( s );
break;
default:
hsAssert( false, "Unknown compression type in plMipmap::Read()" );
return totalWritten;
}
totalWritten += fTotalSize;
}
return totalWritten;
}
///////////////////////////////////////////////////////////////////////////////
//// Utility Functions ////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//// IReadRawImage ////////////////////////////////////////////////////////////
void plMipmap::IReadRawImage( hsStream *stream )
{
UInt32 i;
UInt8 *data = (UInt8 *)fImage;
switch( fPixelSize )
{
case 32:
for( i = 0; i < fNumLevels; i++ )
{
stream->ReadSwap32( fLevelSizes[ i ] >> 2, (UInt32 *)data );
data += fLevelSizes[ i ];
}
break;
case 16:
for( i = 0; i < fNumLevels; i++ )
{
stream->ReadSwap16( fLevelSizes[ i ] >> 1, (UInt16 *)data );
data += fLevelSizes[ i ];
}
break;
default:
hsThrow( hsBadParamException() );
}
}
//// IWriteRawImage ///////////////////////////////////////////////////////////
void plMipmap::IWriteRawImage( hsStream *stream )
{
UInt32 i;
UInt8 *data = (UInt8 *)fImage;
switch( fPixelSize )
{
case 32:
for( i = 0; i < fNumLevels; i++ )
{
stream->WriteSwap32( fLevelSizes[ i ] >> 2, (UInt32 *)data );
data += fLevelSizes[ i ];
}
break;
case 16:
for( i = 0; i < fNumLevels; i++ )
{
stream->WriteSwap16( fLevelSizes[ i ] >> 1, (UInt16 *)data );
data += fLevelSizes[ i ];
}
break;
default:
hsThrow( hsBadParamException() );
}
}
plMipmap *plMipmap::ISplitAlpha()
{
plMipmap *retVal = TRACKED_NEW plMipmap();
retVal->CopyFrom(this);
memset( retVal->fImage, 0, fTotalSize );
UInt8 *curLoc = (UInt8 *)fImage;
UInt8 *destLoc = (UInt8 *)retVal->fImage;
UInt32 numBytes = fTotalSize;
UInt32 curByte = 0;
switch( fUncompressedInfo.fType )
{
case fUncompressedInfo.kRGB8888:
// first byte is the alpha channel, we will drop this byte into the red channel for compression
while (curByte < numBytes)
{
curLoc += 3;
destLoc += 2; // make the destination pointer point at the red channel
*destLoc = *curLoc; // copy the information
destLoc += 2; // make the destination pointer point at the beginning of the next pixel
curLoc ++; // same here for source pointer
curByte += 4;
}
break;
default:
break; // not going to mess with other formats for now
}
return retVal;
}
// alphaChannel must be in the format generated from ISplitAlpha, or strange things will happen
void plMipmap::IRecombineAlpha( plMipmap *alphaChannel )
{
UInt8 *curLoc = (UInt8 *)alphaChannel->fImage;
UInt8 *destLoc = (UInt8 *)fImage;
UInt32 numBytes = fTotalSize;
UInt32 curByte = 0;
switch( fUncompressedInfo.fType )
{
case fUncompressedInfo.kRGB8888:
// first byte is the alpha channel, we will grab this byte from the red channel for reconstitution
while (curByte < numBytes)
{
curLoc += 2; // pointer points at the red channel (where the alpha is stored)
destLoc += 3;
*destLoc = *curLoc; // copy the data
destLoc++; // move the pointer to the next pixel
curLoc += 2;
curByte += 4;
}
break;
default:
break; // not going to mess with other formats for now
}
fFlags |= plBitmap::kAlphaChannelFlag;
}
plMipmap *plMipmap::IReadRLEImage( hsStream *stream )
{
UInt32 count,color;
bool done = false;
plMipmap *retVal = TRACKED_NEW plMipmap(fWidth,fHeight,plMipmap::kARGB32Config,1);
UInt32 *curPos = (UInt32*)retVal->fImage;
UInt32 curLoc = 0;
while (!done)
{
count = stream->ReadSwap32();
color = stream->ReadSwap32();
if (count == 0)
done = true;
else
{
for (UInt32 i=0; i<count; i++)
{
*curPos = color;
curPos++;
curLoc++;
}
}
}
// We really don't want to suddenly start calling this uncompressed now that it's read in.
// Case in point, on export we load in all previously exported textures (like this JPEG one)
// share those, add any textures that aren't already there, then write the whole thing back
// out. Viola, we just converted our nice small compressed 1024x1024 JPEG (~128k) to a
// monster uncompressed 4Mb which it will remain for ever more.
// retVal->fCompressionType = kUncompressed;
return retVal;
}
void plMipmap::IWriteRLEImage( hsStream *stream, plMipmap *mipmap )
{
UInt32 count=0,color=0,curColor=0;
UInt32 curPos = 0;
UInt32 totalSize = mipmap->fLevelSizes[0]/4; // we only save the first mipmap level
UInt32 *src = (UInt32*)mipmap->fImage;
curColor = *src;
curColor &= 0x00FFFFFF; // strip the alpha (if there is any)
while (curPos < totalSize)
{
color = *src;
color &= 0x00FFFFFF; // strip the alpha (if there is any)
if (color != curColor)
{
stream->WriteSwap32(count);
stream->WriteSwap32(curColor);
count = 0;
curColor = color;
}
count++;
src++;
curPos++;
}
stream->WriteSwap32(count);
stream->WriteSwap32(color);
stream->WriteSwap32(0); // terminate with zero count
stream->WriteSwap32(0);
}
void plMipmap::IReadJPEGImage( hsStream *stream )
{
UInt8 flags = 0;
flags = stream->ReadByte();
plMipmap *temp = nil;
plMipmap *alpha = nil;
if (flags & kColorDataRLE)
temp = IReadRLEImage(stream);
else
temp = plJPEG::Instance().ReadFromStream(stream);
if (temp)
{
// copy the data
CopyFrom(temp);
if (flags & kAlphaDataRLE)
alpha = IReadRLEImage(stream);
else
alpha = plJPEG::Instance().ReadFromStream(stream);
if (alpha)
{
IRecombineAlpha(alpha);
delete alpha;
}
delete temp;
}
}
void plMipmap::IWriteJPEGImage( hsStream *stream )
{
plMipmap *alpha = ISplitAlpha();
UInt8 flags = 0;
hsNullStream *nullStream = TRACKED_NEW hsNullStream();
IWriteRLEImage(nullStream,this);
if (nullStream->GetBytesWritten() < 5120) // we use RLE if it can get the image size under 5k, otherwise we use JPEG
flags |= kColorDataRLE;
delete nullStream;
nullStream = TRACKED_NEW hsNullStream();
IWriteRLEImage(nullStream,alpha);
if (nullStream->GetBytesWritten() < 5120)
flags |= kAlphaDataRLE;
delete nullStream;
stream->WriteByte(flags);
if (flags & kColorDataRLE)
IWriteRLEImage(stream,this);
else
{
plJPEG::Instance().SetWriteQuality(70);
plJPEG::Instance().WriteToStream(stream, this);
}
if (flags & kAlphaDataRLE)
IWriteRLEImage(stream,alpha);
else
{
plJPEG::Instance().SetWriteQuality(100);
plJPEG::Instance().WriteToStream(stream, alpha);
}
delete alpha;
}
void plMipmap::IReadPNGImage(hsStream* stream)
{
plMipmap* temp = NULL;
temp = plPNG::Instance().ReadFromStream(stream);
if (temp) {
CopyFrom(temp);
delete temp;
}
}
void plMipmap::IWritePNGImage(hsStream* stream)
{
plPNG::Instance().WriteToStream(stream, this);
}
//// GetLevelSize /////////////////////////////////////////////////////////////
// Get the size of a single mipmap level (0 is the largest)
UInt32 plMipmap::GetLevelSize( UInt8 level )
{
if( fLevelSizes == nil )
IBuildLevelSizes();
return fLevelSizes[ level ];
}
//// IBuildLevelSizes /////////////////////////////////////////////////////////
// Creates the table of level sizes, for quick reference
void plMipmap::IBuildLevelSizes()
{
UInt8 level;
UInt32 width, height, rowBytes;
delete [] fLevelSizes;
fLevelSizes = TRACKED_NEW UInt32[ fNumLevels ];
memset( fLevelSizes, 0, sizeof( UInt32 ) * fNumLevels );
for( level = 0, width = fWidth, height = fHeight, rowBytes = fRowBytes; level < fNumLevels; level++ )
{
switch( fCompressionType )
{
case kDirectXCompression:
if( ( width | height ) & 0x03 )
fLevelSizes[ level ] = height * rowBytes;
else
fLevelSizes[ level ] = ( height * width * (UInt32)fDirectXInfo.fBlockSize ) >> 4;
break;
case kUncompressed:
case kJPEGCompression:
case kPNGCompression:
fLevelSizes[ level ] = height * rowBytes;
break;
default:
hsAssert( false, "Bad compression type." );
return;
}
// Scale down and go!
if( width > 1 )
{
width >>= 1;
rowBytes >>= 1;
}
if( height > 1 )
height >>= 1;
}
}
//// GetLevelPtr //////////////////////////////////////////////////////////////
UInt8 *plMipmap::GetLevelPtr( UInt8 level, UInt32 *width, UInt32 *height, UInt32 *rowBytes )
{
UInt8 *data, i;
UInt32 w, h, r;
if( fLevelSizes == nil )
IBuildLevelSizes();
for( i = 0, data = (UInt8 *)fImage, w = fWidth, h = fHeight, r = fRowBytes; i < level; i++ )
{
data += fLevelSizes[ i ];
if( w > 1 )
{
w >>= 1;
r >>= 1;
}
if( h > 1 )
h >>= 1;
}
if( width != nil )
*width = w;
if( height != nil )
*height = h;
if( rowBytes != nil )
*rowBytes = r;
return data;
}
//// SetCurrLevel /////////////////////////////////////////////////////////////
// Sets the current level pointer for use with GetAddr*
void plMipmap::SetCurrLevel( UInt8 level )
{
fCurrLevel = level;
fCurrLevelPtr = GetLevelPtr( level, &fCurrLevelWidth, &fCurrLevelHeight, &fCurrLevelRowBytes );
}
//// SetConfig ////////////////////////////////////////////////////////////////
void plMipmap::SetConfig( unsigned config )
{
switch( config )
{
case kRGB32Config:
fPixelSize = 32;
fSpace = kDirectSpace;
fFlags = kNoFlag;
break;
case kARGB32Config:
fPixelSize = 32;
fSpace = kDirectSpace;
fFlags = kAlphaChannelFlag;
break;
case kRGB16Config:
fPixelSize = 16;
fSpace = kDirectSpace;
fFlags = kAlphaBitFlag;
break;
case kColor8Config:
fPixelSize = 8;
fSpace = kIndexSpace;
fFlags = kNoFlag;
break;
case kGray8Config:
fPixelSize = 8;
fSpace = kDirectSpace;
fFlags = kNoFlag;
break;
case kGray44Config:
fPixelSize = 8;
fSpace = kGraySpace;
fFlags = kAlphaChannelFlag;
break;
case kGray4Config:
fPixelSize = 4;
fSpace = kGraySpace;
fFlags = kNoFlag;
break;
default:
hsDebugMessage( "unknown config", config );
break;
}
}
//// ClipToMaxSize ////////////////////////////////////////////////////////////
// Looks for mipmap levels above the given dimension and "clips" them out,
// i.e. deletes them. So if you have a 512x1024 mipmap and call this with
// a size of 256, when this function returns the mipmap will be 256x128 in
// size.
void plMipmap::ClipToMaxSize( UInt32 maxDimension )
{
UInt8 *srcData, *destData, i;
UInt32 newSize;
for( i = 0, newSize = fTotalSize, srcData = (UInt8 *)fImage; fWidth > maxDimension || fHeight > maxDimension; i++ )
{
srcData += fLevelSizes[ i ];
newSize -= fLevelSizes[ i ];
if( fWidth > 1 )
{
fWidth >>= 1;
fRowBytes >>= 1;
}
if( fHeight > 1 )
fHeight >>= 1;
}
if( i == 0 )
// No change
return;
/// Create a new image pointer
destData = TRACKED_NEW UInt8[ newSize ];
hsAssert( destData != nil, "Out of memory in ClipToMaxSize()" );
memcpy( destData, srcData, newSize );
#ifdef MEMORY_LEAK_TRACER
IRemoveFromMemRecord( (UInt8 *)fImage );
#endif
delete [] fImage;
plProfile_DelMem(MemMipmaps, fTotalSize);
fImage = destData;
fTotalSize = newSize;
fNumLevels -= i;
IBuildLevelSizes();
plProfile_NewMem(MemMipmaps, fTotalSize);
#ifdef MEMORY_LEAK_TRACER
IAddToMemRecord( this, plRecord::kViaClipToMaxSize );
#endif
}
//// RemoveMipping ////////////////////////////////////////////////////////////
// Basically removes mipmap levels so that there is only one level remaining;
// i.e. a plain bitmap instead of a mipmap.
void plMipmap::RemoveMipping()
{
UInt8 *destData;
/// Create a new image pointer
destData = TRACKED_NEW UInt8[ fLevelSizes[ 0 ] ];
hsAssert( destData != nil, "Out of memory in ClipToMaxSize()" );
memcpy( destData, fImage, fLevelSizes[ 0 ] );
#ifdef MEMORY_LEAK_TRACER
IRemoveFromMemRecord( (UInt8 *)fImage );
#endif
delete [] fImage;
plProfile_DelMem(MemMipmaps, fTotalSize);
fImage = destData;
fTotalSize = fLevelSizes[ 0 ];
fNumLevels = 1;
fFlags |= kForceOneMipLevel;
IBuildLevelSizes();
plProfile_NewMem(MemMipmaps, fTotalSize);
#ifdef MEMORY_LEAK_TRACER
IAddToMemRecord( this, plRecord::kViaClipToMaxSize );
#endif
}
///////////////////////////////////////////////////////////////////////////////
#include "hsCodecManager.h"
namespace {
const UInt32 kVersion = 1;
const hsScalar kDefaultSigma = 1.f;
const UInt32 kDefaultDetailBias = 5;
// Color masks (out of 0-2)
const UInt8 fColorMasks[ 10 ][ 3 ] = { { 2, 0, 0 }, { 0, 2, 2 }, { 2, 0, 2 }, { 0, 2, 0 },
{ 0, 0, 2 }, { 2, 2, 0 }, { 2, 2, 2 }, { 2, 0, 1 }, { 0, 2, 1 }, { 1, 0, 2 } };
}
///////////////////////////////////////////////////////////////////////////////
//// plFilterMask Helper Class ////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// This filter mask class actually introduces a half pixel per level shift
// artifact. It's done that since I wrote it a couple of years ago, and nobody's
// noticed but me, so it's probably okay. But fixing it would be easy. The
// mask needs to extend -n down and n+1 up. To see why, notice that the current
// upper level pixel (i,j) maps to four lower pixels (i<<1,j<<1) .. (i<<1+1,j<<1+1).
// So the mask value on pixel (i<<1,j<<1) should give equal weight to those other
// 3 pixels.
// The mask here is symmetric about the src pixel, which is right if you are
// just filtering the src bitmap. But we're filtering and resampling, so the
// mask needs to be symmetric about the dst pixel, which is off by a half
// dst pixel, or on whole src pixel.
class plFilterMask
{
protected:
int fExt;
hsScalar **fMask;
public:
plFilterMask( hsScalar sig );
virtual ~plFilterMask();
int Begin() const { return -fExt; }
int End() const { return fExt; }
hsScalar Mask( int i, int j ) const { return fMask[ i ][ j ]; }
};
plFilterMask::plFilterMask( hsScalar sig )
{
fExt = (int)( sig * 2.f );
if( fExt < 1 )
fExt = 1;
hsScalar **m = TRACKED_NEW hsScalar *[ ( fExt << 1 ) + 1 ];
m += fExt;
int i, j;
hsScalar ooSigSq = 1.f / ( sig * sig );
for( i = -fExt; i <= fExt; i++ )
{
m[ i ] = ( TRACKED_NEW hsScalar[ ( fExt << 1 ) + 1] ) + fExt;
for( j = -fExt; j <= fExt; j++ )
{
m[ i ][ j ] = expf( -( i*i + j*j ) * ooSigSq );
}
}
fMask = m;
}
plFilterMask::~plFilterMask()
{
int i;
for( i = -fExt; i <= fExt; i++ )
delete [] ( fMask[ i ] - fExt );
delete [] ( fMask - fExt );
}
///////////////////////////////////////////////////////////////////////////////
//// Some More Functions //////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
plMipmap::plMipmap( plMipmap *bm, hsScalar sig, UInt32 createFlags,
hsScalar detailDropoffStart, hsScalar detailDropoffStop,
hsScalar detailMax, hsScalar detailMin)
{
int i;
hsAssert(bm->GetHeight() && bm->GetWidth(), "Degenerate Bitmap into Mipmap");
if( sig <= 0 )
sig = kDefaultSigma;
fHeight = bm->GetHeight();
fWidth = bm->GetWidth();
fRowBytes = bm->GetRowBytes();
fPixelSize = bm->GetPixelSize();
fImage = nil;
fFlags = bm->GetFlags();
UInt32 minDim = fHeight < fWidth ? fHeight : fWidth;
for( fNumLevels = 0; (minDim >> fNumLevels); fNumLevels++ ) /* empty */;
fLevelSizes = nil;
fCompressionType = kUncompressed;
fUncompressedInfo.fType = bm->fUncompressedInfo.fType;
IBuildLevelSizes();
fTotalSize = 0;
for( i = 0; i < fNumLevels; i++ )
fTotalSize += fLevelSizes[ i ];
fCurrLevel = 0;
fImage = (void *)TRACKED_NEW UInt8[ fTotalSize ];
memset(fImage, 0, fTotalSize);
memcpy( fImage, bm->fImage, bm->GetLevelSize( 0 ) );
#ifdef MEMORY_LEAK_TRACER
IAddToMemRecord( this, plRecord::kViaDetailMapConstructor );
#endif
plProfile_NewMem(MemMipmaps, fTotalSize);
/// Filter levels!
plFilterMask mask(sig);
/// First, fill in all the mipmap levels sans detail levels
for( i = 1; i < fNumLevels; i++ )
ICreateLevelNoDetail(i, mask);
if (createFlags & kCreateDetailMask)
{
// Fill in the detail levels afterwards, so we can just grab the current level's texture
// alpha (old way did it at the same time, which accumulated the detail alpha each level down)
detailDropoffStart *= fNumLevels;
detailDropoffStop *= fNumLevels;
switch( createFlags & kCreateDetailMask )
{
case kCreateDetailAlpha:
fFlags |= kAlphaChannelFlag;
for( i = 0; i < fNumLevels; i++ )
IBlendLevelDetailAlpha(i, mask, detailDropoffStart, detailDropoffStop, detailMax, detailMin);
break;
case kCreateDetailAdd:
for( i = 0; i < fNumLevels; i++ )
IBlendLevelDetailAdd( i, mask, detailDropoffStart, detailDropoffStop, detailMax, detailMin);
break;
case kCreateDetailMult:
for( i = 0; i < fNumLevels; i++ )
IBlendLevelDetailMult(i, mask, detailDropoffStart, detailDropoffStop, detailMax, detailMin);
break;
}
}
if( createFlags & kCreateCarryAlpha )
{
for( i = 1; i < fNumLevels; i++ )
ICarryZeroAlpha(i);
}
if( createFlags & (kCreateCarryWhite | kCreateCarryBlack) )
{
UInt32 col = createFlags & kCreateCarryWhite ? 0x00ffffff : 0x00000000;
for( i = 1; i < fNumLevels; i++ )
ICarryColor(i, col);
}
#ifdef MEMORY_LEAK_TRACER
fNumMipmaps++;
#endif
}
//// IGetDetailLevelAlpha /////////////////////////////////////////////////////
// Given the detail range and the current level, returns the detail's alpha
// value at that level.
hsScalar plMipmap::IGetDetailLevelAlpha( UInt8 level, hsScalar dropStart, hsScalar dropStop, hsScalar min, hsScalar max )
{
hsScalar detailAlpha;
detailAlpha = ( level - dropStart ) * ( min - max ) / ( dropStop - dropStart ) + max;
if( min < max )
detailAlpha = hsMinimum( max, hsMaximum( min, detailAlpha ) );
else
detailAlpha = hsMinimum( min, hsMaximum( max, detailAlpha ) );
return detailAlpha;
}
void plMipmap::SetBitmapAsLevel(UInt8 iDst, plMipmap *bm, hsScalar sig, UInt32 createFlags,
hsScalar detailDropoffStart, hsScalar detailDropoffStop,
hsScalar detailMax, hsScalar detailMin)
{
SetCurrLevel( iDst );
hsAssert((bm->fHeight == fCurrLevelHeight) && (bm->fWidth == fCurrLevelWidth), "Wrong size bitmap for Mipmap level.");
memcpy( fCurrLevelPtr, bm->fImage, bm->GetLevelSize( 0 ) );
plFilterMask mask(sig);
int i;
// Fill in levels w/o detail first
for( i = iDst+1; i < fNumLevels; i++ )
ICreateLevelNoDetail(i, mask);
// Now fill in the detail alphas, if we have any
switch( createFlags & kCreateDetailMask )
{
case 0:
break;
case kCreateDetailAlpha:
for( i = iDst; i < fNumLevels; i++ )
IBlendLevelDetailAlpha(i, mask, detailDropoffStart, detailDropoffStop, detailMax, detailMin);
break;
case kCreateDetailAdd:
for( i = iDst; i < fNumLevels; i++ )
IBlendLevelDetailAdd(i, mask, detailDropoffStart, detailDropoffStop, detailMax, detailMin);
break;
case kCreateDetailMult:
for( i = iDst; i < fNumLevels; i++ )
IBlendLevelDetailMult(i, mask, detailDropoffStart, detailDropoffStop, detailMax, detailMin);
break;
}
if( createFlags & kCreateCarryAlpha )
{
for( i = iDst+1; i < fNumLevels; i++ )
ICarryZeroAlpha(i);
}
if( createFlags & (kCreateCarryWhite | kCreateCarryBlack) )
{
UInt32 col = createFlags & kCreateCarryWhite ? 0x00ffffff : 0x00000000;
for( i = iDst+1; i < fNumLevels; i++ )
ICarryColor(i, col);
}
SetCurrLevel(0);
}
//// ICreateLevelNoDetail /////////////////////////////////////////////////////
// Creates one level of a mipmap based on a filter on the previous layer.
// This version assumes no detail map fading.
void plMipmap::ICreateLevelNoDetail( UInt8 iDst, const plFilterMask& mask )
{
hsAssert(fPixelSize == 32, "Only 32 bit implemented");
ASSERT_UNCOMPRESSED();
int i, j, ii, jj;
if( 32 == fPixelSize )
{
SetCurrLevel(iDst);
UInt8 *src = (UInt8 *)GetLevelPtr( iDst-1 );
UInt8 *dst = (UInt8 *)GetLevelPtr(iDst);
UInt32 srcRowBytes = fCurrLevelRowBytes << 1;
UInt32 srcHeight = fCurrLevelHeight << 1;
UInt32 srcWidth = fCurrLevelWidth << 1;
for( i = 0; i < fCurrLevelHeight; i++ )
{
for( j = 0; j < fCurrLevelWidth; j++ )
{
UInt8 *center = src + (i << 1) * srcRowBytes + (j << 3);
UInt32 chan;
for( chan = 0; chan < 4; chan++ )
{
hsScalar w = 0;
hsScalar a = 0;
for( ii = mask.Begin(); ii <= mask.End(); ii++ )
{
for( jj = mask.Begin(); jj <= mask.End(); jj++ )
{
if( (ii + (i << 1) >= 0)&&(ii + (i << 1) < srcHeight)
&&(jj + (j << 1) >= 0)&&(jj + (j << 1) < srcWidth) )
{
w += mask.Mask(ii, jj);
a += (hsScalar(center[ii*srcRowBytes + (jj<<2) + chan]) + 0.5f) * mask.Mask(ii, jj);
}
}
}
a /= w;
dst[i * fCurrLevelRowBytes + (j << 2) + chan] = (UInt8)a;
}
}
}
}
}
void plMipmap::ICarryZeroAlpha(UInt8 iDst)
{
hsAssert(fPixelSize == 32, "Only 32 bit implemented");
ASSERT_UNCOMPRESSED();
int i, j;
if( 32 == fPixelSize )
{
SetCurrLevel(iDst);
UInt8 *src = (UInt8 *)GetLevelPtr( iDst-1 );
UInt8 *dst = (UInt8 *)GetLevelPtr(iDst);
UInt32 srcRowBytes = fCurrLevelRowBytes << 1;
UInt32 srcHeight = fCurrLevelHeight << 1;
UInt32 srcWidth = fCurrLevelWidth << 1;
const UInt8 alphaOff = 3;
for( i = 0; i < fCurrLevelHeight; i++ )
{
for( j = 0; j < fCurrLevelWidth; j++ )
{
UInt8 *center = src + (i << 1) * srcRowBytes + (j << 3) + alphaOff;
if( !center[0]
|| !center[4]
|| !center[srcRowBytes + 0]
|| !center[srcRowBytes + 4] )
{
dst[i * fCurrLevelRowBytes + (j << 2) + alphaOff] = 0;
}
}
}
}
}
void plMipmap::ICarryColor(UInt8 iDst, UInt32 col)
{
hsAssert(fPixelSize == 32, "Only 32 bit implemented");
ASSERT_UNCOMPRESSED();
int i, j;
if( 32 == fPixelSize )
{
SetCurrLevel(iDst);
UInt32* src = (UInt32*)GetLevelPtr( iDst-1 );
UInt32* dst = (UInt32*)GetLevelPtr(iDst);
UInt32 srcHeight = fCurrLevelHeight << 1;
UInt32 srcWidth = fCurrLevelWidth << 1;
const UInt8 alphaOff = 3;
for( i = 0; i < fCurrLevelHeight; i++ )
{
for( j = 0; j < fCurrLevelWidth; j++ )
{
UInt32* center = src + (i << 1) * srcWidth + (j << 1);
if( ((center[0] & 0x00ffffff) == col)
||((center[1] & 0x00ffffff) == col)
||((center[srcHeight] & 0x00ffffff) == col)
||((center[srcHeight+1] & 0x00ffffff) == col) )
{
dst[i * fCurrLevelWidth + j] &= 0xff000000;
dst[i * fCurrLevelWidth + j] |= col;
}
}
}
}
}
//// IBlendLevelDetailAlpha ///////////////////////////////////////////////////
// Blends in the detail alpha for a given level. This version assumes
// standard detail map blending.
void plMipmap::IBlendLevelDetailAlpha( UInt8 iDst, const plFilterMask& mask,
hsScalar detailDropoffStart, hsScalar detailDropoffStop,
hsScalar detailMax, hsScalar detailMin )
{
hsAssert(fPixelSize == 32, "Only 32 bit implemented");
ASSERT_UNCOMPRESSED();
int i, j;
UInt32 offset;
SetCurrLevel(iDst);
UInt8 *dst = (UInt8 *)GetLevelPtr(iDst);
hsScalar detailAlpha = IGetDetailLevelAlpha( iDst, detailDropoffStart, detailDropoffStop, detailMin, detailMax );
for( i = 0; i < fCurrLevelHeight; i++ )
{
for( j = 0; j < fCurrLevelWidth; j++ )
{
UInt32 chan = 3; // Alpha channel only
offset = i * fCurrLevelRowBytes + (j << 2) + chan;
float a = (float)dst[ offset ] * detailAlpha;
dst[ offset ] = (UInt8)a;
}
}
}
//// IBlendLevelDetailAdd /////////////////////////////////////////////////////
// Blends in the detail alpha for a given level. This version assumes additive
// detail map blending. (Shesh, gotta hate C sometimes....)
void plMipmap::IBlendLevelDetailAdd( UInt8 iDst, const plFilterMask& mask,
hsScalar detailDropoffStart, hsScalar detailDropoffStop,
hsScalar detailMax, hsScalar detailMin )
{
hsAssert(fPixelSize == 32, "Only 32 bit implemented");
ASSERT_UNCOMPRESSED();
int i, j;
UInt32 offset;
SetCurrLevel(iDst);
UInt8 *dst = (UInt8 *)GetLevelPtr(iDst);
hsScalar detailAlpha = IGetDetailLevelAlpha( iDst, detailDropoffStart, detailDropoffStop, detailMin, detailMax );
for( i = 0; i < fCurrLevelHeight; i++ )
{
for( j = 0; j < fCurrLevelWidth; j++ )
{
UInt32 chan;
/// Blend all but the alpha channel, since we're doing additive blending
for( chan = 0; chan < 3; chan++ )
{
offset = i * fCurrLevelRowBytes + (j << 2) + chan;
float a = (float)dst[ offset ] * detailAlpha;
dst[ offset ] = (UInt8)a;
}
}
}
}
//// IBlendLevelDetailMult ////////////////////////////////////////////////////
// Blends in the detail alpha for a given level. This version assumes
// multiplicitive detail map blending. (Shesh, gotta hate C sometimes....)
void plMipmap::IBlendLevelDetailMult( UInt8 iDst, const plFilterMask& mask,
hsScalar detailDropoffStart, hsScalar detailDropoffStop,
hsScalar detailMax, hsScalar detailMin )
{
hsAssert(fPixelSize == 32, "Only 32 bit implemented");
ASSERT_UNCOMPRESSED();
int i, j;
UInt32 offset;
SetCurrLevel(iDst);
UInt8 *dst = (UInt8 *)GetLevelPtr(iDst);
hsScalar detailAlpha = IGetDetailLevelAlpha( iDst, detailDropoffStart, detailDropoffStop, detailMin, detailMax );
hsScalar invDetailAlpha = ( 1.f - detailAlpha ) * 255.f;
for( i = 0; i < fCurrLevelHeight; i++ )
{
for( j = 0; j < fCurrLevelWidth; j++ )
{
UInt32 chan;
for( chan = 0; chan < 4; chan++ )
{
offset = i * fCurrLevelRowBytes + (j << 2) + chan;
float a = (float)dst[ offset ];
// Mult should fade to white, not black like with additive blending
a = invDetailAlpha + a * detailAlpha;
dst[ offset ] = (UInt8)a;
}
}
}
}
//// EnsureKonstantBorder /////////////////////////////////////////////////////
// Checks a mipmap's levels and makes sure that if the top level has constant
// border color/alpha, the rest of the levels get that border, too, regardless
// of filtering. (This is us guessing that that border was apparently what
// was intented, thus removing ugly stretching problems on clamped textures).
void plMipmap::EnsureKonstantBorder( hsBool clampU, hsBool clampV )
{
if( fPixelSize != 32 )
{
hsAssert( false, "Only 32 bit color supported in EnsureKonstantBorder()" );
return;
}
if( !clampU && !clampV )
return; // Um, exactly what are we supposed to do, again?
UInt32 uColor, vColor;
int i;
if( clampU && !IGrabBorderColor( false, &uColor ) )
return;
if( clampV && !IGrabBorderColor( true, &vColor ) )
return;
if( clampU && clampV && ( uColor != vColor ) )
return;
for( i = 1; i < fNumLevels; i++ )
{
SetCurrLevel( i );
if( clampU )
ISetCurrLevelUBorder( uColor );
if( clampV )
ISetCurrLevelVBorder( vColor );
}
SetCurrLevel( 0 );
}
//// IGrabBorderColor /////////////////////////////////////////////////////////
// Grabs the top level's border color, or returns false if not all pixels
// on the border are the same color/alpha.
hsBool plMipmap::IGrabBorderColor( hsBool grabVNotU, UInt32 *color )
{
int i;
UInt32 *src1 = (UInt32 *)fImage, *src2, testColor;
if( !grabVNotU )
{
src2 = (UInt32 *)( (UInt8 *)fImage + fRowBytes * ( fHeight - 1 ) );
testColor = *src1;
for( i = 0; i < fWidth; i++ )
{
if( src1[ i ] != testColor || src2[ i ] != testColor )
return false;
}
*color = testColor;
return true;
}
else
{
src2 = src1 + ( fWidth - 1 );
testColor = *src1;
for( i = 0; i < fHeight; i++ )
{
if( *src1 != testColor || *src2 != testColor )
return false;
src1 += fWidth;
src2 += fWidth;
}
*color = testColor;
return true;
}
}
//// ISetCurrLevelUBorder /////////////////////////////////////////////////////
void plMipmap::ISetCurrLevelUBorder( UInt32 color )
{
int i;
UInt32 *src1 = (UInt32 *)fCurrLevelPtr, *src2;
src2 = (UInt32 *)( (UInt8 *)fCurrLevelPtr + fCurrLevelRowBytes * ( fCurrLevelHeight - 1 ) );
for( i = 0; i < fCurrLevelWidth; i++ )
{
src1[ i ] = color;
src2[ i ] = color;
}
}
//// ISetCurrLevelVBorder /////////////////////////////////////////////////////
void plMipmap::ISetCurrLevelVBorder( UInt32 color )
{
int i;
UInt32 *src1 = (UInt32 *)fCurrLevelPtr, *src2;
src2 = src1 + ( fCurrLevelWidth - 1 );
for( i = 0; i < fCurrLevelHeight; i++ )
{
*src1 = color;
*src2 = color;
src1 += fCurrLevelWidth;
src2 += fCurrLevelWidth;
}
}
void plMipmap::Filter(hsScalar sig)
{
hsAssert(fPixelSize == 32, "Only 32 bit implemented");
ASSERT_UNCOMPRESSED();
int i, j, ii, jj;
if( 32 == fPixelSize )
{
UInt8 *dst = (UInt8 *)(fImage);
UInt8* src = (UInt8*)HSMemory::New(fRowBytes * fHeight);
HSMemory::BlockMove(dst, src, fRowBytes * fHeight);
if( sig <= 0 )
sig = kDefaultSigma;
plFilterMask mask(sig);
UInt32 srcRowBytes = fRowBytes;
UInt32 srcHeight = fHeight;
UInt32 srcWidth = fWidth;
for( i = 0; i < fHeight; i++ )
{
for( j = 0; j < fWidth; j++ )
{
UInt8 *center = src + i * srcRowBytes + (j << 2);
UInt32 chan;
for( chan = 0; chan < 4; chan++ )
{
hsScalar w = 0;
hsScalar a = 0;
for( ii = mask.Begin(); ii <= mask.End(); ii++ )
{
for( jj = mask.Begin(); jj <= mask.End(); jj++ )
{
if( (ii + i >= 0)&&(ii + i < srcHeight)
&&(jj + j >= 0)&&(jj + j < srcWidth) )
{
w += mask.Mask(ii, jj);
a += (hsScalar(center[ii*srcRowBytes + (jj<<2) + chan]) + 0.5f) * mask.Mask(ii, jj);
}
}
}
a /= w;
dst[i * fRowBytes + (j << 2) + chan] = (UInt8)(a);
}
}
}
HSMemory::Delete(src);
}
}
static void CopyPixels(UInt32 srcWidth, UInt32 srcHeight,void *srcPixels,
UInt32 skipX, UInt32 skipY, UInt32 dstFormat,
void * &destPixels, UInt32 copyOptions)
{
int y;
int xInc = skipX + 1;
int yInc = skipY + 1;
int i = 0;
int firstX = 0;
int rowSkip = yInc * srcWidth; // Number of pixels to skip for each line
const hsRGBAColor32 *p =(const hsRGBAColor32 *)srcPixels;
UInt16 *pixels16 = (UInt16*)destPixels;
UInt8 *pixels8 = (UInt8*)destPixels;
for(y = 0; y < srcHeight; y += yInc, firstX += rowSkip)
{
const hsRGBAColor32 *srcPix = &(p[firstX]);
int x;
switch (dstFormat)
{
case plMipmap::kPixelAI88:
for(x =0; x < srcWidth; x += xInc)
pixels16[i++]= ((srcPix[x].a & 0xff) << 8) | (srcPix[x].r & 0xff);
break;
case plMipmap::kPixelI8:
for(x =0; x < srcWidth; x += xInc)
pixels8[i++]= srcPix[x].r;
break;
case plMipmap::kPixelARGB4444:
for(x = 0; x < srcWidth; x += xInc)
pixels16[i++]= (((srcPix[x].r>>4) & 0xf) << 8)
| (((srcPix[x].g >> 4) & 0xf) << 4)
| (((srcPix[x].b >> 4) & 0xf) )
| (((srcPix[x].a >> 4) & 0xf) << 12);
break;
case plMipmap::kPixelARGB1555:
for(x = 0; x < srcWidth; x += xInc)
pixels16[i++]= (((srcPix[x].r>>3) & 0x1f) << 10) |
(((srcPix[x].g >> 3) & 0x1f) << 5) |
((srcPix[x].b >> 3) & 0x1f) | ((srcPix[x].a == 0) ? 0 : 0x8000);
break;
}
}
destPixels = (char *)destPixels + (i * ((dstFormat == plMipmap::kPixelI8 ) ? 1 : 2));
}
UInt32 plMipmap::CopyOutPixels(UInt32 destXSize, UInt32 destYSize,
UInt32 dstFormat, void *destPixels, UInt32 copyOptions)
{
hsAssert(fPixelSize == 32, "Only 32 bit implemented");
ASSERT_UNCOMPRESSED();
int i;
int skipX = fWidth/destXSize - 1;
int skipY = fHeight/destYSize - 1;
hsAssert(!fCurrLevel,"Mip Map not at level 0");
for(i = 0 ; i < (fNumLevels - fCurrLevel); i++)
{
CopyPixels(fWidth >> i , fHeight >> i, GetLevelPtr( i ), skipX, skipY,
dstFormat, destPixels, copyOptions);
}
return 0;
}
//// CopyFrom /////////////////////////////////////////////////////////////////
void plMipmap::CopyFrom( const plMipmap *source )
{
hsAssert( source != nil, "nil source in plMipmap::CopyFrom()" );
plProfile_DelMem(MemMipmaps, fTotalSize);
#ifdef MEMORY_LEAK_TRACER
IRemoveFromMemRecord( (UInt8 *)fImage );
#endif
delete [] fImage;
fWidth = source->fWidth;
fHeight = source->fHeight;
fRowBytes = source->fRowBytes;
fPixelSize = source->fPixelSize;
fFlags = source->fFlags;
fSpace = source->fSpace;
fCompressionType = source->fCompressionType;
fTotalSize = source->fTotalSize;
fImage = (void *)TRACKED_NEW UInt8[ fTotalSize ];
memcpy( fImage, source->fImage, fTotalSize );
#ifdef MEMORY_LEAK_TRACER
IAddToMemRecord( this, plRecord::kViaCopyFrom );
#endif
plProfile_NewMem(MemMipmaps, fTotalSize);
fNumLevels = source->fNumLevels;
switch( fCompressionType )
{
case kDirectXCompression:
{
fDirectXInfo.fBlockSize = source->fDirectXInfo.fBlockSize;
fDirectXInfo.fCompressionType = source->fDirectXInfo.fCompressionType;
}
break;
case kUncompressed:
case kJPEGCompression:
case kPNGCompression:
fUncompressedInfo.fType = source->fUncompressedInfo.fType;
break;
default:
hsAssert(false, "Reading unknown compression format.");
break;
}
// Gotta do this AFTER we set our block size, etc.
IBuildLevelSizes();
// We just changed our texture, so if we have a texture ref, we better dirty it
if( GetDeviceRef() != nil )
GetDeviceRef()->SetDirty( true );
}
//// Clone ////////////////////////////////////////////////////////////////////
plMipmap *plMipmap::Clone() const
{
plMipmap *newMap = TRACKED_NEW plMipmap;
newMap->CopyFrom( this );
return newMap;
}
//// Composite ////////////////////////////////////////////////////////////////
// Compositing function. Take a (smaller) mipmap and composite it onto this one
// at the given location
void plMipmap::Composite( plMipmap *source, UInt16 x, UInt16 y, plMipmap::CompositeOptions *options )
{
UInt8 level, numLevels, srcNumLevels, srcLevelOffset, levelsToSkip;
UInt16 pX, pY;
UInt32 *srcLevelPtr, *dstLevelPtr, *srcPtr, *dstPtr;
UInt32 srcRowBytes, dstRowBytes, srcRowBytesToCopy, r, g, b, dR, dG, dB, srcWidth, srcHeight;
UInt32 srcAlpha, oneMinusAlpha, destAlpha;
UInt16 srcClipX, srcClipY;
// Currently we only support 32 bit uncompressed mipmaps
if( fPixelSize != 32 || fCompressionType == kDirectXCompression )
{
hsAssert( false, "Destination mipmap on composite has unsupported format" );
return;
}
if( source->fPixelSize != 32 || source->fCompressionType == kDirectXCompression )
{
hsAssert( false, "Source mipmap on composite has unsupported format" );
return;
}
// Grab the correct options pointer
if( options == nil )
{
static CompositeOptions defaultOptions;
options = &defaultOptions;
}
// Src level skipping
srcWidth = source->fWidth;
srcHeight = source->fHeight;
srcLevelPtr = (UInt32 *)source->fImage;
srcRowBytes = source->fRowBytes;
srcNumLevels = source->fNumLevels;
for( srcLevelOffset = 0, levelsToSkip = options->fSrcLevelsToSkip; levelsToSkip > 0; levelsToSkip--, srcLevelOffset++ )
{
srcWidth >>= 1;
srcHeight >>= 1;
srcLevelPtr += source->fLevelSizes[ srcLevelOffset ] >> 2;
srcRowBytes >>= 1;
srcNumLevels--;
}
// Src clipping setup
srcClipX = srcClipY = 0;
srcRowBytesToCopy = srcRowBytes;
if( options->fSrcClipY > 0 )
{
srcHeight -= options->fSrcClipY;
srcClipY = options->fSrcClipY;
}
if( options->fSrcClipHeight > 0 )
{
srcHeight = options->fSrcClipHeight;
}
if( options->fSrcClipX > 0 )
{
srcWidth -= options->fSrcClipX;
srcClipX = options->fSrcClipX;
srcRowBytesToCopy -= options->fSrcClipX * ( source->fPixelSize >> 3 );
}
if( options->fSrcClipWidth > 0 )
{
srcWidth = options->fSrcClipWidth;
srcRowBytesToCopy = srcWidth * ( source->fPixelSize >> 3 );
}
// Position checks
if( x + srcWidth > fWidth || y + srcHeight > fHeight )
{
hsAssert( false, "Illegal position on mipmap composite" );
return;
}
// Do the composite on each level
numLevels = fNumLevels;
if( numLevels > srcNumLevels )
numLevels = srcNumLevels;
dstLevelPtr = (UInt32 *)fImage;
dstRowBytes = fRowBytes;
if( options->fFlags & kForceOpaque )
{
for( level = 0; level < numLevels; level++, y >>= 1, x >>= 1 )
{
srcPtr = srcLevelPtr;
dstPtr = dstLevelPtr + ( y * dstRowBytes >> 2 ) + x;
// Clipping
srcPtr += srcClipY * ( srcRowBytes >> 2 ) + srcClipX;
for( pY = (UInt16)srcHeight; pY > 0; pY-- )
{
memcpy( dstPtr, srcPtr, srcRowBytesToCopy );
for( pX = 0; pX < srcWidth; pX++ )
{
// Force the alpha opaque
dstPtr[ pX ] |= 0xff000000;
}
dstPtr += dstRowBytes >> 2;
srcPtr += srcRowBytes >> 2;
}
srcLevelPtr += source->fLevelSizes[ level + srcLevelOffset ] >> 2;
dstLevelPtr += fLevelSizes[ level ] >> 2;
srcRowBytes >>= 1;
dstRowBytes >>= 1;
srcRowBytesToCopy >>= 1;
if( srcHeight > 1 )
srcHeight >>= 1;
srcClipX >>= 1;
srcClipY >>= 1;
}
}
else if( options->fFlags & kCopySrcAlpha )
{
for( level = 0; level < numLevels; level++, y >>= 1, x >>= 1 )
{
srcPtr = srcLevelPtr;
dstPtr = dstLevelPtr + ( y * dstRowBytes >> 2 ) + x;
// Clipping
srcPtr += srcClipY * ( srcRowBytes >> 2 ) + srcClipX;
for( pY = (UInt16)srcHeight; pY > 0; pY-- )
{
memcpy( dstPtr, srcPtr, srcRowBytesToCopy );
if( options->fFlags & kDestPremultiplied )
{
// multiply color values by alpha
for( pX = 0; pX < srcWidth; pX++ )
{
srcAlpha = ((dstPtr[ pX ] >> 24) & 0x000000ff);
dstPtr[ pX ] = ( srcAlpha << 24 )
| (((((dstPtr[ pX ] >> 16) & 0xff)*srcAlpha + 127)/255) << 16)
| (((((dstPtr[ pX ] >> 8) & 0xff)*srcAlpha + 127)/255) << 8)
| (((((dstPtr[ pX ] ) & 0xff)*srcAlpha + 127)/255) );
}
}
dstPtr += dstRowBytes >> 2;
srcPtr += srcRowBytes >> 2;
}
srcLevelPtr += source->fLevelSizes[ level + srcLevelOffset ] >> 2;
dstLevelPtr += fLevelSizes[ level ] >> 2;
srcRowBytes >>= 1;
dstRowBytes >>= 1;
srcRowBytesToCopy >>= 1;
if( srcHeight > 1 )
srcHeight >>= 1;
srcClipX >>= 1;
srcClipY >>= 1;
}
}
else if( options->fFlags & kMaskSrcAlpha )
{
for( level = 0; level < numLevels; level++, y >>= 1, x >>= 1 )
{
srcPtr = srcLevelPtr;
dstPtr = dstLevelPtr + ( y * dstRowBytes >> 2 ) + x;
// Clipping
srcPtr += srcClipY * ( srcRowBytes >> 2 ) + srcClipX;
for( pY = (UInt16)srcHeight; pY > 0; pY-- )
{
for( pX = 0; pX < srcWidth; pX++ )
{
srcAlpha = options->fOpacity * ( ( srcPtr[ pX ] >> 16 ) & 0x0000ff00 ) / 255 / 256;
if( srcAlpha != 0 )
{
if( options->fFlags & kDestPremultiplied )
dstPtr[ pX ] = ( srcAlpha << 24 )
| (((((srcPtr[ pX ] >> 16) & 0xff)*srcAlpha + 127)/255) << 16)
| (((((srcPtr[ pX ] >> 8) & 0xff)*srcAlpha + 127)/255) << 8)
| (((((srcPtr[ pX ] ) & 0xff)*srcAlpha + 127)/255) );
else
dstPtr[ pX ] = ( srcPtr[ pX ] & 0x00ffffff ) | ( srcAlpha << 24 );
}
}
dstPtr += dstRowBytes >> 2;
srcPtr += srcRowBytes >> 2;
}
srcLevelPtr += source->fLevelSizes[ level + srcLevelOffset ] >> 2;
dstLevelPtr += fLevelSizes[ level ] >> 2;
srcRowBytes >>= 1;
dstRowBytes >>= 1;
srcRowBytesToCopy >>= 1;
if( srcHeight > 1 )
srcHeight >>= 1;
srcClipX >>= 1;
srcClipY >>= 1;
}
}
else
{
for( level = 0; level < numLevels; level++, y >>= 1, x >>= 1 )
{
srcPtr = srcLevelPtr;
dstPtr = dstLevelPtr + ( y * dstRowBytes >> 2 ) + x;
// Clipping
srcPtr += srcClipY * ( srcRowBytes >> 2 ) + srcClipX;
for( pY = (UInt16)srcHeight; pY > 0; pY-- )
{
// Reverse the loop so we can count downwards--slightly faster
pX = (UInt16)srcWidth;
do
{
pX--;
// Wacko trick here. Alphas are 0-255, which means scaling by alpha would
// be a v' = v * alpha / 255 operation sequence. However, since we hate
// dividing by 255 all the time, we actually scale the alpha just ever so
// slightly so it's 0-256, which makes the divide a simple shift. Note
// that this will result in some tiny bit of aliasing, but it shouldn't be
// enough to notice
if (!(srcPtr[pX] >> 24)) // Zero alpha. Skip this pixel
continue;
srcAlpha = options->fOpacity * ( ( srcPtr[ pX ] >> 16 ) & 0x0000ff00 ) / 255 / 256;
oneMinusAlpha = 256 - srcAlpha;
destAlpha = dstPtr[ pX ] & 0xff000000;
r = (UInt32)((( srcPtr[ pX ] >> 16 ) & 0x000000ff) * options->fRedTint);
g = (UInt32)((( srcPtr[ pX ] >> 8 ) & 0x000000ff) * options->fGreenTint);
b = (UInt32)((( srcPtr[ pX ] ) & 0x000000ff) * options->fBlueTint);
dR = ( dstPtr[ pX ] >> 16 ) & 0x000000ff;
dG = ( dstPtr[ pX ] >> 8 ) & 0x000000ff;
dB = ( dstPtr[ pX ] ) & 0x000000ff;
r = ( r * srcAlpha ) >> 8;
g = ( g * srcAlpha ) >> 8;
b = ( b * srcAlpha ) >> 8;
dR = ( dR * oneMinusAlpha ) >> 8;
dG = ( dG * oneMinusAlpha ) >> 8;
dB = ( dB * oneMinusAlpha ) >> 8;
// Dest alpha for now is just our original dest alpha
dstPtr[ pX ] = ( ( r + dR ) << 16 ) | ( ( g + dG ) << 8 ) | ( b + dB ) | destAlpha;
if( !( options->fFlags & kBlendWriteAlpha ) )
continue;
// Unless our blend option is set of course
dstPtr[ pX ] = ( dstPtr[ pX ] & 0x00ffffff ) | ( srcAlpha << 24 );
} while( pX > 0 );
dstPtr += dstRowBytes >> 2;
srcPtr += srcRowBytes >> 2;
}
srcLevelPtr += source->fLevelSizes[ level + srcLevelOffset ] >> 2;
dstLevelPtr += fLevelSizes[ level ] >> 2;
srcRowBytes >>= 1;
dstRowBytes >>= 1;
if( srcWidth > 1 )
srcWidth >>= 1;
if( srcHeight > 1 )
srcHeight >>= 1;
srcClipX >>= 1;
srcClipY >>= 1;
}
}
// All done!
if( GetDeviceRef() != nil )
GetDeviceRef()->SetDirty( true );
}
//// Colorize /////////////////////////////////////////////////////////////////
// Colorizes a mipmap so that each level is color-coded. Assume max 10 levels
// (coloring wraps after 10).
void plMipmap::Colorize()
{
UInt32 currColor, width, height;
UInt8 currLevel;
if( fPixelSize != 32 && fCompressionType != kDirectXCompression )
{
/// Most likely this is a luminance or luminance/alpha map,
/// so we ignore it (it's up to the device to make sure we
/// only get 32-bit or compressed mipmaps)
return;
}
if( fFlags & kForceOneMipLevel )
{
// Don't colorize if it's not mipmapped...
return;
}
/// First handle compressed levels, if any
currLevel = 0;
currColor = 0;
width = fWidth;
height = fHeight;
if( fCompressionType == kDirectXCompression )
{
for( ; currLevel < fNumLevels; currLevel++ )
{
/// Are we over the threshold?
SetCurrLevel( currLevel );
if( ( fCurrLevelWidth | fCurrLevelHeight ) & 0x03 )
break;
/// Since this level is compressed, we have to use the codec...
hsCodecManager::Instance().ColorizeCompMipmap( this, fColorMasks[ currColor ] );
/// Increment the color
currColor = ( currColor >=9 ) ? 0 : currColor + 1;
}
}
/// Now loop through the uncompressed levels
for( ; currLevel < fNumLevels; currLevel++ )
{
/// Do this one
IColorLevel( currLevel, fColorMasks[ currColor ] );
/// Increment the color
currColor = ( currColor >=9 ) ? 0 : currColor + 1;
}
SetCurrLevel( 0 );
}
//// IColorLevel //////////////////////////////////////////////////////////////
// Colorizes the current level of a mipmap according to the color mask given
// (percentages of r, g & b in the range of 0-2).
void plMipmap::IColorLevel( UInt8 level, const UInt8 *colorMask )
{
UInt32 index, max, color, gray, grayDiv2, *data, width, height;
UInt8 compMasks[ 3 ][ 2 ] = { { 0, 0 }, { 0, 0xff }, { 0xff, 0 } };
data = (UInt32 *)GetLevelPtr( level, &width, &height );
max = fLevelSizes[ level ] >> 2;
for( index = 0; index < max; index++ )
{
/// Get color and calculate gray (average of r, g & b)
color = data[ index ];
gray = ( ( color >> 16 ) & 0xff ) + ( ( color >> 8 ) & 0xff ) + ( color & 0xff );
gray /= 3;
gray = 0xff - ( ( 0xff - gray ) >> 1 ); // Lighten it 50%
grayDiv2 = gray >> 1;
/// Preserve alpha
color &= 0xff000000;
/// Now rewrite the components based on the color mask
color |= ( ( gray & compMasks[ colorMask[ 0 ] ][ 0 ] ) |
( grayDiv2 & compMasks[ colorMask[ 0 ] ][ 1 ] ) ) << 16;
color |= ( ( gray & compMasks[ colorMask[ 1 ] ][ 0 ] ) |
( grayDiv2 & compMasks[ colorMask[ 1 ] ][ 1 ] ) ) << 8;
color |= ( ( gray & compMasks[ colorMask[ 2 ] ][ 0 ] ) |
( grayDiv2 & compMasks[ colorMask[ 2 ] ][ 1 ] ) );
data[ index ] = color;
}
}
///////////////////////////////////////////////////////////////////////////////
//// Scaling //////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//// ScaleNicely //////////////////////////////////////////////////////////////
// Does a nice (smoothed) scaling of a 1-level mipmap onto another 1-level
// mipmap. Works only for 32-bit mipmaps.
void plMipmap::ScaleNicely( UInt32 *destPtr, UInt16 destWidth, UInt16 destHeight,
UInt16 destStride, plMipmap::ScaleFilter filter ) const
{
UInt16 destX, destY, srcX, srcY;
Int16 srcStartX, srcEndX, srcStartY, srcEndY;
float srcPosX, srcPosY, destToSrcXScale, destToSrcYScale, filterWidth, filterHeight, weight;
float totalWeight;
hsColorRGBA color, accumColor;
float whyWaits[ 16 ], whyWait, xWeights[ 16 ];
UInt32 *srcPtr;
// Init
destToSrcXScale = (float)fWidth / (float)destWidth;
destToSrcYScale = (float)fHeight / (float)destHeight;
// Filter size is the radius of the area (or rather, half the box size) around the source position
// that we sample from. We calculate it so that a 1:1 scale would result in a filter size of 1 (thus
// making a box filter at 1:1 result in a straight copy of the original)
filterWidth = 1.f * destToSrcXScale;
filterHeight = 1.f * destToSrcYScale;
// If we are upsampling, we still want a filter at least a pixel half-width/height, which will just do
// a bilerp up. That doesn't make this function correctly resample, or excuse the incredibly complicated
// code to do something incredibly simple, but at least it doesn't fail so obviously.
if( filterWidth < 1.f )
filterWidth = 1.f;
if( filterHeight < 1.f )
filterHeight = 1.f;
// Process
for( destY = 0; destY < destHeight; destY++ )
{
// Calculate the span across this row
srcPosY = destY * destToSrcYScale;
srcStartY = (Int16)( srcPosY - filterHeight );
if( srcStartY < 0 )
srcStartY = 0;
srcEndY = (Int16)( srcPosY + filterHeight );
if( srcEndY >= fHeight )
srcEndY = (Int16)(fHeight - 1);
// Precalc the y weights
for( srcY = srcStartY; srcY <= srcEndY && ( srcY - srcStartY ) < 16; srcY++ )
whyWaits[ srcY - srcStartY ] = 1.f - ( fabs( (float)srcY - srcPosY ) / filterHeight );
for( destX = 0; destX < destWidth; destX++ )
{
// For this pixel in the destination, figure out where in the source image we virtually are
srcPosX = destX * destToSrcXScale;
// Range of pixels that the filter covers
srcStartX = (Int16)( srcPosX - filterWidth );
if( srcStartX < 0 )
srcStartX = 0;
srcEndX = (Int16)( srcPosX + filterWidth );
if( srcEndX >= fWidth )
srcEndX = (Int16)(fWidth - 1);
// Precalc the x weights
for( srcX = srcStartX; srcX <= srcEndX && ( srcX - srcStartX ) < 16; srcX++ )
xWeights[ srcX - srcStartX ] = 1.f - ( fabs( (float)srcX - srcPosX ) / filterWidth );
// Sum up all the weighted colors in the filter area
accumColor.Set( 0.f, 0.f, 0.f, 0.f );
totalWeight = 0.f;
for( srcY = srcStartY; srcY <= srcEndY; srcY++ )
{
if( srcY - srcStartY < 16 )
whyWait = whyWaits[ srcY - srcStartY ];
else
whyWait = 1.f - ( fabs( (float)srcY - srcPosY ) / filterHeight );
if( whyWait <= 0.f )
continue;
srcPtr = GetAddr32( srcStartX, srcY );
for( srcX = srcStartX; srcX <= srcEndX; srcX++, srcPtr++ )
{
// Our weight...
weight = ( srcX - srcStartX < 16 ) ? xWeights[ srcX - srcStartX ] :
( 1.f - ( fabs( (float)srcX - srcPosX ) / filterWidth ) );
weight *= whyWait;
if( weight > 0.f )
{
// Grab pixel values from us...
color.FromARGB32( *srcPtr );
color *= weight;
accumColor += color;
totalWeight += weight;
}
}
}
accumColor *= 1.f / totalWeight;
// Set the final value
*destPtr = accumColor.ToARGB32();
destPtr++;
}
destPtr += destStride - destWidth;
}
}
//// ResizeNicely /////////////////////////////////////////////////////////////
// Resizes us using the ScaleNicely function. Only works for 1-level, 32bpp
// uncompressed mipmaps.
hsBool plMipmap::ResizeNicely( UInt16 newWidth, UInt16 newHeight, plMipmap::ScaleFilter filter )
{
// Make a temp buffer
UInt32 *newData = TRACKED_NEW UInt32[ newWidth * newHeight ];
if( newData == nil )
return false;
// Scale to it
ScaleNicely( newData, newWidth, newHeight, newWidth, filter );
// Reset us to that
Reset();
fWidth = newWidth;
fHeight = newHeight;
fRowBytes = fWidth * fPixelSize >> 3;
fTotalSize = fRowBytes * fWidth;
fNumLevels = 1;
IBuildLevelSizes();
fImage = newData;
SetCurrLevel( 0 );
plProfile_NewMem(MemMipmaps, fTotalSize);
#ifdef MEMORY_LEAK_TRACER
IAddToMemRecord( this, plRecord::kViaResize );
#endif
// All done!
return true;
}
#ifdef MEMORY_LEAK_TRACER
//// Debug Mipmap Memory Leak Tracker /////////////////////////////////////////
plMipmap::plRecord *plMipmap::fRecords = nil;
UInt32 plMipmap::fNumMipmaps = 0;
void plMipmap::IAddToMemRecord( plMipmap *mip, plRecord::Method method )
{
plRecord *newRecord = TRACKED_NEW plRecord;
newRecord->fCompressionType = mip->fCompressionType;
newRecord->fCreationMethod = method;
newRecord->fHeight = mip->fHeight;
newRecord->fWidth = mip->fWidth;
newRecord->fImage = mip->fImage;
newRecord->fNumLevels = mip->fNumLevels;
newRecord->fRowBytes = mip->fRowBytes;
if( mip->GetKey() )
strcpy( newRecord->fKeyName, mip->GetKeyName() );
else
strcpy( newRecord->fKeyName, "<noKey>" );
if( mip->fCompressionType != kDirectXCompression )
newRecord->fUncompressedInfo.fType = mip->fUncompressedInfo.fType;
else
{
newRecord->fDirectXInfo.fBlockSize = mip->fDirectXInfo.fBlockSize;
newRecord->fDirectXInfo.fCompressionType = mip->fDirectXInfo.fCompressionType;
}
newRecord->Link( &fRecords );
}
void plMipmap::IRemoveFromMemRecord( UInt8 *image )
{
plRecord *record;
for( record = fRecords; record != nil; record = record->fNext )
{
if( record->fImage == image )
{
record->Unlink();
delete record;
return;
}
}
}
void plMipmap::IReportLeaks()
{
plRecord *record, *next;
static char msg[ 512 ], m2[ 128 ];
UInt32 size;
hsStatusMessage( "--- plMipmap Leaks ---\n" );
for( record = fRecords; record != nil; )
{
size = record->fHeight * record->fRowBytes;
if( size >= 1024 )
sprintf( msg, "%s, %4.1f kB: \t%dx%d, %d levels, %d bpr", record->fKeyName, size / 1024.f, record->fWidth, record->fHeight, record->fNumLevels, record->fRowBytes );
else
sprintf( msg, "%s, %u bytes: \t%dx%d, %d levels, %d bpr", record->fKeyName, size, record->fWidth, record->fHeight, record->fNumLevels, record->fRowBytes );
if( record->fCompressionType != kDirectXCompression )
sprintf( m2, " UType: %d", record->fUncompressedInfo.fType );
else
sprintf( m2, " DXT%d BSz: %d", record->fDirectXInfo.fCompressionType, record->fDirectXInfo.fBlockSize );
strcat( msg, m2 );
switch( record->fCreationMethod )
{
case plRecord::kViaCreate: strcat( msg, " via Create\n" ); break;
case plRecord::kViaRead: strcat( msg, " via Read\n" ); break;
case plRecord::kViaClipToMaxSize: strcat( msg, " via ClipToMaxSize\n" ); break;
case plRecord::kViaDetailMapConstructor: strcat( msg, " via DetailMapConstructor\n" ); break;
case plRecord::kViaCopyFrom: strcat( msg, " via CopyFrom\n" ); break;
case plRecord::kViaResize: strcat( msg, " via Resize\n" ); break;
}
hsStatusMessage( msg );
next = record->fNext;
record->Unlink();
delete record;
record = next;
}
hsStatusMessage( "--- End of plMipmap Leaks ---\n" );
}
#endif