Ilya
Ilya

Reputation: 45

Process Bitmap in C# using underlying buffer accessed with Bitmap.LockBits function

I need to do an analysis with an arbitrary image. I would like to start with the easiest example - just copy a image to picturebox.

Bitmap foreImg = new Bitmap("input.jpg");
//output image
Bitmap resImg = new Bitmap(foreImg.Width, foreImg.Height);
unsafe
{
    BitmapData oneBits = foreImg.LockBits(new Rectangle(0, 0, foreImg.Width, foreImg.Height), ImageLockMode.ReadOnly, foreImg.PixelFormat);
    BitmapData thrBits = resImg.LockBits(new Rectangle(0, 0, resImg.Width, resImg.Height), ImageLockMode.WriteOnly, resImg.PixelFormat);

    System.Threading.Tasks.Parallel.For(0, foreImg.Width * foreImg.Height, j =>
    {
        Pixel* pxOne = (Pixel*)((byte*)oneBits.Scan0 + j * sizeof(Pixel));
        Pixel* pxRes = (Pixel*)((byte*)thrBits.Scan0 + j * sizeof(Pixel));
        pxRes->Green = pxOne->Green;
        pxRes->Red = pxOne->Red;
        pxRes->Blue = pxOne->Blue;

     });

    foreImg.UnlockBits(oneBits);
    resImg.UnlockBits(thrBits);
}

In the result of my program the image is distorted Original: original_image After: after_image. What am I doing wrong?

Upvotes: 0

Views: 964

Answers (2)

Jacek Blaszczynski
Jacek Blaszczynski

Reputation: 3269

Your code for image copy has couple errors due to assumptions which turn not true for particular image which is copied. First assumption us that when you create new target image for copy operation it will have exactly the same pixel representation as the source image what may be sometimes true but in many cases will not:

Bitmap resImg = new Bitmap(foreImg.Width, foreImg.Height);

should be instead:

Bitmap resImg = new Bitmap(foreImg.Width, foreImg.Height, foreImg.PixelFormat);

The next assumption which may or may not turn wrong depending on image is an implicit assumption that the source image PixelFormat is exactly 3 bytes in size and corresponds to PixelFormat.Format24bppRgb format (or multiple of 3 bytes as I do not know what is the size of Red, Green or Blue channel in your Pixel structure and it could be PixelFormat.Format48bppRgb format) and consequently the bytes are copied from the source image to the destination image based on this assumption.

To perform exact copy it is necessary to copy exactly the same number of bytes from source image to destination image and it does not require using an underlying Pixel structure but instead it can be based on integer copy. Last but not least if the goal is to copy image instead of analyzing it's content Pixel by Pixel the fastest method is to use specialized memory copy function:

System.Buffer.MemoryCopy((void*)oneBits.Scan0, (void*)thrBits.Scan0, byteLength, byteLength);

Below there is a code listing with code which copies an image using ulong as a carrier. I have added function which returns Pixel size in bytes which is used to calculate image size in bytes and perform exact copy. However it can be used to select matching Pixel structure which than can be used to analyze image data. For instance if an image has PixelFormat.Format24bppRgb format one can use Pixel structure of 3 byte size and RGB colors. For other formats it would be necessary to define other Pixel structures which would directly replicate image Pixel format.

using System;
using System.Drawing;
using System.Drawing.Imaging;

namespace DrawingImagingOperations
{
    class Program
    {
        static void Main(string[] args)
        {
            Bitmap foreImg = new Bitmap(@"..\..\YaHI9.jpg");
            //output image
            Bitmap resImg = new Bitmap(foreImg.Width, foreImg.Height, foreImg.PixelFormat);
            unsafe
            {
                BitmapData oneBits = foreImg.LockBits(new Rectangle(0, 0, foreImg.Width, foreImg.Height), ImageLockMode.ReadOnly, foreImg.PixelFormat);
                BitmapData thrBits = resImg.LockBits(new Rectangle(0, 0, resImg.Width, resImg.Height), ImageLockMode.WriteOnly, resImg.PixelFormat);
                int pixelSize = GetPixelSize(foreImg.PixelFormat);

                var byteLength = foreImg.Width * foreImg.Height * pixelSize;
                var length = byteLength / sizeof(UInt64);
                var reminder = byteLength % sizeof(UInt64);


                System.Threading.Tasks.Parallel.For(0, length, j =>
                {
                    ulong* pxOne = (ulong*)((byte*)oneBits.Scan0 + j * sizeof(UInt64));
                    ulong* pxRes = (ulong*)((byte*)thrBits.Scan0 + j * sizeof(UInt64));
                    *pxRes = *pxOne;
                });

                if (reminder > 0)
                {
                    byte* pSrc = (byte*)oneBits.Scan0 + (pixelSize * length);
                    byte* pDst = (byte*)thrBits.Scan0 + (pixelSize * length);
                    for (int j = length; j < byteLength; j++)
                        *pDst++ = *pSrc++;

                }

                foreImg.UnlockBits(oneBits);
                resImg.UnlockBits(thrBits);
            }

            resImg.Save(@"..\..\imgCopy.jpg");
        }

        internal static int GetPixelSize(PixelFormat data)
        {
            switch (data)
            {
                case PixelFormat.Format8bppIndexed:
                    return 1;
                case PixelFormat.Format16bppGrayScale:
                case PixelFormat.Format16bppRgb555:
                case PixelFormat.Format16bppRgb565:
                case PixelFormat.Format16bppArgb1555:
                    return 2;
                case PixelFormat.Format24bppRgb:
                    return 3;
                case PixelFormat.Canonical:
                case PixelFormat.Format32bppArgb:
                case PixelFormat.Format32bppPArgb:
                case PixelFormat.Format32bppRgb:
                    return 4;
                case PixelFormat.Format48bppRgb:
                    return 6;
                case PixelFormat.Format64bppArgb:
                case PixelFormat.Format64bppPArgb:
                    return 8;
            }

            throw new FormatException("Unsupported image format: " + data);
        }
    }
}

Upvotes: 0

Ilya
Ilya

Reputation: 45

Thanks! The problem was is that PixelFormat of input images does not match with my struct Pixel. Indeed, I wasn't add alpha byte, and in this case I was suppose to use Format24bppRgb.

Upvotes: 1

Related Questions