Search code examples
c#.nettransparencygdi+mouse-cursor

How to Render a Transparent Cursor to Bitmap preserving alpha channel?


I use the code below to render a transparent icon:

    private void button1_Click(object sender, EventArgs e)
    {
        // using LoadCursorFromFile from user32.dll
        var cursor = NativeMethods.LoadCustomCursor(@"d:\Temp\Cursors\Cursors\aero_busy.ani");

        // cursor -> bitmap
        Bitmap bitmap = new Bitmap(48, 48, PixelFormat.Format32bppArgb);
        Graphics gBitmap = Graphics.FromImage(bitmap);
        cursor.DrawStretched(gBitmap, new Rectangle(0, 0, 32, 32));

        // 1. Draw bitmap on a form canvas
        Graphics gForm = Graphics.FromHwnd(this.Handle);
        gForm.DrawImage(bitmap, 50, 50);

        // 2. Draw cursor directly to a form canvas
        cursor.Draw(gForm, new Rectangle(100, 50, 32, 32));

        cursor.Dispose();
    }

Unfortunately I am unable to render a transparent Cursor to Bitmap! It works when I draw Cursor directly to the form canvas, but there is a problem when I draw Cursor to bitmap. Any advice is highly appreciated.

alt text


Solution

  • The solution you have right now doesn't stay completely with managed code. Your own comment says that you are P/Invoking LoadCursorFromFile from user32.dll. And regardless, using the Win32 API is really nothing that you should be afraid of.

    As I mentioned in a comment, what you're trying to do is often problematic with GDI+ drawing functions, like most of those provided by the .NET Framework. The task is made much easier by using GDI instead. You can use the following code to create a bitmap from a cursor (or icon, they're basically interchangeable) that does respect the alpha channel:

    [StructLayout(LayoutKind.Sequential)]    
    private struct ICONINFO
    {
        public bool fIcon;
        public int xHotspot;
        public int yHotspot;
        public IntPtr hbmMask;
        public IntPtr hbmColor;
    }
    
    [DllImport("user32")]
    private static extern bool GetIconInfo(IntPtr hIcon, out ICONINFO pIconInfo);
    
    [DllImport("user32.dll")]
    private static extern IntPtr LoadCursorFromFile(string lpFileName);
    
    [DllImport("gdi32.dll", SetLastError = true)]
    private static extern bool DeleteObject(IntPtr hObject);
    
    private Bitmap BitmapFromCursor(Cursor cur)
    {
        ICONINFO ii;
        GetIconInfo(cur.Handle, out ii);
    
        Bitmap bmp = Bitmap.FromHbitmap(ii.hbmColor);
        DeleteObject(ii.hbmColor);
        DeleteObject(ii.hbmMask);
    
        BitmapData bmData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat);
        Bitmap dstBitmap = new Bitmap(bmData.Width, bmData.Height, bmData.Stride, PixelFormat.Format32bppArgb, bmData.Scan0);
        bmp.UnlockBits(bmData);
    
        return new Bitmap(dstBitmap);
    }
    
    private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
    {
        //Using LoadCursorFromFile from user32.dll, get a handle to the icon
        IntPtr hCursor = LoadCursorFromFile("C:\\Windows\\Cursors\\Windows Aero\\aero_busy.ani");
    
        //Create a Cursor object from that handle
        Cursor cursor = new Cursor(hCursor);
    
        //Convert that cursor into a bitmap
        using (Bitmap cursorBitmap = BitmapFromCursor(cursor))
        {
            //Draw that cursor bitmap directly to the form canvas
            e.Graphics.DrawImage(cursorBitmap, 50, 50);
        }
    }
    

    If this code doesn't compile, make sure that you've added using statements for System.Drawing, System.Drawing.Imaging, and System.Runtime.InteropServices. Also remember to wire up the Form1_Paint method as the handler for your form's Paint event.

    It is tested to work:

    cursor, drawn from bitmap complete with alpha channel