mirror of
https://github.com/YuzuZensai/Pien-Studio.git
synced 2026-09-02 14:18:35 +00:00
✨ feat: layer effects, bucket fill, simple brush
This commit is contained in:
@@ -1,34 +1,37 @@
|
||||
import React from "react";
|
||||
import type { FaceBlurMethod, Layer } from "@pien-studio/types";
|
||||
import type { FaceBlurMethod, Layer, LayerEffect } from "@pien-studio/types";
|
||||
import type { FaceDetectionOverlay } from "./use-face-detection";
|
||||
|
||||
type FaceBlurPreview = {
|
||||
layerId: string;
|
||||
method: FaceBlurMethod;
|
||||
amount: number;
|
||||
regions: { x: number; y: number; width: number; height: number; sourceWidth: number; sourceHeight: number; censorColor?: string }[];
|
||||
censorColor?: string;
|
||||
effects: LayerEffect[];
|
||||
};
|
||||
|
||||
type UseFaceBlurWorkflowOptions = {
|
||||
selectedLayer: Layer | null;
|
||||
faceDetectionsLayerId: string | null;
|
||||
faceDetections: FaceDetectionOverlay[];
|
||||
setImageLayerFaceBlur: (layerId: string, faceBlur: Layer["faceBlur"] | undefined) => void;
|
||||
setLayerEffect: (layerId: string, effect: LayerEffect) => void;
|
||||
removeLayerEffect: (layerId: string, kind: LayerEffect["kind"]) => void;
|
||||
};
|
||||
|
||||
export function useFaceBlurWorkflow(options: UseFaceBlurWorkflowOptions) {
|
||||
const { selectedLayer, faceDetectionsLayerId, faceDetections, setImageLayerFaceBlur } = options;
|
||||
const { selectedLayer, faceDetectionsLayerId, faceDetections, setLayerEffect, removeLayerEffect } = options;
|
||||
const [blurMethod, setBlurMethod] = React.useState<FaceBlurMethod>("gaussian");
|
||||
const [blurAmount, setBlurAmount] = React.useState(14);
|
||||
const [censorColor, setCensorColor] = React.useState("#111111");
|
||||
const [selectedFaceIndices, setSelectedFaceIndices] = React.useState<number[]>([]);
|
||||
const [faceBlurPreview, setFaceBlurPreview] = React.useState<FaceBlurPreview | null>(null);
|
||||
const hasDetectableSelection = Boolean(selectedLayer && selectedLayer.type === "image" && faceDetectionsLayerId === selectedLayer.id);
|
||||
const hasDetectableSelection = Boolean(selectedLayer && selectedLayer.type === "raster" && faceDetectionsLayerId === selectedLayer.id);
|
||||
|
||||
const faceBlurEffect = React.useMemo(
|
||||
() => selectedLayer?.effects.find((e): e is Extract<LayerEffect, { kind: "face-blur" }> => e.kind === "face-blur") ?? null,
|
||||
[selectedLayer?.effects],
|
||||
);
|
||||
|
||||
const buildBlurRegions = React.useCallback(
|
||||
(indices: number[]) => {
|
||||
if (!selectedLayer || selectedLayer.type !== "image") return [];
|
||||
if (!selectedLayer || selectedLayer.type !== "raster") return [];
|
||||
if (faceDetectionsLayerId !== selectedLayer.id || faceDetections.length === 0) return [];
|
||||
const indexSet = new Set(indices);
|
||||
return faceDetections
|
||||
@@ -57,17 +60,19 @@ export function useFaceBlurWorkflow(options: UseFaceBlurWorkflowOptions) {
|
||||
}, []);
|
||||
|
||||
const clearBlur = React.useCallback(() => {
|
||||
if (!selectedLayer || selectedLayer.type !== "image") return;
|
||||
setImageLayerFaceBlur(selectedLayer.id, undefined);
|
||||
if (!selectedLayer) return;
|
||||
removeLayerEffect(selectedLayer.id, "face-blur");
|
||||
setFaceBlurPreview(null);
|
||||
}, [selectedLayer, setImageLayerFaceBlur]);
|
||||
}, [selectedLayer, removeLayerEffect]);
|
||||
|
||||
const blurFaces = React.useCallback(
|
||||
(indices: number[]) => {
|
||||
if (!selectedLayer || selectedLayer.type !== "image" || !selectedLayer.sourceUri) return;
|
||||
if (!selectedLayer || selectedLayer.type !== "raster" || !selectedLayer.sourceUri) return;
|
||||
if (faceDetectionsLayerId !== selectedLayer.id || faceDetections.length === 0) return;
|
||||
const regions = buildBlurRegions(indices);
|
||||
setImageLayerFaceBlur(selectedLayer.id, {
|
||||
setLayerEffect(selectedLayer.id, {
|
||||
kind: "face-blur",
|
||||
enabled: true,
|
||||
method: blurMethod,
|
||||
amount: blurAmount,
|
||||
regions,
|
||||
@@ -76,7 +81,7 @@ export function useFaceBlurWorkflow(options: UseFaceBlurWorkflowOptions) {
|
||||
setSelectedFaceIndices([]);
|
||||
setFaceBlurPreview(null);
|
||||
},
|
||||
[blurAmount, blurMethod, buildBlurRegions, censorColor, faceDetections.length, faceDetectionsLayerId, selectedLayer, setImageLayerFaceBlur],
|
||||
[blurAmount, blurMethod, buildBlurRegions, censorColor, faceDetections.length, faceDetectionsLayerId, selectedLayer, setLayerEffect],
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -85,7 +90,7 @@ export function useFaceBlurWorkflow(options: UseFaceBlurWorkflowOptions) {
|
||||
setFaceBlurPreview(null);
|
||||
return;
|
||||
}
|
||||
if (selectedLayer?.faceBlur) {
|
||||
if (faceBlurEffect) {
|
||||
setSelectedFaceIndices((prev) => (prev.length === 0 ? prev : []));
|
||||
return;
|
||||
}
|
||||
@@ -94,10 +99,10 @@ export function useFaceBlurWorkflow(options: UseFaceBlurWorkflowOptions) {
|
||||
if (prev.length === next.length && prev.every((value, index) => value === next[index])) return prev;
|
||||
return next;
|
||||
});
|
||||
}, [faceDetections, hasDetectableSelection, selectedLayer?.faceBlur]);
|
||||
}, [faceDetections, hasDetectableSelection, faceBlurEffect]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!hasDetectableSelection || !selectedLayer || selectedLayer.type !== "image") {
|
||||
if (!hasDetectableSelection || !selectedLayer || selectedLayer.type !== "raster") {
|
||||
setFaceBlurPreview(null);
|
||||
return;
|
||||
}
|
||||
@@ -109,10 +114,14 @@ export function useFaceBlurWorkflow(options: UseFaceBlurWorkflowOptions) {
|
||||
|
||||
setFaceBlurPreview({
|
||||
layerId: selectedLayer.id,
|
||||
method: blurMethod,
|
||||
amount: blurAmount,
|
||||
regions: buildBlurRegions(selectedFaceIndices),
|
||||
censorColor,
|
||||
effects: [{
|
||||
kind: "face-blur",
|
||||
enabled: true,
|
||||
method: blurMethod,
|
||||
amount: blurAmount,
|
||||
regions: buildBlurRegions(selectedFaceIndices),
|
||||
censorColor,
|
||||
}],
|
||||
});
|
||||
}, [blurAmount, blurMethod, buildBlurRegions, censorColor, hasDetectableSelection, selectedFaceIndices, selectedLayer]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user