What's the difference between Bitmap.Clone() and new Bitmap(Bitmap)?
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);
}
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.