[win8.1] ストアアプリで手軽に画像を加工しよう

Windows Store App Advent Calendar 2013
http://qiita.com/advent-calendar/2013/win-store-app/participants
の4日目です。

Windows 8のストアアプリの場合は、画像の加工が大変だったのですが、8.1の場合は「<a href=”http://msdn.microsoft.com/ja-jp/library/system.windows.media.imaging.rendertargetbitmap(v=vs.110).aspx”>RenderTargetBitmap クラス </a>」を使うと「手軽」にできますっ!!! というネタです。実際に手軽にできるかどうかは別として(!?)、DirectXを使ったり、Unityを使ったり、OpenCVを使ったり、いろいろ独自で画像処理をがんばったりしなくても、できる範囲ができました。って感じですね。8 の時は手が出しようがなかったのですが、まあ、できるようになった第一歩かと。ちなみに、この手の方法で画像加工をする場合は、WPFで組むほうが断然楽です。いろいろなエフェクトも用意されているし、図形なんかもいろいろあります。WinRTの場合は、結構画像まわりが削られてしまっているのでBlendを使ってもあまりうまくできなことが多いのです。
が、あえてストアアプリでやる利用があるとすれば、画像のプレビューとか、ちょっとしたフレームの加工とか、そんな感じですかね。本格的なところはDirectXを使ったほうがいい気がします。ちょっと、DirectX – C++/CXの調査は停滞していて進めていないのですが。

■できあがりイメージ

出来上がりは、こんな感じです。

画像の上に文字を張り付けたり(これが一番簡単)、切り抜きをしたり、透明度を変えて擬似的に色調を変えてみたり、マスクをかけてみたり。それぞれは、XAMLでキャンバス(canvas)上に重ね合わせています。重ねた後の画像をそのまま保存できるのが、RenderTargetBitmapクラスの利点です…つーか、WPFのほうがすんなり使えるんですがね。

■重ね合わせの方法

重ね合わせの部分をピックアップすると、下記のようにCanvasの上に貼り付けていきます。RenderTargetBitmap自体は、UIElementのレンダリングを保存できるので、GridでもOKですし、ImageそのままでOKです。

<Canvas 
        x:Name="panel4"
        Tapped="panel_Tapped"
        HorizontalAlignment="Left" Height="200" VerticalAlignment="Top" Width="200" Canvas.Left="224" Margin="783,173,0,0">

    <Image 
            x:Name="img4"
            Source="ms-appx:///images/haruna.jpg"
            HorizontalAlignment="Left" Height="200"  VerticalAlignment="Top" Width="200" Opacity="0.5"/>
    <Image 
            x:Name="img4mask"
            Source="ms-appx:///images/mask.png"
            HorizontalAlignment="Left" Height="200"  VerticalAlignment="Top" Width="200" Opacity="1"/>
    <TextBlock 
            FontSize="32"
            Canvas.Left="10" TextWrapping="Wrap" Text="マスク" Canvas.Top="151"/>
</Canvas>

ここではマスク画像を、mask.png であらかじめ作っておいて重ね合わせているわけですが、本当は動的に作りたいところですよね。

■ファイルの読み込み

定番のファイル読み込みは、こんな感じ。FileOpenPicker を使います。

/// <summary>
/// ファイルを選択
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void Button_Click(object sender, RoutedEventArgs e)
{
    var picker = new FileOpenPicker();
    picker.CommitButtonText = "画像を選択する";
    picker.FileTypeFilter.Add(".jpg");
    picker.FileTypeFilter.Add(".jpeg");
    picker.FileTypeFilter.Add(".png");
    picker.ViewMode = PickerViewMode.Thumbnail;
    picker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
    // 画像ファイルを指定する
    var file = await picker.PickSingleFileAsync();
    // 選択されなかった場合
    if (file == null)
    {
        return;
    }
    // 画面へ表示
    var stream = await file.OpenReadAsync();
    var bmp = new BitmapImage();
    bmp.SetSource(stream);
    this.img1.Source = bmp;
    this.img2.Source = bmp;
    this.img3.Source = bmp;
    this.img4.Source = bmp;
}

