Torque3D/Engine/source/gfx/bitmap/gBitmap.cpp

1450 lines
40 KiB
C++
Raw Normal View History

2012-09-19 15:15:01 +00:00
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "gfx/bitmap/gBitmap.h"
#include "core/resourceManager.h"
#include "core/stream/fileStream.h"
#include "core/strings/stringFunctions.h"
#include "core/color.h"
#include "gfx/bitmap/bitmapUtils.h"
#include "math/mRect.h"
#include "console/console.h"
#include "platform/profiler.h"
#include "console/engineAPI.h"
#include "gfx/bitmap/ddsFile.h"
2012-09-19 15:15:01 +00:00
using namespace Torque;
2012-09-19 15:15:01 +00:00
const U32 GBitmap::csFileVersion = 3;
Vector<GBitmap::Registration> GBitmap::sRegistrations( __FILE__, __LINE__ );
GBitmap::GBitmap()
: mInternalFormat(GFXFormatR8G8B8),
mBits(NULL),
mByteSize(0),
mWidth(0),
mHeight(0),
mBytesPerPixel(0),
mNumMipLevels(0),
mHasTransparency(false)
{
2020-05-11 20:00:25 +00:00
std::fill_n(mMipLevelOffsets, c_maxMipLevels, 0xffffffff);
2012-09-19 15:15:01 +00:00
}
GBitmap::GBitmap(const GBitmap& rCopy)
{
mInternalFormat = rCopy.mInternalFormat;
mByteSize = rCopy.mByteSize;
mBits = new U8[mByteSize];
dMemcpy(mBits, rCopy.mBits, mByteSize);
mWidth = rCopy.mWidth;
mHeight = rCopy.mHeight;
mBytesPerPixel = rCopy.mBytesPerPixel;
mNumMipLevels = rCopy.mNumMipLevels;
dMemcpy(mMipLevelOffsets, rCopy.mMipLevelOffsets, sizeof(mMipLevelOffsets));
mHasTransparency = rCopy.mHasTransparency;
}
GBitmap::GBitmap(const U32 in_width,
const U32 in_height,
const bool in_extrudeMipLevels,
const GFXFormat in_format)
: mBits(NULL),
mByteSize(0)
{
for (U32 i = 0; i < c_maxMipLevels; i++)
mMipLevelOffsets[i] = 0xffffffff;
allocateBitmap(in_width, in_height, in_extrudeMipLevels, in_format);
mHasTransparency = false;
}
GBitmap::GBitmap(const U32 in_width,
const U32 in_height,
const U8* data )
: mBits(NULL),
mByteSize(0)
{
allocateBitmap(in_width, in_height, false, GFXFormatR8G8B8A8);
mHasTransparency = false;
for (U32 x = 0; x < in_width; x++)
{
for (U32 y = 0; y < in_height; y++)
{
U32 offset = (x + y * in_width) * 4;
ColorI color(data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3]);
if (color.alpha < 255)
mHasTransparency = true;
setColor(x, y, color);
}
}
}
//--------------------------------------------------------------------------
GBitmap::~GBitmap()
{
deleteImage();
}
//--------------------------------------------------------------------------
void GBitmap::sRegisterFormat( const GBitmap::Registration &reg )
{
U32 insert = sRegistrations.size();
for ( U32 i = 0; i < sRegistrations.size(); i++ )
{
if ( sRegistrations[i].priority <= reg.priority )
{
insert = i;
break;
}
}
sRegistrations.insert( insert, reg );
}
const GBitmap::Registration *GBitmap::sFindRegInfo( const String &extension )
{
for ( U32 i = 0; i < GBitmap::sRegistrations.size(); i++ )
{
const GBitmap::Registration &reg = GBitmap::sRegistrations[i];
const Vector<String> &extensions = reg.extensions;
for ( U32 j = 0; j < extensions.size(); ++j )
{
if ( extensions[j].equal( extension, String::NoCase ) )
return &reg;
}
}
return NULL;
}
bool GBitmap::sFindFile( const Path &path, Path *outPath )
{
PROFILE_SCOPE( GBitmap_sFindFile );
const String origExt( String::ToLower( path.getExtension() ) );
Path tryPath( path );
for ( U32 i = 0; i < sRegistrations.size(); i++ )
{
const Registration &reg = sRegistrations[i];
const Vector<String> &extensions = reg.extensions;
for ( U32 j = 0; j < extensions.size(); ++j )
{
// We've already tried this one.
if ( extensions[j] == origExt )
continue;
tryPath.setExtension( extensions[j] );
if ( !Torque::FS::IsFile( tryPath ) )
continue;
if ( outPath )
*outPath = tryPath;
return true;
}
}
return false;
}
bool GBitmap::sFindFiles( const Path &path, Vector<Path> *outFoundPaths )
{
PROFILE_SCOPE( GBitmap_sFindFiles );
Path tryPath( path );
for ( U32 i = 0; i < GBitmap::sRegistrations.size(); i++ )
{
const GBitmap::Registration &reg = GBitmap::sRegistrations[i];
const Vector<String> &extensions = reg.extensions;
for ( U32 j = 0; j < extensions.size(); ++j )
{
tryPath.setExtension( extensions[j] );
if ( Torque::FS::IsFile( tryPath ) )
{
if ( outFoundPaths )
outFoundPaths->push_back( tryPath );
else
return true;
}
}
}
return outFoundPaths ? outFoundPaths->size() > 0 : false;
}
String GBitmap::sGetExtensionList()
{
String list;
for ( U32 i = 0; i < sRegistrations.size(); i++ )
{
const Registration &reg = sRegistrations[i];
for ( U32 j = 0; j < reg.extensions.size(); j++ )
{
list += reg.extensions[j];
list += " ";
}
}
return list;
}
//--------------------------------------------------------------------------
void GBitmap::deleteImage()
{
delete [] mBits;
mBits = NULL;
mByteSize = 0;
mWidth = 0;
mHeight = 0;
mNumMipLevels = 0;
}
//--------------------------------------------------------------------------
void GBitmap::copyRect(const GBitmap *src, const RectI &srcRect, const Point2I &dstPt, const U32 srcMipLevel, const U32 dstMipLevel)
{
if(src->getFormat() != getFormat())
return;
if(srcRect.extent.x + srcRect.point.x > src->getWidth(srcMipLevel) || srcRect.extent.y + srcRect.point.y > src->getHeight(srcMipLevel))
return;
if(srcRect.extent.x + dstPt.x > getWidth(dstMipLevel) || srcRect.extent.y + dstPt.y > getHeight(dstMipLevel))
return;
for(U32 i = 0; i < srcRect.extent.y; i++)
{
dMemcpy(getAddress(dstPt.x, dstPt.y + i, dstMipLevel),
src->getAddress(srcRect.point.x, srcRect.point.y + i, srcMipLevel),
mBytesPerPixel * srcRect.extent.x);
}
}
//--------------------------------------------------------------------------
void GBitmap::allocateBitmap(const U32 in_width, const U32 in_height, const bool in_extrudeMipLevels, const GFXFormat in_format )
{
//-------------------------------------- Some debug checks...
U32 svByteSize = mByteSize;
U8 *svBits = mBits;
AssertFatal(in_width != 0 && in_height != 0, "GBitmap::allocateBitmap: width or height is 0");
if (in_extrudeMipLevels == true)
{
AssertFatal(isPow2(in_width) == true && isPow2(in_height) == true, "GBitmap::GBitmap: in order to extrude mip levels, bitmap w/h must be pow2");
}
mInternalFormat = in_format;
mWidth = in_width;
mHeight = in_height;
mBytesPerPixel = 1;
switch (mInternalFormat)
{
case GFXFormatA8:
case GFXFormatL8: mBytesPerPixel = 1;
break;
case GFXFormatR8G8B8: mBytesPerPixel = 3;
break;
case GFXFormatR8G8B8A8_LINEAR_FORCE:
2012-09-19 15:15:01 +00:00
case GFXFormatR8G8B8X8:
case GFXFormatR8G8B8A8: mBytesPerPixel = 4;
break;
case GFXFormatL16:
2012-09-19 15:15:01 +00:00
case GFXFormatR5G6B5:
case GFXFormatR5G5B5A1: mBytesPerPixel = 2;
break;
2018-09-16 22:54:21 +00:00
case GFXFormatR16G16B16A16F:
case GFXFormatR16G16B16A16: mBytesPerPixel = 8;
break;
default:
AssertFatal(false, "GBitmap::GBitmap: misunderstood format specifier");
break;
2012-09-19 15:15:01 +00:00
}
// Set up the mip levels, if necessary...
mNumMipLevels = 1;
U32 allocPixels = in_width * in_height * mBytesPerPixel;
mMipLevelOffsets[0] = 0;
if (in_extrudeMipLevels == true)
{
U32 currWidth = in_width;
U32 currHeight = in_height;
while (currWidth != 1 || currHeight != 1)
2012-09-19 15:15:01 +00:00
{
mMipLevelOffsets[mNumMipLevels] = mMipLevelOffsets[mNumMipLevels - 1] +
(currWidth * currHeight * mBytesPerPixel);
currWidth >>= 1;
currHeight >>= 1;
if (currWidth == 0) currWidth = 1;
if (currHeight == 0) currHeight = 1;
mNumMipLevels++;
allocPixels += currWidth * currHeight * mBytesPerPixel;
}
U32 expectedMips = mFloor(mLog2(mMax(in_width, in_height))) + 1;
AssertFatal(mNumMipLevels == expectedMips, "GBitmap::allocateBitmap: mipmap count wrong");
2012-09-19 15:15:01 +00:00
}
AssertFatal(mNumMipLevels <= c_maxMipLevels, "GBitmap::allocateBitmap: too many miplevels");
// Set up the memory...
mByteSize = allocPixels;
mBits = new U8[mByteSize];
dMemset(mBits, 0xFF, mByteSize);
if(svBits != NULL)
{
dMemcpy(mBits, svBits, getMin(mByteSize, svByteSize));
delete[] svBits;
}
}
//--------------------------------------------------------------------------
void GBitmap::allocateBitmapWithMips(const U32 in_width, const U32 in_height, const U32 in_numMips, const GFXFormat in_format)
{
//-------------------------------------- Some debug checks...
U32 svByteSize = mByteSize;
U8 *svBits = mBits;
AssertFatal(in_width != 0 && in_height != 0, "GBitmap::allocateBitmap: width or height is 0");
mInternalFormat = in_format;
mWidth = in_width;
mHeight = in_height;
mBytesPerPixel = 1;
switch (mInternalFormat)
{
case GFXFormatA8:
case GFXFormatL8: mBytesPerPixel = 1;
break;
case GFXFormatR8G8B8: mBytesPerPixel = 3;
break;
case GFXFormatR8G8B8X8:
case GFXFormatR8G8B8A8: mBytesPerPixel = 4;
break;
case GFXFormatL16:
case GFXFormatR5G6B5:
case GFXFormatR5G5B5A1: mBytesPerPixel = 2;
break;
2018-12-05 10:01:58 +00:00
case GFXFormatR16G16B16A16F:
case GFXFormatR16G16B16A16: mBytesPerPixel = 8;
break;
default:
AssertFatal(false, "GBitmap::GBitmap: misunderstood format specifier");
break;
}
// Set up the mip levels, if necessary...
mNumMipLevels = 1;
U32 allocPixels = in_width * in_height * mBytesPerPixel;
mMipLevelOffsets[0] = 0;
if (in_numMips != 0)
{
U32 currWidth = in_width;
U32 currHeight = in_height;
do
{
mMipLevelOffsets[mNumMipLevels] = mMipLevelOffsets[mNumMipLevels - 1] +
(currWidth * currHeight * mBytesPerPixel);
currWidth >>= 1;
currHeight >>= 1;
if (currWidth == 0) currWidth = 1;
if (currHeight == 0) currHeight = 1;
mNumMipLevels++;
allocPixels += currWidth * currHeight * mBytesPerPixel;
} while (currWidth != 1 || currHeight != 1 && mNumMipLevels != in_numMips);
}
AssertFatal(mNumMipLevels <= c_maxMipLevels, "GBitmap::allocateBitmap: too many miplevels");
// Set up the memory...
mByteSize = allocPixels;
mBits = new U8[mByteSize];
dMemset(mBits, 0xFF, mByteSize);
if (svBits != NULL)
{
dMemcpy(mBits, svBits, getMin(mByteSize, svByteSize));
delete[] svBits;
}
}
2012-09-19 15:15:01 +00:00
//--------------------------------------------------------------------------
void GBitmap::extrudeMipLevels(bool clearBorders)
{
if(mNumMipLevels == 1)
allocateBitmap(getWidth(), getHeight(), true, getFormat());
switch (getFormat())
{
case GFXFormatR5G5B5A1:
{
for(U32 i = 1; i < mNumMipLevels; i++)
bitmapExtrude5551(getBits(i - 1), getWritableBits(i), getHeight(i), getWidth(i));
break;
}
case GFXFormatR8G8B8:
{
for(U32 i = 1; i < mNumMipLevels; i++)
bitmapExtrudeRGB(getBits(i - 1), getWritableBits(i), getHeight(i-1), getWidth(i-1));
break;
}
case GFXFormatR8G8B8A8:
case GFXFormatR8G8B8X8:
{
for(U32 i = 1; i < mNumMipLevels; i++)
bitmapExtrudeRGBA(getBits(i - 1), getWritableBits(i), getHeight(i-1), getWidth(i-1));
break;
}
2018-12-05 10:01:58 +00:00
case GFXFormatR16G16B16A16F:
{
for (U32 i = 1; i < mNumMipLevels; i++)
bitmapExtrudeFPRGBA(getBits(i - 1), getWritableBits(i), getHeight(i - 1), getWidth(i - 1));
break;
}
2012-09-19 15:15:01 +00:00
default:
break;
}
if (clearBorders)
{
for (U32 i = 1; i<mNumMipLevels; i++)
{
U32 width = getWidth(i);
U32 height = getHeight(i);
if (height<3 || width<3)
// bmp is all borders at this mip level
dMemset(getWritableBits(i),0,width*height*mBytesPerPixel);
else
{
width *= mBytesPerPixel;
U8 * bytes = getWritableBits(i);
U8 * end = bytes + (height-1)*width - mBytesPerPixel; // end = last row, 2nd column
// clear first row sans the last pixel
dMemset(bytes,0,width-mBytesPerPixel);
bytes -= mBytesPerPixel;
while (bytes<end)
{
// clear last pixel of row N-1 and first pixel of row N
bytes += width;
dMemset(bytes,0,mBytesPerPixel*2);
}
// clear last row sans the first pixel
dMemset(bytes+2*mBytesPerPixel,0,width-mBytesPerPixel);
}
}
}
}
//--------------------------------------------------------------------------
void GBitmap::chopTopMips(U32 mipsToChop)
{
U32 scalePower = getMin(mipsToChop, getNumMipLevels() - 1);
U32 newMipCount = getNumMipLevels() - scalePower;
U32 realWidth = getMax((U32)1, getWidth() >> scalePower);
U32 realHeight = getMax((U32)1, getHeight() >> scalePower);
U8 *destBits = mBits;
U32 destOffsets[c_maxMipLevels];
for (U32 i = scalePower; i<mNumMipLevels; i++)
{
// Copy to the new bitmap...
dMemcpy(destBits,
getWritableBits(i),
getSurfaceSize(i));
destOffsets[i - scalePower] = destBits - mBits;
destBits += getSurfaceSize(i);
}
dMemcpy(mMipLevelOffsets, destOffsets, sizeof(destOffsets));
mWidth = realWidth;
mHeight = realHeight;
mByteSize = destBits - mBits;
mNumMipLevels = newMipCount;
}
2012-09-19 15:15:01 +00:00
//--------------------------------------------------------------------------
void GBitmap::extrudeMipLevelsDetail()
{
AssertFatal(getFormat() == GFXFormatR8G8B8, "Error, only handles RGB for now...");
U32 i,j;
if(mNumMipLevels == 1)
allocateBitmap(getWidth(), getHeight(), true, getFormat());
for (i = 1; i < mNumMipLevels; i++) {
bitmapExtrudeRGB(getBits(i - 1), getWritableBits(i), getHeight(i-1), getWidth(i-1));
}
// Ok, now that we have the levels extruded, we need to move the lower miplevels
// closer to 0.5.
for (i = 1; i < mNumMipLevels - 1; i++) {
U8* pMipBits = (U8*)getWritableBits(i);
U32 numBytes = getWidth(i) * getHeight(i) * 3;
U32 shift = i;
U32 start = ((1 << i) - 1) * 0x80;
for (j = 0; j < numBytes; j++) {
U32 newVal = (start + pMipBits[j]) >> shift;
AssertFatal(newVal <= 255, "Error, oob");
pMipBits[j] = U8(newVal);
}
}
AssertFatal(getWidth(mNumMipLevels - 1) == 1 && getHeight(mNumMipLevels - 1) == 1,
"Error, last miplevel should be 1x1!");
((U8*)getWritableBits(mNumMipLevels - 1))[0] = 0x80;
((U8*)getWritableBits(mNumMipLevels - 1))[1] = 0x80;
((U8*)getWritableBits(mNumMipLevels - 1))[2] = 0x80;
}
//--------------------------------------------------------------------------
bool GBitmap::setFormat(GFXFormat fmt)
{
if (getFormat() == fmt)
return true;
PROFILE_SCOPE(GBitmap_setFormat);
// this is a nasty pointer math hack
// is there a quick way to calc pixels of a fully mipped bitmap?
U32 pixels = 0;
for (U32 i=0; i < mNumMipLevels; i++)
pixels += getHeight(i) * getWidth(i);
switch( getFormat() )
{
case GFXFormatR8G8B8:
switch ( fmt )
{
case GFXFormatR5G5B5A1:
#ifdef _XBOX
bitmapConvertRGB_to_1555(mBits, pixels);
#else
bitmapConvertRGB_to_5551(mBits, pixels);
#endif
mInternalFormat = GFXFormatR5G5B5A1;
mBytesPerPixel = 2;
break;
case GFXFormatR8G8B8A8:
case GFXFormatR8G8B8X8:
// Took this out, it may crash -patw
//AssertFatal( mNumMipLevels == 1, "Do the mip-mapping in hardware." );
bitmapConvertRGB_to_RGBX( &mBits, pixels );
mInternalFormat = fmt;
mBytesPerPixel = 4;
mByteSize = pixels * 4;
break;
default:
AssertWarn(0, "GBitmap::setFormat: unable to convert bitmap to requested format.");
return false;
}
break;
case GFXFormatR8G8B8X8:
switch( fmt )
{
// No change needed for this
case GFXFormatR8G8B8A8:
mInternalFormat = GFXFormatR8G8B8A8;
break;
case GFXFormatR8G8B8:
bitmapConvertRGBX_to_RGB( &mBits, pixels );
mInternalFormat = GFXFormatR8G8B8;
mBytesPerPixel = 3;
mByteSize = pixels * 3;
break;
default:
AssertWarn(0, "GBitmap::setFormat: unable to convert bitmap to requested format.");
return false;
}
break;
case GFXFormatR8G8B8A8:
switch( fmt )
{
// No change needed for this
case GFXFormatR8G8B8X8:
mInternalFormat = GFXFormatR8G8B8X8;
break;
case GFXFormatR8G8B8:
bitmapConvertRGBX_to_RGB( &mBits, pixels );
mInternalFormat = GFXFormatR8G8B8;
mBytesPerPixel = 3;
mByteSize = pixels * 3;
break;
default:
AssertWarn(0, "GBitmap::setFormat: unable to convert bitmap to requested format.");
return false;
}
break;
case GFXFormatA8:
switch( fmt )
{
case GFXFormatR8G8B8A8:
mInternalFormat = GFXFormatR8G8B8A8;
bitmapConvertA8_to_RGBA( &mBits, pixels );
mBytesPerPixel = 4;
mByteSize = pixels * 4;
break;
default:
AssertWarn(0, "GBitmap::setFormat: unable to convert bitmap to requested format.");
return false;
}
break;
default:
AssertWarn(0, "GBitmap::setFormat: unable to convert bitmap to requested format.");
return false;
}
U32 offset = 0;
for (U32 j=0; j < mNumMipLevels; j++)
{
mMipLevelOffsets[j] = offset;
offset += getHeight(j) * getWidth(j) * mBytesPerPixel;
}
return true;
}
//------------------------------------------------------------------------------
bool GBitmap::checkForTransparency()
{
mHasTransparency = false;
ColorI pixel(255, 255, 255, 255);
switch (mInternalFormat)
{
// Non-transparent formats
case GFXFormatL8:
case GFXFormatL16:
2012-09-19 15:15:01 +00:00
case GFXFormatR8G8B8:
case GFXFormatR5G6B5:
break;
// Transparent formats
case GFXFormatA8:
case GFXFormatR8G8B8A8:
case GFXFormatR5G5B5A1:
// Let getColor() do the heavy lifting
for (U32 x = 0; x < mWidth; x++)
{
for (U32 y = 0; y < mHeight; y++)
{
if (getColor(x, y, pixel))
{
if (pixel.alpha < 255)
{
mHasTransparency = true;
break;
}
}
}
}
break;
default:
AssertFatal(false, "GBitmap::checkForTransparency: misunderstood format specifier");
break;
}
return mHasTransparency;
}
//------------------------------------------------------------------------------
2021-01-01 20:05:21 +00:00
LinearColorF GBitmap::sampleTexel(F32 u, F32 v, bool retAlpha) const
2012-09-19 15:15:01 +00:00
{
LinearColorF col(0.5f, 0.5f, 0.5f);
2017-03-04 02:17:07 +00:00
// normally sampling wraps all the way around at 1.0,
// but locking doesn't support this, and we seem to calc
// the uv based on a clamped 0 - 1...
Point2F max((F32)(getWidth()-1), (F32)(getHeight()-1));
Point2F posf;
posf.x = mClampF(((u) * max.x), 0.0f, max.x);
posf.y = mClampF(((v) * max.y), 0.0f, max.y);
Point2I posi((S32)posf.x, (S32)posf.y);
const U8 *buffer = getBits();
U32 lexelindex = ((posi.y * getWidth()) + posi.x) * mBytesPerPixel;
if(mBytesPerPixel == 2)
{
//U16 *buffer = (U16 *)lockrect->pBits;
}
else if(mBytesPerPixel > 2)
{
col.red = F32(buffer[lexelindex + 0]) / 255.0f;
2012-09-19 15:15:01 +00:00
col.green = F32(buffer[lexelindex + 1]) / 255.0f;
2017-03-04 02:17:07 +00:00
col.blue = F32(buffer[lexelindex + 2]) / 255.0f;
2021-01-01 20:05:21 +00:00
if (retAlpha)
{
if (getHasTransparency())
col.alpha = F32(buffer[lexelindex + 3]) / 255.0f;
else
col.alpha = 1.0f;
}
2017-03-04 02:17:07 +00:00
}
2012-09-19 15:15:01 +00:00
2017-03-04 02:17:07 +00:00
return col;
2012-09-19 15:15:01 +00:00
}
//--------------------------------------------------------------------------
bool GBitmap::getColor(const U32 x, const U32 y, ColorI& rColor) const
{
if (x >= mWidth || y >= mHeight)
return false;
const U8* pLoc = getAddress(x, y);
switch (mInternalFormat) {
case GFXFormatA8:
case GFXFormatL8:
rColor.set( *pLoc, *pLoc, *pLoc, *pLoc );
break;
case GFXFormatL16:
rColor.set(U8(U16((pLoc[0] << 8) + pLoc[1])), 0, 0, 0);
2012-09-19 15:15:01 +00:00
case GFXFormatR8G8B8:
case GFXFormatR8G8B8X8:
rColor.set( pLoc[0], pLoc[1], pLoc[2], 255 );
break;
case GFXFormatR8G8B8A8:
rColor.set( pLoc[0], pLoc[1], pLoc[2], pLoc[3] );
break;
case GFXFormatR5G5B5A1:
#if defined(TORQUE_OS_MAC)
rColor.set( (*((U16*)pLoc) >> 0) & 0x1F,
(*((U16*)pLoc) >> 5) & 0x1F,
(*((U16*)pLoc) >> 10) & 0x1F,
((*((U16*)pLoc) >> 15) & 0x01) ? 255 : 0 );
#else
rColor.set( *((U16*)pLoc) >> 11,
(*((U16*)pLoc) >> 6) & 0x1f,
(*((U16*)pLoc) >> 1) & 0x1f,
(*((U16*)pLoc) & 1) ? 255 : 0 );
#endif
break;
default:
AssertFatal(false, "Bad internal format");
return false;
}
return true;
}
//--------------------------------------------------------------------------
bool GBitmap::setColor(const U32 x, const U32 y, const ColorI& rColor)
{
if (x >= mWidth || y >= mHeight)
return false;
U8* pLoc = getAddress(x, y);
switch (mInternalFormat) {
case GFXFormatA8:
case GFXFormatL8:
*pLoc = rColor.alpha;
break;
case GFXFormatL16:
dMemcpy(pLoc, &rColor, 2 * sizeof(U8));
break;
2012-09-19 15:15:01 +00:00
case GFXFormatR8G8B8:
dMemcpy( pLoc, &rColor, 3 * sizeof( U8 ) );
break;
case GFXFormatR8G8B8A8:
case GFXFormatR8G8B8X8:
dMemcpy( pLoc, &rColor, 4 * sizeof( U8 ) );
break;
case GFXFormatR5G6B5:
#ifdef TORQUE_OS_MAC
*((U16*)pLoc) = (rColor.red << 11) | (rColor.green << 5) | (rColor.blue << 0) ;
#else
*((U16*)pLoc) = (rColor.blue << 0) | (rColor.green << 5) | (rColor.red << 11);
#endif
break;
case GFXFormatR5G5B5A1:
#ifdef TORQUE_OS_MAC
*((U16*)pLoc) = (((rColor.alpha>0) ? 1 : 0)<<15) | (rColor.blue << 10) | (rColor.green << 5) | (rColor.red << 0);
#else
*((U16*)pLoc) = (rColor.blue << 1) | (rColor.green << 6) | (rColor.red << 11) | ((rColor.alpha>0) ? 1 : 0);
#endif
break;
default:
AssertFatal(false, "Bad internal format");
return false;
}
return true;
}
//--------------------------------------------------------------------------
U8 GBitmap::getChanelValueAt(U32 x, U32 y, U32 chan)
{
ColorI pixelColor = ColorI(255,255,255,255);
getColor(x, y, pixelColor);
if (mInternalFormat == GFXFormatL16)
{
chan = 0;
}
switch (chan) {
case 0: return pixelColor.red;
case 1: return pixelColor.green;
case 2: return pixelColor.blue;
default: return pixelColor.alpha;
}
}
2012-09-19 15:15:01 +00:00
//-----------------------------------------------------------------------------
bool GBitmap::combine( const GBitmap *bitmapA, const GBitmap *bitmapB, const TextureOp combineOp )
2012-09-19 15:15:01 +00:00
{
// Check bitmapA format
switch( bitmapA->getFormat() )
{
case GFXFormatR8G8B8:
case GFXFormatR8G8B8X8:
case GFXFormatR8G8B8A8:
break;
default:
Con::errorf( "GBitmap::combine - invalid format for bitmapA" );
return false;
}
// Check bitmapB format
switch( bitmapB->getFormat() )
{
case GFXFormatR8G8B8:
case GFXFormatR8G8B8X8:
case GFXFormatR8G8B8A8:
break;
default:
Con::errorf( "GBitmap::combine - invalid format for bitmapB" );
return false;
}
// Determine format of result texture
// CodeReview: This is dependent on the order of the GFXFormat enum. [5/11/2007 Pat]
GFXFormat resFmt = static_cast<GFXFormat>( getMax( bitmapA->getFormat(), bitmapB->getFormat() ) );
U32 resWidth = getMax( bitmapA->getWidth(), bitmapB->getWidth() );
U32 resHeight = getMax( bitmapA->getHeight(), bitmapB->getHeight() );
// Adjust size OF bitmap based on the biggest one
if( bitmapA->getWidth() != bitmapB->getWidth() ||
bitmapA->getHeight() != bitmapB->getHeight() )
{
// Delete old bitmap
deleteImage();
// Allocate new one
allocateBitmap( resWidth, resHeight, false, resFmt );
}
// Adjust format of result bitmap (if resFmt == getFormat() it will not perform the format convert)
setFormat( resFmt );
// Perform combine
U8 *destBits = getWritableBits();
const U8 *aBits = bitmapA->getBits();
const U8 *bBits = bitmapB->getBits();
for( S32 y = 0; y < getHeight(); y++ )
2012-09-19 15:15:01 +00:00
{
for( S32 x = 0; x < getWidth(); x++ )
2012-09-19 15:15:01 +00:00
{
for( S32 _byte = 0; _byte < mBytesPerPixel; _byte++ )
2012-09-19 15:15:01 +00:00
{
U8 pxA = 0;
U8 pxB = 0;
// Get contributions from A and B
if( y < bitmapA->getHeight() &&
x < bitmapA->getWidth() &&
_byte < bitmapA->mBytesPerPixel )
pxA = *aBits++;
if( y < bitmapB->getHeight() &&
x < bitmapB->getWidth() &&
_byte < bitmapB->mBytesPerPixel )
pxB = *bBits++;
// Combine them (clamp values 0-U8_MAX)
switch( combineOp )
{
case Add:
2012-09-19 15:15:01 +00:00
*destBits++ = getMin( U8( pxA + pxB ), U8_MAX );
break;
case Subtract:
2012-09-19 15:15:01 +00:00
*destBits++ = getMax( U8( pxA - pxB ), U8( 0 ) );
break;
default:
AssertFatal(false, "GBitmap::combine - Invalid combineOp");
break;
}
}
}
}
return true;
}
void GBitmap::fill( const ColorI &rColor )
{
// Set the first pixel using the slow
// but proper method.
setColor( 0, 0, rColor );
mHasTransparency = rColor.alpha < 255;
// Now fill the first row of the bitmap by
// copying the first pixel across the row.
const U32 stride = getWidth() * mBytesPerPixel;
const U8 *src = getBits();
U8 *dest = getWritableBits() + mBytesPerPixel;
const U8 *end = src + stride;
for ( ; dest != end; dest += mBytesPerPixel )
dMemcpy( dest, src, mBytesPerPixel );
// Now copy the first row to all the others.
//
// TODO: This could adaptively size the copy
// amount to copy more rows from the source
// and reduce the total number of memcpy calls.
//
dest = getWritableBits() + stride;
end = src + ( stride * getHeight() );
for ( ; dest != end; dest += stride )
dMemcpy( dest, src, stride );
}
void GBitmap::fillWhite()
{
dMemset( getWritableBits(), 255, mByteSize );
mHasTransparency = false;
}
GBitmap* GBitmap::createPaddedBitmap() const
{
if (isPow2(getWidth()) && isPow2(getHeight()))
return NULL;
AssertFatal(getNumMipLevels() == 1,
"Cannot have non-pow2 bitmap with miplevels");
U32 width = getWidth();
U32 height = getHeight();
U32 newWidth = getNextPow2(getWidth());
U32 newHeight = getNextPow2(getHeight());
GBitmap* pReturn = new GBitmap(newWidth, newHeight, false, getFormat());
for (U32 i = 0; i < height; i++)
{
U8* pDest = (U8*)pReturn->getAddress(0, i);
const U8* pSrc = (const U8*)getAddress(0, i);
dMemcpy(pDest, pSrc, width * mBytesPerPixel);
pDest += width * mBytesPerPixel;
// set the src pixel to the last pixel in the row
const U8 *pSrcPixel = pDest - mBytesPerPixel;
for(U32 j = width; j < newWidth; j++)
for(U32 k = 0; k < mBytesPerPixel; k++)
*pDest++ = pSrcPixel[k];
}
for(U32 i = height; i < newHeight; i++)
{
U8* pDest = (U8*)pReturn->getAddress(0, i);
U8* pSrc = (U8*)pReturn->getAddress(0, height-1);
dMemcpy(pDest, pSrc, newWidth * mBytesPerPixel);
}
return pReturn;
}
GBitmap* GBitmap::createPow2Bitmap() const
{
if (isPow2(getWidth()) && isPow2(getHeight()))
return NULL;
AssertFatal(getNumMipLevels() == 1,
"Cannot have non-pow2 bitmap with miplevels");
U32 width = getWidth();
U32 height = getHeight();
U32 newWidth = getNextPow2(getWidth());
U32 newHeight = getNextPow2(getHeight());
GBitmap* pReturn = new GBitmap(newWidth, newHeight, false, getFormat());
U8* pDest = (U8*)pReturn->getAddress(0, 0);
const U8* pSrc = (const U8*)getAddress(0, 0);
F32 yCoeff = (F32) height / (F32) newHeight;
F32 xCoeff = (F32) width / (F32) newWidth;
F32 currY = 0.0f;
for (U32 y = 0; y < newHeight; y++)
{
F32 currX = 0.0f;
//U32 yDestOffset = (pReturn->mWidth * pReturn->mBytesPerPixel) * y;
//U32 xDestOffset = 0;
//U32 ySourceOffset = (U32)((mWidth * mBytesPerPixel) * currY);
//F32 xSourceOffset = 0.0f;
for (U32 x = 0; x < newWidth; x++)
{
pDest = (U8*) pReturn->getAddress(x, y);
pSrc = (U8*) getAddress((S32)currX, (S32)currY);
for (U32 p = 0; p < pReturn->mBytesPerPixel; p++)
{
pDest[p] = pSrc[p];
}
currX += xCoeff;
}
currY += yCoeff;
}
return pReturn;
}
void GBitmap::copyChannel( U32 index, GBitmap *outBitmap ) const
{
AssertFatal( index < mBytesPerPixel, "GBitmap::copyChannel() - Bad channel offset!" );
AssertFatal( outBitmap, "GBitmap::copyChannel() - Null output bitmap!" );
AssertFatal( outBitmap->getWidth() == getWidth(), "GBitmap::copyChannel() - Width mismatch!" );
AssertFatal( outBitmap->getHeight() == getHeight(), "GBitmap::copyChannel() - Height mismatch!" );
U8 *outBits = outBitmap->getWritableBits();
const U32 outBytesPerPixel = outBitmap->getBytesPerPixel();
const U8 *srcBits = getBits() + index;
const U8 *endBits = getBits() + mByteSize;
for ( ; srcBits < endBits; )
{
*outBits = *srcBits;
outBits += outBytesPerPixel;
srcBits += mBytesPerPixel;
}
}
//------------------------------------------------------------------------------
bool GBitmap::read(Stream& io_rStream)
{
// Handle versioning
U32 version;
io_rStream.read(&version);
AssertFatal(version == csFileVersion, "Bitmap::read: incorrect file version");
//-------------------------------------- Read the object
U32 fmt;
io_rStream.read(&fmt);
mInternalFormat = GFXFormat(fmt);
mBytesPerPixel = 1;
switch (mInternalFormat) {
case GFXFormatA8:
case GFXFormatL8: mBytesPerPixel = 1;
break;
case GFXFormatR8G8B8: mBytesPerPixel = 3;
break;
case GFXFormatR8G8B8A8: mBytesPerPixel = 4;
break;
case GFXFormatL16:
2012-09-19 15:15:01 +00:00
case GFXFormatR5G6B5:
case GFXFormatR5G5B5A1: mBytesPerPixel = 2;
break;
default:
AssertFatal(false, "GBitmap::read: misunderstood format specifier");
break;
}
io_rStream.read(&mByteSize);
mBits = new U8[mByteSize];
io_rStream.read(mByteSize, mBits);
io_rStream.read(&mWidth);
io_rStream.read(&mHeight);
io_rStream.read(&mNumMipLevels);
for (U32 i = 0; i < c_maxMipLevels; i++)
io_rStream.read(&mMipLevelOffsets[i]);
checkForTransparency();
return (io_rStream.getStatus() == Stream::Ok);
}
bool GBitmap::write(Stream& io_rStream) const
{
// Handle versioning
io_rStream.write(csFileVersion);
//-------------------------------------- Write the object
io_rStream.write(U32(mInternalFormat));
io_rStream.write(mByteSize);
io_rStream.write(mByteSize, mBits);
io_rStream.write(mWidth);
io_rStream.write(mHeight);
io_rStream.write(mNumMipLevels);
for (U32 i = 0; i < c_maxMipLevels; i++)
io_rStream.write(mMipLevelOffsets[i]);
return (io_rStream.getStatus() == Stream::Ok);
}
//------------------------------------------------------------------------------
//-------------------------------------- Persistent I/O
//
bool GBitmap::readBitmap( const String &bmType, Stream &ioStream )
{
PROFILE_SCOPE(ResourceGBitmap_readBitmap);
2012-09-19 15:15:01 +00:00
const GBitmap::Registration *regInfo = GBitmap::sFindRegInfo( bmType );
if ( regInfo == NULL )
{
Con::errorf( "[GBitmap::readBitmap] unable to find registration for extension [%s]", bmType.c_str() );
return false;
2012-09-19 15:15:01 +00:00
}
return regInfo->readFunc( ioStream, this );
}
bool GBitmap::writeBitmap( const String &bmType, Stream &ioStream, U32 compressionLevel )
{
const GBitmap::Registration *regInfo = GBitmap::sFindRegInfo( bmType );
if ( regInfo == NULL )
{
Con::errorf( "[GBitmap::writeBitmap] unable to find registration for extension [%s]", bmType.c_str() );
return false;
2012-09-19 15:15:01 +00:00
}
return regInfo->writeFunc( this, ioStream, (compressionLevel == U32_MAX) ? regInfo->defaultCompression : compressionLevel );
}
template<> void *Resource<GBitmap>::create(const Torque::Path &path)
{
PROFILE_SCOPE( ResourceGBitmap_create );
#ifdef TORQUE_DEBUG_RES_MANAGER
Con::printf( "Resource<GBitmap>::create - [%s]", path.getFullPath().c_str() );
#endif
FileStream stream;
stream.open( path.getFullPath(), Torque::FS::File::Read );
if ( stream.getStatus() != Stream::Ok )
{
Con::errorf( "Resource<GBitmap>::create - failed to open '%s'", path.getFullPath().c_str() );
return NULL;
}
GBitmap *bmp = new GBitmap;
const String extension = path.getExtension();
if( !bmp->readBitmap( extension, stream ) )
{
Con::errorf( "Resource<GBitmap>::create - error reading '%s'", path.getFullPath().c_str() );
delete bmp;
bmp = NULL;
}
return bmp;
}
template<> ResourceBase::Signature Resource<GBitmap>::signature()
{
return MakeFourCC('b','i','t','m');
}
Resource<GBitmap> GBitmap::load(const Torque::Path &path)
{
Resource<GBitmap> ret = _load( path );
if ( ret != NULL )
return ret;
// Do a recursive search.
return _search( path );
}
Resource<GBitmap> GBitmap::_load(const Torque::Path &path)
{
PROFILE_SCOPE( GBitmap_load );
if ( Torque::FS::IsFile( path ) )
return ResourceManager::get().load( path );
Path foundPath;
if ( GBitmap::sFindFile( path, &foundPath ) )
{
Resource<GBitmap> ret = ResourceManager::get().load( foundPath );
if ( ret != NULL )
return ret;
}
return Resource< GBitmap >( NULL );
}
Resource<GBitmap> GBitmap::_search(const Torque::Path &path)
{
PROFILE_SCOPE( GBitmap_search );
// If unable to load texture in current directory
// look in the parent directory. But never look in the root.
Path newPath( path );
while ( true )
{
String filePath = newPath.getPath();
String::SizeType slash = filePath.find( '/', filePath.length(), String::Right );
if ( slash == String::NPos )
break;
slash = filePath.find( '/', filePath.length(), String::Right );
if ( slash == String::NPos )
break;
String truncPath = filePath.substr( 0, slash );
newPath.setPath( truncPath );
Resource<GBitmap> ret = _load( newPath );
if ( ret != NULL )
return ret;
}
return Resource< GBitmap >( NULL );
}
U32 GBitmap::getSurfaceSize(const U32 mipLevel) const
{
// Bump by the mip level.
U32 height = getMax(U32(1), mHeight >> mipLevel);
U32 width = getMax(U32(1), mWidth >> mipLevel);
if (mInternalFormat >= GFXFormatBC1 && mInternalFormat <= GFXFormatBC3)
{
// From the directX docs:
// max(1, width ÷ 4) x max(1, height ÷ 4) x 8(DXT1) or 16(DXT2-5)
U32 sizeMultiple = 0;
switch (mInternalFormat)
{
case GFXFormatBC1:
sizeMultiple = 8;
break;
case GFXFormatBC2:
case GFXFormatBC3:
sizeMultiple = 16;
break;
default:
AssertISV(false, "DDSFile::getSurfaceSize - invalid compressed texture format, we only support DXT1-5 right now.");
break;
}
return getMax(U32(1), width / 4) * getMax(U32(1), height / 4) * sizeMultiple;
}
else
{
return height * width* mBytesPerPixel;
}
}
2012-09-19 15:15:01 +00:00
DefineEngineFunction( getBitmapInfo, String, ( const char *filename ),,
2021-01-01 20:05:21 +00:00
"Returns image info in the following format: width TAB height TAB bytesPerPixel TAB format. "
2012-09-19 15:15:01 +00:00
"It will return an empty string if the file is not found.\n"
"@ingroup Rendering\n" )
{
Resource<GBitmap> image = GBitmap::load( filename );
if ( !image )
return String::EmptyString;
2021-01-01 20:05:21 +00:00
return String::ToString( "%d\t%d\t%d\t%d", image->getWidth(),
2012-09-19 15:15:01 +00:00
image->getHeight(),
2021-01-01 20:05:21 +00:00
image->getBytesPerPixel(),
image->getFormat());
2012-09-19 15:15:01 +00:00
}
DefineEngineFunction(saveScaledImage, bool, (const char* bitmapSource, const char* bitmapDest, S32 resolutionSize), ("", "", 512),
"Loads an image from the source path, and scales it down to the target resolution before"
"Saving it out to the destination path.\n")
{
bool isDDS = false;
if (bitmapSource == 0 || bitmapSource[0] == '\0' || bitmapDest == 0 || bitmapDest[0] == '\0')
{
return false;
}
Added sanity check to ensure that the requested file to be scaled via saveScaledImage actually exists Shifts integration of other modules with the OptionsMenu so other modules can inject their own options categories to a callOnModules hook Updated ExampleModule to use new options menu integration angle to show example option Deleted unneeded dropdown_textEdit_image asset def from baseUI Fixed incorrect internal values for the terrainIcon_image asset def that made it present as a redundant terrain material asset Cleaned up old, bad loadFilters calls and replaced them with the proper refresh() calls. Removed old, bad calls for jumping through the asset browser's tree from when it was still hardcoded organization, which cuts down a lot of error spam Cleaned up some of the asset type's preview image assignment code to be more reliable Made terrain materials now use a similar preview proxy shape as regular materials Fixed erroneous duplicate GuiInspectorTypeShapeAssetPtr::onControlDropped, which was breaking drag-n-drop actions of shapeAssets into inspector fields Added proper logic for drag-n-drop actions of imageAssets into inspector fields Add sanity check after creating new asset to avoid redundant attempts at initialization of the new asset Fixed ConvexShape Editor tooling so you can now use the UI to apply the selected material to the selected surface Added tools menu to the menubar with the Project Importer entry so the PI can be launched from either tool Implemented ability to drag-n-drop imageAssets onto MaterialEditor map fields and have it work Implemented ability to drag-n-drop imageAssets onto TerrainMaterial Editor map fields and have it work Made the TerrainMaterial editor dialogue have a non-modal background so you can interact with the editor as normal while it's up Add sanity check to avoid attempting to mark EditorTree items if we couldn't find it's id renamed BaseMap references in terrain material editor to diffuseMap for consistency
2022-03-27 08:05:48 +00:00
if (!Platform::isFile(bitmapSource))
{
return false;
}
//First, gotta check the extension, as we have some extra work to do if it's
//a DDS file
const char* ret = dStrrchr(bitmapSource, '.');
if (ret)
{
if (String::ToLower(ret) == String(".dds"))
isDDS = true;
}
else
{
return false; //no extension? bail out
}
GBitmap* image = NULL;
if (isDDS)
{
Resource<DDSFile> dds = DDSFile::load(bitmapSource, 0);
if (dds != NULL)
{
image = new GBitmap();
if (!dds->decompressToGBitmap(image))
{
delete image;
image = NULL;
}
}
}
else
{
Resource<GBitmap> resImage = GBitmap::load(bitmapSource);
image = new GBitmap(*resImage);
}
if (!image)
return false;
Torque::Path sourcePath = Torque::Path(bitmapSource);
if (isPow2(image->getWidth()) && isPow2(image->getHeight()))
image->extrudeMipLevels();
U32 mipCount = image->getNumMipLevels();
U32 targetMips = mFloor(mLog2((F32)resolutionSize)) + 1;
if (mipCount > targetMips)
{
image->chopTopMips(mipCount - targetMips);
}
//TODO: support different format targets, for now we just force
//to png for simplicity
Torque::Path destinationPath = Torque::Path(bitmapDest);
destinationPath.setExtension("png");
// Open up the file on disk.
FileStream fs;
if (!fs.open(destinationPath.getFullPath(), Torque::FS::File::Write))
{
Con::errorf("saveScaledImage() - Failed to open output file '%s'!", bitmapDest);
return false;
}
else
{
image->writeBitmap("png", fs);
fs.close();
}
return true;
}