title: Is there a way to perfectly calculate the difference in radius between an outer rectangle and an inner rectangle?
body: <p>Okay, so I have this function:</p>
  <pre><code>function roundedRect(x,y,w,h,r1,r2,r3,r4){
      ctx.beginPath();
      ctx.moveTo(x + r1,y);
      ctx.lineTo(x + w - r2,y);
      ctx.arcTo(x + w,y,x + w,y + r2,r2);
      ctx.lineTo(x + w,y + h - r3);
      ctx.arcTo(x + w,y + h,x + w - r3,y + h,r3);
      ctx.lineTo(x + r4,y + h);
      ctx.arcTo(x,y + h,x,y + h - r4,r4);
      ctx.lineTo(x,y + r1);
      ctx.arcTo(x,y,x + r1,y,r1);
      ctx.closePath();
  }
  </code></pre>
  <p>This draws a rectangle with arced edges according to values for radii. Here's how it looks in action:</p>
  <p><div class="snippet" data-lang="js" data-hide="true" data-console="false" data-babel="false">
  <div class="snippet-code snippet-currently-hidden">
  <pre class="snippet-code-html lang-html prettyprint-override"><code>&lt;canvas id = "c" width = "100" height = "100"&gt;&lt;/canvas&gt;
  
  &lt;script&gt;
      let ctx = document.getElementById("c").getContext("2d");
      function roundedRect(ctx,x,y,w,h,r1,r2,r3,r4){
          ctx.beginPath();
          ctx.moveTo(x + r1,y);
          ctx.lineTo(x + w - r2,y);
          ctx.arcTo(x + w,y,x + w,y + r2,r2);
          ctx.lineTo(x + w,y + h - r3);
          ctx.arcTo(x + w,y + h,x + w - r3,y + h,r3);
          ctx.lineTo(x + r4,y + h);
          ctx.arcTo(x,y + h,x,y + h - r4,r4);
          ctx.lineTo(x,y + r1);
          ctx.arcTo(x,y,x + r1,y,r1);
          ctx.closePath();
      }
      
      ctx.lineWidth = 10;
      ctx.fillStyle = "red";
      ctx.strokeStyle = "black";
      roundedRect(ctx,5,5,90,90,10,10,10,30);
      ctx.stroke();
      ctx.fill();
  &lt;/script&gt;</code></pre>
  </div>
  </div>
  </p>
  <p>Now, let's say I wanted to render one of these rectangles inside the other. The first render is <em>only</em> the stroke, and the second render is <em>only</em> the fill. This looks like so:</p>
  <p><div class="snippet" data-lang="js" data-hide="true" data-console="false" data-babel="false">
  <div class="snippet-code snippet-currently-hidden">
  <pre class="snippet-code-html lang-html prettyprint-override"><code>&lt;canvas id = "c" width = "100" height = "100"&gt;&lt;/canvas&gt;
  
  &lt;script&gt;
      let ctx = document.getElementById("c").getContext("2d");
      function roundedRect(ctx,x,y,w,h,r1,r2,r3,r4){
          ctx.beginPath();
          ctx.moveTo(x + r1,y);
          ctx.lineTo(x + w - r2,y);
          ctx.arcTo(x + w,y,x + w,y + r2,r2);
          ctx.lineTo(x + w,y + h - r3);
          ctx.arcTo(x + w,y + h,x + w - r3,y + h,r3);
          ctx.lineTo(x + r4,y + h);
          ctx.arcTo(x,y + h,x,y + h - r4,r4);
          ctx.lineTo(x,y + r1);
          ctx.arcTo(x,y,x + r1,y,r1);
          ctx.closePath();
      }
      
      ctx.fillStyle = "red";
      ctx.strokeStyle = "black";
      ctx.lineWidth = 10;
      roundedRect(ctx,5,5,90,90,10,10,10,30);
      ctx.stroke();
      ctx.beginPath();
      roundedRect(ctx,5,5,90,90,10,10,10,30);
      ctx.fill();
  &lt;/script&gt;</code></pre>
  </div>
  </div>
  </p>
  <p>Everything's looking great so far. But it's <em>not</em> good so far. If I reduce the transparency of the render then you can see a very ugly issue:</p>
  <p><div class="snippet" data-lang="js" data-hide="true" data-console="false" data-babel="false">
  <div class="snippet-code snippet-currently-hidden">
  <pre class="snippet-code-html lang-html prettyprint-override"><code>&lt;canvas id = "c" width = "100" height = "100"&gt;&lt;/canvas&gt;
  
  &lt;script&gt;
      let ctx = document.getElementById("c").getContext("2d");
      function roundedRect(ctx,x,y,w,h,r1,r2,r3,r4){
          ctx.beginPath();
          ctx.moveTo(x + r1,y);
          ctx.lineTo(x + w - r2,y);
          ctx.arcTo(x + w,y,x + w,y + r2,r2);
          ctx.lineTo(x + w,y + h - r3);
          ctx.arcTo(x + w,y + h,x + w - r3,y + h,r3);
          ctx.lineTo(x + r4,y + h);
          ctx.arcTo(x,y + h,x,y + h - r4,r4);
          ctx.lineTo(x,y + r1);
          ctx.arcTo(x,y,x + r1,y,r1);
          ctx.closePath();
      }
      
      ctx.globalAlpha = 0.3;
      ctx.fillStyle = "red";
      ctx.strokeStyle = "black";
      ctx.lineWidth = 10;
      roundedRect(ctx,5,5,90,90,10,10,10,30);
      ctx.stroke();
      ctx.beginPath();
      roundedRect(ctx,5,5,90,90,10,10,10,30);
      ctx.fill();
  &lt;/script&gt;</code></pre>
  </div>
  </div>
  </p>
  <p>You can see that the edges of the fill overlap the stroke, and it causes a very ugly effect. The obvious solution is to scoot the fill in a bit (increase its x and y by half the stroke width, decrease its width and height by full stroke width) but that causes there to be an ugly, unwanted gap in each corner as can be seen below:</p>
  <p><div class="snippet" data-lang="js" data-hide="true" data-console="false" data-babel="false">
  <div class="snippet-code snippet-currently-hidden">
  <pre class="snippet-code-html lang-html prettyprint-override"><code>&lt;canvas id = "c" width = "100" height = "100"&gt;&lt;/canvas&gt;
  
  &lt;script&gt;
      let ctx = document.getElementById("c").getContext("2d");
      function roundedRect(ctx,x,y,w,h,r1,r2,r3,r4){
          ctx.beginPath();
          ctx.moveTo(x + r1,y);
          ctx.lineTo(x + w - r2,y);
          ctx.arcTo(x + w,y,x + w,y + r2,r2);
          ctx.lineTo(x + w,y + h - r3);
          ctx.arcTo(x + w,y + h,x + w - r3,y + h,r3);
          ctx.lineTo(x + r4,y + h);
          ctx.arcTo(x,y + h,x,y + h - r4,r4);
          ctx.lineTo(x,y + r1);
          ctx.arcTo(x,y,x + r1,y,r1);
          ctx.closePath();
      }
      
      ctx.globalAlpha = 0.3;
      ctx.fillStyle = "red";
      ctx.strokeStyle = "black";
      ctx.lineWidth = 10;
      roundedRect(ctx,5,5,90,90,10,10,10,30);
      ctx.stroke();
      ctx.beginPath();
      roundedRect(ctx,10,10,80,80,10,10,10,30);
      ctx.fill();
  &lt;/script&gt;</code></pre>
  </div>
  </div>
  </p>
  <p>A fix for this would be to simply calculate a new radius for each corner. This absolutely works so long as it's done right. I found that multiplying by 0.666 seems to yield a <em>very</em> close result most of the time, and it's what I've been using up until now. But, as can be seen below, it's far from perfect and its imperfections only grow stronger with larger radii.</p>
  <p><div class="snippet" data-lang="js" data-hide="true" data-console="false" data-babel="false">
  <div class="snippet-code snippet-currently-hidden">
  <pre class="snippet-code-html lang-html prettyprint-override"><code>&lt;canvas id = "c" width = "100" height = "100"&gt;&lt;/canvas&gt;
  
  &lt;script&gt;
      let ctx = document.getElementById("c").getContext("2d");
      function roundedRect(ctx,x,y,w,h,r1,r2,r3,r4){
          ctx.beginPath();
          ctx.moveTo(x + r1,y);
          ctx.lineTo(x + w - r2,y);
          ctx.arcTo(x + w,y,x + w,y + r2,r2);
          ctx.lineTo(x + w,y + h - r3);
          ctx.arcTo(x + w,y + h,x + w - r3,y + h,r3);
          ctx.lineTo(x + r4,y + h);
          ctx.arcTo(x,y + h,x,y + h - r4,r4);
          ctx.lineTo(x,y + r1);
          ctx.arcTo(x,y,x + r1,y,r1);
          ctx.closePath();
      }
      
      ctx.globalAlpha = 0.3;
      ctx.fillStyle = "red";
      ctx.strokeStyle = "black";
      ctx.lineWidth = 10;
      roundedRect(ctx,5,5,90,90,10,10,10,30);
      ctx.stroke();
      ctx.beginPath();
      roundedRect(ctx,10,10,80,80,10 * 0.666,10  * 0.666,10 * 0.666,30 * 0.666);
      ctx.fill();
  &lt;/script&gt;</code></pre>
  </div>
  </div>
  </p>
  <p>So now finally onto my question... Is there any means of perfectly calculating this new radius? Is there some magic number to multiply by which will always yield 100% perfect results? Is it a lot harder than I expect? In my final result, I want a way to make these two rectangles line up perfectly no matter the radius.</p>