7

Consider this code for loading, modifying and saving a Bitmap image:

    using (Bitmap bmp = new Bitmap("C:\\test.jpg"))
    {
        bmp.RotateFlip(RotateFlipType.Rotate180FlipNone);
        bmp.Save("C:\\test.jpg");
    }

it runs without any exception. But consider this one:

    using (Bitmap bmp = new Bitmap("C:\\test.jpg"))
    {
        using (Bitmap bmpClone = (Bitmap)bmp.Clone())
        {
            //You can replace "bmpClone" in the following lines with "bmp",
            //exception occurs anyway                    
            bmpClone.RotateFlip(RotateFlipType.Rotate180FlipNone);
            bmpClone.Save("C:\\test.jpg");
        }
    }

It ends in an ExternalException with this message: "A generic error occurred in GDI+". What's wrong here? Any kind of lock on opened file? If so, why the first block works? What is the proper code for cloning a System.Drawing.Bitmap while we may need to edit main object or its clone in the memory and still have them both loaded in memory?

hamid reza
  • 416
  • 6
  • 14

4 Answers4

5

Yes, the file is locked when the first bitmap object is loaded and thus bmpClone.Save() to the same file fails because you have a logical deadlock.

When opening Bitmaps by filename, the file is locked throughout the life of the Bitmap. If you use a stream, the stream must remain open.

Update:

If you wish to have two bitmaps in memory for use outside of the scope of the method you are in, then you won't be using a using block.

Create the first image from file, and then clone it. Utilize them as needed throughout your UI lifecycle but make sure you clean them up using Dispose() when they are no longer needed so that underlying resources are released.

Also, from MSDN:

Saving the image to the same file it was constructed from is not allowed and throws an exception

That's pretty awkward. If the object created using clone() keeps information on the image source (e.g. a handle on the original file) or you can't otherwise unlock the file while using the Bitmap instances, then you'll probably have to either save to a new file, or open from a temporary copy of the original.

Try this instead:

// ... make a copy of test.jpg called test_temp.jpg

Bitmap bmpOriginal = new Bitmap("C:\\test_temp.jpg"))
Bitmap bmpClone = (Bitmap)bmp.Clone();

// ... do stuff          
bmpClone.RotateFlip(RotateFlipType.Rotate180FlipNone);

bmpClone.Save("C:\\test.jpg");

// ... cleanup
bmpOriginal.Dispose();
bmpClone.Dispose();
jscharf
  • 5,829
  • 3
  • 24
  • 16
  • OK! But what if we really do need to have two copies of this Bitmap in memory (Two picture boxes one showing an "original" image, and one showing "modified" one, whenever -anytime- our user might click on Save button)? – hamid reza Aug 23 '09 at 18:17
  • Create two clones then and keep one unmodified. – dtb Aug 23 '09 at 18:20
  • @dtb: Problem occurs on Save method, in the second block, "bmp" is still unmodified. One solution is: creating a new Bitmap with same width+height+PixelFormat of source and drawing the source on it (implement cloning yourself), but is this solution wise? What is the efficient, fast and standard solution? How cloning of a Bitmap can be useful when it might result in such an exception? – hamid reza Aug 23 '09 at 18:28
  • 1
    `new Bitmap(Bitmap)` does exactly that: creating a new Bitmap with same width+height+PixelFormat of source and drawing the source on it – dtb Aug 23 '09 at 18:30
  • 1
    @dtb: It cannot be , because drawing on a Bitmap canvas is not always an option for creating a copy of a Bitmap. For example when the source bitmap is an 8 bit indexed one which Graphic.FromImage is not allowed for it, drawing is not possible, while new Bitmap(Bitmap) is working and possible. – hamid reza Aug 24 '09 at 00:12
5

You could also load the bitmap without file locking using the simple workaround:

using (Stream s = File.OpenRead(@"\My Documents\My Pictures\Waterfall.jpg"))
Bitmap _backImage = (Bitmap)Bitmap.FromStream(s);
ptnik
  • 98
  • 1
  • 6
5

This is how I copy Bitmaps:

