Files
syntaxbullet ff762b8f17 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.
2026-07-11 16:41:22 +02:00

53 lines
3.1 KiB
TypeScript

import { commandIds } from "@commands/ids";
import type { Vec2D } from "@core/geometry";
import type { Layer } from "@core/layer";
import type { AppStore } from "@editor/store";
import { applyPolygonMask } from "@platform/browser/maskRaster";
export async function commitInpaintLasso(store: AppStore) {
const state = store.getState();
const edit = state.editor.maskEdit;
const session = state.editor.maskShapeSession;
const minimumPoints = session?.shape === "rectangle" ? 2 : 3;
if (edit?.kind !== "inpaintRegion" || !edit.inpaintRegionId || !session || session.points.length < minimumPoints) {
store.dispatch(commandIds.toolClearMaskShape, undefined);
return;
}
const region = state.document.inpaintRegions.find((candidate) => candidate.id === edit.inpaintRegionId);
const target = findLayer(state.document.artboards.flatMap((artboard) => artboard.layers), edit.targetLayerId);
const asset = region ? state.document.assets.find((candidate) => candidate.id === region.maskAssetId) : undefined;
if (!region || !target || (target.type !== "image" && target.type !== "raster") || !asset) {
store.dispatch(commandIds.toolClearMaskShape, undefined);
return;
}
const documentPoints = session.shape === "rectangle" ? rectanglePoints(session.points[0]!, session.points[1]!) : session.points;
const points = documentPoints.map((point) => documentPointToAssetPoint(point, target, asset.intrinsicSize));
const source = await applyPolygonMask(asset.source, asset.intrinsicSize.w, asset.intrinsicSize.h, points, session.mode);
store.dispatch(commandIds.documentApplyInpaintRegionMaskOperation, { regionId: region.id, source, mimeType: "image/png", operation: { type: "paint" } });
store.dispatch(commandIds.toolClearMaskShape, undefined);
}
function rectanglePoints(start: Vec2D, end: Vec2D): Vec2D[] {
return [start, { x: end.x, y: start.y }, end, { x: start.x, y: end.y }];
}
function documentPointToAssetPoint(point: Vec2D, layer: Extract<Layer, { type: "image" | "raster" }>, intrinsicSize: { w: number; h: number }): Vec2D {
const source = layer.sourceRect ?? { x: 0, y: 0, ...intrinsicSize };
const destination = { x: layer.transform.position.x + source.x * layer.transform.scale.x, y: layer.transform.position.y + source.y * layer.transform.scale.y, w: source.w * layer.transform.scale.x, h: source.h * layer.transform.scale.y };
const center = { x: destination.x + destination.w / 2, y: destination.y + destination.h / 2 };
const dx = point.x - center.x;
const dy = point.y - center.y;
const cos = Math.cos(-layer.transform.rotation);
const sin = Math.sin(-layer.transform.rotation);
const x = center.x + dx * cos - dy * sin;
const y = center.y + dx * sin + dy * cos;
return { x: source.x + (x - destination.x) / Math.max(0.0001, layer.transform.scale.x), y: source.y + (y - destination.y) / Math.max(0.0001, layer.transform.scale.y) };
}
function findLayer(layers: readonly Layer[], id: string): Layer | undefined {
for (const layer of layers) {
if (layer.id === id) return layer;
if (layer.type === "group") { const child = findLayer(layer.children, id); if (child) return child; }
}
}