ITHCWY: Robert Ellison's Blog

Fastest image merge (alpha blend) in GDI+

Blended image from Catfood Earth

I've been experimenting with the best way to merge two images in C#. That is, paint one image on top of another with some level of transparency as opposed to using one color as a transparency mask. I tried three candidates, all included below:

SimpleBlend is the naive approach using GetPixel and SetPixel to add the desired alpha value to the second image before painting it on top of the first.

MatrixBlend configures a ColorMatrix to specify the desired alpha and then paints the second image on to the first using the matrix.

ManualBlend locks and directly manipulates the image data. This uses pointers and so introduces unsafe code (it's possible to marshal the image data into a managed array but I wanted to look at the performance with raw access).

I tested each approach ten times with a couple of large JPEG images. The results are:

SimpleBlend: 17.69 seconds
MatrixBlend: 0.74 seconds
ManualBlend: 1.13 seconds

I expect ManualBlend could be optimized further but both this and MatrixBlend are an order of magnitude faster than the naive approach. I'll be using MatrixBlend as no unsafe code is required.

private static void SimpleBlend(string image1path, string image2path, byte alpha)
{
    using (Bitmap image1 = (Bitmap)Bitmap.FromFile(image1path))
    {
        using (Bitmap image2 = (Bitmap)Bitmap.FromFile(image2path))
        {
            // update the alpha for each pixel of image 2
            for (int x = 0; x < image2.Width; x++)
            {
                for (int y = 0; y < image2.Height; y++)
                {
                    image2.SetPixel(x, y, Color.FromArgb(alpha, image2.GetPixel(x, y)));
                }
            }

            // draw image 2 on image 1
            using (Graphics g = Graphics.FromImage(image1))
            {
                g.CompositingMode = CompositingMode.SourceOver;
                g.CompositingQuality = CompositingQuality.HighQuality;

                g.DrawImageUnscaled(image2, 0, 0);
            }
        }
    }
}

private static void MatrixBlend(string image1path, string image2path, byte alpha)
{
    // for the matrix the range is 0.0 - 1.0
    float alphaNorm = (float)alpha / 255.0F;
    using (Bitmap image1 = (Bitmap)Bitmap.FromFile(image1path))
    {
        using (Bitmap image2 = (Bitmap)Bitmap.FromFile(image2path))
        {
            // just change the alpha
            ColorMatrix matrix = new ColorMatrix(new float[][]{
                new float[] {1F, 0, 0, 0, 0},
                new float[] {0, 1F, 0, 0, 0},
                new float[] {0, 0, 1F, 0, 0},
                new float[] {0, 0, 0, alphaNorm, 0},
                new float[] {0, 0, 0, 0, 1F}});

            ImageAttributes imageAttributes = new ImageAttributes();
            imageAttributes.SetColorMatrix(matrix);

            using (Graphics g = Graphics.FromImage(image1))
            {
                g.CompositingMode = CompositingMode.SourceOver;
                g.CompositingQuality = CompositingQuality.HighQuality;

                g.DrawImage(image2, 
                    new Rectangle(0, 0, image1.Width, image1.Height), 
                    0, 
                    0, 
                    image2.Width, 
                    image2.Height, 
                    GraphicsUnit.Pixel, 
                    imageAttributes);
            }
        }
    }
}

private static void ManualBlend(string image1path, string image2path, byte alpha)
{
    // percentage of destination and source image
    float alphaDst = (float)alpha / 255.0F;
    float alphaSrc = 1.0F - alphaDst;

    using (Bitmap image1 = (Bitmap)Bitmap.FromFile(image1path))
    {
        Rectangle imageRect = new Rectangle(0, 0, image1.Width, image1.Height);
        BitmapData image1Data = image1.LockBits(imageRect, ImageLockMode.ReadWrite,
            PixelFormat.Format32bppArgb);
                
        using (Bitmap image2 = (Bitmap)Bitmap.FromFile(image2path))
        {
            BitmapData image2Data = image2.LockBits(imageRect, ImageLockMode.ReadOnly,
                PixelFormat.Format32bppArgb);

            unsafe
            {
                uint* image1Raw = (uint*)image1Data.Scan0.ToPointer();
                uint* image2Raw = (uint*)image2Data.Scan0.ToPointer();
                int stride = image1Data.Stride / sizeof(uint);
                int currentPixel;
                uint image1Pixel, image2Pixel;
                uint srcRed, dstRed, finRed;
                uint srcGreen, dstGreen, finGreen;
                uint srcBlue, dstBlue, finBlue;

                for (int x = 0; x < imageRect.Width; x++)
                {
                    for (int y = 0; y < imageRect.Height; y++)
                    {
                        currentPixel = (y * stride) + x;
                        image1Pixel = image1Raw[currentPixel];
                        image2Pixel = image2Raw[currentPixel];

                        srcRed = (image1Pixel >> 16) & 0xFF;
                        srcGreen = (image1Pixel >> 8) & 0xFF;
                        srcBlue = image1Pixel & 0xFF;

                        dstRed = (image2Pixel >> 16) & 0xFF;
                        dstGreen = (image2Pixel >> 8) & 0xFF;
                        dstBlue = image2Pixel & 0xFF;

                        finRed = (uint)((alphaSrc * (float)srcRed) + 
                            (alphaDst * (float)dstRed));
                        finGreen = (uint)((alphaSrc * (float)srcGreen) + 
                            (alphaDst * (float)dstGreen));
                        finBlue = (uint)((alphaSrc * (float)srcBlue) +
                            (alphaDst * (float)dstBlue));

                        image1Raw[currentPixel] = 
                            (uint)(finBlue | finGreen << 8 | finRed << 16 | 0xFF << 24);
                    }
                }
            }
            image2.UnlockBits(image2Data);
        }
        image1.UnlockBits(image1Data);
    }
}

Add Comment

All comments are moderated to weed out spam. Email address is optional and is only used to display your Gravatar.