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


73

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

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);

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


3
我遇到的情况是,我正在读取的文件是每个像素TIFF文件1位。 new Bitmap(A)返回每个像素32位的位图,而(Bitmap)A.Clone()每个像素仍为1位。由于我将图像嵌入PDF以便以后通过电子邮件发送,因此将图像保持在1位很重要。@Aelios @HansPassant
gmlobdell

Answers:


72

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

SO上有很多关于Clone()的问题,程序员希望它避免位图的典型麻烦,即从中加载文件的锁。没有。可能的实际用法是避免使用库方法的麻烦,该库方法在传递的位图上不适当地调用Dispose()。

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


4
同意 在许多地方需要使用相同的位图(未修改)的情况下,我们使用了Clone(),但是我们想减少副本使用的内存量。我不知道的一件事是是否修改了其中一个克隆(即SetPixel),是否导致所有共享像素数据都被修改,或者是否导致修改后的对象分配了自己的像素数据(因此仅修改了自己的像素数据) )。
马特·史密斯

@MattSmith,即使使用ReandOnly标志,也会在锁定命令后复制数据。
Pedro77

@HansPassant,通过“成事”,你的意思是,“调用.Dispose()方法”时,你这样说:“只有使用的clone()当您传递的引用代码,成事在该位图,你不想失去的对象。 ”
kdbanman 2015年

109

阅读以前的答案,我担心像素数据将在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);
  }

2
那么,哪种方法最好获得图像和所有数据的完整单独副本?

如果您需要单独的副本,则可以使用新的Bitmap()。这不会将文件锁定保持在原始文件上,所需的cpu时间和内存将用于复制的位置,而不是用于您开始修改副本的位置。但是,如果不确定是否要修改副本,则.Clone()可能是一个更好的选择。
Anlo

2
clone-lockbits-unlockbits的最后这一点使我能够裁剪图像(通过克隆)并覆盖其原始文件名。许多人都建议通过MemoryStream,使用Marshal.Copy,使用Graphics.FromImage并通过MemoryStream保存原始图像,并且所有操作都失败了(在运行IIS7.5的Windows Server上,但没有问题) VS)。
2016年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.