CXO2's picture

Saving Texture to PNG File (With transparency)

Hi, there ;)
I am going to Save a 2D Texture into a png file, but I dont know how
I could save the texture into bmp format, but the transparency is lost.
Here my code

        /// <summary>
        /// Save Texture to Image
        /// </summary>
        /// <returns>Bitmap contain original image texture</returns>
        public Bitmap CopyImage()
        {
            if (texture.IsError)
                return null;
 
            try
            {
                GLCheck.Call(() => GL.BindTexture(TextureTarget.Texture2D, texture.ID));
 
                Bitmap bmp = new Bitmap(texture.Width, texture.Height);
                BitmapData data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
                GLCheck.Call(() => GL.GetTexImage(TextureTarget.Texture2D, 0, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, data.Scan0));
                bmp.UnlockBits(data);
 
                // Re-flip the texture if its flipped
                if (texture.IsFlipped)
                    bmp.RotateFlip(RotateFlipType.RotateNoneFlipY);
 
                return bmp; // <-- I am pretty sure the transparency is lost because its bmp image...
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message, "Error");
                return null;
            }
        }

Can someone tell me how to save the texture into a png format (with transparency?)
Thanks! :D

Oh! I forgot something, GLCheck.Call() its just a method that I created to check OpenGL State each time I called OpenGL Functions
here the class (feel free to use if you want to use it ;) )

    public static class GLCheck
    {
 
        // NOTE: YOU WILL NEED THIS IF YOU ARE RUNNING ON .NET FRAMEWORK 2.0
        // Comment this if you are using Framework that newer than 2.0
        public delegate void Action();
 
        /// <summary>
        /// Call OpenGL function and check for the error
        /// </summary>
        /// <param name="callback">OpenGL Function to be called</param>
        public static void Call(Action callback)
        {
            callback();
            CheckError();
        }
 
        /// <summary>
        /// Check for the OpenGL Error
        /// </summary>
        public static void CheckError()
        {
            // Note: you can add StackTrace to include file and line numbers of your error just in case for debugging purpose xD
 
            ErrorCode errorCode = GL.GetError();
 
            if (errorCode == ErrorCode.NoError)
                return;
 
            string error = "unknown error";
            string description = "no description";
 
            // Decode the error code
            switch (errorCode)
            {
                case ErrorCode.InvalidEnum:
                    {
                        error = "GL_INVALID_ENUM";
                        description = "an unacceptable value has been specified for an enumerated argument";
                        break;
                    }
 
                case ErrorCode.InvalidValue:
                    {
                        error = "GL_INVALID_VALUE";
                        description = "a numeric argument is out of range";
                        break;
                    }
 
                case ErrorCode.InvalidOperation:
                    {
                        error = "GL_INVALID_OPERATION";
                        description = "the specified operation is not allowed in the current state";
                        break;
                    }
 
                case ErrorCode.StackOverflow:
                    {
                        error = "GL_STACK_OVERFLOW";
                        description = "this command would cause a stack overflow";
                        break;
                    }
 
                case ErrorCode.StackUnderflow:
                    {
                        error = "GL_STACK_UNDERFLOW";
                        description = "this command would cause a stack underflow";
                        break;
                    }
 
                case ErrorCode.OutOfMemory:
                    {
                        error = "GL_OUT_OF_MEMORY";
                        description = "there is not enough memory left to execute the command";
                        break;
                    }
 
                case ErrorCode.InvalidFramebufferOperationExt:
                    {
                        error = "GL_INVALID_FRAMEBUFFER_OPERATION_EXT";
                        description = "the object bound to FRAMEBUFFER_BINDING_EXT is not \"framebuffer complete\"";
                        break;
                    }
                default:
                    {
                        error = errorCode.ToString();
                        description = "";
                        break;
                    }
            }
 
            // Log the error
            Debug.WriteLine("An internal OpenGL call failed: " + error + " (" + description + ")");
        }
    }
 
// To Call it:
// GLCheck.Call(() => [your opengl call here]);
// you cant use this for method that return data (non void method)

Comments

Comment viewing options

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

The GL.GetTexImage call looks correct. The question is whether System.Drawing.Bitmap can encode png images with an alpha channel.

I do not have a reference handy, but I remember having some trouble getting this to work. I think .Net and Mono behave differently here (one is premultiplying alpha, one is not), but I don't remember which was which.

You might get better results by using a different encoder or saving to a raw file and encoding using an external application (you can invoke the encoder using Process.Start()). I am pretty sure a couple of managed PNG implementations exist now - that might also be worth a look.

CXO2's picture

I see
I dont mind if this code only work for .NET Framework only, I only use this on windows (but still, the game will run on multiple platform with Mono)

I cant find any encoder bmp -> png with transparency, can you recommend me a / few tools to doing this?
About Managed Implementation, I only found bmp.MakeTransparent(Color.White); which convert all white pixel into transparent.

the main problem it will convert all white pixel (which is NOT transparent) into transparent (note: I have lot textures, so I am not really sure that I have one the image like that)

this will be temporary solution for now.
but.. I believe there are better solution, got any other idea? :)

the Fiddler's picture

Edit: try using this Bitmap constructor with an alpha-capable PixelFormat. If this doesn't help, see below.

There are two questions here:

  1. Is GL.GetTexImage actually returning an alpha channel?
    var data_array = new byte[4 * width * height];
    GL.GetTexImage(TextureTarget.Texture2D, 0, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, data_array));

    Inspect data_array to ensure it contains alpha values.

  2. Does System.Drawing.Bitmap.Save() support an alpha channel? Some people say it does, some say it doesn't. I have no idea.

If GL.GetTexImage is indeed returning alpha values, then the issue is in System.Drawing.Bitmap.Save. In this case, you can use something like PngCs to save the image.

CXO2's picture

I am sorry that I just replied now
My old laptop is broken, so I decide to buy new one.
lucky that I back up whole my project files xD

I will try this tomorrow, thanks! :)