倾斜转换

查看在 SkiaSharp 中倾斜转换如何创建倾斜图形对象

在 SkiaSharp 中,倾斜转换会使图形对象倾斜,例如此图像中的阴影:

倾斜阴影文本程序中的倾斜效果示例

倾斜会将矩形转换为平行四边形,但倾斜后椭圆形仍是椭圆形。

尽管 Xamarin.Forms 会定义转换、缩放和旋转的属性,但 Xamarin.Forms 中没有对应于倾斜的属性。

SKCanvasSkew 方法接受水平倾斜和垂直倾斜的两个参数:

public void Skew (Single xSkew, Single ySkew)

第二个 Skew 方法将这些参数组合在单个 SKPoint 值中:

public void Skew (SKPoint skew)

但是,不太可能单独使用这两种方法之一。

倾斜试验”页允许你试验介于 -10 和 10 之间的倾斜值。 文本字符串位于页面左上角,并具有从两个 Slider 元素获取的倾斜值。 下面是 SkewExperimentPage 类中的 PaintSurface 处理程序:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    SKImageInfo info = args.Info;
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear();

    using (SKPaint textPaint = new SKPaint
    {
        Style = SKPaintStyle.Fill,
        Color = SKColors.Blue,
        TextSize = 200
    })
    {
        string text = "SKEW";
        SKRect textBounds = new SKRect();
        textPaint.MeasureText(text, ref textBounds);

        canvas.Skew((float)xSkewSlider.Value, (float)ySkewSlider.Value);
        canvas.DrawText(text, 0, -textBounds.Top, textPaint);
    }
}

xSkew 参数的正数值会使文本底部向右移动,其负数值会使文本底部向左移动。 ySkew 的正数值会使文本右侧向下移动,其负数值会使文本右侧向上移动:

倾斜试验页面的屏幕截图(包含三部分)

如果 xSkew 值是 ySkew 值的负值,则结果为旋转,但也会有所缩放。

转换公式如下所示:

x' = x + xSkew · y

y' = ySkew · x + y

例如,对于正数 xSkew 值,转换后的 x' 值会随着 y 的增加而增加。 这就是导致倾斜的原因。

如果三角形宽为 200 像素,高度为 100 像素,其左上角位于点 (0, 0),并且以 1.5 的 xSkew 值呈现,则会得到以下平行四边形:

倾斜转换对矩形的影响

底边的坐标具有 100 的 y 值,因此向右移动了 150 像素。

对于 xSkewySkew 的非零值,只有点 (0, 0) 保持不变。 该点可以被视为是倾斜的中心。 如果需要倾斜中心作为其他内容(通常会是这种情况),则没有 Skew 方法会提供该功能。 需要将 Translate 调用与 Skew 调用显式合并。 若要将倾斜居中于 pxpy,请进行以下调用:

canvas.Translate(px, py);
canvas.Skew(xSkew, ySkew);
canvas.Translate(-px, -py);

复合转换公式为:

x' = x + xSkew · (y – py)

y' = ySkew · (x – px) + y

如果 ySkew 为零,则不会使用 px 值。 该值与 ySkewpy 类似,是不相关的。

你可能会觉得用倾斜角来表示倾斜更合适,例如本图中的角度 α:

倾斜转换对带有倾斜角度的矩形的影响

150 像素移动量与 100 像素垂直量的比值就是该角度的正切值,在本例中为 56.3 度。

倾斜角度试验”页的 XAML 文件类似于“倾斜角度”页,但 Slider 元素的范围从 –90 度到 90 度不等。 SkewAngleExperiment 代码隐藏文件使页面上的文本居中,并使用 Translate 将倾斜中心设置为页面中心。 代码底部的短 SkewDegrees 方法会将角度转换为倾斜值:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    SKImageInfo info = args.Info;
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear();

    using (SKPaint textPaint = new SKPaint
    {
        Style = SKPaintStyle.Fill,
        Color = SKColors.Blue,
        TextSize = 200
    })
    {
        float xCenter = info.Width / 2;
        float yCenter = info.Height / 2;

        string text = "SKEW";
        SKRect textBounds = new SKRect();
        textPaint.MeasureText(text, ref textBounds);
        float xText = xCenter - textBounds.MidX;
        float yText = yCenter - textBounds.MidY;

        canvas.Translate(xCenter, yCenter);
        SkewDegrees(canvas, xSkewSlider.Value, ySkewSlider.Value);
        canvas.Translate(-xCenter, -yCenter);
        canvas.DrawText(text, xText, yText, textPaint);
    }
}

