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:
parent
f4204906a2
commit
7fb87cc729
1 changed files with 140 additions and 0 deletions
|
|
@ -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);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue