ghostyles/soft-contour.jsjavascript
/**
 * ==Ghostyle==
 * @name         Soft Contour (split demo)
 * @version      2.0.0
 * @author       NINA
 * @release_date 2025-01-01
 * @description  Demo split contour: face-api a sinistra, UV-space a destra.
 * ==/Ghostyle==
 */

/**
 * Meta' sinistra in pixel-space: usa i 68 landmark face-api e i helper contour.
 */
export function onDraw(ctx, landmarks) {
  const jaw = landmarks.getJawOutline();
  const nose = landmarks.getNose();

  ctx.save();
  if (!gstmxx.clipLeftHalf(ctx, landmarks)) {
    ctx.restore();
    return;
  }

  gstmxx.drawContourBand(ctx, [jaw[1], jaw[2], jaw[3], jaw[4], jaw[5]], 'contour 2D');
  gstmxx.drawContourBand(ctx, [jaw[15], jaw[14], jaw[13], jaw[12], jaw[11]], 'contour 2D');
  gstmxx.drawContourBand(ctx, [nose[0], nose[1], nose[2]], 'nose 2D');
  gstmxx.drawContourBand(ctx, [nose[4], nose[5], nose[6]], 'nose 2D');

  ctx.restore();
}

/**
 * Meta' destra in UV-space: dipinge bande morbide sulla texture MediaPipe.
 * E' lo stesso intento visivo del contour 2D, ma con pipeline mesh UV.
 */
export function paintUV(ctx) {
  const landmarks3d = gstmxx.lastLandmarks3d;

  ctx.save();
  if (!gstmxx.clipRightHalfUV(ctx, landmarks3d)) {
    ctx.restore();
    return;
  }

  const w = ctx.canvas.width;
  const h = ctx.canvas.height;

  const shade = (x, y, rx, ry, a) => {
    const g = ctx.createRadialGradient(x, y, rx * 0.2, x, y, rx);
    g.addColorStop(0, `rgba(130, 82, 48, ${a})`);
    g.addColorStop(1, 'rgba(130, 82, 48, 0.02)');
    ctx.fillStyle = g;
    ctx.beginPath();
    ctx.ellipse(x, y, rx, ry, 0, 0, Math.PI * 2);
    ctx.fill();
  };

  shade(w * 0.30, h * 0.56, w * 0.12, h * 0.10, 0.26);
  shade(w * 0.70, h * 0.56, w * 0.12, h * 0.10, 0.26);
  shade(w * 0.50, h * 0.52, w * 0.08, h * 0.18, 0.22);

  ctx.restore();
}