カメラに配置した装飾アイテムを移動・変形させるサンプル

2012年6月18日(月)
PROJECT KySS

次に、CatchPhotoPage.xamlを展開して表示されるCatchPhotoPage.xaml.vbをダブルクリックして、リスト5のコードを記述します。

ロジックコードを記述する

リスト5 (CatchPhotoPage.xaml.vb)

Option Strict On
Imports Microsoft.Devices
Imports System.IO
Imports System.IO.IsolatedStorage
Imports System.Xml.Linq
Imports Microsoft.Phone
Imports System.Windows.Media.Imaging

画像の上をピンチして拡大縮小、回転、ドラッグを可能にするクラスの含まれる、MultiTouch.Behaviors.Silverlight4名前空間をインポートします。

Imports MultiTouch.Behaviors.Silverlight4
Imports System.Windows.Interactivity

Partial Public Class CatchPhotoPage
  Inherits PhoneApplicationPage

  Public Sub New()
    InitializeComponent()
  End Sub

myPhotoCameraを、PhotoCameraクラスのメンバ変数として宣言します。PhotoCameraクラスは、カメラアプリケーションの基本カメラ機能を提供し、イメージ キャプチャ、フォーカス、解像度、フラッシュ モードなどの機能を有効にして構成するためのメンバが含まれています。

  Dim myPhotoCamera As PhotoCamera
  Dim imageFileName As String
  Dim recordDate As String
  Dim bufferValue As Integer = 5119

画像の上をピンチして拡大縮小、回転、ドラッグを可能にするMultiTouchBehaviorの新しいインスタンス、behaviorオブジェクトを作成します。

  Dim behavior As New MultiTouchBehavior

Imageクラスの新しいインスタンスmyImageオブジェクトを作成します。

  Dim myImage As New Image

ページがアクティブになった時呼び出されるメソッド

NavigationModeがNavigationMode.Backで、「戻る」 ナビゲーション履歴の最新コンテンツへの移動がなされた時は、myImageオブジェクトを非表示にします。それ以外は表示します。これはBackキーでこのページに戻ってきた時は、配置していた四文字熟語の画像を非表示にする処理です。

MainPage.xamlから送られた引数の値をNavigationContext.QueryStringで受け取り、変数kanjiNameに格納しておきます。四文字熟語の画像ファイル名が格納されます。ImageのインスタンスmyImageオブジェクトのプロパティを設定します。SourceプロパティにはkanjiName変数の値を指定します。InkPresenterにmyImageオブジェクトが追加されていない場合は、追加します。

MultiTouchBehaviorのインスタンス、behaviorの各プロパティを設定します。回転、拡大縮小、ドラッグに関するプロパティにTrueを指定します。AreFingersVisibleにTrueを指定すると指でホールドした位置に●の円が表示されます。

このプロパティに関する詳細な情報はWebにも掲載されていませんので、このように設定するものだと認識してください。Attachメソッドで指定されたオブジェクトにアタッチします。この場合、myImageオブジェクトにアタッチされ、配置された画像は、どの場所にでも回転、拡大縮小、ドラッグが可能になります。

変数storageを、ファイルとディレクトリを格納している分離ストレージ領域を表すIsolateStorageFileクラスとして宣言します。Path.Combineで分離ストレージ内のMultiTouchBehaviorというフォルダとImageList.xmlファイルを連結し、変数filePathに格納します。

分離ストレージのMultiTouchBehaviorフォルダにImageList.xmlファイルが存在する場合は、「フォルダ」アイコンの「データ一覧」ボタンを使用可能にし、それ以外は使用不可とします。PhotoCameraの新しいインスタンス、myPhotoCameraオブジェクトを作成します。Rectangleに配置したmyVideoBrushという名前のVideoBrushに、SetSourceメソッドでmyPhotoCameraオブジェクトを指定します。

AddHandlerステートメントで、カメラ オブジェクトが初期化された時に発生する、Initializedイベントにイベントハンドラを追加します。Succeededプロパティで、カメラ操作が失敗した時は処理を抜けます。

それ以外は、変数myResolution を使って、AvailableResolutionsプロパティで、使用できるカメラの解像度を参照します。カメラでキャプチャしたイメージの解像度を設定できるResolutionプロパティに、先に取得したmyResolutionの一番先頭の解像度(640×480)を指定します。フラッシュモードを自動に設定します。

Try~Catch~End Tryで例外処理を行います。例外が発生した場合は、メッセージを表示します。別スレッドからの表示になります。

