Generate a heart-shaped vignette on a canvas

by Arjan Haverkamp

CSS

body {
  background-color: #fff
}

JavaScript

function vignetteHeart(width, height, innerPercent) {
  // Clamp innerPercent between 0 and 1
  const pct = Math.max(0, Math.min(innerPercent, 1));

  // Create and size the canvas
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');
  canvas.width = width;
  canvas.height = height;

  //
  // Draw white vignette (transparent center -> white edges)
  //
  const cx = width / 2;
  const cy = height / 2;
  const rx1 = width / 2;
  const ry1 = height / 2;

  ctx.save();
  ctx.translate(cx, cy);
  ctx.scale(rx1, ry1); // unit circle -> ellipse

  const r0 = pct;             // inner (transparent) radius
  const r1 = Math.SQRT2;      // reaches corners
  const grad = ctx.createRadialGradient(0, 0, r0, 0, 0, r1);
  grad.addColorStop(0, 'rgba(255,0,255,0)');
  grad.addColorStop(1, 'rgba(255,0,255,0.85)');

  ctx.fillStyle = grad;
  ctx.fillRect(-1, -1, 2, 2);
  ctx.restore();

  //
  // Punch out the heart
  //
  // SVG heart path (viewBox ~ 0 0 16 16). We'll fill it to erase pixels.
  const HEART_PATH = 'M8 1.314C12.438-3.248 23.534 4.735 8 15C-7.534 4.736 3.562-3.248 8 1.314';
  const path = new Path2D(HEART_PATH);

  // Size the heart with some padding, keep aspect, center it
  const padding = Math.round(Math.min(width, height) / 15);
  const size = Math.min(width, height) - 2 * padding; // max heart box
  const scale = size / 16; // the path is ~16x16 units
  const drawW = 16 * scale;
  const drawH = 16 * scale;
  const ox = (width - drawW) / 2;
  const oy = (height - drawH) / 2;

  // Erase (make transparent) where the heart is
  ctx.save();
  ctx.globalCompositeOperation = 'destination-out';
  ctx.translate(ox, oy);
  ctx.scale(scale, scale);
  ctx.fill(path); // fill to remove the interior
  ctx.restore();

  // (Optional) draw a visible outline on top:
  ctx.save();
  ctx.translate(ox, oy);
  ctx.scale(scale, scale);
  ctx.lineWidth = 0.1;
  ctx.strokeStyle =...