撮影した写真と手持ちの画像を合成して保存するサンプルアプリ

2012年12月11日(火)
薬師寺 国安

[合成]ボタンをクリックした時の処理

画像を合成するBiltメソッドのColorに指定する色をWhiteにしています。

Image1(元画像)のWidthとHeightで初期化された、新しいWriteableBitmapのインスタンスwb1オブジェクトを作成します。

Image2(テクスチャ)のWidthとHeightで初期化された、新しいWriteableBitmapのインスタンスw2オブジェクトを作成します。

イメージデータやファイルからデータを読み取る、FromStreamメソッドで「元画像」のデータを読み取り、myBilt1変数で参照しておきます。

同じく、「テクスチャ」の画像データを読み取りmyBilt2変数で参照しておきます。

表示領域を指定するRect構造体の新しいインスタンス、myRectオブジェクトを作成します。

Biltメソッドで2枚の画像を合成します。Biltの書式は下記の通りです。myBilt1をmyBilt2で、myRectの領域に、色をWhiteに指定して、BlendModeをMultiplyで合成します。Multiplyはデスティネーションカラーとソースカラーを乗算するモードです。ColorをRedに指定すると、全体が、赤味がかって表示されます。

WriteableBitmap.Bilt(destRect As Rect,source As WriteableBitmap,sourceRect As Rect,color As Color,BlendMode As WriteableBitmapExtension.BlendMode)

Image3にmyBilt1オブジェクトを指定します。2枚の画像が合成されて表示されます。

Invalidateメソッドで、ビットマップ全体の再描画を行います。[保存]ボタンの使用を可能にします。

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

Private Async Sub makeButton_Click(sender As Object, e As RoutedEventArgs) Handles makeButton.Click
    Dim myColor = Colors.White
    wb1 = New WriteableBitmap(CInt(Image1.Width), CInt(Image1.Height))
    Dim wb2 As WriteableBitmap = New WriteableBitmap(CInt(Image2.Width), CInt(Image2.Height))
    myBilt1 = Await wb1.FromStream(Await myFile.OpenReadAsync)
    Dim myBilt2 = Await wb2.FromStream(Await myFile2.OpenReadAsync)
    Dim myRect As New Rect(0, 0, Image1.Width, Image1.Height)
    myBilt1.Blit(myRect, myBilt2, myRect, myColor, WriteableBitmapExtensions.BlendMode.Multiply)
    Image3.Source = myBilt1
    myBilt1.Invalidate()
    saveButton.IsEnabled = True
  End Sub

[保存]ボタンがクリックされた時の処理

合成された画像(myBilt1)のストリームを取得し、FlushAsyncメソッドで、ストリームに対応する全てのバッファを非同期にクリアし、バッファ内のデータを基となるデバイスに書き込みます。

合成された画像(myBilt1)のストリームの長さで初期化された、バイト配列のオブジェクトmyPixelを作成します。ReadAsyncメソッドで、現在のストリームからバイトシーケンスを非同期的に読み取り、読み取ったバイト数だけストリームの位置を進めます。

インデックスとなる変数offSetを宣言します。合成された画像(myBilt1)の高さと幅に対して、反復処理を行います。offSet変数には、4ビットを乗算したバイト型の配列変数myPixelの、インデックスになる値を格納します。

バイト型のBRGA(青、赤、緑、アルファ)変数に、ピクセルのバイト数を転送し、offSetに対応するmyPixelバイト変数にRGBAの値を指定します。

ピクチャライブラリのサブフォルダーTextureにアクセスします。CreateFileAsyncメソッドで、「年月日時間分秒.png」ファイルを作成します。CreateAsyncメソッドは、フォルダーまたはファイルグループに新規ファイルを作成するメソッドです。

OpenAsyncメソッドで読み取りと書き込み可能な状態で、作成したPNG画像を開きます。OpenAsyncメソッドは、ファイルのランダムアクセスストリームを開くメソッドです。

イメージデータの作成、編集、保存に使用されるイメージエンコーダーを含む、BitmapEncoderクラス用の変数myEncoderを宣言します。
CreateAsyncメソッドで、指定済みコーデックのために新しいBitmapEncoderを非同期的に作成し、ストリーム上でこれを初期化します。
CreateAsyncメソッドには、「指定したエンコーダーのCLSID」と「ストリームのイメージファイルが書き込まれる場所」を指定します。ここでは、「指定したエンコーダーのCLSID」には、BitmapEncoder.PngEncoderIdを、「ストリームのイメージファイルが書き込まれる場所」にはmyFileStreamを指定しています。

