泥庭

2013年5月26日

マルチタッチで拡大縮小・回転・移動 その3

Filed under: .NET, ストアアプリ, Windows8 — タグ: , , — yone64 @ 8:44 PM

前回前々回の続き。

とりあえず、タッチポイントの中心で拡大縮小・回転できるようにはなったのですが、三角関数とか使いたくないですよね?CompositeTransformが、すべての変形を保持しているので補正が必要になるので、履歴を持つTransformと現在の変形を持つTransformに分ければもう少し話は簡単になります。

XAMLは、下記通り。MatrixTransformとCompositeTransformで変形を表すことで、中心が移動した時の補正を不要にします。

<Ellipse Height="75"  Width="278" Margin="449,306,0,0"  Fill="Red"
         HorizontalAlignment="Left" VerticalAlignment="Top"
         ManipulationMode="All" ManipulationDelta="Ellipse_ManipulationDelta">
    <Ellipse.RenderTransform>
        <TransformGroup>
            <MatrixTransform/>
            <CompositeTransform/>
        </TransformGroup>
    </Ellipse.RenderTransform>
</Ellipse>

コードビハインド側は、TransformGroupの変形をMatrixTransformに移し替えるとともに、CompositeTransformに新たなる変形を設定します。

private void Ellipse_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
    var group = ((UIElement)sender).RenderTransform as TransformGroup;

    var matrix = group.Children[0] as MatrixTransform;
    var composit = group.Children[1] as CompositeTransform;

    matrix.Matrix = group.Value;

    var center = matrix.TransformPoint(new Point(e.Position.X, e.Position.Y));
    composit.CenterX = center.X;
    composit.CenterY = center.Y;

    composit.ScaleX = e.Delta.Scale;
    composit.ScaleY = e.Delta.Scale;

    composit.Rotation = e.Delta.Rotation;

    composit.TranslateX = e.Delta.Translation.X;
    composit.TranslateY = e.Delta.Translation.Y;

}

これで、RoomMetroで発表した内容は全部です。あれから一か月もたってしまった。

2013年5月23日

マルチタッチで拡大縮小・回転・移動 その2

Filed under: .NET, ストアアプリ, Windows8 — タグ: , , — yone64 @ 12:25 AM

前々回の続き。

さてさて、中心点を変えた場合になぜオブジェクトがワープするかというと、下図参照。

image

中央の赤四角を、左上の点で2倍に拡大したものがオレンジの四角です。この拡大の中心を右下の点に移動させると拡大後の四角は緑色のものになります。なので、中心点を移動させた場合、それと同じ方向に(拡大倍率ー1)倍移動させ補正する必要があります。

private void Rectangle_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
    var transform = ((UIElement)sender).RenderTransform as CompositeTransform;
    var diffX = e.Position.X - transform.CenterX;
    var diffY = e.Position.Y - transform.CenterY;

    transform.CenterX = e.Position.X;
    transform.CenterY = e.Position.Y;

    transform.TranslateX += e.Delta.Translation.X + diffX * (transform.ScaleX - 1);
    transform.TranslateY += e.Delta.Translation.Y + diffY * (transform.ScaleY - 1);

    transform.ScaleX *= e.Delta.Scale;
    transform.ScaleY *= e.Delta.Scale;

    transform.Rotation += e.Delta.Rotation;
}

とりあえず、これで拡大時の補正が終了です。同様に回転時の補正もする必要があります。

image

拡大縮小時と同様、中心を左上から右下に移動したケースです。今度はちょっと複雑ですが、中心の移動線の角度+回転角だけ回転させた座標と中心の移動船の角度の座標の差分を補正する必要がでます。

private void Rectangle_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
    var transform = ((UIElement)sender).RenderTransform as CompositeTransform;
    var diffX = e.Position.X - transform.CenterX;
    var diffY = e.Position.Y - transform.CenterY;

    var rad = Math.Atan2(diffY, diffX);
    var d = rad + transform.Rotation * Math.PI / 180;
    var dist = Math.Sqrt(diffX * diffX + diffY * diffY);
    var newX = dist * Math.Cos(d);
    var newY = dist * Math.Sin(d);

    transform.CenterX = e.Position.X;
    transform.CenterY = e.Position.Y;

    transform.ScaleX *= e.Delta.Scale;
    transform.ScaleY *= e.Delta.Scale;

    transform.Rotation += e.Delta.Rotation;

    transform.TranslateX += e.Delta.Translation.X + diffX * (transform.ScaleX - 1) - (diffX - newX) * transform.ScaleX;
    transform.TranslateY += e.Delta.Translation.Y + diffY * (transform.ScaleY - 1) - (diffY - newY) * transform.ScaleY;
}

