WriteableBitmap

31

WPF позволяет отображать растровые изображения с помощью элемента Image. Однако подобное отображение — дорога с односторонним движением. Приложение берет готовое растровое изображение, читает его и отображает в окне. Сам по себе элемент Image не позволяет ни создавать, ни редактировать информацию растрового изображения.

Здесь на помощь приходит WriteableBitmap. Этот класс унаследован от BitmapSource — класса, который используется при установке свойства Image.Source (либо напрямую, во время установки графического изображения в коде, либо неявно, когда это делается в XAML-разметке). В то время как BitmapSource является доступным только для чтения отражением данных растрового изображения, объект WriteableBitmap — это модифицируемый массив пикселей, который открывает перед разработчиком множество интересных возможностей.

Важно понять, что WriteableBitmap — не лучший способ рисования графического приложения в большинстве приложений. Если нужна низкоуровневая альтернатива системе элементов WPF, следует начать с того, что демонстрировал класс Visual.

Например, класс Visual — блестящий инструмент для создания инструмента построения диаграмм или игры с анимацией. Класс WriteableBitmap больше подходит для приложений, в которых нужна манипуляция отдельными пикселями, например, генератору фракталов, акустическому анализатору, инструменту визуализации научных данных либо приложению, которое обрабатывает низкоуровневые графические данные, полученные от внешнего аппаратного устройства (вроде веб-камеры). Хотя WriteableBitmap обеспечивает тонкий контроль, это довольно сложный класс, который требует написания большего объема кода, чем другие подходы.

Генерация растрового изображения

Чтобы сгенерировать растровое изображение с помощью WriteableBitmap, необходимо предоставить несколько ключевых фрагментов информации: ширину и высоту в пикселях, разрешение DPI по обоим измерениям, а также формат графического изображения.

Ниже приведен пример создания графического изображения размером с текущее окно:

WriteableBitmap wb = new WriteableBitmap((int)this.ActualWidth,
   (int)this.ActualHeight, 96, 96, PixelFormats.Bgra32, null);

Перечисление PixelFormats включает в себя длинный список пиксельных форматов, но только около половины из них являются записываемыми форматами и поддерживаются классом WriteableBitmap. Ниже описаны значения, которые можно использовать:

Bgra32

Этот формат (выбранный в текущем примере) использует 32-битный цвет sRGB. Это значит, что каждый пиксель представлен 32 битами, или 4 байтами. Первый байт представляет уровень синего канала (в виде числа от 0 до 256), второй байт — уровень зеленого канала, третий — красного, а четвертый — альфазначение (где 0 означает полную прозрачность, а 255 — полную непрозрачность). Порядок цветов (blue, green, red, alpha — синий, зеленый, красный, альфа) соответствует буквам в имени Bgra32.

Bgr32

Этот формат использует 4 байта на пиксель, как и Bgra32. Отличие состоит в том, что канал альфа игнорируется. Данный формат можно применять, когда не требуется прозрачность.

Pbgra32

Этот формат использует 4 байта на пиксель, как и Bgra32. Отличие связано со способом обработки полупрозрачных пикселей. Для того чтобы оптимизировать производительность вычислений прозрачности, каждый байт цвета умножен в обратном порядке (premultiplied; отсюда Р в Pbgra32). Это значит, что каждый байт цвета умножен на значение альфа и разделен на 255. Поэтому частично прозрачный пиксель, имеющий значения В, G, R, А (255, 100, 0, 200) в Bgra32, будет представлен в Pbgra32 как (200, 78, 0, 200).

BlackWhite, Gray2, Gray4, Gray8

Это форматы черно-белого отображения и отображения с оттенками серого. Число, следующее за Gray, соответствует количеству битов на пиксель. Таким образом, эти форматы компактны, но не поддерживают цвета.

Indexed1, Indexed2, Indexed4, Indexed8

Это индексированные форматы, т.е. каждый пиксель указывает на значение в палитре цвета. При использовании одного из этих форматов в качестве последнего аргумента конструктора WriteableBitmap должен передаваться соответствующий объект ColorPalette. Число, следующее за Indexed, соответствует количеству битов на пиксель. Индексированные форматы компактны, но несколько сложнее в работе, и поддерживают меньше цветов — 2, 14, 16 или 256, соответственно.

Первые три формата — Bgra32, Bgr32 и Pbgra32 — используются чаще других.

Запись в WriteableBitmap

После создания объект WriteableBitmap имеет нулевые значения во всех байтах. По сути, это — большой черный прямоугольник.

Для заполнения WriteableBitmap содержимым используется метод WritePixels(), который копирует массив байтов в указанную позицию растрового изображения. Метод WritePixels() можно вызывать для установки одиночного пикселя, всего растрового изображения либо выбранной прямоугольной области. Для извлечения пикселей из WriteableBitmap предназначен метод CopyPixels(), который передает нужные байты в байтовый массив. Методы WritePixels() и CopyPixels() в сочетании не обеспечивают удобную программную модель, но это та цена, которую приходится платить за возможность низкоуровневого доступа к пикселям.

Для успешного использования WritePixels() необходимо понимать формат изображения и то, как в нем кодируются пиксели в байты. Например, в 32-разрядном типе растрового изображения Bgra32 каждый пиксель требует 4 байта — по одному для синего, зеленого, красного и альфа компонентов. Ниже показано, как установить их вручную и затем передать в массив:

byte blue =100;
byte green = 50;
byte red = 50;
byte alpha = 255;
byte[] colorData = {blue, green, red, alpha};

Обратите внимание, что здесь важен порядок. В байтовом массиве должна выдерживаться последовательность синей, зеленой, красной и альфа-составляющей, согласно стандарту Bgra32.

При вызове методу WritePixels() передается экземпляр Int32Rect, указывающий прямоугольную область растрового изображения, которую требуется обновить. Int32Rect включает в себя четыре фрагмента информации: координаты X и Y верхнего левого угла обновляемой области, а также ее ширину и высоту.

В следующем коде принимается массив colorData, показанный в предыдущем коде, который затем используется для установки первого пикселя в WriteableBitmap:

// Обновить одиночный пиксель. Область начинается с (0,0)
//и имеет размер в 1 пиксель в ширину и 1 пиксель в высоту.
Int32Rect rect = new Int32Rect(0, 0, 1, 1);
// Записать 4 байта из массива в растровое изображение.
wb.WritePixels(rect, colorData, 4, 0);

С помощью такого подхода можно создать код процедуры, которая генерирует WriteableBitmap. Понадобится лишь пройти в цикле по всем столбцам и строкам изображения, обновляя по одному пикселю за каждую итерацию.

Пройди тесты
Лучший чат для C# программистов