据我所知,复制位图有两种方法。
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);
这些方法有什么不同?我特别关注于内存和线程方面的区别。
阅读之前的答案,我担心像素数据会在位图的克隆实例之间共享。因此,我进行了一些测试,以查明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()
之后解锁文件,并且不会抛出异常。使用.Clone()
创建的克隆体来使用Graphics
类进行修改时,不会修改原始对象:
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)
所得到的结果是相同的。 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秒内使用0.9GB存储列表中的1,000,000个副本。如预期的那样,与new Bitmap()
相比,Clone()
非常轻量级:
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);
}
"深拷贝"和"浅拷贝"之间的常见区别,也是几乎过时的IClonable接口的问题。Clone()方法创建一个新的Bitmap对象,但像素数据与原始位图对象共享。Bitmap(Image)构造函数也创建一个新的Bitmap对象,但它具有自己的像素数据副本。
在SO上有许多关于Clone()的问题,程序员希望它避免位图的典型问题,即从中加载的文件被锁定。 但它并不会避免这个问题。可能实用的用法是避免库方法不适当地调用Dispose()来释放传递的位图。
重载可能很有用,可以利用像素格式转换或裁剪选项。
new Bitmap(A)
返回32比特每像素的位图,而(Bitmap)A.Clone()
仍然是每像素1比特。由于我要将图像嵌入PDF以便以后发送电子邮件,因此保持图像为1比特很重要。@Aelios @HansPassant - gmlobdell