- 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.
68 lines
3.2 KiB
TypeScript
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 } : {}) };
|
|
}
|