Search code examples
c#image-processingunsafe

Calculate average of images using pointers


My code calculates average color of each pixel in an image and returns a new image.

Image calculateAverage(Bitmap image1, Bitmap image2)
{
    // Locking the image into memory.
    BitmapData sourceData = image1.LockBits(new Rectangle(0, 0, image1.Width, image1.Height),
        ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

    // All of the pixels will be stored in this array(each 4 numbers represent a pixel).
    byte[] sourceBuffer = new byte[sourceData.Stride * sourceData.Height];

    // Copying the image pixels into sourceBuffer.
    Marshal.Copy(sourceData.Scan0, sourceBuffer, 0, sourceBuffer.Length);

    // Don't need the image, unlock memory.
    image1.UnlockBits(sourceData);

    // --- Same thing for the second image ---
    BitmapData blendData = image2.LockBits(new Rectangle(0, 0, image2.Width, image2.Height),
        ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
    byte[] blendBuffer = new byte[blendData.Stride * blendData.Height];
    Marshal.Copy(blendData.Scan0, blendBuffer, 0, blendBuffer.Length);
    image2.UnlockBits(blendData);
    //---

    // Calculating average of each color in each pixel.
    for (int k = 0; (k + 4 < sourceBuffer.Length) && (k + 4 < blendBuffer.Length); k += 4)
    {
        sourceBuffer[k] = (byte)calcualteAverage(sourceBuffer[k], blendBuffer[k]);
        sourceBuffer[k + 1] = (byte)calcualteAverage(sourceBuffer[k + 1], blendBuffer[k + 1]);
        sourceBuffer[k + 2] = (byte)calcualteAverage(sourceBuffer[k + 2], blendBuffer[k + 2]);
        // Average of Alpha
        sourceBuffer[k + 3] = (byte)calcualteAverage(sourceBuffer[k + 3], sourceBuffer[k + 3]);
    }
    
    // Saving the result.
    Bitmap resultBitmap = new Bitmap(image1.Width, image1.Height);
    BitmapData resultData = resultBitmap.LockBits(new Rectangle(0, 0,
        resultBitmap.Width, resultBitmap.Height),
        ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);

    Marshal.Copy(sourceBuffer, 0, resultData.Scan0, sourceBuffer.Length);
    resultBitmap.UnlockBits(resultData);

    return resultBitmap;
}

For performance reasons I need to rewrite this method using unsafe code though I'm a noob with pointers; don't know how it can improve performance or what will change in the algorithm?


Solution

  • The function can be replaced by this:

    public static Bitmap CalculateAverage(
        Bitmap sourceBitmap, Bitmap blendBitmap)
    {
        // Locking the images into the memory.
        BitmapData sourceData = sourceBitmap.LockBits(
            new Rectangle(0, 0, sourceBitmap.Width, sourceBitmap.Height),
            ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
    
        BitmapData blendData = blendBitmap.LockBits(
            new Rectangle(0, 0, blendBitmap.Width, blendBitmap.Height),
            ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
    
        // Get required variables. This wont work for images with different sizes.
        int resultWidth = sourceBitmap.Width;
        int resultHeight = sourceBitmap.Height;
        int stride = sourceData.Stride;
    
        // result will be stored here.
        var resultBitmap = new Bitmap(resultWidth, resultHeight);
        BitmapData resultData = resultBitmap.LockBits(
            new Rectangle(0, 0, resultWidth, resultHeight),
            ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
    
        unsafe
        {
            // Getting address of locked images.
            byte* sourceAddress = (byte*)sourceData.Scan0.ToPointer();
            byte* blendAddress = (byte*)blendData.Scan0.ToPointer();
            byte* resultAddress = (byte*)resultData.Scan0.ToPointer();
    
            // Iterating throgh pixels and storing averages inside the result variable.
            for (int y = 0; y < resultHeight; y++)
            {
                for (int x = 0; x < resultWidth; x++)
                {
                    for (int color = 0; color < 4; color++)
                    {
                        int currentByte = (y * stride) + x * 4 + color;
    
                        resultAddress[currentByte] = (byte)average(
                            sourceAddress[currentByte],
                            blendAddress[currentByte]);
                    }
                }
            }
        }
        
        // Unlocking the Bits; returning the result bitmap.
        sourceBitmap.UnlockBits(sourceData);
        blendBitmap.UnlockBits(blendData);
        resultBitmap.UnlockBits(resultData);
        return resultBitmap;
    }
    

    I cannot test it at the moment but this might give a hint in the right direction. If there are any questions please don't hesitate to ask. It's based on this code: How to calculate the average rgb color values of a bitmap.