お絵かきアプリを作る場合にキャンバスやレイヤー、カメラなどを変形させて(移動・回転・スケールなど)、ユーザーの描写操作を助けたり、あるいは描写結果全体を変形させたりしたい場合があります。
この時、Xaml側は RenderTransform を設定するだけで表示できますが、CanvasRenderTargetなどを使ってオフスクリーン描画したい場合には、RenderTransform による行列変換と CanvasRenderTarget 等から生成できる CanvasDrawingSession.Transform に与える変換行列の変形結果を一致させる必要があります。そのための実装例を以下に示します。
<Border RenderTransformOrigin="0.5, 0.5">
<Border.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="{x:Bind Scale, Mode=OneWay}"
ScaleY="{x:Bind Scale, Mode=OneWay}" />
<RotateTransform Angle="{x:Bind Rotate, Mode=OneWay}" />
<TranslateTransform X="{x:Bind TranslateX, Mode=OneWay}"
Y="{x:Bind TranslateY, Mode=OneWay}" />
</TransformGroup>
</Border.RenderTransform>
</Border>
回転した軸に沿った移動はユーザーに負担の掛かる操作になってしまう可能性があるので避けたいです。
そのためにも TranslateTransform を TransformGroup の最後に置くことで回転・スケールの影響を受けない移動変形が表現できます。
回転軸も通常の左上 原点の座標系では扱いづらいため 直感的に操作できる 画像中心 = RenderTransformOrigin="0.5, 0.5"
を指定しています。RenderTaransformOrigin は0~1に正規化された座標指定を行います。
この行列変換をオフスクリーン描画時の CanvasDrawingSession でも再現するには以下のように行列を掛け合わせます。
using System;
using System.Numeric;
// 一旦原点座標に移動させてから回転・スケールを行列変換し、キャンバス中央からの移動量を適用する
var center = new Vector2(cut.CanvasSize.Width, cut.CanvasSize.Height) * 0.5f;
imageLayerTransform = Matrix3x2.CreateTranslation(-center)
* Matrix3x2.CreateRotation(image.RotationDegree * MathF.PI / 180f)
* Matrix3x2.CreateScale(image.Scale)
* Matrix3x2.CreateTranslation(center + new Vector2(image.TranslateX, image.TranslateY))
;
この画像中心を軸にした変形を CanvasRenderTarget 等の原点が X=0, Y=0 の座標系で同じように実現するため、Matrix3x2.CreateTranslation(-center)
による原点座標への移動した上での回転・スケール変換、および Matrix3x2.CreateTranslation(center + (移動量))
による中心原点への戻しと指定移動量の指定をしています。