[DllImport("kernel32.dll", EntryPoint = "CopyMemory")]
static extern void CopyMemory(IntPtr Destination, IntPtr Source, uint Length);

public static Bitmap KernellDllCopyBitmap(Bitmap bmp, bool CopyPalette = true)
{
    Bitmap bmpDest = new Bitmap(bmp.Width, bmp.Height, bmp.PixelFormat);

    if (!KernellDllCopyBitmap(bmp, bmpDest, CopyPalette))
        bmpDest = null;

    return bmpDest;
}


/// <summary>
/// Copy bitmap data.
/// Note: bitmaps must have same size and pixel format.
/// </summary>
/// <param name="bmpSrc">Source Bitmap</param>
/// <param name="bmpDest">Destination Bitmap</param>
/// <param name="CopyPalette">Must copy Palette</param>
public static bool KernellDllCopyBitmap(Bitmap bmpSrc, Bitmap bmpDest, bool CopyPalette = false)
{
    bool copyOk = false;
    copyOk = CheckCompatibility(bmpSrc, bmpDest);
    if (copyOk)
    {
        BitmapData bmpDataSrc;
        BitmapData bmpDataDest;

        //Lock Bitmap to get BitmapData
        bmpDataSrc = bmpSrc.LockBits(new Rectangle(0, 0, bmpSrc.Width, bmpSrc.Height), ImageLockMode.ReadOnly, bmpSrc.PixelFormat);
        bmpDataDest = bmpDest.LockBits(new Rectangle(0, 0, bmpDest.Width, bmpDest.Height), ImageLockMode.WriteOnly, bmpDest.PixelFormat);
        int lenght = bmpDataSrc.Stride * bmpDataSrc.Height;

        CopyMemory(bmpDataDest.Scan0, bmpDataSrc.Scan0, (uint)lenght);

        bmpSrc.UnlockBits(bmpDataSrc);
        bmpDest.UnlockBits(bmpDataDest);

        if (CopyPalette && bmpSrc.Palette.Entries.Length > 0)
            bmpDest.Palette = bmpSrc.Palette;
    }
    return copyOk;
}

    public static bool CheckCompatibility(Bitmap bmp1, Bitmap bmp2)
    {
        return ((bmp1.Width == bmp2.Width) && (bmp1.Height == bmp2.Height) && (bmp1.PixelFormat == bmp2.PixelFormat));
    }

## ImageCopyBenchmark ##

Image Size: {Width=1024, Height=1024}.
Image PixelFormat: Format8bppIndexed.
Bitmap.Clone(): 0,00 ms (Not a DeepCopy... Same pixel data - look here)
Bitmap.Clone() + RotateFlip (to guet a deep copy): 2,02 ms
KernellDllCopyBitmap: 0,52 ms (THE BEST!)
MarshalCopyBitmap: 2,21 ms

Community
  • 1
  • 1
Pedro77
  • 5,176
  • 7
  • 61
  • 91
0

Here is my vanity method:

    private static unsafe Bitmap DuplicateBitmap(Bitmap inputBitmap)
    {
        byte[] buffer = new byte[inputBitmap.Height * inputBitmap.Width *
                            Image.GetPixelFormatSize(inputBitmap.PixelFormat) / 8];  

        fixed (byte* p = buffer)
        {
            BitmapData b1Data = new BitmapData()
            {
                Scan0 = (IntPtr)p,
                Height = inputBitmap.Height,
                Width = inputBitmap.Width,
                PixelFormat = inputBitmap.PixelFormat,
                Stride = inputBitmap.Width * Image.GetPixelFormatSize(inputBitmap.PixelFormat) / 8,
            }; 

            inputBitmap.LockBits(new Rectangle(Point.Empty, inputBitmap.Size),
                ImageLockMode.ReadOnly | ImageLockMode.UserInputBuffer, inputBitmap.PixelFormat, b1Data); // copy out.   

            Bitmap b2 = new Bitmap(b1Data.Width, b1Data.Height, b1Data.Stride, inputBitmap.PixelFormat, b1Data.Scan0);

            inputBitmap.UnlockBits(b1Data); 

            return b2;
        }
    }

10% faster (depending on bitmap size...)

dduster
  • 1
  • 2