/*==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 . 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.h" #include 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; ifCompressionType = 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( bool clampU, bool 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. bool plMipmap::IGrabBorderColor( bool 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. bool 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 = ""; 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