import React from "react"; import type { FaceBlurMethod, Layer, LayerEffect } from "@pien-studio/types"; import type { FaceDetectionOverlay } from "./use-face-detection"; type FaceBlurPreview = { layerId: string; effects: LayerEffect[]; }; type FaceSelectionState = { key: string; indices: number[]; }; type UseFaceBlurWorkflowOptions = { selectedLayer: Layer | null; faceDetectionsLayerId: string | null; faceDetections: FaceDetectionOverlay[]; setLayerEffect: (layerId: string, effect: LayerEffect) => void; removeLayerEffect: (layerId: string, kind: LayerEffect["kind"]) => void; }; export function useFaceBlurWorkflow(options: UseFaceBlurWorkflowOptions) { const { selectedLayer, faceDetectionsLayerId, faceDetections, setLayerEffect, removeLayerEffect } = options; const [blurMethod, setBlurMethod] = React.useState("gaussian"); const [blurAmount, setBlurAmount] = React.useState(14); const [censorColor, setCensorColor] = React.useState("#111111"); const [faceSelection, setFaceSelection] = React.useState(null); const hasDetectableSelection = Boolean(selectedLayer && selectedLayer.type === "raster" && faceDetectionsLayerId === selectedLayer.id); const faceBlurEffect = React.useMemo( () => selectedLayer?.effects.find((e): e is Extract => e.kind === "face-blur") ?? null, [selectedLayer?.effects], ); const selectionKey = `${selectedLayer?.id ?? "none"}:${faceDetectionsLayerId ?? "none"}:${faceDetections.length}:${faceBlurEffect ? "effect" : "empty"}`; const defaultSelectedFaceIndices = React.useMemo(() => { if (!hasDetectableSelection || faceBlurEffect) return []; return faceDetections.map((_, index) => index); }, [faceDetections, faceBlurEffect, hasDetectableSelection]); const selectedFaceIndices = faceSelection?.key === selectionKey ? faceSelection.indices : defaultSelectedFaceIndices; const buildBlurRegions = React.useCallback( (indices: number[]) => { if (!selectedLayer || selectedLayer.type !== "raster") return []; if (faceDetectionsLayerId !== selectedLayer.id || faceDetections.length === 0) return []; const indexSet = new Set(indices); return faceDetections .filter((_, index) => indexSet.has(index)) .map((face) => { const baseWidth = Math.max(1, selectedLayer.width ?? face.sourceWidth); const baseHeight = Math.max(1, selectedLayer.height ?? face.sourceHeight); const scaleX = face.sourceWidth / baseWidth; const scaleY = face.sourceHeight / baseHeight; return { x: Math.max(0, Math.floor(face.x * scaleX)), y: Math.max(0, Math.floor(face.y * scaleY)), width: Math.max(1, Math.floor(face.width * scaleX)), height: Math.max(1, Math.floor(face.height * scaleY)), sourceWidth: face.sourceWidth, sourceHeight: face.sourceHeight, censorColor, }; }); }, [censorColor, faceDetections, faceDetectionsLayerId, selectedLayer], ); const toggleFaceIndex = React.useCallback((index: number) => { setFaceSelection((prev) => { const current = prev?.key === selectionKey ? prev.indices : defaultSelectedFaceIndices; const indices = current.includes(index) ? current.filter((item) => item !== index) : [...current, index]; return { key: selectionKey, indices }; }); }, [defaultSelectedFaceIndices, selectionKey]); const clearBlur = React.useCallback(() => { if (!selectedLayer) return; removeLayerEffect(selectedLayer.id, "face-blur"); setFaceSelection({ key: selectionKey, indices: [] }); }, [removeLayerEffect, selectedLayer, selectionKey]); const blurFaces = React.useCallback( (indices: number[]) => { if (!selectedLayer || selectedLayer.type !== "raster" || !selectedLayer.sourceUri) return; if (faceDetectionsLayerId !== selectedLayer.id || faceDetections.length === 0) return; const regions = buildBlurRegions(indices); setLayerEffect(selectedLayer.id, { kind: "face-blur", enabled: true, method: blurMethod, amount: blurAmount, regions, censorColor, }); setFaceSelection({ key: selectionKey, indices: [] }); }, [blurAmount, blurMethod, buildBlurRegions, censorColor, faceDetections.length, faceDetectionsLayerId, selectedLayer, selectionKey, setLayerEffect], ); const faceBlurPreview = React.useMemo(() => { if (!hasDetectableSelection || !selectedLayer || selectedLayer.type !== "raster") { return null; } if (selectedFaceIndices.length === 0) { return null; } return { layerId: selectedLayer.id, effects: [{ kind: "face-blur", enabled: true, method: blurMethod, amount: blurAmount, regions: buildBlurRegions(selectedFaceIndices), censorColor, }], }; }, [blurAmount, blurMethod, buildBlurRegions, censorColor, hasDetectableSelection, selectedFaceIndices, selectedLayer]); return { blurMethod, setBlurMethod, blurAmount, setBlurAmount, censorColor, setCensorColor, selectedFaceIndices, faceBlurPreview, toggleFaceIndex, clearBlur, blurFaces, }; }