PR

写真をハート型に切り抜いて撮影するサンプル

2012年6月4日(月)
PROJECT KySS

前ページからの続きです。

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

変数storageを、ファイルとディレクトリを格納している分離ストレージ領域を表すIsolateStorageFileクラスとして宣言します。Path.CombineでImageInFrameというフォルダとimageFileNameに格納されている画像名とを連結し、変数filePathに格納します。同様にImageInFrameというフォルダと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名前空間に属しています。WriteableBitmapクラスは書き込み更新することのできるBitmapSourceを提供するクラスです。ellipseImageのSourceプロパティにimageSourceオブジェクトを指定します。一度分離ストレージに保存した画像を読み込んで、ellipseImageに表示しています。

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

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

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

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

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

変数xmlStorageを、ファイルとディレクトリを格納している分離ストレージ領域を表すIsolateStorageFileクラスとして宣言します。ImageInFrameというフォルダ内にImageList.xmlが存在していない場合は、Visual Basicの埋め込み式を用いて、XML宣言とルート要素、その子要素として、属性に”記録日”を指定し、その子要素として要素を作成します。埋め込み式の構文である を用いて“記録日”属性の値にメンバ変数recordDateの値を、要素の値に、メンバ変数imageFileNameの値を指定します。これは ASP.NET で使用される構文と同じです。

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

ImageList.xmlファイルが存在する場合は、新しいStreamWriter 生成し、IsolatedStorageFile.OpenFileメソッドで、指定したファイルアクセスを使用して指定したモードでファイルを開き、初期化します。Writeメソッドで埋め込み式のXMLをストリームに書き込みます。別スレッドで、保存した旨のメッセージを表示し、「データ一覧」のフォルダアイコンの使用を可能にします。

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

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

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

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

  Private Sub myPhotoCamera_CaptureImageAvailable(sender As Object, e As ContentReadyEventArgs)
      Dim storage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
 
      Dim filePath As String = Path.Combine("ImageInFrame", imageFileName)
      Dim xmlFilePath As String = Path.Combine("ImageInFrame", "ImageList.xml")
  
      Deployment.Current.Dispatcher.BeginInvoke(Sub()
          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
        Dim imageStorage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
        Using stream As IsolatedStorageFileStream = imageStorage.OpenFile(filePath, FileMode.Open, FileAccess.Read)
            Dim imageSource As WriteableBitmap = PictureDecoder.DecodeJpeg(stream)
            ellipseImage.Source = imageSource
        End Using
            Dim myWriteableBitmap As WriteableBitmap
            myWriteableBitmap = New WriteableBitmap(InkPresenter1, Nothing)
            If imageStorage.FileExists(Path.Combine("ImageInFrame", imageFileName)) = True Then
              imageStorage.DeleteFile(Path.Combine("ImageInFrame", imageFileName))
            End If
        Using Stream As IsolatedStorageFileStream = storage.CreateFile(Path.Combine("ImageInFrame", 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"?>
                                  <思い出>
                                    <風景 記録日=<%= recordDate %>>
                                      <画像名><%= imageFileName %></画像名>
                                    </風景>
                                  </思い出>
        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
            Me.Dispatcher.BeginInvoke(Sub()
              MessageBox.Show("画像とXMLファイルを保存しました。")
              TryCast(ApplicationBar.Buttons(1), ApplicationBarIconButton).IsEnabled = True
                                       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 = <風景 記録日=<%= recordDate %>>
                                         <画像名><%= imageFileName %></画像名>
                                       </風景>
              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)
                    Me.Dispatcher.BeginInvoke(Sub()
                      MessageBox.Show("画像とXMLファイルを保存しました。")
                      TryCast(ApplicationBar.Buttons(1), ApplicationBarIconButton).IsEnabled = True
                                           End Sub) 
                  End Using
              End If
          End Using
        End If
  End Sub

カメラのアイコンがタップされた時の処理

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

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

  Private Sub GoShutter(sender As System.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

フォルダアイコンがタップされた時の処理

RemoveHandlerステートメントで、イベントとイベント ハンドラの関連付けを解除します。これから作成する、DataIchiranPage.xamlに遷移します。

  Private Sub GoIchiran(sender As Object, e As EventArgs)
    RemoveHandler myPhotoCamera.CaptureImageAvailable, AddressOf myPhotoCamera_CaptureImageAvailable
    myPhotoCamera = Nothing
    NavigationService.Navigate(New Uri("/DataIchiranPage.xaml", UriKind.Relative))
  End Sub
End Class

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

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

DataIchiranPage.xamlの編集とコントロールの配置

x:NameがApplicationTitleというTextBlockのTextプロパティに「データの一覧」と指定します。ツールボックスからListBoxコントロールを1個配置します(図3)。

 図3:ListBoxコントロールを配置した(クリックで拡大)

書き出されるXAMLをリスト3のように編集します。

リスト3 編集されたXAMLコード(DataIchiranPage.xaml)

(1)プロパティ要素内にListBoxTemplateというキー名の 要素を配置し、要素に適用するテンプレートを定義します。要素を配置し、Marginプロパティに20を指定して余白を設けます。この中に子要素として、要素を配置します。SourceプロパティにはimageFileNameをバインドします。の下に要素を配置し、TextプロパティにrecordDateをバインドします。ここで指定する名称は、VBコード内のクラスで定義するプロパティ名です。

(2)(1)で定義したテンプレートを、要素のItemTemplateプロパティに指定して参照させます。

<phone:PhoneApplicationPage 
    x:Class="WP71_ImageInTheCameraFrame.DataIchiranPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    mc:Ignorable="d" d:DesignHeight="768" d:DesignWidth="480"
    shell:SystemTray.IsVisible="True">
    <phone:PhoneApplicationPage.Resources> ■(1)
      <DataTemplate x:Key="ListBoxTemplate"> ■(1)
        <StackPanel Margin="20"> ■(1)
          <Image Width="240" Height="320" Stretch="Fill" Source="{Binding imageFileName}" Margin="35"/> ■(1)
          <TextBlock Text="{Binding recordDate}"/> ■(1)
        </StackPanel> ■(1)
      </DataTemplate> ■(1)
    </phone:PhoneApplicationPage.Resources> ■(1)
    <!--LayoutRoot は、すべてのページ コンテンツが配置されるルート グリッドです-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
      <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
      </Grid.RowDefinitions>
 
      <!--TitlePanel は、アプリケーション名とページ タイトルを格納します-->
      <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
        <TextBlock x:Name="ApplicationTitle" Text="データの一覧" Style="{StaticResource PhoneTextNormalStyle}"/>
      </StackPanel>
 
      <!--ContentPanel - 追加コンテンツをここに入力します-->
      <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
        <ListBox Height="663" Margin="12,27,18,0" Name="ListBox1" VerticalAlignment="Top" ItemTemplate="{StaticResource ListBoxTemplate}"/> ■(2)
      </Grid>
    </Grid>
    <!--ApplicationBar の使用法を示すサンプル コード-->
  ~コード略~
</phone:PhoneApplicationPage>
  • フレームで切り抜いて撮影するサンプル

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

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