Создание изображений из элементов управления WPF

Существует достаточно распространенная задача, когда окно приложения или его часть необходимо сохранить как изображение. В 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 позволяет получить массив пикселей изображения для дальнейшей обработки. Все просто, но есть два интересных момента:

  • Смещенное изображение элемента WPFПервый заключается в шаге 3. Если не убрать сдвиги элемента, то сохраняемые элементы будут так же смещены и на изображении. Например, как на рисунке справа (чтобы было лучше заметно специально добавлен серый фон).
  • Второй момент в прозрачности фона многих элементов управления. Сохраненное изображение WPF GridГенерируемое изображение будет содержать пиксели в формате с альфа-каналом (например 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.