Существует достаточно распространенная задача, когда окно приложения или его часть необходимо сохранить как изображение. В WPF есть класс, который очень упрощает ее решение – RenderTargetBitmap. Он позволяет получить изображение любого элемента управления WPF (включая его дочерние элементы) в растровом формате. Рассмотрим пример использования данного класса и некоторые особенности.
Сразу перейдем к коду примера.
/// <summary>Renders the Visual object and store it to file.</summary>
/// <param name="baseElement">The Visual object to be used as a bitmap. </param>
/// <param name="imageWidth">The height of the bitmap.</param>
/// <param name="imageHeight">The width of the bitmap.</param>
/// <param name="pathToOutputFile">Full path to the output file.</param>
private void SaveControlImage(
Visual baseElement, int imageWidth, int imageHeight, string pathToOutputFile)
{
// 1) get current dpi
var pSource = PresentationSource.FromVisual(Application.Current.MainWindow);
Matrix m = pSource.CompositionTarget.TransformToDevice;
double dpiX = m.M11 * 96;
double dpiY = m.M22 * 96;
// 2) create RenderTargetBitmap
var elementBitmap = new RenderTargetBitmap(imageWidth, imageHeight, dpiX, dpiY, PixelFormats.Default);
// 3) undo element transformation
var drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen()) {
var visualBrush = new VisualBrush(baseElement);
drawingContext.DrawRectangle(
visualBrush,
null,
new Rect(new Point(0, 0), new Size(imageWidth / m.M11, imageHeight / m.M22)));
}
// 4) draw element
elementBitmap.Render(drawingVisual);
// 5) create PNG image
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(elementBitmap));
// 6) save image to file
using (var imageFile = new FileStream(pathToOutputFile, FileMode.Create, FileAccess.Write)) {
encoder.Save(imageFile);
imageFile.Flush();
imageFile.Close();
}
}
Код достаточно простой, но краткие комментарии к каждому шагу не повредят:
- Шаг 1 - Получаем DPI для монитора. Т.к. WPF для размеров и координат оперирует единицей измерения равной 1/96 дюйма, то используем коэффициент 96 для получения DPI.
- Шаг 2 – Создаем экземпляр класса RenderTargetBitmap
- Шаг 3 – Сдвигаем элемент к левому верхнему углу изображения (т.к. для него скорее всего задано смещение). При этом так же учитываем масштабирование монитора (m.M11 и m.M12)
- Шаг 4 – Переводим элемент в растровое изображение. Тут есть интересный момент с фоном элемента, который мы рассмотрим далее.
- Шаг 5 – Переводим растровое изображение в PNG формат. В WPF доступны следующие кодировщики: BmpBitmapEncoder, GifBitmapEncoder, JpegBitmapEncoder, PngBitmapEncoder, TiffBitmapEncoder, WmpBitmapEncoder.
- Шаг 6 – Создаем поток в памяти и записываем туда данные для дальнейшего PNG файла.
Результатом данного кода будет сохраненный на диске файл с изображением в PNG формате. Так же класс RenderTargetBitmap позволяет получить массив пикселей изображения для дальнейшей обработки. Все просто, но есть два интересных момента:
- Первый заключается в шаге 3. Если не убрать сдвиги элемента, то сохраняемые элементы будут так же смещены и на изображении. Например, как на рисунке справа (чтобы было лучше заметно специально добавлен серый фон).
- Второй момент в прозрачности фона многих элементов управления. Генерируемое изображение будет содержать пиксели в формате с альфа-каналом (например PixelFormat.Pbgra32). При сохранении в формат, без поддержки прозрачности необходимо будет задать фон. В приложенном примере для упрощения задан белый фон для Grid. Поэтому его копии можно сохранять в любом другом формате (можно попробовать, заменив PngBitmapEncoder на любой другой из отмеченных выше).
Ну дополнительно - пример кода печати элемента WPF. Это очень просто:
PrintDialog dlgPrint = new PrintDialog();
// Print
if (dlgPrint.ShowDialog() == true) {
dlgPrint.PrintVisual(element, "The picture is drawn dynamically");
}
Осталось дать ссылку на демонстрационный проект для Visual Studio 2010:
RenderTargetBitmapDemo.zip.