65.9K
CodeProject is changing. Read more.
Home

Rendering Transparent PNGs with C# and Windows Forms

starIconstarIconstarIconstarIconstarIcon

5.00/5 (5 votes)

Aug 7, 2016

CPOL

1 min read

viewsIcon

9321

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.

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.