BitmapEncoderクラスのSetPixelDataメソッドで、引数で指定されたパラメーターを使用して、フレームビットマップピクセルデータを指定します。SetPixelDataの書式は下記の通りです。

BitmapEncoder.SetPixelData(ピクセルデータのピクセル形式,ピクセルデータのアルファモード,ピクセルの幅,ピクセルの高さ, 水平方向の解像度のドット/インチ、ピクセル データ(通常96), 垂直方向の解像度のドット/インチ、ピクセル データ(通常96),ピクセルデータ)

BitmapEncoderのFlushAsyncメソッドで、非同期的に現在のフレームデータをコミットし、イメージファイルのデータ全てをフラッシュします。合成した画像が保存されます。

保存した旨のメッセージを表示します。

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

  Private Async Sub saveButton_Click(sender As Object, e As RoutedEventArgs) Handles saveButton.Click
    Using myStream As Stream = myBilt1.PixelBuffer.AsStream 
    Await myStream.FlushAsync()
 
    Dim myPixel As Byte() = New Byte(CInt(myStream.Length - 1)) {}
    Await myStream.ReadAsync(myPixel, 0, myPixel.Length - 1)
    Dim offSet As Integer
 
    For row As Integer = 0 To CInt(CUInt(myBilt1.PixelHeight) - 1)
      For col As Integer = 0 To CInt(CUInt(myBilt1.PixelWidth) - 1)
        offSet = CInt((row * CUInt(myBilt1.PixelWidth) * 4) + (col * 4))
        Dim B As Byte = myPixel(offSet)
        Dim G As Byte = myPixel(offSet + 1)
        Dim R As Byte = myPixel(offSet + 2)
        Dim A As Byte = myPixel(offSet + 3)
        myPixel(offSet) = R
        myPixel(offSet + 1) = G
        myPixel(offSet + 2) = B
        myPixel(offSet + 3) = A
      Next
    Next
 
    Dim myFolder As StorageFolder = Windows.Storage.KnownFolders.PicturesLibrary
    Dim SubFolder = Await myFolder.CreateFolderAsync("Texture", CreationCollisionOption.OpenIfExists)
    Dim mySaveFile As StorageFile = Await SubFolder.CreateFileAsync(DateTime.Now.ToString("yyyyMMddHHmmss") & ".png")
 
    Using myFileStream As IRandomAccessStream = Await mySaveFile.OpenAsync(FileAccessMode.ReadWrite)
      Dim myEncoder As BitmapEncoder = Await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, myFileStream)
        myEncoder.SetPixelData(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Premultiplied, CUInt(myBilt1.PixelWidth), CUInt(myBilt1.PixelHeight), 96, 96, myPixel)
        Await myEncoder.FlushAsync
        'Await myFileStream.GetOutputStreamAt(0).FlushAsync()
      End Using
    End Using
    Dim message As New MessageDialog("保存しました。")
    Await message.ShowAsync
  End Sub
End Class

今回はここまでです。ありがとうございました。

  • 2枚の画像を合成して保存するサンプルアプリ

薬師寺国安事務所

薬師寺国安事務所代表。Visual Basic プログラミングと、マイクロソフト系の技術をテーマとした、書籍や記事の執筆を行う。
1950年生まれ。事務系のサラリーマンだった40歳から趣味でプログラミングを始め、1996年より独学でActiveXに取り組む。1997年に薬師寺聖とコラボレーション・ユニット PROJECT KySS を結成。2003年よりフリーになり、PROJECT KySS の活動に本格的に参加、.NETやRIAに関する書籍や記事を多数執筆する傍ら、受託案件のプログラミングも手掛ける。Windows Phoneアプリ開発を経て、現在はWindows ストア アプリを多数公開中

Microsoft MVP for Development Platforms - Client App Dev (Oct 2003-Sep 2012)。Microsoft MVP for Development Platforms - Windows Phone Development(Oct 2012-Sep 2013)。Microsoft MVP for Development Platforms - Client Development(Oct 2013-Sep 2014)。Microsoft MVP for Development Platforms-Windows Platform Development (Oct 2014-Sep 2015)。

連載バックナンバー

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています