I was recently working on a Surface project at Microsoft (that will be shown at BETT đ ) and one of the requirements was to provide an external âadministration consoleâ. As part of that console I wanted to show an âscreenshotâ of the current game running on the Surface unit; after playing around for a while it turned out it was pretty straightforward.
We did consider sending over the RAW XAML from the Surface to the Console, but that would potentially have issues when resources werenât available, so the approach that was taken was to create a JPG screenshot and send it over as a byte array via WCF.
Rendering to a BitmapFrame
The key to this approach is RenderTargetBitmap which allows us to render any WPF Visual to a BitmapFrame as follows:
RenderTargetBitmap renderTarget = new RenderTargetBitmap(200, 200, 96, 96, PixelFormats.Pbgra32); renderTarget.Render(myVisual); BitmapFrame bitmapFrame = BitmapFrame.Create(renderTarget);
Then from there we can use JpegBitmapEncoder to create a JPG from that BitmapFrame:
JpegBitmapEncoder jpgEncoder = new JpegBitmapEncoder(); jpgEncoder.Frames.Add(bitmapFrame);
Then we can output that JPG to a stream of our choice using the Save() method.
Problems
While this works for many cases, and indeed worked perfectly for the Surface application, we do encounter problems when the source we are rendering has Transforms applied or when itâs not positioned at 0,0. When this occurs the screenshots we take will have the content shiftedâout of frameâ resulting in black borders, or content 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 that with our RenderTargetBitmap:
DrawingVisual drawingVisual = new DrawingVisual(); DrawingContext drawingContext = drawingVisual.RenderOpen(); using (drawingContext) { drawingContext.DrawRectangle(sourceBrush, null, new Rect(new Point(0, 0), new Point(200, 200))); } 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 lot up into a Extension Method. Rather than extending Visual, Iâve chosen to use UIElement so I have access to the RenderSize to calculate the required size 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(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; }
I’ve bundled the extension method class in with a small demo app and you can grab the source to both from here:
Very good post, i too had the same problem, however when i published my xbap application under partial trust, it throw a Security Exception, Is DrawingVisual and DrawingContext will work only on full trust,
Thank Your
@abi I’ve just had a look through, and it won’t work in partial trust because of the JpegBitmapEncoder – it’s not supported in that situatation. I guess it’s to stop you creating a bitmap on the fly and bypassing the security for where you can and can’t grab images from.
Thanks srobbins for the reply , Encoders wont work in partial trust, do you have any suggetions to take the bytes of RenderTargetBitmap, im really stuck here.
Thanks
The only other suggestion I can think of is to render to a WritableBitmap (WriteableBitmap rb = new WriteableBitmap(renderTarget);) then try grabbing the pixels from that. You will need to find another library to encode the data though, unless you want to move it around in a raw format.
This repeatably causes low level exceptions on my machine (as you read the stack trace, know that I’ve ensured my code runs on UI thread). I’m using .NET 3.0 (I know I could update, but I have other constraints).
Unhandled Exception on Dispatcher (System.Windows.Threading.Dispatcher). Handled=False, Exception=System.Runtime.InteropServices.COMException (0x8000FFFF): Catastrophic failure (Exception from HRESULT: 0x8000FFFF (E_UNEXPECTED))
at MS.Internal.HRESULT.Check(Int32 hr)
at System.Windows.Media.Composition.DUCE.Channel.SendCommand(Byte* pCommandData, Int32 cSize)
at System.Windows.Media.Composition.DUCE.CompositionNode.SetAlpha(ResourceHandle hCompositionNode, Double alpha, Channel channel)
at System.Windows.Media.Visual.DisconnectBitmapEffectPropertiesOnAllChannels()
at System.Windows.Media.Visual.set_VisualBitmapEffect(BitmapEffect value)
at System.Windows.UIElement.OnBitmapEffectChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
at System.Windows.DependencyObject.OnPropertyChanged(DependencyPropertyChangedEventArgs e)
at System.Windows.FrameworkElement.OnPropertyChanged(DependencyPropertyChangedEventArgs e)
at System.Windows.DependencyObject.NotifyPropertyChange(DependencyPropertyChangedEventArgs args)
at System.Windows.DependencyObject.UpdateEffectiveValue(EntryIndex entryIndex, DependencyProperty dp, PropertyMetadata metadata, EffectiveValueEntry oldEntry, EffectiveValueEntry& newEntry, Boolean coerceWithDeferredReference, OperationType operationType)
at System.Windows.DependencyObject.InvalidateProperty(DependencyProperty dp)
at System.Windows.StyleHelper.InvalidateDependents(Style ownerStyle, FrameworkTemplate frameworkTemplate, DependencyObject container, DependencyProperty dp, FrugalStructList`1& dependents, Boolean invalidateOnlyContainer)
at System.Windows.StyleHelper.OnTriggerSourcePropertyInvalidated(Style ownerStyle, FrameworkTemplate frameworkTemplate, DependencyObject container, DependencyProperty dp, DependencyPropertyChangedEventArgs changedArgs, Boolean invalidateOnlyContainer, FrugalStructList`1& triggerSourceRecordFromChildIndex, FrugalMap& propertyTriggersWithActions, Int32 sourceChildIndex)
at System.Windows.FrameworkElement.OnPropertyChanged(DependencyPropertyChangedEventArgs e)
at System.Windows.DependencyObject.NotifyPropertyChange(DependencyPropertyChangedEventArgs args)
at System.Windows.DependencyObject.UpdateEffectiveValue(EntryIndex entryIndex, DependencyProperty dp, PropertyMetadata metadata, EffectiveValueEntry oldEntry, EffectiveValueEntry& newEntry, Boolean coerceWithDeferredReference, OperationType operationType)
at System.Windows.DependencyObject.SetValueCommon(DependencyProperty dp, Object value, PropertyMetadata metadata, Boolean coerceWithDeferredReference, OperationType operationType, Boolean isInternal)
at System.Windows.DependencyObject.SetValue(DependencyPropertyKey key, Object value)
at System.Windows.DependencyObject.SetValue(DependencyPropertyKey dp, Boolean value)
at System.Windows.ReverseInheritProperty.FirePropertyChangeInAncestry(DependencyObject element, Boolean oldValue, DeferredElementTreeState treeState)
at System.Windows.ReverseInheritProperty.FirePropertyChangeInAncestry(DependencyObject element, Boolean oldValue, DeferredElementTreeState treeState)
at System.Windows.ReverseInheritProperty.FirePropertyChangeInAncestry(DependencyObject element, Boolean oldValue, DeferredElementTreeState treeState)
at System.Windows.ReverseInheritProperty.FirePropertyChangeInAncestry(DependencyObject element, Boolean oldValue, DeferredElementTreeState treeState)
at System.Windows.ReverseInheritProperty.OnOriginValueChanged(DependencyObject oldOrigin, DependencyObject newOrigin, DeferredElementTreeState& oldTreeState)
at System.Windows.Input.MouseDevice.ChangeMouseOver(IInputElement mouseOver, Int32 timestamp)
at System.Windows.Input.MouseDevice.PreNotifyInput(Object sender, NotifyInputEventArgs e)
at System.Windows.Input.InputManager.ProcessStagingArea()
at System.Windows.Input.InputManager.ProcessInput(InputEventArgs input)
at System.Windows.Input.InputProviderSite.ReportInput(InputReport inputReport)
at System.Windows.Interop.HwndMouseInputProvider.ReportInput(IntPtr hwnd, InputMode mode, Int32 timestamp, RawMouseActions actions, Int32 x, Int32 y, Int32 wheel)
at System.Windows.Interop.HwndMouseInputProvider.FilterMessage(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at System.Windows.Interop.HwndSource.InputFilterMessage(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Boolean isSingleParameter)
at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Boolean isSingleParameter, Delegate catchHandler)
I am getting the COMException as well. Does any one know of a solution? Thanks.
Haven’t got a clue I’m afraid. If you put the sample code somewhere I can try it here to see if it’s a “your machine” issue if that’s any help.
Excelent post!
But how it works if the UIElement is not visible? Taking screenshot from a hidden windows
@cristi Good question, I have no idea đ If the Window itself is invisible, and isn’t rendering, then you could always render whatever “root” content you have in there (grid, stackpanel, canvas etc.)
Thanks a lot for this great piece of code! Saved me a lot of research. đ
Best regards from Germany
Very nice, thank you for posting. Saved lots of time here too.
Thank you very much for the code sample – it’s excellent! One question – I’m loading a usercontrol into a tabcontrol with a FlowDirection of RightToLeft and when the code grabs the screenshot of the usercontrol, it’s always backwards as a result of the tabcontrol’s flow layout. Any ideas how to get around that?
I haven’t tested it, but you could possibly create a grid (or another container), set the flow direction, add your user control to it and screenshot the grid. Then re-parent the user control back where it came from.
I gave that a try this morning, but didn’t have any luck with it. When I figure out a solution, I’ll be sure to post it. Thanks for your suggestion!
I used a style for the TabControl and set the TabPanel’s FlowDirection to RightToLeft to get the tabs on the right. Then I left the FlowDirection of the TabControl itself as LeftToRight. Here’s the TabPanel tag that I have within the ControlTemplate:
Excellent post! Solves what I needed to do (which is to screen capture the WPF window).
Thank you!
[…] a bunch of WPF pages. In WPF this is an easy task. Especially when somenone already has created an extension method for it:. The extension method is in C#, so i have converted it to VB.NET and added another extension method […]
Great post! I was really hoping it would help me with what I am trying to do, but it doesn’t work. I am trying to use the code to get a screenshot of the contents of a wpf WebBrowser control. All I am getting is a black image. I was hoping to use the screenshot in an Image control because the WebBrowser control does not allow any other control to overlay it.