5

I'm using the following code to lock a rectangle region of a bitmap

Recangle rect = new rect(X,Y,width,height);
BitmapData bitmapData = bitmap.LockBits(rect, ImageLockMode.ReadOnly,
                        bitmap.PixelFormat);

What seems to be the issue is bitmapData.Scan0 gives me IntPtr of the top left corner of the rectangle. When I use memcpy, it copies the contiguous region in memory upto the specified length.

memcpy(bitmapdest.Scan0, bitmapData.Scan0, 
             new UIntPtr((uint (rect.Width*rect.Height*3)));

If following is my bitmap data,

a b c d e
f g h i j
k l m n o
p q r s t

and if the rectangle is (2, 1, 3 ,3) i.e, the region

g h i
l m n
q r s

using memcpy gives me bitmap with the following region

g h i
j k l
m n o

I can understand why it copies the contiguous memory region. Bottom line is I want to copy a rectangle region using Lockbits.

Edit: I used Bitmap.Clone,

using (Bitmap bitmap= (Bitmap)Image.FromFile(@"Data\Edge.bmp"))
{
     bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);
     Rectangle cropRect = new Rectangle(new Point(i * croppedWidth, 0),new Size(croppedWidth, _original.Height));
     _croppedBitmap= bitmap.Clone(cropRect, bitmap.PixelFormat);
}

but it was faster when I flipped Y (less than 500ms)

bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);

but it was very slow when I didn't flip Y (30 seconds)

Image size used was 60000x1500.

Dinesh
  • 115
  • 2
  • 7
  • 2
    Without [a good, _minimal_, _complete_ code example](http://stackoverflow.com/help/mcve) that reliably reproduces the problem, it's not possible to answer. That said, `LockBits()` can't magically reformat the data behind your bitmap, so I don't see any reason to expect a straight `memcpy()` to work. You'll probably need to copy the data scanline by scanline; have you checked the `BitmapData.Stride` property? It should tell you what you need to add to `Scan0` for each next row. – Peter Duniho Apr 21 '15 at 08:39
  • @PeterDuniho I get why memcpy won't work. So you mean copying scanline by scanline is the only way to get this done? Can you explain why Bitmap.Clone takes different times in those two cases ? – Dinesh Apr 21 '15 at 08:56
  • Is there a reason not using DrawImage? – TaW Apr 21 '15 at 09:06
  • @TaW I don't want to create a new bitmap object. The reason is I'm working with OpenGL. When I say I want to memcpy I'm copying the bitmap data to a Pixel buffer object. So creating a new bitmap and copying that data to buffer object is essentially having two copies of the same bitmap as cropped and original. – Dinesh Apr 21 '15 at 09:13
  • You are finding out the Hard Way that bitmaps are normally stored upside-down in memory. The last scanline is stored first. So your LockBits() rectangle requires a lot of pixels to be moved around to arrange them in the order you asked for. You might well be ahead by locking the entire width x height and using memcpy() for each individual scanline. Still, pixel data from the image file is decoded on the fly and you measure that cost as well, you can't escape that. WIC is the better api to handle very large bitmaps, exposed in .NET by the System.Windows.Media.Imaging namespace. – Hans Passant Apr 21 '15 at 11:24
  • @HansPassant if [Bitmap.Clone](http://stackoverflow.com/questions/12709360/whats-the-difference-between-bitmap-clone-and-new-bitmapbitmap) does not actually copy the bitmap data why does it require to move pixels ? As you said bitmap scanlines are stored upside down, then the cloned bitmap will also follow the same ordering right. But According to my observation cloning with flipping is much faster than not flipping, can you explain this? – Dinesh Apr 21 '15 at 13:19
  • _"cloning with flipping is much faster than not flipping"_ -- a 60x difference seems particularly unreasonable. But it seems to me that Hans is saying that by default, the underlying bitmap object is stored with the bottom row of pixels as the first row of _data_ in memory. I don't know why this would be, nor why the _target_ bitmap would default to the opposite (which is the only way that a flipped copy would be faster), but it would explain the behavior you see (which in turn matches a bug I reported in GDI+ drawing years ago on Connect...no longer even on the site). – Peter Duniho Apr 21 '15 at 15:53

1 Answers1

1

Don't really get what your problem is. The following code copies the correct bitmap region into a managed array (I used 32bpp, alpha is always 255 for a 24bpp bitmap of course):

int x = 1;
int y = 1;
int w = 2;
int h = 2;

Bitmap bmp = new Bitmap(@"path\to\bitmap.bmp");

Rectangle rect = new Rectangle(x, y, w, h);
System.Drawing.Imaging.BitmapData bmpData =
    bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly,
    System.Drawing.Imaging.PixelFormat.Format32bppArgb);

IntPtr ptr = bmpData.Scan0;

int bytes = 4 * w * h;
byte[] rgbValues = new byte[bytes];

System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);

bmp.UnlockBits(bmpData);
Robert S.
  • 1,942
  • 16
  • 22
  • Your solution may have a problem when trying to copy a rectangular area on a bitmap, like discussed [here](http://stackoverflow.com/questions/10771300/bitmap-lockbits-confusion). I have learnt that by using `LockBits` the only way to do this is to iterate over the whole bitmap with stride offset, as like the solution discussed in the link above. – Dinesh May 06 '15 at 07:42
  • I tested the code with a 4x4 pixel bitmap. The above code copies the correct inner 2x2 pixel block without problems. You can also change and re-insert this 2x2 block into the bitmap without problems. A similar code sample can be found on the MSDN. You can choose a desired pixel format in the `LockBits` call which will also affect the stride. To be honest I dunno why this works. Maybe `Marshal.Copy` does some magic internally? – Robert S. May 06 '15 at 11:49
  • It will not work when stride/"bytes per pixel" == "image width". Usually this is true, that is why your test is working. Otherwise, you will copy real image data to the "border memory optimization garbage" and vice verse. – Pedro77 May 21 '15 at 11:47
  • As I understand the stride is `bytes per pixel * image width` so your "not working" will only be true if bytes per pixel is 1. `Format32bppArgb` will exactly ensure `stride = 4 * image width`. Correct me if I'm wrong. – Robert S. May 21 '15 at 17:36