Вопрос

I’m trying to crop a 24bpp image using memcpy like I read here: cropping an area from BitmapData with C#. The problem I’m having is that it only works when my sourceImage is 32bpp. It gives me a corrupt image when my sourceImage is 24bpp.

class Program
{
    [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
    static unsafe extern int memcpy(byte* dest, byte* src, long count);

    static void Main(string[] args)
    {
        var image = new Bitmap(@"C:\Users\Vincent\Desktop\CroppedScaledBitmaps\adsadas.png");

        //Creates a 32bpp image - Will work eventhough I treat it as a 24bpp image in the CropBitmap method...
        //Bitmap newBitmap = new Bitmap(image);

        //Creates a 24bpp image - Will produce a corrupt cropped bitmap
        Bitmap newBitmap = (Bitmap)image.Clone();

        var croppedBitmap = CropBitmap(newBitmap, new Rectangle(0, 0, 150, 150));
        croppedBitmap.Save(@"C:\Users\Vincent\Desktop\CroppedScaledBitmaps\PieceOfShit.png", ImageFormat.Png);

        Console.ReadLine();
    }

    static public Bitmap CropBitmap(Bitmap sourceImage, Rectangle rectangle)
    {
        Console.WriteLine("Bits per pixel of sourceImage: {0}", Image.GetPixelFormatSize(sourceImage.PixelFormat));

        var sourceBitmapdata = sourceImage.LockBits(rectangle, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);

        var croppedImage = new Bitmap(rectangle.Width, rectangle.Height, PixelFormat.Format24bppRgb);
        var croppedBitmapData = croppedImage.LockBits(new Rectangle(0, 0, rectangle.Width, rectangle.Height), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);

        unsafe
        {
            byte* sourceImagePointer = (byte*)sourceBitmapdata.Scan0.ToPointer();
            byte* croppedImagePointer = (byte*)croppedBitmapData.Scan0.ToPointer();
            memcpy(croppedImagePointer, sourceImagePointer, croppedBitmapData.Stride * rectangle.Height);
        }
        sourceImage.UnlockBits(sourceBitmapdata);
        croppedImage.UnlockBits(croppedBitmapData);
        return croppedImage;
    }
}

I’m very confused, because the only thing I’m changing is the sourceImage PixelFormat, not any of the code in the CropBitmap method. So I always call LockBits using 24bpp Pixelformat, even if the sourceImage is 32bpp.

I’ve tried different methods of calculating the number of bytes I’m copying but everything resulted in more or less the same corrupted image.

Any help is appreciated!

Это было полезно?

Решение

You are trying to copy the data as if it was one continuous block, but it isn't.

The image data is arranged in scan lines, but as you are selecting a part of the image, you don't want all the data from each scan line, you only want the data that represents the pixels that you have selected. A scan line contains the data for the pixels that you specified when you called LockBits, but also data for the pixels outside that area.

The Stride value is the difference in memory address from one scan line to the next. The Stride value may also include padding between the scan lines. Note also that the Stride value can be negative, which happens when the image data is stored upside down in memory.

You want to copy the relevant data from one line of the source image to the line in the destination image. As there can be gaps both in the source data and destination data, you can't copy the data as a single chunk of data.

You would need to loop through the lines and copy each line separately, I haven't tested this code, but something like this:

byte* sourceImagePointer = (byte*)sourceBitmapdata.Scan0.ToPointer();
byte* croppedImagePointer = (byte*)croppedBitmapData.Scan0.ToPointer();
int width = rectange.Width * 3; // for 24 bpp pixel data
for (int y = 0; y < rectangle.Height; y++) {
  memcpy(croppedImagePointer, sourceImagePointer, width);
  sourceImagePointer += sourceBitmapdata.Stride;
  croppedImagePointer += croppedBitmapData.Stride;
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top