42

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

Timo Kähkönen
  • 11,962
  • 9
  • 71
  • 112

2 Answers2

36

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

Live example is in http://jsfiddle.net/xjHUk/278/ and the main code is this:
(only output window: http://jsfiddle.net/xjHUk/279/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}; 
    b.x=Math.round(b.x);
    b.y=Math.round(b.y);
    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/277/ 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):
http://www.rubenswieringa.com/code/as3/flex/DistortImage/

And for additional comparison an example of VALID perspective calculation:
http://zehfernando.com/f/TriangleTest.swf

Community
  • 1
  • 1
Timo Kähkönen
  • 11,962
  • 9
  • 71
  • 112
  • Is it possible to do a four corner distortion using an image in SVG? I want to show some perspective - imagine a folded greeting card. – jon_wu Oct 03 '14 at 22:39
  • 2
    Sorry about late answer, but find a lib just now, although it has been there from 2013. The technique uses transform-attribute. The good thing in this technique is that it can transform any DOM element, not just svg paths as my proposal. The bad thing is that the quality is not so excellent, it is somewhat pixelated in vector shapes, but okay when used bitmap images. I made an example: http://jsbin.com/xuxataqahe. The lib is from http://edankwan.github.io/PerspectiveTransform.js/. The lib may be suitable to use with SVG images also, but not tested it yet. – Timo Kähkönen Jul 26 '15 at 23:57
  • This is great. Would it be difficult to modify this solver to calculate a distortion where the lines of the envelope are curves as in [this example](http://www.neuroproductions.be/experiments/envelope-distort-with-actionscript)? – ALx Aug 01 '16 at 15:01
  • Can we manage an SVG-only solution? I don't see why SVG transformations don't just take a full 3x3 matrix, as would be applied to 3-vectors being used as homogeneous representations of 2-points. That would allow easy projective transformations. It's annoying me, and this is one of the places my annoyance has brought me. – Thomas Poole Apr 29 '17 at 01:27
  • Is it possible to do this without Javascript? Just using declarative styles within SVG? – Christopher Schultz May 15 '18 at 21:01
20

The selected answer is outdated.

Even though SVG does not support changing the perspective of elements, it is possible to achieve this by using CSS3 Transforms.

CSS3 is an extremely powerful way of animating objects. CSS3 Transform can be created and applied in real time using Javascript.

There is good support for CSS3 Transform, with all recent version of major browsers supporting it. (IE 8+, Chrome 31+, Safari 7.1+, Opera 26+, Firefox 34+, Android Browser 4.1+). More info: http://caniuse.com/#feat=transforms2d

For some browser versions you would need to use browser-specific prefixes to create the transformations. However, there is very neat site that explains the good way of handling that: http://bl.ocks.org/mbostock/10571478

Some CSS3 Transform properties are: rotate(angle), scale(dimension). I know, they look soooo similar to SVG Transforms rotate(angle), scale(x y), but it is best not to take them as equal since there are fundamental differences between the two, with CSS3 being far more powerful than SVG Transforms. In addition, CSS3 Transforms has a property perspective that you definitely need to look at.

There is no better place to find more information about CSS3 Transforms than the W3: http://www.w3.org/TR/css3-transforms/

Here is a code snippet: an example of CSS3 Transforms

div {
  height: 150px;
  width: 150px;
}
.container {
  perspective: 500px;
  border: 1px solid black;
  background: gray;
}
.transformed {
  transform: rotateY(50deg);
  background: blue;
}
<!doctype html>
<html>
<body>
<div class="container">
  <div class="transformed"> Hola! He sido transformado!</div>
</div>
</body>
</html>

Happy transforming!

dev4life
  • 10,785
  • 6
  • 60
  • 73
  • 2
    How is it possible to make a four corner drag in your method? This is the point in my QA. – Timo Kähkönen Feb 09 '15 at 02:00
  • @timo yes you can control the perspective through javascript. It would be some very complex coding and may take me a few days to figure out. But in theory it can be done. – Aero Wang Mar 25 '15 at 15:32
  • @AeroWindwalker: Do you mean four corner drag by controlling css transform-attribute values (matrix3D, translate3D, rotate3D, scale3D, transform-origin, perspective, perspective-origin etc.) using javascript? First you know the source and destination points of four corners and you try to calculate right values for those css attributes? – Timo Kähkönen Mar 25 '15 at 21:01
  • Correct, but to be honest if you have the access to the SVG file you can add attributes into the file making them stretchable (perhaps having the users upload the files or mirroring them off remote url) - in which case your life would be easier. – Aero Wang Mar 26 '15 at 00:54
  • @user2070775: The technique you proposed exists. I made a live example that uses lib named PerspectiveTransform.js. See my comment in post: http://stackoverflow.com/a/12919399/1691517. – Timo Kähkönen Aug 07 '15 at 14:28
  • I know this is an ancient thread by now, but I am trying to learn this approach with pure SVG and javascript and I have a question: will this technique be valid for paths that contain bezier curves? – Sergey Rudenko Jan 09 '18 at 16:32
  • @user2070775: "I made a live example that uses lib named PerspectiveTransform.js." The live example is here: http://jsbin.com/xuxataqahe/1/edit?html,css,output – Timo Kähkönen Sep 05 '18 at 21:50
  • @TimoKähkönen this is SOO COOL:) – Sergey Rudenko Sep 05 '18 at 22:06
  • 4
    @user2070775 While most CSS3 is supported within SVG, perspective/3d transforms - like the one you show in your examples - are always ignored by the SVG renderer. – Alex Oct 25 '18 at 05:14