Search code examples
javascriptcanvashtml5-canvashtml2canvas

Draw dashed border/outline around nontransparent part of image on canvas


I'm drawing an image onto a canvas and trying to apply red dashed outline of approximately 2px but end up with solid red outline. The source image is a .png image with top and bottom texts as part of the image as following example.

A Polar Bear with top and bottom text.

I am using following function to generate red outline:

import { saveAs } from 'file-saver';
export const genOutline = (
  imgSrc
) => {
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');
  const refImage = new Image();
  refImage.src = imgSrc;
  refImage.style.width = "max-content";
  refImage.style.height = "max-content";
  refImage.onload = () => {
    //define hight and width of the canvas
    canvas.width = refImage.width + 30;
    canvas.height = refImage.height + 30;

    var dArr = [-1, -1, 0, -1, 1, -1, -1, 0, 1, 0, -1, 1, 0, 1, 1, 1], // offset array
      s = 2,  // thickness scale
      i = 0,  // iterator
      x = 5,  // final position
      y = 5;

    // draw images at offsets from the array scaled by s
    for (; i < dArr.length; i += 2) {
      ctx.drawImage(refImage, x + dArr[i] * s, y + dArr[i + 1] * s);
    }

    // fill with color
    ctx.globalCompositeOperation = "source-in";
    ctx.fillStyle = "red";
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    ctx.

    // draw original image in normal mode
    ctx.globalCompositeOperation = "source-over";
    ctx.drawImage(refImage, x, y);

    canvas.toBlob((blob) => {
      console.log("blob", blob);
      saveAs(blob, "image_outlined.png");
      canvas.remove();
    });
  }
}

But I want to achieve something similar to what is in the following image:

A Girl portrait with top and bottom text and red dashed outline.


Solution

  • In your code I do not see you tried anything with dashes...
    You can do dashes with setLineDash here are two small examplea

    class Shape {
      constructor(x, y, width, height, color, dashes) {
        this.color = color
        this.dashes = dashes 
        this.path = new Path2D();
        this.path.arc(x, y, height / 3, 0, Math.PI, true);
        this.path.rect(x + width / 2, y, -width, height);
      }
    
      draw(ctx) {
        ctx.beginPath();
        ctx.setLineDash(this.dashes);
        ctx.strokeStyle = this.color;
        ctx.stroke(this.path);
      }
    }
    
    
    var ctx = canvas.getContext("2d");
    var shapes = [];
    shapes.push(new Shape(30, 30, 50, 50, "blue", [3, 3]));
    shapes.push(new Shape(95, 50, 40, 40, "red", [5, 5]));
    
    shapes.forEach((s) => s.draw(ctx));
    <canvas id="canvas" width="160" height="160"></canvas>

    const ctx = canvas.getContext("2d");
    ctx.setLineDash([10,5])
    
    function drawSpiral(x, y, radiusChange, maxRadius) {
      ctx.lineTo(x, y);
      var a = 0, r = radiusChange;
      while (r < maxRadius) {
        a += 1 / r * Math.PI;
        r = a * radiusChange;
        ctx.lineTo(x + Math.cos(a) * r, y + Math.sin(a) * r);
      }
      ctx.stroke();
    }
    drawSpiral(80, 80, 4, 80);
    <canvas id="canvas" width="160" height="160"></canvas>

    The big problem is doing that around the picture outline.
    Your solution for outline was to draw images at offsets, that won't do for dashes you really need to get a true outline then draw it with lines