Original:taking WPF "screenshots"
I was recently working in a Surface project at Microsoft (that'll be shown at BETT) and one of the requirements were to Provide an external "administration console". As part of this console I wanted to show a "screenshot" of the current game running on the Surface unit; After playing around for a while the IT turned out it is pretty straightforward.
We do consider sending over the RAW XAML from the Surface to the Console, but that would potentially has issues when Res Ources weren ' t available, so the approach that is taken is to create a JPG screenshot and send it over as a byte array V IA WCF.
Rendering to a Bitmapframe
The key to this approach are rendertargetbitmap which allows us to render any WPF Visual to a bitmapframe as follows:
New RenderTargetBitmap (bitmapframe.create (rendertarget);
Then from there we can use Jpegbitmapencoder to create a JPG from that Bitmapframe:
New Jpegbitmapencoder (); JpgEncoder.Frames.Add (bitmapframe);
Then we can output this JPG to a stream of our choice using the Save () method.
Problems
While the-works for many cases, and indeed worked perfectly for the Surface application, we did encounter problems when th E source We are rendering have Transforms applied or when it's not positioned at 0,0. When this occurs the screenshots we take would have the content shifted ' out of frame ' resulting in black borders, or Conten T missing altogether. The following screenshot demonstrates the problem:
Workaround
To work around the problem we can use a VisualBrush to "draw" our source element onto a new Visual, and render this with O ur rendertargetbitmap:
New= drawingvisual.renderopen (); using (drawingcontext) { nullnew Rect (new Point (00New Point ());} Rendertarget.render (drawingvisual);
It's not ideal, but I ' ve yet to find a better workaround for it.
Putting it all Together
To make it more useful, we can wrap the whole IoT up into a Extension Method. Rather than extending Visual, I ' ve chosen to use UIElement so I had access to the rendersize to calculate the required Si Ze of the output bitmap. I ' ve also included parameters to scale the resulting bitmap and to set the JPG quality level:
//////Gets a JPG "screenshot" of the current UIElement//////UIElement to screenshot///Scale to render the screenshot///JPG Quality///Byte array of JPG data Public Static byte[] Getjpgimage ( ThisUIElement Source,DoubleScaleintquality) { DoubleActualHeight =source. Rendersize.height; DoubleActualWidth =source. Rendersize.width; DoubleRenderheight = ActualHeight *Scale ; DoubleRenderwidth = ActualWidth *Scale ; RenderTargetBitmap RenderTarget=NewRenderTargetBitmap ((int) Renderwidth, (int) Renderheight, the, the, PIXELFORMATS.PBGRA32); VisualBrush Sourcebrush=NewVisualBrush (source); DrawingVisual drawingvisual=Newdrawingvisual (); DrawingContext DrawingContext=Drawingvisual.renderopen (); using(DrawingContext) {Drawingcontext.pushtransform (NewScaleTransform (scale, scale)); Drawingcontext.drawrectangle (Sourcebrush,NULL,NewRect (NewPoint (0,0),NewPoint (ActualWidth, actualHeight)); } rendertarget.render (DrawingVisual); Jpegbitmapencoder Jpgencoder=NewJpegbitmapencoder (); Jpgencoder.qualitylevel=quality; JPGENCODER.FRAMES.ADD (Bitmapframe.create (rendertarget)); Byte[] _imagearray; using(MemoryStream outputstream =NewMemoryStream ()) {Jpgencoder.save (OutputStream); _imagearray=Outputstream.toarray (); } return_imagearray;}
WPF does not lose scope for controls (reprinted)