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

Autant que je sache, il existe deux manières 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); 

Comment ces approches diffèrent-elles? Je suis particulièrement intéressé par la différence en termes de mémoire et de threading.

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 object Bitmap, mais les données de pixels sont partagées avec l’object bitmap d’origine. Le constructeur Bitmap (Image) crée également un nouvel object Bitmap, mais qui possède sa propre copie des données de pixels.

Utiliser Clone () est très rarement utile. Beaucoup de questions à ce sujet à SO où le programmeur espère que Clone () évite le problème typique avec les bitmaps, le verrou sur le fichier à partir duquel il a été chargé. Ce n’est pas le cas. Utilisez uniquement Clone () lorsque vous transmettez une référence au code qui dispose du bitmap et que vous ne voulez pas perdre l’object.

En lisant les réponses précédentes, je me suis inquiété que les données de pixels soient partagées entre les instances clonées de Bitmap. J’ai donc effectué des tests pour découvrir les différences entre Bitmap.Clone() et la 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 

Utiliser le new Bitmap(original) 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 permet d’obtenir 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 l’ object ICloneable.Clone() et Bitmap Bitmap.Clone(Rectangle, PixelFormat) .

Ensuite, j’ai essayé quelques tests simples en utilisant le code suivant.

Le stockage de 50 copies dans la liste a pris 6,2 secondes et s’est traduit par 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 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)); 

En utilisant Clone() je pouvais stocker 1 000 000 copies dans la liste pendant 0,7 seconde et utiliser 0,9 Go. Comme prévu, Clone() est très léger par rapport à la 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 sums maintenant à 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 object Graphics partir de l'image ne déclenche 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 à l'aide de l'object Graphics afin de déclencher la copie. Enfin, en utilisant LockBits , les données seront ImageLockMode.ReadOnly 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); }