私の知る限り、ビットマップをコピーする方法は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);
これらのアプローチはどのように異なりますか?特にメモリとスレッドの違いに興味があります。
以前の回答を読んで、ピクセルデータがビットマップの複製されたインスタンス間で共有されるのではないかと心配しました。そこで、との違いを見つけるためにいくつかのテストを実行しまし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のメモリ使用量になりました(元のイメージは24bppおよび3456x2400ピクセル=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秒間に1 000 000のコピーをリストに保存でき、0.9GBを使用できます。予想通り、Clone()
と比較して非常に軽量ですnew Bitmap()
:
for(int i = 0; i < 1000000; i++)
{
list.Add((Bitmap) original.Clone());
}
このメソッドを使用するクローンは、Clone()
コピーオンライトです。ここでは、クローンの1つのランダムなピクセルをランダムな色に変更します。この操作により、元のピクセルデータからすべてのピクセルデータのコピーがトリガーされるようです。これは、7.8秒と1.6GBに戻ったためです。
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);
}
これは、「深い」コピーと「浅い」コピーの一般的な違いであり、ほとんど廃止されたIClonableインターフェイスの問題でもあります。Clone()メソッドは新しいビットマップオブジェクトを作成しますが、ピクセルデータは元のビットマップオブジェクトと共有されます。Bitmap(Image)コンストラクターも新しいビットマップオブジェクトを作成しますが、ピクセルデータの独自のコピーを持っています。
SOでのClone()に関する多くの質問。プログラマーは、ビットマップの一般的な問題、つまり、ロード元のファイルのロックを回避することを望んでいます。そうではありません。おそらく実用的な使用法は、渡されたビットマップでDispose()を不適切に呼び出すライブラリメソッドの問題を回避することです。
オーバーロードは、ピクセル形式の変換またはトリミングオプションを利用して役立つ場合があります。