Sign in to follow this  
codemastermm

SDL & OpenGL Texture Transparency

Recommended Posts

I am using SDL and OpenGL (of course by the topic) and am attempting to make a function that loads a file and returns it as an OpenGL texture with transparency. Currently, I already have a function that returns an OpenGL texture, but I need it to also make certain parts of it transparent (for example, the color magenta - FF00FF (255,0,255)). I would like to modify my current function to make the returned textures transparent. Below is my current code for texture loading:
static int  powerOfTwo(int input)
{
    int value = 1;
    while(value < input)
        value <<= 1;
    return value;
}

GLuint  LoadGLTexture(const char* filename)
{
    SDL_Surface *pImage = NULL, *image = NULL;
    GLuint      texture = 0;
    int         w = 0, h = 0, ybegin, yend;
    SDL_Rect    area;
    Uint32      saved_flags;
    Uint16      pitch;
    Uint8       saved_alpha, *pixels, *line;
    
    pImage = IMG_Load(filename);
    if(!pImage)
    {
        printf("Unable to load texture '%s'\n", filename);
        return 0;
    }
    
    w = powerOfTwo(pImage->w);
    h = powerOfTwo(pImage->h);
    /*
    texcoord[0] = 0.0f;
    texcoord[1] = 0.0f;
    texcoord[2] = (GLfloat)pImage->w / w;
    texcoord[3] = (GLfloat)pImage->h / h;
    */
  
    if(!(image = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 32,
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
    0x000000FF,
    0x0000FF00,
    0x00FF0000,
    0xFF000000
#else
    0xFF000000,
    0x00FF0000,
    0x0000FF00,
    0x000000FF
#endif
    )))
    {
        printf("SDL_CreateRGBSurface: Unable to create RGB Surface for texture '%s'\n", filename);
        return 0;
    }
    
    saved_flags = pImage->flags&(SDL_SRCALPHA|SDL_RLEACCELOK);
    saved_alpha = pImage->format->alpha;
    if((saved_flags & SDL_SRCALPHA) == SDL_SRCALPHA)
        SDL_SetAlpha(pImage, 0, 0);
        
    area.x = area.y = 0;
    area.w = pImage->w;
    area.h = pImage->h;
    SDL_BlitSurface(pImage, &area, image, &area);
    
    if((saved_flags & SDL_SRCALPHA) == SDL_SRCALPHA)
        SDL_SetAlpha(pImage, saved_flags, saved_alpha);

	line = new Uint8[image->pitch];
	pixels = static_cast<Uint8*>(image->pixels);
	pitch = image->pitch;
	ybegin = 0;
	yend = image->h - 1;

	if(SDL_MUSTLOCK(image))
        SDL_LockSurface(image);
	while(ybegin < yend)
    {
		memcpy(line, pixels + pitch * ybegin, pitch);
		memcpy(pixels + pitch * ybegin, pixels + pitch * yend, pitch);
		memcpy(pixels + pitch * yend, line, pitch);
		ybegin++;
		yend--;
	}
	if(SDL_MUSTLOCK(image))
        SDL_UnlockSurface(image);
    
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, image->pixels);
    
    if(pImage)
        SDL_FreeSurface(pImage); 
    if(image)
        SDL_FreeSurface(image);
    if(line)
        delete[] line;
    
    return texture;
}

Thanks for any help!

Share this post


Link to post
Share on other sites
You get a pointer to the pixels (before they are given to OpenGL), and you examine each pixel. If (pixel&0xffffff) is 0xff00ff, then set it to 0x00000000, else set it to ((pixel&0xffffff)|0xff000000). Then upload the data as an actual texture.

When you draw using this transparency, use GL_ONE,GL_ONE_MINUS_SRC_ALPHA blend function, because the color pixels will blend towards black as they blend towards translucency. If you use GL_SRC_ALPHA, then you'll get black fringes.

Share this post


