Windows 8.1ストア・アプリでは、自アプリの画面キャプチャが撮れる。画面キャプチャ機能を提供するRenderTargetBitmapクラスの使い方と注意点を解説する。
powered by Insider.NET
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」から無償で入手できる。
Win 8.1アプリ用に新しく用意されたRenderTargetBitmapクラスで画面キャプチャをするには、次の表に示す2つのメソッドを主に利用する。
メソッド | 説明 |
---|---|
RenderAsync | 画面をキャプチャする。 キャプチャ対象は、引数に指定したコントロールと、その子どものコントロール。また、キャプチャ後の画像サイズを指定することも可能 |
GetPixelsAsync | キャプチャした画像をバイト・ストリームとして取り出す。 画像のフォーマットは、BGRA8(=ピクセル当たり青/緑/赤/アルファ各8bit、計32bit) |
また、RenderTargetBitmapクラスはImageSourceクラスを継承しているので、ImageコントロールのSourceプロパティやImageBrushコントロールのImageSourceプロパティなどにRenderTargetBitmapオブジェクトを直接セットできる。
RenderTargetBitmapオブジェクトを用意しておき、RenderAsyncメソッドを呼び出すときにキャプチャしたいコントロールを渡せばよい。指定したコントロールと、その子どものコントロールの全てがキャプチャされる。
○画面を作る
実際に試してみるために、次のような画面を用意する。
まず、キャプチャする対象となる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>
次に、キャプチャした画像を表示するためのImageコントロール(上の画像で右下にある濃い灰色の四角)を配置する(次のコード)。
<Border Background="#444444" BorderBrush="LightGray" BorderThickness="2" >
<Image x:Name="image1" HorizontalAlignment="Stretch" MinHeight="150" Stretch="Uniform" />
</Border>
○キャプチャする
まず、コードビハインドのメンバ変数として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オブジェクトは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
そうしたら、次のコードのようにすれば、キャプチャした結果を「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
たったこれだけで、次の画像のようにキャプチャした画像が「image1」コントロールに表示される。
これはナビゲーション用のサムネイルなどに応用できるだろう。ただし、サムネイルとして使う場合には、画像サイズを小さくした方がよい。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)
*2Window.Current.Contentオブジェクト: 現在表示している画面の最上位レベルのコントロール。通常は、App.xaml.cs/.vbのOnLaunchedメソッドでFrameコントロール(Windows.UI.Xaml.Controls名前空間)が設定されている。
これで、次の画像のように画面全体のキャプチャが「image1」コントロールに表示される。
このように簡単に画面キャプチャが撮れるのだが、キャプチャ対象を起点とするビジュアル・ツリーに含まれないものはキャプチャされないことに注意してほしい。最初の例では、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
最初の例では、キャプチャ対象のコントロールの領域内に見えているものでも、キャプチャ対象のコントロールを起点とするビジュアル・ツリーに含まれていないものはキャプチャされなかった(親コントロールの青緑色の背景)。では逆に、ビジュアル・ツリーに含まれているが、キャプチャ対象のコントロールの領域からはみ出している場合はどうなるだろうか? これを試す前にちょっと準備をしておこう。
次のコードのようにして、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プロパティを利用して、いくつかのコントロールをBorderコントロールの外にはみ出すように移動させてみる(次の画像)。
これでBorderコントロールを対象としてキャプチャすると(RenderAsyncメソッドの引数をthis.captureTargetに戻しておこう)、次の画像のようになる。
はみ出したコントロールをクリップすればよい。
ただし、今回の例でいうと、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>
……省略……
*3今回の例で、Borderコントロールでクリップすると、画面での見た目はクリップされるのだが、キャプチャ結果はクリップされない。画面ではクリップされて見えなくなっているコントロールまで、キャプチャされてしまう。
これで実行してみると、次の画像のように画面での見た目も、キャプチャした画像も、きちんとクリップされる。
Win 8.1アプリ用に新しく用意されたRenderTargetBitmapクラスを使うと、簡単に画面をキャプチャしてイメージ・ソースとして利用できる。それをファイルに保存するのはちょっと手間だが、それはキャプチャに限ったことではない。なお、見た目どおりにキャプチャされるのではなく、対象のコントロールを起点としたビジュアル・ツリーに含まれるコントロールだけがキャプチャされる。
画面キャプチャと画像の処理については、次のドキュメントも参照してほしい。
Copyright© Digital Advantage Corp. All Rights Reserved.