gBitmap.cpp
Engine/source/gfx/bitmap/gBitmap.cpp
Public Functions
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 <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> is not <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">found.\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Rendering\n</a>" )
Detailed Description
Public Functions
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 <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> is not <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">found.\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Rendering\n</a>" )
1 2//----------------------------------------------------------------------------- 3// Copyright (c) 2012 GarageGames, LLC 4// 5// Permission is hereby granted, free of charge, to any person obtaining a copy 6// of this software and associated documentation files (the "Software"), to 7// deal in the Software without restriction, including without limitation the 8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9// sell copies of the Software, and to permit persons to whom the Software is 10// furnished to do so, subject to the following conditions: 11// 12// The above copyright notice and this permission notice shall be included in 13// all copies or substantial portions of the Software. 14// 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21// IN THE SOFTWARE. 22//----------------------------------------------------------------------------- 23 24#include "platform/platform.h" 25#include "gfx/bitmap/gBitmap.h" 26 27#include "core/resourceManager.h" 28#include "core/stream/fileStream.h" 29#include "core/strings/stringFunctions.h" 30#include "core/color.h" 31#include "gfx/bitmap/bitmapUtils.h" 32#include "math/mRect.h" 33#include "console/console.h" 34#include "platform/profiler.h" 35#include "console/engineAPI.h" 36 37using namespace Torque; 38 39const U32 GBitmap::csFileVersion = 3; 40 41Vector<GBitmap::Registration> GBitmap::sRegistrations( __FILE__, __LINE__ ); 42 43 44GBitmap::GBitmap() 45 : mInternalFormat(GFXFormatR8G8B8), 46 mBits(NULL), 47 mByteSize(0), 48 mWidth(0), 49 mHeight(0), 50 mBytesPerPixel(0), 51 mNumMipLevels(0), 52 mHasTransparency(false) 53{ 54 std::fill_n(mMipLevelOffsets, c_maxMipLevels, 0xffffffff); 55} 56 57GBitmap::GBitmap(const GBitmap& rCopy) 58{ 59 mInternalFormat = rCopy.mInternalFormat; 60 61 mByteSize = rCopy.mByteSize; 62 mBits = new U8[mByteSize]; 63 dMemcpy(mBits, rCopy.mBits, mByteSize); 64 65 mWidth = rCopy.mWidth; 66 mHeight = rCopy.mHeight; 67 mBytesPerPixel = rCopy.mBytesPerPixel; 68 mNumMipLevels = rCopy.mNumMipLevels; 69 dMemcpy(mMipLevelOffsets, rCopy.mMipLevelOffsets, sizeof(mMipLevelOffsets)); 70 71 mHasTransparency = rCopy.mHasTransparency; 72} 73 74 75GBitmap::GBitmap(const U32 in_width, 76 const U32 in_height, 77 const bool in_extrudeMipLevels, 78 const GFXFormat in_format) 79 : mBits(NULL), 80 mByteSize(0) 81{ 82 for (U32 i = 0; i < c_maxMipLevels; i++) 83 mMipLevelOffsets[i] = 0xffffffff; 84 85 allocateBitmap(in_width, in_height, in_extrudeMipLevels, in_format); 86 87 mHasTransparency = false; 88} 89 90GBitmap::GBitmap(const U32 in_width, 91 const U32 in_height, 92 const U8* data ) 93 : mBits(NULL), 94 mByteSize(0) 95{ 96 allocateBitmap(in_width, in_height, false, GFXFormatR8G8B8A8); 97 98 mHasTransparency = false; 99 100 for (U32 x = 0; x < in_width; x++) 101 { 102 for (U32 y = 0; y < in_height; y++) 103 { 104 U32 offset = (x + y * in_width) * 4; 105 106 ColorI color(data[offset], 107 data[offset + 1], 108 data[offset + 2], 109 data[offset + 3]); 110 111 if (color.alpha < 255) 112 mHasTransparency = true; 113 114 setColor(x, y, color); 115 } 116 } 117} 118 119 120//-------------------------------------------------------------------------- 121 122GBitmap::~GBitmap() 123{ 124 deleteImage(); 125} 126 127//-------------------------------------------------------------------------- 128 129void GBitmap::sRegisterFormat( const GBitmap::Registration ® ) 130{ 131 U32 insert = sRegistrations.size(); 132 for ( U32 i = 0; i < sRegistrations.size(); i++ ) 133 { 134 if ( sRegistrations[i].priority <= reg.priority ) 135 { 136 insert = i; 137 break; 138 } 139 } 140 141 sRegistrations.insert( insert, reg ); 142} 143 144const GBitmap::Registration *GBitmap::sFindRegInfo( const String &extension ) 145{ 146 for ( U32 i = 0; i < GBitmap::sRegistrations.size(); i++ ) 147 { 148 const GBitmap::Registration ® = GBitmap::sRegistrations[i]; 149 const Vector<String> &extensions = reg.extensions; 150 151 for ( U32 j = 0; j < extensions.size(); ++j ) 152 { 153 if ( extensions[j].equal( extension, String::NoCase ) ) 154 return ® 155 } 156 } 157 158 return NULL; 159} 160 161bool GBitmap::sFindFile( const Path &path, Path *outPath ) 162{ 163 PROFILE_SCOPE( GBitmap_sFindFile ); 164 165 const String origExt( String::ToLower( path.getExtension() ) ); 166 167 Path tryPath( path ); 168 169 for ( U32 i = 0; i < sRegistrations.size(); i++ ) 170 { 171 const Registration ® = sRegistrations[i]; 172 const Vector<String> &extensions = reg.extensions; 173 174 for ( U32 j = 0; j < extensions.size(); ++j ) 175 { 176 // We've already tried this one. 177 if ( extensions[j] == origExt ) 178 continue; 179 180 tryPath.setExtension( extensions[j] ); 181 if ( !Torque::FS::IsFile( tryPath ) ) 182 continue; 183 184 if ( outPath ) 185 *outPath = tryPath; 186 return true; 187 } 188 } 189 190 return false; 191} 192 193bool GBitmap::sFindFiles( const Path &path, Vector<Path> *outFoundPaths ) 194{ 195 PROFILE_SCOPE( GBitmap_sFindFiles ); 196 197 Path tryPath( path ); 198 199 for ( U32 i = 0; i < GBitmap::sRegistrations.size(); i++ ) 200 { 201 const GBitmap::Registration ® = GBitmap::sRegistrations[i]; 202 const Vector<String> &extensions = reg.extensions; 203 204 for ( U32 j = 0; j < extensions.size(); ++j ) 205 { 206 tryPath.setExtension( extensions[j] ); 207 208 if ( Torque::FS::IsFile( tryPath ) ) 209 { 210 if ( outFoundPaths ) 211 outFoundPaths->push_back( tryPath ); 212 else 213 return true; 214 } 215 } 216 } 217 218 return outFoundPaths ? outFoundPaths->size() > 0 : false; 219} 220 221String GBitmap::sGetExtensionList() 222{ 223 String list; 224 225 for ( U32 i = 0; i < sRegistrations.size(); i++ ) 226 { 227 const Registration ® = sRegistrations[i]; 228 for ( U32 j = 0; j < reg.extensions.size(); j++ ) 229 { 230 list += reg.extensions[j]; 231 list += " "; 232 } 233 } 234 235 return list; 236} 237 238//-------------------------------------------------------------------------- 239void GBitmap::deleteImage() 240{ 241 delete [] mBits; 242 mBits = NULL; 243 mByteSize = 0; 244 245 mWidth = 0; 246 mHeight = 0; 247 mNumMipLevels = 0; 248} 249 250 251//-------------------------------------------------------------------------- 252 253void GBitmap::copyRect(const GBitmap *src, const RectI &srcRect, const Point2I &dstPt, const U32 srcMipLevel, const U32 dstMipLevel) 254{ 255 if(src->getFormat() != getFormat()) 256 return; 257 if(srcRect.extent.x + srcRect.point.x > src->getWidth(srcMipLevel) || srcRect.extent.y + srcRect.point.y > src->getHeight(srcMipLevel)) 258 return; 259 if(srcRect.extent.x + dstPt.x > getWidth(dstMipLevel) || srcRect.extent.y + dstPt.y > getHeight(dstMipLevel)) 260 return; 261 262 for(U32 i = 0; i < srcRect.extent.y; i++) 263 { 264 dMemcpy(getAddress(dstPt.x, dstPt.y + i, dstMipLevel), 265 src->getAddress(srcRect.point.x, srcRect.point.y + i, srcMipLevel), 266 mBytesPerPixel * srcRect.extent.x); 267 } 268} 269 270//-------------------------------------------------------------------------- 271void GBitmap::allocateBitmap(const U32 in_width, const U32 in_height, const bool in_extrudeMipLevels, const GFXFormat in_format ) 272{ 273 //-------------------------------------- Some debug checks... 274 U32 svByteSize = mByteSize; 275 U8 *svBits = mBits; 276 277 AssertFatal(in_width != 0 && in_height != 0, "GBitmap::allocateBitmap: width or height is 0"); 278 279 if (in_extrudeMipLevels == true) 280 { 281 AssertFatal(isPow2(in_width) == true && isPow2(in_height) == true, "GBitmap::GBitmap: in order to extrude mip levels, bitmap w/h must be pow2"); 282 } 283 284 mInternalFormat = in_format; 285 mWidth = in_width; 286 mHeight = in_height; 287 288 mBytesPerPixel = 1; 289 switch (mInternalFormat) 290 { 291 case GFXFormatA8: 292 case GFXFormatL8: mBytesPerPixel = 1; 293 break; 294 case GFXFormatR8G8B8: mBytesPerPixel = 3; 295 break; 296 case GFXFormatR8G8B8A8_LINEAR_FORCE: 297 case GFXFormatR8G8B8X8: 298 case GFXFormatR8G8B8A8: mBytesPerPixel = 4; 299 break; 300 case GFXFormatL16: 301 case GFXFormatR5G6B5: 302 case GFXFormatR5G5B5A1: mBytesPerPixel = 2; 303 break; 304 case GFXFormatR16G16B16A16F: 305 case GFXFormatR16G16B16A16: mBytesPerPixel = 8; 306 break; 307 default: 308 AssertFatal(false, "GBitmap::GBitmap: misunderstood format specifier"); 309 break; 310 } 311 312 // Set up the mip levels, if necessary... 313 mNumMipLevels = 1; 314 U32 allocPixels = in_width * in_height * mBytesPerPixel; 315 mMipLevelOffsets[0] = 0; 316 317 318 if (in_extrudeMipLevels == true) 319 { 320 U32 currWidth = in_width; 321 U32 currHeight = in_height; 322 323 while (currWidth != 1 || currHeight != 1) 324 { 325 mMipLevelOffsets[mNumMipLevels] = mMipLevelOffsets[mNumMipLevels - 1] + 326 (currWidth * currHeight * mBytesPerPixel); 327 currWidth >>= 1; 328 currHeight >>= 1; 329 if (currWidth == 0) currWidth = 1; 330 if (currHeight == 0) currHeight = 1; 331 332 mNumMipLevels++; 333 allocPixels += currWidth * currHeight * mBytesPerPixel; 334 } 335 336 U32 expectedMips = mFloor(mLog2(mMax(in_width, in_height))) + 1; 337 AssertFatal(mNumMipLevels == expectedMips, "GBitmap::allocateBitmap: mipmap count wrong"); 338 } 339 AssertFatal(mNumMipLevels <= c_maxMipLevels, "GBitmap::allocateBitmap: too many miplevels"); 340 341 // Set up the memory... 342 mByteSize = allocPixels; 343 mBits = new U8[mByteSize]; 344 345 dMemset(mBits, 0xFF, mByteSize); 346 347 if(svBits != NULL) 348 { 349 dMemcpy(mBits, svBits, getMin(mByteSize, svByteSize)); 350 delete[] svBits; 351 } 352} 353 354//-------------------------------------------------------------------------- 355void GBitmap::allocateBitmapWithMips(const U32 in_width, const U32 in_height, const U32 in_numMips, const GFXFormat in_format) 356{ 357 //-------------------------------------- Some debug checks... 358 U32 svByteSize = mByteSize; 359 U8 *svBits = mBits; 360 361 AssertFatal(in_width != 0 && in_height != 0, "GBitmap::allocateBitmap: width or height is 0"); 362 363 mInternalFormat = in_format; 364 mWidth = in_width; 365 mHeight = in_height; 366 367 mBytesPerPixel = 1; 368 switch (mInternalFormat) 369 { 370 case GFXFormatA8: 371 case GFXFormatL8: mBytesPerPixel = 1; 372 break; 373 case GFXFormatR8G8B8: mBytesPerPixel = 3; 374 break; 375 case GFXFormatR8G8B8X8: 376 case GFXFormatR8G8B8A8: mBytesPerPixel = 4; 377 break; 378 case GFXFormatL16: 379 case GFXFormatR5G6B5: 380 case GFXFormatR5G5B5A1: mBytesPerPixel = 2; 381 break; 382 case GFXFormatR16G16B16A16F: 383 case GFXFormatR16G16B16A16: mBytesPerPixel = 8; 384 break; 385 default: 386 AssertFatal(false, "GBitmap::GBitmap: misunderstood format specifier"); 387 break; 388 } 389 390 // Set up the mip levels, if necessary... 391 mNumMipLevels = 1; 392 U32 allocPixels = in_width * in_height * mBytesPerPixel; 393 mMipLevelOffsets[0] = 0; 394 395 396 if (in_numMips != 0) 397 { 398 U32 currWidth = in_width; 399 U32 currHeight = in_height; 400 401 do 402 { 403 mMipLevelOffsets[mNumMipLevels] = mMipLevelOffsets[mNumMipLevels - 1] + 404 (currWidth * currHeight * mBytesPerPixel); 405 currWidth >>= 1; 406 currHeight >>= 1; 407 if (currWidth == 0) currWidth = 1; 408 if (currHeight == 0) currHeight = 1; 409 410 mNumMipLevels++; 411 allocPixels += currWidth * currHeight * mBytesPerPixel; 412 } while (currWidth != 1 || currHeight != 1 && mNumMipLevels != in_numMips); 413 } 414 AssertFatal(mNumMipLevels <= c_maxMipLevels, "GBitmap::allocateBitmap: too many miplevels"); 415 416 // Set up the memory... 417 mByteSize = allocPixels; 418 mBits = new U8[mByteSize]; 419 420 dMemset(mBits, 0xFF, mByteSize); 421 422 if (svBits != NULL) 423 { 424 dMemcpy(mBits, svBits, getMin(mByteSize, svByteSize)); 425 delete[] svBits; 426 } 427} 428 429//-------------------------------------------------------------------------- 430void GBitmap::extrudeMipLevels(bool clearBorders) 431{ 432 if(mNumMipLevels == 1) 433 allocateBitmap(getWidth(), getHeight(), true, getFormat()); 434 435 switch (getFormat()) 436 { 437 case GFXFormatR5G5B5A1: 438 { 439 for(U32 i = 1; i < mNumMipLevels; i++) 440 bitmapExtrude5551(getBits(i - 1), getWritableBits(i), getHeight(i), getWidth(i)); 441 break; 442 } 443 444 case GFXFormatR8G8B8: 445 { 446 for(U32 i = 1; i < mNumMipLevels; i++) 447 bitmapExtrudeRGB(getBits(i - 1), getWritableBits(i), getHeight(i-1), getWidth(i-1)); 448 break; 449 } 450 451 case GFXFormatR8G8B8A8: 452 case GFXFormatR8G8B8X8: 453 { 454 for(U32 i = 1; i < mNumMipLevels; i++) 455 bitmapExtrudeRGBA(getBits(i - 1), getWritableBits(i), getHeight(i-1), getWidth(i-1)); 456 break; 457 } 458 459 case GFXFormatR16G16B16A16F: 460 { 461 for (U32 i = 1; i < mNumMipLevels; i++) 462 bitmapExtrudeFPRGBA(getBits(i - 1), getWritableBits(i), getHeight(i - 1), getWidth(i - 1)); 463 break; 464 } 465 466 default: 467 break; 468 } 469 if (clearBorders) 470 { 471 for (U32 i = 1; i<mNumMipLevels; i++) 472 { 473 U32 width = getWidth(i); 474 U32 height = getHeight(i); 475 if (height<3 || width<3) 476 // bmp is all borders at this mip level 477 dMemset(getWritableBits(i),0,width*height*mBytesPerPixel); 478 else 479 { 480 width *= mBytesPerPixel; 481 U8 * bytes = getWritableBits(i); 482 U8 * end = bytes + (height-1)*width - mBytesPerPixel; // end = last row, 2nd column 483 // clear first row sans the last pixel 484 dMemset(bytes,0,width-mBytesPerPixel); 485 bytes -= mBytesPerPixel; 486 while (bytes<end) 487 { 488 // clear last pixel of row N-1 and first pixel of row N 489 bytes += width; 490 dMemset(bytes,0,mBytesPerPixel*2); 491 } 492 // clear last row sans the first pixel 493 dMemset(bytes+2*mBytesPerPixel,0,width-mBytesPerPixel); 494 } 495 } 496 } 497} 498 499//-------------------------------------------------------------------------- 500void GBitmap::chopTopMips(U32 mipsToChop) 501{ 502 U32 scalePower = getMin(mipsToChop, getNumMipLevels() - 1); 503 U32 newMipCount = getNumMipLevels() - scalePower; 504 505 U32 realWidth = getMax((U32)1, getWidth() >> scalePower); 506 U32 realHeight = getMax((U32)1, getHeight() >> scalePower); 507 508 U8 *destBits = mBits; 509 510 U32 destOffsets[c_maxMipLevels]; 511 512 for (U32 i = scalePower; i<mNumMipLevels; i++) 513 { 514 // Copy to the new bitmap... 515 dMemcpy(destBits, 516 getWritableBits(i), 517 getSurfaceSize(i)); 518 519 destOffsets[i - scalePower] = destBits - mBits; 520 destBits += getSurfaceSize(i); 521 } 522 523 dMemcpy(mMipLevelOffsets, destOffsets, sizeof(destOffsets)); 524 525 mWidth = realWidth; 526 mHeight = realHeight; 527 mByteSize = destBits - mBits; 528 mNumMipLevels = newMipCount; 529} 530 531//-------------------------------------------------------------------------- 532void GBitmap::extrudeMipLevelsDetail() 533{ 534 AssertFatal(getFormat() == GFXFormatR8G8B8, "Error, only handles RGB for now..."); 535 U32 i,j; 536 537 if(mNumMipLevels == 1) 538 allocateBitmap(getWidth(), getHeight(), true, getFormat()); 539 540 for (i = 1; i < mNumMipLevels; i++) { 541 bitmapExtrudeRGB(getBits(i - 1), getWritableBits(i), getHeight(i-1), getWidth(i-1)); 542 } 543 544 // Ok, now that we have the levels extruded, we need to move the lower miplevels 545 // closer to 0.5. 546 for (i = 1; i < mNumMipLevels - 1; i++) { 547 U8* pMipBits = (U8*)getWritableBits(i); 548 U32 numBytes = getWidth(i) * getHeight(i) * 3; 549 550 U32 shift = i; 551 U32 start = ((1 << i) - 1) * 0x80; 552 553 for (j = 0; j < numBytes; j++) { 554 U32 newVal = (start + pMipBits[j]) >> shift; 555 AssertFatal(newVal <= 255, "Error, oob"); 556 pMipBits[j] = U8(newVal); 557 } 558 } 559 AssertFatal(getWidth(mNumMipLevels - 1) == 1 && getHeight(mNumMipLevels - 1) == 1, 560 "Error, last miplevel should be 1x1!"); 561 ((U8*)getWritableBits(mNumMipLevels - 1))[0] = 0x80; 562 ((U8*)getWritableBits(mNumMipLevels - 1))[1] = 0x80; 563 ((U8*)getWritableBits(mNumMipLevels - 1))[2] = 0x80; 564} 565 566//-------------------------------------------------------------------------- 567bool GBitmap::setFormat(GFXFormat fmt) 568{ 569 if (getFormat() == fmt) 570 return true; 571 572 PROFILE_SCOPE(GBitmap_setFormat); 573 574 // this is a nasty pointer math hack 575 // is there a quick way to calc pixels of a fully mipped bitmap? 576 U32 pixels = 0; 577 for (U32 i=0; i < mNumMipLevels; i++) 578 pixels += getHeight(i) * getWidth(i); 579 580 switch( getFormat() ) 581 { 582 case GFXFormatR8G8B8: 583 switch ( fmt ) 584 { 585 case GFXFormatR5G5B5A1: 586#ifdef _XBOX 587 bitmapConvertRGB_to_1555(mBits, pixels); 588#else 589 bitmapConvertRGB_to_5551(mBits, pixels); 590#endif 591 mInternalFormat = GFXFormatR5G5B5A1; 592 mBytesPerPixel = 2; 593 break; 594 595 case GFXFormatR8G8B8A8: 596 case GFXFormatR8G8B8X8: 597 // Took this out, it may crash -patw 598 //AssertFatal( mNumMipLevels == 1, "Do the mip-mapping in hardware." ); 599 600 bitmapConvertRGB_to_RGBX( &mBits, pixels ); 601 mInternalFormat = fmt; 602 mBytesPerPixel = 4; 603 mByteSize = pixels * 4; 604 break; 605 606 default: 607 AssertWarn(0, "GBitmap::setFormat: unable to convert bitmap to requested format."); 608 return false; 609 } 610 break; 611 612 case GFXFormatR8G8B8X8: 613 switch( fmt ) 614 { 615 // No change needed for this 616 case GFXFormatR8G8B8A8: 617 mInternalFormat = GFXFormatR8G8B8A8; 618 break; 619 620 case GFXFormatR8G8B8: 621 bitmapConvertRGBX_to_RGB( &mBits, pixels ); 622 mInternalFormat = GFXFormatR8G8B8; 623 mBytesPerPixel = 3; 624 mByteSize = pixels * 3; 625 break; 626 627 default: 628 AssertWarn(0, "GBitmap::setFormat: unable to convert bitmap to requested format."); 629 return false; 630 } 631 break; 632 633 case GFXFormatR8G8B8A8: 634 switch( fmt ) 635 { 636 // No change needed for this 637 case GFXFormatR8G8B8X8: 638 mInternalFormat = GFXFormatR8G8B8X8; 639 break; 640 641 case GFXFormatR8G8B8: 642 bitmapConvertRGBX_to_RGB( &mBits, pixels ); 643 mInternalFormat = GFXFormatR8G8B8; 644 mBytesPerPixel = 3; 645 mByteSize = pixels * 3; 646 break; 647 648 default: 649 AssertWarn(0, "GBitmap::setFormat: unable to convert bitmap to requested format."); 650 return false; 651 } 652 break; 653 654 case GFXFormatA8: 655 switch( fmt ) 656 { 657 case GFXFormatR8G8B8A8: 658 mInternalFormat = GFXFormatR8G8B8A8; 659 bitmapConvertA8_to_RGBA( &mBits, pixels ); 660 mBytesPerPixel = 4; 661 mByteSize = pixels * 4; 662 break; 663 664 default: 665 AssertWarn(0, "GBitmap::setFormat: unable to convert bitmap to requested format."); 666 return false; 667 } 668 break; 669 670 default: 671 AssertWarn(0, "GBitmap::setFormat: unable to convert bitmap to requested format."); 672 return false; 673 } 674 675 U32 offset = 0; 676 for (U32 j=0; j < mNumMipLevels; j++) 677 { 678 mMipLevelOffsets[j] = offset; 679 offset += getHeight(j) * getWidth(j) * mBytesPerPixel; 680 } 681 682 return true; 683} 684 685//------------------------------------------------------------------------------ 686 687bool GBitmap::checkForTransparency() 688{ 689 mHasTransparency = false; 690 691 ColorI pixel(255, 255, 255, 255); 692 693 switch (mInternalFormat) 694 { 695 // Non-transparent formats 696 case GFXFormatL8: 697 case GFXFormatL16: 698 case GFXFormatR8G8B8: 699 case GFXFormatR5G6B5: 700 break; 701 // Transparent formats 702 case GFXFormatA8: 703 case GFXFormatR8G8B8A8: 704 case GFXFormatR5G5B5A1: 705 // Let getColor() do the heavy lifting 706 for (U32 x = 0; x < mWidth; x++) 707 { 708 for (U32 y = 0; y < mHeight; y++) 709 { 710 if (getColor(x, y, pixel)) 711 { 712 if (pixel.alpha < 255) 713 { 714 mHasTransparency = true; 715 break; 716 } 717 } 718 } 719 } 720 721 break; 722 default: 723 AssertFatal(false, "GBitmap::checkForTransparency: misunderstood format specifier"); 724 break; 725 } 726 727 return mHasTransparency; 728} 729 730//------------------------------------------------------------------------------ 731LinearColorF GBitmap::sampleTexel(F32 u, F32 v, bool retAlpha) const 732{ 733 LinearColorF col(0.5f, 0.5f, 0.5f); 734 // normally sampling wraps all the way around at 1.0, 735 // but locking doesn't support this, and we seem to calc 736 // the uv based on a clamped 0 - 1... 737 Point2F max((F32)(getWidth()-1), (F32)(getHeight()-1)); 738 Point2F posf; 739 posf.x = mClampF(((u) * max.x), 0.0f, max.x); 740 posf.y = mClampF(((v) * max.y), 0.0f, max.y); 741 Point2I posi((S32)posf.x, (S32)posf.y); 742 743 const U8 *buffer = getBits(); 744 U32 lexelindex = ((posi.y * getWidth()) + posi.x) * mBytesPerPixel; 745 746 if(mBytesPerPixel == 2) 747 { 748 //U16 *buffer = (U16 *)lockrect->pBits; 749 } 750 else if(mBytesPerPixel > 2) 751 { 752 col.red = F32(buffer[lexelindex + 0]) / 255.0f; 753 col.green = F32(buffer[lexelindex + 1]) / 255.0f; 754 col.blue = F32(buffer[lexelindex + 2]) / 255.0f; 755 if (retAlpha) 756 { 757 if (getHasTransparency()) 758 col.alpha = F32(buffer[lexelindex + 3]) / 255.0f; 759 else 760 col.alpha = 1.0f; 761 } 762 } 763 764 return col; 765} 766 767//-------------------------------------------------------------------------- 768bool GBitmap::getColor(const U32 x, const U32 y, ColorI& rColor) const 769{ 770 if (x >= mWidth || y >= mHeight) 771 return false; 772 773 const U8* pLoc = getAddress(x, y); 774 775 switch (mInternalFormat) { 776 case GFXFormatA8: 777 case GFXFormatL8: 778 rColor.set( *pLoc, *pLoc, *pLoc, *pLoc ); 779 break; 780 case GFXFormatL16: 781 rColor.set(U8(U16((pLoc[0] << 8) + pLoc[1])), 0, 0, 0); 782 case GFXFormatR8G8B8: 783 case GFXFormatR8G8B8X8: 784 rColor.set( pLoc[0], pLoc[1], pLoc[2], 255 ); 785 break; 786 787 case GFXFormatR8G8B8A8: 788 rColor.set( pLoc[0], pLoc[1], pLoc[2], pLoc[3] ); 789 break; 790 791 case GFXFormatR5G5B5A1: 792#if defined(TORQUE_OS_MAC) 793 rColor.set( (*((U16*)pLoc) >> 0) & 0x1F, 794 (*((U16*)pLoc) >> 5) & 0x1F, 795 (*((U16*)pLoc) >> 10) & 0x1F, 796 ((*((U16*)pLoc) >> 15) & 0x01) ? 255 : 0 ); 797#else 798 rColor.set( *((U16*)pLoc) >> 11, 799 (*((U16*)pLoc) >> 6) & 0x1f, 800 (*((U16*)pLoc) >> 1) & 0x1f, 801 (*((U16*)pLoc) & 1) ? 255 : 0 ); 802#endif 803 break; 804 805 default: 806 AssertFatal(false, "Bad internal format"); 807 return false; 808 } 809 810 return true; 811} 812 813 814//-------------------------------------------------------------------------- 815 816 817bool GBitmap::setColor(const U32 x, const U32 y, const ColorI& rColor) 818{ 819 if (x >= mWidth || y >= mHeight) 820 return false; 821 822 U8* pLoc = getAddress(x, y); 823 824 switch (mInternalFormat) { 825 case GFXFormatA8: 826 case GFXFormatL8: 827 *pLoc = rColor.alpha; 828 break; 829 830 case GFXFormatL16: 831 dMemcpy(pLoc, &rColor, 2 * sizeof(U8)); 832 break; 833 834 case GFXFormatR8G8B8: 835 dMemcpy( pLoc, &rColor, 3 * sizeof( U8 ) ); 836 break; 837 838 case GFXFormatR8G8B8A8: 839 case GFXFormatR8G8B8X8: 840 dMemcpy( pLoc, &rColor, 4 * sizeof( U8 ) ); 841 break; 842 843 case GFXFormatR5G6B5: 844 #ifdef TORQUE_OS_MAC 845 *((U16*)pLoc) = (rColor.red << 11) | (rColor.green << 5) | (rColor.blue << 0) ; 846 #else 847 *((U16*)pLoc) = (rColor.blue << 0) | (rColor.green << 5) | (rColor.red << 11); 848 #endif 849 break; 850 851 case GFXFormatR5G5B5A1: 852 #ifdef TORQUE_OS_MAC 853 *((U16*)pLoc) = (((rColor.alpha>0) ? 1 : 0)<<15) | (rColor.blue << 10) | (rColor.green << 5) | (rColor.red << 0); 854 #else 855 *((U16*)pLoc) = (rColor.blue << 1) | (rColor.green << 6) | (rColor.red << 11) | ((rColor.alpha>0) ? 1 : 0); 856 #endif 857 break; 858 859 default: 860 AssertFatal(false, "Bad internal format"); 861 return false; 862 } 863 864 return true; 865} 866 867//-------------------------------------------------------------------------- 868U8 GBitmap::getChanelValueAt(U32 x, U32 y, U32 chan) 869{ 870 ColorI pixelColor = ColorI(255,255,255,255); 871 getColor(x, y, pixelColor); 872 if (mInternalFormat == GFXFormatL16) 873 { 874 chan = 0; 875 } 876 switch (chan) { 877 case 0: return pixelColor.red; 878 case 1: return pixelColor.green; 879 case 2: return pixelColor.blue; 880 default: return pixelColor.alpha; 881 } 882} 883 884//----------------------------------------------------------------------------- 885 886bool GBitmap::combine( const GBitmap *bitmapA, const GBitmap *bitmapB, const TextureOp combineOp ) 887{ 888 // Check bitmapA format 889 switch( bitmapA->getFormat() ) 890 { 891 case GFXFormatR8G8B8: 892 case GFXFormatR8G8B8X8: 893 case GFXFormatR8G8B8A8: 894 break; 895 896 default: 897 Con::errorf( "GBitmap::combine - invalid format for bitmapA" ); 898 return false; 899 } 900 901 // Check bitmapB format 902 switch( bitmapB->getFormat() ) 903 { 904 case GFXFormatR8G8B8: 905 case GFXFormatR8G8B8X8: 906 case GFXFormatR8G8B8A8: 907 break; 908 909 default: 910 Con::errorf( "GBitmap::combine - invalid format for bitmapB" ); 911 return false; 912 } 913 914 // Determine format of result texture 915 // CodeReview: This is dependent on the order of the GFXFormat enum. [5/11/2007 Pat] 916 GFXFormat resFmt = static_cast<GFXFormat>( getMax( bitmapA->getFormat(), bitmapB->getFormat() ) ); 917 U32 resWidth = getMax( bitmapA->getWidth(), bitmapB->getWidth() ); 918 U32 resHeight = getMax( bitmapA->getHeight(), bitmapB->getHeight() ); 919 920 // Adjust size OF bitmap based on the biggest one 921 if( bitmapA->getWidth() != bitmapB->getWidth() || 922 bitmapA->getHeight() != bitmapB->getHeight() ) 923 { 924 // Delete old bitmap 925 deleteImage(); 926 927 // Allocate new one 928 allocateBitmap( resWidth, resHeight, false, resFmt ); 929 } 930 931 // Adjust format of result bitmap (if resFmt == getFormat() it will not perform the format convert) 932 setFormat( resFmt ); 933 934 // Perform combine 935 U8 *destBits = getWritableBits(); 936 const U8 *aBits = bitmapA->getBits(); 937 const U8 *bBits = bitmapB->getBits(); 938 939 for( S32 y = 0; y < getHeight(); y++ ) 940 { 941 for( S32 x = 0; x < getWidth(); x++ ) 942 { 943 for( S32 _byte = 0; _byte < mBytesPerPixel; _byte++ ) 944 { 945 U8 pxA = 0; 946 U8 pxB = 0; 947 948 // Get contributions from A and B 949 if( y < bitmapA->getHeight() && 950 x < bitmapA->getWidth() && 951 _byte < bitmapA->mBytesPerPixel ) 952 pxA = *aBits++; 953 954 if( y < bitmapB->getHeight() && 955 x < bitmapB->getWidth() && 956 _byte < bitmapB->mBytesPerPixel ) 957 pxB = *bBits++; 958 959 // Combine them (clamp values 0-U8_MAX) 960 switch( combineOp ) 961 { 962 case Add: 963 *destBits++ = getMin( U8( pxA + pxB ), U8_MAX ); 964 break; 965 966 case Subtract: 967 *destBits++ = getMax( U8( pxA - pxB ), U8( 0 ) ); 968 break; 969 default: 970 AssertFatal(false, "GBitmap::combine - Invalid combineOp"); 971 break; 972 } 973 } 974 } 975 } 976 977 return true; 978} 979 980void GBitmap::fill( const ColorI &rColor ) 981{ 982 // Set the first pixel using the slow 983 // but proper method. 984 setColor( 0, 0, rColor ); 985 mHasTransparency = rColor.alpha < 255; 986 987 // Now fill the first row of the bitmap by 988 // copying the first pixel across the row. 989 const U32 stride = getWidth() * mBytesPerPixel; 990 const U8 *src = getBits(); 991 U8 *dest = getWritableBits() + mBytesPerPixel; 992 const U8 *end = src + stride; 993 for ( ; dest != end; dest += mBytesPerPixel ) 994 dMemcpy( dest, src, mBytesPerPixel ); 995 996 // Now copy the first row to all the others. 997 // 998 // TODO: This could adaptively size the copy 999 // amount to copy more rows from the source 1000 // and reduce the total number of memcpy calls. 1001 // 1002 dest = getWritableBits() + stride; 1003 end = src + ( stride * getHeight() ); 1004 for ( ; dest != end; dest += stride ) 1005 dMemcpy( dest, src, stride ); 1006} 1007 1008void GBitmap::fillWhite() 1009{ 1010 dMemset( getWritableBits(), 255, mByteSize ); 1011 mHasTransparency = false; 1012} 1013 1014GBitmap* GBitmap::createPaddedBitmap() const 1015{ 1016 if (isPow2(getWidth()) && isPow2(getHeight())) 1017 return NULL; 1018 1019 AssertFatal(getNumMipLevels() == 1, 1020 "Cannot have non-pow2 bitmap with miplevels"); 1021 1022 U32 width = getWidth(); 1023 U32 height = getHeight(); 1024 1025 U32 newWidth = getNextPow2(getWidth()); 1026 U32 newHeight = getNextPow2(getHeight()); 1027 1028 GBitmap* pReturn = new GBitmap(newWidth, newHeight, false, getFormat()); 1029 1030 for (U32 i = 0; i < height; i++) 1031 { 1032 U8* pDest = (U8*)pReturn->getAddress(0, i); 1033 const U8* pSrc = (const U8*)getAddress(0, i); 1034 1035 dMemcpy(pDest, pSrc, width * mBytesPerPixel); 1036 1037 pDest += width * mBytesPerPixel; 1038 // set the src pixel to the last pixel in the row 1039 const U8 *pSrcPixel = pDest - mBytesPerPixel; 1040 1041 for(U32 j = width; j < newWidth; j++) 1042 for(U32 k = 0; k < mBytesPerPixel; k++) 1043 *pDest++ = pSrcPixel[k]; 1044 } 1045 1046 for(U32 i = height; i < newHeight; i++) 1047 { 1048 U8* pDest = (U8*)pReturn->getAddress(0, i); 1049 U8* pSrc = (U8*)pReturn->getAddress(0, height-1); 1050 dMemcpy(pDest, pSrc, newWidth * mBytesPerPixel); 1051 } 1052 1053 return pReturn; 1054} 1055 1056GBitmap* GBitmap::createPow2Bitmap() const 1057{ 1058 if (isPow2(getWidth()) && isPow2(getHeight())) 1059 return NULL; 1060 1061 AssertFatal(getNumMipLevels() == 1, 1062 "Cannot have non-pow2 bitmap with miplevels"); 1063 1064 U32 width = getWidth(); 1065 U32 height = getHeight(); 1066 1067 U32 newWidth = getNextPow2(getWidth()); 1068 U32 newHeight = getNextPow2(getHeight()); 1069 1070 GBitmap* pReturn = new GBitmap(newWidth, newHeight, false, getFormat()); 1071 1072 U8* pDest = (U8*)pReturn->getAddress(0, 0); 1073 const U8* pSrc = (const U8*)getAddress(0, 0); 1074 1075 F32 yCoeff = (F32) height / (F32) newHeight; 1076 F32 xCoeff = (F32) width / (F32) newWidth; 1077 1078 F32 currY = 0.0f; 1079 for (U32 y = 0; y < newHeight; y++) 1080 { 1081 F32 currX = 0.0f; 1082 //U32 yDestOffset = (pReturn->mWidth * pReturn->mBytesPerPixel) * y; 1083 //U32 xDestOffset = 0; 1084 //U32 ySourceOffset = (U32)((mWidth * mBytesPerPixel) * currY); 1085 //F32 xSourceOffset = 0.0f; 1086 for (U32 x = 0; x < newWidth; x++) 1087 { 1088 pDest = (U8*) pReturn->getAddress(x, y); 1089 pSrc = (U8*) getAddress((S32)currX, (S32)currY); 1090 for (U32 p = 0; p < pReturn->mBytesPerPixel; p++) 1091 { 1092 pDest[p] = pSrc[p]; 1093 } 1094 currX += xCoeff; 1095 } 1096 currY += yCoeff; 1097 } 1098 1099 return pReturn; 1100} 1101 1102void GBitmap::copyChannel( U32 index, GBitmap *outBitmap ) const 1103{ 1104 AssertFatal( index < mBytesPerPixel, "GBitmap::copyChannel() - Bad channel offset!" ); 1105 AssertFatal( outBitmap, "GBitmap::copyChannel() - Null output bitmap!" ); 1106 AssertFatal( outBitmap->getWidth() == getWidth(), "GBitmap::copyChannel() - Width mismatch!" ); 1107 AssertFatal( outBitmap->getHeight() == getHeight(), "GBitmap::copyChannel() - Height mismatch!" ); 1108 1109 U8 *outBits = outBitmap->getWritableBits(); 1110 const U32 outBytesPerPixel = outBitmap->getBytesPerPixel(); 1111 const U8 *srcBits = getBits() + index; 1112 const U8 *endBits = getBits() + mByteSize; 1113 1114 for ( ; srcBits < endBits; ) 1115 { 1116 *outBits = *srcBits; 1117 outBits += outBytesPerPixel; 1118 srcBits += mBytesPerPixel; 1119 } 1120} 1121 1122//------------------------------------------------------------------------------ 1123 1124bool GBitmap::read(Stream& io_rStream) 1125{ 1126 // Handle versioning 1127 U32 version; 1128 io_rStream.read(&version); 1129 AssertFatal(version == csFileVersion, "Bitmap::read: incorrect file version"); 1130 1131 //-------------------------------------- Read the object 1132 U32 fmt; 1133 io_rStream.read(&fmt); 1134 mInternalFormat = GFXFormat(fmt); 1135 mBytesPerPixel = 1; 1136 switch (mInternalFormat) { 1137 case GFXFormatA8: 1138 case GFXFormatL8: mBytesPerPixel = 1; 1139 break; 1140 case GFXFormatR8G8B8: mBytesPerPixel = 3; 1141 break; 1142 case GFXFormatR8G8B8A8: mBytesPerPixel = 4; 1143 break; 1144 case GFXFormatL16: 1145 case GFXFormatR5G6B5: 1146 case GFXFormatR5G5B5A1: mBytesPerPixel = 2; 1147 break; 1148 default: 1149 AssertFatal(false, "GBitmap::read: misunderstood format specifier"); 1150 break; 1151 } 1152 1153 io_rStream.read(&mByteSize); 1154 1155 mBits = new U8[mByteSize]; 1156 io_rStream.read(mByteSize, mBits); 1157 1158 io_rStream.read(&mWidth); 1159 io_rStream.read(&mHeight); 1160 1161 io_rStream.read(&mNumMipLevels); 1162 for (U32 i = 0; i < c_maxMipLevels; i++) 1163 io_rStream.read(&mMipLevelOffsets[i]); 1164 1165 checkForTransparency(); 1166 1167 return (io_rStream.getStatus() == Stream::Ok); 1168} 1169 1170bool GBitmap::write(Stream& io_rStream) const 1171{ 1172 // Handle versioning 1173 io_rStream.write(csFileVersion); 1174 1175 //-------------------------------------- Write the object 1176 io_rStream.write(U32(mInternalFormat)); 1177 1178 io_rStream.write(mByteSize); 1179 io_rStream.write(mByteSize, mBits); 1180 1181 io_rStream.write(mWidth); 1182 io_rStream.write(mHeight); 1183 1184 io_rStream.write(mNumMipLevels); 1185 for (U32 i = 0; i < c_maxMipLevels; i++) 1186 io_rStream.write(mMipLevelOffsets[i]); 1187 1188 return (io_rStream.getStatus() == Stream::Ok); 1189} 1190 1191//------------------------------------------------------------------------------ 1192//-------------------------------------- Persistent I/O 1193// 1194 1195bool GBitmap::readBitmap( const String &bmType, Stream &ioStream ) 1196{ 1197 PROFILE_SCOPE(ResourceGBitmap_readBitmap); 1198 const GBitmap::Registration *regInfo = GBitmap::sFindRegInfo( bmType ); 1199 1200 if ( regInfo == NULL ) 1201 { 1202 Con::errorf( "[GBitmap::readBitmap] unable to find registration for extension [%s]", bmType.c_str() ); 1203 return NULL; 1204 } 1205 1206 return regInfo->readFunc( ioStream, this ); 1207} 1208 1209bool GBitmap::writeBitmap( const String &bmType, Stream &ioStream, U32 compressionLevel ) 1210{ 1211 const GBitmap::Registration *regInfo = GBitmap::sFindRegInfo( bmType ); 1212 1213 if ( regInfo == NULL ) 1214 { 1215 Con::errorf( "[GBitmap::writeBitmap] unable to find registration for extension [%s]", bmType.c_str() ); 1216 return NULL; 1217 } 1218 1219 return regInfo->writeFunc( this, ioStream, (compressionLevel == U32_MAX) ? regInfo->defaultCompression : compressionLevel ); 1220} 1221 1222template<> void *Resource<GBitmap>::create(const Torque::Path &path) 1223{ 1224 PROFILE_SCOPE( ResourceGBitmap_create ); 1225 1226#ifdef TORQUE_DEBUG_RES_MANAGER 1227 Con::printf( "Resource<GBitmap>::create - [%s]", path.getFullPath().c_str() ); 1228#endif 1229 1230 FileStream stream; 1231 1232 stream.open( path.getFullPath(), Torque::FS::File::Read ); 1233 1234 if ( stream.getStatus() != Stream::Ok ) 1235 { 1236 Con::errorf( "Resource<GBitmap>::create - failed to open '%s'", path.getFullPath().c_str() ); 1237 return NULL; 1238 } 1239 1240 GBitmap *bmp = new GBitmap; 1241 const String extension = path.getExtension(); 1242 if( !bmp->readBitmap( extension, stream ) ) 1243 { 1244 Con::errorf( "Resource<GBitmap>::create - error reading '%s'", path.getFullPath().c_str() ); 1245 delete bmp; 1246 bmp = NULL; 1247 } 1248 1249 return bmp; 1250} 1251 1252template<> ResourceBase::Signature Resource<GBitmap>::signature() 1253{ 1254 return MakeFourCC('b','i','t','m'); 1255} 1256 1257Resource<GBitmap> GBitmap::load(const Torque::Path &path) 1258{ 1259 Resource<GBitmap> ret = _load( path ); 1260 if ( ret != NULL ) 1261 return ret; 1262 1263 // Do a recursive search. 1264 return _search( path ); 1265} 1266 1267Resource<GBitmap> GBitmap::_load(const Torque::Path &path) 1268{ 1269 PROFILE_SCOPE( GBitmap_load ); 1270 1271 if ( Torque::FS::IsFile( path ) ) 1272 return ResourceManager::get().load( path ); 1273 1274 Path foundPath; 1275 if ( GBitmap::sFindFile( path, &foundPath ) ) 1276 { 1277 Resource<GBitmap> ret = ResourceManager::get().load( foundPath ); 1278 if ( ret != NULL ) 1279 return ret; 1280 } 1281 1282 return Resource< GBitmap>( NULL ); 1283} 1284 1285Resource<GBitmap> GBitmap::_search(const Torque::Path &path) 1286{ 1287 PROFILE_SCOPE( GBitmap_search ); 1288 1289 // If unable to load texture in current directory 1290 // look in the parent directory. But never look in the root. 1291 Path newPath( path ); 1292 while ( true ) 1293 { 1294 String filePath = newPath.getPath(); 1295 String::SizeType slash = filePath.find( '/', filePath.length(), String::Right ); 1296 1297 if ( slash == String::NPos ) 1298 break; 1299 1300 slash = filePath.find( '/', filePath.length(), String::Right ); 1301 if ( slash == String::NPos ) 1302 break; 1303 1304 String truncPath = filePath.substr( 0, slash ); 1305 newPath.setPath( truncPath ); 1306 1307 Resource<GBitmap> ret = _load( newPath ); 1308 if ( ret != NULL ) 1309 return ret; 1310 } 1311 1312 return Resource< GBitmap>( NULL ); 1313} 1314 1315U32 GBitmap::getSurfaceSize(const U32 mipLevel) const 1316{ 1317 // Bump by the mip level. 1318 U32 height = getMax(U32(1), mHeight >> mipLevel); 1319 U32 width = getMax(U32(1), mWidth >> mipLevel); 1320 1321 if (mInternalFormat >= GFXFormatBC1 && mInternalFormat <= GFXFormatBC3) 1322 { 1323 // From the directX docs: 1324 // max(1, width ÷ 4) x max(1, height ÷ 4) x 8(DXT1) or 16(DXT2-5) 1325 1326 U32 sizeMultiple = 0; 1327 1328 switch (mInternalFormat) 1329 { 1330 case GFXFormatBC1: 1331 sizeMultiple = 8; 1332 break; 1333 case GFXFormatBC2: 1334 case GFXFormatBC3: 1335 sizeMultiple = 16; 1336 break; 1337 default: 1338 AssertISV(false, "DDSFile::getSurfaceSize - invalid compressed texture format, we only support DXT1-5 right now."); 1339 break; 1340 } 1341 1342 return getMax(U32(1), width / 4) * getMax(U32(1), height / 4) * sizeMultiple; 1343 } 1344 else 1345 { 1346 return height * width* mBytesPerPixel; 1347 } 1348} 1349 1350DefineEngineFunction( getBitmapInfo, String, ( const char *filename ),, 1351 "Returns image info in the following format: width TAB height TAB bytesPerPixel TAB format. " 1352 "It will return an empty string if the file is not found.\n" 1353 "@ingroup Rendering\n" ) 1354{ 1355 Resource<GBitmap> image = GBitmap::load( filename ); 1356 if ( !image ) 1357 return String::EmptyString; 1358 1359 return String::ToString( "%d\t%d\t%d\t%d", image->getWidth(), 1360 image->getHeight(), 1361 image->getBytesPerPixel(), 1362 image->getFormat()); 1363} 1364