cropping an area from BitmapData with C#

后端 未结 5 1369
囚心锁ツ
囚心锁ツ 2020-12-17 23:28

I have a bitmap sourceImage.bmp

locking it\'s bits:

BitmapData dataOriginal = sourceImage.LockBits(new Rectangle(0, 0, sourceImage.W         


        
相关标签:
5条回答
  • 2020-12-17 23:50

    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!

    0 讨论(0)
  • 2020-12-17 23:50

    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;
    }
    
    0 讨论(0)
  • 2020-12-17 23:55

    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);
    }
    
    0 讨论(0)
  • 2020-12-18 00:08

    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:

    original

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

    cropped

    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.

    0 讨论(0)
  • 2020-12-18 00:09

    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!

    0 讨论(0)
提交回复
热议问题