web-dev-qa-db-fra.com

Quelle est la différence entre Bitmap.Clone () et le nouveau Bitmap (Bitmap)?

Pour autant que je sache, il existe deux façons de copier un bitmap.

Bitmap.Clone ()

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

nouveau Bitmap ()

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

En quoi ces approches diffèrent-elles? Je m'intéresse particulièrement à la différence en termes de mémoire et de threading.

68
Tom Wright

C'est la différence commune entre une copie "profonde" et une copie "superficielle", également un problème avec l'interface IClonable presque obsolète. La méthode Clone () crée un nouvel objet Bitmap mais les données de pixels sont partagées avec l'objet bitmap d'origine. Le constructeur Bitmap (Image) crée également un nouvel objet Bitmap mais qui a sa propre copie des données de pixels.

Beaucoup de questions sur Clone () à SO où le programmeur espère qu'il évitera les problèmes typiques avec les bitmaps, le verrou sur le fichier à partir duquel il a été chargé. Il ne le fait pas. Un peut-être pratique l'utilisation évite les problèmes avec une méthode de bibliothèque qui appelle de manière inappropriée Dispose () sur un bitmap passé.

Les surcharges peuvent être utiles, profitant de la conversion de format de pixel ou des options de recadrage.

67
Hans Passant

En lisant les réponses précédentes, je craignais que les données de pixels soient partagées entre des instances clonées de Bitmap. J'ai donc effectué quelques tests pour découvrir les différences entre Bitmap.Clone() et new Bitmap().

Bitmap.Clone() conserve le fichier d'origine verrouillé:

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

L'utilisation de new Bitmap(original) à la place déverrouillera le fichier après original.Dispose(), et l'exception ne sera pas levée. L'utilisation de la classe Graphics pour modifier le clone (créé avec .Clone()) ne modifiera pas l'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

De même, l'utilisation de la méthode LockBits génère différents blocs de mémoire pour l'original et le 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);

Les résultats sont les mêmes avec object ICloneable.Clone() et Bitmap Bitmap.Clone(Rectangle, PixelFormat).

Ensuite, j'ai essayé quelques benchmarks simples en utilisant le code suivant.

Le stockage de 50 copies dans la liste a pris 6,2 secondes et a entraîné une utilisation de la mémoire de 1,7 Go (l'image d'origine est de 24 bpp et 3456 x 2400 pixels = 25 Mo):

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

En utilisant Clone() à la place, j'ai pu stocker 1 000 000 d'exemplaires dans la liste pendant 0,7 seconde et en utilisant 0,9 Go. Comme prévu, Clone() est très léger par rapport à new Bitmap():

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

Les clones utilisant la méthode Clone() sont copiés sur écriture. Ici, je change un pixel aléatoire en une couleur aléatoire sur le clone. Cette opération semble déclencher une copie de toutes les données de pixels de l'original, car nous sommes maintenant de retour à 7,8 secondes et 1,6 Go:

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

La simple création d'un objet Graphics à partir de l'image ne déclenchera pas la copie:

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

Vous devez dessiner quelque chose en utilisant l'objet Graphics afin de déclencher la copie. Enfin, utiliser LockBits d'autre part, copiera les données même si ImageLockMode.ReadOnly Est spécifié:

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