Leap Motionで画像のトリミングと保存を行うためのサンプルプログラムを作る

2013年11月9日(土)
薬師寺 国安

今回のLeap Motionアプリは、画面上に表示された画像の一部分を切り取って保存するアプリです。早速開発手順を見ていきましょう。

まずWPFプロジェクトを作成しよう

今回のLeap MotionアプリもWPFで作成します。

これには、Visual Studio 2012(以下、VS 2012)のIDEを起動して、メニューバーから[ファイル]−[新規作成]−[プロジェクト]と選択して、それにより表示される[新しいプロジェクト]ダイアログで「Visual Basic」のテンプレートから「WPF アプリケーション」を選択します。

※(Leap Motionは、.NET Framework 3.5と4.0に対応しています。しかし、.NET Framework 4.5でも動作します。今回のアプリは全て.NET Framework 4.5で作成しています。
しかし、あくまでも対応しているのは、.NET Framework 3.5と4.0です。心配な方は、.NET Framework 4.0で作成すると安心でしょう)。
[名前]欄には、ここでは「ImageTrimmingLeapMotion」と指定します。

ソリューション・エクスプローラー内にImageというフォルダを作成して、画像を1枚配置しています。画像サイズは640×480サイズの画像にしてください。今回はJPEG画像を使用していますが、PNG画像でも問題はありません。

ダウンロードされたサンプルファイルには、これらのファイルは追加済です。

WPFの基本的な作成手順は、「画面上の図形を5本の指で操作する基本的なLeap Motionプログラムを作る」と同じ手順となるので、説明を割愛します。具体的な手順は、第1回の「参照の追加」「プロジェクトのルートに「LeapCSharp.dll」と「Leapd.dll」を追加する」「プロパティを設定する」を参考にしてください。

ポーリング方式の採用

今回のサンプルは今までのサンプルとは異なり、「ポーリング方式」を作用しています。「ポーリング方式」とは、Controllerクラス(Leap名前空間)だけを使用した方式です。詳細については、Build Insiderの「C#によるLeap Motion開発の全体像(http://www.buildinsider.net/small/leapmotioncs/01)」を参照してください。

NuGetパッケージの管理

ソリューション・エクスプローラー内の「参照設定」を選択して、右クリックのメニューで表示される「NuGetパッケージの管理」から、コンポーネントをインストールする必要があります。

WriteableBitmapExをインストールする

NuGetの検索欄に「WriteableBitmapEx」と入力すると、WriteableBitmapExパッケージのインストール画面が表示されるので、[インストール]をタップします。筆者の場合はすでにインストール済みであるため、インストール済みの緑色のチェックが表示されています(図1)。

図1:WriteableBitmapExをインストールする。筆者の環境ではすでにインストール済みとなっている(クリックで拡大)

今回のLeap Motionアプリについて

今回のアプリは、画面に配置した画像の一部分を切り抜いて保存するアプリです。このトリミング操作をLeap Motionで行います(図2参照)。

図2:切り出したい個所を選択(上画像)、[切り出す]ボタンで切り出す(中画像)、[保存]ボタンで、C:\LeapmotionTrimmingImageフォルダに保存する(下)(クリックで拡大)

保存される画像のファイル名はTrimmingTest.pngで固定されており、上書き保存になります。

実際に動かした動画は次のようになります。選択範囲を指定するには、コツが必要なため動画を見てください。

画面のレイアウト(MainWindow.xaml)

今回はCanvasではなく、デフォルトのGridを使っています。

まず、サイズが640×480サイズでのBackgroundがTransparentのGridを配置し、その子要素として、名前がImage1というImageコントロールを配置。し、サイズを640×480とし、SourceプロパティにImageフォルダの画像を指定しておきます。

次に、名前がInkCanvas1という名前のInkCanvasコントロールを配置し、サイズは640×480で、BackgroundにTransparentと指定しておきます。InkCanvas要素は、インク・ストロークを受け取って表示する領域を定義する要素です。

