据我所知,有两种复制位图的方法。
Bitmap.Clone()
Bitmap A = new Bitmap("somefile.png");
Bitmap B = (Bitmap)A.Clone();
新的Bitmap()
Bitmap A = new Bitmap("somefile.png");
Bitmap B = new Bitmap(A);
这些方法有何不同?我对内存和线程方面的差异特别感兴趣。
Answers:
这是“深”副本和“浅”副本之间的常见区别,这也是几乎不建议使用的IClonable接口的问题。Clone()方法创建一个新的Bitmap对象,但像素数据与原始位图对象共享。Bitmap(Image)构造函数还会创建一个新的Bitmap对象,但是该对象具有自己的像素数据副本。
SO上有很多关于Clone()的问题,程序员希望它避免位图的典型麻烦,即从中加载文件的锁。没有。可能的实际用法是避免使用库方法的麻烦,该库方法在传递的位图上不适当地调用Dispose()。
利用像素格式转换或裁剪选项的优势,重载可能会很有用。
.Dispose()
方法”时,你这样说:“只有使用的clone()当您传递的引用代码,成事在该位图,你不想失去的对象。 ”
阅读以前的答案,我担心像素数据将在Bitmap的克隆实例之间共享。因此,我进行了一些测试以找出Bitmap.Clone()
和之间的区别new Bitmap()
。
Bitmap.Clone()
保持原始文件锁定:
Bitmap original = new Bitmap("Test.jpg");
Bitmap clone = (Bitmap) original.Clone();
original.Dispose();
File.Delete("Test.jpg"); // Will throw System.IO.IOException
使用new Bitmap(original)
代替将在之后的文件解锁original.Dispose()
,并且不会引发异常。使用Graphics
该类修改克隆(使用创建.Clone()
)不会修改原始副本:
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
同样,使用该LockBits
方法将为原始副本和克隆生成不同的存储块:
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);
结果是既同object ICloneable.Clone()
和Bitmap Bitmap.Clone(Rectangle, PixelFormat)
。
接下来,我使用以下代码尝试了一些简单的基准测试。
在列表中存储50个副本花费了6.2秒,导致1.7 GB内存使用(原始图像为24 bpp和3456 x 2400像素= 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));
使用Clone()
,而不是我可以存储在0.7秒列表中的1 000万份,并使用0.9 GB。与预期的相比,Clone()
它的重量非常轻new Bitmap()
:
for(int i = 0; i < 1000000; i++)
{
list.Add((Bitmap) original.Clone());
}
使用该Clone()
方法的克隆是写时复制。在这里,我将克隆上的一个随机像素更改为一种随机颜色。该操作似乎触发了原始图像中所有像素数据的复制,因为我们现在回到了7.8秒和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);
}
Graphics
仅从图像创建对象不会触发复制:
for(int i = 0; i < 50; i++)
{
Bitmap clone = (Bitmap) original.Clone();
Graphics.FromImage(clone).Dispose();
list.Add(clone);
}
您必须使用该Graphics
对象绘制一些东西才能触发副本。最后,使用LockBits
,即使ImageLockMode.ReadOnly
指定,也将复制数据:
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)
返回每个像素32位的位图,而(Bitmap)A.Clone()
每个像素仍为1位。由于我将图像嵌入PDF以便以后通过电子邮件发送,因此将图像保持在1位很重要。@Aelios @HansPassant