連載
» 2013年11月25日 18時00分 公開

2カ月で160本作った還暦開発者が送る10のアプリ開発ノウハウ(7):選択した画像と、テクスチャとなる画像を合成して保存するには (3/4)

[薬師寺国安,PROJECT KySS]

メイン画面のロジックコードを記述する(MainWindow.xaml.vb)

 次に、[ソリューション・エクスプローラー内]の「MainWindow.xaml」を展開して表示される、「MainWindow.xaml.vb」のコードを記述する。

 ここではコードが長くなるため今回のテーマと肝となる「画像の合成」「データの一覧表示」部分の解説にとどめている。名前空間の読み込み、メンバー変数の宣言も省略している。全てのコードを見る場合は、サンプルをダウンロードして「MainWindow.xaml.vb」ファイルを見ていただきたい。

ページがアクティブになった時の処理(OnNavigatedToメソッド処理)

Protected Overrides Async Sub OnNavigatedTo(e As Navigation.NavigationEventArgs)
        ichiranButton.Visibility = Xaml.Visibility.Visible
        Dim myFolder As StorageFolder = Windows.Storage.KnownFolders.PicturesLibrary
        Dim SubFolder = Await myFolder.CreateFolderAsync("BiltImage", CreationCollisionOption.OpenIfExists)
        Dim myPictureFile = Await SubFolder.GetFilesAsync
        If myPictureFile.Count > 0 Then
            ichiranButton.IsEnabled = True
        Else
            ichiranButton.IsEnabled = False
        End If
        MyBase.OnNavigatedTo(e)
    End Sub

 以降、上記コードの中身を詳細に見ていこう。

 まず、非同期処理で行われるためメソッドの先頭にAsyncを追加している。

 ピクチャライブラリーにアクセスし、CreateFolderAsyncメソッドで「BiltImage」というフォルダーを作成する。その際、「CreationCollisionOption.OpenIfExists」を指定すると、同名フォルダーがあるときは、そのフォルダー名を返し、ない時は新規に作成する。「CreationCollisionOption Enumeration」の詳細については、下記のURLを参照してほしい。

 GetFilesAsyncメソッドでは、「BiltImage」フォルダー内のファイルを取得し、変数myPictureFileで参照しておく。

 Countプロパティでは、そのフォルダー内のファイルの数を取得し、ファイルが存在する場合は、「データ一覧」ボタンの使用を可能にする。それ以外は使用を不可とする。

「元画像」ボタンがタップされた時の処理(readButton1_Clickメソッド処理)

    Private Async Sub readButton1_Click(sender As Object, e As RoutedEventArgs) Handles readButton1.Click
        resultImage.Source = Nothing
        Image1.Source = Nothing
        Dim myFileOpenPicker As New FileOpenPicker
        myFileOpenPicker.ViewMode = PickerViewMode.Thumbnail
        myFileOpenPicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary
        myFileOpenPicker.FileTypeFilter.Add(".png")
        myFileOpenPicker.FileTypeFilter.Add(".jpg")
        myFile = Await myFileOpenPicker.PickSingleFileAsync()
        If myFile Is Nothing = False Then
            Dim myBmp As New BitmapImage
            myBmp.SetSource(Await myFile.OpenReadAsync)
            If myBmp.PixelWidth <> 640 OrElse myBmp.PixelHeight <> 480 Then
                Dim message As New MessageDialog("640×480サイズの画像を指定してください")
                Await message.ShowAsync
                Exit Sub
            Else
                Image1.Source = myBmp
            End If
            readButton2.IsEnabled = True
        Else
            readButton2.IsEnabled = False
        End If
    End Sub

 以降、上記コードの中身を詳細に見ていこう。

 まず、非同期処理で行われるためメソッドの先頭にAsyncを追加している。

 FileOpenPickerクラスの新しいインスタンスmyFileOpenPickerオブジェクトを作成する。FileOpenPickerクラスは、ユーザーが選択し、ファイルを開くことのできるUI要素を表すクラスだ。

 ファイルの表示モードを指定するViewModeプロパティにサムネイル表示を指定する。サムネイル表示の他にリスト(List)表示がある。ファイルを開く最初の場所を設定するSuggestedStartLocationプロパティに、ピクチャライブラリーを指定しておく。開くファイルタイプを指定するFileTypeFilter.Addで「.png」と「.jpg」を指定しておく。

 PNGファイルを指定する場合は、FileTyleFilter.Add(".png")と指定する。"*.pn"ではないので注意してほしい。

 PickSingleFileAsynメソッドで、ユーザーが1つのファイルを選択できるようにファイルピッカーを表示し、変数myFileで参照する。

 変数myFileがファイルを参照している場合は、新しいBitmapImageクラスのインスタンスmyBmpオブジェクトに、SetSourceメソッドで「Await myFile.OpenReadAsync」と指定する。そして、ファイルの内容を読み込むために、現在のファイルをランダムアクセスストリームで開く。

 もし指定したファイルの画像サイズが640×480より大きかった場合は、メッセージを表示して処理を抜ける。それ以外の場合は、Image1のSourceプロパティにmyBmpオブジェクトを指定する。これで、ファイルピッカーで指定したファイルが表示される。

 また、画像を選択する際、選択をやめてキャンセルした場合も、「合成画像」ボタンの使用は不可としておく。それ以外の場合は使用可とする。

