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

Tom*_*ght 70 c# clone bitmap

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

Bitmap.Clone()

Bitmap A = new Bitmap("somefile.png");
Bitmap B = (Bitmap)A.Clone();
Run Code Online (Sandbox Code Playgroud)

新的位图()

Bitmap A = new Bitmap("somefile.png");
Bitmap B = new Bitmap(A);
Run Code Online (Sandbox Code Playgroud)

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

Anl*_*nlo 106

阅读之前的答案,我担心像素数据将在克隆的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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

同样,使用该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);
Run Code Online (Sandbox Code Playgroud)

结果是既同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));
Run Code Online (Sandbox Code Playgroud)

使用Clone(),而不是我可以存储在0.7秒列表中的1 000万份,并使用0.9 GB.正如所料,Clone()与以下相比,重量非常轻new Bitmap():

  for(int i = 0; i < 1000000; i++)
  {
    list.Add((Bitmap) original.Clone());
  }
Run Code Online (Sandbox Code Playgroud)

使用该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);
  }
Run Code Online (Sandbox Code Playgroud)

Graphics仅从图像创建对象不会触发副本:

  for(int i = 0; i < 50; i++)
  {
    Bitmap clone = (Bitmap) original.Clone();
    Graphics.FromImage(clone).Dispose();
    list.Add(clone);
  }
Run Code Online (Sandbox Code Playgroud)

您必须使用该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);
  }
Run Code Online (Sandbox Code Playgroud)

  • 那么,哪种方法最适合获得图像和所有数据的完整单独副本? (2认同)
  • clone-lockbits-unlockbits的最后一位使我能够裁剪图像(通过克隆)并覆盖其原始文件名.通过MemoryStream获取原始图像,使用Marshal.Copy,使用Graphics.FromImage并通过MemoryStream保存已经被各种人推荐,并且都失败了(在Windows Server上运行IIS7.5;但是没有问题) VS). (2认同)

Han*_*ant 69

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

使用克隆()是非常罕见有用.关于它的很多问题,程序员希望Clone()避免使用位图的典型问题,锁定加载它的文件.它没有.当您传递对处理位图的代码的引用并且您不想丢失对象时,仅使用Clone().

  • 同意.我们在需要使用的情况下使用Clone()在许多地方使用相同的Bitmap(未修改),但我们希望减少副本使用的内存量.我不知道的一件事是你修改了一个克隆(即SetPixel),如果这会导致所有共享像素数据被修改,或者它是否导致修改后的像素数据分配它自己的像素数据(因此只是修改它自己的). (4认同)