PCで撮影した写真を並べて最適な1枚を選べるプログラムをつくる

2013年9月12日(木)
薬師寺 国安

Webカメラが装備されてなくてエラーが発生した時の処理

警告メッセージを表示し、「Attach Camera」の使用を不可として、処理を抜けます。非同期処理で行われるためメソッドの先頭にAsyncを追加します。

  Private Async Sub ErrorShow()
    Dim message As New MessageDialog("カメラが装備されておりません。")
    Await message.ShowAsync
    shutterButton.IsEnabled = False
    Exit Sub
  End Sub

Webカメラのデバイスの種類が選択された時の処理

デバイスの表示されている選択ボックスから選択したインデックス番号を、メンバ変数CameraNoに格納します。
AppBar1のバーを表示状態にします。
MediaCaptureの新しいインスタンスmyMediaCaptureオブジェクトを作成します。
InitializeAsyncメソッドで、以下の内容でMediaCaptureオブジェクトを初期化します。
新しいMediaCaptureInitializationSettingsクラスのインスタンスを作成します。MediaCaptureInitializationSettingsクラスは、MediaCaptureオブジェクトの初期化設定をするクラスです。VideoDevideIdプロパティにComboBox1より選択されたインデックスに対応する、コレクション変数myCameraのデバイスIDを指定します。
CaptureElement1のSourceプロパティにデバイスIDで初期化されたmyMediaCaptureを指定します。StartPreviewAsyncメソッドでプレビューを開始します。これで、Webカメラの画像が表示されます。マウスの右クリックで表示される、「Attach Camera」アイコンの使用を可能にします。非同期処理で行われるため、メソッドの先頭にAsyncを追加します。

  Private Async Sub cameraComboBox_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles cameraComboBox.SelectionChanged
    Try
      cameraNo = cameraComboBox.SelectedIndex
      AppBar1.Visibility = Xaml.Visibility.Visible
      myMediaCapture = New MediaCapture
      Await myMediaCapture.InitializeAsync(New MediaCaptureInitializationSettings With {.VideoDeviceId = myCamera(cameraComboBox.SelectedIndex).Id})
      CaptureElement1.Source = myMediaCapture
      Await myMediaCapture.StartPreviewAsync
      shutterButton.IsEnabled = True
    Catch
      Exit Sub
    End Try
  End Sub

「Attach Camera」アイコンがタップされた時の処理

MediaElement1を再生します。つまりシャッター音が鳴ります。
ピクチャライブラリにアクセスし、CreateFolderAsyncメソッドで、ピクチャフォルダ内にPhotoAlbumというサブフォルダを作成します。その際、CreationCollisionOption.OpenIfExistsと指定しておくと、同名フォルダやファイルがある場合は、そのフォルダやファイル名を返し、無い場合は新規に作成してくれます。
CreateFileAsyncメソッドで現在の年月日時間分秒.pngファイルを作成します。メンバ変数saveImageFileNameに、このpngファイル名を格納しておきます。
イメージストリームの書式を表す新しい、ImageEncodingPropertiesクラスのインスタンスmyImageEncodingPropertyオブジェクトを作成します。書式のサブタイプを表すSubtypeプロパティにpngを指定し、Widthに640、Heightに480と指定します。
CapturePhotoToStorageFileAsyncメソッドで、ストレージファイルにフォトをキャプチャします。書式は以下の通りです。

CapturePhotoToStorageFileAsync(ImageEncodingProperties,IStorageFile)

