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);
}
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 @HansPassant – Apoenzyme