feat: add inpaint region functionality and related tools
- Enhanced cursor behavior for new tools: semantic select, mask lasso, and mask rectangle. - Updated mask edit state to include mask asset ID and kind. - Implemented inpaint region commands for adding, applying, and removing inpaint regions. - Introduced new operations for lasso and semantic selection tools. - Created UI components for candidate review and inpaint region management. - Added tests for inpaint region commands to ensure functionality. - Updated various components to support new inpaint features and improve user experience.
This commit is contained in:
@@ -8,6 +8,6 @@ export function canvasCursorClass(interactionMode: InteractionMode, isPanning: b
|
||||
if (!canBrush) return "cursor-not-allowed";
|
||||
return hasBrushPreview ? "cursor-none" : "cursor-crosshair";
|
||||
}
|
||||
if (interactionMode.type === "tool" && interactionMode.tool === "magicWand") return "cursor-crosshair";
|
||||
if (interactionMode.type === "tool" && (interactionMode.tool === "magicWand" || interactionMode.tool === "semanticSelect" || interactionMode.tool === "maskLasso" || interactionMode.tool === "maskRectangle")) return "cursor-crosshair";
|
||||
return "cursor-default";
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ const visualEditorChanges: Array<[string, (state: AppState) => AppState]> = [
|
||||
...state,
|
||||
editor: {
|
||||
...state.editor,
|
||||
maskEdit: { targetLayerId: "target", maskLayerId: "mask", viewMode: "overlay" },
|
||||
maskEdit: { kind: "layerMask", targetLayerId: "target", maskLayerId: "mask", maskAssetId: "mask-asset", viewMode: "overlay" },
|
||||
},
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -23,6 +23,7 @@ function visualEditorStatesEqual(a: EditorState, b: EditorState): boolean {
|
||||
maskEditStatesEqual(a.maskEdit, b.maskEdit) &&
|
||||
brushPreviewStatesEqual(a.brushPreview, b.brushPreview) &&
|
||||
brushStrokePreviewStatesEqual(a.brushStrokePreview, b.brushStrokePreview) &&
|
||||
a.maskShapeSession === b.maskShapeSession &&
|
||||
generationStatesEqual(a.generation, b.generation) &&
|
||||
visualToolStatesEqual(a.tools, b.tools)
|
||||
);
|
||||
@@ -50,7 +51,7 @@ function transformTargetsEqual(a: TransformTarget, b: TransformTarget): boolean
|
||||
function maskEditStatesEqual(a: MaskEditState | undefined, b: MaskEditState | undefined): boolean {
|
||||
if (a === b) return true;
|
||||
if (!a || !b) return false;
|
||||
return a.targetLayerId === b.targetLayerId && a.maskLayerId === b.maskLayerId && a.viewMode === b.viewMode;
|
||||
return a.kind === b.kind && a.targetLayerId === b.targetLayerId && a.maskAssetId === b.maskAssetId && a.maskLayerId === b.maskLayerId && a.inpaintRegionId === b.inpaintRegionId && a.viewMode === b.viewMode;
|
||||
}
|
||||
|
||||
function brushPreviewStatesEqual(a: BrushPreviewState | undefined, b: BrushPreviewState | undefined): boolean {
|
||||
@@ -80,7 +81,7 @@ function interactionModesEqual(a: InteractionMode, b: InteractionMode): boolean
|
||||
}
|
||||
|
||||
function brushSettingsEqual(a: BrushSettings, b: BrushSettings): boolean {
|
||||
return a.color === b.color && a.size === b.size && a.hardness === b.hardness;
|
||||
return a.color === b.color && a.size === b.size && a.hardness === b.hardness && a.opacity === b.opacity && a.flow === b.flow && a.smoothing === b.smoothing && a.pressureSize === b.pressureSize;
|
||||
}
|
||||
|
||||
function vec2Equal(a: Vec2D, b: Vec2D): boolean {
|
||||
|
||||
@@ -15,6 +15,8 @@ import {
|
||||
} from "@input/index";
|
||||
import { beginBrushSession, canPreviewBrush, cancelBrushSession, commitBrushSession, updateBrushSession, type BrushSession } from "@operations/paint/brush";
|
||||
import { applyMagicWandAt } from "@operations/masks/magic-wand";
|
||||
import { commitInpaintLasso } from "@operations/masks/lasso";
|
||||
import { applySemanticSelectionAt } from "@operations/masks/semantic-select";
|
||||
|
||||
export type CanvasInputOptions = {
|
||||
globalKeybindConsumer: GlobalKeybindConsumer;
|
||||
@@ -122,6 +124,12 @@ export function useCanvasInput(
|
||||
|
||||
const state = store.getState();
|
||||
const documentPoint = viewportPointToDocumentPoint(inputEvent.position, state.editor.viewport);
|
||||
if (!isOperationWorkspacePanel(state.editor.workspace.panel) && (state.editor.tools.activeTool === "maskLasso" || state.editor.tools.activeTool === "maskRectangle") && state.editor.maskEdit?.kind === "inpaintRegion" && (inputEvent.buttons & 1) === 1) {
|
||||
store.dispatch(commandIds.toolBeginMaskShape, { point: documentPoint, mode: inputEvent.shiftKey ? "add" : inputEvent.altKey ? "subtract" : "replace" });
|
||||
canvas.setPointerCapture(event.pointerId);
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
const brush = !isOperationWorkspacePanel(state.editor.workspace.panel) && (inputEvent.buttons & 1) === 1 && !isPanInteractionMode(state.editor.tools.interactionMode)
|
||||
? beginBrushSession(state.document, state.editor, documentPoint)
|
||||
: undefined;
|
||||
@@ -139,6 +147,11 @@ export function useCanvasInput(
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (!isOperationWorkspacePanel(state.editor.workspace.panel) && state.editor.tools.activeTool === "semanticSelect") {
|
||||
void applySemanticSelectionAt(store, documentPoint, inputEvent.shiftKey ? "add" : inputEvent.altKey ? "subtract" : "replace");
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
const currentState = store.getState();
|
||||
const selectionToolActive = currentState.editor.tools.activeTool === "select";
|
||||
@@ -153,6 +166,12 @@ export function useCanvasInput(
|
||||
|
||||
const handlePointerMove = (event: PointerEvent) => {
|
||||
const inputEvent = pointerInputEventFromPointerEvent(event);
|
||||
if (store.getState().editor.maskShapeSession) {
|
||||
const point = viewportPointToDocumentPoint(inputEvent.position, store.getState().editor.viewport);
|
||||
store.dispatch(commandIds.toolAppendMaskShape, { point });
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (brushSession.current) {
|
||||
if ((inputEvent.buttons & 1) !== 1 || isPanInteractionMode(store.getState().editor.tools.interactionMode)) {
|
||||
commitActiveBrushSession(inputEvent.position);
|
||||
@@ -163,7 +182,7 @@ export function useCanvasInput(
|
||||
const point = viewportPointToDocumentPoint(inputEvent.position, store.getState().editor.viewport);
|
||||
store.dispatch(commandIds.toolSetBrushPreview, { position: point });
|
||||
const settings = store.getState().editor.tools.brush;
|
||||
brushSession.current = updateBrushSession({ store, session: brushSession.current, point, color: settings.color, size: settings.size, hardness: settings.hardness });
|
||||
brushSession.current = updateBrushSession({ store, session: brushSession.current, point, color: settings.color, size: settings.size, hardness: settings.hardness, opacity: settings.opacity, flow: settings.flow, smoothing: settings.smoothing, pressure: inputEvent.pressure ?? 1, pressureSize: settings.pressureSize });
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
@@ -187,6 +206,13 @@ export function useCanvasInput(
|
||||
|
||||
const handlePointerUp = (event: PointerEvent) => {
|
||||
const inputEvent = pointerInputEventFromPointerEvent(event);
|
||||
if (store.getState().editor.maskShapeSession) {
|
||||
const point = viewportPointToDocumentPoint(inputEvent.position, store.getState().editor.viewport);
|
||||
store.dispatch(commandIds.toolAppendMaskShape, { point });
|
||||
void commitInpaintLasso(store);
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (brushSession.current) {
|
||||
commitActiveBrushSession(inputEvent.position);
|
||||
event.preventDefault();
|
||||
|
||||
Reference in New Issue
Block a user