Capturing Snapshot in Windows 8.1 Store App

During development in Windows Store App, to capture a snapshot is one of the most common requests developers will face with. By the API restrictions in previous WinRT, there was no way for a Windows Store App to capture screenshots, neither XAML nor JavaScript could do this. But in Window 8.1 Apps, we could take the advantage of the following new feature/ API to reverse the outdated situation:

Rendering the XAML tree to a bitmap

WebView for JavaScirpt

I will go through these two updates with simplified code snippet in the following paragraphs.

Rendering the XAML Tree to a bitmap

Windows Runtime for Windows 8.1 adds a new type of RenderTargetBitmap into Windows.UI.Xaml.Media.Imaging namespace, which contains two important methods:

RenderTargetBitmap.RenderAsync: Renders a snapshot of a UIElement visual tree to an image source.

RenderTargetBitmap.GetPixelsAsync: Retrieves the previously rendered RenderTargetBitmap image as a buffered stream of bytes in BGRA8 format.

Below is C# sample selected from XAML render to bitmap sample, detailed code could be download in the sample.

Render XAML Tree to image source

    1: RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap();
    2: await renderTargetBitmap.RenderAsync(RenderedGrid);
    3: RenderedImage.Source = renderTargetBitmap;

Render XAML Tree to file

    1: // Render to an image at the current system scale and retrieve pixel contents
    2: RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap();
    3: await renderTargetBitmap.RenderAsync(RenderedGrid);
    4: var pixelBuffer = await renderTargetBitmap.GetPixelsAsync();
    5:  
    6: var savePicker = new FileSavePicker();
    7: savePicker.DefaultFileExtension = ".png";
    8: savePicker.FileTypeChoices.Add(".png", new List<string> { ".png" });
    9: savePicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
   10: savePicker.SuggestedFileName = "snapshot.png";
   11:  
   12: // Prompt the user to select a file
   13: var saveFile = await savePicker.PickSaveFileAsync();
   14:  
   15: // Verify the user selected a file
   16: if (saveFile == null)
   17:     return;
   18:  
   19: // Encode the image to the selected file on disk
   20: using (var fileStream = await saveFile.OpenAsync(FileAccessMode.ReadWrite))
   21: {
   22:      var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, fileStream);
   23:  
   24:      encoder.SetPixelData(
   25:     BitmapPixelFormat.Bgra8,
   26:     BitmapAlphaMode.Ignore,
   27:     (uint)renderTargetBitmap.PixelWidth,
   28:     (uint)renderTargetBitmap.PixelHeight,
   29:     DisplayInformation.GetForCurrentView().LogicalDpi,
   30:     DisplayInformation.GetForCurrentView().LogicalDpi,
   31:     pixelBuffer.ToArray());
   32:  
   33:      await encoder.FlushAsync();
   34: }

WebView for JavaScript

Windows 8.1 makes significant change on WebView control, with new methods GoBack, GoForward, Stop, Refresh, CanGoBack, and CanGoForward added, we do not need to perform such action by injecting JavaScript, and many other new properties and events really make WebView more powful than it used to be. I will write another blog to dig into detail about these changes.

One of the amazing changes I’d like to introduce here is CapturePreviewToStreamAsync method, besides capture web page content, which brings capturing snapshots for JavaScript Windows Store App into possible!

Here is an entire example for how to use CapturePreviewToStreamAsync method to capture the HTML into image file inside a WebView based on HTML WebView control sample.

    1: <body>
    2:     <p>Content goes here</p>
    3:     <input type="url" id="urlField" />
    4:     <button id="goOrStopButton">Go</button>
    5:     <button id="captureToImage">capture</button>
    6:     <div>
    7:         <x-ms-webview id="webview" style="width:1000px;height:800px"></x-ms-webview>
    8:     </div>
    9: </body>
    1: var page = WinJS.UI.Pages.define("default.html", {
    2:     ready: function (element, options) {
    3:         document.getElementById("goOrStopButton").addEventListener("click", goToUrl, false);
    4:         document.getElementById("captureToImage").addEventListener("click", captureToImage, false);
    5:         var webviewControl = document.getElementById("webview");
    6:         webviewControl.navigate("https://go.microsoft.com/fwlink/?LinkId=294155");
    7:     }
    8: });
    9:  
   10: function goToUrl() {
   11:     var destinationUrl = document.getElementById("urlField").value;
   12:     try {
   13:         document.getElementById("webview").navigate(destinationUrl);
   14:     } catch (error) {
   15:         WinJS.log && WinJS.log("\"" + destinationUrl + "\" is not a valid absolute URL.\n", "sdksample", "error");
   16:         return;
   17:     }
   18: }
   19:  
   20: function captureToImage() {
   21:     var webviewControl = document.getElementById("webview");
   22:     Windows.Storage.ApplicationData.current.localFolder.createFileAsync("test.png", Windows.Storage.CreationCollisionOption.replaceExisting).then(function (file) {
   23:         file.openAsync(Windows.Storage.FileAccessMode.readWrite).then(function (stream) {
   24:             var captureOperation = webviewControl.capturePreviewToBlobAsync();
   25:             captureOperation.oncomplete = function (completeEvent) {
   26:                 var inputStream = completeEvent.target.result.msDetachStream();
   27:                 Windows.Storage.Streams.RandomAccessStream.copyAsync(inputStream, stream).then(function () {
   28:                     stream.flushAsync().done(function () {
   29:                         inputStream.close();
   30:                         stream.close();
   31:                     });
   32:                 });
   33:             };
   34:             captureOperation.start();
   35:         });
   36:     });
   37: }

It will be very easy to retrieve HTML in a certain DOM object by innerHTML property, and perform a capture of WebView after redirected to a fake page created by the HTML we got.

- Jazzen

Comments

  • Anonymous
    October 20, 2013
    Is it possible to capture the whole screen of a JavaScript Store App - it should be a wrapped WebView itself, isn't it? Perhaps using those two approaches together...?

  • Anonymous
    October 20, 2013
    @Albert Kühner, unfortunately, WinJs still does not provide such API, capturing the WebView wrapped content is a workaround. One explaination is that this is because the limitation if IE engine that WinJs app runs on, but I also hope one day in future we JS developer could get this function:)

  • Anonymous
    October 24, 2013
    Wrapping the full app to a WebView doesn't seem to be an (easy) option, since WinRT APIs are not available there. Thus, as it's likely that the app uses those APIs, one needs to come up with mocked API lib or something to load the app in a WebView. This ability to take screenshots of the running app would be useful e.g. for automated UI regression testing, but now it seems that there's no sensible options to do that. If someone can prove otherwise, would love to hear more about the topic.

  • Anonymous
    January 12, 2014
    Is it possible to capture Image using coded UI?