|
|
|
/*==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"
|
|
|
|
|
|
|
|
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 )
|
|
|
|
{
|
|
|
|
fUncompressedInfo.fType = format;
|
|
|
|
}
|
|
|
|
else if( compType == kJPEGCompression )
|
|
|
|
{
|
|
|
|
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 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;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
//// 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:
|
|
|
|
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:
|
|
|
|
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 );
|
|
|
|
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 )
|
|
|
|
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
|