次に名前がButton1というButtonコントロールを配置し、Contentに「切り出す」と指定しておきます。

切り出した画像を表示させるのに、resultImageという名前のImageコントロールを配置し、StretchにNoneと指定しておきます。切り取ったサイズの状態を保持したまま表示されます。

名前がsaveButtonという名前のButtonコントロールを配置し、Contentに「保存」と指定しておきます。

保存した旨のメッセージを表示するmessageTextBlockという名前のTextBlockコントロールを配置します。

ルートのGridコントロールの1番最後に要素を記述します。これによりInkPresenterコントロールが1番前面に表示されるようになります。このInkPresenterコントロールをImageやButtonコントロールよりも先に配置すると、表示されるタッチ・ポイントがImageやButtonコントロールの背後に表示されてしまうので、注意してください。

書き出されるXAMLはリスト1のようになります。

リスト1 (MainWindow.xaml)

<Window x:Class="MainWindow"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="MainWindow" Height="1080" Width="1920" WindowState="Maximized">
  <Grid>
    <Grid Background="Transparent" VerticalAlignment="Top" HorizontalAlignment="Left"  Width="640" Height="480">
      <Image  x:Name="Image1" Height="480" Stretch="UniformToFill"  Source="Image/冬の桜.jpg" Width="640"/>
      <InkCanvas x:Name="inkCanvas1" Height="480" Width="640" Background="Transparent"/>
    </Grid>
    <Button x:Name="Button1" Content="切り出す" HorizontalAlignment="Left" Height="154" VerticalAlignment="Top" Width="621" FontFamily="Meiryo UI" FontSize="36" IsEnabled="False" Margin="10,485,0,0" FontWeight="Bold" />
    <Image x:Name="resultImage"  Stretch="None" Width="640" Height="480" Canvas.Left="854" Canvas.Top="57" Margin="858,20,414,549" />
    <Button x:Name="saveButton" Content="保存"  HorizontalAlignment="Left" Height="143"  VerticalAlignment="Top" Width="174"   IsEnabled="False" Canvas.Left="659" Canvas.Top="57" FontSize="36" FontWeight="Bold" Margin="662,10,0,0" />
    <TextBlock x:Name="messageTextBlock" HorizontalAlignment="Left" Height="138" Margin="662,559,0,0" TextWrapping="Wrap"  VerticalAlignment="Top" Width="1126" FontSize="36" Foreground="Red" FontWeight="Bold"/>
    <InkPresenter Name="paintCanvas"/>
  </Grid>
</Window>

レイアウトは図3のようになります。

図3:各コントロールをレイアウトした(クリックで拡大)

プログラム・コード(MainWindow.xaml.vb)

では、次にプログラム・コード(MainWindows.xaml.vbファイル)を見ていきましょう。

名前空間の読み込み

まず、Leap Motionを扱うため、「Leap」名前空間を読み込み、次に、インクの操作を行うクラスを提供する「System.Windows.Ink」名前空間を読み込みます。

XAMLまたはコードで使用できる基本図形のライブラリにアクセスできるようにする、「System.Windows.Shapes」名前空間を読み込みます。

ファイル・モード、ファイル・アクセス、ファイル共有のための列挙体、およびパス操作やストリームの操作のためのクラスが含まれる、「System.IO」名前空間を読み込みます(リスト2)。

リスト2 名前空間の読み込み(MainWindow.xaml.vb)

Imports Leap
Imports System.Windows.Ink
Imports System.Windows.Shapes
Imports System.IO

メンバー変数の宣言

次にメンバー変数を宣言します。

今回はWin32 APIを使用するためにWin32 APIの宣言も行います。下記のリスト3の部分がWin32 APIに関する宣言か所になります。

