views:

374

answers:

2

Hi all. I'm trying to create a JPG from part of my WPF Applications. Like a screenshot, only of individual UI Elements. 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

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.

+3  A: 

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))); 
Jeffrey L Whitledge
How can I do that when the array is being filled by "_imageArray = outputStream.ToArray();"?
Adam S
I think it's not just black, it may be transparent black (can be quickly checked by saving to png). So you can draw image over desired background and save resulting image.
max
If is is transparent black as @max suggests, then you can just scan through the byte array setting the color for each pixel that has a zero in the alpha channel.
Jeffrey L Whitledge
Jeffrey - I have no idea how to do that...I know zero about JPGs or how they are described by the bytes.
Adam S
@Adam S - I just added some (untried) code that might help. I hope it helps!
Jeffrey L Whitledge
Jeffrey's original idea was infeasible, but his updated answer is exactly what I was going to suggest: Just draw a white rectangle on the RenderTargetBitmap before drawing the UIElement.
Ray Burns
A: 

Unfortunately the only thing that worked was just setting the element's background in the XAML. I didn't want to do this, but I guess it is what I need to do in this case. Thanks anyway for the suggestion.

Adam S