2026-05-10 03:06:54 +07:00
|
|
|
import type { Layer } from "@pien-studio/types";
|
|
|
|
|
|
2026-05-31 03:03:49 +07:00
|
|
|
type FaceDetection = {
|
|
|
|
|
x: number;
|
|
|
|
|
y: number;
|
|
|
|
|
width: number;
|
|
|
|
|
height: number;
|
|
|
|
|
label?: string;
|
|
|
|
|
};
|
2026-05-10 03:06:54 +07:00
|
|
|
|
|
|
|
|
type Viewport = { x: number; y: number; scale: number };
|
|
|
|
|
|
|
|
|
|
export function buildFaceLabelOverlays(
|
|
|
|
|
layers: Layer[],
|
|
|
|
|
faceOverlayLayerId: string,
|
|
|
|
|
faceDetections: FaceDetection[],
|
|
|
|
|
viewport: Viewport,
|
|
|
|
|
) {
|
|
|
|
|
const layer = layers.find((item) => item.id === faceOverlayLayerId);
|
|
|
|
|
if (!layer) return [];
|
|
|
|
|
|
2026-05-31 03:03:49 +07:00
|
|
|
const layerWidth = layer.width;
|
|
|
|
|
const layerHeight = layer.height;
|
2026-05-10 03:06:54 +07:00
|
|
|
if (!layerWidth || !layerHeight) return [];
|
|
|
|
|
|
|
|
|
|
const centerX = layer.x + layerWidth / 2;
|
|
|
|
|
const centerY = layer.y + layerHeight / 2;
|
|
|
|
|
const radians = (layer.rotation * Math.PI) / 180;
|
|
|
|
|
const cos = Math.cos(radians);
|
|
|
|
|
const sin = Math.sin(radians);
|
2026-05-31 03:03:49 +07:00
|
|
|
const placed: Array<{
|
|
|
|
|
left: number;
|
|
|
|
|
top: number;
|
|
|
|
|
width: number;
|
|
|
|
|
height: number;
|
|
|
|
|
}> = [];
|
2026-05-10 03:06:54 +07:00
|
|
|
|
|
|
|
|
return faceDetections.map((face, index) => {
|
|
|
|
|
const worldX = layer.x + face.x;
|
|
|
|
|
const worldY = layer.y + face.y;
|
|
|
|
|
const dx = worldX - centerX;
|
|
|
|
|
const dy = worldY - centerY;
|
|
|
|
|
const rotatedWorldX = centerX + dx * cos - dy * sin;
|
|
|
|
|
const rotatedWorldY = centerY + dx * sin + dy * cos;
|
|
|
|
|
|
|
|
|
|
const text = face.label ?? `Person ${index + 1}`;
|
|
|
|
|
const estimatedWidth = Math.max(72, Math.min(260, text.length * 6 + 14));
|
|
|
|
|
const estimatedHeight = 18;
|
|
|
|
|
const left = viewport.x + rotatedWorldX * viewport.scale;
|
|
|
|
|
let top = viewport.y + rotatedWorldY * viewport.scale - 22;
|
|
|
|
|
|
|
|
|
|
while (
|
|
|
|
|
placed.some((rect) => {
|
2026-05-31 03:03:49 +07:00
|
|
|
const intersectsX =
|
|
|
|
|
left < rect.left + rect.width && left + estimatedWidth > rect.left;
|
|
|
|
|
const intersectsY =
|
|
|
|
|
top < rect.top + rect.height && top + estimatedHeight > rect.top;
|
2026-05-10 03:06:54 +07:00
|
|
|
return intersectsX && intersectsY;
|
|
|
|
|
})
|
|
|
|
|
) {
|
|
|
|
|
top -= estimatedHeight + 4;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
placed.push({ left, top, width: estimatedWidth, height: estimatedHeight });
|
|
|
|
|
return { id: `${layer.id}-face-label-${index}`, text, left, top };
|
|
|
|
|
});
|
|
|
|
|
}
|