Search code examples
javascriptcanvashtml5-canvaspngsharp

How do I use the Canvas to draw a smaller, but same shape inside another whilst maintaining a constant border thickness?


enter image description here

I want to to take any shape, create a smaller version of it, and then use a negative mask to create a new shape that is like an outline of the first object, but with a constant thickness border. To my understanding, this is generally not possible without distorting the smaller image slightly (e.g. changing it's aspect ratio). So I'm ok if the solution creates some distortion of the smaller image.

I also know how to create a negative mask:

ctx.globalCompositeOperation = 'source-out'

Don’t worry about giving me the code for drawing these shapes on the canvas. I have code that does that fine. The problem I’m finding is that the border thickness is not constant because I need to distort the smaller shape to make it so. At the moment I haven’t distorted it and don’t know how.

One approach I thought of might be to move along the outer edge of the large image, and for each point, draw a corresponding point some constant distance inwards, which will be on the edge of the smaller image. In effect tracing the smaller image as you go around the larger one. But I don’t know how to sure the angle is correct. Do I have to use some radius of curvature?

Whether it's a some kind of mathematical solution, algorithm or a pointer to a library that I can use I'm all ears!

Thanks in advance.


Solution

  • Here is one possibility using destination-out to cut out a piece at a time in a temporary canvas to later combine them in the "main" canvas, it's a small code at the end ...

    the image I'm starting with is a sunflower:

    enter image description here

    I thought that would be a good start because of all the odd shape edges

    var s = 10 // thickness scale
    
    var img = new Image;
    img.onload = draw;
    img.src = "https://i.imgur.com/eKKoWVT.png"
    
    var canvas = document.getElementById('c')
    var t_canvas = document.createElement('canvas');
    t_canvas.width = canvas.width
    t_canvas.height = canvas.height
    
    var ctx = canvas.getContext('2d')
    var t_ctx = t_canvas.getContext('2d')
    
    function draw() {
      for (i = 0; i < 8; i += Math.PI / 64) {
        t_ctx.globalCompositeOperation = "source-over";
        t_ctx.drawImage(img, 0, 0, 400, 400);
        t_ctx.globalCompositeOperation = "destination-out";
        t_ctx.drawImage(img, Math.sin(i) * s, Math.cos(i) * s, 400, 400);
        ctx.drawImage(t_canvas, 0, 0, 400, 400);
      }
    }
    <canvas id="c" width=400 height=400></canvas>


    Here is a "slow motion" process showing how the inline drawing is extracted from the big image

    var s = 50 // thickness scale
    var i = 0
    
    var img = new Image;
    img.onload = draw
    img.src = "https://i.imgur.com/eKKoWVT.png"
    
    var canvas = document.getElementById('c')
    var t_canvas = document.createElement('canvas');
    t_canvas.width = canvas.width
    t_canvas.height = canvas.height
    
    var ctx = canvas.getContext('2d')
    var t_ctx = t_canvas.getContext('2d')
    
    function draw() {
      t_ctx.globalCompositeOperation = "source-over";
      t_ctx.drawImage(img, 0, 0, 400, 400);
      t_ctx.globalCompositeOperation = "destination-out";
      t_ctx.drawImage(img, Math.sin(i) * s, Math.cos(i) * s, 400, 400);
      
      ctx.globalCompositeOperation = "xor";
      ctx.drawImage(t_canvas, 0, 0, 400, 400);
      
      ctx.moveTo(200 + Math.sin(i) * 9, 200 + Math.cos(i) * 9)
      ctx.lineTo(200 + Math.sin(i) * s, 200 + Math.cos(i) * s)
      ctx.stroke()
    
      i += Math.PI / 64
      if (i < 8)
        setTimeout(draw, 100);
    }
    <canvas id="c" width=400 height=400></canvas>