Game Development Community

PNGs and GIFs as alpha channels

by Argiris Bendilas · in Torque Game Builder · 07/12/2007 (3:16 am) · 7 replies

Hi.

Inside gTexManager.cc there's the following function:

-----------------------------------------------------------------------------------------------------------------------------------------
GBitmap *TextureManager::loadBitmapInstance(const char *textureName, bool recurse /* = true */)
{
char fileNameBuffer[512];
#ifdef TORQUE_TOOLS
Platform::makeFullPathName( textureName, fileNameBuffer, 512 );
#else
dStrcpy(fileNameBuffer, textureName);
#endif
GBitmap *bmp = NULL;

// Loop through the supported extensions to find the file.
U32 len = dStrlen(fileNameBuffer);
for (U32 i = 0; i < EXT_ARRAY_SIZE && bmp == NULL; i++)
{

if (sgForcePalettedTexture == true && dglDoesSupportPalettedTexture())
dStrcpy(fileNameBuffer + len, extArray_8[i]);
else
dStrcpy(fileNameBuffer + len, extArray[i]);

bmp = (GBitmap*)ResourceManager->loadInstance(fileNameBuffer);

// CAF: if a jpg, and RGB, look for file.alpha.jpg as alpha channel
if ( (!sgForcePalettedTexture || !dglDoesSupportPalettedTexture()) && !dStricmp(extArray[i],".jpg") && bmp && bmp->getFormat()==GBitmap::RGB)
{
dStrcpy(fileNameBuffer + len, ".alpha.jpg");//Modified
GBitmap * bmpAlpha = (GBitmap*)ResourceManager->loadInstance(fileNameBuffer);
S32 w = bmp->getWidth();
S32 h = bmp->getHeight();
if (bmpAlpha && bmpAlpha->getWidth() == w && bmpAlpha->getHeight() == h && bmpAlpha->bytesPerPixel==1)
{
GBitmap * bmp2 = new GBitmap(w,h,false,GBitmap::RGBA);
U8 * rgbBits = bmp->getWritableBits();
U8 * alphaBits = bmpAlpha->getWritableBits();
U8 * bmpBits = bmp2->getWritableBits();
for (S32 wi=0; wi {
for (S32 hi=0; hi {
bmpBits[wi*4 + hi*4*w + 0] = rgbBits[wi*3 + hi*3*w + 0];
bmpBits[wi*4 + hi*4*w + 1] = rgbBits[wi*3 + hi*3*w + 1];
bmpBits[wi*4 + hi*4*w + 2] = rgbBits[wi*3 + hi*3*w + 2];
bmpBits[wi*4 + hi*4*w + 3] = alphaBits[wi + hi*w];
}
}
delete bmpAlpha;
delete bmp;
bmp = bmp2;
}
}
}

// If unable to load texture in current directory
// look in the parent directory. But never look in the root.
fileNameBuffer[len] = 0;
if (!bmp && recurse)
{
char *name = dStrrchr(fileNameBuffer, '/');
if (name)
{
*name++ = 0;
char *parent = dStrrchr(fileNameBuffer, '/');
if (parent)
{
parent[1] = 0;
dStrcat(fileNameBuffer, name);
return loadBitmapInstance(fileNameBuffer);
}
}
}
return bmp;
}
-----------------------------------------------------------------------------------------------------------------------------------------



Somewhere in there there's this:

-----------------------------------------------------------------------------------------------------------------------------------------
if ( (!sgForcePalettedTexture || !dglDoesSupportPalettedTexture()) && !dStricmp(extArray[i],".jpg") && bmp && bmp->getFormat()==GBitmap::RGB)
{
dStrcpy(fileNameBuffer + len, ".alpha.jpg");

-----------------------------------------------------------------------------------------------------------------------------------------


This basically says that "non-palletized 24-bit images can get their alpha-layer automatically loaded from a separate '.jpg' file if it is alongside the specified one and it contains a suffix of '.alpha.jpg'"

That's all fine when you want to work with JPGs. But when you can save lots of KBs (and ultimaterly MBs) by using GIF or PNG files (because it's the recommended format to use), then you have a problem!

#1
07/12/2007 (3:16 am)
What code modifications would it take in order for this method to use PNGs or GIFs as alpha channels while the source file being any of the TGB supported formats?

For those quick to recommend changing the extension from "jpg" to the desired one, let me just say it won't work.

Why does it HAVE to be a JPG in the first place? That is the question!

Thank you in advance!
#2
07/12/2007 (7:57 am)
PNGs can be 32 bit color, which means that they already have 8 bits in there for an alpha channel. If you're using PNGs, it's just wasteful to include a separate file for the alpha map -- just use the alpha channel inside the original PNG.
#3
07/12/2007 (8:41 am)
Clint, we managed to do it after all and was worth it. Big time!

I'm aware of PNGs that include an alpha channel but they are HUGE!

We did a test with a transparent PNG which was 140K. When we flattened it into an 8-bit PNG and created an 8-bit grayscale PNG for its mask, the total size of the combined images was 60K. This is a 57% file size reduction and you know you can't save enough hard disk space, especially in downloadable games.

Tomorrow we'll probably post the way to do this, in case someone else finds it interesting.
#4
07/12/2007 (8:51 am)
Argiris -- that's really bizarre. I have somewhat of a mental grasp on why that might be (based on how PNGs store its information), but it's still a bit odd.

You might be aware of this, but memory-wise, at least in older versions of TGB (not sure if they fixed this in 1.5), having a separate alpha graphic means that you use twice the memory, because TGB loads both textures into RAM separately, rather than combining them initially. On our project, we were having more issues with running out of RAM, and we were able to cut our runtime memory usage in half by switching from .jpg/.alpha.jpg pairs to transparent PNGs. I can understand your position though -- downloadable games have different priorities.

Thanks for posting this info!

--clint
#5
07/12/2007 (9:11 am)
Clint, that reduction was basically because the images we used had flat colors and you know that JPGs are optimized for variant pixel colors and formations.

I didn't know that about the increase in memory when you use a separate alpha image. Can anyone from Garage Games confirm this?

What engine changes would it require to have rendering to texture in TGB?

I think it's a very crucial asset missing from TGB and I really have no idea why the development team spent their time adding the Behaviors feature to the engine when they could have worked on more bug fixes and features that can really make a difference!
#6
07/12/2007 (9:40 am)
The engine changes needed are different ones:

1. Implementation of some kind of mechanism to do it at all (PBuffer for OpenGL, RenderTargets or similar for DX)
2. Classes to set the render target dynamically and reset it to backbuffer for rendering without breaking anything actively.

As you see especially the first point will be a problem as the mechanisms work a little different and you would have to implement it in the current callwrapping system ... unless you decide to kick DX out totally (which is what I would do in the end as the DX implementation as present in TGE / TGB is more of a "don't use it fallback" joke than a serious alternative renderer due to its massive performance hit)

And the memory increase is normal. The engine does not go and check for the bit count and change the texture creation flag to take such mixes into account.
But you could do that yourself and load the alpha mask as A8 texture instead of a A8R8G8B8 and similar and extend TGB with that possibility.
Or even do a "on load combination step" basing on the datablocks which shouldn't be too hard actually.


Behaviors actually make a large difference in creating a game.
As you progress above "eye candy prototype" I think you will start really loving it and the fact that you can test your stuff straight within the editor. But the project needs to build around the idea, integrating it into a half finished game might become a nightmare and I wouldn't try to do it ...

Especially the current incarnation of the behavior / component system isn't the final one from what mentioned. The idea is (at least mentioned in a posting from an employee so it might change) to make collision and physic behaviors as well, similar as TXB.
Which is something, many have been waiting and hoping for for a long time now.


PS: Why is this in general discussion and not C++ where it should be? :)
#7
07/12/2007 (9:48 am)
Marc, could you give me a pointer, as to how to accomplish loading the alpha mask as a 1-channel texture?

What is the "on load combination step" you're referring to?

Thanks!