//----------------------------------------------------------------------------- // 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" using namespace Torque; const U32 GBitmap::csFileVersion = 3; Vector GBitmap::sRegistrations( __FILE__, __LINE__ ); GBitmap::GBitmap() : mInternalFormat(GFXFormatR8G8B8), mBits(NULL), mByteSize(0), mWidth(0), mHeight(0), mBytesPerPixel(0), mNumMipLevels(0), mHasTransparency(false), mNumFaces(1) { std::fill_n(mMipLevelOffsets, c_maxMipLevels, 0xffffffff); std::fill_n(mFaceOffsets, 6, 0xffffffff); } 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)); dMemcpy(mFaceOffsets, rCopy.mFaceOffsets, sizeof(mFaceOffsets)); mHasTransparency = rCopy.mHasTransparency; mNumFaces = rCopy.mNumFaces; } GBitmap::GBitmap(const U32 in_width, const U32 in_height, const bool in_extrudeMipLevels, const GFXFormat in_format, const U32 in_numFaces) : mBits(NULL), mByteSize(0), mNumFaces(in_numFaces) { for (U32 i = 0; i < c_maxMipLevels; i++) mMipLevelOffsets[i] = 0xffffffff; for(U32 i = 0; i < 6; i++) mFaceOffsets[i] = 0xffffffff; allocateBitmap(in_width, in_height, in_extrudeMipLevels, in_format, in_numFaces); mHasTransparency = false; } GBitmap::GBitmap(const U32 in_width, const U32 in_height, const U8* data, const U32 in_numFaces) : mBits(NULL), mByteSize(0), mNumFaces(in_numFaces) { allocateBitmap(in_width, in_height, false, GFXFormatR8G8B8A8, in_numFaces); 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(); } //-------------------------------------------------------------------------- U32 GBitmap::getFormatBytesPerPixel(GFXFormat fmt) { switch (fmt) { // 8-bit formats case GFXFormatA8: case GFXFormatL8: case GFXFormatA4L4: return 1; // 16-bit formats case GFXFormatR5G6B5: case GFXFormatR5G5B5A1: case GFXFormatR5G5B5X1: case GFXFormatA8L8: case GFXFormatL16: case GFXFormatR16F: case GFXFormatD16: return 2; // 24-bit formats case GFXFormatR8G8B8: case GFXFormatR8G8B8_SRGB: return 3; // 32-bit formats case GFXFormatR8G8B8A8: case GFXFormatR8G8B8X8: case GFXFormatB8G8R8A8: case GFXFormatR8G8B8A8_SRGB: case GFXFormatR32F: case GFXFormatR10G10B10A2: case GFXFormatR11G11B10: case GFXFormatD24X8: case GFXFormatD24S8: case GFXFormatD24FS8: case GFXFormatR16G16: case GFXFormatR16G16F: case GFXFormatR8G8B8A8_LINEAR_FORCE: return 4; // 64-bit formats case GFXFormatR16G16B16A16: case GFXFormatR16G16B16A16F: case GFXFormatD32FS8X24: return 8; // 128-bit formats case GFXFormatR32G32B32A32F: return 16; default: AssertWarn(false, "getFormatBytesPerPixel() - Unknown or compressed format"); return 4; } } //-------------------------------------------------------------------------- void GBitmap::sRegisterFormat( const GBitmap::Registration ® ) { 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 ® = GBitmap::sRegistrations[i]; const Vector &extensions = reg.extensions; for ( U32 j = 0; j < extensions.size(); ++j ) { if ( extensions[j].equal( extension, String::NoCase ) ) return ® } } 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 ® = sRegistrations[i]; const Vector &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 *outFoundPaths ) { PROFILE_SCOPE( GBitmap_sFindFiles ); Path tryPath( path ); for ( U32 i = 0; i < GBitmap::sRegistrations.size(); i++ ) { const GBitmap::Registration ® = GBitmap::sRegistrations[i]; const Vector &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 ® = 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, const U32 in_numFaces) { //-------------------------------------- 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; mNumFaces = in_numFaces; mBytesPerPixel = getFormatBytesPerPixel(mInternalFormat); // Set up the mip levels, if necessary... mNumMipLevels = 1; mMipLevelOffsets[0] = 0; if (in_extrudeMipLevels == true) { U32 currWidth = in_width; U32 currHeight = in_height; while (currWidth != 1 || currHeight != 1) { mMipLevelOffsets[mNumMipLevels] = mMipLevelOffsets[mNumMipLevels - 1] + (currWidth * currHeight * mBytesPerPixel); currWidth >>= 1; currHeight >>= 1; if (currWidth == 0) currWidth = 1; if (currHeight == 0) currHeight = 1; mNumMipLevels++; } U32 expectedMips = mFloor(mLog2(mMax(in_width, in_height))) + 1; AssertFatal(mNumMipLevels == expectedMips, "GBitmap::allocateBitmap: mipmap count wrong"); } AssertFatal(mNumMipLevels <= c_maxMipLevels, "GBitmap::allocateBitmap: too many miplevels"); U32 faceStride = 0; for (U32 mip = 0; mip < mNumMipLevels; mip++) faceStride += getWidth(mip) * getHeight(mip) * mBytesPerPixel; for (U32 face = 0; face < mNumFaces; face++) mFaceOffsets[face] = face * faceStride; U32 allocBytes = faceStride * mNumFaces; // Set up the memory... mByteSize = allocBytes; 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, const U32 in_numFaces) { //-------------------------------------- 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; mNumFaces = in_numFaces; mBytesPerPixel = getFormatBytesPerPixel(mInternalFormat); // Set up the mip levels, if necessary... mNumMipLevels = 1; 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++; } while ((currWidth != 1 || currHeight != 1) && (mNumMipLevels != in_numMips)); } AssertFatal(mNumMipLevels <= c_maxMipLevels, "GBitmap::allocateBitmap: too many miplevels"); U32 faceStride = 0; for (U32 mip = 0; mip < mNumMipLevels; mip++) faceStride += getWidth(mip) * getHeight(mip) * mBytesPerPixel; for (U32 face = 0; face < mNumFaces; face++) mFaceOffsets[face] = face * faceStride; U32 allocBytes = faceStride * mNumFaces; // Set up the memory... mByteSize = allocBytes; mBits = new U8[mByteSize]; dMemset(mBits, 0xFF, mByteSize); if (svBits != NULL) { dMemcpy(mBits, svBits, getMin(mByteSize, svByteSize)); delete[] svBits; } } //-------------------------------------------------------------------------- void GBitmap::extrudeMipLevels(bool clearBorders) { if(mNumMipLevels == 1) allocateBitmap(getWidth(), getHeight(), true, getFormat()); if (getFormat() == GFXFormatR5G5B5A1) { for (U32 i = 1; i < mNumMipLevels; i++) bitmapExtrude5551(getBits(i - 1), getWritableBits(i), getHeight(i), getWidth(i)); } else { for (U32 i = 1; i < mNumMipLevels; i++) { bitmapResizeToOutput( getBits(i - 1), getHeight(i - 1), getWidth(i - 1), getWritableBits(i), getHeight(i), getWidth(i), mBytesPerPixel, getFormat() ); } } if (clearBorders) { for (U32 i = 1; i> scalePower); U32 realHeight = getMax((U32)1, getHeight() >> scalePower); U8 *destBits = mBits; U32 destOffsets[c_maxMipLevels]; for (U32 i = scalePower; i> 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); if (getFormat() == GFXFormatR8G8B8 && fmt == GFXFormatR5G5B5A1) { #ifdef _XBOX bitmapConvertRGB_to_1555(mBits, pixels); #else bitmapConvertRGB_to_5551(mBits, pixels); #endif mInternalFormat = GFXFormatR5G5B5A1; mBytesPerPixel = 2; } else { bitmapConvertToOutput(&mBits, pixels, getFormat(), fmt); mInternalFormat = fmt; mBytesPerPixel = getFormatBytesPerPixel(fmt); } 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; if (!mBits || mByteSize == 0) return false; ColorI pixel(255, 255, 255, 255); // Only check formats that can *possibly* have alpha. switch (mInternalFormat) { case GFXFormatA8: case GFXFormatA4L4: case GFXFormatA8L8: case GFXFormatR5G5B5A1: case GFXFormatR8G8B8A8: case GFXFormatB8G8R8A8: case GFXFormatR8G8B8A8_SRGB: case GFXFormatR10G10B10A2: case GFXFormatR16G16B16A16: case GFXFormatR16G16B16A16F: case GFXFormatR32G32B32A32F: break; // alpha-capable default: return false; // skip formats with no alpha } 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; } } } } return mHasTransparency; } //------------------------------------------------------------------------------ LinearColorF GBitmap::sampleTexel(F32 u, F32 v, bool retAlpha) const { LinearColorF col(0.5f, 0.5f, 0.5f); // 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; col.green = F32(buffer[lexelindex + 1]) / 255.0f; col.blue = F32(buffer[lexelindex + 2]) / 255.0f; if (retAlpha) { if (getHasTransparency()) col.alpha = F32(buffer[lexelindex + 3]) / 255.0f; else col.alpha = 1.0f; } } return col; } //-------------------------------------------------------------------------- bool GBitmap::getColor(const U32 x, const U32 y, ColorI& rColor, const U32 mipLevel, const U32 face) const { if (x >= mWidth || y >= mHeight) return false; U32 targMip = getNumMipLevels() < mipLevel ? getNumMipLevels()-1 : mipLevel; U32 targFace = getNumFaces() < face ? getNumFaces()-1 : face; const U8* p = getAddress(x, y, targMip, targFace); switch (mInternalFormat) { // --- 8-bit --- case GFXFormatA8: rColor.set(255, 255, 255, p[0]); break; case GFXFormatL8: rColor.set(p[0], p[0], p[0], 255); break; case GFXFormatA4L4: { U8 v = p[0]; U8 lum = (v & 0x0F) * 17; U8 alp = ((v >> 4) & 0x0F) * 17; rColor.set(lum, lum, lum, alp); break; } // --- 16-bit --- case GFXFormatR5G6B5: { U16 c = ((U16*)p)[0]; #ifdef TORQUE_BIG_ENDIAN c = convertLEndianToHost(c); #endif U8 r = (c >> 11) & 0x1F; U8 g = (c >> 5) & 0x3F; U8 b = c & 0x1F; rColor.set((r << 3) | (r >> 2), (g << 2) | (g >> 4), (b << 3) | (b >> 2), 255); break; } case GFXFormatR5G5B5A1: { U16 c = ((U16*)p)[0]; #ifdef TORQUE_BIG_ENDIAN c = convertLEndianToHost(c); #endif U8 r = (c >> 11) & 0x1F; U8 g = (c >> 6) & 0x1F; U8 b = (c >> 1) & 0x1F; U8 a = (c & 0x01) ? 255 : 0; rColor.set((r << 3) | (r >> 2), (g << 3) | (g >> 2), (b << 3) | (b >> 2), a); break; } case GFXFormatA8L8: { U16 c = ((U16*)p)[0]; #ifdef TORQUE_BIG_ENDIAN c = convertLEndianToHost(c); #endif U8 l = c & 0xFF; U8 a = (c >> 8) & 0xFF; rColor.set(l, l, l, a); break; } case GFXFormatL16: { U16 l = ((U16*)p)[0]; #ifdef TORQUE_BIG_ENDIAN l = convertLEndianToHost(l); #endif rColor.set(convert16To8(l), convert16To8(l), convert16To8(l), 255); break; } case GFXFormatR16F: { const U16* v = (U16*)p; rColor.set( floatTo8(convertHalfToFloat(v[0])), 0, 0, 255 ); break; } // --- 24-bit --- case GFXFormatR8G8B8: case GFXFormatR8G8B8_SRGB: rColor.set(p[0], p[1], p[2], 255); break; // --- 32-bit --- case GFXFormatR32F: { const F32* v = (F32*)p; rColor.set( floatTo8(v[0]), // red 0, // green 0, // blue 255 // alpha ); break; } case GFXFormatR16G16: { const U16* v = (U16*)p; #ifdef TORQUE_BIG_ENDIAN U16 r = convertLEndianToHost(v[0]); U16 g = convertLEndianToHost(v[1]); #else U16 r = v[0]; U16 g = v[1]; #endif rColor.set( convert16To8(r), // red convert16To8(g), // green 0, // blue 255 // alpha ); break; } case GFXFormatR16G16F: { const U16* v = (U16*)p; rColor.set( floatTo8(convertHalfToFloat(v[0])), floatTo8(convertHalfToFloat(v[1])), 0, 255 ); break; } case GFXFormatR8G8B8A8: case GFXFormatR8G8B8A8_SRGB: case GFXFormatR8G8B8X8: rColor.set(p[0], p[1], p[2], (mInternalFormat == GFXFormatR8G8B8X8) ? 255 : p[3]); break; case GFXFormatB8G8R8A8: rColor.set(p[2], p[1], p[0], p[3]); break; // --- 64-bit --- case GFXFormatR16G16B16A16: { const U16* v = (U16*)p; #ifdef TORQUE_BIG_ENDIAN rColor.set( convert16To8(v[2]), convert16To8(v[1]), convert16To8(v[0]), convert16To8(v[3])); #else rColor.set( convert16To8(v[0]), convert16To8(v[1]), convert16To8(v[2]), convert16To8(v[3])); #endif break; } case GFXFormatR16G16B16A16F: { const U16* v = (const U16*)p; rColor.set(floatTo8( convertHalfToFloat(v[0])), floatTo8(convertHalfToFloat(v[1])), floatTo8(convertHalfToFloat(v[2])), floatTo8(convertHalfToFloat(v[3]))); break; } // --- 128-bit --- case GFXFormatR32G32B32A32F: { const F32* v = (const F32*)p; rColor.set( floatTo8(v[0]), floatTo8(v[1]), floatTo8(v[2]), floatTo8(v[3])); 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* p = getAddress(x, y); switch (mInternalFormat) { // --- 8-bit --- case GFXFormatA8: *p = rColor.alpha; break; case GFXFormatL8: *p = rColor.red; // L = R channel break; case GFXFormatA4L4: { U8 lum = rColor.red / 17; U8 alp = rColor.alpha / 17; *p = (alp << 4) | (lum & 0x0F); break; } // --- 16-bit --- case GFXFormatR5G6B5: { U16 r = rColor.red * 31 / 255; U16 g = rColor.green * 63 / 255; U16 b = rColor.blue * 31 / 255; #ifdef TORQUE_BIG_ENDIAN * (U16*)p = (r << 11) | (g << 5) | b; #else * (U16*)p = (b) | (g << 5) | (r << 11); #endif break; } case GFXFormatR5G5B5A1: { U16 r = rColor.red * 31 / 255; U16 g = rColor.green * 31 / 255; U16 b = rColor.blue * 31 / 255; U16 a = (rColor.alpha > 0) ? 1 : 0; #ifdef TORQUE_BIG_ENDIAN * (U16*)p = (a << 15) | (b << 10) | (g << 5) | r; #else * (U16*)p = (r << 11) | (g << 6) | (b << 1) | a; #endif break; } case GFXFormatA8L8: { U16 l = rColor.red; U16 a = rColor.alpha; #ifdef TORQUE_BIG_ENDIAN * (U16*)p = (a << 8) | l; #else * (U16*)p = (l) | (a << 8); #endif break; } case GFXFormatL16: *(U16*)p = convert8To16(rColor.red); break; case GFXFormatR16F: { U16* v = (U16*)p; v[0] = convertFloatToHalf(rColor.red / 255.f); break; } // --- 24-bit --- case GFXFormatR8G8B8: case GFXFormatR8G8B8_SRGB: p[0] = rColor.red; p[1] = rColor.green; p[2] = rColor.blue; break; // --- 32-bit --- case GFXFormatR32F: { F32* v = (F32*)p; v[0] = rColor.red / 255.f; break; } case GFXFormatR16G16: { U16* v = (U16*)p; v[0] = convert8To16(rColor.red); v[1] = convert8To16(rColor.green); break; } case GFXFormatR16G16F: { U16* v = (U16*)p; v[0] = convertFloatToHalf(rColor.red / 255.f); v[1] = convertFloatToHalf(rColor.green / 255.f); break; } case GFXFormatR8G8B8A8: case GFXFormatR8G8B8A8_SRGB: case GFXFormatR8G8B8X8: p[0] = rColor.red; p[1] = rColor.green; p[2] = rColor.blue; p[3] = (mInternalFormat == GFXFormatR8G8B8X8) ? 255 : rColor.alpha; break; case GFXFormatB8G8R8A8: p[0] = rColor.blue; p[1] = rColor.green; p[2] = rColor.red; p[3] = rColor.alpha; break; // --- 64-bit --- case GFXFormatR16G16B16A16: { U16* v = (U16*)p; v[0] = convert8To16(rColor.red); v[1] = convert8To16(rColor.green); v[2] = convert8To16(rColor.blue); v[3] = convert8To16(rColor.alpha); break; } case GFXFormatR16G16B16A16F: { U16* v = (U16*)p; v[0] = convertFloatToHalf(rColor.red / 255.f); v[1] = convertFloatToHalf(rColor.green / 255.f); v[2] = convertFloatToHalf(rColor.blue / 255.f); v[3] = convertFloatToHalf(rColor.alpha / 255.f); break; } // --- 128-bit --- case GFXFormatR32G32B32A32F: { F32* v = (F32*)p; v[0] = rColor.red / 255.f; v[1] = rColor.green / 255.f; v[2] = rColor.blue / 255.f; v[3] = rColor.alpha / 255.f; break; } default: AssertFatal(false, "Bad internal format in setColor"); 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 || mInternalFormat == GFXFormatL8) { chan = 0; } switch (chan) { case 0: return pixelColor.red; case 1: return pixelColor.green; case 2: return pixelColor.blue; default: return pixelColor.alpha; } } //----------------------------------------------------------------------------- bool GBitmap::combine( const GBitmap *bitmapA, const GBitmap *bitmapB, const TextureOp combineOp ) { // 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( 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++ ) { for( S32 x = 0; x < getWidth(); x++ ) { for( S32 _byte = 0; _byte < mBytesPerPixel; _byte++ ) { 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: *destBits++ = getMin( U8( pxA + pxB ), U8_MAX ); break; case Subtract: *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) { PROFILE_SCOPE(GBitmap_Read); // 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 = getFormatBytesPerPixel(mInternalFormat); 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 { PROFILE_SCOPE(GBitmap_Write); // 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, const Torque::Path& path) { PROFILE_SCOPE(ResourceGBitmap_readBitmap); 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; } return regInfo->readFunc(path, this); } bool GBitmap::readBitmapStream(const String& bmType, Stream& ioStream, U32 len) { PROFILE_SCOPE(ResourceGBitmap_readBitmapStream); 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; } return regInfo->readStreamFunc(ioStream, this, len); } bool GBitmap::writeBitmap( const String &bmType, const Torque::Path& path, U32 compressionLevel ) { FileStream stream; if (!stream.open(path, Torque::FS::File::Write)) { Con::errorf("GBitmap::writeBitmap failed to open path %s", path.getFullFileName().c_str()); stream.close(); return false; } // free file for stb stream.close(); 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; } return regInfo->writeFunc(path, this, (compressionLevel == U32_MAX) ? regInfo->defaultCompression : compressionLevel ); } bool GBitmap::writeBitmapStream(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; } return regInfo->writeStreamFunc(bmType, ioStream, this, (compressionLevel == U32_MAX) ? regInfo->defaultCompression : compressionLevel); } template<> void *Resource::create(const Torque::Path &path) { PROFILE_SCOPE( ResourceGBitmap_create ); #ifdef TORQUE_DEBUG_RES_MANAGER Con::printf( "Resource::create - [%s]", path.getFullPath().c_str() ); #endif GBitmap* bmp = new GBitmap; FileStream stream; Torque::Path dbm = path; dbm.setExtension("dbm"); if (Torque::FS::IsFile(dbm)) { Torque::FS::FileNodeRef assetFile = Torque::FS::GetFileNode(path); Torque::FS::FileNodeRef compiledFile = Torque::FS::GetFileNode(dbm); if (assetFile != NULL && compiledFile != NULL) { if (compiledFile->getModifiedTime() >= assetFile->getModifiedTime()) { #ifdef TORQUE_DEBUG_RES_MANAGER Con::printf("Resource::create - Loading cached image file: %s", dbm.getFullPath().c_str()); #endif stream.open(dbm.getFullPath(), Torque::FS::File::Read); bmp->read(stream); return bmp; } } } stream.open( path.getFullPath(), Torque::FS::File::Read ); if ( stream.getStatus() != Stream::Ok ) { Con::errorf( "Resource::create - failed to open '%s'", path.getFullPath().c_str() ); return NULL; } const String extension = path.getExtension(); if( !bmp->readBitmap( extension, path ) ) { // we can only get here if the stream was successful, so attempt to read the stream. Con::warnf("Was unable to load as file, going to try the stream instead."); if (!bmp->readBitmapStream(extension, stream, stream.getStreamSize())) { Con::errorf("Resource::create - error reading '%s'", path.getFullPath().c_str()); delete bmp; bmp = NULL; } } return bmp; } template<> ResourceBase::Signature Resource::signature() { return MakeFourCC('b','i','t','m'); } Resource GBitmap::load(const Torque::Path &path) { Resource ret = _load( path ); if ( ret != NULL ) return ret; // Do a recursive search. return _search( path ); } Resource 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 ret = ResourceManager::get().load( foundPath ); if ( ret != NULL ) return ret; } return Resource< GBitmap >( NULL ); } Resource 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 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; } } DefineEngineFunction( getBitmapInfo, String, ( const char *filename ),, "Returns image info in the following format: width TAB height TAB bytesPerPixel TAB format. " "It will return an empty string if the file is not found.\n" "@ingroup Rendering\n" ) { Resource image = GBitmap::load( filename ); if ( !image ) return String::EmptyString; return String::ToString( "%d\t%d\t%d\t%d", image->getWidth(), image->getHeight(), image->getBytesPerPixel(), image->getFormat()); } DefineEngineFunction(saveScaledImage, bool, (const char* bitmapSource, const char* bitmapDest, S32 resolutionSize), ("", "", 256), "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; bool isHDR = false; if (bitmapSource == 0 || bitmapSource[0] == '\0' || bitmapDest == 0 || bitmapDest[0] == '\0') { return false; } 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; if (String::ToLower(ret) == String(".hdr")) isHDR = true; } else { return false; //no extension? bail out } GBitmap* image = NULL; if (isDDS) { Resource dds = DDSFile::load(bitmapSource, 0); if (dds != NULL) { image = new GBitmap(); if (!dds->decompressToGBitmap(image)) { delete image; image = NULL; } } } else { Resource 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(); image->setFormat(GFXFormatR8G8B8A8); U32 mipCount = image->getNumMipLevels(); U32 targetMips = mFloor(mLog2((F32)(resolutionSize ? resolutionSize : 256))) + 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"); if(!image->writeBitmap("png", destinationPath.getFullPath())) { Con::errorf("saveScaledImage() - Error writing %s !", bitmapDest); delete image; return false; } delete image; return true; }