GetFilesAsyncメソッドでPhotoAlbumフォルダ内のファイルを取得し、Countプロパティで個数を取得して、メンバ変数Indexに格納します。
新しいBitmapImageのインスタンスsaveBmpオブジェクトを作成します。SetSourceメソッドに、
Await myPictureFiles(Index - 1).OpenReadAsync
と指定し、OpenReadAsyncメソッドで、ランダムアクセスストリームを開き、BitmapSourceのソースイメージに設定します。
新しいImageのインスタンスmyImageオブジェクトを作成します。Widthに320、Heightに240、SourceプロパティにsaveBmpオブジェクトを指定します。
新しいStackPanelのインスタンスmyStackPanelオブジェクトを作成します。UIElementの描画位置に影響する変換情報を設定するRenderTransformプロパティに、1 つのオブジェクトに複数の異なる変換を適用することができるCompositeTransformを指定します。
ジェスチャとともにUIElementの動作および操作に使用される、ManipulationModeに、すべての相互作用モードを有効にする、ManipulationModes.Allを指定します。UIElement. ManipulationModes列挙体については、下記のURLを参考にしてください。
→ http://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.input.manipulationmodes
SetValueメソッドでZIndexPropertyの値に1ずつ加算されるメンバ変数noの値を指定します。選択されたStackPanelが前面に表示されます。
新しいButtonのインスタンスdelButtonオブジェクトを作成します。Contentプロパティに「削除」、Tagプロパティに、1ずつ加算されるメンバ変数noの値を文字列にキャストして指定します。どの「削除」ボタンがクリックされたかの判定に使用します。
myStackPanelオブジェクトに、myImage、delButtonオブジェクトを追加し、PhotoAreaという名前を持つCanvas内にAddメソッドでmyStackPanelオブジェクトを追加します。写した写真が同じ位置に重なって表示されます。
AddHandlerステートメントで、操作中に入力デバイスが位置を変更した時に発生するManipulationDeltaイベントにイベントハンドラを追加します。イベントハンドラ内では以下の処理を行います。
TranslateXとTranslateYでx軸とy軸にそって並行移動する距離を設定します。
ScaleXとScaleYでオブジェクトを拡大縮小する値を設定します。
Rotaionプロパティでオブジェクトを回転します。CenterXとCenterYには実寸の画像の半分のWidthとHeightの値を指定します。これで、画像の中心を起点として回転します。
これによって、写された画像は、ピンチで拡大縮小、回転が可能になります。
SetValueメソッドでZIndexPropertyに1ずつ加算されるメンバ変数noの値を指定します。すると、選択された画像が一番手前に配置され、マウスのドラッグで写された画像が、好きな位置に配置できるようになります。
AddHandlerステートメントで、delButtonがクリックされた時のイベントハンドラを追加します。イベントハンドラ内では以下の処理を行います。
Buttonの情報を保持しているdelSenderオブジェクトをDirectCastでButtonにキャストし、そのTagプロパティの値を取得し、数値に変換して変数delIndexに格納しておきます。
コレクションメンバ変数である、myPictureFiles内のdelIndexに該当する画像ファイルを、DeleteAsyncメソッドで削除します。「削除しました。」のメッセージを表示し、画面を再描画するために、DataReadプロシージャを実行します。
非同期処理で行われるためメソッドの先頭にAsyncを追加します。

  Private Async Sub shutterButton_Click(sender As Object, e As RoutedEventArgs) Handles shutterButton.Click
    MediaElement1.Play()
    Dim myFolder As StorageFolder = Windows.Storage.KnownFolders.PicturesLibrary
    Dim mySubFolder = Await myFolder.CreateFolderAsync("PhotoAlbum", CreationCollisionOption.OpenIfExists)
    Dim myFile As StorageFile = Await mySubFolder.CreateFileAsync(DateTime.Now.ToString("yyyy年MM月dd日HH時mm分ss秒") & ".png")
    saveImageFileName = DateTime.Now.ToString("yyyy年MM月dd日HH時mm分ss秒") & ".png"
    Dim myImageEncodingProperty As New ImageEncodingProperties
    myImageEncodingProperty.Subtype = "png"
    myImageEncodingProperty.Width = 640
    myImageEncodingProperty.Height = 480
 
      Await myMediaCapture.CapturePhotoToStorageFileAsync(myImageEncodingProperty, myFile)
 
    myPictureFiles = Await mySubFolder.GetFilesAsync()
    Index = myPictureFiles.Count
    saveBmp = New BitmapImage
    saveBmp.SetSource(Await myPictureFiles(Index - 1).OpenReadAsync)
    Dim myImage As New Image
    With myImage
      .Width = 320
      .Height = 240
      .Source = saveBmp
    End With
 
    Dim myStackPanel As New StackPanel
    With myStackPanel
      .RenderTransform = New CompositeTransform
      .ManipulationMode = ManipulationModes.All
      .SetValue(Canvas.ZIndexProperty, no)
    End With
 
    Dim delButton As New Button
    delButton.Content = "削除"
    delButton.Tag = no.ToString
 
    myStackPanel.Children.Add(myImage)
    myStackPanel.Children.Add(delButton)
 
    PhotoArea.Children.Add(myStackPanel)
 
    AddHandler myStackPanel.ManipulationDelta, Sub(mySender As Object, myArgs As ManipulationDeltaRoutedEventArgs)
      Dim myTrans = DirectCast(myStackPanel.RenderTransform, CompositeTransform)
      myTrans.TranslateX = myTrans.TranslateX + myArgs.Delta.Translation.X
      myTrans.TranslateY = myTrans.TranslateY + myArgs.Delta.Translation.Y
      myTrans.ScaleX = myTrans.ScaleX * myArgs.Delta.Scale
      myTrans.ScaleY = myTrans.ScaleY * myArgs.Delta.Scale
      myTrans.CenterX = 320
      myTrans.CenterY = 240
      myTrans.Rotation = myTrans.Rotation + myArgs.Delta.Rotation
      myStackPanel.SetValue(Canvas.ZIndexProperty, no)
    End Sub
    AddHandler delButton.Click, Async Sub(delSender As Object, delArgs As RoutedEventArgs)
                                Dim delIndex = CInt(DirectCast(delSender, Button).Tag)
                                Await myPictureFiles(delIndex).DeleteAsync
                                Dim message As New MessageDialog("削除しました。")
                                Await message.ShowAsync
                                DataReload()
                              End Sub
    no = no + 1
    ichiranButton.IsEnabled = True
  End Sub

