The Skew Transform

PDF for offline use
Sample Code:
Related APIs:

Let us know how you feel about this

Translation Quality


0/250

last updated: 2017-03

See how the skew transform can create tilted graphical objects in SkiaSharp

In SkiaSharp, the skew transform tilts graphical objects, such as the shadow in this image:

The skew transforms rectangles into parallelograms, but a skewed ellipse is still an ellipse.

Although Xamarin.Forms defines properties for translation, scaling, and rotations, there is no corresponding property in Xamarin.Forms for skew.

The Skew method of SKCanvas accepts two arguments for horizontal skew and vertical skew:

public void Skew (Single xSkew, Single ySkew)

A second Skew method combines those arguments in a single SKPoint value:

public void Skew (SKPoint skew)

However, it's unlikely that you'll be using either of these two methods in isolation.

The Skew Experiment page lets you experiment with skew values that range between –10 and 10. A text string is positioned in the upper-left corner of the page, with skew values obtained from two Slider elements. Here is the PaintSurface handler in the SkewExperimentPage class:

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);
    }
}

Values of the xSkew argument shift the bottom of the text right for positive values or left for negative values. Values of ySkew shift the right of the text down for positive values or up for negative values:

If xSkew is the negative of ySkew, the result is rotation, but also scaled somewhat as the Windows display indicates.

The transform formulas are as follows:

x' = x + xSkew · y

y' = ySkew · x + y

For example, for a positive xSkew value, the transformed x' value increases as y increases. That's what causes the tilt.

If a triangle 200 pixels wide and 100 pixels high is positioned with its upper-left corner at the point (0, 0) and is rendered with an xSkew value of 1.5, the following parallelogram results:

The coordinates of the bottom edge have y values of 100, so it is shifted 150 pixels to the right.

For non-zero values of xSkew or ySkew, only the point (0, 0) remains the same. That point can be considered the center of skewing. If you need the center of skewing to be something else (which is usually the case), there is no Skew method that provides that. You'll need to explicitly combine Translate calls with the Skew call. To center the skewing at px and py, make the following calls:

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

The composite transform formulas are:

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

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

If ySkew is zero, and you're only specifying a non-zero value of xSkew, then px value is not used. The value is irrelevant, and similarly for ySkew and py.

You might feel more comfortable specifying skew as an angle of tilt, such as the angle α in this diagram:

The ratio of the 150-pixel shift to the 100-pixel vertical is the tangent of that angle, in this example 56.3 degrees.

The XAML file of the Skew Angle Experiment page is similar to the Skew Angle page except that the Slider elements range from –90 to 90 degrees. The SkewAngleExperiment code-behind file centers the text on the page and uses Translate to set a center of skewing to the center of the page. A short SkewDegrees method at the bottom of the code converts angles to skew values:

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));
}

As an angle approaches positive or negative 90 degrees, the tangent approaches infinity, but angles up to about 80 degrees or so are usable:

A small negative horizontal skew can mimic oblique or italic text, as the Oblique Text page demonstrates. The ObliqueTextPage class shows how it's done:

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));
}

The TextAlign property of SKPaint is set to Center. Without any transforms, the DrawText call with coordinates of (0, 0) would position the text with the horizontal center of the baseline at the upper-left corner. The SkewDegrees skews the text horizontally 20 degrees relative to the baseline. The Translate call moves the horizontal center of the text's baseline to the center of the canvas:

The Skew Shadow Text page demonstrates how to use a combination of a 45-degree skew and vertical scale to make a text shadow that tilts away from the text. Here's the pertinent part of the PaintSurface handler:

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);
}

The shadow is displayed first and then the text:

The vertical coordinate passed to the DrawText method indicates the position of the text relative to the baseline. That is the same vertical coordinate used for the center of skewing. This technique will not work if the text string contains descenders. For example, subsitute the word "quirky" for "Shadow" and here's the result:

The shadow and text are still aligned at the baseline, but the effect just looks wrong. To fix it you need to obtain the text bounds:

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

The Translate calls need to be adjusted by the height of the descenders:

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);

Now the shadow extends from the bottom of those descenders:

Xamarin Workbook

If it's not already installed, install the Xamarin Workbooks app first. The workbook file should download automatically, but if it doesn't, just click to start the workbook download manually.