Link to post
Share on other sites
What would be the best way to cycle through each pixel, then?
Also, where in the function? (I am presuming right before the glTexImage2D)

Wouldn't cycling through each pixel, though, be quite slow? Is there any better alternative or something? If not, that's okay, but I am just concerned about speed a little bit.

Share this post


Link to post
Share on other sites
I think I did this a few years ago. It should be possible to setup a color key. Then (I think) you can use SDL_DisplayFormatAlpha() which will take a surface with a color key set and give you a new surface with transparency using an alpha channel. Then you can just use that to build your OpenGL texture.

There's a few details I can't really remember, but that's the basics.

Share this post


Link to post
Share on other sites
I found some really old code of mine (IIRC it worked, but I can't be sure) that does what you want:

SDL_Surface * surface = IMG_Load("something.tga");

// the color key is a disgusting shade of bright purple
SDL_SetColorKey(surface, SDL_SRCCOLORKEY, SDL_MapRGB(surface->format, 255,0,255));

SDL_Surface * alphasurface = SDL_DisplayFormatAlpha(surface);

// standard OpenGL stuff here
unsigned int gltex;
glGenTextures(1, &gltex);
glBindTexture(GL_TEXTURE_2D);
glTexImage2D(GL_TEXTURE_2D, 0, 4, alphasurface->w, alphasurface->h, 0, GL_BGRA_EXT,
GL_UNSIGNED_BYTE, alphasurface->pixels);
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );

SDL_FreeSurface(alphasurface);
SDL_FreeSurface(surface);


You'll also need to make sure the proper OpenGL alpha testing stuff is turned on.

Edit: Oh, by the way, if you are using something like .tga or .png or anything else that supports alpha for your textures, you can just include the alpha information in those files and not worry about setting color keys and junk like that.

Share this post


Link to post
Share on other sites
As hplus mentioned the best way is to cycle through the SDL_Surface using something like the SDL_GetPixel and SDL_PutPixel functions you can find in the SDL documentation and setting the pixels yourself. This will allow you to set multiple color keys and even change certain colors to different alpha values on load. Since you should only be doing this once when you load the texture the speed shouldn't be an issue unless you plan on loading lots of textures on the fly.

Share this post


Link to post
Share on other sites
BradDaBug's code didn't seem to work at all for me.
I have blending enabled as below:

glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

and I literally copied and pasted his code into my current function (with mild modifying, such as replacing the "something.tga" with filename, correcting his Binding and returning the gltex variable).

evillive2's comment on SDL_GetPixel and SDL_PutPixel sounds like a good idea, but whenever I go to use the two functions, it says that they both are undeclared (I have both SDL.h and SDL_image.h included in the file and I have even checked SDL's documentation for the functions; they don't seem to be actual functions in SDL or something).

Thanks again for all your help; I'll keep trying to crack this darn thing open.

I think, looking at my function, that part of my problem is that it loads the texture into one surface, Blits the surface onto another one after resizing it into a proper texture size (8, 16, 32, 64, 128, ...) and then converts that second surface into the OpenGL texture. I think that somewhere between the conversion between surface to surface or during the conversion from surface to OpenGL texture we have to set the color key and change the surface or the such into a surface with alpha transparency. Just a big brainstorm but so far I don't seem to have any luck with it.

Share this post


Link to post
Share on other sites
Quote:
Original post by codemastermm
evillive2's comment on SDL_GetPixel and SDL_PutPixel sounds like a good idea, but whenever I go to use the two functions, it says that they both are undeclared (I have both SDL.h and SDL_image.h included in the file and I have even checked SDL's documentation for the functions; they don't seem to be actual functions in SDL or something).

They aren't actually part of SDL. See here. You've got to add the functions to your project somewhere.

Edit: Are you blitting the image, then setting the color key and getting the alpha surface, or are you blitting after you do that? IIRC unless you specifically tell SDL to include alpha information when blitting (via SDL_SetAlpha()) it'll ignore it, and the new blitted surface won't have any alpha info.

Share this post


Link to post
Share on other sites
I might switch my textures into PNG or TGA since colorkeying is a bit of a hassale.

How about would I load a TGA or PNG in, given that they already have alpha values and won't need much more of a conversion to enable alpha transparency with those.

Share this post


Link to post
Share on other sites
Ok... I've been trying like crazy to get this working - nothing has worked!

I attempted using colorkeying - I used it before AND after copying the surface to the new surface before returning the GL texture.

I have also attempting using a TGA texture - this, instead of displaying with an alpha value, displays nothing at all! - it displays a pure white box...

can anyone, perhaps, take a look at the actual function I posted above and help me find where, in my code that I should place colorkeying functions or such of that sort?

Thanks for any more help..

Share this post


Link to post
Share on other sites
This is the code from my engine, which loads textures in a format supported by the SDL_image library.


SDL_Surface* lpTexture = IMG_Load(szFilename);
if (!lpTexture)
{
// some error
return;
}

m_iTextureWidth = lpTexture->w;
m_iTextureHeight = lpTexture->h;


glGenTextures(1, &m_iglTexture);
rval = glGetError();
if (rval != GL_NO_ERROR)
{
if (lpTexture)
{
SDL_FreeSurface(lpTexture);
lpTexture = NULL;
}

// some error
return;
}

glBindTexture(GL_TEXTURE_2D, m_iglTexture);
rval = glGetError();
if (rval != GL_NO_ERROR)
{
if (lpTexture)
{
SDL_FreeSurface(lpTexture);
lpTexture = NULL;
}

// some error
return;
}



int byte = 0;
int w = lpTexture->w, h = lpTexture->h;
int iTotalBytes = w * h * 4;
unsigned char* lpNewTexture = new unsigned char[iTotalBytes];
if (!lpNewTexture)
{
if (lpTexture)
{
SDL_FreeSurface(lpTexture);
lpTexture = NULL;
}

// some error
return;
}

for (int y = 0; y < h; y++)
{
for (int x = 0; x < w; x++)
{
Uint8 r,g,b,a;
Uint32 color = GetPixel(lpTexture, x, y);

if(!bUseColorKey)
{
SDL_GetRGB(color, lpTexture->format, &r, &g, &b);
a = 0xff;
}

else
{
SDL_GetRGBA(color, lpTexture->format, &r, &g, &b, &a);

if ((r == iColorKeyRed) && (g == iColorKeyGreen) && (b == iColorKeyBlue))
{
a = 0x00;
}
else
{
a = 0xff;
}
}

lpNewTexture[byte++] = r;
lpNewTexture[byte++] = g;
lpNewTexture[byte++] = b;
lpNewTexture[byte++] = a;
}
}







...and the GetPixel() function used...


Uint32 GetPixel(SDL_Surface *Surface, Sint32 X, Sint32 Y)
{

Uint8 *bits;
Uint32 Bpp;

if (X<0) puts("x too small in GetPixel!");
if (X>=Surface->w) puts("x too big in GetPixel!");

Bpp = Surface->format->BytesPerPixel;

bits = ((Uint8 *)Surface->pixels)+Y*Surface->pitch+X*Bpp;

// Get the pixel
switch(Bpp) {
case 1:
return *((Uint8 *)Surface->pixels + Y * Surface->pitch + X);
break;
case 2:
return *((Uint16 *)Surface->pixels + Y * Surface->pitch/2 + X);
break;
case 3: { // Format/endian independent
Uint8 r, g, b;
r = *((bits)+Surface->format->Rshift/8);
g = *((bits)+Surface->format->Gshift/8);
b = *((bits)+Surface->format->Bshift/8);
return SDL_MapRGB(Surface->format, r, g, b);
}
break;
case 4:
return *((Uint32 *)Surface->pixels + Y * Surface->pitch/4 + X);
break;
}

return -1;
}



Hopefully the code is straightforward.

Best regards,

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this