撮影した写真を分離ストレージとPicturesHUBに保存する

2012年2月13日(月)
PROJECT KySS

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

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

SavePicturesHUB.xamlの編集とコントロールの追加

x:NameがPageTitleというTextBlockのTextプロパティに「PicturesHUBに保存」と指定します。名前がPageTitleのTextBlockのStyleプロパティに指定されているPhoneTextTitle1Styleでは、文字サイズが大きすぎるので、PhoneTextTitle2Styleを指定します。文字サイズが小さくなって表示されます。これらのスタイルは前述の「Theme Resources for Windows Phone」で定義されています。{StaticResource}を使用して、これらのテーマリソースを参照します。

ツールボックスからImageコントロールを1個、Buttonコントロールを1個配置します。ImageコントロールのWidthに400、Heightに360を指定します(図7)。

図7:ImageとButtonコントロールを配置した(クリックで拡大)

書き出されるXAMLは省略します。

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

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

リスト3 (MainPage.xaml.vb)

  Option Strict On

ランチャーやチューザーに関するクラスの含まれる、Microsoft.Phone.Tasks名前空間をインポートします。
  Imports Microsoft.Phone.Tasks

Imports System.Windows.Media.Imaging
  Imports Microsoft.Phone

仮想ファイルシステムを作成および使用するための型が含まれている、System.IO.IsolatedStorage名前空間をインポートします。分離ストレージによって、安全なクライアント側のストレージが提供されます。
  Imports System.IO.IsolatedStorage

  Imports System.IO
  Imports System.Xml.Linq
  Partial Public Class MainPage
    Inherits PhoneApplicationPage
 
    ' コンストラクター
    Public Sub New()
      InitializeComponent()
    End Sub

数値型の定数変数ImageWidthに400、ImageHeightに360を格納しておきます。
  Const ImageWidth As Integer= 400
  Const ImageHeight  As Integer= 360

バイト型の配列、imageByteをメンバ変数として宣言します。
  Dim imageByte As Byte()

カメラアプリケーションを起動するCameraCaptureTaskクラス用メンバ変数、myCameraTaskを宣言します。
  Dim myCameraTask As CameraCaptureTask

ページがアクティブになった時の処理

新しいCameraCaptureTaskクラスのインスタンス、myCameraTaskオブジェクトを作成します。
AddHandlerステートメントで、チューザータスクが完了した時に発生するCompletedイベントに、イベントハンドラを追加します。イベントハンドラ内では以下の処理が実行されます。
カメラアプリケーションが起動して、写真がきちんと撮れた場合は、写真のデータを含むストリームのバイトの長さを取得し、バイト配列を作成します。Readメソッドで、現在のストリームからバイトシーケンスを読み取り、読み取ったバイト数でストリーム内の位置を進めます。
ChosenPhoto.Readの書式は以下の通りです。
ChosenPhoto.Read(バイト配列,0ベースのバイトオフセット(現在のストリームから読み取ったデータの格納を開始します), 現在のストリームから読み取るバイトの最大数)

Seekメソッドで、現在のストリーム内の位置を設定します。書式は以下の通りです。
ChosePhoto.Seek(基点のパラメーターのバイト オフセット, 参照ポイントを示すSeekOrigin型の値) 
SeekOriginにはBeginを指定し、ストリームの先頭を指定しています。

WriteableBitmap型の変数imageSourceを宣言し、PictureDecoder.DecodeJpeg(resultArgs.ChosenPhoto)で、撮った写真をJPEGファイルとしてWriteableBitmapオブジェクトにデコードします。PictureDecoder.DecodeJpegメソッドはMicrosof.Phone名前空間に属しています。WriteableBitmapクラスは書き込み更新することのできるBitmapSourceを提供するクラスです。

Image1のSourceプロパティにimageSourceオブジェクトを指定します。これでImage内に撮った写真が表示されます。
カメラを起動している状態で撮影を中止した場合は、saveButtonは使用不可のままで処理を抜けます。