リスト3 (Win32 APIに関する宣言)

  Private Declare Function SetCursorPos Lib "user32" (x As Integer, y As Integer) As Boolean
  Private Declare Function apimouse_event Lib "user32" Alias "mouse_event" (ByVal dwFlags As Int32, ByVal dx As Int32, ByVal dy As Int32, ByVal cButtons As Int32, ByVal dwExtraInfo As Int32) As Boolean
  Private Const MOUSEEVENTF_MOVE = &H1
  Private Const MOUSEEVENTF_LEFTDOWN = &H2
  Private Const MOUSEEVENTF_LEFTUP = &H4

新しいControllerクラスのインスタンスであるleapメンバー変数を宣言します。次に、インク・ストローク(=System.Windows.Ink名前空間のStrokeクラスで表現される、WPF上でのインクの線)の外観を指定する新しいDrawingAttributesクラス(System.Windows.Ink名前空間)のインスタンスである「touchIndicatorメンバー変数」を宣言します。デジタイザーとスタイラスから収集された単一のデータ・ポイントを表すStylusPoint構造体の「touchPointメンバー変数」を宣言します。

次にRectangleクラス型の「rectメンバー変数」を宣言します。また、System.Wiondows.Pointクラスからの「pメンバー変数」を宣言します。切り出した画像を保存するフォルダ名を格納する「savePathメンバー変数」を宣言します。

そのほかの必要なメンバー変数も宣言しています(リスト4)。

リスト4 メンバー変数の宣言(MainWindow.xaml.vb)

Private leap As New Controller
  Private touchIndicator As New DrawingAttributes
  Public touchPoint As StylusPoint
  Private rect As Rectangle
  Private p As System.Windows.Point

  Private savePath As String
  Private windowWidth As Double = 1920
  Private windowHeight As Double = 1080
  Private x As Integer
  Private y As Integer
  Private tx As Double
  Private ty As Double
  Private FingersCount As Integer

MainWindow_Loadedメソッドの処理

MainWindow_Loadedメソッド(=メイン・ウィンドウのLoadedイベントのハンドラー)では、MainWindowが読み込まれたときの処理を実装します。

メンバー変数savePathに切り出した画像を保存するフォルダを指定します。

指定したフォルダが存在しなかった場合は、CreateDirectoryメソッドでフォルダを作成します。

inkCanvas1のEditingMode(ユーザー編集モードの設定)プロパティに、InkCanvasEditingMode.None(ペンがInkCanvasにデータを送信する時に、アクションが実行されないようにする)を指定します。

AddHandlerステートメントを使って、構成ツリーのオブジェクトがレンダリングされる直前に発生する「CompositionTarget.Renderingイベント」に対するイベント・ハンドラーとしてUpdateメソッドを指定します(※Updateメソッドの実装内容は後述)。

インク・ストロークの外観を表す、DrawingAttributesオブジェクトのインスタンス「touchIndicator」のWidthプロパティとHeightプロパティに、それぞれ「20」を指定します。スタイラスの形状を指定するStylusTipプロパティに「StylusTip.Ellipse」を指定して円形とします。Leap Motionの上で指をかざすと、かざした指の本数に応じて20px(px=ピクセル)の円が表示されるようになります(リスト5)。

リスト5  MainWindowが読み込まれたときの処理を行うMainWindow_Loadedメソッドの実装内容(MainWindow.xaml.vb)

  Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
    savePath = "C:\LeapmotionTrimmingImage" ' 切り出した画像を保存するフォルダ
    If Directory.Exists(savePath) = False Then
      Directory.CreateDirectory(savePath)
    End If
    inkCanvas1.EditingMode = InkCanvasEditingMode.None
    AddHandler CompositionTarget.Rendering, AddressOf Update ' Updateイベント・ハンドラ—を実行する
    touchIndicator.Width = 20
    touchIndicator.Height = 20
 
    touchIndicator.StylusTip = StylusTip.Ellipse ' 20pxの円が表示される
  End Sub

Updateメソッドの処理