結果、こんな感じになりました。これでワープしなくなります。ちなみに最後の2行はまとめてもう少し短くなります。

transform.TranslateX += e.Delta.Translation.X - diffX + newX * transform.ScaleX;
transform.TranslateY += e.Delta.Translation.Y - diffY + newY * transform.ScaleY;

#三角関数計算はもう少し簡単になったりするのかな?

2013年5月19日

マルチタッチで拡大縮小・回転・移動

Filed under: .NET, ストアアプリ, Windows8 — タグ: , , , — yone64 @ 4:40 PM

マルチタッチの醍醐味はやっぱり拡大縮小・回転ですよね。
これはManipulationDeltaイベントをハンドルすることで実現することができます。

まず、XAML上で動かしたい対象オブジェクトにCompositeTransformを設定します。

<Rectangle Fill="#FF53F030" HorizontalAlignment="Left" Height="100" Margin="940,188,0,0" Stroke="Black" VerticalAlignment="Top" Width="100" 
           ManipulationMode="All" ManipulationDelta="Rectangle_ManipulationDelta">
    <Rectangle.RenderTransform>
        <CompositeTransform/>
    </Rectangle.RenderTransform>
</Rectangle>

ManipulationDeltaイベントでは、移動差分・拡大縮小差分・回転差分が取得できるので、これをCompositeTransformに設定します。

private void Rectangle_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
    var transform = ((UIElement)sender).RenderTransform as CompositeTransform;

    transform.TranslateX += e.Delta.Translation.X;
    transform.TranslateY += e.Delta.Translation.Y;

    transform.ScaleX *= e.Delta.Scale;
    transform.ScaleY *= e.Delta.Scale;

    transform.Rotation += e.Delta.Rotation;
}

簡単ですね。でも少し操作してみるとわかりますが、拡大縮小および回転の起点が左上で固定されています。これを変更するためには、XAML上でRenderTransformOriginを設定します。”0.5,0.5”を設定すると起点がRectangleの中心になります。

<Rectangle Fill="#FF53F030" HorizontalAlignment="Left" Height="100" Margin="940,188,0,0" Stroke="Black" VerticalAlignment="Top" Width="100" 
           ManipulationMode="All" ManipulationDelta="Rectangle_ManipulationDelta" RenderTransformOrigin="0.5,0.5">
    <Rectangle.RenderTransform>
        <CompositeTransform/>
    </Rectangle.RenderTransform>
</Rectangle>

これの起点をタッチごとに変更するためには、下記通りManipulationDeltaイベントでCompositeTransformのCenterXおよびCenterYを設定するとよいのですが、ことはそんなに単純ではなく…。

private void Rectangle_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
    var transform = ((UIElement)sender).RenderTransform as CompositeTransform;

    transform.CenterX = e.Position.X;
    transform.CenterY = e.Position.Y;

    transform.TranslateX += e.Delta.Translation.X;
    transform.TranslateY += e.Delta.Translation.Y;

    transform.ScaleX *= e.Delta.Scale;
    transform.ScaleY *= e.Delta.Scale;

    transform.Rotation += e.Delta.Rotation;
}

この状態で実行すると、拡大縮小・回転時にRectangleがワープします。残念。

2013年5月18日

手書き文字を認識する

Filed under: .NET, ストアアプリ, Windows8 — タグ: , , , — yone64 @ 3:58 PM

WindowsStoreAppではInkManagerクラスを利用することで、比較的簡単に手書き文字を認識することができます。

サンプルはGitHubにあげてありますので、そちらを参照ください。

まず、GetRecognizersメソッドでInkRecongizerのコレクションを取得し、その中から日本語のInkRecongizerをDefaultRecognizerに設定します。

// 文字識別用のInkRecognizerを取得する。
var recongnizer =
    inkmanager.GetRecognizers()
    .FirstOrDefault(r => r.Name.Contains("日本語"));

// 取得できた場合はInkManagerに設定
if (recongnizer != null)
{
    inkmanager.SetDefaultRecognizer(recongnizer);
}

次に、PointerPressed/PointerMoved/PointerReleasedの各イベントで、それぞれInkManager.ProcessPointerXXXXメソッドを呼び出し手書き情報をInkManagerに認識させます。なお、InkManagerには書き順も認識させないといけないため、PointerPress時のPointerIdを保持し異なる入力イベントが同時に発生することを抑制しています。

// 描画開始
private void Canvas_PointerPressed(object sender, PointerRoutedEventArgs e)
{
    // 入力中のPointerがある場合は、新しい入力は開始しない。
    if (pid != null)
    {
        return;
    }

    // PointerPointの取得
    var canvas = sender as Canvas;
    var point = e.GetCurrentPoint(canvas);

    // 線の初期化
    polyline = new Polyline
    {
        StrokeThickness = 3,
        Stroke = new SolidColorBrush(Colors.Red)
    };

    // 線に頂点を追加
    polyline.Points.Add(point.Position);

    // Canvasに登録して描画
    canvas.Children.Add(polyline);

    // MoveイベントがCanvasで発生するようにキャプチャ
    canvas.CapturePointer(e.Pointer);

    // InkManagerに描画が始まったことを通知
    inkmanager.ProcessPointerDown(point);

    // PointerIdを保持
    pid = point.PointerId;
}

// 描画中
private void Canvas_PointerMoved(object sender, PointerRoutedEventArgs e)
{
    // PointerPointの取得
    var canvas = sender as Canvas;
    var point = e.GetCurrentPoint(canvas);

    // 開始した描画ポイントととこなる場合は、処理しない。
    if (pid != point.PointerId) return;

    // InkManagerに描画ポイントを追加
    polyline.Points.Add(point.Position);

    // InkManagerに描画ポイントの追加
    inkmanager.ProcessPointerUpdate(point);
}

// 描画終了
private void Canvas_PointerReleased(object sender, PointerRoutedEventArgs e)
{
    // PointerPointの取得
    var canvas = (Canvas)sender;
    var point = e.GetCurrentPoint(canvas);

    // 現在描画中の入力デバイス以外からのイベントは受け取らない
    if (pid != point.PointerId)
    {
        return;
    }

    // マウスをリリース
    canvas.ReleasePointerCapture(e.Pointer);

    // InkManagerに描画が終わったことを通知
    inkmanager.ProcessPointerUp(point);
    pid = null;
}

最後にRecognizeAsyncを呼び出して、認識された文字列を取得します。

var rec = await inkmanager.
    RecognizeAsync(InkRecognitionTarget.All);

textBox1.Text =
    string.Join(",",
        rec.FirstOrDefault().GetTextCandidates()
    );

実行結果

スクリーンショット (17)

image

認識された文字列が、確度の高いものから順番に取得できます。

 

2013年5月11日

マルチタッチでお絵かき

Filed under: .NET, ストアアプリ, Windows8 — タグ: , , — yone64 @ 1:09 AM

前回の続き。Room metro #16のハンズオン2つ目です。

今回使うのは、PointerXXXXというイベント群です。これらのイベントに共通するイベント引数PointerRoutedEventArgsからは、PointerPointクラスが取得でき、タッチポイントに関するさまざまな情報が取得できます。このクラスから取得できるPointerIdを利用して、今発生しているイベントの入力ポインターを識別できます。

private void Canvas_PointerEntered(object sender, PointerRoutedEventArgs e)
{
    var canvas = (Canvas)sender;
    var pointerPoint = e.GetCurrentPoint(canvas);

    // 入力ポイントを一意に識別するId
    uint pid = pointerPoint.PointerId;
}

PointerIdと描画中の線を紐づけておくことで、マルチタッチでお絵かきが可能になります。

[5/18追記]プロジェクトをGitHubにあげました。
https://github.com/yone64/RoomMetro16/tree/master/Pointer

XAML側

<Page
    x:Class="Pointer.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Pointer"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Canvas Background="{StaticResource ApplicationPageBackgroundThemeBrush}" 
            PointerPressed="Canvas_PointerPressed" 
            PointerMoved="Canvas_PointerMoved" 
            PointerReleased="Canvas_PointerReleased"
            >
</Page>

コードビハインド側

/// <summary>
/// PointerIdと描画中の線を紐づけるDictionary
/// </summary>
private Dictionary<uint, Polyline> dic = new Dictionary<uint, Polyline>();

