Keeping a Bitmap’s Transparency when Copying

I’ve been working on a new control this week that depends a whole lot on transparency and the alpha channel.  The Compact Framework’s Bitmap class is actually really, really bad and it’s made for all sorts of swearing. 


The design of the control requires me to regularly create a “transparent” background.  We know that you can’t create a transparent bitmap directly (which is the fundamental thing that is killing me) so I end up using a TransparentClearBitmap method that first appeared in Project Resistance that does a LockBits and walks the image setting transparency.  This is slow (600ms for my bitmap on the emulator).


I needed to draw on top of this Bitmap to assemble my control layout. The issue is that every time I repainted the control due to a change, I need a new transparent Bitmap and had to pay the 600ms piper.  Needless to say this gave for a really bad user experience.  Well how about just caching the transparent bitmap and returning a copy of it when necessary?  That should be simple, right?


I tried to use the Bitmap constructor to make a copy:


var bmp = new Bitmap(m_transparentBitmap);


Of course it failed. The new Bitmap lost all transparency data.


Well maybe the Clone method would do what I want, after all that’s what I needed – an exact clone:


var bmp = m_transparentBitmap.Clone;


Again, all transparency data was lost. Joy.


So it was off to the drawing board to re-implement what should have already been working.  Here’s the result – a TransparentBitmap that you can clone and actually keep the transparency info:


public class TransparentBitmap
{
    private Bitmap m_bmp;

    public int Width { get; private set; }
    public int Height { get; private set; }

    public TransparentBitmap(int width, int height)
    {
        Width = width;
        Height = height;
        m_bmp = new Bitmap(width, height, (System.Drawing.Imaging.PixelFormat)PixelFormat.F32bppARGB);

        GraphicTools.TransparentClearBitmap(m_bmp);
    }

    private TransparentBitmap(Bitmap image)
    {
        m_bmp = image;
        Width = image.Width;
        Height = image.Height;
    }

    public static implicit operator Bitmap(TransparentBitmap t)
    {
        return t.m_bmp;
    }

    public TransparentBitmap Clone()
    {
        var clone = new Bitmap(Width, Height, (System.Drawing.Imaging.PixelFormat)PixelFormat.F32bppARGB);

        var dest = clone.LockBits(
                new Rectangle(0, 0, Width, Height),
                ImageLockMode.WriteOnly,
                (System.Drawing.Imaging.PixelFormat)PixelFormat.F32bppARGB);

        try
        {
            var src = m_bmp.LockBits(
                    new Rectangle(0, 0, Width, Height),
                    ImageLockMode.ReadOnly,
                    (System.Drawing.Imaging.PixelFormat)PixelFormat.F32bppARGB);

            try
            {
                int bytes = src.Width * src.Height * 4;
                memcpy(dest.Scan0, src.Scan0, bytes);
            }
            finally
            {
                m_bmp.UnlockBits(src);
            }
        }
        finally
        {
            clone.UnlockBits(dest);
        }

        return new TransparentBitmap(clone);
    }

    [DllImport("coredll.dll", SetLastError = true)]
    private static extern IntPtr memcpy(IntPtr to, IntPtr from, int count);
}


The Clone takes only about 4ms to run instead of 600ms to create new, so it certainly improved things.

One Comment

Leave a Reply