WPF Screenshot JPG from UIElement with C#

Adam S picture Adam S · Jun 4, 2010 · Viewed 8.9k times · Source

I'm trying to create a JPG from part of my WPF Applications. Like a screenshot, only of individual UIElements. I started here: http://www.grumpydev.com/2009/01/03/taking-wpf-screenshots/

I am using his extension method, which essential allows you to get a byte[] with UIElement.GetJpgImage(). This can then be written using a filestream to a JPG image. If I make a JPG of the whole window, it looks just fine! However, this is not ideal because it just captures what the user sees. Things that are not visible because of the scrollviewer or because their parent was animated to a small size won't show up.

If I take a "screenshot" of, say, a grid that I use for layout: alt text http://img697.imageshack.us/img697/4233/fullscreenshot2.jpg

I get this crap with a black background. I don't want that. Furthermore, if I've collapsed this grid's height using animation, I won't get anything at all. Those are actually templated checkboxes, they should have black text above them, and the background of the grid should be white. Here's the code that someone else wrote to return the byte[] array that gets written to a filestream:

public static byte[] GetJpgImage(this UIElement source, double scale, int quality)
{
    double actualHeight = source.RenderSize.Height;
    double actualWidth = source.RenderSize.Width;

    double renderHeight = actualHeight * scale;
    double renderWidth = actualWidth * scale;

    RenderTargetBitmap renderTarget = new RenderTargetBitmap((int) renderWidth, (int) renderHeight, 96, 96, PixelFormats.Pbgra32);
    VisualBrush sourceBrush = new VisualBrush(source);

    DrawingVisual drawingVisual = new DrawingVisual();
    DrawingContext drawingContext = drawingVisual.RenderOpen();

    using (drawingContext)
    {
        drawingContext.PushTransform(new ScaleTransform(scale, scale));
        drawingContext.DrawRectangle(sourceBrush, null, new Rect(new Point(0, 0), new Point(actualWidth, actualHeight)));
    }
    renderTarget.Render(drawingVisual);

    JpegBitmapEncoder jpgEncoder = new JpegBitmapEncoder();
    jpgEncoder.QualityLevel = quality;
    jpgEncoder.Frames.Add(BitmapFrame.Create(renderTarget));

    Byte[] _imageArray;

    using (MemoryStream outputStream = new MemoryStream())
    {
        jpgEncoder.Save(outputStream);
        _imageArray = outputStream.ToArray();
    }

    return _imageArray;
}

Somewhere in there, we're getting a black background. Any insight?

EDIT: If I set the grid's background property to white, the screenshot comes out as expected. However, it's not feasible to set everything's background that I need to take a screenshot of.

Answer

Jeffrey L Whitledge picture Jeffrey L Whitledge · Jun 4, 2010

Just a guess, I would think that a black background would represent portions of the byte array that are not set to anything in this process. The initial zeros in the array would appear as black.

To avoid this, I suggest initializing the array with 0xFF (byte.MaxValue) values.

UPDATED:

From looking at this closer, I think you should draw a white rectangle onto the image before you render the UI element. That ought to work anyway.

Just before this line of code

drawingContext.DrawRectangle(sourceBrush, null, new Rect(new Point(0, 0), new Point(actualWidth, actualHeight))); 

put something like this

drawingContext.DrawRectangle(Brushes.White, null, new Rect(new Point(0, 0), new Point(actualWidth, actualHeight)));