Как написать метод расширения, который преобразует System.Drawing.Bitmap в массив байтов?

Как написать метод расширения, который преобразует System.Drawing.Bitmap в массив байтов? Почему бы и нет:

<Extension()> _
Public Function ToByteArray(ByVal image As System.Drawing.Bitmap) As Byte()
    Using ms = New MemoryStream()
        image.Save(ms, image.RawFormat)
        Return ms.ToArray()
    End Using
End Function

Тем не менее, когда я использую это, я получаю "System.Runtime.InteropServices.ExternalException: общая ошибка в GDI +", выведенная из операции Save(). Что я делаю неправильно?

Ответ 1

Как утверждает кто-то другой, это известная ошибка GDI +.

Однако обычно это появляется, когда вы закрыли исходный поток изображения, прежде чем полностью его прочитать. Просто загрузка нового объекта изображения будет загружать только метаданные, такие как ширина, высота, глубина цвета и т.д., А не фактические пиксели. Они будут лениво загружены позднее.

Этого можно избежать, копируя изображение (во время загрузки) в новое изображение, созданное в памяти. Я предполагаю, что входной поток все еще доступен в это время. После того, как вы приобретете новый класс изображений на основе памяти, вы можете свободно распоряжаться источником. (Другим решением было бы не закрывать/удалять исходный поток).

Изменить: Проблема описана в KB814675 зависимостях Bitmap и Image constructor вместе с обходным решением.

Создать неиндексированное изображение

Этот подход требует, чтобы новое изображение находилось в неиндексированном пиксельном формате (более 8 бит на пиксель), даже если исходное изображение было в индексированном формате. В этом обходном пути используется метод Graphics.DrawImage() для копирования изображения в новый объект Bitmap:

  • Построить исходное растровое изображение из потока, из памяти или из файла.
  • Создайте новый Bitmap того же размера, с пиксельным форматом более 8 бит на пиксель (BPP).
  • Используйте метод Graphics.FromImage() для получения объекта Graphics для второго растрового изображения.
  • Используйте Graphics.DrawImage(), чтобы нарисовать первый битмап на втором растровом изображении.
  • Используйте Graphics.Dispose() для удаления графики.
  • Используйте Bitmap.Dispose(), чтобы избавиться от первого растрового изображения.

Создать индексированное изображение

В этом обходном пути создается объект Bitmap в индексированном формате:

  • Построить исходное растровое изображение из потока, из памяти или из файла.
  • Создайте новый битмап с тем же размером и размером в пикселях, что и первый битмап.
  • Используйте метод Bitmap.LockBits() для блокировки всего изображения для обоих объектов Bitmap в собственном формате пикселей.
  • Используйте либо функцию Marshal.Copy, либо другую функцию копирования памяти, чтобы скопировать биты изображения из первого растрового изображения во второй битмап.
  • Используйте метод Bitmap.UnlockBits(), чтобы разблокировать оба объекта Bitmap.
  • Используйте Bitmap.Dispose(), чтобы избавиться от первого растрового изображения.

Ответ 2

Известная ошибка GDI +.

Вы не можете закрыть MemoryStream сразу.

Скопируйте выходной массив в другой массив байтов, затем закройте поток.

Ответ 3

Попробуйте изменить image.RawFormat на что-то вроде JPEG или PNG. Возможно, что некоторые изображения можно открывать с помощью растрового изображения, но не сохранять (по крайней мере, в исходном формате).

Ответ 4

Согласно этой теме, я использую следующий код, и он работает правильно:

using (var ms = new MemoryStream(bytes))
{
    using (var bitmap = new Bitmap(ms))
    {
        var bitmapCopy = new Bitmap(bitmap, bitmap.Width, bitmap.Height);
        return bitmapCopy;
    }
}