// Pointerが押されたときのイベント
private void Canvas_PointerPressed(object sender, PointerRoutedEventArgs e)
{
    // PointerPointの取得
    var canvas = (Canvas)sender;
    var pointerPoint = e.GetCurrentPoint(canvas);
    
    // 赤色でPolylineを描画
    var line = new Polyline 
    {
        StrokeThickness = 3,
        Stroke = new SolidColorBrush(Colors.Red)
    };

    // Polylineに頂点を追加
    line.Points.Add(pointerPoint.Position);

    // PointerIdとPolylineを紐づけ
    dic[pointerPoint.PointerId] = line;

    // キャンバスに描画する線を追加
    canvas.Children.Add(line);

    // MoveイベントがCanvas上で発生するようにPointerをキャプチャ
    canvas.CapturePointer(e.Pointer);
}

// Pointerが動いた時のイベント
private void Canvas_PointerMoved(object sender, PointerRoutedEventArgs e)
{
    // PointerPointの取得
    var pointerPoint = e.GetCurrentPoint((UIElement)sender);
    var pid = pointerPoint.PointerId;

    // Moveイベントは押下中じゃなくても発生するので
    // 描画中かどうかの判断を行う。
    if (dic.ContainsKey(pid))
    {
        // 描画中のPolylineに頂点を追加する
        dic[pid].Points.Add(pointerPoint.Position);
    }
}

// Pointerが離された時のイベント
private void Canvas_PointerReleased(object sender, PointerRoutedEventArgs e)
{
    // PointerPointの取得
    var canvas = (Canvas)sender;
    var pointerPoint = e.GetCurrentPoint(canvas);

    // PointerIdとPolylineを紐づけを解除し、描画を終了。
    dic.Remove(pointerPoint.PointerId);

    // Pointerのキャプチャも終了する
    canvas.ReleasePointerCapture(e.Pointer);
}

実行結果

スクリーンショット (10)

補足

MSDNにある通り、PointerReleasedイベントの代わりにPointerCanceledやPointerCaptureLostイベントが発生することがあるので、きちんとそちらのイベントも対応する必要があります。

2013年5月8日

タッチされたデバイスを取得する

Filed under: .NET, ストアアプリ, Windows8 — タグ: , — yone64 @ 10:01 PM

先日、Room metro #16で発表してきました。

せっかくなので、内容を紹介。前回と前々回は前ふりです。

WindowsStoreアプリでは、TappedイベントからPointerDeviceTypeプロパティーでイベントが発生したデバイスを取得することができます。取得可能なデバイスは次の3種類。

  • Mouse
  • Pen
  • Touch

なお、HoldingイベントはMouseでは発生しません。RightTappedイベントは、Holdingイベント後にエミュレートされます。またHoldingイベントからは、HoldingStateから下記状態が取得できます。

  • Started
  • Completed
  • Canceled

ホールドが始まるとStartedが入った状態でイベントが、終了するとCompletedが入った状態でイベントが発生します。ホールド中にタッチポイントが動くとCanceledが入った状態でイベントが発生します。

XAML側

<Page
    x:Class="DeviceType.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:DeviceType"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <Rectangle Fill="#FF1717C5"  Height="400" Stroke="Black" Width="400" Tapped="Rectangle_Tapped" 
                   Holding="Rectangle_Holding" RightTapped="Rectangle_RightTapped" DoubleTapped="Rectangle_DoubleTapped"/>
        <TextBox x:Name="textBox1" TextWrapping="Wrap" Width="200" HorizontalAlignment="Left" IsReadOnly="True"/>
    </Grid>
</Page>

コードビハインド側

private void Rectangle_Tapped(object sender, TappedRoutedEventArgs e)
{
    var message = e.PointerDeviceType + "で、TAP!";
    InsertString(message);
}

private void Rectangle_Holding(object sender, HoldingRoutedEventArgs e)
{
    var message = e.PointerDeviceType + "で、Hold " + e.HoldingState;
    InsertString(message);
}

private void Rectangle_RightTapped(object sender, RightTappedRoutedEventArgs e)
{
    var message = e.PointerDeviceType + "で、Right TAP!";
    textBox1.Text = message + Environment.NewLine + textBox1.Text;
}

private void Rectangle_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
{
    var message = e.PointerDeviceType + "で、Double TAP!";
    InsertString(message);
}

private void InsertString(string message)
{
    textBox1.Text = message + Environment.NewLine + textBox1.Text;
}

WordPress.com で無料サイトやブログを作成.