/*==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 "HeadSpin.h"
#include "plMipmap.h"
#include "hsStream.h"
#include "hsExceptions.h"

#include "hsColorRGBA.h"
#include "plPipeline/hsGDeviceRef.h"
#include "plProfile.h"
#include "plJPEG/plJPEG.h"
#include <math.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_t width, uint32_t height, unsigned config, uint8_t numLevels, uint8_t compType, uint8_t format )
{
    Create( width, height, config, numLevels, compType, format );

#ifdef MEMORY_LEAK_TRACER
    fNumMipmaps++;
#endif
}

//// Create ///////////////////////////////////////////////////////////////////

void    plMipmap::Create( uint32_t width, uint32_t height, unsigned config, uint8_t numLevels, uint8_t compType, uint8_t 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 *)new uint8_t[ 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_t *)fImage );
#endif

        delete[] (uint8_t*)fImage;
        plProfile_DelMem(MemMipmaps, fTotalSize);
    }
    fImage = nil;
}


///////////////////////////////////////////////////////////////////////////////
//// Virtual Functions ////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

//// GetTotalSize /////////////////////////////////////////////////////////////
//  Get the total size in bytes

uint32_t  plMipmap::GetTotalSize() const
{
    return fTotalSize;
}

//// Read /////////////////////////////////////////////////////////////////////

uint32_t  plMipmap::Read( hsStream *s )
{
    uint32_t 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_t  amtToSkip = 0;

    fWidth = s->ReadLE32();
    fHeight = s->ReadLE32();
    fRowBytes = s->ReadLE32();
    fTotalSize = s->ReadLE32();
    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 *)new uint8_t[ 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_t  plMipmap::Write( hsStream *s )
{
    uint32_t totalWritten = plBitmap::Write( s );

    s->WriteLE32( fWidth );
    s->WriteLE32( fHeight );
    s->WriteLE32( fRowBytes );
    s->WriteLE32( 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_t      i;
    uint8_t       *data = (uint8_t *)fImage;


    switch( fPixelSize )
    {
        case 32:
            for( i = 0; i < fNumLevels; i++ )
            {   
                stream->ReadLE32( fLevelSizes[ i ] >> 2, (uint32_t *)data );
                data += fLevelSizes[ i ];
            }
            break;

        case 16:
            for( i = 0; i < fNumLevels; i++ )
            {
                stream->ReadLE16( fLevelSizes[ i ] >> 1, (uint16_t *)data );
                data += fLevelSizes[ i ];
            }
            break;

        default:
            hsThrow( hsBadParamException() );
    }
}

//// IWriteRawImage ///////////////////////////////////////////////////////////

void    plMipmap::IWriteRawImage( hsStream *stream )
{
    uint32_t      i;
    uint8_t       *data = (uint8_t *)fImage;


    switch( fPixelSize )
    {
        case 32:
            for( i = 0; i < fNumLevels; i++ )
            {   
                stream->WriteLE32( fLevelSizes[ i ] >> 2, (uint32_t *)data );
                data += fLevelSizes[ i ];
            }
            break;

        case 16:
            for( i = 0; i < fNumLevels; i++ )
            {
                stream->WriteLE16( fLevelSizes[ i ] >> 1, (uint16_t *)data );
                data += fLevelSizes[ i ];
            }
            break;

        default:
            hsThrow( hsBadParamException() );
    }
}

plMipmap *plMipmap::ISplitAlpha()
{
    plMipmap *retVal = new plMipmap();
    retVal->CopyFrom(this);
    memset( retVal->fImage, 0, fTotalSize );

    uint8_t *curLoc = (uint8_t *)fImage;
    uint8_t *destLoc = (uint8_t *)retVal->fImage;
    uint32_t numBytes = fTotalSize;
    uint32_t curByte = 0;

    switch( fUncompressedInfo.fType )
    {
    case UncompressedInfo::kRGB8888:
        // first uint8_t is the alpha channel, we will drop this uint8_t 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_t *curLoc = (uint8_t *)alphaChannel->fImage;
    uint8_t *destLoc = (uint8_t *)fImage;
    uint32_t numBytes = fTotalSize;
    uint32_t curByte = 0;

    switch( fUncompressedInfo.fType )
    {
    case UncompressedInfo::kRGB8888:
        // first uint8_t is the alpha channel, we will grab this uint8_t 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_t count,color;
    bool done = false;

    plMipmap *retVal = new plMipmap(fWidth,fHeight,plMipmap::kARGB32Config,1);

    uint32_t *curPos = (uint32_t*)retVal->fImage;
    uint32_t curLoc = 0;

    while (!done)
    {
        count = stream->ReadLE32();
        color = stream->ReadLE32();
        if (count == 0)
            done = true;
        else
        {
            for (uint32_t 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_t count=0,color=0,curColor=0;
    uint32_t curPos = 0;
    uint32_t totalSize = mipmap->fLevelSizes[0]/4; // we only save the first mipmap level

    uint32_t *src = (uint32_t*)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->WriteLE32(count);
            stream->WriteLE32(curColor);
            count = 0;
            curColor = color;
        }
        count++;
        src++;
        curPos++;
    }
    stream->WriteLE32(count);
    stream->WriteLE32(color);
    stream->WriteLE32(0); // terminate with zero count
    stream->WriteLE32(0);
}

void plMipmap::IReadJPEGImage( hsStream *stream )
{
    uint8_t 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_t flags = 0;

    hsNullStream *nullStream = 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 = 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_t  plMipmap::GetLevelSize( uint8_t level )
{
    if( fLevelSizes == nil )
        IBuildLevelSizes();

    return fLevelSizes[ level ];
}

//// IBuildLevelSizes /////////////////////////////////////////////////////////
//  Creates the table of level sizes, for quick reference

void    plMipmap::IBuildLevelSizes()
{
    uint8_t       level;
    uint32_t      width, height, rowBytes;


    delete [] fLevelSizes;
    fLevelSizes = new uint32_t[ fNumLevels ];
    memset( fLevelSizes, 0, sizeof( uint32_t ) * 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_t)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_t   *plMipmap::GetLevelPtr( uint8_t level, uint32_t *width, uint32_t *height, uint32_t *rowBytes )
{
    uint8_t   *data, i;
    uint32_t  w, h, r;


    if( fLevelSizes == nil )
        IBuildLevelSizes();

    for( i = 0, data = (uint8_t *)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_t 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_t maxDimension )
{
    uint8_t       *srcData, *destData, i;
    uint32_t      newSize;


    for( i = 0, newSize = fTotalSize, srcData = (uint8_t *)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 = new uint8_t[ newSize ];
    hsAssert( destData != nil, "Out of memory in ClipToMaxSize()" );
    memcpy( destData, srcData, newSize );

#ifdef MEMORY_LEAK_TRACER
    IRemoveFromMemRecord( (uint8_t *)fImage );
#endif
    delete[] (uint8_t*)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_t       *destData;


    /// Create a new image pointer
    destData = new uint8_t[ fLevelSizes[ 0 ] ];
    hsAssert( destData != nil, "Out of memory in ClipToMaxSize()" );
    memcpy( destData, fImage, fLevelSizes[ 0 ] );

#ifdef MEMORY_LEAK_TRACER
    IRemoveFromMemRecord( (uint8_t *)fImage );
#endif
    delete[] (uint8_t*)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_t kVersion           = 1;
    const float kDefaultSigma    = 1.f;
    const uint32_t kDefaultDetailBias = 5;

    // Color masks (out of 0-2)
    const uint8_t 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;
        float        **fMask;

    public:

        plFilterMask( float sig );
        virtual ~plFilterMask();

        int     Begin() const { return -fExt; }
        int     End() const { return fExt; }

        float    Mask( int i, int j ) const { return fMask[ i ][ j ]; }
};

plFilterMask::plFilterMask( float sig )
{
    fExt = (int)( sig * 2.f );
    if( fExt < 1 )
        fExt = 1;

    float **m = new float *[ ( fExt << 1 ) + 1 ];
    m += fExt;
    int i, j;
    float ooSigSq = 1.f / ( sig * sig );

    for( i = -fExt; i <= fExt; i++ )
    {
        m[ i ] = ( new float[ ( 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, float sig, uint32_t createFlags, 
        float detailDropoffStart, float detailDropoffStop, 
        float detailMax, float 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_t 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 *)new uint8_t[ 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_t 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.

float plMipmap::IGetDetailLevelAlpha( uint8_t level, float dropStart, float dropStop, float min, float max )
{
    float 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_t iDst, plMipmap *bm, float sig, uint32_t createFlags, 
                                      float detailDropoffStart, float detailDropoffStop, 
                                      float detailMax, float 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_t 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_t iDst, const plFilterMask& mask )
{
    hsAssert(fPixelSize == 32, "Only 32 bit implemented");
    ASSERT_UNCOMPRESSED();

    int i, j, ii, jj;

    if( 32 == fPixelSize )
    {
        SetCurrLevel(iDst);

        uint8_t *src = (uint8_t *)GetLevelPtr( iDst-1 );
        uint8_t *dst = (uint8_t *)GetLevelPtr(iDst);

        uint32_t srcRowBytes = fCurrLevelRowBytes << 1;
        uint32_t srcHeight = fCurrLevelHeight << 1;
        uint32_t srcWidth = fCurrLevelWidth << 1;

        for( i = 0; i < fCurrLevelHeight; i++ )
        {
            for( j = 0; j < fCurrLevelWidth; j++ )
            {
                uint8_t *center = src + (i << 1) * srcRowBytes + (j << 3);

                uint32_t chan;
                for( chan = 0; chan < 4; chan++ )
                {
                    float w = 0;
                    float 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 += (float(center[ii*srcRowBytes + (jj<<2) + chan]) + 0.5f) * mask.Mask(ii, jj);
                            }
                        }
                    }
                    a /= w;

                    dst[i * fCurrLevelRowBytes + (j << 2) + chan] = (uint8_t)a;
                }
            }
        }
    }
}

void plMipmap::ICarryZeroAlpha(uint8_t iDst)
{
    hsAssert(fPixelSize == 32, "Only 32 bit implemented");
    ASSERT_UNCOMPRESSED();

    int i, j;

    if( 32 == fPixelSize )
    {
        SetCurrLevel(iDst);

        uint8_t *src = (uint8_t *)GetLevelPtr( iDst-1 );
        uint8_t *dst = (uint8_t *)GetLevelPtr(iDst);

        uint32_t srcRowBytes = fCurrLevelRowBytes << 1;
        uint32_t srcHeight = fCurrLevelHeight << 1;
        uint32_t srcWidth = fCurrLevelWidth << 1;

        const uint8_t alphaOff = 3;
        for( i = 0; i < fCurrLevelHeight; i++ )
        {
            for( j = 0; j < fCurrLevelWidth; j++ )
            {
                uint8_t *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_t iDst, uint32_t col)
{
    hsAssert(fPixelSize == 32, "Only 32 bit implemented");
    ASSERT_UNCOMPRESSED();

    int i, j;

    if( 32 == fPixelSize )
    {
        SetCurrLevel(iDst);

        uint32_t* src = (uint32_t*)GetLevelPtr( iDst-1 );
        uint32_t* dst = (uint32_t*)GetLevelPtr(iDst);

        uint32_t srcHeight = fCurrLevelHeight << 1;
        uint32_t srcWidth = fCurrLevelWidth << 1;

        const uint8_t alphaOff = 3;
        for( i = 0; i < fCurrLevelHeight; i++ )
        {
            for( j = 0; j < fCurrLevelWidth; j++ )
            {
                uint32_t* 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_t iDst, const plFilterMask& mask, 
                                          float detailDropoffStart, float detailDropoffStop, 
                                          float detailMax, float detailMin )
{
    hsAssert(fPixelSize == 32, "Only 32 bit implemented");
    ASSERT_UNCOMPRESSED();

    int     i, j;
    uint32_t  offset;


    SetCurrLevel(iDst);

    uint8_t *dst = (uint8_t *)GetLevelPtr(iDst);

    float detailAlpha = IGetDetailLevelAlpha( iDst, detailDropoffStart, detailDropoffStop, detailMin, detailMax );

    for( i = 0; i < fCurrLevelHeight; i++ )
    {
        for( j = 0; j < fCurrLevelWidth; j++ )
        {
            uint32_t chan = 3;    // Alpha channel only
            offset = i * fCurrLevelRowBytes + (j << 2) + chan;

            float a = (float)dst[ offset ] * detailAlpha;
            dst[ offset ] = (uint8_t)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_t iDst, const plFilterMask& mask, 
                                          float detailDropoffStart, float detailDropoffStop, 
                                          float detailMax, float detailMin )
{
    hsAssert(fPixelSize == 32, "Only 32 bit implemented");
    ASSERT_UNCOMPRESSED();

    int     i, j;
    uint32_t  offset;


    SetCurrLevel(iDst);

    uint8_t *dst = (uint8_t *)GetLevelPtr(iDst);

    float detailAlpha = IGetDetailLevelAlpha( iDst, detailDropoffStart, detailDropoffStop, detailMin, detailMax );

    for( i = 0; i < fCurrLevelHeight; i++ )
    {
        for( j = 0; j < fCurrLevelWidth; j++ )
        {
            uint32_t 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_t)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_t iDst, const plFilterMask& mask, 
                                          float detailDropoffStart, float detailDropoffStop, 
                                          float detailMax, float detailMin )
{
    hsAssert(fPixelSize == 32, "Only 32 bit implemented");
    ASSERT_UNCOMPRESSED();

    int     i, j;
    uint32_t  offset;


    SetCurrLevel(iDst);

    uint8_t *dst = (uint8_t *)GetLevelPtr(iDst);

    float    detailAlpha = IGetDetailLevelAlpha( iDst, detailDropoffStart, detailDropoffStop, detailMin, detailMax );
    float    invDetailAlpha = ( 1.f - detailAlpha ) * 255.f;

    for( i = 0; i < fCurrLevelHeight; i++ )
    {
        for( j = 0; j < fCurrLevelWidth; j++ )
        {
            uint32_t 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_t)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_t  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_t *color )
{
    int         i;
    uint32_t      *src1 = (uint32_t *)fImage, *src2, testColor;


    if( !grabVNotU )
    {
        src2 = (uint32_t *)( (uint8_t *)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_t color )
{
    int     i;
    uint32_t  *src1 = (uint32_t *)fCurrLevelPtr, *src2;


    src2 = (uint32_t *)( (uint8_t *)fCurrLevelPtr + fCurrLevelRowBytes * ( fCurrLevelHeight - 1 ) );

    for( i = 0; i < fCurrLevelWidth; i++ )
    {
        src1[ i ] = color;
        src2[ i ] = color;
    }
}

//// ISetCurrLevelVBorder /////////////////////////////////////////////////////

void    plMipmap::ISetCurrLevelVBorder( uint32_t color )
{
    int     i;
    uint32_t  *src1 = (uint32_t *)fCurrLevelPtr, *src2;


    src2 = src1 + ( fCurrLevelWidth - 1 );

    for( i = 0; i < fCurrLevelHeight; i++ )
    {
        *src1 = color;
        *src2 = color;
        src1 += fCurrLevelWidth;
        src2 += fCurrLevelWidth;
    }
}

void plMipmap::Filter(float sig)
{
    hsAssert(fPixelSize == 32, "Only 32 bit implemented");
    ASSERT_UNCOMPRESSED();

    int i, j, ii, jj;

    if( 32 == fPixelSize )
    {
        uint8_t *dst = (uint8_t *)(fImage);

        uint8_t* src = (uint8_t*)HSMemory::New(fRowBytes * fHeight);
        HSMemory::BlockMove(dst, src, fRowBytes * fHeight);

        if( sig <= 0 )
            sig = kDefaultSigma;

        plFilterMask mask(sig);

        uint32_t srcRowBytes = fRowBytes;
        uint32_t srcHeight = fHeight;
        uint32_t srcWidth = fWidth;

        for( i = 0; i < fHeight; i++ )
        {
            for( j = 0; j < fWidth; j++ )
            {
                uint8_t *center = src + i * srcRowBytes + (j << 2);

                uint32_t chan;
                for( chan = 0; chan < 4; chan++ )
                {
                    float w = 0;
                    float 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 += (float(center[ii*srcRowBytes + (jj<<2) + chan]) + 0.5f) * mask.Mask(ii, jj);
                            }
                        }
                    }
                    a /= w;

                    dst[i * fRowBytes + (j << 2) + chan] = (uint8_t)(a);
                }
            }
        }

        HSMemory::Delete(src);
    }
}

static void CopyPixels(uint32_t srcWidth, uint32_t srcHeight,void *srcPixels,
                    uint32_t skipX, uint32_t skipY, uint32_t dstFormat,
                    void * &destPixels, uint32_t 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_t *pixels16 = (uint16_t*)destPixels;
    uint8_t *pixels8 = (uint8_t*)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_t plMipmap::CopyOutPixels(uint32_t destXSize, uint32_t destYSize, 
                    uint32_t dstFormat, void *destPixels, uint32_t 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_t *)fImage );
#endif
    delete[] (uint8_t*)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 *)new uint8_t[ 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 = 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_t x, uint16_t y, plMipmap::CompositeOptions *options )
{
    uint8_t   level, numLevels, srcNumLevels, srcLevelOffset, levelsToSkip;
    uint16_t  pX, pY;
    uint32_t  *srcLevelPtr, *dstLevelPtr, *srcPtr, *dstPtr;
    uint32_t  srcRowBytes, dstRowBytes, srcRowBytesToCopy, r, g, b, dR, dG, dB, srcWidth, srcHeight;
    uint32_t  srcAlpha, oneMinusAlpha, destAlpha;
    uint16_t  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_t *)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_t *)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_t)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_t)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_t)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_t)srcHeight; pY > 0; pY-- )         
            {
                // Reverse the loop so we can count downwards--slightly faster
                pX = (uint16_t)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_t)((( srcPtr[ pX ] >> 16 ) & 0x000000ff) * options->fRedTint);
                    g = (uint32_t)((( srcPtr[ pX ] >> 8  ) & 0x000000ff) * options->fGreenTint);
                    b = (uint32_t)((( 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_t      currColor, width, height;
    uint8_t       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_t level, const uint8_t *colorMask )
{   
    uint32_t      index, max, color, gray, grayDiv2, *data, width, height;
    uint8_t       compMasks[ 3 ][ 2 ] = { { 0, 0 }, { 0, 0xff }, { 0xff, 0 } };
    

    data = (uint32_t *)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_t *destPtr, uint16_t destWidth, uint16_t destHeight,
                                uint16_t destStride, plMipmap::ScaleFilter filter ) const
{
    uint16_t      destX, destY, srcX, srcY;
    int16_t       srcStartX, srcEndX, srcStartY, srcEndY;
    float       srcPosX, srcPosY, destToSrcXScale, destToSrcYScale, filterWidth, filterHeight, weight;
    float       totalWeight;
    hsColorRGBA color, accumColor;
    float       whyWaits[ 16 ], whyWait, xWeights[ 16 ];
    uint32_t      *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_t)( srcPosY - filterHeight );
        if( srcStartY < 0 ) 
            srcStartY = 0;

        srcEndY = (int16_t)( srcPosY + filterHeight );
        if( srcEndY >= fHeight ) 
            srcEndY = (int16_t)(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_t)( srcPosX - filterWidth );
            if( srcStartX < 0 ) 
                srcStartX = 0;
            
            srcEndX = (int16_t)( srcPosX + filterWidth );
            if( srcEndX >= fWidth ) 
                srcEndX = (int16_t)(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_t newWidth, uint16_t newHeight, plMipmap::ScaleFilter filter )
{
    // Make a temp buffer
    uint32_t  *newData = new uint32_t[ 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_t            plMipmap::fNumMipmaps = 0;

void    plMipmap::IAddToMemRecord( plMipmap *mip, plRecord::Method method )
{
    plRecord    *newRecord = 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() )
        newRecord->fKeyName = mip->GetKeyName();
    else
        newRecord->fKeyName = _TEMP_CONVERT_FROM_LITERAL( "<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_t *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_t      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
