Files
image-studio/operations/generation/preconditions.ts
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

68 lines
3.2 KiB
TypeScript

import type { ImageDocument } from "@core/document";
import { createDocumentReadIndex, resolveIndexedLayerBounds } from "@editor/document-indexes";
import type { SelectionState } from "@editor/state";
import type { GenerateSettings } from "@editor/tools";
export type GenerationPrecondition =
| { ready: true }
| { ready: false; message: string; repair?: "add-mask" | "set-outpaint-padding" };
export function checkGenerationPreconditions(
document: ImageDocument,
selection: SelectionState,
settings: GenerateSettings,
): GenerationPrecondition {
if (!settings.prompt.trim()) return missing("Enter a prompt to generate an image.");
const artboard = selection.artboardId
? document.artboards.find((candidate) => candidate.id === selection.artboardId)
: document.artboards[0];
if (!artboard) return missing("Create an artboard before generating an image.");
if (settings.mode === "text-to-image") return { ready: true };
if (selection.layerIds.length !== 1 || !selection.layerIds[0]) {
return missing(modeSelectionMessage(settings.mode));
}
const index = createDocumentReadIndex(document);
const layerInfo = index.layerInfoById.get(selection.layerIds[0]);
if (!layerInfo || layerInfo.artboardId !== artboard.id || (layerInfo.layer.type !== "image" && layerInfo.layer.type !== "raster")) {
return missing(modeSelectionMessage(settings.mode));
}
const asset = index.assetById.get(layerInfo.layer.assetId);
if (!asset) return missing("The selected layer is missing its source image.");
if (asset.intrinsicSize.w <= 0 || asset.intrinsicSize.h <= 0) return missing("The selected image has an invalid size.");
if (settings.mode === "outpaint") {
const padding = settings.outpaint;
if (padding.left + padding.top + padding.right + padding.bottom <= 0) {
return missing("Add outpaint padding on at least one side.", "set-outpaint-padding");
}
}
if (settings.mode !== "inpaint") return { ready: true };
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.");
}
if (!resolveIndexedLayerBounds(index, layerInfo.layer)) return missing("The selected layer has invalid geometry.");
return { ready: true };
}
function modeSelectionMessage(mode: GenerateSettings["mode"]): string {
if (mode === "image-to-image") return "Select exactly one image or raster layer for image-to-image generation.";
if (mode === "inpaint") return "Select exactly one masked image or raster layer to inpaint.";
return "Select exactly one image or raster layer to outpaint.";
}
function missing(message: string, repair?: Extract<GenerationPrecondition, { ready: false }>["repair"]): GenerationPrecondition {
return { ready: false, message, ...(repair ? { repair } : {}) };
}