Qual é a diferença entre Bitmap.Clone () e novo Bitmap (Bitmap)?

Tanto quanto eu posso dizer, existem duas maneiras de copiar um bitmap.

Bitmap.Clone ()

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

novo bitmap ()

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

Como essas abordagens diferem? Estou particularmente interessado na diferença em termos de memory e segmentação.

É a diferença comum entre uma cópia “profunda” e uma “superficial”, também um problema com a interface IClonable quase obsoleta. O método Clone () cria um novo object Bitmap, mas os dados de pixel são compartilhados com o object de bitmap original. O construtor Bitmap (Imagem) também cria um novo object Bitmap, mas um que possui sua própria cópia dos dados de pixel.

Usando Clone () é muito raramente útil. Muitas perguntas sobre isso no SO, onde o programador espera que Clone () evite o problema típico com bitmaps, o bloqueio no arquivo do qual foi carregado. Não faz. Só use Clone () quando você passar uma referência ao código que descarta o bitmap e você não quer perder o object.

Lendo as respostas anteriores, fiquei preocupado que os dados de pixel fossem compartilhados entre instâncias clonadas de Bitmap. Então Bitmap.Clone() alguns testes para descobrir as diferenças entre o Bitmap.Clone() e o new Bitmap() .

Bitmap.Clone() mantém o arquivo original bloqueado:

  Bitmap original = new Bitmap("Test.jpg"); Bitmap clone = (Bitmap) original.Clone(); original.Dispose(); File.Delete("Test.jpg"); // Will throw System.IO.IOException 

Usando o new Bitmap(original) vez disso, irá desbloquear o arquivo após original.Dispose() , e a exceção não será lançada. Usando a class Graphics para modificar o clone (criado com .Clone() ) não irá modificar o original:

  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 

Da mesma forma, usando o método LockBits produz diferentes blocos de memory para o original e clone:

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

Os resultados são os mesmos com o object ICloneable.Clone() e Bitmap Bitmap.Clone(Rectangle, PixelFormat) .

Em seguida, tentei alguns benchmarks simples usando o seguinte código.

Armazenar 50 cópias na lista levou 6,2 segundos e resultou em 1,7 GB de uso de memory (a imagem original é 24 bpp e 3456 x 2400 pixels = 25 MB):

  Bitmap original = new Bitmap("Test.jpg"); long mem1 = Process.GetCurrentProcess().PrivateMemorySize64; Stopwatch timer = Stopwatch.StartNew(); List list = new List(); 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)); 

Usando Clone() vez disso, eu poderia armazenar 1 000 000 cópias na lista durante 0,7 segundos e usando 0,9 GB. Como esperado, o Clone() é muito leve em comparação com o new Bitmap() :

  for(int i = 0; i < 1000000; i++) { list.Add((Bitmap) original.Clone()); } 

Clones usando o método Clone() são copy-on-write. Aqui eu mudo um pixel random para uma cor aleatória no clone. Essa operação parece acionar uma cópia de todos os dados de pixel do original, porque agora estamos de volta a 7,8 segundos e 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); } 

Apenas criar um object Graphics partir da imagem não acionará a cópia:

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

Você tem que desenhar algo usando o object Graphics para acionar a cópia. Por fim, usando LockBits por outro lado, copiará os dados mesmo se ImageLockMode.ReadOnly for especificado:

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