1

I have a quadrilateral, the coordinates of which are known. I need to do a perspective transform and warp an image to those coordinates using Skia. I have referred to the links in Skia's page like https://developer.xamarin.com/guides/xamarin-forms/advanced/skiasharp/transforms/non-affine/ but not able to use the coordinates. How do I use the coordinates instead of matrix?

AUser123
  • 651
  • 1
  • 7
  • 21

1 Answers1

3

Here is a function that takes the coordinates of the four corners on the destination image and the size of the source image. It then generates a matrix that can be passed to skia:

public static SKMatrix CreateMatrixFromPoints(SKPoint topLeft, SKPoint topRight, SKPoint botRight, SKPoint botLeft, float width, float height)
{
    (float x1, float y1) = (topLeft.X, topLeft.Y);
    (float x2, float y2) = (topRight.X, topRight.Y);
    (float x3, float y3) = (botRight.X, botRight.Y);
    (float x4, float y4) = (botLeft.X, botLeft.Y);
    (float w, float h) = (width, height);

    float scaleX = (y1 * x2 * x4 - x1 * y2 * x4 + x1 * y3 * x4 - x2 * y3 * x4 - y1 * x2 * x3 + x1 * y2 * x3 - x1 * y4 * x3 + x2 * y4 * x3) / (x2 * y3 * w + y2 * x4 * w - y3 * x4 * w - x2 * y4 * w - y2 * w * x3 + y4 * w * x3);
    float skewX = (-x1 * x2 * y3 - y1 * x2 * x4 + x2 * y3 * x4 + x1 * x2 * y4 + x1 * y2 * x3 + y1 * x4 * x3 - y2 * x4 * x3 - x1 * y4 * x3) / (x2 * y3 * h + y2 * x4 * h - y3 * x4 * h - x2 * y4 * h - y2 * h * x3 + y4 * h * x3);
    float transX = x1;
    float skewY = (-y1 * x2 * y3 + x1 * y2 * y3 + y1 * y3 * x4 - y2 * y3 * x4 + y1 * x2 * y4 - x1 * y2 * y4 - y1 * y4 * x3 + y2 * y4 * x3) / (x2 * y3 * w + y2 * x4 * w - y3 * x4 * w - x2 * y4 * w - y2 * w * x3 + y4 * w * x3);
    float scaleY = (-y1 * x2 * y3 - y1 * y2 * x4 + y1 * y3 * x4 + x1 * y2 * y4 - x1 * y3 * y4 + x2 * y3 * y4 + y1 * y2 * x3 - y2 * y4 * x3) / (x2 * y3 * h + y2 * x4 * h - y3 * x4 * h - x2 * y4 * h - y2 * h * x3 + y4 * h * x3);
    float transY = y1;
    float persp0 = (x1 * y3 - x2 * y3 + y1 * x4 - y2 * x4 - x1 * y4 + x2 * y4 - y1 * x3 + y2 * x3) / (x2 * y3 * w + y2 * x4 * w - y3 * x4 * w - x2 * y4 * w - y2 * w * x3 + y4 * w * x3);
    float persp1 = (-y1 * x2 + x1 * y2 - x1 * y3 - y2 * x4 + y3 * x4 + x2 * y4 + y1 * x3 - y4 * x3) / (x2 * y3 * h + y2 * x4 * h - y3 * x4 * h - x2 * y4 * h - y2 * h * x3 + y4 * h * x3);
    float persp2 = 1;

    return new SKMatrix(scaleX, skewX, transX, skewY, scaleY, transY, persp0, persp1, persp2);
}

Here is how I got this function:

From the page you've linked, skia calculates the transformed coordinates like this:

x' = ScaleX·x + SkewX·y + TransX
y' = SkewY·x + ScaleY·y + TransY
z` = Persp0·x + Persp1·y + Persp2

xFinal = x' / z'
yFinal = y' / z'

where (x, y) is the original position of a pixel, (xFinal, yFinal) is its new position, and variations of Scale/Skew/Trans/Persp are the values from the supplied matrix.

First, let's slightly rewrite the formulas:

xFinal = (ScaleX·x + SkewX·y + TransX) / (Persp0·x + Persp1·y + Persp2)
yFinal = (SkewY·x + ScaleY·y + TransY) / (Persp0·x + Persp1·y + Persp2)

Given the position of the corners of the quadrilateral (x1,y1), (x2,y2), (x3,y3), (x4,y4) and the size of the image (w,h), we need to find the values of ScaleX, SkewX, TransX, SkewY, ScaleY, TransY, Persp0, Persp1, and Persp2 such that after applying the formulas above:

  • (0,0) becomes (x1,y1) (image top left -> quadrilateral top left)
  • (w,0) becomes (x2,y2) (image top right -> quadrilateral top right)
  • (w,h) becomes (x3,y3) (image bottom right -> quadrilateral bottom right)
  • (0,h) becomes (x4,y4) (image bottom left -> quadrilateral bottom left)

Now we can substitute (x,y) for (0,0); (w,0); (w,h); (0,h) and (xFinal,yFinal) for (x1,y1); (x2,y2); (x3,y3); (x4,y4).

x1 = (ScaleX·0 + SkewX·0 + TransX) / (Persp0·0 + Persp1·0 + Persp2)
y1 = (SkewY·0 + ScaleY·0 + TransY) / (Persp0·0 + Persp1·0 + Persp2)

x2 = (ScaleX·w + SkewX·0 + TransX) / (Persp0·w + Persp1·0 + Persp2)
y2 = (SkewY·w + ScaleY·0 + TransY) / (Persp0·w + Persp1·0 + Persp2)

x3 = (ScaleX·w + SkewX·h + TransX) / (Persp0·w + Persp1·h + Persp2)
y3 = (SkewY·w + ScaleY·h + TransY) / (Persp0·w + Persp1·h + Persp2)

x4 = (ScaleX·0 + SkewX·h + TransX) / (Persp0·0 + Persp1·h + Persp2)
y4 = (SkewY·0 + ScaleY·h + TransY) / (Persp0·0 + Persp1·h + Persp2)

By simplifying and rearranging that, we end up with a system of linear equations.

x1·Persp2 - TransX = 0
y1·Persp2 - TransY = 0

Persp0·w·x2 + Persp2·x2 - ScaleX·w - TransX = 0
Persp0·w·y2 + Persp2·y2 - SkewY·w - TransY = 0

Persp0·w·x3 + Persp1·h·x3 + Persp2·x3 - ScaleX·w - SkewX·h - TransX = 0
Persp0·w·y3 + Persp1·h·y3 + Persp2·y3 - SkewY·w - ScaleY·h - TransY = 0

Persp1·h·x4 + Persp2·x4 - SkewX·h - TransX = 0
Persp1·h·y4 + Persp2·y4 - ScaleY·h - TransY = 0

All that's left is solving it for ScaleX, SkewX, TransX, SkewY, ScaleY, TransY, Persp0, Persp1, and Persp2. In other words, we want to find a way to express these variables in terms of all the other ones which we know. I used this calculator to do the job. It's output are the formulas from the very beginning.

Equbuxu
  • 31
  • 2