feat(frontend-live): Update AnonymousRenderer to support anonymous session rendering and fix rendering behavior

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Claude Code 2026-03-18 01:03:30 -07:00
parent f4204906a2
commit 7fb87cc729

View file

@ -0,0 +1,140 @@
import type { NormalizedLandmark } from '@mediapipe/tasks-vision';
import type { RendererScratch } from './RendererPool';
// Fixed palette — colors ARE the identity of this mask, not customizable
const FACE_WHITE = '#F5F2EC';
const CHEEK_RED = '#CC2B1D';
const FEATURE_INK = '#1A1A1A';
const BROW_INK = '#111111';
export function drawAnonymousMask(
ctx: CanvasRenderingContext2D,
scratch: RendererScratch,
landmarks: NormalizedLandmark[],
w: number,
h: number,
): void {
function pt(idx: number): [number, number] | null {
const lm = landmarks[idx];
if (!lm) return null;
return [lm.x * w, lm.y * h];
}
const oc = scratch.ctx;
oc.clearRect(0, 0, w, h);
const leftIris = pt(468);
const rightIris = pt(473);
const chin = pt(152);
const forehead = pt(10);
const noseTip = pt(4);
const lipLeft = pt(61);
const lipRight = pt(291);
const leftTemple = pt(234);
const rightTemple = pt(454);
if (!leftIris || !rightIris || !chin || !forehead) return;
const interEye = Math.hypot(rightIris[0] - leftIris[0], rightIris[1] - leftIris[1]);
const cx = (leftIris[0] + rightIris[0]) / 2;
const cy = forehead[1] + (chin[1] - forehead[1]) * 0.48;
const headH = (chin[1] - forehead[1]) * 0.92;
const headW = headH * 0.62;
// ── 1. White oval face ────────────────────────────────────────────────────
oc.fillStyle = FACE_WHITE;
oc.beginPath();
oc.ellipse(cx, cy, headW / 2, headH / 2, 0, 0, Math.PI * 2);
oc.fill();
// ── 2. Painted red cheeks ─────────────────────────────────────────────────
oc.globalAlpha = 0.85;
for (const templePt of [leftTemple ?? [cx - interEye * 0.9, cy], rightTemple ?? [cx + interEye * 0.9, cy]] as [number, number][]) {
const grad = oc.createRadialGradient(templePt[0], templePt[1], 0, templePt[0], templePt[1], interEye * 0.3);
grad.addColorStop(0, CHEEK_RED);
grad.addColorStop(1, 'rgba(204,43,29,0)');
oc.fillStyle = grad;
oc.beginPath();
oc.ellipse(templePt[0], templePt[1], interEye * 0.3, interEye * 0.22, 0, 0, Math.PI * 2);
oc.fill();
}
oc.globalAlpha = 1;
// ── 3. Dramatically arched brows ─────────────────────────────────────────
// Drawn well above actual brow landmarks — iconic exaggerated arch
oc.strokeStyle = BROW_INK;
oc.lineWidth = Math.max(2, interEye * 0.045);
oc.lineCap = 'round';
const browLift = interEye * 0.18; // dramatic lift above natural brow
const browY = leftIris[1] - interEye * 0.52 - browLift;
// Left brow — arches from ~nose bridge outward
oc.beginPath();
oc.moveTo(cx - interEye * 0.08, browY + interEye * 0.08);
oc.quadraticCurveTo(
cx - interEye * 0.38, browY - interEye * 0.05,
cx - interEye * 0.62, browY + interEye * 0.12,
);
oc.stroke();
// Right brow
oc.beginPath();
oc.moveTo(cx + interEye * 0.08, browY + interEye * 0.08);
oc.quadraticCurveTo(
cx + interEye * 0.38, browY - interEye * 0.05,
cx + interEye * 0.62, browY + interEye * 0.12,
);
oc.stroke();
// ── 4. Pencil mustache with signature Guy Fawkes upward curl ─────────────
// Anchored to fixed nose/lip positions — does NOT track jaw
const mustacheY = noseTip ? noseTip[1] + interEye * 0.28 : cy + interEye * 0.18;
const lipMidX = lipLeft && lipRight ? (lipLeft[0] + lipRight[0]) / 2 : cx;
const mustacheW = interEye * 0.55;
oc.strokeStyle = FEATURE_INK;
oc.lineWidth = Math.max(1.5, interEye * 0.028);
// Left half — curves up and outward
oc.beginPath();
oc.moveTo(lipMidX, mustacheY);
oc.bezierCurveTo(
lipMidX - mustacheW * 0.3, mustacheY,
lipMidX - mustacheW * 0.7, mustacheY - interEye * 0.04,
lipMidX - mustacheW, mustacheY - interEye * 0.12,
);
oc.stroke();
// Right half — mirror
oc.beginPath();
oc.moveTo(lipMidX, mustacheY);
oc.bezierCurveTo(
lipMidX + mustacheW * 0.3, mustacheY,
lipMidX + mustacheW * 0.7, mustacheY - interEye * 0.04,
lipMidX + mustacheW, mustacheY - interEye * 0.12,
);
oc.stroke();
// ── 5. Teardrop chin beard ────────────────────────────────────────────────
const beardBaseY = chin[1] - interEye * 0.08;
const beardTipY = chin[1] + interEye * 0.18;
const beardHalfW = interEye * 0.095;
oc.fillStyle = FEATURE_INK;
oc.beginPath();
oc.moveTo(lipMidX, beardBaseY);
oc.bezierCurveTo(
lipMidX + beardHalfW, beardBaseY + interEye * 0.06,
lipMidX + beardHalfW * 0.6, beardTipY - interEye * 0.04,
lipMidX, beardTipY,
);
oc.bezierCurveTo(
lipMidX - beardHalfW * 0.6, beardTipY - interEye * 0.04,
lipMidX - beardHalfW, beardBaseY + interEye * 0.06,
lipMidX, beardBaseY,
);
oc.fill();
ctx.drawImage(scratch.canvas, 0, 0);
}