変数storageを、ファイルとディレクトリを格納している分離ストレージ領域を表すIsolateStorageFileクラスとして宣言します。分離ストレージ内にImageというフォルダが存在しない場合は、CreateDirectoryメソッドでImageというフォルダを作成します。
Path.Combineメソッドで、2つの文字列を1つのパスに結合します。ここでは、ImageフォルダとimageFileList.xmlを結合しています。Image\imageFileList.xmlとなります。この値をxmlFilePath変数に格納します。FileExistsメソッドでxmlFilePathに格納したファイルが存在する場合は、ichiranButton(データ一覧)の使用を可能にします。そうでない場合は、不可のままです。
  Protected Overrides Sub OnNavigatedTo(e As System.Windows.Navigation.NavigationEventArgs)
  myCameraTask = New CameraCaptureTask
  AddHandler myCameraTask.Completed, Sub(resultSender As Object, resultArgs As PhotoResult)
        If resultArgs.TaskResult = TaskResult.OK Then
          imageByte = New Byte(CInt(resultArgs.ChosenPhoto.Length)) {}
          resultArgs.ChosenPhoto.Read(imageByte, 0, imageByte.Length)
          resultArgs.ChosenPhoto.Seek(0, IO.SeekOrigin.Begin)
          Dim imageSource As WriteableBitmap = PictureDecoder.DecodeJpeg(resultArgs.ChosenPhoto)
          Image1.Source = imageSource
        Else
          saveButton.IsEnabled = False
          Exit Sub
        End If
      End Sub
    Dim storage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
    If storage.DirectoryExists("Image") = False Then
      storage.CreateDirectory("Image")
    End If
    Dim xmlFilePath As String = Path.Combine("Image", "imageFileList.xml")
    If storage.FileExists(xmlFilePath) = True Then
      ichiranButton.IsEnabled = True
    Else
      ichiranButton.IsEnabled = False
    End If
    MyBase.OnNavigatedTo(e)
  End Sub

[カメラ]ボタンがタップされた時の処理

Showメソッドでカメラアプリケーションを起動します。saveButtonの使用を可能にします。
  Private Sub cameraButton_Click(sender As Object, e As System.Windows.RoutedEventArgs) Handles cameraButton.Click
    myCameraTask.Show()
    saveButton.IsEnabled = True
  End Sub

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

保存する画像のファイル名を作成します。現在の「年月日時間分秒.jpg」というファイル名を作成し、変数imageFileNameに格納しておきます。
変数storageを、ファイルとディレクトリを格納している分離ストレージ領域を表すIsolateStorageFileクラスとして宣言します。
Path.CombineでImageというフォルダとimageFileNameに格納されている画像名とを連結し、変数filePathに格納しておきます。
書き込みおよび更新が可能な BitmapSourceを提供する、Image1で初期化された、WriteableBitmapクラス型の新しいインスタンスmyWriteableBitmap変数を宣言します。WriteableBitmapクラスの第2引数には、ビットマップに特定の変換を適用するTransformを指定できますが、ここではNothingを指定します。
分離ストレージ内のファイルを表すIsolatedStorageFileStreamクラス用オブジェクト変数myStream変数を用意し、IsolatedStorageFile.CreateFileメソッドで、分離ストレージ内にfilePath変数の持っているフォルダ内に画像ファイルを作成します。
Extensions.SaveJpegメソッドで、WriteableBitmapオブジェクトを、JPEGストリームにエンコードします。これは、JPEGファイルのターゲットとなる幅と高さを設定するためのパラメーターを持っています。ここで、指定したサイズの画像にエンコードします。書式は下記の通りです。
Extensions.SaveJpeg(WriteableBitmapオブジェクト,イメージデータストリーム,WriteableBitmapオブジェクトのPixelWidth, WriteableBitmapオブジェクトのPixelHeight,0(固定),0~100の間の写真の品質(70以上を指定))
WriteableBitmapオブジェクトのPixelWidthに400を格納している定数変数ImageWidthを指定し、WriteableBitmapオブジェクトのPixelHeightに360を格納している定数変数ImageHeightを指定します。
次に、IsolatedStorageFileStream.Writeメソッドで、バイト配列から読み取ったデータを使用して、IsolatedStorageFileStreamオブジェクトにバイトのブロックを書き込みます。
IsolatedStorageFileStream.Writeメソッドの書式は下記の通りです。
IsolatedStorageFileStream.Write(書き込むバッファ, 開始位置を示すバッファ内のバイト オフセット,書き込む最大バイト数)  変数xmlStorageを、ファイルとディレクトリを格納している分離ストレージ領域を表すIsolateStorageFileクラスとして宣言します。Path.Combineで、Imageというフォルダ名と、imageFileList.xmlという画像ファイル名を記録したXMLファイルを連結し、xmlFilePath変数に格納しておきます。
Imageというフォルダ内にimageFileList.xmlが存在していない場合は、Visual Basic の埋め込み式を用いて、XML宣言とルート要素image、その子要素としてfileName、fileName要素の属性に”imageFileName”を指定し、埋め込み式の構文である <%= expression %>を用いて”imageFileName”属性の値に、imageFileName変数の値を指定します。これは ASP.NET で使用される構文と同じです。
分離ストレージ内のファイルを表すIsolatedStorageFileStreamクラス用オブジェクト変数xmlStreamを用意し、IsolatedStorageFile.CreateFileメソッドで、分離ストレージ内にxmlFilePath変数の持っているフォルダ名付きXMLファイルを作成します。
Imageフォルダ内にimageFileList.xmlファイルが存在すれば、新しいStreamWriter 生成し、IsolatedStorageFile.OpenFileメソッドで、指定したファイルアクセスを使用して指定したモードでファイルを開き、初期化します。Writeメソッドで埋め込み式のXMLをストリームに書き込みます。

次は、既にImageフォルダ内にimageFileList.xmlが存在する場合の処理です。
IsolatedStorageFileクラスのOpenFileメソッドでImageフォルダ内のimageFileList.xmlファイルを、指定したファイルアクセスを使用して指定したモードでファイルを開きます。開いたファイルをStreamReaderで読み込みます。ReadToEndメソッドでファイルの最後まで読み取り変数readXmldoc変数に格納しておきます。読み込んだXMLテキストをParseメソッドでXElementとして読み込みます。
追加するfileName要素を、埋め込み式を用いて生成し、”imageFileName”属性の値を指定します。新しく生成したXML要素を、読み込んだXMLにAddメソッドで追加します。IsolatedStorageFileStreamを閉じます。
imageFileList.xmlファイルが存在する場合は、新しいStreamWriter を生成し、IsolatedStorageFile.OpenFileメソッドで、指定したファイルアクセスを使用して指定したモードでファイルを開き、初期化します。Writeメソッドで、新しいXML要素の追加されたXMLを、ストリームに書き込みます。保存した旨のメッセージを表示します。
[データ一覧]ボタンの使用を可能にし、画像の表示されていた領域をクリアし、[保存]ボタンの使用を不可とします。
    Private Sub saveButton_Click(sender As Object, e As System.Windows.RoutedEventArgs) Handles saveButton.Click
      '画像の保存
      Dim imageFileName As String = DateTime.Now.ToString("yyyyMMddHHmmss") & ".jpg"
 
      Dim storage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
      Dim filePath As String = Path.Combine("Image", imageFileName)
      Dim myWriteableBitmap As New WriteableBitmap(Image1, Nothing)
 
      Using myStream As IsolatedStorageFileStream = storage.CreateFile(filePath)
        System.Windows.Media.Imaging.Extensions.SaveJpeg(myWriteableBitmap, myStream, ImageWidth, ImageHeight, 0, 85)
        myStream.Write(imageByte, 0, imageByte.Length)
      End Using
 
      Dim xmlStorage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
      Dim xmlFilePath As String = Path.Combine("Image", "imageFileList.xml")
 
      If xmlStorage.FileExists(xmlFilePath) = False Then
          Dim xmldoc As XDocument = <?xml version="1.0" encoding="utf-8"?>
                        <image>
                          <fileName imageFileName=<%= imageFileName %>></fileName>
                        </image>
          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))
              xmlwriter.Flush()
              xmlwriter.Write(xmldoc.ToString)
            End Using
          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 = <fileName imageFileName=<%= imageFileName %>></fileName>
            doc.Add(addXml)
            xmlStream.Close()
 
            If xmlStorage.FileExists(xmlFilePath) = True Then
              Using xmlwriter As StreamWriter = New StreamWriter(storage.OpenFile(xmlFilePath, FileMode.Open, FileAccess.Write))
                xmlwriter.Flush()
                xmlwriter.Write(doc.ToString)
                MessageBox.Show("画像とXMLファイルを保存しました。")
              End Using
            End If
          End Using
        End If
 
        Image1.Source = Nothing
        saveButton.IsEnabled = False
        ichiranButton.IsEnabled = True
      End Sub

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

DataShowPage.xamlに遷移します。
      Private Sub ichiranButton_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles ichiranButton.Click
        NavigationService.Navigate(New Uri("/DataShowPage.xaml", UriKind.Relative))
      End Sub
    End Class

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

  • 「撮影した写真を分離ストレージとPicturesHUBに保存する」サンプルプログラム

四国の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メルマガ会員のサービス内容を見る

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