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.
<img src="1117099/transparentPNG.jpg" style="height: 125px; width: 305px;" />
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
{
IntPtr hBitmap = bmp.GetHbitmap();
DIBSECTION dibsection = new DIBSECTION();
GetObjectDIBSection(hBitmap, Marshal.SizeOf(dibsection), ref dibsection);
using (Bitmap destBmp = new Bitmap(dibsection.dsBm.bmWidth,
dibsection.dsBm.bmHeight, PixelFormat.Format32bppArgb))
{
BitmapData bitmapData = destBmp.LockBits(new Rectangle(0, 0,
dibsection.dsBm.bmWidth, dibsection.dsBm.bmHeight),
ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
int bytesPerPixel = 4; int heightInPixels = bitmapData.Height; int widthInBytes = bitmapData.Width * bytesPerPixel; byte* ptrFirstPixel = (byte*)bitmapData.Scan0;
RGBQUAD* pBits = (RGBQUAD*)(void*)dibsection.dsBm.bmBits;
int index; byte* currentLine;
for (int y= 0; y < heightInPixels; y++)
{
currentLine = ptrFirstPixel + (y * bitmapData.Stride);
for (int x = 0; x < widthInBytes; x += bytesPerPixel)
{
index = y * dibsection.dsBmih.biWidth + (x / bytesPerPixel);
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;
}
}
}
destBmp.UnlockBits(bitmapData);
destBmp.RotateFlip(RotateFlipType.RotateNoneFlipY);
DeleteObject(hBitmap);
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.