0

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?

Jacek Blaszczynski
  • 3,183
  • 14
  • 25
Ilya
  • 45
  • 1
  • 8
  • Could you explain what kind of image processing you are up to? – Richard Rublev Nov 01 '17 at 11:09
  • 1
    What is `Pixel`? – harold Nov 01 '17 at 11:10
  • I'm guessing that your Pixel struct is missing a byte for Alpha – Mesh Nov 01 '17 at 11:12
  • In any case don't lock with unknown formats, choose the format that matches your processing, looks like you assume `Format24bppRgb` – harold Nov 01 '17 at 11:13
  • You always really, really care about the pixel format when you manipulate bitmap data directly. So be explicit what you ask for in the LockBits() call, foreImg.PixelFormat is clearly not a happy choice. This may require a conversion, it is not for free. But you might be able to make it pay it off, using a UInt32* instead of copying individual bytes can greatly speed up the code. – Hans Passant Nov 01 '17 at 12:05
  • And note that you automatically have a mismatch, the JPEG is going to be 24bppRgb and the bitmap you create is going to match the video adapter setting, 32bppArgb on any modern machine. So you must either use two different Pixel definitions or ask for 32bppArgb in the LockBits() call. I recommend the latter so your code will work with any kind of image. And can benefit from UInt32*. – Hans Passant Nov 01 '17 at 12:10
  • If you want to ignore the input format, and have it always work the same way, a quick fix is to simply start by painting your picture onto a blank 32bppArgb bitmap of the same dimensions. – Nyerguds Nov 23 '17 at 12:06

2 Answers2

1

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.

Ilya
  • 45
  • 1
  • 8
0

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);
        }
    }
}
Jacek Blaszczynski
  • 3,183
  • 14
  • 25
  • That `byteLength` is wrong. Images are saved _per line_, and often those lines are rounded to the next multiple of 4 bytes. To ensure this is right, you need to use the `Stride` instead of `Width * pixelSize`, and copy the data per line using that stride. The `Stride` is available as property from the `BitmapData` objects. – Nyerguds Nov 06 '17 at 11:47
  • @Nyerguds: `That byteLength is wrong. Images are saved per line, and often those lines are rounded to the next multiple of 4 bytes`. How **often** has anything to do with System.Drawing.Bitmap implementation format. – Jacek Blaszczynski Nov 06 '17 at 13:17
  • Uh... what you just wrote isn't actually a question, but unless you're using a 32bpp image, this causes problems whenever an image width isn't an exact multiple of 4 pixels. – Nyerguds Nov 06 '17 at 13:27