Files
image-studio/editor/state.ts
syntaxbullet 38565382c3 feat: add feather brush tool with adjustable settings and blending functionality
- Implemented feather brush tool in the brushRaster module, allowing for feathered edges in brush strokes.
- Added new FeatherControls component for UI adjustments of feather settings including size, radius, strength, and smoothing.
- Updated brush preview logic to accommodate feather tool alongside existing brush and eraser tools.
- Enhanced layer rendering to support feather mask previews and interactions.
- Introduced blending logic for feathered strokes to mix blurred mask values with original pixels.
- Added unit tests for feather blending functionality and tool keybindings.
- Updated cursor handling to reflect feather tool usage.
2026-07-11 22:08:25 +02:00

193 lines
4.7 KiB
TypeScript

import type { ImageDocument } from "@core/document";
import type { Angle, Rect, Size, Transform, Vec2D } from "@core/geometry";
import type { ArtboardId, AssetId, GenerationCandidateId, GenerationJobId, InpaintRegionId, LayerId } from "@core/id";
import type { GenerateArchitecture, GenerateMode, GenerateSettings, ToolState } from "./tools";
import type { TransformSession } from "./transform";
export type ViewportState = {
center: Vec2D;
zoom: number;
rotation: Angle;
size: Size;
};
export type SelectionState = {
artboardId?: ArtboardId;
layerIds: LayerId[];
};
export type MaskViewMode = "composite" | "blackWhite" | "alpha" | "overlay";
export type MaskEditState = {
kind: "layerMask" | "inpaintRegion";
targetLayerId: LayerId;
maskAssetId: AssetId;
maskLayerId?: LayerId;
inpaintRegionId?: InpaintRegionId;
viewMode?: MaskViewMode;
};
export type BrushPreviewState = {
position: Vec2D;
};
export type BrushStrokePreviewState = {
layerId: LayerId;
assetId: AssetId;
source: string;
pendingTargetLayerId?: LayerId;
intrinsicSize?: Size;
};
export type MaskShapeSession = {
shape: "lasso" | "rectangle";
points: Vec2D[];
mode: "replace" | "add" | "subtract";
};
export type GenerationCandidate = {
id: GenerationCandidateId;
source: string;
mimeType: string;
intrinsicSize: Size;
mode: GenerateSettings["mode"];
settings: GenerateSettings;
seed: number;
favorite?: boolean;
width: number;
height: number;
inputImage?: string;
maskImage?: string;
blendMaskImage?: string;
placement: {
artboardId: ArtboardId;
layerName: string;
transform: Transform;
};
inpaint?: {
targetLayerId: LayerId;
regionId: InpaintRegionId;
sourceAssetId: AssetId;
maskAssetId: AssetId;
inputImage: string;
maskImage: string;
editMaskImage: string;
blendMaskImage: string;
revision: { source: string; mask: string };
crop: {
assetBounds: Rect;
documentBounds: Rect;
padding: number;
maskedAreaOnly: boolean;
};
mask: {
polarity: GenerateSettings["inpaint"]["maskPolarity"];
activeBounds: Rect;
};
backend: {
growMaskBy: number;
maskedContent: GenerateSettings["inpaint"]["maskedContent"];
maskBlur: number;
maskFeather: number;
maskExpand: number;
cropPadding: number;
};
};
};
export type GenerationCompareMode = "result" | "before" | "split";
export type GenerationJobKind = "generate" | "regenerate" | "refine" | "replace" | "mask";
export type GenerationJobStatus = "running" | "succeeded" | "failed" | "cancelled";
export type GenerationJob = {
id: GenerationJobId;
kind: GenerationJobKind;
label: string;
status: GenerationJobStatus;
startedAt: number;
finishedAt?: number;
error?: string;
progress?: number;
detail?: string;
};
export type GenerationState = {
candidates: GenerationCandidate[];
selectedCandidateId?: string;
compareMode: GenerationCompareMode;
jobs: GenerationJob[];
resources: GenerationResourcesState;
};
export type GenerationArchitectureOption = {
value: GenerateArchitecture;
label: string;
defaultModel: string;
models: string[];
supportedModes: GenerateMode[];
};
export type GenerationOptions = {
architectures?: GenerationArchitectureOption[];
models?: string[];
inpaintModels?: string[];
textEncoders?: string[];
vaes?: string[];
samplers?: string[];
schedulers?: string[];
controlModels?: string[];
structureControls?: Array<"canny" | "depth" | "pose">;
semanticSelection?: boolean;
sam3Models?: string[];
};
export type GenerationResourcesState = { status: "idle" | "loading" | "ready" | "failed"; options?: GenerationOptions; error?: string };
export type CommandPaletteState = {
open: boolean;
query: string;
selectedIndex: number;
};
export type WorkspacePanel = "none" | "generate" | "chromaKey" | "layers";
export function isOperationWorkspacePanel(panel: WorkspacePanel): boolean {
return panel === "generate" || panel === "chromaKey";
}
export type WorkspaceState = {
panel: WorkspacePanel;
};
export type EditorState = {
viewport: ViewportState;
selection: SelectionState;
tools: ToolState;
generation: GenerationState;
commandPalette: CommandPaletteState;
workspace: WorkspaceState;
transformSession?: TransformSession;
maskEdit?: MaskEditState;
brushPreview?: BrushPreviewState;
brushStrokePreview?: BrushStrokePreviewState;
maskShapeSession?: MaskShapeSession;
pointerSession?: { type: "pan" };
};
export type HistorySnapshot = {
document: ImageDocument;
editor: EditorState;
};
export type HistoryState = {
past: HistorySnapshot[];
future: HistorySnapshot[];
};
export type AppState = {
document: ImageDocument;
editor: EditorState;
history: HistoryState;
};