title: Significant error when approximating elliptical arcs with bezier curves on canvas with javascript
body: <p>I'm trying to convert svg path to canvas in javascript, however it's really hard to map svg path elliptical arcs to canvas path. One of the ways is to approximate using multiple bezier curves.</p>
  
  <p>I have successfully <a href="https://jsfiddle.net/fqdhv8jr/8/" rel="noreferrer">implemented</a> the approximation of elliptical arcs with bezier curves however the approximation isn't very accurate.</p>
  
  <p>My code:</p>
  
  <pre><code>var canvas = document.getElementById("canvas");
  var ctx = canvas.getContext("2d");
  
  canvas.width = document.body.clientWidth;
  canvas.height = document.body.clientHeight;
  ctx.strokeWidth = 2;
  ctx.strokeStyle = "#000000";
  function clamp(value, min, max) {
    return Math.min(Math.max(value, min), max)
  }
  
  function svgAngle(ux, uy, vx, vy ) {
    var dot = ux*vx + uy*vy;
    var len = Math.sqrt(ux*ux + uy*uy) * Math.sqrt(vx*vx + vy*vy);
  
    var ang = Math.acos( clamp(dot / len,-1,1) );
    if ( (ux*vy - uy*vx) &lt; 0)
      ang = -ang;
    return ang;
  }
  
  function generateBezierPoints(rx, ry, phi, flagA, flagS, x1, y1, x2, y2) {
    var rX = Math.abs(rx);
    var rY = Math.abs(ry);
  
    var dx2 = (x1 - x2)/2;
    var dy2 = (y1 - y2)/2;
  
    var x1p =  Math.cos(phi)*dx2 + Math.sin(phi)*dy2;
    var y1p = -Math.sin(phi)*dx2 + Math.cos(phi)*dy2;
  
    var rxs = rX * rX;
    var rys = rY * rY;
    var x1ps = x1p * x1p;
    var y1ps = y1p * y1p;
  
    var cr = x1ps/rxs + y1ps/rys;
    if (cr &gt; 1) {
      var s = Math.sqrt(cr);
      rX = s * rX;
      rY = s * rY;
      rxs = rX * rX;
      rys = rY * rY;
    }
  
    var dq = (rxs * y1ps + rys * x1ps);
    var pq = (rxs*rys - dq) / dq;
    var q = Math.sqrt( Math.max(0,pq) );
    if (flagA === flagS)
      q = -q;
    var cxp = q * rX * y1p / rY;
    var cyp = - q * rY * x1p / rX;
  
    var cx = Math.cos(phi)*cxp - Math.sin(phi)*cyp + (x1 + x2)/2;
    var cy = Math.sin(phi)*cxp + Math.cos(phi)*cyp + (y1 + y2)/2;
  
    var theta = svgAngle( 1,0, (x1p-cxp) / rX, (y1p - cyp)/rY );
  
    var delta = svgAngle(
      (x1p - cxp)/rX, (y1p - cyp)/rY,
      (-x1p - cxp)/rX, (-y1p-cyp)/rY);
  
    delta = delta - Math.PI * 2 * Math.floor(delta / (Math.PI * 2));
  
    if (!flagS)
      delta -= 2 * Math.PI;
  
    var n1 = theta, n2 = delta;
  
  
    // E(n)
    // cx +acosθcosη−bsinθsinη
    // cy +asinθcosη+bcosθsinη
    function E(n) {
      var enx = cx + rx * Math.cos(phi) * Math.cos(n) - ry * Math.sin(phi) * Math.sin(n);
      var eny = cy + rx * Math.sin(phi) * Math.cos(n) + ry * Math.cos(phi) * Math.sin(n);
      return {x: enx,y: eny};
    }
  
    // E'(n)
    // −acosθsinη−bsinθcosη
    // −asinθsinη+bcosθcosη
    function Ed(n) {
      var ednx = -1 * rx * Math.cos(phi) * Math.sin(n) - ry * Math.sin(phi) * Math.cos(n);
      var edny = -1 * rx * Math.sin(phi) * Math.sin(n) + ry * Math.cos(phi) * Math.cos(n);
      return {x: ednx, y: edny};
    }
  
    var n = [];
    n.push(n1);
  
    var interval = Math.PI/4;
  
    while(n[n.length - 1] + interval &lt; n2)
      n.push(n[n.length - 1] + interval)
  
    n.push(n2);
  
    function getCP(n1, n2) {
      var en1 = E(n1);
      var en2 = E(n2);
      var edn1 = Ed(n1);
      var edn2 = Ed(n2);
  
      var alpha = Math.sin(n2 - n1) * (Math.sqrt(4 + 3 * Math.pow(Math.tan((n2 - n1)/2), 2)) - 1)/3;
  
      console.log(en1, en2);
  
      return {
        cpx1: en1.x + alpha*edn1.x,
        cpy1: en1.y + alpha*edn1.y,
        cpx2: en2.x - alpha*edn2.x,
        cpy2: en2.y - alpha*edn2.y,
        en1: en1,
        en2: en2
      };
    }
  
    var cps = []
    for(var i = 0; i &lt; n.length - 1; i++) {
      cps.push(getCP(n[i],n[i+1]));
    }
    return cps;
  }
  
  // M100,200
  ctx.moveTo(100,200)
  // a25,100 -30 0,1 50,-25
  var rx = 25, ry=100 ,phi =  -30 * Math.PI / 180, fa = 0, fs = 1, x = 100, y = 200, x1 = x + 50, y1 = y - 25;
  
  var cps = generateBezierPoints(rx, ry, phi, fa, fs, x, y, x1, y1);
  
  var limit = 4;
  
  for(var i = 0; i &lt; limit &amp;&amp; i &lt; cps.length; i++) {
    ctx.bezierCurveTo(cps[i].cpx1, cps[i].cpy1,
                      cps[i].cpx2, cps[i].cpy2,
                      i &lt; limit - 1 ? cps[i].en2.x : x1, i &lt; limit - 1 ? cps[i].en2.y : y1);
  }
  ctx.stroke()
  </code></pre>
  
  <p>With the result:</p>
  
  <p><a href="https://i.stack.imgur.com/rbkM9.png" rel="noreferrer"><img src="https://i.stack.imgur.com/rbkM9.png" alt="Elliptical arc and its approximation"></a></p>
  
  <p>The red line represents the svg path elliptical arc and the black line represents the approximation</p>
  
  <p>How can I accurately draw any possible elliptical arc on canvas?</p>
  
  <h1>Update:</h1>
  
  <p>Forgot to mention the original source of the algorithm: <a href="https://mortoray.com/2017/02/16/rendering-an-svg-elliptical-arc-as-bezier-curves/" rel="noreferrer">https://mortoray.com/2017/02/16/rendering-an-svg-elliptical-arc-as-bezier-curves/</a></p>