Timo Timo - 4 months ago 43x
CSS Question

Perspective transform of SVG paths (four corner distort)

How is it possible to distort paths in SVG in browser so that they are distorted to certain perspective using possibly javascript or css? The perspective distort can be made easily in Photoshop, Illustrator etc, but how about browsers?

This is source path:

enter image description here

And this is the path after transformation:

enter image description here


This is my drag distort proposal (share you knowledge, Q&A-style).

Live example is in http://jsfiddle.net/xjHUk/86/ and the main code is this:
(only output window: http://jsfiddle.net/xjHUk/86/embedded/result/)

function transferPoint (xI, yI, source, destination)
    var ADDING = 0.001; // to avoid dividing by zero

    var xA = source[0].x;
    var yA = source[0].y;

    var xC = source[2].x;
    var yC = source[2].y;

    var xAu = destination[0].x;
    var yAu = destination[0].y;

    var xBu = destination[1].x;
    var yBu = destination[1].y;

    var xCu = destination[2].x;
    var yCu = destination[2].y;

    var xDu = destination[3].x;
    var yDu = destination[3].y;

    // Calcultations
    // if points are the same, have to add a ADDING to avoid dividing by zero
    if (xBu==xCu) xCu+=ADDING;
    if (xAu==xDu) xDu+=ADDING;
    if (xAu==xBu) xBu+=ADDING;
    if (xDu==xCu) xCu+=ADDING;
    var kBC = (yBu-yCu)/(xBu-xCu);
    var kAD = (yAu-yDu)/(xAu-xDu);
    var kAB = (yAu-yBu)/(xAu-xBu);
    var kDC = (yDu-yCu)/(xDu-xCu);

    if (kBC==kAD) kAD+=ADDING;
    var xE = (kBC*xBu - kAD*xAu + yAu - yBu) / (kBC-kAD);
    var yE = kBC*(xE - xBu) + yBu;

    if (kAB==kDC) kDC+=ADDING;
    var xF = (kAB*xBu - kDC*xCu + yCu - yBu) / (kAB-kDC);
    var yF = kAB*(xF - xBu) + yBu;

    if (xE==xF) xF+=ADDING;
    var kEF = (yE-yF) / (xE-xF);

    if (kEF==kAB) kAB+=ADDING;
    var xG = (kEF*xDu - kAB*xAu + yAu - yDu) / (kEF-kAB);
    var yG = kEF*(xG - xDu) + yDu;

    if (kEF==kBC) kBC+=ADDING;
    var xH = (kEF*xDu - kBC*xBu + yBu - yDu) / (kEF-kBC);
    var yH = kEF*(xH - xDu) + yDu;

    var rG = (yC-yI)/(yC-yA);
    var rH = (xI-xA)/(xC-xA);

    var xJ = (xG-xDu)*rG + xDu;
    var yJ = (yG-yDu)*rG + yDu;

    var xK = (xH-xDu)*rH + xDu;
    var yK = (yH-yDu)*rH + yDu;

    if (xF==xJ) xJ+=ADDING;
    if (xE==xK) xK+=ADDING;
    var kJF = (yF-yJ) / (xF-xJ); //23
    var kKE = (yE-yK) / (xE-xK); //12

    var xKE;
    if (kJF==kKE) kKE+=ADDING;
    var xIu = (kJF*xF - kKE*xE + yE - yF) / (kJF-kKE);
    var yIu = kJF * (xIu - xJ) + yJ;

    var b={x:xIu,y:yIu}; 
    return b;

The result is distorted correctly to perspective (two vanishing point one). The principle of two point perspective calculation is here. The script can handle SVG path data if it meets the following requirements:

  • All coordinates are absolute (which means uppercase letters). See this.
  • Arc ("A") is not used
  • V and H are normalized to L

Arcs can be normalized, but I have not found any crossbrowser way yet. V and H to L is easy task, you have to get the last used x or y coordinate and add the missing one after L.

The same script can handle also curves in path (curves are from Times). The following is exactly same code but the path attribute ("d") is different:

http://jsfiddle.net/xjHUk/45/ function dummy(a) {return a;} (This code has no check for invalid positions, like the above).

Paths of above examples are got from SVG versions of Arial and Times. Please note that fonts uses Cartesian coordinate system, in which y-coordinate increases when going upwards. Otherwise SVG uses Polar coordinate system, which is used in bitmap images and css. This means that when using paths from SVG fonts in above code, the path have to be flipped vertically and scaled to desired font-size. TTF fonts (and their SVG counterparts) have usually em size 2048, so the bounding box of glyph is without scaling 2048 px, which usually is too much when SVG glyph path is converted to SVG path.

But if you want to distort other SVG paths, then flipping and scaling in unnecessary.

This is fairly long code (much because of drag functionality), but I think that the same effect can be achieved also some css-3D-transform-way, but not luck in such implementation yet...

For comparison an example of non-perspective distort (SVG's main competitor SWF):

And for additional comparison an example of VALID perspective calculation: