79 lines
3.9 KiB
TypeScript
79 lines
3.9 KiB
TypeScript
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; 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 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.");
|
|
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, 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;
|
|
}
|