Quantcast
Channel: .NET – Marius Bancila's Blog
Viewing all articles
Browse latest Browse all 39

Render the screen of a Windows Store App to a bitmap in Windows 8.1

$
0
0

In WPF, Silverlight and Windows Phone it is possible to render a visual object into a bitmap using the RenderTargetBitmap. This functionality, that I find pretty basic, was not available for Windows Store applications. Fortunately, Windows 8.1 provides that functionality for Windows Store applications too, through the same RenderTargetBitmap class.

There are some limitations though:

  • it should be used in the code behind (not declared in XAML) because you have to call RenderAsync
  • collapsed visual objects are not rendered (only visible ones)
  • in rare circumstances the content can be lost due to the interaction with lower level systems; in this case a specific exception is triggered
  • the rendered target bitmap does not automatically scale when the current DPI settings change
  • the maximum rendered size of a XAML visual tree is restricted by the maximum dimensions of a DirectX texture

Here is a demo Windows Store application that has several controls and a button that when pressed a screenshot of the area shown in red (it’s a grid) is taken. The bitmap is saved on disk, but also displayed as the source for the image control shown in the preview area.

wsas1

<Page>
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Name="mainGrid">

      <TextBlock Text="Windows Store App Screenshot Demo" Style="{StaticResource HeaderTextBlockStyle}"
                 Grid.Column="0" Grid.ColumnSpan="5" Grid.Row="0"
                 Margin="0,30,0,30"
                 TextAlignment="Center" />

      <Grid Name="controlsGrid" Grid.Column="1" Grid.ColumnSpan="2" Grid.Row="1" Margin="5,5,5,50">
      </Grid>
    </Grid>
</Page>

The handler for the Click button even looks like this:

async void btnScreenshot_Click(object sender, RoutedEventArgs e)
{
   var bitmap = await SaveScreenshotAsync(controlsGrid);

   imagePreview.Source = bitmap;
}

SaveScreenshotAsync is an async method that takes the reference to the FrameworkElement to be rendered to a bitmap (in this case the constrolsGrid) and returns a Task<RenderedTargetBitmap> that can be awaited on. As soon as we have the bitmap we set it as the source for the image control (imagePreview).

wsas2

async Task<RenderTargetBitmap> SaveScreenshotAsync(FrameworkElement uielement)
{
   var file = await PickSaveImageAsync();

   return await SaveToFileAsync(uielement, file);         
}      

async Task<RenderTargetBitmap> PickSaveImageAsync()
{
   var filePicker = new FileSavePicker();
   filePicker.FileTypeChoices.Add("Bitmap", new List() { ".bmp" });
   filePicker.FileTypeChoices.Add("JPEG format", new List() { ".jpg" });
   filePicker.FileTypeChoices.Add("Compuserve format", new List() { ".gif" });
   filePicker.FileTypeChoices.Add("Portable Network Graphics", new List() { ".png" });
   filePicker.FileTypeChoices.Add("Tagged Image File Format", new List() { ".tif" });
   filePicker.DefaultFileExtension = ".jpg";
   filePicker.SuggestedFileName = "screenshot";
   filePicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
   filePicker.SettingsIdentifier = "picture picker";
   filePicker.CommitButtonText = "Save picture";

   return await filePicker.PickSaveFileAsync();
}

SaveScreenshotAsync is an async method that takes the FrameworkElement to be rendered to a bitmap and returns a Task<RenderedTargetBitmap> that can be awaited on. This method first prompts the user to select a destination file for the rendered bitmap. When the file is available it calls SaveToFileAsync to rendered the bitmap and write it to the file.

async Task<RenderTargetBitmap> SaveToFileAsync(FrameworkElement uielement, StorageFile file)
{
   if (file != null)
   {
      CachedFileManager.DeferUpdates(file);

      Guid encoderId = GetBitmapEncoder(file.FileType);

      try
      {
         using (var stream = await file.OpenAsync(FileAccessMode.ReadWrite))
         {
            return await CaptureToStreamAsync(uielement, stream, encoderId);
         }
      }
      catch(Exception ex)
      {
         DisplayMessage(ex.Message);
      }

      var status = await CachedFileManager.CompleteUpdatesAsync(file);
   }

   return null;
}

Guid GetBitmapEncoder(string fileType)
{
   Guid encoderId = BitmapEncoder.JpegEncoderId;
   switch (fileType)
   {
      case ".bmp":
         encoderId = BitmapEncoder.BmpEncoderId;
         break;
      case ".gif":
         encoderId = BitmapEncoder.GifEncoderId;
         break;
      case ".png":
         encoderId = BitmapEncoder.PngEncoderId;
         break;
      case ".tif":
         encoderId = BitmapEncoder.TiffEncoderId;
         break;
   }

   return encoderId;
}

async void DisplayMessage(string error)
{
   var dialog = new MessageDialog(error);

   await dialog.ShowAsync();
}

SaveToFileAsync is an async method that takes the FrameworkElement to be rendered to a bitmap and the StorageFile when the bitmap is to be saved and returns a Task<RenderedTargetBitmap> that can be awaited on. The file is opened asynchronous for read-write access and the returned IRandomAccessStream is passed further together with the framework element and the bitmap encoder id (that specifies how the bitmap should be encoded, i.e. BMP, JPEG, PNG, GIF, etc.) to CaptureToStreamAsync.

async Task<RenderTargetBitmap> CaptureToStreamAsync(FrameworkElement uielement, IRandomAccessStream stream, Guid encoderId)
{
   try
   {
      var renderTargetBitmap = new RenderTargetBitmap();
      await renderTargetBitmap.RenderAsync(uielement);

      var pixels = await renderTargetBitmap.GetPixelsAsync();

      var logicalDpi = DisplayInformation.GetForCurrentView().LogicalDpi;
      var encoder = await BitmapEncoder.CreateAsync(encoderId, stream);
      encoder.SetPixelData(
          BitmapPixelFormat.Bgra8,
          BitmapAlphaMode.Ignore,
          (uint)renderTargetBitmap.PixelWidth,
          (uint)renderTargetBitmap.PixelHeight,
          logicalDpi,
          logicalDpi,
          pixels.ToArray());

      await encoder.FlushAsync();

      return renderTargetBitmap;
   }
   catch (Exception ex)
   {
      DisplayMessage(ex.Message);
   }

   return null;
}

CaptureToStreamAsync creates a new RenderTargetBitmap object and calls RenderAsync to render the visual tree of the framework element to a bitmap. After the bitmap is rendered it retries the image as a buffer of byes in the BGRA8 format. It then asynchronously creates a BitmapEncoder for the IRandomAccessStream stream that it received as an argument, it calls SetPixelData to set the pixels data (notice the BitmapPixelFormat.Bgra8 parameter that matches the pixels format returned by GetPixelsAsync) and later asynchronously flushes all the image data, basically writing it to the file. It then returns that RenderTargetBitmap object that it created, which is used eventually as the source for the image control.

Here is how the saved JPEG image (also seen in the preview screenshot above) looks likes:
wsas3

You can check the source code of the attached WinRT Screenshot demo (454). It requires Visual Studio 2013 and Windows 8.1.


Viewing all articles
Browse latest Browse all 39

Trending Articles