Подтвердить что ты не робот

Какая разница между Bitmap.Clone() и новым растровым (Bitmap)?

Насколько я могу судить, существует два способа копирования растрового изображения.

Bitmap.Clone()

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

новый битмап()

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

Как эти подходы отличаются? Меня особенно интересует разница в памяти и потоке.

4b9b3361

Ответ 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);
  }