AddHandlerステートメントで、イメージが利用可能な場合に発生するCaptureImageAvailableイベントに、myPhotoCamera_CaptureImageAvailableイベントハンドラを追加します。

  Protected Overrides Sub OnNavigatedTo(e As System.Windows.Navigation.NavigationEventArgs)
    If e.NavigationMode = NavigationMode.Back Then
      myImage.Visibility = Windows.Visibility.Collapsed
    Else
      myImage.Visibility = Windows.Visibility.Visible
    End If
    Dim kanjiName As String = NavigationContext.QueryString("imagename")
    With myImage
      .Width = 400
      .Height = 100
      .Stretch = Stretch.Uniform
      .Source = New BitmapImage(New Uri(kanjiName, UriKind.Relative))
    End With

'この記述がないとBackキーで戻った時Element is already the child of another element.のエラーが出るので注意

    If InkPresenter1.Children.Contains(myImage) = False Then
      InkPresenter1.Children.Add(myImage)
    End If
      With behavior
        .CenterX = 200
        .CenterY = 50
        .IsInertiaEnabled = False
        .IsTranslateEnabled = True
        .IsScaleEnabled = True
        .MinimumScale = 50
        .MaximumScale = 360
        .IsPivotEnabled = False
        .AreFingersVisible = True
        .Attach(myImage)
      End With
 
      Dim storage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
      Dim filePath As String = Path.Combine("MultiTouchBehavior", "ImageList.xml")
      If storage.FileExists(filePath) = True Then
        TryCast(ApplicationBar.Buttons(1), ApplicationBarIconButton).IsEnabled = True
      Else
        TryCast(ApplicationBar.Buttons(1), ApplicationBarIconButton).IsEnabled = False
      End If

 
      myPhotoCamera = New PhotoCamera
      myVideoBrush.SetSource(myPhotoCamera)
 
      AddHandler myPhotoCamera.Initialized, Sub(cameraSender As Object, cameraArgs As CameraOperationCompletedEventArgs)
          Try
            If cameraArgs.Succeeded = False Then
              Exit Sub
            Else
              Dim myResolution As IEnumerable(Of Size) = myPhotoCamera.AvailableResolutions
              myPhotoCamera.Resolution = myResolution.First
              myPhotoCamera.FlashMode = FlashMode.Auto
            End If
          Catch
              Deployment.Current.Dispatcher.BeginInvoke(Sub()
                MessageBox.Show("カメラが開始するまでしばらくお待ちください。")
                                                     End Sub)
          End Try
                  End Sub
      AddHandler myPhotoCamera.CaptureImageAvailable, AddressOf myPhotoCamera_CaptureImageAvailable
      MyBase.OnNavigatedTo(e)
    End Sub

イメージが利用可能な場合に発生する処理

変数storageを、ファイルとディレクトリを格納している分離ストレージ領域を表すIsolateStorageFileクラスとして宣言します。Path.Combineで、MultiTouchBehaviorというフォルダとimageFileNameに格納されている画像名とを連結し、変数filePathに格納します。同様にMultiTouchBehaviorというフォルダとImageList.xmlというファイル名を連結して、変数xmlFilePathに格納します。

IsolatedStorageFileクラスのCreateFileメソッドで変数filePathに格納されているファイルを作成します。バイト型として5119の領域を確保します。e.ImageStream.Readメソッドで、イメージストリームからバイト数を読み取り、変数myByteReadに格納します。この変数が、0より大きい間、IsolatedStorageFileStream.Writeメソッドで、読み込んだイメージストリームのバイト数をバッファーに書き込んでいきます。

写真の撮影とエンコードが正しくできているかは、ContentReadyEventArgs.ImageStreamプロパティから取得できますので、これを分離ストレージへ保存します。e.ImageStreamを閉じます。

e.ImageStream.Readの書式は下記の通りです。

e.ImageStream.Read(バイト配列, データの格納を開始するバッファー内のバイト オフセット(通常0を指定), 現在のストリームから読み取るバイトの最大数。)

IsolatedStorageFileStream.Writeメソッドの書式は下記の通りです。

IsolatedStorageFileStream.Write(書き込むバッファー, 開始位置を示すバッファー内のバイト オフセット(通常0を指定) ,書き込む最大バイト数,)

別スレッドで以下の処理を行います。

