What's the difference between Bitmap.Clone() and new Bitmap(Bitmap)?
Asked Answered
P

2

81

As far as I can tell, there are two ways of copying a bitmap.

Bitmap.Clone()

Bitmap A = new Bitmap("somefile.png");
Bitmap B = (Bitmap)A.Clone();

new Bitmap()

Bitmap A = new Bitmap("somefile.png");
Bitmap B = new Bitmap(A);

How do these approaches differ? I'm particularly interested in the difference in terms of memory and threading.

Prudy answered 3/10, 2012 at 13:18 Comment(1)
I had a case where the file I was reading was a 1 bit per pixel TIFF file. new Bitmap(A) returned a 32 bit per pixel bitmap, while (Bitmap)A.Clone() was still 1 bit per pixel. Since I was embedding the image in a PDF for later emailing, keeping the image at 1 bit was important. @Aelios @HansPassantApoenzyme
P
85

It is the common difference between a "deep" and a "shallow" copy, also an issue with the almost-deprecated IClonable interface. The Clone() method creates a new Bitmap object but the pixel data is shared with the original bitmap object. The Bitmap(Image) constructor also creates a new Bitmap object but one that has its own copy of the pixel data.

Lots of questions about Clone() at SO where the programmer hopes that it avoids the typical trouble with bitmaps, the lock on the file from which it was loaded. It doesn't. A possibly practical usage is avoiding trouble with a library method that inappropriately calls Dispose() on a passed bitmap.

The overloads may be useful, taking advantage of the pixel format conversion or the cropping options.

Physical answered 3/10, 2012 at 13:34 Comment(3)
Agreed. We used Clone() in the case where we need to use have the same Bitmap used (unmodified) in many places, but we wanted to cut down on the amount of memory used by the copies. One thing I don't know is if you modify one of the clones (i.e. SetPixel), if that causes all shared pixel data to be modify, or if it causes the modified one to allocate its own pixel data (thus just modifying its own).Manton
@MattSmith, the data will be copied after the lock command, even with ReandOnly flag.Navigable
Thank you so much. This has its own copy of the pixel data is why my program keeps crashing. This fixed it by using new Bitmap(oldBitmap) instead of oldBitmap.Clone()Ligroin
R
120

Reading the previous answers, I got worried that the pixel data would be shared between cloned instances of Bitmap. So I performed some tests to find out the differences between Bitmap.Clone() and new Bitmap().

Bitmap.Clone() keeps the original file locked:

  Bitmap original = new Bitmap("Test.jpg");
  Bitmap clone = (Bitmap) original.Clone();
  original.Dispose();
  File.Delete("Test.jpg"); // Will throw System.IO.IOException

Using new Bitmap(original) instead will unlock the file after original.Dispose(), and the exception will not be thrown. Using the Graphics class to modify the clone (created with .Clone()) will not modify the original:

  Bitmap original = new Bitmap("Test.jpg");
  Bitmap clone = (Bitmap) original.Clone();
  Graphics gfx = Graphics.FromImage(clone);
  gfx.Clear(Brushes.Magenta);
  Color c = original.GetPixel(0, 0); // Will not equal Magenta unless present in the original

Similarly, using the LockBits method yields different memory blocks for the original and clone:

  Bitmap original = new Bitmap("Test.jpg");
  Bitmap clone = (Bitmap) original.Clone();
  BitmapData odata = original.LockBits(new Rectangle(0, 0, original.Width, original.Height), ImageLockMode.ReadWrite, original.PixelFormat);
  BitmapData cdata = clone.LockBits(new Rectangle(0, 0, clone.Width, clone.Height), ImageLockMode.ReadWrite, clone.PixelFormat);
  Assert.AreNotEqual(odata.Scan0, cdata.Scan0);

The results are the same with both object ICloneable.Clone() and Bitmap Bitmap.Clone(Rectangle, PixelFormat).

Next, I tried some simple benchmarks using the following code.