「合成画像」ボタンがタップされた時の処理(readButton2_Clickメソッド処理)

 以下の処理は、『「元画像」ボタンがタップされた時の処理(readButton1_Clickメソッド処理)』と同じであるため、そちらを参照してほしい。

    Private Async Sub readButton2_Click(sender As Object, e As RoutedEventArgs) Handles readButton2.Click
        Image2.Source = Nothing
        Dim myFileOpenPicker As New FileOpenPicker
        myFileOpenPicker.ViewMode = PickerViewMode.Thumbnail
        myFileOpenPicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary
         myFileOpenPicker.FileTypeFilter.Add(".png")
        myFileOpenPicker.FileTypeFilter.Add(".jpg")
        myFile2 = Await myFileOpenPicker.PickSingleFileAsync()
        If myFile2 Is Nothing = False Then
         Dim myBmp As New BitmapImage
            myBmp.SetSource(Await myFile2.OpenReadAsync)
            If myBmp.PixelWidth <> 640 OrElse myBmp.PixelHeight <> 480 Then
                Dim message As New MessageDialog("640×480サイズの画像を指定してください")
                Await message.ShowAsync
                Exit Sub
            Else
                Image2.Source = myBmp
            End If
            makeButton.IsEnabled = True
        Else
            makeButton.IsEnabled = False
        End If
    End Sub

「合成」ボタンがタップされた時の処理(makeButton_Clickメソッド処理)

 このアプリの肝となる、2枚の画像を合成する処理だ。Nugetで取得したWriteableBitmapEのBiltメソッドを使用する。

    Private Async Sub makeButton_Click(sender As Object, e As RoutedEventArgs) Handles makeButton.Click
        Dim myColor = Colors.White
        Dim wb1 As WriteableBitmap = New WriteableBitmap(CInt(Image1.Width), CInt(Image1.Height))
        Dim wb2 As WriteableBitmap = New WriteableBitmap(CInt(Image2.Width), CInt(Image2.Height))
        wb1.SetSource(Await myFile.OpenReadAsync)
        wb2.SetSource(Await myFile2.OpenReadAsync)
        Dim myRect As New Rect(0, 0, Image1.Width, Image1.Height)
     ‘ このアプリの肝。2枚の画像をBiltメソッドで合成する。
        wb1.Blit(myRect, wb2, myRect, myColor, WriteableBitmapExtensions.BlendMode.Multiply)
        wb1.Invalidate()
        Using myStream As Stream = wb1.PixelBuffer.AsStream 
            Await myStream.FlushAsync()
        End Using
        Dim myFolder As StorageFolder = Windows.Storage.KnownFolders.PicturesLibrary
        Dim SubFolder = Await myFolder.CreateFolderAsync("BiltImage", CreationCollisionOption.OpenIfExists)
        myFile = Await SubFolder.CreateFileAsync(DateTime.Now.ToString("yyyy年MM月dd日HH時mm分ss秒") & ".png")
        mySaveFile = myFile
        FileName = DateTime.Now.ToString("yyyy年MM月dd日HH時mm分ss秒") & ".png"
        Dim myID As Guid = Windows.Graphics.Imaging.BitmapEncoder.PngEncoderId
        Await WriteableBitmapSaveExtensions.SaveToFile(wb1, myFile, myID)
        ichiranButton.IsEnabled = True
        Image1.Source = Nothing
        Image2.Source = Nothing
        resultImage.Source = wb1
        makeButton.IsEnabled = False
        readButton2.IsEnabled = False
    End Sub

 以降、上記コードの中身を詳細に見ていこう。まず、非同期処理で行われるためメソッドの先頭にAsyncを追加している。

 次に、色をWhiteとしておく。他の色にすると画像自体にその色のフィルターが掛かってしまう。