次は、CompositionTarget.Renderingイベント・ハンドラーであるUpdateメソッドの処理です。

まず、上記のコードを実装した段階で、「Update」の位置に[エラー修正のオプション]というスマート・タグが表示されるので、そのタグのメニューから['ChangeBackgroundLeapMotion.MainWindow' に 'Update' のメソッド スタブを生成]をクリックすると、Updateメソッドのひな型が追加されます。次に、そのメソッド内を実装していきます。

「paintCanvas」という名前のInkPresenter内をクリアします。この処理を行っていないと、Updateメソッドは常に呼び出されているため、円を画面上で動かすと、円の軌跡が残ったまま表示されてしまいます。そのため、InkPresenter内をクリアする必要があります。

Leap Motionのフレームを表すFrame型(Leap名前空間)の変数「frame」を宣言します。InteractionBox型(Leap名前空間)の変数「interactionBox」を宣言し、そこにleap.Frame.InteractionBoxプロパティからInteractionBoxオブジェクトを取得します。InteractionBoxオブジェクトは、Leap Motionで認識できる稼働範囲となります。InteractionBoxオブジェクトを使用することで、指やツール(=ペンなど)の位置を実際のディスプレイの座標系に変換できます(リスト6)。
詳細な図については、「Leap Motionでのタッチ操作はどう開発するのか?」を参照してください。

リスト6 指やツールの位置を実際のディスプレイの座標系に変換する(MainWindow.xaml.vb)

Private Sub Update(sender As Object, e As EventArgs)
    paintCanvas.Strokes.Clear()
    windowWidth = Me.Width
    windowHeight = Me.Height

    Dim frame As Leap.Frame = leap.Frame
    Dim interactionBox As InteractionBox = leap.Frame.InteractionBox
End Sub

Leap.Frame.Pointablesプロパティで得られるPointableListオブジェクト内を変数「Pointable」で反復処理しながら、1つずつPointableオブジェクトを処理し、それぞれのタッチ位置を取得していきます。

interactionBoxオブジェクトのNormalizePointメソッドに引数としてPointable.StabilizedTipPositionプロパティ値を渡し、ポインター上の位置を取得します。

変数「windowWidth」と「windowHeight」で表されたクライアント領域のウィンドウがある場合、以下のリスト7で示す計算式を使用してこのウィンドウ内のタッチ・ポイントの2D座標を得ることができます。

リスト7 ウィンドウ内のタッチ・ポイントの2D座標を得るコード(MainWindow.xaml.vb)

Private Sub Update(sender As Object, e As EventArgs)

……コード略……

  For Each Pointable As Pointable In leap.Frame.Pointables
    Dim normalizedPosition As Leap.Vector = interactionBox.NormalizePoint(Pointable.StabilizedTipPosition)
    tx = normalizedPosition.x * windowWidth
    ty = windowHeight - normalizedPosition.y * windowHeight
    touchPoint = New StylusPoint(tx, ty)  ' ウィンドウ内のタッチ・ポイントの位置を取得する
 
  ……コード略(後述)……
  Next
End Sub

さらに、上記のFor Eachステートメントの一番下に次のリスト8を追記して、ディスプレイ上に円形のタッチ・ポイントを表示します。

リスト8 ディスプレイ上にタッチ・ポイントを表示する処理(MainWindow.xaml.vb)

For Each Pointable As Pointable In leap.Frame.Pointables
  ……コード略(前述)……
  
  Dim tips As New StylusPointCollection(New StylusPoint() {touchPoint})
  Dim touchStroke As New Stroke(tips, touchIndicator)
  paintCanvas.Strokes.Add(touchStroke)
Next

次にLeap Motionのタッチ処理になります。タッチのイメージは次の図3のようなイメージです。

Updateメソッド(ホバー)の処理

手前側が「ホバー状態(hovering)」、奥側が「タッチ状態(touching)」を表わします。空間の範囲は前後「1」〜「-1」となっています。

