Bitmap.Clone()和新的Bitmap(Bitmap)有什么区别?

时间:2012-10-03 13:18:50

标签: c# bitmap clone

据我所知,有两种方法可以复制位图。

Bitmap.Clone()

Bitmap A = new Bitmap("somefile.png");
Bitmap B = (Bitmap)A.Clone();

新的位图()

Bitmap A = new Bitmap("somefile.png");
Bitmap B = new Bitmap(A);

这些方法有何不同?我对内存和线程方面的差异特别感兴趣。

2 个答案:

答案 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()的库方法的麻烦。

过载可能很有用,利用像素格式转换或裁剪选项。