void SkewDegrees(SKCanvas canvas, double xDegrees, double yDegrees)
{
    canvas.Skew((float)Math.Tan(Math.PI * xDegrees / 180),
                (float)Math.Tan(Math.PI * yDegrees / 180));
}

当角度接近正 90 度或负 90 度时,正切值会接近无穷大,但最多 80 度左右的角度是可用的:

倾斜角度试验页面的屏幕截图(包含三部分)

如 "斜体文字"页面所示,小幅负水平倾斜可模仿斜体或斜体文字。 ObliqueTextPage 类显示其完成方式:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    SKImageInfo info = args.Info;
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear();

    using (SKPaint textPaint = new SKPaint()
    {
        Style = SKPaintStyle.Fill,
        Color = SKColors.Maroon,
        TextAlign = SKTextAlign.Center,
        TextSize = info.Width / 8       // empirically determined
    })
    {
        canvas.Translate(info.Width / 2, info.Height / 2);
        SkewDegrees(canvas, -20, 0);
        canvas.DrawText(Title, 0, 0, textPaint);
    }
}

void SkewDegrees(SKCanvas canvas, double xDegrees, double yDegrees)
{
    canvas.Skew((float)Math.Tan(Math.PI * xDegrees / 180),
                (float)Math.Tan(Math.PI * yDegrees / 180));
}

SKPaintTextAlign 属性设置为 Center。 如果没有任何转换,带 (0, 0) 坐标的 DrawText 调用会将文本定位在左上角基线的水平中心。 SkewDegrees 会将文本相对于基线水平倾斜 20 度。 Translate 调用会将文本基线的水平中心移动到画布中心:

倾斜文本页面的屏幕截图(包含三部分)

倾斜阴影文本页面演示了如何使用 45 度倾斜和垂直比例的组合来制作偏离文字的文字阴影。 下面是 PaintSurface 处理程序的相关部分:

using (SKPaint textPaint = new SKPaint())
{
    textPaint.Style = SKPaintStyle.Fill;
    textPaint.TextSize = info.Width / 6;   // empirically determined

    // Common to shadow and text
    string text = "Shadow";
    float xText = 20;
    float yText = info.Height / 2;

    // Shadow
    textPaint.Color = SKColors.LightGray;
    canvas.Save();
    canvas.Translate(xText, yText);
    canvas.Skew((float)Math.Tan(-Math.PI / 4), 0);
    canvas.Scale(1, 3);
    canvas.Translate(-xText, -yText);
    canvas.DrawText(text, xText, yText, textPaint);
    canvas.Restore();

    // Text
    textPaint.Color = SKColors.Blue;
    canvas.DrawText(text, xText, yText, textPaint);
}

首先显示阴影,然后显示文本:

倾斜阴影文本页面的屏幕截图(包含三部分)

传递给 DrawText 方法的垂直坐标指示文本相对于基线的位置。 这与用于倾斜中心的垂直坐标相同。 如果文本字符串包含下行字符,此方法将不起作用。 例如,将单词“quirky”替换为“Shadow”,结果如下:

倾斜阴影文本页面的屏幕截图(包含三部分),其中包含采用下伸形式的变形词

阴影和文本仍与基线对齐,但效果看起来不太对。 若要修复此问题,需要获取文本边界:

SKRect textBounds = new SKRect();
textPaint.MeasureText(text, ref textBounds);

需要根据下行字母的高度进行调整 Translate 调用:

canvas.Translate(xText, yText + textBounds.Bottom);
canvas.Skew((float)Math.Tan(-Math.PI / 4), 0);
canvas.Scale(1, 3);
canvas.Translate(-xText, -yText - textBounds.Bottom);

现在,阴影从这些下行字符的底部延伸:

倾斜阴影文本页面的屏幕截图(包含三部分),其中包含针对下伸部分的调整