You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
461 lines
10 KiB
461 lines
10 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//=============================================================================// |
|
|
|
#include "bitmap/bitmap.h" |
|
#include "dbg.h" |
|
|
|
// Should be last include |
|
#include "tier0/memdbgon.h" |
|
|
|
bool Bitmap_t::IsValid() const |
|
{ |
|
if ( m_nWidth <= 0 || m_nHeight <= 0 || m_pBits == NULL ) |
|
{ |
|
Assert( m_nWidth == 0 ); |
|
Assert( m_nHeight == 0 ); |
|
Assert( m_pBits == NULL ); |
|
return false; |
|
} |
|
return true; |
|
} |
|
|
|
void Bitmap_t::Clear() |
|
{ |
|
if ( m_pBits && m_bOwnsBuffer ) |
|
{ |
|
free( m_pBits ); |
|
} |
|
Reset(); |
|
} |
|
|
|
void Bitmap_t::Init( int xs, int ys, ImageFormat imageFormat, int nStride ) |
|
{ |
|
|
|
// Check for bogus allocation sizes |
|
if (xs <= 0 || ys <= 0 ) |
|
{ |
|
Assert( xs == 0 ); |
|
Assert( ys == 0 ); |
|
Clear(); |
|
return; |
|
} |
|
|
|
int nPixSize = ImageLoader::SizeInBytes( imageFormat ); |
|
|
|
// Auto detect stride |
|
if ( nStride == 0 ) |
|
{ |
|
nStride = nPixSize * xs; |
|
} |
|
|
|
// Check for NOP |
|
if ( |
|
m_pBits |
|
&& m_bOwnsBuffer |
|
&& m_nWidth == xs |
|
&& m_nHeight == ys |
|
&& nStride == m_nStride |
|
&& nPixSize == m_nPixelSize ) |
|
{ |
|
// We're already got a buffer of the right size. |
|
// The only thing that might be wrong is the pixel format. |
|
m_ImageFormat = imageFormat; |
|
return; |
|
} |
|
|
|
// Free up anything already allocated |
|
Clear(); |
|
|
|
// Remember dimensions and pixel format |
|
m_nWidth = xs; |
|
m_nHeight = ys; |
|
m_ImageFormat = imageFormat; |
|
m_nPixelSize = nPixSize; |
|
m_nStride = nStride; |
|
|
|
// Allocate buffer. Because this is a PC game, |
|
// failure is impossible....right? |
|
m_pBits = (byte *)malloc( ys * m_nStride ); |
|
|
|
// Assume ownership |
|
m_bOwnsBuffer = true; |
|
} |
|
|
|
void Bitmap_t::SetBuffer( int nWidth, int nHeight, ImageFormat imageFormat, unsigned char *pBits, bool bAssumeOwnership, int nStride ) |
|
{ |
|
Assert( pBits ); |
|
Assert( nWidth > 0 ); |
|
Assert( nHeight > 0 ); |
|
|
|
// Free up anything already allocated |
|
Clear(); |
|
|
|
// Remember dimensions and pixel format |
|
m_nWidth = nWidth; |
|
m_nHeight = nHeight; |
|
m_ImageFormat = imageFormat; |
|
m_nPixelSize = ImageLoader::SizeInBytes( imageFormat ); |
|
if ( nStride == 0 ) |
|
{ |
|
m_nStride = m_nPixelSize * nWidth; |
|
} |
|
else |
|
{ |
|
m_nStride = nStride; |
|
} |
|
|
|
// Set our buffer pointer |
|
m_pBits = pBits; |
|
|
|
// Assume ownership of the buffer, if requested |
|
m_bOwnsBuffer = bAssumeOwnership; |
|
|
|
// We should be good to go |
|
Assert( IsValid() ); |
|
} |
|
|
|
Color Bitmap_t::GetColor( int x, int y ) const |
|
{ |
|
Assert( x >= 0 && x < m_nWidth ); |
|
Assert( y >= 0 && y < m_nHeight ); |
|
Assert( m_pBits ); |
|
|
|
// Get pointer to pixel data |
|
byte *ptr = m_pBits + (y*m_nStride) + x* m_nPixelSize; |
|
|
|
// Check supported image formats |
|
switch ( m_ImageFormat ) |
|
{ |
|
case IMAGE_FORMAT_RGBA8888: |
|
return Color( ptr[0], ptr[1], ptr[2], ptr[3] ); |
|
|
|
case IMAGE_FORMAT_ABGR8888: |
|
return Color( ptr[3], ptr[2], ptr[1], ptr[0] ); |
|
|
|
default: |
|
Assert( !"Unsupport image format!"); |
|
return Color( 255,0,255,255 ); |
|
} |
|
} |
|
|
|
void Bitmap_t::SetColor( int x, int y, Color c ) |
|
{ |
|
Assert( x >= 0 && x < m_nWidth ); |
|
Assert( y >= 0 && y < m_nHeight ); |
|
Assert( m_pBits ); |
|
|
|
// Get pointer to pixel data |
|
byte *ptr = m_pBits + (y*m_nStride) + x* m_nPixelSize; |
|
|
|
// Check supported image formats |
|
switch ( m_ImageFormat ) |
|
{ |
|
case IMAGE_FORMAT_RGBA8888: |
|
ptr[0] = c.r(); |
|
ptr[1] = c.g(); |
|
ptr[2] = c.b(); |
|
ptr[3] = c.a(); |
|
break; |
|
|
|
case IMAGE_FORMAT_ABGR8888: |
|
ptr[0] = c.a(); |
|
ptr[1] = c.b(); |
|
ptr[2] = c.g(); |
|
ptr[3] = c.r(); |
|
break; |
|
|
|
default: |
|
Assert( !"Unsupport image format!"); |
|
break; |
|
} |
|
} |
|
|
|
//bool LoadVTF( const char *pszFilename ) |
|
//{ |
|
// |
|
// // Load the raw file data |
|
// CUtlBuffer fileData; |
|
// if ( !filesystem->ReadFile( pszFilename, "game", fileData ) ) |
|
// { |
|
// Warning( "Failed to load %s\n", pszFilename); |
|
// return false; |
|
// } |
|
// |
|
// return LoadVTFFromBuffer( fileData, pszFilename ); |
|
//} |
|
// |
|
//bool LoadVTFFromBuffer( CUtlBuffer fileData, const char *pszDebugName = "buffer" ) |
|
//{ |
|
// |
|
// // Parse it into VTF object |
|
// IVTFTexture *pVTFTexture( CreateVTFTexture() ); |
|
// if ( !pVTFTexture->Unserialize( fileData ) ) |
|
// { |
|
// DestroyVTFTexture( pVTFTexture ); |
|
// Warning( "Failed to deserialize VTF %s\n", pszDebugName); |
|
// return false; |
|
// } |
|
// |
|
// // We are re-reading our own files, so they should be 8888's |
|
// if ( pVTFTexture->Format() != IMAGE_FORMAT_RGBA8888 ) |
|
// { |
|
// DestroyVTFTexture( pVTFTexture ); |
|
// Warning( "%s isn't RGBA8888\n", pszDebugName); |
|
// return false; |
|
// } |
|
// |
|
// // Copy the image data |
|
// Allocate( pVTFTexture->Width(), pVTFTexture->Height() ); |
|
// for ( int y = 0 ; y < m_nHeight ; ++y ) |
|
// { |
|
// memcpy( PixPtr(0, y), pVTFTexture->ImageData(0, 0, 0, 0, y), m_nWidth*4 ); |
|
// } |
|
// |
|
// // Clean up |
|
// DestroyVTFTexture( pVTFTexture ); |
|
// return true; |
|
//} |
|
// |
|
//bool SaveVTF( CUtlBuffer &outBuffer ) |
|
//{ |
|
// // Create the VTF to write into |
|
// IVTFTexture *pVTFTexture( CreateVTFTexture() ); |
|
// const int nFlags = TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_NOLOD | TEXTUREFLAGS_SRGB; |
|
// if ( !pVTFTexture->Init( m_nWidth, m_nHeight, 1, IMAGE_FORMAT_RGBA8888, nFlags, 1, 1 ) ) |
|
// { |
|
// DestroyVTFTexture( pVTFTexture ); |
|
// return false; |
|
// } |
|
// |
|
// // write the rgba image to the vtf texture using the pixel writer |
|
// CPixelWriter pixelWriter; |
|
// pixelWriter.SetPixelMemory( pVTFTexture->Format(), pVTFTexture->ImageData(), pVTFTexture->RowSizeInBytes( 0 ) ); |
|
// |
|
// for (int y = 0; y < m_nHeight; ++y) |
|
// { |
|
// pixelWriter.Seek( 0, y ); |
|
// for (int x = 0; x < m_nWidth; ++x) |
|
// { |
|
// Color c = GetPix( x, y ); |
|
// pixelWriter.WritePixel( c.r(), c.g(), c.b(), c.a() ); |
|
// } |
|
// } |
|
// |
|
// // Serialize to the buffer |
|
// if ( !pVTFTexture->Serialize( outBuffer ) ) |
|
// { |
|
// DestroyVTFTexture( pVTFTexture ); |
|
// return false; |
|
// } |
|
// DestroyVTFTexture( pVTFTexture ); |
|
// return true; |
|
//} |
|
|
|
//void Resize( int nNewSizeX, int nNewSizeY, const Image *pImgSrc = NULL ) |
|
//{ |
|
// if ( pImgSrc == NULL ) |
|
// { |
|
// pImgSrc = this; |
|
// } |
|
// |
|
// if ( nNewSizeX == m_nWidth && nNewSizeY == m_nHeight && pImgSrc == this ) |
|
// { |
|
// return; |
|
// } |
|
// |
|
// byte *pNewData = (byte *)malloc( nNewSizeX * nNewSizeY * 4 ); |
|
// ImgUtl_StretchRGBAImage( pImgSrc->m_pBits, pImgSrc->m_nWidth, pImgSrc->m_nHeight, pNewData, nNewSizeX, nNewSizeY ); |
|
// Clear(); |
|
// m_pBits = pNewData; |
|
// m_nWidth = nNewSizeX; |
|
// m_nHeight = nNewSizeY; |
|
//} |
|
// |
|
//void Crop( int x0, int y0, int nNewSizeX, int nNewSizeY, const Image *pImgSrc ) |
|
//{ |
|
// if ( pImgSrc == NULL ) |
|
// { |
|
// pImgSrc = this; |
|
// } |
|
// |
|
// if ( nNewSizeX == m_nWidth && nNewSizeY == m_nHeight && pImgSrc == this ) |
|
// { |
|
// return; |
|
// } |
|
// |
|
// |
|
// Assert( x0 >= 0 ); |
|
// Assert( y0 >= 0 ); |
|
// Assert( x0 + nNewSizeX <= pImgSrc->m_nWidth ); |
|
// Assert( y0 + nNewSizeY <= pImgSrc->m_nHeight ); |
|
// |
|
// // Allocate new buffer |
|
// int nRowSize = nNewSizeX * 4; |
|
// byte *pNewData = (byte *)malloc( nNewSizeY * nRowSize ); |
|
// |
|
// // Copy data, one row at a time |
|
// for ( int y = 0 ; y < nNewSizeY ; ++y ) |
|
// { |
|
// memcpy( pNewData + y*nRowSize, pImgSrc->PixPtr(x0, y0+y), nRowSize ); |
|
// } |
|
// |
|
// // Replace current buffer with the new one |
|
// Clear(); |
|
// m_pBits = pNewData; |
|
// m_nWidth = nNewSizeX; |
|
// m_nHeight = nNewSizeY; |
|
//} |
|
|
|
void Bitmap_t::MakeLogicalCopyOf( Bitmap_t &src, bool bTransferBufferOwnership ) |
|
{ |
|
// What does it mean to make a logical copy of an |
|
// invalid bitmap? I'll tell you what it means: you have a bug. |
|
Assert( src.IsValid() ); |
|
|
|
// Free up anything we already own |
|
Clear(); |
|
|
|
// Copy all of the member variables so we are |
|
// a logical copy of the source bitmap |
|
m_nWidth = src.m_nWidth; |
|
m_nHeight = src.m_nHeight; |
|
m_nPixelSize = src.m_nPixelSize; |
|
m_nStride = src.m_nStride; |
|
m_ImageFormat = src.m_ImageFormat; |
|
m_pBits = src.m_pBits; |
|
Assert( !m_bOwnsBuffer ); |
|
|
|
// Check for assuming ownership of the buffer |
|
if ( bTransferBufferOwnership ) |
|
{ |
|
if ( src.m_bOwnsBuffer ) |
|
{ |
|
m_bOwnsBuffer = true; |
|
src.m_bOwnsBuffer = false; |
|
} |
|
else |
|
{ |
|
// They don't own the buffer? Then who does? |
|
// Maybe nobody, and it would safe to assume |
|
// ownership. But more than likely, this is a |
|
// bug. |
|
Assert( src.m_bOwnsBuffer ); |
|
|
|
// And a leak is better than a double-free. |
|
// Don't assume ownership of the buffer. |
|
} |
|
} |
|
} |
|
|
|
void Bitmap_t::Crop( int x0, int y0, int nWidth, int nHeight, const Bitmap_t *pImgSource ) |
|
{ |
|
// Check for cropping in place, then save off our data to a temp |
|
Bitmap_t temp; |
|
if ( pImgSource == this || !pImgSource ) |
|
{ |
|
temp.MakeLogicalCopyOf( *this, m_bOwnsBuffer ); |
|
pImgSource = &temp; |
|
} |
|
|
|
// No source image? |
|
if ( !pImgSource->IsValid() ) |
|
{ |
|
Assert( pImgSource->IsValid() ); |
|
return; |
|
} |
|
|
|
// Sanity check crop rectangle |
|
Assert( x0 >= 0 ); |
|
Assert( y0 >= 0 ); |
|
Assert( x0 + nWidth <= pImgSource->Width() ); |
|
Assert( y0 + nHeight <= pImgSource->Height() ); |
|
|
|
// Allocate buffer |
|
Init( nWidth, nHeight, pImgSource->Format() ); |
|
|
|
// Something wrong? |
|
if ( !IsValid() ) |
|
{ |
|
Assert( IsValid() ); |
|
return; |
|
} |
|
|
|
// Copy the data a row at a time |
|
int nRowSize = m_nWidth * m_nPixelSize; |
|
for ( int y = 0 ; y < m_nHeight ; ++y ) |
|
{ |
|
memcpy( GetPixel(0,y), pImgSource->GetPixel( x0, y + y0 ), nRowSize ); |
|
} |
|
} |
|
|
|
void Bitmap_t::SetPixelData( const Bitmap_t &src, int nSrcX1, int nSrcY1, int nCopySizeX, int nCopySizeY, int nDestX1, int nDestY1 ) |
|
{ |
|
// Safety |
|
if ( !src.IsValid() ) |
|
{ |
|
Assert( src.IsValid() ); |
|
return; |
|
} |
|
if ( !IsValid() ) |
|
{ |
|
Assert( IsValid() ); |
|
return; |
|
} |
|
|
|
// You need to specify a valid source rectangle, we cannot clip that for you |
|
if ( nSrcX1 < 0 || nSrcY1 < 0 || nSrcX1 + nCopySizeX > src.Width() || nSrcY1 + nCopySizeY > src.Height() ) |
|
{ |
|
Assert( nSrcX1 >= 0 ); |
|
Assert( nSrcY1 >= 0 ); |
|
Assert( nSrcX1 + nCopySizeX <= src.Width() ); |
|
Assert( nSrcY1 + nCopySizeY <= src.Height() ); |
|
return; |
|
} |
|
|
|
// But we can clip the rectangle if it extends outside the destination image in a perfectly |
|
// reasonable way |
|
if ( nDestX1 < 0 ) |
|
{ |
|
nCopySizeX += nDestX1; |
|
nDestX1 = 0; |
|
} |
|
if ( nDestX1 + nCopySizeX > Width() ) |
|
{ |
|
nCopySizeX = Width() - nDestX1; |
|
} |
|
if ( nDestY1 < 0 ) |
|
{ |
|
nCopySizeY += nDestY1; |
|
nDestY1 = 0; |
|
} |
|
if ( nDestY1 + nCopySizeY > Height() ) |
|
{ |
|
nCopySizeY = Height() - nDestY1; |
|
} |
|
if ( nCopySizeX <= 0 || nCopySizeY <= 0 ) |
|
{ |
|
return; |
|
} |
|
|
|
// Copy the pixel data |
|
for ( int y = 0 ; y < nCopySizeY ; ++y ) |
|
{ |
|
// Wow, this could be a lot faster in the common case |
|
// that the pixe formats are the same. But...this code |
|
// is simple and works, and is NOT the root of all evil. |
|
for ( int x = 0 ; x < nCopySizeX ; ++x ) |
|
{ |
|
Color c = src.GetColor( nSrcX1 + x, nSrcY1 + y ); |
|
SetColor( nDestX1 + x, nDestY1 + y, c ); |
|
} |
|
} |
|
} |
|
|
|
void Bitmap_t::SetPixelData( const Bitmap_t &src, int nDestX1, int nDestY1 ) |
|
{ |
|
SetPixelData( src, 0, 0, src.Width(), src.Height(), nDestX1, nDestY1 ); |
|
} |
|
|
|
|