Was ist der Unterschied zwischen Bitmap.Clone () und neuer Bitmap (Bitmap)?

73

Soweit ich das beurteilen kann, gibt es zwei Möglichkeiten, eine Bitmap zu kopieren.

Bitmap.Clone ()

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

neue Bitmap ()

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

Wie unterscheiden sich diese Ansätze? Ich interessiere mich besonders für den Unterschied in Bezug auf Speicher und Threading.

Tom Wright
quelle
3
Ich hatte einen Fall, in dem die Datei, die ich las, eine 1-Bit-TIFF-Datei pro Pixel war. new Bitmap(A)gab eine 32-Bit-Bitmap pro Pixel zurück, während (Bitmap)A.Clone()noch 1 Bit pro Pixel vorhanden war. Da ich das Bild für spätere E-Mails in eine PDF-Datei einbettete, war es wichtig, das Bild auf 1 Bit zu halten. @Aelios @HansPassant
gmlobdell

Antworten:

72

Es ist der gemeinsame Unterschied zwischen einer "tiefen" und einer "flachen" Kopie, auch ein Problem mit der fast veralteten IClonable-Schnittstelle. Die Clone () -Methode erstellt ein neues Bitmap-Objekt, aber die Pixeldaten werden mit dem ursprünglichen Bitmap-Objekt geteilt. Der Bitmap (Bild) -Konstruktor erstellt auch ein neues Bitmap-Objekt, das jedoch über eine eigene Kopie der Pixeldaten verfügt.

Viele Fragen zu Clone () bei SO, bei denen der Programmierer hofft, dass er die typischen Probleme mit Bitmaps vermeidet, die Sperre für die Datei, aus der sie geladen wurde. Das tut es nicht. Eine möglicherweise praktische Anwendung besteht darin, Probleme mit einer Bibliotheksmethode zu vermeiden, die Dispose () in einer übergebenen Bitmap unangemessen aufruft.

Die Überladungen können nützlich sein und die Pixelformatkonvertierung oder die Zuschneideoptionen nutzen.

Hans Passant
quelle
4
Einverstanden. Wir haben Clone () für den Fall verwendet, dass wir an vielen Stellen dieselbe Bitmap verwenden müssen (unverändert), aber wir wollten den von den Kopien verwendeten Speicherplatz reduzieren. Eine Sache, die ich nicht weiß, ist, ob Sie einen der Klone (dh SetPixel) ändern, ob dadurch alle gemeinsam genutzten Pixeldaten geändert werden oder ob der geänderte seine eigenen Pixeldaten zuweist (und somit nur seine eigenen ändert) ).
Matt Smith
@MattSmith, die Daten werden nach dem Sperrbefehl auch mit dem ReandOnly-Flag kopiert.
Pedro77
@HansPassant, mit " disposes " meinen Sie "ruft die .Dispose()Methode auf", wenn Sie Folgendes sagen: "Verwenden Sie Clone () nur, wenn Sie einen Verweis auf Code übergeben, der die Bitmap entsorgt, und Sie das Objekt nicht verlieren möchten. ""
Kdbanman
108

Beim Lesen der vorherigen Antworten machte ich mir Sorgen, dass die Pixeldaten von geklonten Instanzen von Bitmap gemeinsam genutzt werden könnten. Also habe ich einige Tests durchgeführt, um die Unterschiede zwischen Bitmap.Clone()und herauszufinden new Bitmap().

Bitmap.Clone() hält die Originaldatei gesperrt:

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

Wenn Sie new Bitmap(original)stattdessen verwenden, wird die Datei danach entsperrt original.Dispose(), und die Ausnahme wird nicht ausgelöst. Wenn Sie die GraphicsKlasse zum Ändern des Klons verwenden (erstellt mit .Clone()), wird das Original nicht geändert:

  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

In ähnlicher Weise LockBitsergibt die Verwendung der Methode unterschiedliche Speicherblöcke für das Original und den Klon:

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

Die Ergebnisse sind bei beiden object ICloneable.Clone()und gleich Bitmap Bitmap.Clone(Rectangle, PixelFormat).

Als nächstes habe ich einige einfache Benchmarks mit dem folgenden Code ausprobiert.

Das Speichern von 50 Kopien in der Liste dauerte 6,2 Sekunden und führte zu einer Speichernutzung von 1,7 GB (das Originalbild ist 24 bpp und 3456 x 2400 Pixel = 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));

Mit Clone()stattdessen könnte ich 1 000 000 Kopien in der Liste während 0,7 Sekunden und mit 0,9 GB speichern. Wie erwartet, Clone()ist sehr leicht im Vergleich zu new Bitmap():

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

Klone, die diese Clone()Methode verwenden, werden beim Schreiben kopiert. Hier ändere ich ein zufälliges Pixel in eine zufällige Farbe auf dem Klon. Dieser Vorgang scheint eine Kopie aller Pixeldaten des Originals auszulösen, da wir jetzt wieder bei 7,8 Sekunden und 1,6 GB sind:

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

Wenn Sie nur ein GraphicsObjekt aus dem Bild erstellen, wird die Kopie nicht ausgelöst:

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

Sie müssen mit dem GraphicsObjekt etwas zeichnen , um die Kopie auszulösen. Schließlich Verwendung LockBitsauf der anderen Seite, werden die Daten kopieren , selbst wenn ImageLockMode.ReadOnlyangegeben wird:

  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);
  }
Anlo
quelle
2
Welche Methode ist am besten geeignet, um eine vollständige separate Kopie des Bildes und aller Daten zu erhalten?
Don
Wenn Sie eine separate Kopie benötigen, würde ich eine neue Bitmap () verwenden. Dadurch wird die Dateisperre für die Originaldatei nicht beibehalten, und die benötigte CPU-Zeit und der benötigte Speicher werden am Ort der Kopie verwendet, nicht an dem Ort, an dem Sie mit dem Ändern der Kopie beginnen. Wenn Sie jedoch nicht sicher sind, ob die Kopie geändert wird oder nicht, ist .Clone () wahrscheinlich die bessere Option.
Anlo
2
Dieses letzte Stück der Clone-Lockbits-Unlockbits ermöglichte es mir, ein Bild (per Klon) zuzuschneiden und seinen ursprünglichen Dateinamen zu überschreiben. Das Abrufen des Originalbilds über MemoryStream, mit Marshal.Copy, mit Graphics.FromImage und das Speichern über einen MemoryStream wurden von verschiedenen Personen empfohlen und waren alle fehlgeschlagen (unter Windows Server unter IIS7.5; es gab jedoch kein Problem in VS).
Montag,