画像を削除した際に、画面を再描画する処理

PhotoAreaという名前のCanvas内をクリアし、メンバ変数noを0で初期化します。
ピクチャライブラリのサブフォルダPhotoAlbumにアクセスします。GetFilesAsyncメソッドで、ファイルを取得してコレクションメンバ変数myPictureFilesに格納します。
コレクション変数myPictureFiles内のファイルを変数myFileに格納しながら、以下の処理を繰り返します。
新しいBitmapImageのインスタンスsaveBmpオブジェクトを作成します。SetSourceメソッドに、
Await myFile.OpenReadAsync
と指定し、OpenReadAsyncメソッドで、ランダムアクセスストリームを開き、BitmapSourceのソースイメージに設定します。
新しいImageのインスタンスmyImageオブジェクトを作成します。Widthに320、Heightに240、SourceプロパティにsaveBmpオブジェクトを指定します。
新しいStackPanelのインスタンスmyStackPanelオブジェクトを作成します。
UIElementの描画位置に影響する変換情報を設定するRenderTransformプロパティに、1 つのオブジェクトに複数の異なる変換を適用することができるCompositeTransformを指定します。
ジェスチャとともにUIElementの動作および操作に使用される、ManipulationModeに、すべての相互作用モードを有効にする、ManipulationModes.Allを指定します。UIElement. ManipulationModes列挙体については、下記のURLを参考にしてください。
→ http://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.input.manipulationmodes
新しいButtonのインスタンスdelButtonオブジェクトを作成します。Contentプロパティに「削除」、Tagプロパティにメンバ変数noの値を文字列にキャストして指定します。どの「削除」ボタンがクリックされたかの判定に使用します。
myStackPanelオブジェクトに、myImage、delButtonオブジェクトを追加し、PhotoAreaという名前を持つCanvas内にAddメソッドでmyStackPanelオブジェクトを追加します。写した写真が同じ位置に重なって表示されます。
AddHandlerステートメントで、操作中に入力デバイスが位置を変更した時に発生するManipulationDeltaイベントにイベントハンドラを追加します。イベントハンドラ内では以下の処理を行います。
TranslateXとTranslateYでx軸とy軸にそって並行移動する距離を設定します。
ScaleXとScaleYでオブジェクトを拡大縮小する値を設定します。
Rotaionプロパティでオブジェクトを回転します。CenterXとCenterYには実寸の画像の半分のWidthとHeightの値を指定します。これで、画像の中心を起点として回転します。
これによって、写された画像は、ピンチで拡大縮小、回転が可能になります。
SetValueメソッドでZIndexPropertyに1ずつ加算されるメンバ変数noの値を指定します。選択された画像が一番手前に配置されます。これで、マウスのドラッグで写された画像が、好きな位置に配置できるようになります。
AddHandlerステートメントで、delButtonがクリックされた時のイベントハンドラを追加します。イベントハンドラ内では以下の処理を行います。
Buttonの情報を保持しているdelSenderオブジェクトをDirectCastでButtonにキャストし、そのTagプロパティの値を取得し、数値に変換して変数delIndexに格納しておきます。
delIndexの値が0より小さい場合、つまり画像ファイルが存在しない場合は、「Folder」アイコンの使用を不可とします。それ以外は使用を可とします。
コレクションメンバ変数である、myPictureFiles内のdelIndexに該当する画像ファイルを、DeleteAsyncメソッドで削除します。「削除しました。」のメッセージを表示し、画面を再描画するために、DataReloadプロシージャを実行します。
非同期処理で行われるためメソッドの先頭にAsyncを追加します。

  Private Async Sub DataReload()
    PhotoArea.Children.Clear()
    no = 0
    Dim myFolder As StorageFolder = Windows.Storage.KnownFolders.PicturesLibrary
    Dim mySubFolder = Await myFolder.CreateFolderAsync("PhotoAlbum", CreationCollisionOption.OpenIfExists)
 
    myPictureFiles = Await mySubFolder.GetFilesAsync()
 
    For Each myFile As IStorageFile In myPictureFiles
      saveBmp = New BitmapImage
      saveBmp.SetSource(Await myFile.OpenReadAsync)
      Dim myImage As New Image
      With myImage
        .Width = 320
        .Height = 240
        .Source = saveBmp
      End With
 
      Dim myStackPanel As New StackPanel
      With myStackPanel
        .RenderTransform = New CompositeTransform
        .ManipulationMode = ManipulationModes.All
      End With
 
      Dim delButton As New Button
      delButton.Content = "削除"
      delButton.Tag = no.ToString

 
      myStackPanel.Children.Add(myImage)
      myStackPanel.Children.Add(delButton)
 
      PhotoArea.Children.Add(myStackPanel)
 
      AddHandler myStackPanel.ManipulationDelta, Sub(mySender As Object, myArgs As ManipulationDeltaRoutedEventArgs)
        Dim myTrans = DirectCast(myStackPanel.RenderTransform, CompositeTransform)
        myTrans.TranslateX = myTrans.TranslateX + myArgs.Delta.Translation.X
        myTrans.TranslateY = myTrans.TranslateY + myArgs.Delta.Translation.Y
        myTrans.ScaleX = myTrans.ScaleX * myArgs.Delta.Scale
        myTrans.ScaleY = myTrans.ScaleY * myArgs.Delta.Scale
        myTrans.CenterX = 320
        myTrans.CenterY = 240
        myTrans.Rotation = myTrans.Rotation + myArgs.Delta.Rotation
        myStackPanel.SetValue(Canvas.ZIndexProperty, no)
      End Sub
      AddHandler delButton.Click, Async Sub(delSender As Object, delArgs As RoutedEventArgs)
                                  Dim delIndex = CInt(DirectCast(delSender, Button).Tag)
                                  If delIndex < 0 Then
                                    ichiranButton.IsEnabled = False
                                  Else
                                    ichiranButton.IsEnabled = True
                                  End If
                                  Await myPictureFiles(delIndex).DeleteAsync
                                  Dim message As New MessageDialog("削除しました。")
                                  Await message.ShowAsync
                                  DataReload()
                                End Sub
 
      no = no + 1
    Next
  End Sub

戻る(←)アイコンがタップされた時の処理

PhotoAreaという名前のCanvasを表示状態にします。
Webカメラのデバイス名を表示している、camerComboBoxを表示状態にします。
AppBar1を表示状態にします。myFrameを非表示にし、DataShowとDataReloadプロシージャを実行します。

  Private Sub backButton_Click(sender As Object, e As RoutedEventArgs) Handles backButton.Click
    PhotoArea.Visibility = Xaml.Visibility.Visible
    cameraComboBox.Visibility = Xaml.Visibility.Visible
    AppBar1.Visibility = Xaml.Visibility.Visible
    AppBar1.IsEnabled = True
    myFrame.Visibility = Xaml.Visibility.Collapsed
    DataShow()
    DataReload()
  End Sub
  • 撮影した写真から最適な1枚を選ぶサンプルプログラム

薬師寺国安事務所

薬師寺国安事務所代表。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メルマガ会員のサービス内容を見る

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