sglasby's picture

Texture transparency from GDI Bitmap - different display on various systems

(This problem has also been put on Stack Overflow, but I've not seen any results there as of yet: )

I am writing a support class for sprite/texture atlas functionality, using C# with OpenTK. Most functionality is working fine thus far (simple 2D tiles on an orthographic view).

My problem relates to unexpected display results when calling the GDI+ Bitmap.MakeTransparent() method to set a color (Magenta / 0xFFFF00FF) for use as a color-key.

It would seem that I am using incorrect pixel format parameters for the bitmap.LockBits() and GL.TexImage2D() calls. My code was based on examples which indeed worked, but which had in common that the rectangle passed to LockBits() was for the entire image.

The calls which pertain to this process are:

<!-- language: C# -->
Bitmap bitmap = new Bitmap(filename);
GL.GenTextures(1, out texture_id);
GL.BindTexture(TextureTarget.Texture2D, texture_id);
// "rect" is initialized for one of:
//   - the dimensions of the entire image 
//     (0, 0, bitmap.width, bitmap.height)
//   - the dimensions for a sub-rectangle within the image (for one tile)
//     (tile_x * tile_width, tile_y * tile_height, tile_width, tile_height)
// I observe different behaviors for a sub-rectangle, 
// as compared to the entire image, when in combination with 
// the .MakeTransparent() call.
// This code is in a load_tile() method, and the plan was to make 
// multiple calls per image file, one per tile to extract as a GL texture.  
// Without transparency, that worked fine.
Rectangle  rect = new Rectangle(xx, yy, width, height);
BitmapData data = bitmap.LockBits(rect,
// In the absence of calling bitmap.MakeTransparent(),
// images loaded and displayed as expected with Format24bppRgb.
// With MakeTransparent() and Format32bppRgb, the results seem to be OS-dependent.
//     (At first I thought the "correct" combination to be found, 
//     but then found that the results were "right" only under Windows 7.)
        OpenTK.Graphics.OpenGL.TextureTarget.Texture2D,   // texture_target,
        0,                                                // level,
        OpenTK.Graphics.OpenGL.PixelInternalFormat.Rgba,  // internal_format
        data.Width, data.Height,                          // width, height, 
        0,                                                // border,
        OpenTK.Graphics.OpenGL.PixelFormat.Bgra,          // pixel_format
        OpenTK.Graphics.OpenGL.PixelType.UnsignedByte,    // pixel_type
        data.Scan0                                        // pixels
// Certainly the internal_format and pixel_format arguments are pertinent,
// but other combinations I have tried produced various undesired display results.
// After reading various (OpenGL, OpenTK, and GDI+) docs, still I am not enlightened..

I have tested a small demo using the code above on different boxen, and observe these results:

Windows 7 box: magenta pixels act as transparent (the desired result)
Windows XP box: magenta pixels rendered as black
Ubuntu Linux box: magenta pixels rendered as magenta

This surprises me, as I anticipated that (GDI+ and OpenGL and the OpenTK bindings) would act the same on different boxes.

To the extent that I have absorbed the GDI+ and OpenGL / OpenTK API documentation, I think my puzzlement relates to these two points:

What is a correct way of calling MakeTransparent() + LockBits() + GL.TexImage2D(), so as to result in the specified color being rendered as transparent?

Why do I see strange display results (as if the "stride" was mis-calculated) for certain pixel format parameter combinations, when LockBits() is called for a sub-rectangle rather than the entire image?

(I can post complete code, or images showing what I am seeing on the not-working system combinations, if wanted.)


Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
Inertia's picture

You should add at least 1 check for GL errors after teximage and parameter setup, the unreliable behaviour you describe is usually related to previous errors.

"MSDN" wrote:

When you call MakeTransparent, the bitmap will be converted to the Format32bppArgb format, as this format supports an alpha channel.

-> follow this link. This is wrong in 2 places in your code.

P.S: Best practice is to use the alpha channel of your texture to set transparency, chroma keying with magenta is very oldskool ;)

sglasby's picture

OK, I looked at the MSDN page you linked, and indeed it specifies Format32bppArgb.
Also, I added some GL error checking, via calling GL.GetError() after certain GL calls.

However, the result I observe on my XP box is a very odd display, of a sort I have seen earlier in trying various parameter combinations.

Recently, in the course of trying (aimlessly at this point) other parameters, I tried Format32bppPArgb, and to my surprise the transparency suddenly worked (tested on my XP box, and an Ubuntu Linux box, same result on both).

As the MSDN page for .MakeTransparent() says the Bitmap is converted to
it would make some sense that
would be the needed 'internal format' arg 3 for GL.TexImage2D(),
however what I observe seems to contradict the documentation.

I have whittled down my code into a small project on Github:
Also, I will try to attach a couple of tiny "working" and "not working" screenshots.

sglasby's picture

I added the two screen shots into the github repository.
They are the files:

sglasby's picture

You mentioned that transparency via color-key is very old-school.
This is true, but I should point out that my goal is very old-school, as it is an engine for a 2D tile game of a classic style.

As such, it is a functional requirement that image files intended for color-key use should be supported.
(Naturally, I desire also to support image files with an alpha channel.)

My aim is to make an engine which takes some pains to make content editing/creation a low-fuss activity, and the majority of the tile/sprite art resources I have found are set up for color-key use, rather than having an alpha channel.

Thanks for your attempt at aid thus far. I have a working combination (Format32bppPArgb), but it is not clear _why_ this combination works, or that it is "correct" on all systems where the program will run.

Inertia's picture

Format32bppPArgb uses premultiplied alpha, more here.