CodeZine(コードジン)

特集ページ一覧

画像処理ができるWindows Phoneアプリケーションを作ろう

~ComponentOne Studio for Windows Phoneを活用したアプリ例~

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2012/06/21 14:00

 Windows Phoneアプリを作成する際に重要となるポイントは「分かりやすいデザイン」と「直感的な操作感」です。今回は、複数のWindows Phone用コントロールを組み合わせ、さらにXAMLで調整し、ひと目で操作が分かる「画像をトリミングするサンプルアプリ」を作成してみます。

 Windows Phoneアプリは分かりやすいデザインと直感的な操作感が重要です。この2つを両立させるにはWindows Phone用コントロールの組み合わせやXAMLによる修飾が不可欠です。ComponentOne Studio for Windows Phoneを使うときも同様で複数のコントロールを組み合わせることでコンポーネントを使う効果が高まるときがあります。

 そこでDockPanel for Windows Phone(C1DockPanel)、Imaging for Windows Phone(C1Bitmap)、ProgressBar for Windows Phone(C1ProgressBar)およびC1GestureListenerを使って画像をトリミングするサンプルを作成してみたいと思います。

 なお、サンプルとしてはVB版とC#版の両方を用意し、XAML定義を始めとしてそれぞれのコードが対応づけられるようにプロシージャ順やロジックにも配慮してあるので、ご活用ください(CZ1206ImageVB、CZ1206ImageCS)。

コンポーネントの登録

 ComponentOne Studio for Windows PhoneのコントロールをVisual Studioで利用するには、関連する26個のコントロールをツールボックスへ登録が必要です。[ツールボックスアイテムの選択]ダイアログで名前空間でソートし、C1.Phoneで始まるコントロールをすべてチェックしてください。

図1 Windows Phoneコンポーネントの追加
図1 Windows Phoneコンポーネントの追加

初期準備

アプリ用プロジェクトの準備

 まずは、PictureHubにある写真を画面に表示する部分を作成してみましょう。

 [ファイル]-[新規作成]-[プロジェクト]-[Silverlight for Windows Phone]-[Windows Phoneアプリケーション]の順で選択して、新規にWindows Phoneアプリ用のプロジェクトを作成します。

 プロジェクトが生成されたら、アプリに必要なXAML定義とロジックコードを記述するためのファイルを作成しましょう。クラスを作成する前にファイルを格納する場所として、ソリューションエクスプローラでプロジェクトに[Models]フォルダ、[Viwes]フォルダ、[ViewModels]フォルダ、[Images]フォルダを作成します。そして、MainPage.xamlを[View]フォルダにコピーします。

 このまま実行するとエラーが発生するので、スタートアップ画面であるMainPage.xamlの位置を「MainPage.xaml」から「Views/MainPage.xaml」に変更します。

リスト1 WMAppManifest.xamlの変更例
    :
<DefaultTask  Name ="_default" NavigationPage="Views/MainPage.xaml"/>
    :

C1Bitmapの利用方法

 C1BitmapクラスはWindows Phoneのページに直接貼り付けて利用するものではありません。C1BitmapクラスはWritebleBitmapクラスと同じように、ImageコントロールのSourceに指定して利用します。

 つまり、XAML定義を変更しなくてもSourceに指定している部分をC1Bitmapに切り替えれば、既存アプリにC1Bitmapクラスの豊富な機能が追加できる足がかりを簡単に作ることができます。

画像表示部分の作成

 では、PictureHubから画像を選択して表示する部分を作成しましょう。MainPage.xamlのXAML定義は次のようになります。

リスト2 MainPage.xamlのXAML記述例
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,0,0" 
      VerticalAlignment="Top" HorizontalAlignment="Left"
      Height="535" Width="456">
    <Image x:Name="C1Sample_Image" Stretch="None" Tap="C1Sample_Image_Tap"  />
    <my:C1ProgressBar x:Name="Now_ProgressBar" IsIndeterminate="True" />
</Grid>

 ContentPanel部分にImageコントロールとC1ProgressBarを設置し、ApplicationBarMenuItemに画像選択用のメニューを追加しています(上記リスト2では、見やすさを考慮し一部コードを省略しています。詳細はサンプルコードを確認ください)。これでWindows Phone全体に画像表示エリアを確保することになります。

 注目すべきプロパティとしては、StretchをNoneにしている点です。なぜこのようなプロパティ値をとるかというと、画像の拡大縮小をImageコントロールまかせにせず、コード内で画面に納まらないときだけ縦横比を維持しつつ、縮小表示のみ(拡大表示は抑止)対応するようにしたいからです。

画面表示関連のコード

リスト3-1 MainPage.xamlのコードビハインドの記述例(Visual Basic)
Partial Public Class MainPage
    Inherits PhoneApplicationPage

    Private IsNewPageInstance As Boolean = False

    ' コンストラクター
    Public Sub New()
        InitializeComponent()
        Me.IsNewPageInstance = True
        Me.ApplicationBar.IsMenuEnabled = True
        Me.Now_ProgressBar.IsIndeterminate = False
    End Sub

    ''' <summary>
    ''' 画像選択
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Private Sub Choose_Click(sender As System.Object, e As System.EventArgs)
        Me.Now_ProgressBar.IsIndeterminate = True
        Me.ApplicationBar.IsMenuEnabled = False
        App.ViewModel.PictureShow().Execute(New Size(CType(Me.C1Sample_Image.Parent, Grid).ActualWidth, _
                                                     CType(Me.C1Sample_Image.Parent, Grid).ActualHeight))
    End Sub

    ''' <summary>
    ''' 写真取得
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Private Sub PropertyChanged(sender As Object, e As PropertyChangedEventArgs)
        If e.PropertyName = "Item" Then
            Me.C1Sample_Image.Source = App.ViewModel.PictureItem.ImageSource
            Me.ApplicationBar.IsMenuEnabled = True
            Me.Now_ProgressBar.IsIndeterminate = False
        End If
    End Sub

    ''' <summary>
    ''' ページ遷移時に情報を復元する
    ''' </summary>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Protected Overrides Sub OnNavigatedTo(e As System.Windows.Navigation.NavigationEventArgs)
        MyBase.OnNavigatedTo(e)

        If IsNewPageInstance Then
            AddHandler App.ViewModel.PropertyChanged, AddressOf PropertyChanged
        End If
    End Sub
End Class
リスト3-2 MainPage.xamlのコードビハインドの記述例(C#)
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Phone.Controls;

namespace CZ1206ImageCS
{
    public partial class MainPage : PhoneApplicationPage
    {
        private Boolean IsNewPageInstance = false;

        // コンストラクター
        public MainPage()
        {
            InitializeComponent();
            this.IsNewPageInstance = true;
            this.ApplicationBar.IsMenuEnabled = true;
            this.Now_ProgressBar.IsIndeterminate = false;
        }

        /// <summary>
        /// 画像選択
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Choose_Click(System.Object sender, 
            System.EventArgs e)
        {
            this.Now_ProgressBar.IsIndeterminate = true;
            this.ApplicationBar.IsMenuEnabled = false;
            App.ViewModel.PictureShow.Execute(new Size(((Grid)this.C1Sample_Image.Parent).ActualWidth,
                                                       ((Grid)this.C1Sample_Image.Parent).ActualHeight));
        }

        /// <summary>
        /// 写真取得
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void PropertyChanged(System.Object sender, 
            System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Item")
            {
                this.C1Sample_Image.Source = App.ViewModel.PictureItem.ImageSource;
                this.ApplicationBar.IsMenuEnabled = true;
                this.Now_ProgressBar.IsIndeterminate = false;
            }
        }

        /// <summary>
        /// ページ遷移時に情報を復元する
        /// </summary>
        /// <param name="e"></param>
        protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
        {
            base.OnNavigatedTo(e);

            if (IsNewPageInstance)
            {
                App.ViewModel.PropertyChanged += new PropertyChangedEventHandler(PropertyChanged);
            }
        }
    }
}

 画面上で[画像の選択]メニューをクリックして、Choose_Clickイベントプロシージャを呼び出します。この中では画面上の縦横のサイズをパラメータにして、ViewModelのPictureShow.Executeメソッドを呼び出しています。

 PictureShow.Executeメソッドは、画像選択用の画面を開くだけで実行が終わります。

図2 画像の選択
図2 画像の選択

 画面表示時に自動実行する「OnNavigatedTo」の中でイベントの登録を行っているため、画像を選択した時にはPropertyChangedプロシージャが実行されます。

リスト4-1 画像選択イベントの登録(Visual Basic)
AddHandler App.ViewModel.PropertyChanged, AddressOf PropertyChanged
リスト4-2 画像選択イベントの登録(C#)
App.ViewModel.PropertyChanged += new PropertyChangedEventHandler(PropertyChanged);

 今回のサンプルではApp.ViewModelを経由してPictureModelクラスを使用しています。PictureModelクラスの中ではPictureHubから画像を取得したり、トリミングや色調変換などのロジックも記述したりなどができます。

PictureHubから画像を取得するコード

 PictureModelクラスにPictureHubから画像を取得するコードを記述しましょう。PictureModelクラスを読み解くには、コードの後ろの方にあるShowメソッドの内容から見ていくのが良いでしょう。

リスト5-1 PicuterHubから画像を取得するModelの例(Visual Basic)
Public Class PictureModel
    Implements INotifyPropertyChanged

    Private PhotoTask As New Microsoft.Phone.Tasks.PhotoChooserTask

    ''' <summary>
    ''' PictureHubから画像を選択する
    ''' </summary>
    ''' <param name="parameter">画像描画領域の最大</param>
    ''' <remarks></remarks>
    Public Sub Show(ByVal parameter As Size)
        Me.ActureSize = parameter
        AddHandler Me.PhotoTask.Completed,
            (Sub(d_Sender, d_e)
                 If d_e.TaskResult = Microsoft.Phone.Tasks.TaskResult.OK Then
                     Dim photo As New Imaging.BitmapImage
                     photo.SetSource(d_e.ChosenPhoto)
                     photo.CreateOptions = BitmapCreateOptions.IgnoreImageCache
                     Me.Original_Bitmap = New C1.Phone.Imaging.C1Bitmap(New Imaging.WriteableBitmap(photo))
                 End If
             End Sub)
        Me.PhotoTask.Show()
    End Sub

