連載
» 2013年11月21日 11時53分 UPDATE

WinRT/Metro TIPS:Windows 8.1の新機能、画面キャプチャを利用するには?[Windows 8.1ストア・アプリ開発]

Windows 8.1ストア・アプリでは、自アプリの画面キャプチャが撮れる。画面キャプチャ機能を提供するRenderTargetBitmapクラスの使い方と注意点を解説する。

[山本康彦(http://www.bluewatersoft.jp/),BluewaterSoft]
WinRT/Metro TIPS
業務アプリInsider/Insider.NET

powered by Insider.NET

「WinRT/Metro TIPS」のインデックス

連載目次

 Windows 8.1(以下、Win 8.1)用のWindowsストア・アプリ(以下、Win 8.1アプリ)からは、自アプリの画面キャプチャが撮れるようになった。指定したコントロール(および、それに含まれるコントロール)のキャプチャや、画面全体のキャプチャが可能だ。本稿では、画面キャプチャ機能を提供するRenderTargetBitmapクラス(Windows.UI.Xaml.Media.Imaging名前空間)の使い方と注意点を解説する。本稿のサンプルは「Windows Store app samples:MetroTips #58(Windows 8.1版)」からダウンロードできる。

事前準備

 Win 8.1アプリを開発するには、Win 8.1とVisual Studio 2013(以降、VS 2013)が必要である。本稿ではOracle VM VirtualBox上で64bit版Windows 8.1 Pro Preview(日本語版)とVisual Studio Express 2013 Preview for Windows(日本語版)を使用してプログラミングしている。これらを準備する方法や注意事項は、「WinRT/Metro TIPS: Win8用のソース・コードをWin8.1用に変換するには?[Win 8.1]」の記事をご参照いただきたい。また、本稿のソース・コードは、64bit版Windows 8.1 Pro(日本語版の製品版)とVisual Studio Express 2013 for Windows(日本語版の製品版)*1でも動作を確認している。

*1マイクロソフト公式ダウンロード・センターの「Microsoft Visual Studio Express 2013 for Windows」から無償で入手できる。


RenderTargetBitmapクラス

 Win 8.1アプリ用に新しく用意されたRenderTargetBitmapクラスで画面キャプチャをするには、次の表に示す2つのメソッドを主に利用する。

メソッド 説明
RenderAsync 画面をキャプチャする。
キャプチャ対象は、引数に指定したコントロールと、その子どものコントロール。また、キャプチャ後の画像サイズを指定することも可能
GetPixelsAsync キャプチャした画像をバイト・ストリームとして取り出す。
画像のフォーマットは、BGRA8(=ピクセル当たり青/緑/赤/アルファ各8bit、計32bit)
RenderTargetBitmapクラスの主要なメソッド
ここにいう「子ども」とは、クラスの継承関係ではなく、ビジュアル・ツリーにおける親子関係だ。大ざっぱにいえば、XAMLコードでタグが入れ子になっているものである。

 また、RenderTargetBitmapクラスはImageSourceクラスを継承しているので、ImageコントロールのSourceプロパティやImageBrushコントロールのImageSourceプロパティなどにRenderTargetBitmapオブジェクトを直接セットできる。

指定したコントロールをキャプチャするには?

 RenderTargetBitmapオブジェクトを用意しておき、RenderAsyncメソッドを呼び出すときにキャプチャしたいコントロールを渡せばよい。指定したコントロールと、その子どものコントロールの全てがキャプチャされる。

○画面を作る

 実際に試してみるために、次のような画面を用意する。

画面キャプチャを試してみるための画面 画面キャプチャを試してみるための画面
大きい赤枠の部分が、キャプチャの対象にするBorderコントロール。その子要素として、ImageコントロールやTextBlockコントロールなどをいくつか配置した。右下にある灰色の四角は、キャプチャした画像を表示するためのImageコントロール。その他、処理を実行するためのボタンをいくつか配置してある。
なお、ここで使っている画像は、「Windows アプリ アート ギャラリー」のものを利用させていただいた。

 まず、キャプチャする対象となるBorderコントロール(上の画像で大きい赤枠の部分)を配置し、その中にはImageコントロールやTextBlockコントロールなどをいくつか適当に配置する(次のコード)。また、このBorderコントロールの親に当たるコントロールには、適当に背景色を設定しておいてほしい(上の画像で大部分を占める青緑色の部分)。後ほど、Borderコントロールの背景色を透明に設定した効果を確認する。

<Border x:Name="captureTarget" BorderBrush="Red" BorderThickness="5" Background="Transparent">
  <Grid>
    <Grid.RowDefinitions>
      ……省略……
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
      ……省略……
    </Grid.ColumnDefinitions>

    ……省略……

    <Image Source="Assets/IC013.png" Stretch="Uniform" Opacity="0.75" />
    <Image Grid.Column="1" Source="Assets/IC014.png" Stretch="Uniform" Opacity="0.75" />

    ……省略……

    <Grid Grid.ColumnSpan="4" Grid.RowSpan="3">
      <TextBlock FontSize="48" FontWeight="ExtraBold" Foreground="White" TextAlignment="Center"
                 HorizontalAlignment="Center" VerticalAlignment="Center" Opacity="0.6">
        <LineBreak/>RenderTargetBitmap
        <LineBreak/>Test</TextBlock>
    </Grid>

  </Grid>
</Border>

キャプチャ対象のBorderコントロールとその子コントロールの例(XAML)
Borderコントロールの名前は「captureTarget」とする。また、背景色は透明のままにしておく(そのことを強調するために、あえて「Transparent」と指定してある)。

 次に、キャプチャした画像を表示するためのImageコントロール(上の画像で右下にある濃い灰色の四角)を配置する(次のコード)。

<Border Background="#444444" BorderBrush="LightGray" BorderThickness="2" >
  <Image x:Name="image1" HorizontalAlignment="Stretch"  MinHeight="150" Stretch="Uniform" />
</Border>

キャプチャ結果を表示するためのImageコントロールの例(XAML)
Imageコントロールの名前は「image1」とした。

○キャプチャする

 まず、コードビハインドのメンバ変数としてRenderTargetBitmapクラスのオブジェクトを用意する(次のコード)。

private Windows.UI.Xaml.Media.Imaging.RenderTargetBitmap _renderTargetBitmap
  = new Windows.UI.Xaml.Media.Imaging.RenderTargetBitmap();

Private _renderTargetBitmap As Windows.UI.Xaml.Media.Imaging.RenderTargetBitmap _
  = New Windows.UI.Xaml.Media.Imaging.RenderTargetBitmap()

メンバ変数にRenderTargetBitmapオブジェクトを作った(上:C#、下:VB)

 このRenderTargetBitmapオブジェクトはImageコントロールのソースになれるのだから、画面が表示されるときにさっさとImageコントロールにセットしてしまおう(次のコード)。

protected override void OnNavigatedTo(NavigationEventArgs e)
{
  ……省略……
  this.image1.Source = _renderTargetBitmap;
}

Protected Overrides Sub OnNavigatedTo(e As NavigationEventArgs)
  ……省略……
  Me.image1.Source = _renderTargetBitmap
End Sub

画面表示時にRenderTargetBitmapオブジェクトをImageコントロールにセットする(上:C#、下:VB)
太字の部分を追加する。
OnNavigatedToメソッドに既存のコードがある場合は、メソッドの末尾に記述する。作成したページによっては「NavigationHelper の登録」の下に折りたたまれている場合があるので注意。

 そうしたら、次のコードのようにすれば、キャプチャした結果を「image1」コントロールに表示できる。ここでは、前出の画像で「image1」コントロールの左下にある[キャプチャ]ボタンのClickイベント・ハンドラに実装した。

private async void captureButton_Click(object sender, RoutedEventArgs e)
{
  await _renderTargetBitmap.RenderAsync(this.captureTarget);
}

Private Async Sub captureButton_Click(sender As Object, e As RoutedEventArgs)
  Await _renderTargetBitmap.RenderAsync(Me.captureTarget)
End Sub

コントロールをキャプチャする(上:C#、下:VB)
メソッドを自動生成させてから、太字の部分を追加する。
キャプチャ対象のコントロールを引数としてRenderTargetBitmapオブジェクトのRenderAsyncメソッドを呼び出す。RenderTargetBitmapオブジェクトは「image1」コントロールのソースに設定されているので、キャプチャした画像は自動的に「image1」コントロールに表示される。
RenderAsyncメソッドはawaitキーワードを必要とするので、メソッドのシグネチャにはasyncキーワードを追加する必要がある。

 たったこれだけで、次の画像のようにキャプチャした画像が「image1」コントロールに表示される。

赤枠の中をキャプチャして右下の小さいImageコントロールに表示できた 赤枠の中をキャプチャして右下の小さいImageコントロールに表示できた
青緑色の背景色(Borderコントロールより上の階層で指定されている)はキャプチャされていないことに注目。

 これはナビゲーション用のサムネイルなどに応用できるだろう。ただし、サムネイルとして使う場合には、画像サイズを小さくした方がよい。RenderTargetBitmapクラスのRenderAsyncメソッドには、キャプチャ画像の幅と高さを指定できるオーバーロードがあるので、そちらを使えばメモリ使用量を抑えられる。

 ここでもう一度、上のキャプチャ画像を見てほしい。キャプチャ結果を表示しているImageコントロールの背景が灰色のままになっている。青緑色の背景はBorderコントロールより上の階層で指定されているので、Borderコントロールとその下の階層をキャプチャしたときの対象にならないためだ。Borderコントロールの背景は透明なので、キャプチャ結果の背景も透明になり、Imageコントロールにもともと設定してあった灰色が見えているのだ。

画面全体をキャプチャするには?

 Window.Current.Contentオブジェクト(=Windows.UI.Xaml名前空間のUIElementクラス)*2をキャプチャすればよい。

 先ほどのコードビハインドで、RenderTargetBitmapクラスのRenderAsyncメソッドに渡す引数を、BorderコントロールからWindow.Current.Contentオブジェクトに変えるだけだ(次のコード)。

await _renderTargetBitmap.RenderAsync(Window.Current.Content);

Await _renderTargetBitmap.RenderAsync(Window.Current.Content)

画面全体をキャプチャする(上:C#、下:VB)
太字の部分を変更する。
ただし、Window.Current.Contentオブジェクトを起点とするビジュアル・ツリーに含まれないものはキャプチャできない。

*2Window.Current.Contentオブジェクト: 現在表示している画面の最上位レベルのコントロール。通常は、App.xaml.cs/.vbのOnLaunchedメソッドでFrameコントロール(Windows.UI.Xaml.Controls名前空間)が設定されている。


 これで、次の画像のように画面全体のキャプチャが「image1」コントロールに表示される。

画面全体をキャプチャして右下の小さいImageコントロールに表示できた 画面全体をキャプチャして右下の小さいImageコントロールに表示できた

 このように簡単に画面キャプチャが撮れるのだが、キャプチャ対象を起点とするビジュアル・ツリーに含まれないものはキャプチャされないことに注意してほしい。最初の例では、Borderコントロールの中に透けて見えていた親コントロールの青緑色の背景色がキャプチャされなかった。上の例では、画面左上隅の黒いデバッグ表示がキャプチャされていない。その他にも、チャームやトースト、あるいは自アプリから出したメッセージ・ダイアログやフライアウト、MediaElementコントロールで表示している動画などがキャプチャされない。別途公開しているサンプル・コードでは、メッセージ・ダイアログとフライアウトがキャプチャできないことを確かめられる(上の画面の右上にある2つのボタン)。

キャプチャした画像をファイルに保存するには?

 RenderTargetBitmapクラスのGetPixelsAsyncメソッドを使うと、キャプチャした画像のデータを取り出せるピクセル・バッファ(=Windows.Storage.Streams名前空間のIBufferインターフェイス)が得られる。ピクセル・バッファからは、画像データをbyte配列として取り出せる。あとは、BitmapEncoderクラス(Windows.Graphics.Imaging名前空間)を使って、そのbyte配列を特定の画像フォーマットに変換してファイルに書き込む。

 例えば、ファイル・セーブ・ピッカーを出して、ユーザーに保存するファイルを指定してもらい、そこへPNGフォーマットで画像を書き込むには、次のコードのようにする。ここでは、画面右下のImageコントロールのさらに右下にある[保存]ボタンのClickイベント・ハンドラに処理を記述した。

private async void saveButton_Click(object sender, RoutedEventArgs e)
{
  // ファイル・セーブ・ピッカーを出して、保存先を指定してもらう
  var savePicker = new Windows.Storage.Pickers.FileSavePicker();
  savePicker.DefaultFileExtension = ".png";
  savePicker.FileTypeChoices.Add("PNG", new List<string> { ".png" });
  savePicker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.PicturesLibrary;
  savePicker.SuggestedFileName = "snapshot.png";
  var saveFile = await savePicker.PickSaveFileAsync();
  if (saveFile == null)
    return;


  // キャプチャ画像をPNGフォーマットにエンコードしてファイルに保存するには、

  // キャプチャ画像をピクセル・バッファとして取得し、
  Windows.Storage.Streams.IBuffer pixelBuffer
    = await _renderTargetBitmap.GetPixelsAsync();

  // 保存先のファイルを開き、
  using (var fileStream = await saveFile.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite))
  {
    // ファイルにPNGフォーマットで書き込むためのエンコーダを用意し、
    var encoder = await Windows.Graphics.Imaging.BitmapEncoder.CreateAsync(
                          Windows.Graphics.Imaging.BitmapEncoder.PngEncoderId, // PNGフォーマット
                          fileStream);

    // キャプチャ画像の入ったピクセル・バッファから取得したbyte配列をエンコーダに渡して、
    // ファイルに書き込ませる
    encoder.SetPixelData(
        Windows.Graphics.Imaging.BitmapPixelFormat.Bgra8,  // 渡すデータはBGRA8フォーマット
        Windows.Graphics.Imaging.BitmapAlphaMode.Straight, // アルファ値は透明度として扱う
        (uint)_renderTargetBitmap.PixelWidth,  // 画像の幅
        (uint)_renderTargetBitmap.PixelHeight, // 画像の高さ
        Windows.Graphics.Display.DisplayInformation.GetForCurrentView().LogicalDpi, // 横方向のDPI
        Windows.Graphics.Display.DisplayInformation.GetForCurrentView().LogicalDpi, // 縦方向のDPI
        pixelBuffer.ToArray());
    await encoder.FlushAsync();
  }
}

Private Async Sub saveButton_Click(sender As Object, e As RoutedEventArgs)
  ' ファイル・セーブ・ピッカーを出して、保存先を指定してもらう
  Dim savePicker = New Windows.Storage.Pickers.FileSavePicker()
  savePicker.DefaultFileExtension = ".png"
  savePicker.FileTypeChoices.Add("PNG", New List(Of String) From {".png"})
  savePicker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.PicturesLibrary
  savePicker.SuggestedFileName = "snapshot.png"
  Dim saveFile = Await savePicker.PickSaveFileAsync()
  If (saveFile Is Nothing) Then
    Return
  End If


  ' キャプチャ画像をPNGフォーマットにエンコードしてファイルに保存するには、

  ' キャプチャ画像をピクセル・バッファとして取得し、
  Dim pixelBuffer As Windows.Storage.Streams.IBuffer _
      = Await _renderTargetBitmap.GetPixelsAsync()

  ' 保存先のファイルを開き、
  Using fileStream = Await saveFile.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite)
    ' ファイルにPNGフォーマットで書き込むためのエンコーダを用意し、
    Dim encoder = Await Windows.Graphics.Imaging.BitmapEncoder.CreateAsync(
                          Windows.Graphics.Imaging.BitmapEncoder.PngEncoderId,
                          fileStream) ' 「PngEncoderId」=PNGフォーマットを指定

    ' キャプチャ画像の入ったピクセル・バッファから取得したbyte配列をエンコーダに渡して、
    ' ファイルに書き込ませる(引数の意味はC#のコードを参照)
    encoder.SetPixelData(
        Windows.Graphics.Imaging.BitmapPixelFormat.Bgra8,
        Windows.Graphics.Imaging.BitmapAlphaMode.Straight,
        _renderTargetBitmap.PixelWidth,
        _renderTargetBitmap.PixelHeight,
        Windows.Graphics.Display.DisplayInformation.GetForCurrentView().LogicalDpi,
        Windows.Graphics.Display.DisplayInformation.GetForCurrentView().LogicalDpi,
        pixelBuffer.ToArray())
    Await encoder.FlushAsync()
  End Using
End Sub

ユーザーが指定したファイルにキャプチャ画像を書き出す(上:C#、下:VB)
GetPixelsAsyncメソッドなどはawaitキーワードを必要とするので、メソッドのシグネチャにはasyncキーワードを追加する必要がある。

親コントロールからはみ出したコントロールはどうなるか?

 最初の例では、キャプチャ対象のコントロールの領域内に見えているものでも、キャプチャ対象のコントロールを起点とするビジュアル・ツリーに含まれていないものはキャプチャされなかった(親コントロールの青緑色の背景)。では逆に、ビジュアル・ツリーに含まれているが、キャプチャ対象のコントロールの領域からはみ出している場合はどうなるだろうか? これを試す前にちょっと準備をしておこう。

 次のコードのようにして、XAMLコントロールのRenderTransformプロパティを使うとコントロールの位置を動かせる。

<Image Source="Assets/IC013.png" Stretch="Uniform" Opacity="0.75" RenderTransformOrigin="0.5,0.5" >
  <Image.RenderTransform>
    <CompositeTransform TranslateX="-100" TranslateY="-100"/>
  </Image.RenderTransform>
</Image>

RenderTransformプロパティを使ってコントロールの位置を変える例(XAML)
この例では左へ100ピクセル(=TranslateX属性)、上へ100ピクセル(=TranslateY属性)だけ移動させている。

 このRenderTransformプロパティを利用して、いくつかのコントロールをBorderコントロールの外にはみ出すように移動させてみる(次の画像)。

Borderコントロールの子コントロールをBorderコントロールからはみ出すように配置した Borderコントロールの子コントロールをBorderコントロールからはみ出すように配置した

 これでBorderコントロールを対象としてキャプチャすると(RenderAsyncメソッドの引数をthis.captureTargetに戻しておこう)、次の画像のようになる。

Borderコントロールからはみ出したコントロールまでキャプチャされる
Borderコントロールからはみ出したコントロールまでキャプチャされる Borderコントロールからはみ出したコントロールまでキャプチャされる
上の画像は、Borderコントロールをキャプチャしたときの画面。
下の画像は、Borderコントロールをキャプチャしてファイルに保存したもの。
はみ出したコントロールを包括する最小の矩形領域がキャプチャされている。画面の外にはみ出した部分までキャプチャされていることに注目。また、Borderコントロールのビジュアル・ツリーに含まれていないコントロール(画面上部のタイトルや右下のImageコントロールなど)はキャプチャされないことが、ここでも確認できる。

親コントロールからはみ出したコントロールをキャプチャしないためには?

 はみ出したコントロールをクリップすればよい。

 ただし、今回の例でいうと、Borderコントロールでクリップしてはだめ*3で、Borderコントロールの子どものコントロールでクリップしなければならない。例えば、Borderコントロールの中に置かれたGridコントロールを、次のコードのようにしてクリップする。

<Border x:Name="captureTarget" BorderBrush="Red" BorderThickness="5" Background="Transparent">
  <Grid>
    <!-- ↓正しくクリップするには、キャプチャ対象の子要素で行わねばならない -->
    <Grid.Clip>
      <RectangleGeometry Rect="-5.0, -5.0, 800.0, 540.0" />
    </Grid.Clip>
    ……省略……

Borderコントロールからはみ出す部分を、その子要素のGridコントロールでクリップした(XAML)
太字の部分を追加する。
Borderコントロールの外形サイズは、ここには明示されていないが幅800ピクセル/高さ540ピクセルである。
Borderコントロールのボーダー幅が5ピクセルあるので、その子であるGridコントロールの基準座標はBorderコントロールから見て(5,5)になっている。そのため、RectangleGeometryコントロールの起点を(-5,-5)としてBorderコントロールの基準座標に合わせている。Rect属性に与える値は順に、起点座標x/起点座標y/幅/高さである。

*3今回の例で、Borderコントロールでクリップすると、画面での見た目はクリップされるのだが、キャプチャ結果はクリップされない。画面ではクリップされて見えなくなっているコントロールまで、キャプチャされてしまう。


 これで実行してみると、次の画像のように画面での見た目も、キャプチャした画像も、きちんとクリップされる。

Borderコントロールのサイズでクリップした
Borderコントロールのサイズでクリップした Borderコントロールのサイズでクリップした
上の画像は、クリップした画面。
下の画像は、キャプチャした画像をファイルに保存したもの。
赤枠(=キャプチャ対象)から外にはみ出したコントロールはクリップされて、見えないしキャプチャ画像にも入らない。

まとめ

 Win 8.1アプリ用に新しく用意されたRenderTargetBitmapクラスを使うと、簡単に画面をキャプチャしてイメージ・ソースとして利用できる。それをファイルに保存するのはちょっと手間だが、それはキャプチャに限ったことではない。なお、見た目どおりにキャプチャされるのではなく、対象のコントロールを起点としたビジュアル・ツリーに含まれるコントロールだけがキャプチャされる。

 画面キャプチャと画像の処理については、次のドキュメントも参照してほしい。

「WinRT/Metro TIPS」のインデックス

WinRT/Metro TIPS

Copyright© 1999-2017 Digital Advantage Corp. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

Focus

- PR -

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。