(password: str, username: str = "")
| 10232 | const emotion = getEmotion(landmarks, faceWidth, faceHeight); |
| 10233 | |
| 10234 | ctx.scale(-1, 1); ctx.translate(-canvasEl.width, 0); |
| 10235 | const faceLeftPx = (1 - pLeft.x) * canvasEl.width; |
| 10236 | const faceRightPx = (1 - pRight.x) * canvasEl.width; |
| 10237 | const faceTopPx = pTop.y * canvasEl.height; |
| 10238 | const lines = [ |
| 10239 | `[ ${I18N.faceId || 'ID'}: ${faceCount} | ${emotion} ]`, |
| 10240 | `${I18N.pose || 'POSE'} | Yaw:${yaw.toFixed(1)}° Pch:${pitch.toFixed(1)}° Rol:${roll.toFixed(1)}°`, |
| 10241 | `${I18N.shape || 'SHAPE'} | Face:${shapeStr} Jaw:${jawStr} NoseW:${(noseWidth*100).toFixed(1)}%`, |
| 10242 | `${I18N.eyes || 'EYES'} | ${eyeSetStr} Set | Gaze: ${gaze}`, |
| 10243 | ` | Opn L:${leftEyeOpen.toFixed(2)} R:${rightEyeOpen.toFixed(2)}`, |
| 10244 | `${I18N.brows || 'BROWS'} | L:${browLStr} R:${browRStr}`, |
| 10245 | `${I18N.mouth || 'MOUTH'} | Wdh:${mouthW.toFixed(1)}% Opn:${mouthOpen.toFixed(2)}%`, |
| 10246 | `${I18N.asym || 'ASYM'} | Score: ${totalAsym}%` |
| 10247 | ]; |
| 10248 | ctx.font = 'bold 11px monospace'; ctx.strokeStyle='black'; ctx.lineWidth=2.5; ctx.lineJoin='round'; |
| 10249 | const blockWidth = 210, blockHeight = lines.length * 16; |
| 10250 | let textX = faceLeftPx + 15; if (textX + blockWidth > canvasEl.width) textX = faceRightPx - blockWidth - 15; |
| 10251 | textX = Math.max(10, Math.min(textX, canvasEl.width - blockWidth - 10)); |
| 10252 | let startY = faceTopPx - blockHeight - 10; if (startY < 15) startY = faceTopPx + (faceHeight * canvasEl.height) + 20; |
| 10253 | startY = Math.max(15, Math.min(startY, canvasEl.height - blockHeight - 10)); |
| 10254 | lines.forEach((line, idx) => { |
| 10255 | const y = startY + (idx * 16); |
| 10256 | ctx.strokeText(line, textX, y); |
| 10257 | if (idx === 0) ctx.fillStyle = '#00FFCC'; |
no outgoing calls
no test coverage detected