cropping an area from BitmapData with C#

谁说我不能喝 提交于 2019-11-29 06:50:00

I whipped up a quick (and admittedly rough) manual solution that demonstrates how to do this using locked bitmaps. It should be considerably faster than the alternative methods, but does involve a lot more code.

        Bitmap bmp = new Bitmap(@"C:\original.jpg");
        Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
        BitmapData rawOriginal = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

        int origByteCount = rawOriginal.Stride * rawOriginal.Height;
        byte[] origBytes = new Byte[origByteCount];
        Marshal.Copy(rawOriginal.Scan0, origBytes, 0, origByteCount);

        //I want to crop a 100x100 section starting at 15, 15.
        int startX = 15;
        int startY = 15;
        int width = 100;
        int height = 100;
        int BPP = 4;        //4 Bpp = 32 bits, 3 = 24, etc.

        byte[] croppedBytes = new Byte[width * height * BPP];

        //Iterate the selected area of the original image, and the full area of the new image
        for (int i = 0; i < height; i++)
        {
            for (int j = 0; j < width * BPP; j += BPP)
            {
                int origIndex = (startX * rawOriginal.Stride) + (i * rawOriginal.Stride) + (startY * BPP) + (j);
                int croppedIndex = (i * width * BPP) + (j);

                //copy data: once for each channel
                for (int k = 0; k < BPP; k++)
                {
                    croppedBytes[croppedIndex + k] = origBytes[origIndex + k];
                }
            }
        }

        //copy new data into a bitmap
        Bitmap croppedBitmap = new Bitmap(width, height);
        BitmapData croppedData = croppedBitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
        Marshal.Copy(croppedBytes, 0, croppedData.Scan0, croppedBytes.Length);

        bmp.UnlockBits(rawOriginal);
        croppedBitmap.UnlockBits(croppedData);

        croppedBitmap.Save(@"C:\test.bmp");

I used this original image:

To output this image, cropped to 100x100 @ 15,15:

Obviously if you use this code, you'll want to clean it up a bit and add error handling. If I understand your question correctly, doing things this way should eliminate the need to use AForge at all.

Fopedush's answer benefits greatly when we subsitute Marshal.copy with memcpy, because that way we don't have to copy it through a byte[] array. That way the memory gets copied only once, instead of three times!

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

