検索
連載

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

古(いにしえ)からのVBでWindows 8.1向けのさまざまな機能のアプリを開発する手順やコツを解説していく。今回は、選択した画像と、テクスチャとなる画像を合成して保存する方法をサンプルを交えて解説する。

PC用表示 関連情報
Share
Tweet
LINE
Hatena

メイン画面のロジックコードを記述する(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 © ITmedia, Inc. All Rights Reserved.

ページトップに戻る