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