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, 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; } } }