- 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.
53 lines
3.1 KiB
TypeScript
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; }
|
|
}
|
|
}
|