図4:Leap Motionのタッチ検出イメージ(Leap Motion SDKのAPIドキュメントから引用)(クリックで拡大)

まずホバーの場合は、表示されている円がBlueの色になります。タッチ・ポイントの位置をメンバー変数「x」と「y」に格納します。

Win32 APIのSetCursorPos(touchPoint.X, touchPoint.Y)と指定します。タッチ・ポイントの位置とマウスカーソルの位置が同じ位置に表示されます。

apimouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0)と指定して、マウスの左ボタンを外した状態にします。

画面に表示されている指の数をleap.Frame.Fingers.Countプロパティで取得して、メンバー変数「FingersCount」に格納しておきます(リスト9)。

リスト9 ホバー時の処理(MainWindow.xaml.vb)

For Each Pointable As Pointable In leap.Frame.Pointables
  ……コード略(前述)……
If Pointable.TouchDistance > 0 AndAlso Pointable.TouchZone <> Global.Leap.Pointable.Zone.ZONENONE Then
      touchIndicator.Color = Colors.Blue
      x = 0
      y = 0
      x = touchPoint.X
      y = touchPoint.Y
      SetCursorPos(touchPoint.X, touchPoint.Y)
      apimouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0)
 
      FingersCount = leap.Frame.Fingers.Count' 表示されている指の本数を取得して、メンバ変数FingersCountに格納しておく
    ……コード略(続きは後述)……
  End If
Next

次にタッチした処理になります。タッチした場合は、表示されている円が赤に変わります。

Updateメソッド(タッチ)の処理

指が1本認識されている場合は、SetCursorPos(touchPoint.X, touchPoint.Y)と指定して、タッチ・ポイントとカーソルの位置を同じ位置に表示します。

  apimouse_event(MOUSEEVENTF_MOVE, 0, 0, 0, 0)
  apimouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0)

と指定して、マウスを動かした処理と、マウスの左ボタンを押した処理を行います。

タッチ以外の処理ではタッチ・ポイントの色がGoldになり、

  SetCursorPos(touchPoint.X, touchPoint.Y)
  apimouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0)

と指定します。

具体的にはリスト10のコードになります。

リスト10 タッチとタッチ以外の処理

If Pointable.TouchDistance > 0 AndAlso Pointable.TouchZone <> Global.Leap.Pointable.Zone.ZONENONE Then
  ……コード略(前述)……
  ElseIf Pointable.TouchDistance <= 0 Then
    touchIndicator.Color = Colors.Red
        If FingersCount = 1 Then
          SetCursorPos(touchPoint.X, touchPoint.Y)  ' 指が1本認識されている場合は、タッチ・ポイントとカーソルの位置を同じ位置に表示する。
          apimouse_event(MOUSEEVENTF_MOVE, 0, 0, 0, 0)
          apimouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0)
        
        End If
        'タッチ対象外
      Else
        touchIndicator.Color = Colors.Gold
        SetCursorPos(touchPoint.X, touchPoint.Y) 
        apimouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0)
      End If
    Next
  End Sub

inkCanvas1_MouseDownメソッドの処理

次にinkCanvas1の上でマウスが押下された時の処理です。

一度inkCanvas1の内容をクリアしておきます。

inkCanvas1の編集モードがInkCanvasEdithingMode.Noneの時の処理です。

新しいRectangleクラスのインスタンスrectを作成します。Point型メンバー変数pにinkCanvas1に対する相対的なマウスポインターの位置をe.GetPosition(inkCanvas1)で格納します。

RectオブジェクトのSetValueメソッドでInkCanvasのLeftPropertyとTopPropertyにinkCanvas1に対するXとY座標を指定します。RectオブジェクトのWidthとHeightを設定します。rectオブジェクトのStrokeDashArray(破線パターンを設定するプロパティ)プロパティに変数doubles(2,2)の値を指定して、破線とします。StrokeThickness(アウトラインの幅)には3を指定します。アウトラインの色にはGoldを指定し、FillにはTransparent(透明色)を指定します。inkCanvas1にrectオブジェクトを追加します。

