Perspective Transform of Svg Paths (Four Corner Distort)

Perspective transform of SVG paths (four corner distort)

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

Perspective transform for SVG path with Bezier curves

One can apply affine transformations to the control points of Bezier curve and get transformed Bezier curve.

But perspective transformations are not applicable to "usual" (used in fonts) Bezier curves - they produce rational Bezier curves.

Distort pattern like contoured fabric

Use an SVG Filter: feDisplacementMap + feTurbulence. Example:

  <feTurbulence type="turbulence" baseFrequency=".02" numOctaves="4"  result="turb"/>
<feDisplacementMap in="SourceGraphic" in2="turb" scale="0" result="displace" xChannelSelector="B"/>

Longer example: http://codepen.io/mullany/pen/nLtla

Convert SVG Path to Absolute Commands

Here's the JavaScript code I came up with:

function convertToAbsolute(path){
var x0,y0,x1,y1,x2,y2,segs = path.pathSegList;
for (var x=0,y=0,i=0,len=segs.numberOfItems;i<len;++i){
var seg = segs.getItem(i), c=seg.pathSegTypeAsLetter;
if (/[MLHVCSQTA]/.test(c)){
if ('x' in seg) x=seg.x;
if ('y' in seg) y=seg.y;
}else{
if ('x1' in seg) x1=x+seg.x1;
if ('x2' in seg) x2=x+seg.x2;
if ('y1' in seg) y1=y+seg.y1;
if ('y2' in seg) y2=y+seg.y2;
if ('x' in seg) x+=seg.x;
if ('y' in seg) y+=seg.y;
switch(c){
case 'm': segs.replaceItem(path.createSVGPathSegMovetoAbs(x,y),i); break;
case 'l': segs.replaceItem(path.createSVGPathSegLinetoAbs(x,y),i); break;
case 'h': segs.replaceItem(path.createSVGPathSegLinetoHorizontalAbs(x),i); break;
case 'v': segs.replaceItem(path.createSVGPathSegLinetoVerticalAbs(y),i); break;
case 'c': segs.replaceItem(path.createSVGPathSegCurvetoCubicAbs(x,y,x1,y1,x2,y2),i); break;
case 's': segs.replaceItem(path.createSVGPathSegCurvetoCubicSmoothAbs(x,y,x2,y2),i); break;
case 'q': segs.replaceItem(path.createSVGPathSegCurvetoQuadraticAbs(x,y,x1,y1),i); break;
case 't': segs.replaceItem(path.createSVGPathSegCurvetoQuadraticSmoothAbs(x,y),i); break;
case 'a': segs.replaceItem(path.createSVGPathSegArcAbs(x,y,seg.r1,seg.r2,seg.angle,seg.largeArcFlag,seg.sweepFlag),i); break;
case 'z': case 'Z': x=x0; y=y0; break;
}
}
// Record the start of a subpath
if (c=='M' || c=='m') x0=x, y0=y;
}
}

Used like so with the path from the question:

var path = document.querySelector('path');
convertToAbsolute(path);
console.log(path.getAttribute('d'));
// M 17 42 L 117 42 V 142 H 17 Z

Edit: Here's a test page with a path that includes every command (absolute and relative) interleaved and shows that the conversion works in the now-current versions of IE, Chrome, FF, and Safari.

http://phrogz.net/svg/convert_path_to_absolute_commands.svg



Related Topics



Leave a reply



Submit