feat(effects): add Closing brush as inverse of Opening brush

Allow covering revealed darkness again during darkened scene sessions.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-07-30 17:21:40 +08:00
parent cc50e64e21
commit 0f01950cc1
7 changed files with 108 additions and 19 deletions
@@ -1,6 +1,11 @@
import React, { useEffect, useRef } from 'react';
import type { SceneDarknessRevealStroke, SceneDarknessState } from '../../../shared/types';
import type {
SceneDarknessRevealStroke,
SceneDarknessState,
SceneDarknessStrokeMode,
} from '../../../shared/types';
import { normalizeSceneDarknessStrokeMode } from '../../../shared/types/sceneDarkness';
export type SceneDarknessOverlayProps = {
state: SceneDarknessState | null;
@@ -10,14 +15,18 @@ export type SceneDarknessOverlayProps = {
style?: React.CSSProperties;
};
function drawRevealStroke(
function drawDarknessStroke(
ctx: CanvasRenderingContext2D,
stroke: SceneDarknessRevealStroke | { points: { x: number; y: number }[]; radiusN: number },
stroke: SceneDarknessRevealStroke | { points: { x: number; y: number }[]; radiusN: number; mode?: SceneDarknessStrokeMode },
w: number,
h: number,
): void {
const pts = stroke.points;
if (pts.length === 0) return;
const mode = normalizeSceneDarknessStrokeMode(stroke.mode);
ctx.globalCompositeOperation = mode === 'cover' ? 'source-over' : 'destination-out';
ctx.fillStyle = '#000000';
ctx.strokeStyle = '#000000';
const r = stroke.radiusN * Math.min(w, h);
if (pts.length === 1) {
const p = pts[0];
@@ -30,7 +39,6 @@ function drawRevealStroke(
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.lineWidth = r * 2;
ctx.strokeStyle = 'rgba(0,0,0,1)';
ctx.beginPath();
const first = pts[0];
if (!first) return;
@@ -65,12 +73,11 @@ export function SceneDarknessOverlay({
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, w, h);
ctx.globalCompositeOperation = 'destination-out';
for (const stroke of state.strokes) {
drawRevealStroke(ctx, stroke, w, h);
drawDarknessStroke(ctx, stroke, w, h);
}
if (state.draft && state.draft.points.length > 0) {
drawRevealStroke(ctx, state.draft, w, h);
drawDarknessStroke(ctx, state.draft, w, h);
}
}, [state, viewport]);