画像の上でタッチするとGoldの破線枠が表示されます。具体的なコードはリスト11になります。

リスト11 inkCanvas1_MouseDownメソッドの処理

  Private Sub inkCanvas1_MouseDown(sender As Object, e As MouseButtonEventArgs) Handles inkCanvas1.MouseDown
    messageTextBlock.Text = String.Empty
    inkCanvas1.Children.Clear()
    Button1.IsEnabled = True
    If inkCanvas1.EditingMode = InkCanvasEditingMode.None Then
      rect = New Rectangle
      p = e.GetPosition(inkCanvas1)
 
      rect.SetValue(InkCanvas.LeftProperty, p.X)
      rect.SetValue(InkCanvas.TopProperty, p.Y)
      rect.Width = p.X
      rect.Height = p.Y
      Dim doubles As New DoubleCollection
      doubles.Add(2)
      doubles.Add(2)
      rect.StrokeDashArray = doubles ' StrokeDashArray(2,2)と同じ
      rect.StrokeThickness = 3
      rect.Stroke = New SolidColorBrush(Colors.Gold)
      rect.Fill = New SolidColorBrush(Colors.Transparent)
      inkCanvas1.Children.Add(rect)
    End If
  End Sub

inkCanvas1_MouseMoveメソッドの処理

inkCanvas1上でマウスカーソルを移動させた時の処理です。

Point型変数p2にinkCanvas1に対する相対的なマウスポインターの位置をe.GetPosition(inkCanvas1)で格納します。

選択した移動距離の範囲を変数wとhに格納します。移動距離のwの値が0.1と同じか少なかった場合は、rectオブジェクトのSetValueメソッドでInkCanvas.LeftPropertyに移動した範囲のXの値を指定します。rectのWidthに移動した距離を指定します。

移動した距離のhの値が0.1かそれより少なかった場合は、rectオブジェクトのSetValueメソッドでInkCanvas.TopPropertyに移動した範囲のYの値を指定します。rectのHeightに移動した距離を指定します。

具体的なコードはリスト12になります。

リスト12  inkCanvas1_MouseMoveメソッドの処理

  Private Sub inkCanvas1_MouseMove(sender As Object, e As MouseEventArgs) Handles inkCanvas1.MouseMove
    If rect Is Nothing = False Then
      Dim p2 As Point = e.GetPosition(inkCanvas1)
      Dim w As Double = p2.X - p.X ' X座標の移動した距離
      Dim h As Double = p2.Y - p.Y ' Y座標の移動した距離
      If (w <= 0.1) Then
        rect.SetValue(InkCanvas.LeftProperty, p2.X)
        rect.Width = p.X - p2.X
      Else
        rect.Width = w
      End If
      If (h <= 0.1) Then
        rect.SetValue(InkCanvas.TopProperty, p2.Y)
        rect.Height = p.Y - p2.Y
      Else
        rect.Height = h
      End If
    Else
      Exit Sub
    End If
  End Sub

[切り取る(Button1)]がクリックされた時の処理(Button1_Clickメソッド)

File.OpenReadメソッドで、ソリューション・エクスプローラー内のImageフォルダ内の画像を読み取り、streamで参照します。

BitmapImageの新しいインスタンスbmpオブジェクトを作成します。BeginInitでBitmapImageの初期化を開始します。BitmapImageのストリームソースにstreamを指定します。EndInitで初期化を終了します。

bmpオブジェクトで初期化された新しいWriteableBitmapのインスタンスmyWriteableBitmapオブジェクトを作成します。BitmapFactory.ConvertToPbgra32Format(bmp)でbmpをPbgra32フォーマットに変換します。
この変換を行っていないとエラーが表示されますので、注意してください。