変数imageStorageを、ファイルとディレクトリを格納している分離ストレージ領域を表すIsolateStorageFileクラスとして宣言します。分離ストレージ内のファイルを表すIsolatedStorageFileStreamクラス用オブジェクト変数stream変数を用意し、IsolatedStorageFile.OpenFileメソッドで、filePathに格納しているファイルを、指定したモード、指定したファイルアクセスモードで開きます。

WriteableBitmap型の変数imageSourceを宣言し、PictureDecoder.DecodeJpeg(stream)で、撮った写真をJPEGファイルとしてWriteableBitmapオブジェクトにデコードします。PictureDecoder.DecodeJpegメソッドはMicrosof.Phone名前空間に属しています。

backgroundImageのSourceプロパティにimageSourceオブジェクトを指定します。一度分離ストレージに保存した画像を読み込んで、backgroundImageに表示しています。

InkPresenterで初期化された新しいWriteableBitmapのインスタンスを作成します。backgroundImageに画像を表示させた時点で、分離ストレージのMultiTouchBehaviorフォルダ内の保存した画像を、DeleteFileメソッドで削除しておきます。

分離ストレージ内のファイルを表すIsolatedStorageFileStreamクラス用オブジェクト変数Stream変数を用意し、IsolatedStorageFile.CreateFileメソッドで分離ストレージのMultiTouchBehaviorフォルダ内に、変数imageFileNameに格納されている画像ファイルを作成します。

Extensions.SaveJpegメソッドで、WriteableBitmapオブジェクトを、JPEGストリームにエンコードします。これは、JPEGファイルのターゲットとなる幅と高さを設定するためのパラメータを持っています。書式は下記の通りです。

Extensions.SaveJpeg(WriteableBitmapオブジェクト,イメージデータストリーム,WriteableBitmapオブジェクトのPixelWidth, WriteableBitmapオブジェクトのPixelHeight,0(固定),0~100の間の写真の品質(70以上を指定))

分離ストレージのMultiTouchBehavior内に指定した画像ファイルが作成されます。

変数xmlStorageを、ファイルとディレクトリを格納している分離ストレージ領域を表すIsolateStorageFileクラスとして宣言します。MultiTouchBehaviorというフォルダ内にImageList.xmlが存在していない場合は、Visual Basic の埋め込み式を用いて、XML宣言とルート要素、その子要素として、属性に”RecordDate”を指定し、その子要素として要素を作成します。

埋め込み式の構文である を用いて“RecordDate”属性の値にメンバ変数recordDateの値を、要素の値に、メンバ変数imageFileNameの値を指定します。これは ASP.NET で使用される構文と同じです。

分離ストレージ内のファイルを表す、IsolatedStorageFileStreamクラス用のオブジェクト変数であるxmlStreamを用意し、IsolatedStorageFile.CreateFileメソッドで、分離ストレージ内にxmlFilePath変数の持っているフォルダ名付きXMLファイルを作成します。

ImageList.xmlファイルが存在する場合は、新しいStreamWriter 生成し、IsolatedStorageFile.OpenFileメソッドで、指定したファイルアクセスを使用して指定したモードでファイルを開き、初期化します。

Writeメソッドで埋め込み式のXMLをストリームに書き込みます。別スレッドで、保存した旨のメッセージを表示し、「データ一覧」のフォルダアイコンの使用を可能にするIchiranShowプロシージャを実行します。

次は、既に最初からMultiTouchBehaviorフォルダ内に、ImageList.xmlが存在する場合の処理です。

IsolatedStorageFileクラスのOpenFileメソッドで、MultiTouchBehaviorフォルダ内のImageList.xmlファイルを、指定したファイルアクセスを使用して指定したモードで開きます。開いたファイルをStreamReaderで読み込みます。ReadToEndメソッドでファイルの最後まで読み取り変数readXmldoc変数に格納しておきます。読み込んだXMLテキストをParseメソッドでXElementとして読み込みます。

追加する要素を、埋め込み式を用いて生成し、”RecordDate”属性の値や、の内容テキストを指定します。新しく生成したXML要素を、読み込んだXMLにAddメソッドで追加します。IsolatedStorageFileStreamを閉じます。

