web-dev-qa-db-ja.com

Bitmap.Clone()と新しいBitmap(Bitmap)の違いは何ですか?

私が知る限り、ビットマップをコピーする方法は2つあります。

Bitmap.Clone()

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

新しいBitmap()

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

これらのアプローチはどのように異なりますか?私は特にメモリとスレッドの点で違いに興味があります。

68
Tom Wright

これは、「ディープ」コピーと「シャロー」コピーの一般的な違いです。これも、ほぼ廃止されたIClonableインターフェースの問題です。 Clone()メソッドは新しいBitmapオブジェクトを作成しますが、ピクセルデータは元のビットマップオブジェクトと共有されます。 Bitmap(Image)コンストラクターも新しいBitmapオブジェクトを作成しますが、ピクセルデータの独自のコピーを持っています。

SO)でのClone()に関する多くの質問。プログラマーは、ビットマップ、ロード元のファイルのロックに関する一般的な問題を回避したいと考えていますが、そうではありません。使用法は、渡されたビットマップでDispose()を不適切に呼び出すライブラリメソッドの問題を回避しています。

オーバーロードは、ピクセル形式の変換またはトリミングオプションを利用して、役に立つ場合があります。

67
Hans Passant

以前の回答を読んで、ビットマップのクローンインスタンス間でピクセルデータが共有されるのではないかと心配しました。そこで、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
_

代わりに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
_

同様に、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);
_

結果は、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));
_

代わりにClone()を使用すると、0.7秒で0.9 GBを使用して1 000 000コピーをリストに保存できます。予想どおり、Clone()new Bitmap()と比較して非常に軽量です。

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

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

画像からGraphicsオブジェクトを作成するだけでは、コピーはトリガーされません。

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

コピーをトリガーするには、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);
  }
_
104
Anlo