Storing 50 copies in the list took 6.2 seconds and resulted in 1.7 GB memory usage (the original image is 24 bpp and 3456 x 2400 pixels = 25 MB):

  Bitmap original = new Bitmap("Test.jpg");
  long mem1 = Process.GetCurrentProcess().PrivateMemorySize64;
  Stopwatch timer = Stopwatch.StartNew();

  List<Bitmap> list = new List<Bitmap>();
  Random rnd = new Random();
  for(int i = 0; i < 50; i++)
  {
    list.Add(new Bitmap(original));
  }

  long mem2 = Process.GetCurrentProcess().PrivateMemorySize64;
  Debug.WriteLine("ElapsedMilliseconds: " + timer.ElapsedMilliseconds);
  Debug.WriteLine("PrivateMemorySize64: " + (mem2 - mem1));

Using Clone() instead I could store 1 000 000 copies in the list during 0.7 seconds and using 0.9 GB. As expected, Clone() is very light-weight in comparison to new Bitmap():

  for(int i = 0; i < 1000000; i++)
  {
    list.Add((Bitmap) original.Clone());
  }

Clones using the Clone() method are copy-on-write. Here I change one random pixel to a random color on the clone. This operation seems to trigger a copy of all pixel data from the original, because we're now back at 7.8 seconds and 1.6 GB:

  Random rnd = new Random();
  for(int i = 0; i < 50; i++)
  {
    Bitmap clone = (Bitmap) original.Clone();
    clone.SetPixel(rnd.Next(clone.Width), rnd.Next(clone.Height), Color.FromArgb(rnd.Next(0x1000000)));
    list.Add(clone);
  }

Just creating a Graphics object from the image will not trigger the copy:

  for(int i = 0; i < 50; i++)
  {
    Bitmap clone = (Bitmap) original.Clone();
    Graphics.FromImage(clone).Dispose();
    list.Add(clone);
  }

You have to draw something using the Graphics object in order to trigger the copy. Finally, using LockBits on the other hand, will copy the data even if ImageLockMode.ReadOnly is specified:

  for(int i = 0; i < 50; i++)
  {
    Bitmap clone = (Bitmap) original.Clone();
    BitmapData data = clone.LockBits(new Rectangle(0, 0, clone.Width, clone.Height), ImageLockMode.ReadOnly, clone.PixelFormat);
    clone.UnlockBits(data);
    list.Add(clone);
  }
Ruination answered 18/12, 2012 at 15:15 Comment(3)
So, which method is best to get a full separate copy of the image and all data?Francoisefrancolin
If you need a separate copy, I would use new Bitmap(). This will not hold the file lock on the original file and the cpu time and memory needed will be used at the place of copy, not at the place where you start modifying the copy. But if you are not sure whether the copy will be modified or not, .Clone() is probably a better option.Ruination
This last bit of the clone-lockbits-unlockbits was what enabled me to crop an image (via clone) and overwrite its original filename. Getting the original image via MemoryStream, using Marshal.Copy, using Graphics.FromImage and saving via a MemoryStream had all been recommended by various people, and had all failed (on Windows Server, running IIS7.5; but did not have an issue in VS).Augmentation
P
85

It is the common difference between a "deep" and a "shallow" copy, also an issue with the almost-deprecated IClonable interface. The Clone() method creates a new Bitmap object but the pixel data is shared with the original bitmap object. The Bitmap(Image) constructor also creates a new Bitmap object but one that has its own copy of the pixel data.

Lots of questions about Clone() at SO where the programmer hopes that it avoids the typical trouble with bitmaps, the lock on the file from which it was loaded. It doesn't. A possibly practical usage is avoiding trouble with a library method that inappropriately calls Dispose() on a passed bitmap.

The overloads may be useful, taking advantage of the pixel format conversion or the cropping options.

Physical answered 3/10, 2012 at 13:34 Comment(3)
Agreed. We used Clone() in the case where we need to use have the same Bitmap used (unmodified) in many places, but we wanted to cut down on the amount of memory used by the copies. One thing I don't know is if you modify one of the clones (i.e. SetPixel), if that causes all shared pixel data to be modify, or if it causes the modified one to allocate its own pixel data (thus just modifying its own).Manton
@MattSmith, the data will be copied after the lock command, even with ReandOnly flag.Navigable
Thank you so much. This has its own copy of the pixel data is why my program keeps crashing. This fixed it by using new Bitmap(oldBitmap) instead of oldBitmap.Clone()Ligroin

© 2022 - 2024 — McMap. All rights reserved.