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"; export type GenerationPrecondition = | { ready: true } | { ready: false; message: string }; 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 === "group") { 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."); } } if (settings.mode !== "inpaint") return { ready: true }; const layerMask = getLayerMask(layerInfo.layer); if (!layerMask?.enabled) return missing("Add and enable a layer mask on the selected image before inpainting."); const maskLayer = index.layerById.get(layerMask.maskLayerId); if (!maskLayer || maskLayer.type === "group") 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."); 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."); } 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): GenerationPrecondition { return { ready: false, message }; } 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; }