简体   繁体   中英

Sprite/Texture Atlas: GDI+ Bitmap.MakeTransparent for color-key with OpenTK

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);
bitmap.MakeTransparent(Color.Magenta);

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,
                                  ImageLockMode.ReadOnly, 
                                  System.Drawing.Imaging.PixelFormat.Format32bppRgb);
// 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.)

GL.TexImage2D(
        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..

bitmap.UnlockBits(data);

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?

Update: I have whittled down my code into a small project on Github: https://github.com/sglasby/OpenGL_Transparent_Sprite

Also, I stumbled upon a parameter combination that works (arg 3 of LockBits() is Format32bppPArgb), though it is not clear why it works, given that the documentation implies another pixelformat is wanted: http://msdn.microsoft.com/en-us/library/8517ckds.aspx (which states that the bitmap will be in Format32bppArgb after calling MakeTransparent).

While this is a separate issue to your question, in most cases you should actually use premultiplied-alpha ( Format32bppPArgb ). If this format is working correctly, then understanding why Format32bppArgb does not work is mostly an academic exercise.

I ran your example project on Win7 with an Intel 2000HD and got the following results:

  • Format32bppPArgb works correctly
  • Format32bppRgb works correctly
  • Format32bppArgb is scrambled

On further investigation, this does not appear to be linked to OpenGL, but rather to the way Bitmap.LockBits works.

Check the values of data.Stride on the debugger for each approach:

  • Format32bppPArgb has a stride of 128 (4x the bitmap width, correct)
  • Format32bppRgb has a stride of 128 (4x the bitmap width, correct)
  • Format32bppArgb has a stride of 512 (16x the bitmap width, ?)

MSDN does not turn up something useful here. At this point, I cannot tell why this is happening. I'll update this answer if I manage to uncover anything.

Edit: lo and behold, if you force the correct stride when unpacking the data, the output looks correct:

GL.PixelStore(PixelStoreParameter.UnpackRowLength, data.Width * 4); // 4x for 32bpp

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM