タッチ操作による画像の変形を簡単に実現する

ManipulationDeltaイベントではユーザーのタッチ操作を移動、拡大縮小および回転として取得することができます。

<XAML>

<Canvas Background="{StaticResource ApplicationPageBackgroundThemeBrush}" ManipulationMode="All" ManipulationDelta="Image_ManipulationDelta">

    <Image x:Name="Target" Source="neko.jpg">

        <Image.RenderTransform>

            <CompositeTransform />

        </Image.RenderTransform>

    </Image>

</Canvas> 

<CS>

private void Image_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)

{

    var image = (Canvas)sender;

    var tr = (CompositeTransform)Target.RenderTransform;

    // まずは平行移動成分を取得

    var x = e.Delta.Translation.X;

    var y = e.Delta.Translation.Y;

    // 次に拡大縮小

    var scale = e.Delta.Scale;

    // 回転

    var rotation = e.Delta.Rotation;

    // それらをImageに適用します

    tr.TranslateX += x;

    tr.TranslateY += y;

    tr.ScaleX = tr.ScaleY = tr.ScaleX * scale;

    tr.Rotation += rotation;

}

 しかしながら単純に移動、拡大縮小、回転を対象に適用しただけでは、操作の作用点と対象画像の変形基準点が異なるため思ったような結果を得られません。

f:id:nya360:20131210230013p:plain

f:id:nya360:20131210230023p:plain

拡大縮小、回転するときは画像の基準点も同時に移動する必要があります。

まずは拡大縮小から見ていきましょう。

f:id:nya360:20131210230534p:plain

図は点Oを中心にX倍拡大操作した状態です。

点Aを「OAをX倍拡大した位置A'」に移動させる必要があります。

移動距離 = OA' - OA = (OA * X) - OA = OA * (X - 1)

// 操作点Oから画像基準点Aの相対位置OA

var dx = tr.TranslateX - e.Position.X;

var dy = tr.TranslateY - e.Position.Y;

// 移動距離 = OA * (X - 1)

x += dx * (scale - 1);

y += dy * (scale - 1); 

次は回転です。f:id:nya360:20131210230543p:plain図では点Oを中心にR度回転操作を行いました。

点Aを「OAをR度回転したA'」に移動させましょう。

移動距離 = OA' - OA = ROT(OA) - OA

ROT()の詳細については線形代数の範疇になりますのでここではベクトルの回転を表す式だけ紹介しておきます。

|x'| = |cosθ -sinθ||x|

|y'|   |sinθ  cosθ||y|

// OAをrotation回転させただけ移動する

var sin = Math.Sin(rotation * Math.PI / 180);

var cos = Math.Cos(rotation * Math.PI / 180);

// 移動距離 = ROT(OA) - OA

x += (cos * dx - sin * dy) - dx;

y += (sin * dx + cos * dy) - dy; 

 これでタッチ操作に追随する画像変形を実現できます。

 

参考までに完成したManipulationDeltaイベントを載せておきます。

         private void Image_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
        {
            var image = (Canvas)sender;
            var tr = (CompositeTransform)Target.RenderTransform;

            // まずは平行移動から
            var x = e.Delta.Translation.X;
            var y = e.Delta.Translation.Y;

            // 拡大縮小
            var scale = e.Delta.Scale;

            // 操作点Oから画像基準点Aの相対位置OA
            var dx = tr.TranslateX - e.Position.X;
            var dy = tr.TranslateY - e.Position.Y;

            // 移動距離 = OA * (X - 1)
            x += dx * (scale - 1);
            y += dy * (scale - 1);

            // 回転
            var rotation = e.Delta.Rotation;

            // OAをrotation回転させただけ移動する
            var sin = Math.Sin(rotation * Math.PI / 180);
            var cos = Math.Cos(rotation * Math.PI / 180);
            // 移動距離 = ROT(OA) - OA
            x += (cos * dx - sin * dy) - dx;
            y += (sin * dx + cos * dy) - dy;

            tr.TranslateX += x;
            tr.TranslateY += y;
            tr.ScaleX = tr.ScaleY = tr.ScaleX * scale;
            tr.Rotation += rotation;
        }