WriteableBitmapExのCropメソッドで、選択した範囲を切り取ります。切り取った画像をresultImageに表示します。[保存]ボタンの使用を可能にします。

具体的なコードはリスト13になります。

リスト13 Button1_Clickメソッド処理

Private Sub Button1_Click(sender As Object, e As RoutedEventArgs) Handles Button1.Click
    Using stream = File.OpenRead("Image/冬の桜.jpg")
      Dim bmp As BitmapImage = New BitmapImage
      bmp.BeginInit()
      bmp.StreamSource = stream
      bmp.EndInit()
 
      Dim myWriteableBitmap = New WriteableBitmap(bmp)
      myWriteableBitmap = BitmapFactory.ConvertToPbgra32Format(bmp)
      Dim myCrop = myWriteableBitmap.Crop(rect.GetValue(InkCanvas.LeftProperty), rect.GetValue(InkCanvas.TopProperty), rect.Width, rect.Height) ' 選択した範囲をCropメソッドで切り取る
      resultImage.Source = Nothing
      resultImage.Source = myCrop ' 切り取った画像を表示する
 
    End Using
    saveButton.IsEnabled = True
  End Sub

inkCanvas1_MouseUpメソッドの処理

ReleaseAllTouchCapturesメソッドで、キャプチャされている全てのタッチデバイスをこの要素から解放します。

コードはリスト14になります。

リスト14  inkCanvas1_MouseUpメソッドの処理

  Private Sub inkCanvas1_MouseUp(sender As Object, e As MouseButtonEventArgs) Handles inkCanvas1.MouseUp
    inkCanvas1.ReleaseAllTouchCaptures()
  End Sub

[保存(saveButton)]がクリックされた時の処理(saveButton_Clickメソッド)

切り出された画像の表示されている、resultImage.Sourceで初期化された、新しいWriteableBitmapのオブジェクトbmpを作成します。PngBitmapEncoderでPNG形式のイメージのエンコードに使用される新しいエンコーダーのインスタンス、myEncoderを作成します。

指定したBitmapSourceから新しいBitmapFrameを作成して、myEncoderに追加します。

File.Opneメソッドで保存するフォルダとファイル名(TrimmingTest.png)を連結して、フォルダにファイルを書きこみ、FileStream変数fsで参照します。

myEncoder.Save(fs)でビットマップイメージを指定したStream(fs)にエンコードします。

保存した旨のメッセージを表示し、[切り取る]と[保存]ボタンの使用を不可とします。

具体的なコードはリスト15になります。

リスト15 saveButton_Clickメソッド

Private Sub saveButton_Click(sender As Object, e As RoutedEventArgs) Handles saveButton.Click
    Dim bmp As New WriteableBitmap(resultImage.Source)
    Dim myEncoder = New PngBitmapEncoder
    myEncoder.Frames.Add(BitmapFrame.Create(bmp))
    Using fs As FileStream = File.Open(System.IO.Path.Combine(savePath, "TrimmingTest.png"), FileMode.Create)
      myEncoder.Save(fs) ' 指定したフォルダにTrimmingTest.pngとして保存する
  
    End Using
    messageTextBlock.Text = savePath & "フォルダにTrimmingTest.pngとして保存しました。"
    Button1.IsEnabled = False
    saveButton.IsEnabled = False
  End Sub

※注意
今回紹介したサンプルコードを動かす際には、「LeapCSharp.NET4.0.dll」や「LeapCSharp.dll」、「Leap.dll」を読者の皆さん自身のフォルダ内にあるDLLファイルに指定し直さなければ動かない可能性があるので、動かない場合は再指定して下さい。

  • Leap Motionで画像のトリミングと保存を行うためのサンプルプログラム

    『新世代モーションコントローラー Leap Motion -Visual Basicによる実践プログラミング-』 第4回のサンプルプログラムです。
薬師寺国安事務所

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

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