End Class
リスト5-2 PicuterHubから画像を取得するModelの例(C#)
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace CZ1206ImageCS
{
    public class PictureModel : INotifyPropertyChanged
    {
        private Microsoft.Phone.Tasks.PhotoChooserTask PhotoTask = new Microsoft.Phone.Tasks.PhotoChooserTask();

        /// <summary>
        /// PictureHubから画像を選択する
        /// </summary>
        /// <param name="size"></param>
        internal void Show(Size parameter)
        {
            this.ActureSize = parameter;
            this.PhotoTask.Completed += (d_sender, d_e) => {
                if (d_e.TaskResult == Microsoft.Phone.Tasks.TaskResult.OK) {
                    BitmapImage photo = new BitmapImage();
                    photo.SetSource(d_e.ChosenPhoto);
                    photo.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
                    this.Original_Bitmap = new C1.Phone.Imaging.C1Bitmap(new WriteableBitmap(photo));
                }
            };
            this.PhotoTask.Show();
        }
    }
}

 STEP1:

 Showメソッドは画面表示サイズをparameterパラメタで受け取り、内部変数のActureSizeに保存します。

 STEP2:

 PhotoChooserTaskのComplatedイベントのイベントプロシージャを、ラムダ式で次のように定義します。

リスト6-1 イベント登録ラムダ式の例(Visual Basic)
AddHandler Me.PhotoTask.Completed, (Sub(d_Sender, d_e) …………End Sub)
リスト6-2 イベント登録ラムダ式の例(C#)
this.PhotoTask.Completed += (d_sender, d_e) => { …………}

 この定義部分は、すぐには実行されません。

 STEP3:

 PhotoChooserTaskのShowメソッドを実行してPictureHubの画像選択画面が表示されたら、本プロシージャの実行が完了し、呼び出し元に復帰します。

 STEP4:

 写真が選択された時に初めてCompletedイベントが発生してCompletedイベントプロシージャが呼び出されます。このプロシージャの中では、指定された画像をC1Bitmap型のOriginal_Bitmapプロパティに代入します。Original_Bitmapプロパティは設定した画像を画面のサイズに合わせて縮小表示してくれるプロパティです。

リスト7-1 縮小表示するための実装例(Visual Basic)
Private Property Original_Bitmap As C1.Phone.Imaging.C1Bitmap
    Get
        Return _OriginalBitmap
    End Get
    Set(value As C1.Phone.Imaging.C1Bitmap)
        _OriginalBitmap = value
        If Me.ActureSize.Width < value.Width Then
            Me.Scale = Math.Ceiling((Me.ActureSize.Width / value.Width) * 100) / 100
        End If
        If Me.ActureSize.Height < value.Height Then
            Dim heightScale As Double = Math.Ceiling((Me.ActureSize.Height / value.Height) * 100) / 100
            If Me.Scale > heightScale Then
                Me.Scale = heightScale
            End If
        End If
        If Me.Scale > 1.0 Then
            Me.Scale = 1.0
        End If
        Me._Item = New C1.Phone.Imaging.C1Bitmap(CType(value.Width * Me.Scale, Integer), _
                                                 CType(value.Height * Me.Scale, Integer))
        Me._Item.Copy(value, True)
        Call NotifyPropertyChanged("Item")
    End Set
End Property
Private Property _OriginalBitmap As C1.Phone.Imaging.C1Bitmap = Nothing

Public Property Item As C1.Phone.Imaging.C1Bitmap
    Get
        Return _Item
    End Get
    Set(value As C1.Phone.Imaging.C1Bitmap)
        _Item = value
        Call NotifyPropertyChanged("Item")
    End Set
End Property
Private Property _Item As C1.Phone.Imaging.C1Bitmap = Nothing
リスト7-2 縮小表示するための実装例(C#)
private C1.Phone.Imaging.C1Bitmap Original_Bitmap
{
    get {
        return _OriginalBitmap;
    }
    set {
        _OriginalBitmap = value;
        if (this.ActureSize.Width < value.Width) {
            this.Scale = Math.Ceiling((this.ActureSize.Width / value.Width) * 100) / 100;
        }
        if (this.ActureSize.Height < value.Height) {
            double heightScale = Math.Ceiling((this.ActureSize.Height / value.Height) * 100) / 100;
            if (this.Scale > heightScale) {
                this.Scale = heightScale;
            }
        }
        if (this.Scale > 1.0) {
            this.Scale = 1.0;
        }
        this._Item = new C1.Phone.Imaging.C1Bitmap((int)(value.Width * this.Scale),
                                                   (int)(value.Height * this.Scale));
        this._Item.Copy(value, true);
        NotifyPropertyChanged("Item");
    }
}
private C1.Phone.Imaging.C1Bitmap _OriginalBitmap = null;

public C1.Phone.Imaging.C1Bitmap Item
{
    get {
        return _Item;
    }
    set {
        _Item = value;
        NotifyPropertyChanged("Item");
    }
}
private C1.Phone.Imaging.C1Bitmap _Item = null;

 STEP5:

 Original_Bitmapプロパティに値を設定すると、set部にvalue値として設定値が渡されます。set部では画面表示部の縦サイズと画像の縦サイズ、画面表示部の横サイズと画像の横サイズを比較して縦横比を崩さずに画面に収めるための縮小比Scaleを計算します。

 今回のサンプルでは画面表示部よりも画像サイズが小さいときには拡大は行わないので、Scale > 1.0の場合は等倍になるように1.0の値をScale値に採用しています。

 STEP6:

 Scale値が決まれば、あとは、C1Bitmapの機能を使って画面の縮小を行い、その結果をItemプロパティに設定してNotifyPropertyChangedイベントを発生させます。

 このイベントはViewModelを経由してMainPage.xaml.vb(MainPage.xaml.cs)のPropertyChangedイベントプロシージャを起動し、MainPage.xamlに画像を表示します。

表示部分にC1Bitmapを使う利点について

 ImageクラスやBitmapImageクラス、WritebleBitmapクラスを使わず、C1Bitmapを使う利点はどこにあるのでしょうか。それは、C1BitmapにあるCopyメソッドという強力なメソッドの存在にあります。

 今回のサンプルでは、_Itemを生成する際、表示サイズに合わせるよう縦横を指定してnewしていますが、このようにしてサイズを指定したC1BitmapでCopyメソッドを使えば、そのサイズに合わせてコピー元の画像を縮小拡大してくれます。そのため、たった1行の記述だけで画像ファイルを画面サイズに合わせることができます。

図3 画像の表示
図3 画像の表示

範囲選択部分の作成

 前ページまでで、画像表示部分が完成しました。次は、トリミング領域を指定するための定義を行います。トリミングは画面上をタップしてからパンすると、その領域の外側がグレー表示されるようにします。

 範囲表示にはComponentOne Studio for Windows PhoneのC1DockPanelを使います。

図4 範囲選択の表示
図4 範囲選択の表示
リスト8 範囲選択の表示
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,0,0" 
      VerticalAlignment="Top" HorizontalAlignment="Left"
      Height="535" Width="456">
    <Image x:Name="C1Sample_Image" Stretch="None" Tap="C1Sample_Image_Tap"  />
    <my:C1DockPanel x:Name="Selection_Panel">
        <Border my:C1DockPanel.Dock="Top" 
                Height="{Binding Top}" VerticalAlignment="Top"
                Background="{StaticResource MaskBrush}" />
        <Border my:C1DockPanel.Dock="Bottom" 
                Height="{Binding Bottom}" VerticalAlignment="Bottom"
                Background="{StaticResource MaskBrush}" />
        <Border my:C1DockPanel.Dock="Left" 
                Width="{Binding Left}" HorizontalAlignment="Left"
                Background="{StaticResource MaskBrush}" />
        <Border my:C1DockPanel.Dock="Right" 
                Width="{Binding Right}" HorizontalAlignment="Right"
                Background="{StaticResource MaskBrush}" />
    </my:C1DockPanel>
    <my:C1ProgressBar x:Name="Now_ProgressBar" IsIndeterminate="True" />
</Grid>

 C1DockPanelは画面の上下左右の辺にドッキングしたパネルコントロールです。XAML定義では、上下のパネルではHeightプロパティを、左右のパネルではWidthプロパティをBindingしてこのBinding値を画面上の操作(ジェスチャ)に応じて変化させ、範囲指定ができるようにしています。

範囲指定を行うコード

リスト9-1 範囲選択の表示(Visual Basic)
Private Selection As SelectionSize
Private ActureSize As Rect
Private PointStart As New Point
Private PointEnd As New Point
Private WithEvents GestureListener As C1.Phone.C1GestureListener

' コンストラクター
Public Sub New()
InitializeComponent()
Me.IsNewPageInstance = True
Me.ApplicationBar.IsMenuEnabled = True
Me.Now_ProgressBar.IsIndeterminate = False
Me.GestureListener = C1.Phone.C1GestureService.GetGestureListener(Me.C1Sample_Image)
End Sub

''' <summary>
''' 選択範囲の指定を開始する
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
''' <remarks></remarks>
Private Sub GestureListener_DragStarted(sender As Object,
                                    e As C1.Phone.C1DragStartedGestureEventArgs) _
                                Handles GestureListener.DragStarted
Me.ActureSize = New Rect((CType(Me.C1Sample_Image.Parent, Grid).ActualWidth - Me.C1Sample_Image.ActualWidth) / 2, _
                         (CType(Me.C1Sample_Image.Parent, Grid).Height - Me.C1Sample_Image.ActualHeight) / 2, _
                         Me.C1Sample_Image.ActualWidth, _
                         Me.C1Sample_Image.ActualHeight)
Me.PointStart = e.GetPosition(Me.C1Sample_Image)
Me.PointEnd = e.GetPosition(Me.C1Sample_Image)
Call UpdateSelection(Me.PointStart, Me.PointEnd)
End Sub

''' <summary>
''' 選択範囲指定中の描画を行う
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
''' <remarks></remarks>
Private Sub GestureListener_DragDelta(sender As Object,
                                  e As C1.Phone.C1DragDeltaGestureEventArgs) _
                              Handles GestureListener.DragDelta
Me.PointEnd = e.GetPosition(Me.C1Sample_Image)
Call UpdateSelection(Me.PointStart, Me.PointEnd)
End Sub

''' <summary>
''' 選択範囲の終わりを検出する
''' </summary>
''' <param name="sender"></param>
''' <param name="e"></param>
''' <remarks></remarks>
Private Sub GestureListener_DragCompleted(sender As Object,
                                      e As C1.Phone.C1DragCompletedGestureEventArgs) _
                                  Handles GestureListener.DragCompleted
Me.PointEnd = e.GetPosition(Me.C1Sample_Image)
Call UpdateSelection(Me.PointStart, Me.PointEnd)
End Sub

''' <summary>
''' 選択範囲を更新する
''' </summary>
''' <param name="selectStart"></param>
''' <param name="selectEnd"></param>
''' <remarks></remarks>
Private Sub UpdateSelection(ByVal selectStart As Point, ByVal selectEnd As Point)
selectStart.X = Math.Min(Math.Max(selectStart.X, 0), Me.ActureSize.Width)
selectEnd.X = Math.Min(Math.Max(selectEnd.X, 0), Me.ActureSize.Width)
selectStart.Y = Math.Min(Math.Max(selectStart.Y, 0), Me.ActureSize.Height)
selectEnd.Y = Math.Min(Math.Max(selectEnd.Y, 0), Me.ActureSize.Height)
Me.Selection = New SelectionSize(Me.ActureSize, _
                                 New Point(Math.Round(Math.Min(selectStart.X, selectEnd.X)), _
                                           Math.Round(Math.Min(selectStart.Y, selectEnd.Y))), _
                                 New Point(Math.Round(Math.Max(selectStart.X, selectEnd.X)), _
                                           Math.Round(Math.Max(selectStart.Y, selectEnd.Y))))
Call UpdateMask()
End Sub

''' <summary>
''' トリミングエリアを描画する
''' </summary>
''' <remarks></remarks>
Private Sub UpdateMask()
Me.Selection_Panel.DataContext = Me.Selection
End Sub
リスト9-2 範囲選択の表示(C#)
private SelectionSize Selection;
private Rect ActureSize;
private Point PointStart = new Point();
private Point PointEnd = new Point();
private C1.Phone.C1GestureListener GestureListener;

// コンストラクター
public MainPage()
{
    InitializeComponent();
    this.IsNewPageInstance = true;
    this.ApplicationBar.IsMenuEnabled = true;
    this.Now_ProgressBar.IsIndeterminate = false;
    this.GestureListener = C1.Phone.C1GestureService.GetGestureListener(this.C1Sample_Image);
}

/// <summary>
/// 選択範囲の指定を開始する
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void GestureListener_DragStarted(System.Object sender,
     C1.Phone.C1DragStartedGestureEventArgs e)
{
    this.PointStart = e.GetPosition(this.C1Sample_Image);
    this.PointEnd = e.GetPosition(this.C1Sample_Image);
    UpdateSelection(this.PointStart, this.PointEnd);
}

/// <summary>
/// 選択範囲指定中の描画を行う
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void GestureListener_DragDelta(System.Object sender,
     C1.Phone.C1DragDeltaGestureEventArgs e)
{
    this.PointEnd = e.GetPosition(this.C1Sample_Image);
    UpdateSelection(this.PointStart, this.PointEnd);
}

/// <summary>
/// 選択範囲の終わりを検出する
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void GestureListener_DragCompleted(System.Object sender,
     C1.Phone.C1DragCompletedGestureEventArgs e)
{
    this.PointEnd = e.GetPosition(this.C1Sample_Image);
    UpdateSelection(this.PointStart, this.PointEnd);
}

/// <summary>
/// 選択範囲を更新する
/// </summary>
/// <param name="selectStart"></param>
/// <param name="selectEnd"></param>
private void UpdateSelection(Point selectStart, Point selectEnd)
{
    selectStart.X = Math.Min(Math.Max(selectStart.X, 0), this.ActureSize.Width);
    selectEnd.X = Math.Min(Math.Max(selectEnd.X, 0), this.ActureSize.Width);
    selectStart.Y = Math.Min(Math.Max(selectStart.Y, 0), this.ActureSize.Height);
    selectEnd.Y = Math.Min(Math.Max(selectEnd.Y, 0), this.ActureSize.Height);
    this.Selection = new SelectionSize(this.ActureSize,
                                     new Point(Math.Round(Math.Min(selectStart.X, selectEnd.X)), 
                                               Math.Round(Math.Min(selectStart.Y, selectEnd.Y))), 
                                     new Point(Math.Round(Math.Max(selectStart.X, selectEnd.X)), 
                                               Math.Round(Math.Max(selectStart.Y, selectEnd.Y))));
    UpdateMask();
}

/// <summary>
/// トリミングエリアを描画する
/// </summary>
private void UpdateMask()
{
    this.Selection_Panel.DataContext = this.Selection;
}

 描画範囲の指定にはC1.Phone.C1GestureListenerを使用します。DragStartedイベントで範囲指定の開始検知を行いDragCompletedイベントで範囲指定の終了を検知します。もちろん範囲を指定中もDragDeltaイベントにより常時UpdateSelectionプロシージャを呼び出すようになってます。

 UpdateSelectionプロシージャの中ではマスク範囲を掲載してその結果をC1DockPanelのDataContextに設定してXAML上のBindingに値を供給してます。

トリミング機能の実装

 範囲指定ができたらいよいよ次はトリミングの実装です。MainPage.xaml.vb(MainPage.xaml.cs)ではボタンがクリックされたらViewModelを経由してPictureModelのTrimメソッドを呼び出すようにコーディングされています。すべての処理はPictureModelクラスにあるので、PictureModelクラスの内容を見てみましょう。

リスト10-1 トリミング機能の実装例(Visual Basic)
Public Sub Trim(rect As SelectionSize)
    Dim sizeX As Integer = (Me.ActureSize.Width - (rect.Right + rect.Left))
    Dim sizeY As Integer = (Me.ActureSize.Height - (rect.Bottom + rect.Top))
    Dim crop As New C1.Phone.Imaging.C1Bitmap(sizeX, sizeY)
    Dim posX As Integer = (rect.Left - rect.ActureSize.Left)
    Dim posY As Integer = (rect.Top - rect.ActureSize.Top)

    crop.BeginUpdate()
    For row As Integer = 0 To sizeY - 1
        For col As Integer = 0 To sizeX - 1
            crop.SetPixel(col,
                          row,
                          Me.Item.GetPixel(col + posX,
                                           row + posY))
        Next
    Next
    crop.EndUpdate()
    Me.Item = crop
End Sub
リスト10-2 トリミング機能の実装例(C#)
public void Trim(SelectionSize rect)
{
    int sizeX = (int)(this.ActureSize.Width - (rect.Right + rect.Left));
    int sizeY = (int)(this.ActureSize.Height - (rect.Bottom + rect.Top));
    C1.Phone.Imaging.C1Bitmap crop = new C1.Phone.Imaging.C1Bitmap(sizeX, sizeY);
    int posX = (int)(rect.Left - rect.ActureSize.Left);
    int posY = (int)(rect.Top - rect.ActureSize.Top);

    crop.BeginUpdate();
    for (int row = 0; row <= sizeY - 1; row++)
    {
        for (int col = 0; col <= sizeX - 1; col++)
        {
            crop.SetPixel(col,
                row,
                this.Item.GetPixel(col + posX, 
                                   row + posY));
        }
    }
    crop.EndUpdate();
    this.Item = crop;
}

 残念ながらCopyコマンドのようにトリミング専用のコマンドは存在しませんが、ピクセル単位に取得と設定ができるGetPixelとSetPixelがあるので、この2つを使って必要なピクセルを1つずつ転記します。

図5 トリミング結果の例
図5 トリミング結果の例

 後はトリミング後のデータをSNSに投稿したりSkyDriveにアップしたりする機能をつければ、十分実用的なアプリに仕上がるでしょう。

まとめ

 今回は、C1DockPanel、C1Bitmap、C1ProgressBar、C1GestureListenerの4つを組み合わせて画像処理アプリを作成してみました。1つ1つ使うのも良いですが、このような形で組み合わせて使うことでさらに作りやすくなります。ぜひ、単独で利用するだけなく、複数のコントロールを組み合わせ、効率的な開発を実現してみてください。

  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

  • 初音玲(ハツネアキラ)

     国内SIerのSEでパッケージ製品開発を主に行っており、最近は、空間認識や音声認識などを応用した製品を手掛けています。  個人的には、仕事の内容をさらに拡張したHoloLensなどのMRを中心に活動しています。  Microsoft MVP for Windows Development...

All contents copyright © 2005-2021 Shoeisha Co., Ltd. All rights reserved. ver.1.5