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:
@@ -1,5 +1,4 @@
|
||||
import type { ImageDocument } from "@core/document";
|
||||
import { getLayerMask } from "@core/layer-mask-utils";
|
||||
import { createDocumentReadIndex, resolveIndexedLayerBounds } from "@editor/document-indexes";
|
||||
import type { SelectionState } from "@editor/state";
|
||||
import type { GenerateSettings } from "@editor/tools";
|
||||
@@ -44,21 +43,15 @@ export function checkGenerationPreconditions(
|
||||
|
||||
if (settings.mode !== "inpaint") return { ready: true };
|
||||
|
||||
const layerMask = getLayerMask(layerInfo.layer);
|
||||
if (!layerMask?.enabled) return missing("Paint a mask over the area you want AI to replace.", "add-mask");
|
||||
const maskLayer = index.layerById.get(layerMask.maskLayerId);
|
||||
if (!maskLayer || (maskLayer.type !== "image" && maskLayer.type !== "raster")) return missing("The selected layer mask is missing.");
|
||||
const maskAsset = index.assetById.get(maskLayer.assetId);
|
||||
if (!maskAsset) return missing("The selected layer mask is missing its image data.");
|
||||
const region = document.inpaintRegions.find((candidate) => candidate.targetLayerId === layerInfo.layer.id && candidate.enabled);
|
||||
if (!region) return missing("Paint an AI edit region over the area you want to replace.", "add-mask");
|
||||
const maskAsset = index.assetById.get(region.maskAssetId);
|
||||
if (!maskAsset) return missing("The AI edit region is missing its mask data.");
|
||||
if (Math.round(asset.intrinsicSize.w) !== Math.round(maskAsset.intrinsicSize.w) || Math.round(asset.intrinsicSize.h) !== Math.round(maskAsset.intrinsicSize.h)) {
|
||||
return missing("The selected layer and mask image sizes must match.");
|
||||
}
|
||||
|
||||
const layerBounds = resolveIndexedLayerBounds(index, layerInfo.layer);
|
||||
const maskBounds = resolveIndexedLayerBounds(index, maskLayer);
|
||||
if (!layerBounds || !maskBounds || !rectsAligned(layerBounds, maskBounds) || Math.abs(layerInfo.layer.transform.rotation - maskLayer.transform.rotation) > 0.001) {
|
||||
return missing("Align the selected layer and its mask before inpainting.");
|
||||
}
|
||||
if (!resolveIndexedLayerBounds(index, layerInfo.layer)) return missing("The selected layer has invalid geometry.");
|
||||
|
||||
return { ready: true };
|
||||
}
|
||||
@@ -72,7 +65,3 @@ function modeSelectionMessage(mode: GenerateSettings["mode"]): string {
|
||||
function missing(message: string, repair?: Extract<GenerationPrecondition, { ready: false }>["repair"]): GenerationPrecondition {
|
||||
return { ready: false, message, ...(repair ? { repair } : {}) };
|
||||
}
|
||||
|
||||
function rectsAligned(a: { x: number; y: number; w: number; h: number }, b: { x: number; y: number; w: number; h: number }): boolean {
|
||||
return Math.abs(a.x - b.x) <= 0.5 && Math.abs(a.y - b.y) <= 0.5 && Math.abs(a.w - b.w) <= 0.5 && Math.abs(a.h - b.h) <= 0.5;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user