Files
Pien-Studio/apps/web/hooks/use-face-detection.ts
T

173 lines
4.3 KiB
TypeScript
Raw Normal View History

2026-05-10 03:06:54 +07:00
import React from "react";
import type { EditorToolId } from "../store/editor-store";
import {
buildFacePreviews,
loadImageFromUri,
toFaceDetectionOverlays,
} from "../lib/face-detection-utils";
2026-05-10 03:06:54 +07:00
export type FaceDetectionOverlay = {
x: number;
y: number;
width: number;
height: number;
sourceWidth: number;
sourceHeight: number;
label: string;
gender?: string;
genderScore?: number;
};
export type FacePreview = {
id: string;
src: string;
};
type SelectedImageLayer = {
id: string;
sourceUri: string;
width?: number;
height?: number;
};
type UseFaceDetectionOptions = {
tool: EditorToolId;
selectedLayerId: string | null;
selectedImageLayer: SelectedImageLayer | null;
activeLayerStillSelected: (layerId: string) => boolean;
};
export function useFaceDetection(options: UseFaceDetectionOptions) {
const {
tool,
selectedLayerId,
selectedImageLayer,
activeLayerStillSelected,
} = options;
2026-05-10 03:06:54 +07:00
const selectedImageLayerId = selectedImageLayer?.id ?? null;
const selectedImageSourceUri = selectedImageLayer?.sourceUri ?? null;
const selectedImageWidth = selectedImageLayer?.width;
const selectedImageHeight = selectedImageLayer?.height;
const [faceDetections, setFaceDetections] = React.useState<
FaceDetectionOverlay[]
>([]);
const [faceDetectionsLayerId, setFaceDetectionsLayerId] = React.useState<
string | null
>(null);
const [faceStatus, setFaceStatus] = React.useState<
"idle" | "detecting" | "unsupported"
>("idle");
2026-05-10 03:06:54 +07:00
const [facePreviews, setFacePreviews] = React.useState<FacePreview[]>([]);
const resetFaceState = React.useCallback(
(status: "idle" | "detecting" | "unsupported" = "idle") => {
setFaceDetections((prev) => (prev.length === 0 ? prev : []));
setFaceDetectionsLayerId((prev) => (prev === null ? prev : null));
setFaceStatus((prev) => (prev === status ? prev : status));
},
[],
);
2026-05-10 03:06:54 +07:00
React.useEffect(() => {
let canceled = false;
async function detectFaces() {
const layerId = selectedImageLayerId;
if (tool !== "face" || !layerId || !selectedImageSourceUri) {
resetFaceState("idle");
return;
}
try {
resetFaceState("detecting");
2026-05-10 03:06:54 +07:00
const { detectFaceBoxes } = await import("../lib/face-ml");
const result = await detectFaceBoxes(selectedImageSourceUri);
2026-05-10 03:06:54 +07:00
if (canceled) return;
if (!activeLayerStillSelected(layerId) || tool !== "face") return;
const overlays = toFaceDetectionOverlays(
result.faces,
result,
selectedImageWidth,
selectedImageHeight,
);
2026-05-10 03:06:54 +07:00
setFaceDetections(overlays);
setFaceDetectionsLayerId(layerId);
setFaceStatus((prev) => (prev === "idle" ? prev : "idle"));
} catch (err) {
console.error("[face-detection] detectFaces error:", err);
if (!canceled) {
resetFaceState("unsupported");
}
}
}
detectFaces();
return () => {
canceled = true;
};
}, [
activeLayerStillSelected,
resetFaceState,
selectedImageHeight,
selectedImageLayerId,
selectedImageSourceUri,
selectedImageWidth,
tool,
]);
2026-05-10 03:06:54 +07:00
React.useEffect(() => {
if (tool !== "face") {
setFaceDetections([]);
setFaceDetectionsLayerId(null);
setFacePreviews([]);
return;
}
setFaceDetections([]);
setFaceDetectionsLayerId(null);
setFacePreviews([]);
}, [tool, selectedLayerId]);
React.useEffect(() => {
let canceled = false;
async function generateFacePreviews() {
if (
tool !== "face" ||
!selectedImageSourceUri ||
faceDetections.length === 0
) {
2026-05-10 03:06:54 +07:00
setFacePreviews([]);
return;
}
const image = await loadImageFromUri(selectedImageSourceUri);
if (!image || canceled) {
setFacePreviews([]);
return;
}
const previews = buildFacePreviews(
image,
faceDetections,
selectedImageWidth,
selectedImageHeight,
);
2026-05-10 03:06:54 +07:00
if (!canceled) setFacePreviews(previews);
}
generateFacePreviews();
return () => {
canceled = true;
};
}, [
faceDetections,
selectedImageHeight,
selectedImageSourceUri,
selectedImageWidth,
tool,
]);
2026-05-10 03:06:54 +07:00
return { faceDetections, faceDetectionsLayerId, facePreviews, faceStatus };
}