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

Tom Wright picture Tom Wright · Oct 3, 2012 · Viewed 50.9k times · Source

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.

Answer

Anlo picture Anlo · Dec 18, 2012

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