据我所知,复制位图有两种方法。
位图.Clone()
Bitmap A = new Bitmap("somefile.png");
Bitmap B = (Bitmap)A.Clone();
新位图()
Bitmap A = new Bitmap("somefile.png");
Bitmap B = new Bitmap(A);
这些方法有何不同?我对内存和线程方面的差异特别感兴趣。
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);
}
这是“深”和“浅”副本之间的共同区别,也是几乎不推荐使用的 IClonable 接口的问题。Clone() 方法创建一个新的位图对象,但像素数据与原始位图对象共享。Bitmap(Image) 构造函数还创建了一个新的 Bitmap 对象,但它拥有自己的像素数据副本。
SO上有很多关于Clone()的问题,程序员希望它避免位图的典型问题,即加载它的文件的锁定。它没有。一个可能的实际用法是避免在传递的位图上不恰当地调用 Dispose() 的库方法出现问题。
利用像素格式转换或裁剪选项,重载可能很有用。