Click here to Skip to main content
15,879,535 members
Articles / Multimedia / GDI+
Tip/Trick

Rendering Transparent PNGs with C# and Windows Forms

Rate me:
Please Sign up or sign in to vote.
5.00/5 (5 votes)
7 Aug 2016CPOL1 min read 8.7K   5  
Preserving alpha channel drawing transparent PNGs

Introduction

First, I am not really good at this, but I really want to share this fix, so I make it quick.

I am currently working on my own treeview control where I draw all the highlighted nodes by myself and all other nodes are drawn by Windows.

Now I had the problem that the node images are drawn by Windows looking much smoother than the images that are drawn by myself with alphablend function. Unfortunately, using GDI+ drawimage function ends up in the same result. After hours of Google searching, I finally found an solution in the following article:

And here is a pic that shows that issue:

The drawn image on the middle has slightly darker edges on the transitions where transparency begins while the right example is drawn correctly.

Using the Code

Create a new class and copy and paste the code below. Call GetFixedHBitmap method before drawing with alphablend.

Alternatively, the GetFixedHBitmap can be modified to retun a new bitmap object which then can be drawn with GDI+ drawimage method.

Please note that this class is for demonstrating and does not follow the Microsoft code analysis rules.

C#
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

public static class PreservingAlphaChannel
{
    public static IntPtr GetFixedHBitmap(this Bitmap bmp)
    {
        unsafe
        {
            // Get the gdi bitmap object
            IntPtr hBitmap = bmp.GetHbitmap();

            // Create new DIBSECTION struct
            DIBSECTION dibsection = new DIBSECTION();
            // Get the info about the gdi bitmap
            GetObjectDIBSection(hBitmap, Marshal.SizeOf(dibsection), ref dibsection);

            // Create new gdi+ bitmap object with alpha channel
            using (Bitmap destBmp = new Bitmap(dibsection.dsBm.bmWidth, 
            dibsection.dsBm.bmHeight, PixelFormat.Format32bppArgb))
            {
                // Lock bitmap into system memory
                BitmapData bitmapData = destBmp.LockBits(new Rectangle(0, 0, 
                dibsection.dsBm.bmWidth, dibsection.dsBm.bmHeight), 
                ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);

                int bytesPerPixel = 4;  // 4 bytes per pixel 
                int heightInPixels = bitmapData.Height; // Get height in pixels
                int widthInBytes = bitmapData.Width * bytesPerPixel; // Get width in bytes
                byte* ptrFirstPixel = (byte*)bitmapData.Scan0;  // Get address of the first pixel

                // Pointer to the relative intensities red, green, blue
                RGBQUAD* pBits = (RGBQUAD*)(void*)dibsection.dsBm.bmBits;

                int index; byte* currentLine;

                // Vertical loop
                for (int y= 0; y < heightInPixels; y++)
                {
                    // Pointer to the current vertical line
                    currentLine = ptrFirstPixel + (y * bitmapData.Stride);

                    // Horizontal loop, 
                    for (int x = 0; x < widthInBytes; x += bytesPerPixel)
                    {
                        // Index for RGBQUAD info for the current pixel
                        index = y * dibsection.dsBmih.biWidth + (x / bytesPerPixel);

                        // If pixel is 100% transparent, skip pixel
                        if (pBits[index].rgbReserved != 0)
                        {
                            currentLine[x] = pBits[index].rgbBlue;
                            currentLine[x + 1] = pBits[index].rgbGreen;
                            currentLine[x + 2] = pBits[index].rgbRed;
                            currentLine[x + 3] = pBits[index].rgbReserved;
                        }
                    }
                }

                // Unlock bitmap from system memory
                destBmp.UnlockBits(bitmapData);
                // Unfortunately I'm currently dont know why the 
                // new bitmap object is rotated in its y axis (any ideas?)
                destBmp.RotateFlip(RotateFlipType.RotateNoneFlipY);
                // Delete the gdi bitmap object
                DeleteObject(hBitmap);
                // return new gdi bitmap with preserved alpha channel
                return destBmp.GetHbitmap(Color.Black);
            }
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct RGBQUAD
    {
        public byte rgbBlue;
        public byte rgbGreen;
        public byte rgbRed;
        public byte rgbReserved;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct BITMAP
    {
        public Int32 bmType;
        public Int32 bmWidth;
        public Int32 bmHeight;
        public Int32 bmWidthBytes;
        public Int16 bmPlanes;
        public Int16 bmBitsPixel;
        public IntPtr bmBits;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct BITMAPINFOHEADER
    {
        public int biSize;
        public int biWidth;
        public int biHeight;
        public Int16 biPlanes;
        public Int16 biBitCount;
        public int biCompression;
        public int biSizeImage;
        public int biXPelsPerMeter;
        public int biYPelsPerMeter;
        public int biClrUsed;
        public int bitClrImportant;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct DIBSECTION
    {
        public BITMAP dsBm;
        public BITMAPINFOHEADER dsBmih;
        public int dsBitField1;
        public int dsBitField2;
        public int dsBitField3;
        public IntPtr dshSection;
        public int dsOffset;
    }

    [DllImport("gdi32.dll", EntryPoint = "GetObject")]
    private static extern int GetObjectDIBSection
    (IntPtr hObject, int nCount, ref DIBSECTION lpObject);

    [DllImport("gdi32.dll")] [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool DeleteObject(IntPtr hObject);

I hope this helps other people who are annoyed by the same issue.

Many thanks to Garry Trinder and his article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



Comments and Discussions

 
-- There are no messages in this forum --