Насколько я могу судить, существует два способа копирования растрового изображения.
Как эти подходы отличаются? Меня особенно интересует разница в памяти и потоке.
Ответ 1
Это обычная разница между "глубокой" и "мелкой" копией, а также проблемой с почти устаревшим интерфейсом IClonable. Метод Clone() создает новый объект Bitmap, но данные пикселов совместно используются с исходным растровым объектом. Конструктор Bitmap (Image) также создает новый объект Bitmap, но тот, у которого есть своя копия данных пикселя.
Использование Clone() очень редко полезно. Много вопросов об этом в SO, где программист надеется, что Clone() избежит типичной проблемы с растровыми изображениями, блокировкой файла, из которого он был загружен. Это не так. Используйте только Clone(), когда вы передаете ссылку на код, который размещает растровое изображение, и вы не хотите потерять объект.
Ответ 2
Прочитав предыдущие ответы, я был обеспокоен тем, что данные пикселов будут разделяться между клонированными экземплярами Bitmap. Поэтому я провел несколько тестов, чтобы узнать различия между 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 ГБ (исходное изображение составляет 24 бит/с и 3456 х 2400 пикселей = 25 МБ):
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()
, вместо этого я мог хранить 1 000 000 копий в списке в течение 0,7 секунды и использовать 0,9 ГБ. Как и ожидалось, Clone()
является очень легким по сравнению с new Bitmap()
:
for(int i = 0; i < 1000000; i++)
{
list.Add((Bitmap) original.Clone());
}
Клоны, использующие метод Clone()
, - это copy-on-write. Здесь я меняю один случайный пиксель на случайный цвет на клоне. Эта операция, похоже, вызывает копирование всех пиксельных данных из оригинала, потому что мы вернулись на 7,8 секунды и 1,6 ГБ:
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);
}