ImageList.xmlファイルが存在する場合は、新しいStreamWriter 生成し、IsolatedStorageFile.OpenFileメソッドで、指定したファイルアクセスを使用して指定したモードでファイルを開きます。Writeメソッドで新しい要素の追加されたXMLを、ストリームに書き込みます。別スレッドで、保存した旨のメッセージを表示し、「データ一覧」のフォルダアイコンの使用を可能にするIchiranShowプロシージャを実行します。

    Private Sub myPhotoCamera_CaptureImageAvailable(sender As Object, e As ContentReadyEventArgs)
      Dim storage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
      
      Dim filePath As String = Path.Combine("MultiTouchBehavior", imageFileName)
      Dim xmlFilePath As String = Path.Combine("MultiTouchBehavior", "ImageList.xml")
 
      Using myStream As IsolatedStorageFileStream = storage.CreateFile(filePath)
        Dim myBuffer(bufferValue) As Byte
        Dim myByteRead As Integer = -1
        Do
          myByteRead = e.ImageStream.Read(myBuffer, 0, myBuffer.Length)
          myStream.Write(myBuffer, 0, myByteRead)
        Loop While (myByteRead > 0)
        e.ImageStream.Close()
      End Using
 
      Deployment.Current.Dispatcher.BeginInvoke(Sub()
        Dim imageStorage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
          Using stream As IsolatedStorageFileStream = imageStorage.OpenFile(filePath, FileMode.Open, FileAccess.Read)
            Dim imageSource As WriteableBitmap = PictureDecoder.DecodeJpeg(stream)
            backgroundImage.Source = imageSource
        End Using
            Dim myWriteableBitmap As WriteableBitmap
            myWriteableBitmap = New WriteableBitmap(InkPresenter1, Nothing)
          If imageStorage.FileExists(Path.Combine("MultiTouchBehavior", imageFileName)) = True Then
            imageStorage.DeleteFile(Path.Combine("MultiTouchBehavior", imageFileName))
          End If
  
  Using Stream As IsolatedStorageFileStream = storage.CreateFile(Path.Combine("MultiTouchBehavior", imageFileName))
      System.Windows.Media.Imaging.Extensions.SaveJpeg(myWriteableBitmap, Stream, 480, 640, 0, 85)
  End Using
                                               End Sub)
      'XMLファイルの保存
      Dim xmlStorage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
 
      If xmlStorage.FileExists(xmlFilePath) = False Then
        Dim xmldoc As XDocument = <?xml version="1.0" encoding="utf-8"?>
                                  <Kanji>
                                    <Pictures RecordDate=<%= recordDate %>>
                                      <PictureName><%= imageFileName %></PictureName>
                                    </Pictures>
                                  </Kanji>
        Using xmlStream As IsolatedStorageFileStream = xmlStorage.CreateFile(xmlFilePath)
        End Using
 
        If xmlStorage.FileExists(xmlFilePath) = True Then
            Using xmlwriter As StreamWriter = New StreamWriter(xmlStorage.OpenFile(xmlFilePath, FileMode.Open, FileAccess.Write), System.Text.Encoding.UTF8)
              xmlwriter.Flush()
              xmlwriter.Write(xmldoc.ToString)
            End Using
            Deployment.Current.Dispatcher.BeginInvoke(Sub()
                          backgroundImage.Source = Nothing
                          MessageBox.Show("画像とXMLファイルを保存しました。")
                          IchiranShow()
        End Sub)
      End If
    Else
      Dim xmlStream As IsolatedStorageFileStream = xmlStorage.OpenFile(xmlFilePath, FileMode.Open, FileAccess.Read)
      Using xmlreader As StreamReader = New StreamReader(xmlStream)
 
        Dim readXmldoc As String = xmlreader.ReadToEnd
        Dim doc As XElement = XElement.Parse(readXmldoc)
        Dim addXml As XElement = <Pictures RecordDate=<%= recordDate %>>
                                   <PictureName><%= imageFileName %></PictureName>
                                 </Pictures>
        doc.Add(addXml)
        xmlStream.Close()
 
        If xmlStorage.FileExists(xmlFilePath) = True Then
          Using xmlwriter As StreamWriter = New StreamWriter(storage.OpenFile(xmlFilePath, FileMode.Open, FileAccess.Write), System.Text.Encoding.UTF8)
            xmlwriter.Flush()
            xmlwriter.Write(doc.ToString)
            Deployment.Current.Dispatcher.BeginInvoke(Sub()
              backgroundImage.Source = Nothing
              MessageBox.Show("画像とXMLファイルを保存しました。")
              IchiranShow()
                                                         End Sub)
          End Using
 
        End If
      End Using
    End If
  End Sub

[シャッター]アイコンがタップされた時の処理