static public Bitmap cropBitmap(Bitmap sourceImage, Rectangle rectangle)
{
    const int BPP = 4; //4 Bpp = 32 bits; argb
    var sourceBitmapdata = sourceImage.LockBits(rectangle, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
    var croppedImage = new Bitmap(rectangle.Width, rectangle.Height, PixelFormat.Format32bppArgb);
    var croppedBitmapData = croppedImage.LockBits(new Rectangle(0, 0, rectangle.Width, rectangle.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
    unsafe
    {
        croppedBitmapData.Stride = sourceBitmapdata.Stride;
        byte* sourceImagePointer = (byte*)sourceBitmapdata.Scan0.ToPointer();
        byte* croppedImagePointer = (byte*)croppedBitmapData.Scan0.ToPointer();
        memcpy(croppedImagePointer, sourceImagePointer,
               Math.Abs(croppedBitmapData.Stride) * rectangle.Height);
    }
    sourceImage.UnlockBits(sourceBitmapdata);
    croppedImage.UnlockBits(croppedBitmapData);
    return croppedImage;
}

My results are:

BitmapClone: 1823 ms
LockBits: 4857 ms
Rectangle: 1479 ms
My method: 559 ms
My method with LockBits on source image done only once (before loop): 160 ms

I don't have AForge so I haven't included that, but by looking on op's results it would be slower than this. I was testing cropping the image in half.

Please note, that if we would exchange memcpy with:

for (int k = 0; k < Math.Abs(croppedBitmapData.Stride) * rectangle.Height; k++)
     *(croppedImagePointer++) = *(sourceImagePointer++);

it gets 10x slower!

You can try something like this:

public static Bitmap CropBitmap(Bitmap bitmap, int x, int y, int w, int h)
{
   Rectangle rect = new Rectangle(x, y, w, h);
   Bitmap cropped = bitmap.Clone(rect, bitmap.PixelFormat);
   return cropped;
}

And do something like this in yout code (sample):

var croppedImagem = CropBitmap(dataOriginal, 0, 0, 100, 100); 

I hope it helps!

I am a new user and can't vote yet, otherwise I would have upvoted Korwin80's answer as it provides the most efficient working solution, in my opinion. trakos' solution may execute faster but yields scrambled images, at least for me. Here is how I applied Korwin80's solution, with some minor improvements, in my own code:

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

private unsafe Bitmap Crop(Bitmap srcImg, Rectangle rectangle)
{
    if ((srcImg.Width == rectangle.Width) && (srcImg.Height == rectangle.Height))
        return srcImg;

    var srcImgBitmapData = srcImg.LockBits(new Rectangle(0, 0, srcImg.Width, srcImg.Height), ImageLockMode.ReadOnly, srcImg.PixelFormat);
    var bpp = srcImgBitmapData.Stride / srcImgBitmapData.Width; // 3 or 4
    var srcPtr = (byte*)srcImgBitmapData.Scan0.ToPointer() + rectangle.Y * srcImgBitmapData.Stride + rectangle.X * bpp;
    var srcStride = srcImgBitmapData.Stride;

    var dstImg = new Bitmap(rectangle.Width, rectangle.Height, srcImg.PixelFormat);
    var dstImgBitmapData = dstImg.LockBits(new Rectangle(0, 0, dstImg.Width, dstImg.Height), ImageLockMode.WriteOnly, dstImg.PixelFormat);
    var dstPtr = (byte*)dstImgBitmapData.Scan0.ToPointer();
    var dstStride = dstImgBitmapData.Stride;

    for (int y = 0; y < rectangle.Height; y++)
    {
        memcpy(dstPtr, srcPtr, dstStride);
        srcPtr += srcStride;
        dstPtr += dstStride;
    }

    srcImg.UnlockBits(srcImgBitmapData);
    dstImg.UnlockBits(dstImgBitmapData);
    return dstImg;
}
Korwin80

this class gets your bitmap obj . then lockbits. in ctor. When you call crop method, it uses memcpy to copy the desired region to new bmp.

lockbits: tells the garbage collector to NOT move my bits anywhere, cuz im gonna modify it by pointers (scan0).

memcpy : fastest copy. can copy memory blocks. optimized by some experts.

why memcpy fast?

instead of copying byte by byte, (widthheight) times memory access . memcpy does it block by block, much more less than wh times .

internal unsafe sealed class FastImageCroper : IDisposable
{
    private readonly Bitmap _srcImg;
    private readonly BitmapData _srcImgBitmapData;
    private readonly int _bpp;
    private readonly byte* _srtPrt;

    public FastImageCroper(Bitmap srcImg)
    {
        _srcImg = srcImg;
        _srcImgBitmapData = srcImg.LockBits(new Rectangle(0, 0, srcImg.Width, srcImg.Height), ImageLockMode.ReadOnly, srcImg.PixelFormat);
        _bpp = _srcImgBitmapData.Stride / _srcImgBitmapData.Width; // == 4
        _srtPrt = (byte*)_srcImgBitmapData.Scan0.ToPointer();
    }

    public Bitmap Crop(Rectangle rectangle)
    {
        Bitmap dstImg = new Bitmap(rectangle.Width, rectangle.Height, _srcImg.PixelFormat);
        BitmapData dstImgBitmapData = dstImg.LockBits(new Rectangle(0, 0, dstImg.Width, dstImg.Height), ImageLockMode.WriteOnly, dstImg.PixelFormat);
        byte* dstPrt = (byte*)dstImgBitmapData.Scan0.ToPointer();
        byte* srcPrt = _srtPrt + rectangle.Y*_srcImgBitmapData.Stride + rectangle.X*_bpp;

        for (int y = 0; y < rectangle.Height; y++)
        {
            int srcIndex =  y * _srcImgBitmapData.Stride;
            int croppedIndex = y * dstImgBitmapData.Stride;
            memcpy(dstPrt + croppedIndex, srcPrt + srcIndex, dstImgBitmapData.Stride);
        }

        dstImg.UnlockBits(dstImgBitmapData);
        return dstImg;
    }


    public void Dispose()
    {
        _srcImg.UnlockBits(_srcImgBitmapData);
    }


    [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
    private static extern int memcpy(byte* dest, byte* src, long count);
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!