「元画像」ボタンで取り込んだ、台紙となる画像サイズが640×480で初期化された、WriteableBitmapオブジェクトのインスタンスwb1を作成する。

 続いて、「合成画像」ボタンで取り込んだ、合成する画像サイズが640×480で初期化された、新しいWriteableBitmapオブジェクトのインスタンス、wb2を作成する。

 ストリームにアクセスしてBitmapSourceのソースイメージ(この場合、Await myFile.OPenReadAsync)を設定し、変数wb1で参照する(合成元の画像)。同じく、変数wb2で合成する画像を参照する。

 四角形の幅、高さ、および原点を示す新しいインスタンスmyRectを作成する。myRectのWidthに「元画像」ボタンで取り込んで表示されたImage1の「Widthの値」を、HeightにはImage1の「Heightの値」を指定する。

 WriteableBitmapEx のBiltメソッドで「元画像」と「合成画像」を合成する。書式は下記の通りだ。

Wb.Bilt(destRect As Rect,Source As Media.Imaging.WritableBitMap,sourceRect As Rect,color As Windows.UI.Color,BlendMode as Media.Imaging.WriteableBitmapExtensions.BlendMode)

 BlendModeには、必ずWriteableBitmapExtensions.BlendMode.Multiplyと指定する。Invalidateメソッドで、ビットマップ全体を再描画する。

 合成された画像の、各ピクセルの書き込み先のディレクトリバッファーのアクセスを取得し、Stream型の変数myStreamで参照する。FlushAsyncメソッドで、ストリームに対応する全てのバッファーを非同期にクリアし、バッファー内のデータをデバイスに書き込む。ピクチャライブラリーの「BiltImage」サブフォルダーにアクセスする。

 CreateFileAsyncメソッドで、「BiltImage」フォルダー内に「yyyy年MM月dd日HH時mm分ss秒.png」形式のファイルを作成する。

 Windows.Graphics.Imaging.BitmapEncoder.PngEncoderIdで、PNG画像のグローバル一意識別子を取得して、変数myIDに格納しておく。

 WinRTXamlToolkit.ImagingのWriteableBitmapSaveExtensions.SaveToFileメソッドで、合成した画像(wb1)を「yyyy年MM月dd日HH時mm分ss秒.png」形式に書き出す。WriteableBitmapSaveExtensions.SaveToFileメソッドの書式は、下記の通りだ。

WriteableBitmapSaveExtensions.SaveToFile(writeableBitmap as Windows.UI.Xaml.Media.Imageing.WriteableBitmap,output as Windows.Storages.StorageFile,encodeId as System.GUID)

 「データ一覧」アイコンを使用可能にし、Image1とImage2をクリアし、resultImageに合成された画像を表示する。「合成」ボタンと「合成画像」ボタンの使用を不可とする。

Copyright© 2017 ITmedia, Inc. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

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

RSSについて

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

メールマガジン登録

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