Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

cropping an area from BitmapData with C#

I have a bitmap sourceImage.bmp

locking it's bits:

BitmapData dataOriginal = sourceImage.LockBits(new Rectangle(0, 0, sourceImage.Width, sourceImage.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

Do analysis, get a clone:

Bitmap originalClone = AForge.Imaging.Image.Clone(dataOriginal);

unlocking bits:

sourceImage.UnlockBits(dataOriginal);

is it possible to specify which part of "dataOriginal" to copy (x,y,w,h)? or to create new data from the dataOriginal, specifying X and Y coordinates as well as H and W?

The aim is to copy a small area from this image. This method might be faster than DrawImage, that's why I don't use the latter.

Edit:

So I took 29 Mb bitmap and did some hardcore testing! Full-size crop (basically a copy) + 100 iterations.

http://i.minus.com/ibmcUsT1qUGw6f.png

Code:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using AForge;
using AForge.Imaging;
using System.Diagnostics;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;


namespace testCropClone
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private unsafe Bitmap Clone(Bitmap bmp, int startX, int startY, int width, int height)
        {
        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);

        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);

        return croppedBitmap;
        }

        private Bitmap cloneBitmap(Bitmap bmp, int startX, int startY, int width, int height)
        {
            Rectangle srcRect = Rectangle.FromLTRB(startX, startY, width, height);
            Bitmap cloneBitmap = bmp.Clone(srcRect, bmp.PixelFormat);
            return cloneBitmap;
        }


        private Bitmap cloneRectangle(Bitmap bmp, int startX, int startY, int width, int height)
        {
            Rectangle srcRect = Rectangle.FromLTRB(startX, startY, width, height);
            Bitmap dest = new Bitmap(srcRect.Width, srcRect.Height);
            Rectangle destRect = new Rectangle(0, 0, srcRect.Width, srcRect.Height);
            using (Graphics graphics = Graphics.FromImage(dest))
            {
                graphics.DrawImage(bmp, destRect, srcRect, GraphicsUnit.Pixel);
            }
            return dest;
        }


        private Bitmap cloneAforge(Bitmap bmp, int startX, int startY, int width, int height)
        {
            BitmapData rawOriginal = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
            Bitmap cloneBitmap = AForge.Imaging.Image.Clone(rawOriginal);
            bmp.UnlockBits(rawOriginal);
            return cloneBitmap;
        }



        private void button1_Click(object sender, EventArgs e)
        {
            Bitmap source = new Bitmap(@"C:\9\01.bmp");

            Stopwatch s1 = Stopwatch.StartNew();
            for (int i = 0; i < 100; i++)
            {
                Bitmap Clone1 = cloneAforge(source, 0, 0, source.Width, source.Height);
                Clone1.Dispose();

            }

            /*Bitmap Clone1 = cloneAforge(source, 0, 0, source.Width, source.Height);
            Clone1.Save(@"C:\9\01_aforge.bmp");
            Clone1.Dispose();*/

            s1.Stop();
            source.Dispose();
            textBox1.Text = ("" + s1.ElapsedMilliseconds / 100 + " ms");
        }

        private void button2_Click(object sender, EventArgs e)
        {
            Bitmap source = new Bitmap(@"C:\9\01.bmp");

            Stopwatch s1 = Stopwatch.StartNew();
            for (int i = 0; i < 100; i++)
            {
                Bitmap Clone1 = cloneBitmap(source, 0, 0, source.Width, source.Height);
                Clone1.Dispose();

            }

            /*Bitmap Clone1 = cloneBitmap(source, 0, 0, source.Width, source.Height);
            Clone1.Save(@"C:\9\01_bitmap.bmp");
            Clone1.Dispose();*/

            s1.Stop();


            source.Dispose();
            textBox2.Text = ("" + s1.ElapsedMilliseconds / 100 + " ms");
        }

        private void button3_Click(object sender, EventArgs e)
        {
            Bitmap source = new Bitmap(@"C:\9\01.bmp");

            Stopwatch s1 = Stopwatch.StartNew();
            for (int i = 0; i < 100; i++)
            {
                Bitmap Clone1 = Clone(source, 0, 0, source.Width, source.Height);
                Clone1.Dispose();

            }

            /*Bitmap Clone1 = Clone(source, 0, 0, source.Width, source.Height);
            Clone1.Save(@"C:\9\01_bits.bmp");
            Clone1.Dispose();*/

            s1.Stop();
            source.Dispose();
            textBox3.Text = ("" + s1.ElapsedMilliseconds / 100 + " ms");
        }

        private void button4_Click(object sender, EventArgs e)
        {
            Bitmap source = new Bitmap(@"C:\9\01.bmp");

            Stopwatch s1 = Stopwatch.StartNew();
            for (int i = 0; i < 100; i++)
            {
                Bitmap Clone1 = cloneRectangle(source, 0, 0, source.Width, source.Height);
                Clone1.Dispose();

            }


            /*Bitmap Clone1 = cloneRectangle(source, 0, 0, source.Width, source.Height);
            Clone1.Save(@"C:\9\01_rect.bmp");
            Clone1.Dispose();*/


            s1.Stop();
            source.Dispose();
            textBox4.Text = ("" + s1.ElapsedMilliseconds / 100 + " ms");
        }
    }
}

Edit2: (Aforge full-size Crop..) method Nr. 2

        for (int i = 0; i < 100; i++)
        {
            Crop crop = new Crop(new Rectangle(0, 0, source.Width, source.Height));
            var source2 = crop.Apply(source);
            source2.Dispose();

        }

Average = 62ms (40ms less that 1st Aforge approach)

Results:

  1. BitmapClone (0 ms) ?? (cheating, isn't it?)
  2. Aforge #2 (65 ms)
  3. Aforge #1 (105 ms)
  4. Rectangle (170 ms)
  5. Lock Bits (803 ms) (waiting for fixes/new test results..)
like image 435
Alex Avatar asked Mar 13 '12 16:03

Alex


3 Answers

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.

like image 105
Fopedush Avatar answered Sep 22 '22 13:09

Fopedush


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!

like image 3
trakos Avatar answered Sep 24 '22 13:09

trakos


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!

like image 2
Felipe Oriani Avatar answered Sep 20 '22 13:09

Felipe Oriani