ペタペタと4つ分 Image を貼っていますが、実際のツールにする場合は配列をつかうとか諸々修正してください。

■ファイルへ保存

画像ファイルとして保存するときは、選択は FileSavePicker です。PanelSave.Save ってのが独自に作ったところで(実際は、Microsoft さんのサンプル集にあります)。

/// <summary>
/// パネルをクリック
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void panel_Tapped(object sender, TappedRoutedEventArgs e)
{
    var panel = sender as UIElement;
    // 保存先を選択する
    var savePicker = new FileSavePicker();
    savePicker.DefaultFileExtension = ".png";
    savePicker.FileTypeChoices.Add(".png", new List<string> { ".png" });
    savePicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
    savePicker.SuggestedFileName = "snapshot.png";
    var saveFile = await savePicker.PickSaveFileAsync();
    // キャンセルされた場合
    if (saveFile == null)
        return;
    // ファイルに保存
    PanelSave.Save( panel, saveFile);
}

ざっと、該当箇所を整理しただけなので、こんな感じ。RenderTargetBitmapクラスで、レンダリングした後の画像バッファを取り出して、BitmapEncoderを使ってファイルを保存するという流れですね。メモリ上でやりたいときは、MemoryStream を使えばいいはずなのですが、これはまだ試していません。
StorageFile だと、ユーザーがファイルを指定しないといけないので、アプリケーションのデータフォルダやピクチャーフォルダに編集後のファイルを複数ファイル自動で保存できるといいので。このあたりは、また調べていく予定です。

public class PanelSave
{
    /// <summary>
    /// 指定した要素をレンダリングして保存
    /// </summary>
    /// <param name="el"></param>
    /// <param name="saveFile"></param>
    public static async void Save( UIElement el,  StorageFile saveFile )
    {
        // レンダリングをして画像バッファを取得
        RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap();
        await renderTargetBitmap.RenderAsync(el);
        var pixelBuffer = await renderTargetBitmap.GetPixelsAsync();

        // Encode the image to the selected file on disk
        using (var fileStream = await saveFile.OpenAsync(FileAccessMode.ReadWrite))
        {
            // PNG形式で保存
            var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, fileStream);
            encoder.SetPixelData(
                BitmapPixelFormat.Bgra8,
                BitmapAlphaMode.Ignore,
                (uint)renderTargetBitmap.PixelWidth,
                (uint)renderTargetBitmap.PixelHeight,
                DisplayInformation.GetForCurrentView().LogicalDpi,
                DisplayInformation.GetForCurrentView().LogicalDpi,
                pixelBuffer.ToArray());
            await encoder.FlushAsync();
        }
    }
}

保存するとこんな感じ。

■どうやって「手軽」に作るか?

重ね合わせのキャンバスを手軽に保存できることはわかったのですが、じゃあ画像の加工は手軽にできるのか?ってのが問題ですね。ちょこっとやった感じでは、手軽に作れる範囲はかなり限られます。マスク画像や切り抜き画像は、あらかじめマスクや図形の合成を作っておかないといけないので、結構コツが要ります。思い通りできるまでにBlendと格闘、さらにはアドビ系のツールとかExpression Designerとの格闘が必要になるので、手軽にとはいきません。ああ、XAMLでお絵かきできる方ならば、あるいは「手軽」かもと思いもしますが。

  • 文字を画像に重ね合わせる(コピーライトとか日付を表示とか)
  • 画像をフレームに合わせる(あらかじめ、フレームを透過PNGで作っておく)
  • 透明度を変更させて、それっぽい感じに(セピア色とか)

画像自体に手を加えるのは、DirectX の範疇になってしまうので、あくまでXAMLの図形を重ね合わせてできる範囲ですね。画像をブラシにすれば図形の内側に塗り付けてという裏技もあるので結構なことができるはずなのですが、それはまた後日(まだ調べきれていない。WPFだとできるのはわかっているんですが)。

■サンプルコード

使ったサンプルコードは、こちら。
http://sdrv.ms/IpvVFF

カテゴリー: 開発, WinRT パーマリンク