翻译转换
了解如何使用平移变换来移动 SkiaSharp 图形
SkiaSharp 中最简单的变换类型是平移或平移变换。 此变换会朝水平和垂直方向移动图形对象。 从某种意义上讲,平移是最不必要的变换,因为通常你只需更改绘图函数中使用的坐标即可实现相同的效果。 但是,在呈现路径时,所有坐标都封装在路径中,因此应用平移变换来移动整个路径要容易得多。
平移对于动画和简单文本效果也很有用:
SKCanvas
中的 Translate
方法有两个参数,它们会导致后续绘制的图形对象水平和垂直移动:
public void Translate (Single dx, Single dy)
这些参数可为负。 第二个 Translate
方法将两个平移值合并到一个 SKPoint
值中:
public void Translate (SKPoint point)
示例程序的“累积平移”页演示了对 Translate
方法的多次调用是累积性的。 AccumulatedTranslatePage
类显示同一矩形的 20 个版本,每个版本都与前一个矩形有足够的偏移,以便它们沿着对角线拉伸。 下面是 PaintSurface
事件处理程序:
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
using (SKPaint strokePaint = new SKPaint())
{
strokePaint.Color = SKColors.Black;
strokePaint.Style = SKPaintStyle.Stroke;
strokePaint.StrokeWidth = 3;
int rectangleCount = 20;
SKRect rect = new SKRect(0, 0, 250, 250);
float xTranslate = (info.Width - rect.Width) / (rectangleCount - 1);
float yTranslate = (info.Height - rect.Height) / (rectangleCount - 1);
for (int i = 0; i < rectangleCount; i++)
{
canvas.DrawRect(rect, strokePaint);
canvas.Translate(xTranslate, yTranslate);
}
}
}
连续矩形沿着页面向下缓慢移动:
如果累积平移因子为 dx
和 dy
,并且在绘图函数中指定的点为 (x
, y
),则图形对象将在点 (x'
, y'
) 处呈现,其中:
x' = x + dx
y' = y + dy
这些公式称为平移的变换公式。 新 SKCanvas
的 dx
和 dy
默认值为 0。
通常我们会将平移变换用于阴影效果和类似技术,如“平移文本效果”页所示。 下面是 TranslateTextEffectsPage
类中 PaintSurface
处理程序的相关部分:
float textSize = 150;
using (SKPaint textPaint = new SKPaint())
{
textPaint.Style = SKPaintStyle.Fill;
textPaint.TextSize = textSize;
textPaint.FakeBoldText = true;
float x = 10;
float y = textSize;
// Shadow
canvas.Translate(10, 10);
textPaint.Color = SKColors.Black;
canvas.DrawText("SHADOW", x, y, textPaint);
canvas.Translate(-10, -10);
textPaint.Color = SKColors.Pink;
canvas.DrawText("SHADOW", x, y, textPaint);
y += 2 * textSize;
// Engrave
canvas.Translate(-5, -5);
textPaint.Color = SKColors.Black;
canvas.DrawText("ENGRAVE", x, y, textPaint);
canvas.ResetMatrix();
textPaint.Color = SKColors.White;
canvas.DrawText("ENGRAVE", x, y, textPaint);
y += 2 * textSize;
// Emboss
canvas.Save();
canvas.Translate(5, 5);
textPaint.Color = SKColors.Black;
canvas.DrawText("EMBOSS", x, y, textPaint);
canvas.Restore();
textPaint.Color = SKColors.White;
canvas.DrawText("EMBOSS", x, y, textPaint);
}
在这三个示例中,均调用了 Translate
来显示文本,以将其从 x
和 y
变量指定的位置偏移。 然后文本再次以另一种颜色显示,且没有平移效果:
这三个示例均显示了否定 Translate
调用的不同方式:
第一个示例只是再次调用 Translate
,但使用负值。 由于 Translate
调用是累积性的,因此该调用序列只是将总变换还原为默认值零。
第二个示例调用 ResetMatrix
。 这会导致所有变换还原到其默认状态。
第三个示例通过调用 Save
来保存 SKCanvas
对象的状态,然后通过调用 Restore
来还原状态。 这是操控一系列绘图操作的变换的最通用方式。 这些 Save
和 Restore
调用的功能类似于堆栈:可以多次调用 Save
,然后按相反顺序调用 Restore
以还原到之前的状态。 Save
方法返回一个整数,你可以将该整数传递给 RestoreToCount
以有效地多次调用 Restore
。 SaveCount
属性返回当前保存在堆栈中的状态数。
还可以使用 SKAutoCanvasRestore
类还原画布状态。 此类的构造函数需在 using
语句中调用;画布状态会在 using
块的末尾自动还原。
不过,不必担心从 PaintSurface
处理程序的一次调用到下一次调用的变换。 对 PaintSurface
的每次新调用都会传递一个具有默认变换的全新 SKCanvas
对象。
Translate
变换的另一个常见用途是呈现视觉对象,该对象最初是使用便于绘制的坐标创建的。 例如,你可能想要指定以点 (0, 0) 为中心的模拟时钟的坐标。 然后,可以使用变换将时钟显示在所需位置。 [Hendecagram 数组] 页演示了此方法。 HendecagramArrayPage
类首先为 11 角星创建一个 SKPath
对象。 HendecagramPath
对象定义为公共、静态和只读对象,以便可以从其他演示程序访问它。 它是在静态构造函数中创建的:
public class HendecagramArrayPage : ContentPage
{
...
public static readonly SKPath HendecagramPath;
static HendecagramArrayPage()
{
// Create 11-pointed star
HendecagramPath = new SKPath();
for (int i = 0; i < 11; i++)
{
double angle = 5 * i * 2 * Math.PI / 11;
SKPoint pt = new SKPoint(100 * (float)Math.Sin(angle),
-100 * (float)Math.Cos(angle));
if (i == 0)
{
HendecagramPath.MoveTo(pt);
}
else
{
HendecagramPath.LineTo(pt);
}
}
HendecagramPath.Close();
}
}
如果星形的中心是点 (0, 0),则星形的所有点都在围绕该点的圆上。 每个点都是角度的正弦值和余弦值的组合,该角度以 360 度的 5/11 增加。 (也可以通过按圆的 2/11、3/11 或 4/11 增加角度来创建 11 角星。)该圆的半径设置为 100。
如果在不进行任何变换的情况下呈现此路径,则中心将位于 SKCanvas
的左上角,并且只有四分之一可见。 HendecagramPage
的 PaintSurface
处理程序改用 Translate
在画布上平铺星形的多个副本,每个副本的颜色是随机的:
public class HendecagramArrayPage : ContentPage
{
Random random = new Random();
...
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())
{
for (int x = 100; x < info.Width + 100; x += 200)
for (int y = 100; y < info.Height + 100; y += 200)
{
// Set random color
byte[] bytes = new byte[3];
random.NextBytes(bytes);
paint.Color = new SKColor(bytes[0], bytes[1], bytes[2]);
// Display the hendecagram
canvas.Save();
canvas.Translate(x, y);
canvas.DrawPath(HendecagramPath, paint);
canvas.Restore();
}
}
}
}
结果如下:
动画通常涉及到变换。 “十六角星动画”页将 11 角星绕圆移动。 HendecagramAnimationPage
类以某些字段以及 OnAppearing
和 OnDisappearing
方法的重写开头,以启动和停止 Xamarin.Forms 计时器:
public class HendecagramAnimationPage : ContentPage
{
const double cycleTime = 5000; // in milliseconds
SKCanvasView canvasView;
Stopwatch stopwatch = new Stopwatch();
bool pageIsActive;
float angle;
public HendecagramAnimationPage()
{
Title = "Hedecagram Animation";
canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
}
protected override void OnAppearing()
{
base.OnAppearing();
pageIsActive = true;
stopwatch.Start();
Device.StartTimer(TimeSpan.FromMilliseconds(33), () =>
{
double t = stopwatch.Elapsed.TotalMilliseconds % cycleTime / cycleTime;
angle = (float)(360 * t);
canvasView.InvalidateSurface();
if (!pageIsActive)
{
stopwatch.Stop();
}
return pageIsActive;
});
}
protected override void OnDisappearing()
{
base.OnDisappearing();
pageIsActive = false;
}
...
}
angle
字段每隔 5 秒从 0 度动画变换为 360 度。 PaintSurface
处理程序以两种方式使用 angle
属性:在 SKColor.FromHsl
方法中指定颜色的色调,以及作为 Math.Sin
和 Math.Cos
方法的参数来控制星形的位置:
public class HendecagramAnimationPage : ContentPage
{
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
canvas.Translate(info.Width / 2, info.Height / 2);
float radius = (float)Math.Min(info.Width, info.Height) / 2 - 100;
using (SKPaint paint = new SKPaint())
{
paint.Style = SKPaintStyle.Fill;
paint.Color = SKColor.FromHsl(angle, 100, 50);
float x = radius * (float)Math.Sin(Math.PI * angle / 180);
float y = -radius * (float)Math.Cos(Math.PI * angle / 180);
canvas.Translate(x, y);
canvas.DrawPath(HendecagramPage.HendecagramPath, paint);
}
}
}
PaintSurface
处理程序调用 Translate
方法两次,首先变换到画布的中心,然后变换到以 (0, 0) 为中心的圆的周长。 圆的半径设置为尽可能大,同时仍将星形保持在页面范围内:
请注意,星形围绕页面中心旋转时保持相同的方向。 它根本不会旋转。 旋转是旋转变换的任务。