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