WriteableBitmapを利用した画像の操作
Silverlight 3で追加されたWriteableBitmapクラスを利用すると、Silverlightの要素をキャプチャーしたり、キャプチャーした画像の変換を行ったり、じかにピクセル情報を編集したりする機能を持っています。ここでは、WriteableBitmapクラスを利用していくつかのサンプルを解説したいと思います。
画面のキャプチャーを取り変換を行う
まずは現在の画面をキャプチャーして画面に表示するサンプルを考えてみましょう。WriteableBitmapはコンストラクターにUIElementとTransformを受け取るオーバーラードがあるので、Silverlightの画面キャプチャーを行う際はこのコンストラクターを利用してWriteableBitmapのインスタンスを作成します。
[リスト5]のような画面定義のXAMLがあったとして、テキストボックスに表示された内容をボタンクリックのタイミングで画像に変換し、キャンバスに画像を表示するサンプルを考えてみましょう。
<StackPanel x:Name="LayoutRoot">
<StackPanel Orientation="Horizontal">
<TextBlock Text="テキストを入力してください" />
<TextBox Width="150" x:Name="inputText" />
<Button Content="通常のキャプチャー" Click="NormalCapture_Click" />
<Button Content="2倍のサイズでキャプチャー" Click="BigCapture_Click" />
</StackPanel>
<Canvas x:Name="pictureCanvas" />
</StackPanel>
キャプチャーボタンがクリックされた時の記述は[リスト6]のようになります。
private void NormalCapture_Click(object sender, RoutedEventArgs e)
{
pictureCanvas.Children.Clear();
pictureCanvas.Children.Add(new Image()
{
Source = new WriteableBitmap(inputText, null),
});
}
この例では、ボタンがクリックされたタイミングで現在のキャンパスの内容をクリアし、WriteableBitmap経由でテキストボックス(inputText)の内容を画像に変換し、キャンパスに再描画しています。
もし、「取得した画像のサイズを2倍にして表示したい」といった場合は、WritableBitmapのコンストラクターの第2引数にTransformを指定して[リスト7]のように記述します。
private void BigCapture_Click(object sender, RoutedEventArgs e)
{
var tf = new ScaleTransform();
tf.SetValue(ScaleTransform.ScaleXProperty, 2.0);
tf.SetValue(ScaleTransform.ScaleYProperty, 2.0);
pictureCanvas.Children.Clear();
pictureCanvas.Children.Add(new Image()
{
Source = new WriteableBitmap(inputText, tf),
});
}
図10は2倍のサイズでキャプチャーボタンをクリックしたときの実行結果です。
キャプチャーを画像ファイルとして保存する。
WriteableBitmapでは、UIElementであればMediaElementに表示中の動画や、現在表示されているコンテナそのものなど、どんなものでもキャプチャーすることが可能です。Silvelright 3では画面の印刷を行う際にはJavascriptの印刷機能に頼ることが多いですが、WriteableBitmapを使えば、キャプチャー情報を画像としてクライアントのローカルファイルに保存し、印刷に関しては画像ビューワーなどの外部ツールに任せてしまうといったシナリオも考えられます。
[リスト8]は表示しているコンテンツを丸ごとキャプチャーして、ビットマップファイルに保存している例です。
private void Button_Click(object sender, RoutedEventArgs e)
{
// ファイルの保存ダイアログを表示
var saveDialog = new SaveFileDialog()
{
DefaultExt = ".bmp",
Filter = "ビットマップ(*.bmp)|*.bmp",
};
if (saveDialog.ShowDialog() == true)
{
// 現在の画面情報のキャプチャーを取得
var bitmap = new WriteableBitmap(this, null);
// 出力ファイルのストリームを取得
var fileStream = saveDialog.OpenFile();
// Bitmapファイル形式に変換して保存
SaveBitmapImage(bitmap, fileStream);
fileStream.Close();
}
}
/// <summary>
/// WritableBitmapに読み込んだBitmapをビットマップイメージに保存します。
/// </summary>
/// <remarks>
/// Bitmapのファイル形式はGoogleでの検索結果を参照
/// http://www.google.co.jp/search?rlz=1C1GGLS_jaJP357JP358&aq=f&sourceid=chrome&ie=UTF-8&q=bitmap+%E3%83%95%E3%82%A9%E3%83%BC%E3%83%9E%E3%83%83%E3%83%88
/// </remarks>
private void SaveBitmapImage(WriteableBitmap bitmap, Stream fileStream)
{
using (var buffer = new BinaryWriter(fileStream))
{
//ファイルヘッダー
buffer.Write((byte)'B');
buffer.Write((byte)'M');
buffer.Write(54 + bitmap.Pixels.Length * 4);
buffer.Write((short)0);
buffer.Write((short)0);
buffer.Write(54);
// 情報ヘッダー
buffer.Write(40);
buffer.Write(bitmap.PixelWidth);
buffer.Write(-bitmap.PixelHeight);
buffer.Write((short)1);
buffer.Write((short)32);
buffer.Write(new byte[24]);
// ファイルデータ
foreach (var px in bitmap.Pixels)
{
buffer.Write(px);
}
}
}
分離ストレージ以外のユーザーストレージに画像を保存するためには、SaveFileDialogクラスを使いユーザーに対話的にファイルを選択してもらう必要があります。SaveFileDialogで保存ファイル先のファイルが選択されると、OpenFileメソッドからファイルのストリームを取得できます(図11)。
WriteableBitmapクラスではキャプチャーした画像のピクセルごとに色情報は保持していますが、そのまま画像ファイルに変換する機能は実装されていません。今回は自作のSaveBitmapImageメソッドを用意し、画像ファイルのフォーマットでデータを書き出しています。
まとめ
エフェクトとかシェーダーとかいうと拒否反応を起こしそうになる筆者ですが、今回調べていくなかではそれほど低いレイヤーでのプログラムが必要になるということもなく、Silverlight 3で新しく追加になった疑似3Dで画像を変換したり、コントロールにエフェクトを適用したりといった使い方であれば問題なく利用できました。
3Dやエフェクトと言うと身構えてしまいがちですが、結構簡単に実現できてしまうので、まずは試してみてはどうでしょうか。

![図10:[リスト6]、[リスト7]の実行結果](http://cz-cdn.shoeisha.jp/static/images/article/4953/10s.gif)
