【问题标题】:What's the difference between Bitmap.Clone() and new Bitmap(Bitmap)?Bitmap.Clone() 和 new Bitmap(Bitmap) 有什么区别?
【发布时间】:2012-09-24 10:22:24
【问题描述】:

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

Bitmap.Clone()

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

新位图()

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

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

【问题讨论】:

  • 我有一个案例,我正在读取的文件是每像素 1 位的 TIFF 文件。 new Bitmap(A) 返回一个每像素 32 位的位图,而(Bitmap)A.Clone() 仍然是每像素 1 位。由于我将图像嵌入 PDF 以供以后通过电子邮件发送,因此将图像保持在 1 位很重要。 @Aelios @HansPassant

标签: c# bitmap clone


【解决方案1】:

这是“深”和“浅”副本之间的共同区别,也是几乎不推荐使用的 IClonable 接口的问题。 Clone() 方法创建一个新的位图对象,但像素数据与原始位图对象共享。 Bitmap(Image) 构造函数还创建一个新的 Bitmap 对象,但它拥有自己的像素数据副本。

很多关于 Clone() 在 SO 的问题,程序员希望它避免位图的典型问题,即加载它的文件上的锁定。它没有。一个可能的实际用法是避免在传递的位图上不恰当地调用 Dispose() 的库方法出现问题。

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

【讨论】:

  • 同意。我们在需要在许多地方使用(未修改)相同的位图的情况下使用了 Clone(),但我们希望减少副本使用的内存量。我不知道的一件事是,如果您修改其中一个克隆(即 SetPixel),是否会导致修改所有共享像素数据,或者是否会导致修改后的克隆分配自己的像素数据(因此只是修改自己的)。
  • @MattSmith,数据将在 lock 命令后被复制,即使带有 ReandOnly 标志。
【解决方案2】:

阅读前面的答案,我担心像素数据会在 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 秒内使用 0.9 GB 在列表中存储 1 000 000 个副本。正如预期的那样,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()。这不会对原始文​​件进行文件锁定,并且所需的 cpu 时间和内存将在复制的地方使用,而不是在您开始修改副本的地方使用。但是如果你不确定副本是否会被修改,.Clone() 可能是一个更好的选择。
  • clone-lockbits-unlockbits 的最后一位使我能够裁剪图像(通过克隆)并覆盖其原始文件名。通过 MemoryStream 获取原始图像,使用 Marshal.Copy,使用 Graphics.FromImage 和通过 MemoryStream 保存都被各种人推荐,并且都失败了(在 Windows Server 上,运行 IIS7.5;但在VS)。
猜你喜欢
  • 2019-09-21
  • 1970-01-01
  • 1970-01-01
  • 2015-11-19
  • 2011-10-18
  • 1970-01-01
  • 1970-01-01
  • 2015-04-14
  • 2010-09-29
相关资源
最近更新 更多