SkiaSharp 中的矩阵转换
下载示例
利用多样的转换矩阵深入了解 SkiaSharp 转换
这是标准的 3 x 3 变换矩阵,与所有新式2D 图形系统中的矩阵类似。
正如您所看到的,可以在 SkiaSharp 中使用转换,而无需知道转换矩阵,但是转换矩阵对于理论观点非常重要,在使用变换来修改路径或处理复杂的触摸输入时,这两种情况都非常重要。
ResetMatrix 。
矩阵相乘在一起来连接两个矩阵。
默认转换矩阵为恒等矩阵,其中包含对角单元中的1个,其他所有位置均为0。
| 1 0 0 | | 0 1 0 | | 0 0 1 |
SKMatrix.MakeIdentity :
SKMatrix matrix = SKMatrix.MakeIdentity();
SKMatrix除非你计划手动设置这些单元格,否则不要使用构造函数。
1,这里讲默认转换,变换后结果不变。
将第三列设为1,等于轴永远是1,弱化为平面。
矩阵乘法就是把第一个矩阵的横和第二个矩阵的竖排相应位相称,将结果相加。
当 SkiaSharp 呈现图形对象时,每个点 (x,y) 会有效地转换为第三列中的 1 x 3 矩阵:
| x y 1 |
您可以将此 1 x 3 矩阵视为表示3D 坐标系中的一个点,但始终在 Z 等于1的2D 平面上。
然后,这一 3 x 3 的矩阵与变换矩阵相乘,结果是在画布上呈现的点:
| 1 0 0 |
| x y 1 | × | 0 1 0 | = | x' y' z' |
| 0 0 1 |
使用标准矩阵乘法,转换后的点如下所示:
x' = x
y' = y
z' = 1
这是默认转换。
2,下面讲坐标转换
对 Translate 对象调用方法时 SKCanvas , tx 方法的和参数将 ty Translate 成为转换矩阵的第三行中的前两个单元格:
| 1 0 0 | | 0 1 0 | | tx ty 1 |
乘法现在如下所示:
| 1 0 0 |
| x y 1 | × | 0 1 0 | = | x' y' z' |
| tx ty 1 |
下面是转换公式:
x' = x + tx
y' = y + ty
3,缩放
在对 Scale 新对象调用方法时 SKCanvas ,生成的转换矩阵包含 sx sy 对角单元格中的和参数:
| sx 0 0 |
| x y 1 | × | 0 sy 0 | = | x' y' z' |
| 0 0 1 |
转换公式如下所示:
x' = sx · x
y' = sy · y
4,将skew自由变形拉伸
在调用后,转换矩阵 Skew 包含两个自变量的矩阵单元中的两个参数:
│ 1 ySkew 0 │
| x y 1 | × │ xSkew 1 0 │ = | x' y' z' |
│ 0 0 1 │
转换公式为:
x' = x + xSkew · y
y' = ySkew · x + y
5, 旋转
对于 RotateDegrees α角度的或的调用 RotateRadians ,转换矩阵如下所示:
│ cos(α) sin(α) 0 │
| x y 1 | × │ –sin(α) cos(α) 0 │ = | x' y' z' |
│ 0 0 1 │
下面是转换公式:
x' = cos(α) · x - sin(α) · y
y' = sin(α) · x - cos(α) · y
当α为180度时,转换矩阵如下所示:
| –1 0 0 | | 0 –1 0 | | 0 0 1 |
180度旋转等效于水平和垂直翻转对象,这也是通过设置–1的缩放因子来完成的。
非仿射转换一文介绍了非仿射转换。
矩阵相乘
这是指乘法的阶数,这一点非常重要,因为矩阵乘法不能交换。
这意味着由方法调用指定的转换是左操作数 (的乘数) 并且当前的转换矩阵是右操作数) (被乘数。
假设 Translate 调用后跟 Scale :
canvas.Translate(tx, ty);
canvas.Scale(sx, sy);
Scale转换乘以 Translate 复合转换矩阵的转换:
| sx 0 0 | | 1 0 0 | | sx 0 0 | | 0 sy 0 | × | 0 1 0 | = | 0 sy 0 | | 0 0 1 | | tx ty 1 | | tx ty 1 |
Scale``Translate如下所示:
canvas.Scale(sx, sy);
canvas.Translate(tx, ty);
在这种情况下,乘法的顺序会反转,缩放因子有效地应用于翻译因素:
| 1 0 0 | | sx 0 0 | | sx 0 0 | | 0 1 0 | × | 0 sy 0 | = | 0 sy 0 | | tx ty 1 | | 0 0 1 | | tx·sx ty·sy 1 |
下面是 Scale 具有透视点的方法:
canvas.Scale(sx, sy, px, py);
这等效于以下转换和缩放调用:
canvas.Translate(px, py);
canvas.Scale(sx, sy);
canvas.Translate(–px, –py);
这三个变换矩阵按照其在代码中的显示方式的相反顺序进行相乘:
| 1 0 0 | | sx 0 0 | | 1 0 0 | | sx 0 0 | | 0 1 0 | × | 0 sy 0 | × | 0 1 0 | = | 0 sy 0 | | –px –py 1 | | 0 0 1 | | px py 1 | | px–px·sx py–py·sy 1 |
SKMatrix 结构
SKMatrix结构定义了九种类型的读/写属性,这些属性 float 对应于转换矩阵的九个单元:
│ ScaleX SkewY Persp0 │ │ SkewX ScaleY Persp1 │ │ TransX TransY Persp2 │
此属性可用于在顺序、、、、、、、和中的一次拍摄中设置或获取九个值 ScaleX SkewX TransX SkewY ScaleY TransY Persp0 Persp1 Persp2 。
如果这些单元格的默认值为0、0和1,则转换将乘以坐标点,如下所示:
│ ScaleX SkewY 0 │
| x y 1 | × │ SkewX ScaleY 0 │ = | x' y' z' |
│ TransX TransY 1 │
x' = ScaleX · x + SkewX · y + TransX
y' = SkewX · x + ScaleY · y + TransY
z' = 1
仿射转换保留了平行线,这意味着不会将矩形转换为平行四边形以外的任何内容。
这些都是返回 SKMatrix 值:
- MakeTranslation
- MakeScale
MakeScale使用透视点MakeRotation以弧度表示的角度MakeRotation对于具有透视点的以弧度表示的角度- MakeRotationDegrees
MakeRotationDegrees使用透视点- MakeSkew
SKMatrix 值。
这两种 Concat 方法的调用方式如下:
SKMatrix.Concat(ref R, A, B);
SKMatrix.Concat(ref R, ref A, ref B);
这会执行以下乘法操作:
R = B × A
这两种 PostConcat 方法的调用方式如下:
SKMatrix.PostConcat(ref A, B);
SKMatrix.PostConcat(ref A, ref B);
这些调用执行以下操作:
A = A × B
这两种 PreConcat 方法类似:
SKMatrix.PreConcat(ref A, B);
SKMatrix.PreConcat(ref A, ref B);
这些调用执行以下操作:
A = B × A
而且,传递作为方法之一结果的参数通常很方便 Make ,例如:
SKMatrix result;
SKMatrix.Concat(result, SKMatrix.MakeTranslation(100, 100),
SKMatrix.MakeScale(3, 3));
这将创建以下矩阵:
│ 3 0 0 │ │ 0 3 0 │ │ 100 100 1 │
SetScaleTranslate :
SKMatrix R = new SKMatrix();
R.SetScaleTranslate(3, 3, 100, 100);
将 SKMatrix 构造函数用于静态 Rotate 和方法也是安全的 RotateDegrees :
SKMatrix R = new SKMatrix();
SKMatrix.Rotate(ref R, radians);
SKMatrix.Rotate(ref R, radians, px, py);
SKMatrix.RotateDegrees(ref R, degrees);
SKMatrix.RotateDegrees(ref R, degrees, px, py);
它们在功能上与 MakeRotation 和方法相同, MakeRotationDegrees 不同之处在于它们不会实例化 SKMatrix 该值。
PathTransform类引用 HendecagramPath 字段中的对象,但使用其构造函数将转换应用于该路径:
public class PathTransformPage : ContentPage
{
SKPath transformedPath = HendecagramArrayPage.HendecagramPath;
public PathTransformPage()
{
Title = "Path Transform";
SKCanvasView canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
SKMatrix matrix = SKMatrix.MakeScale(3, 3);
SKMatrix.PostConcat(ref matrix, SKMatrix.MakeRotationDegrees(360f / 22));
SKMatrix.PostConcat(ref matrix, SKMatrix.MakeTranslation(300, 300));
transformedPath.Transform(matrix);
}
...
}
(因为星形包含11个点,所以不能同时具有这两个点。 ) 这需要将星形旋转360度除以22。
构造函数 SKMatrix 使用方法从三个单独的转换生成一个对象 PostConcat ,该方法采用以下模式,其中 A、B 和 C 是的实例 SKMatrix :
SKMatrix matrix = A;
SKMatrix.PostConcat(ref A, B);
SKMatrix.PostConcat(ref A, C);
这是一系列连续的祖,因此结果如下:
A × B × C
然后,转换转换向右和向下移动了300像素,因此所有坐标都变为正值。
下面是另一种:
SKMatrix matrix = SKMatrix.MakeRotationDegrees(360f / 22);
SKMatrix.PostConcat(ref matrix, SKMatrix.MakeTranslation(100, 100));
SKMatrix.PostConcat(ref matrix, SKMatrix.MakeScale(3, 3));
然后,该星形相对于其新的左上角的大小增加,这是 (0,0) 的点。
PaintSurface处理程序可以简单地呈现以下路径:
public class PathTransformPage : ContentPage
{
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
using (SKPaint paint = new SKPaint())
{
paint.Style = SKPaintStyle.Stroke;
paint.Color = SKColors.Magenta;
paint.StrokeWidth = 5;
canvas.DrawPath(transformedPath, paint);
}
}
}
它显示在画布的左上角:
此程序的构造函数将矩阵应用到具有以下调用的路径:
transformedPath.Transform(matrix);
TryInvert 方法,该方法可获取反向给定矩阵的矩阵:
SKMatrix inverse;
bool success = matrix.TryInverse(out inverse);
将调用方法 TryInverse ,因为并非所有矩阵都是可逆的,但是不可能将不可逆矩阵用于图形转换。
此 SKMatrix 结构使用以单词开头的方法集合(如下所示)来支持这些操作 Map :
SKPoint transformedPoint = matrix.MapPoint(point);
SKPoint transformedPoint = matrix.MapPoint(x, y);
SKPoint[] transformedPoints = matrix.MapPoints(pointArray);
float transformedValue = matrix.MapRadius(floatValue);
SKRect transformedRect = matrix.MapRect(rect);
此方法仅适用于 SKMatrix 表示转换和缩放的值。
交互式试验
此页需要另外两个也用于其他演示的类:
而且, PaintSurface 处理程序必须 Paint 在每个实例中调用方法 TouchPoint ,并向其传递 SKCanvas 对象。
类可定义用于指定视觉对象特性的属性,并且具有参数的名为 Paint 的方法 SKCanvas 可以呈现视觉对象。
此属性可设置为初始化位置;当用户在画布上拖动圆圈时,属性将发生变化。
类包含类型为的 MatrixPaint 属性 SKPaint ,可将其替换为不同的字号或颜色。
ShowAffineMatrixPage.xaml.cs代码隐藏文件创建三个 TouchPoint 对象,然后将其设置为与从嵌入资源加载的位图的三个角相对应的位置:
public partial class ShowAffineMatrixPage : ContentPage
{
SKMatrix matrix;
SKBitmap bitmap;
SKSize bitmapSize;
TouchPoint[] touchPoints = new TouchPoint[3];
MatrixDisplay matrixDisplay = new MatrixDisplay();
public ShowAffineMatrixPage()
{
InitializeComponent();
string resourceID = "SkiaSharpFormsDemos.Media.SeatedMonkey.jpg";
Assembly assembly = GetType().GetTypeInfo().Assembly;
using (Stream stream = assembly.GetManifestResourceStream(resourceID))
{
bitmap = SKBitmap.Decode(stream);
}
touchPoints[0] = new TouchPoint(100, 100); // upper-left corner
touchPoints[1] = new TouchPoint(bitmap.Width + 100, 100); // upper-right corner
touchPoints[2] = new TouchPoint(100, bitmap.Height + 100); // lower-left corner
bitmapSize = new SKSize(bitmap.Width, bitmap.Height);
matrix = ComputeMatrix(bitmapSize, touchPoints[0].Center,
touchPoints[1].Center,
touchPoints[2].Center);
}
...
}
构造函数通过调用来结束 ComputeMatrix ,这 SKMatrix 三个点计算对象的单元格。
scale值从坐标转换 Xamarin.Forms 为像素:
public partial class ShowAffineMatrixPage : ContentPage
{
...
void OnTouchEffectAction(object sender, TouchActionEventArgs args)
{
bool touchPointMoved = false;
foreach (TouchPoint touchPoint in touchPoints)
{
float scale = canvasView.CanvasSize.Width / (float)canvasView.Width;
SKPoint point = new SKPoint(scale * (float)args.Location.X,
scale * (float)args.Location.Y);
touchPointMoved |= touchPoint.ProcessTouchEvent(args.Id, args.Type, point);
}
if (touchPointMoved)
{
matrix = ComputeMatrix(bitmapSize, touchPoints[0].Center,
touchPoints[1].Center,
touchPoints[2].Center);
canvasView.InvalidateSurface();
}
}
...
}
如果任何 TouchPoint 已移动,则方法将 ComputeMatrix 再次调用并使图面无效。
复合矩阵为 S × A :
public partial class ShowAffineMatrixPage : ContentPage
{
...
static SKMatrix ComputeMatrix(SKSize size, SKPoint ptUL, SKPoint ptUR, SKPoint ptLL)
{
// Scale transform
SKMatrix S = SKMatrix.MakeScale(1 / size.Width, 1 / size.Height);
// Affine transform
SKMatrix A = new SKMatrix
{
ScaleX = ptUR.X - ptUL.X,
SkewY = ptUR.Y - ptUL.Y,
SkewX = ptLL.X - ptUL.X,
ScaleY = ptLL.Y - ptUL.Y,
TransX = ptUL.X,
TransY = ptUL.Y,
Persp2 = 1
};
SKMatrix result = SKMatrix.MakeIdentity();
SKMatrix.Concat(ref result, A, S);
return result;
}
...
}
最后,该 PaintSurface 方法基于该矩阵呈现位图,在屏幕底部显示矩阵,并在位图的三个角呈现触摸点:
public partial class ShowAffineMatrixPage : ContentPage
{
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
// Display the bitmap using the matrix
canvas.Save();
canvas.SetMatrix(matrix);
canvas.DrawBitmap(bitmap, 0, 0);
canvas.Restore();
// Display the matrix in the lower-right corner
SKSize matrixSize = matrixDisplay.Measure(matrix);
matrixDisplay.Paint(canvas, matrix,
new SKPoint(info.Width - matrixSize.Width,
info.Height - matrixSize.Height));
// Display the touchpoints
foreach (TouchPoint touchPoint in touchPoints)
{
touchPoint.Paint(canvas);
}
}
}
下面的 iOS 屏幕显示第一次加载页面时的位图,而另两个屏幕在执行某种操作后显示该位图:
从触摸点计算的矩阵会转换位图,使拐角与触摸点重合。
触摸操作中对此进行了介绍。
3 x 3 矩阵的原因
可能需要二维图形系统只需要 2 x 2 的转换矩阵:
│ ScaleX SkewY │
| x y | × │ │ = | x' y' |
│ SkewX ScaleY │
这适用于缩放、旋转甚至扭曲,但它不能是转换的最基本转换。
线性转换无法进行转换。
在三个维度中,线性变换矩阵如下所示:
│ ScaleX SkewYX SkewZX │
| x y z | × │ SkewXY ScaleY SkewZY │ = | x' y' z' |
│ SkewXZ SkewYZ ScaleZ │
标有标签的单元格 SkewXY 表示该值基于 Y 的值倾斜 x 坐标; 单元 SkewXZ 表示该值基于 Z 的值来倾斜 x 坐标; 对于其他单元格,值会倾斜 Skew 。
可以通过将和设置为0,将此3D 变换矩阵限制为二维平面 SkewZX SkewZY ,并将设置 ScaleZ 为1:
│ ScaleX SkewYX 0 │
| x y z | × │ SkewXY ScaleY 0 │ = | x' y' z' |
│ SkewXZ SkewYZ 1 │
如果二维图形完全在3D 空间(其中 Z 等于1)上绘制,则转换乘法如下所示:
│ ScaleX SkewYX 0 │
| x y 1 | × │ SkewXY ScaleY 0 │ = | x' y' 1 |
│ SkewXZ SkewYZ 1 │
所有内容保持在二维平面上,其中 Z 等于1,但 SkewXZ 和 SkewYZ 单元格实际上变成了二维转换因素。
按照类比 (,3D 图形中的变换基于4个 4 x 4 的矩阵。 )
SKMatrixSkiaSharp 中的结构定义该第三行的属性:
│ ScaleX SkewY Persp0 │
| x y 1 | × │ SkewX ScaleY Persp1 │ = | x' y' z` |
│ TransX TransY Persp2 │
非仿射转换一文中介绍了将这些对象移回该平面时会发生什么情况。
相关链接
对应3d绘制,需要4维矩阵:SkM44
坐标体系:左上角开始,往右下走:
* Skia assumes a right-handed coordinate system:
* +X goes to the right
* +Y goes down
* +Z goes into the screen (away from the viewer)
初始化用二维或者1维数组(按行优先RowMajor,列优先方式ColMajor)
默认时列优先模式存储:
/* Stored in column-major. * Indices * 0 4 8 12 1 0 0 trans_x * 1 5 9 13 e.g. 0 1 0 trans_y * 2 6 10 14 0 0 1 trans_z * 3 7 11 15 0 0 0 1 */
平移:坐标在列上
SkM44& setTranslate(SkScalar x, SkScalar y, SkScalar z = 0) { *this = { 1, 0, 0, x, 0, 1, 0, y, 0, 0, 1, z, 0, 0, 0, 1 }; return *this; } SkM44& setScale(SkScalar x, SkScalar y, SkScalar z = 1) { *this = { x, 0, 0, 0, 0, y, 0, 0, 0, 0, z, 0, 0, 0, 0, 1 }; return *this; }
x不等于0时做拉伸:
/** * A matrix is categorized as 'perspective' if the bottom row is not [0, 0, 0, 1]. * For most uses, a bottom row of [0, 0, 0, X] behaves like a non-perspective matrix, though * it will be categorized as perspective. Calling normalizePerspective() will change the * matrix such that, if its bottom row was [0, 0, 0, X], it will be changed to [0, 0, 0, 1] * by scaling the rest of the matrix by 1/X. * * | A B C D | | A/X B/X C/X D/X | * | E F G H | -> | E/X F/X G/X H/X | for X != 0 * | I J K L | | I/X J/X K/X L/X | * | 0 0 0 X | | 0 0 0 1 | */ void normalizePerspective();
与2d的相互转换,去掉了第三行,第三列:
/* When converting from SkM44 to SkMatrix, the third row and * column is dropped. When converting from SkMatrix to SkM44 * the third row and column remain as identity: * [ a b c ] [ a b 0 c ] * [ d e f ] -> [ d e 0 f ] * [ g h i ] [ 0 0 1 0 ] * [ g h 0 i ] */