feat: implement generation preconditions check and integrate with generation commands
This commit is contained in:
78
operations/generation/preconditions.ts
Normal file
78
operations/generation/preconditions.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user