PhotoCameraにファインダーに表示されるイメージがある場合は、現在の年月日時間分秒.jpgファイル名を作成し、メンバ変数imageFileNameに格納しておきます。また、現在の何年/何月/何日/何時:何分:何秒という文字列を、メンバ変数recordDateに格納しておきます。

CaptureImageメソッドで、ファインダーに表示される現在のイメージのフル解像度キャプチャを開始します。例外が発生した場合は別スレッドで、メッセージを表示します。

  Private Sub GoShutter(sender As Object, e As EventArgs)
    If myPhotoCamera Is Nothing = False Then
      Try
        imageFileName = DateTime.Now.ToString("yyyyMMddHHmmss") & ".jpg"
        recordDate = DateTime.Now.ToString("yyyy/MM/dd/HH:mm:ss")
        myPhotoCamera.CaptureImage()
      Catch
        Me.Dispatcher.BeginInvoke(Sub() MessageBox.Show("エラーが発生しましたが、データは保存されます。"))
      End Try
    End If
  End Sub

[データ一覧]のアイコンがタップされた時の処理

InkPresenterに配置している四文字熟語の画像を削除し、これから作成する、DataIchiranPage.xamlに遷移します。

  Private Sub GoIchiran(sender As Object, e As EventArgs)
    InkPresenter1.Children.Remove(myImage)
    NavigationService.Navigate(New Uri("/DataIchiranPage.xaml", UriKind.Relative))
  End Sub

[四文字熟語選択]のアイコンがタップされた時の処理

MainPage.xamlに遷移します。

 Private Sub KanjiImageSelect(sender As Object, e As EventArgs)
  NavigationService.Navigate(New Uri("/MainPage.xaml", UriKind.Relative))
  End Sub

[データ一覧]アイコンの使用可否を決める処理

変数storageを、ファイルとディレクトリを格納している分離ストレージ領域を表すIsolateStorageFileクラスとして宣言します。Path.Combineメソッドで分離ストレージ内のMultiTouchBehaviorというフォルダとImageLis.xmlというファイル名を連結して、変数filePathに格納します。filePathに格納しているファイルが存在する場合は、[データ一覧]アイコンの使用を可能にし、それ以外は使用不可にします。

  Private Sub IchiranShow()
    Dim storage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
    Dim filePath As String = Path.Combine("MultiTouchBehavior", "ImageList.xml")
    If storage.FileExists(filePath) = True Then
      TryCast(ApplicationBar.Buttons(1), ApplicationBarIconButton).IsEnabled = True
    Else
      TryCast(ApplicationBar.Buttons(1), ApplicationBarIconButton).IsEnabled = False
    End If
  End Sub

[熟語画像の削除]アイコンがタップされた時の処理

InkPresenterに配置されている四文字熟語の画像を消去します。

  Private Sub GoDelete(sender As Object, e As EventArgs)
    InkPresenter1.Children.Remove(myImage)
  End Sub
End Class

「Windows Phone 縦向きのページ」(DataIchiranPage.xaml)の作成

VS2010メニューの「プロジェクト(P)/新しい項目の追加(W)」と選択し、「Windows Phone 縦向きのページ」を選択します。「名前(N)」にはDataIchiranPage.xamlと入力します。

(*)コードとコード解説については、以前の記事「写真をハート型に切り抜いて撮影するサンプル」のDataIchiranPage.xamlとDataIchiranPage.xaml.vbとほとんど同じ処理になりますので、同記事のDataIchiranPage.xaml.vbの解説を参照してください。

今回のサンプルは以上で終了です。

【参照リンク】

PROJECT KySSでは現在、16個のWindows PhoneアプリをMarketplaceに公開しています。試用版もありますので、興味のある方はお試しください。
→参照:Windows Phone App Information(PROJECT KySS)

  • カメラに配置した装飾アイテムを移動・変形させるサンプル

四国のSOHO。薬師寺国安(VBプログラマ)と、薬師寺聖(デザイナ、エンジニア)によるコラボレーション・ユニット。1997年6月、Dynamic HTMLとDirectAnimationの普及を目的として結成。共同開発やユニット名義での執筆活動を行う。XMLおよび.NETに関する著書や連載多数。最新刊は「Silverlight実践プログラミング」両名とも、Microsoft MVP for Development Platforms - Client App Dev (Oct 2003-Sep 2012)。http://www.PROJECTKySS.NET/

連載バックナンバー

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

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

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

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