Is it safe (pun intended) to copy bitmap regions using lockbits this way?
Asked Answered
E

1

6

I think I've found a MUCH faster way to copy bitmaps in c#. (If it's valid, I'm sure I wasn't the first but I haven't seen it anywhere yet.)

The simplest way I can think to ask this is to assert what I based my idea on and if no one shoots holes in it, assume the idea is sound:

    void FastCopyRegion(Bitmap CopyMe, ref Bitmap IntoMe, Rectangle CopyArea)
    {
        //`IntoMe` need not be declared `ref` but it brings
        //   attention to the fact it will be modified
        Debug.Assert(CopyMe.PixelFormat == IntoMe.PixelFormat,
           "PixelFormat mismatch, we could have a problem");
        Debug.Assert(CopyMe.Width == IntoMe.Width,    //This check does not verify
           "Stride mismatch, we could have a problem");// sign of `stride` match
        BitmapData copyData = CopyMe.LockBits(CopyArea,
            ImageLockMode.ReadWrite, CopyMe.PixelFormat);
        IntoMe.UnlockBits(copyData);
    }

1) LockBits simply copies a block of pixel data out of a bitmap to fixed memory to be edited and copied back in using UnlockBits

2) Using LockBits does not affect the copied memory block so it should have no effect on the image copied from.

3) Since you never enter unsafe code, there should be no risk of corrupting memory.

Possible holes I see:

1) If the PixelFormats of the two bitmaps are different, this method may not always copy correctly. However, since LockBits requires specifying a pixelformat, it seems this is handled. (If so, hooray for that overhead the other 99.9% of the time we're not switching pixelformats! /EndSarcasm)

2) If the stride of the two bitmaps doesn't match, there could be a problem (because stride is the outer for loop's incrementor in the copy operation.) This problem would limit copying to bitmaps with equal stride.

Edit: I think assertion #2 must be wrong... I just found an error when trying to later access the bitmap passed through CopyMe. Workaround below, but I'm not sure if it leaves a block of fixed memory lying around. (memory leak alert!)

    void FastCopyRegion(Bitmap CopyMe, ref Bitmap IntoMe, Rectangle CopyArea)
    {
        //`IntoMe` need not be declared `ref` but it brings attention to the fact it will be modified
        Debug.Assert(CopyMe.PixelFormat == IntoMe.PixelFormat, "PixelFormat mismatch, we could have a problem");
        Debug.Assert(CopyMe.Width == IntoMe.Width, "Width mismatch, we could have a problem");
        BitmapData copyD = IntoMe.LockBits(CopyArea, ImageLockMode.ReadWrite, CopyMe.PixelFormat);
        BitmapData copyData = CopyMe.LockBits(CopyArea, ImageLockMode.ReadWrite, CopyMe.PixelFormat);
        CopyMe.UnlockBits(copyData);
        IntoMe.UnlockBits(copyData);
    }
Edomite answered 7/2, 2013 at 14:58 Comment(8)
Forget the question, I don't understand the pun?!Saiz
@Saiz Normally if you're going to use LockBits, you'd follow it with unsafe code to directly access memory. I'm using LockBits and not doing unsafe code.Edomite
This is fairly typical, GDI+ has pretty lousy exception reporting in general and is apt to delay its revenge. Use Bitmap.Clone() instead.Pfeifer
@HansPassant I'm starting to realize that GDI+ problem... Can you suggest a method to quickly copy around 100 50x50 images into a 1600x1200 bitmap using Bitmap.Clone or other existing .NET function? The idea of this method was to do it without unsafe or external code.Edomite
Theoretically, this should be faster than even unsafe code since unsafe would require locking two images, copying pixel by pixel, then unlocking two images.Edomite
There already is such a method, written in unmanaged code with highly optimized direct access to the bitmap bits. It is called Graphics.DrawImage(). It is only slow when you force it to convert the pixel format or scale the image.Pfeifer
@HansPassant I just realized this question has no answer but it's been hit 109 times. Want to combine your comments to an answer and I'll accept? I ended up going with your suggestion of Graphics.DrawImage()Edomite
The other aspect to consider here is an exceptional case. What happens if your code throws an exception while one or more of the bitmaps is locked but before one or more are unlocked?Launcelot
K
3

Use Bitmap.Clone() instead. GDI+ tends to not report exceptions and the resulting bugs will be very hard to track.

A very fast way to copy images into a bitmap is with Graphics.DrawImage() as long as you don't convert pixel format or scale the image.

Kibitz answered 25/6, 2013 at 16:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.