import type { ImageDocument } from "@core/document"; import type { Size, Transform } from "@core/geometry"; import type { ArtboardId } from "@core/id"; import { createDocumentReadIndex } from "@editor/document-indexes"; import type { GenerationCandidate, SelectionState } from "@editor/state"; import type { GenerateSettings } from "@editor/tools"; import type { InpaintBundle } from "./inpaintPrep"; export type GeneratedOutputPlacement = GenerationCandidate["placement"]; export function resolveGeneratedOutputPlacement(options: { document: ImageDocument; selection: SelectionState; settings: GenerateSettings; intrinsicSize: Size; inpaintBundle?: InpaintBundle; }): GeneratedOutputPlacement { const { document, selection, settings, intrinsicSize, inpaintBundle } = options; if (inpaintBundle) { return { artboardId: inpaintBundle.placement.artboardId, layerName: inpaintBundle.placement.layerName, transform: scaleInpaintPlacement(inpaintBundle, intrinsicSize), }; } const artboard = selection.artboardId ? document.artboards.find((candidate) => candidate.id === selection.artboardId) : document.artboards[0]; if (!artboard) throw new Error("Create an artboard before placing generated output."); if (settings.mode === "text-to-image") { return { artboardId: artboard.id, layerName: "Generated image", transform: { position: { x: artboard.bounds.x + (artboard.bounds.w - intrinsicSize.w) / 2, y: artboard.bounds.y + (artboard.bounds.h - intrinsicSize.h) / 2, }, scale: { x: 1, y: 1 }, rotation: 0, }, }; } const target = resolveTarget(document, selection, artboard.id); const sourceScale = { x: target.layer.transform.scale.x * target.asset.intrinsicSize.w / Math.max(1, intrinsicSize.w), y: target.layer.transform.scale.y * target.asset.intrinsicSize.h / Math.max(1, intrinsicSize.h), }; if (settings.mode === "image-to-image") { return { artboardId: artboard.id, layerName: "Generated image", transform: { position: { ...target.layer.transform.position }, scale: sourceScale, rotation: target.layer.transform.rotation, }, }; } if (settings.mode === "outpaint") { const pixelScale = target.layer.transform.scale; return { artboardId: artboard.id, layerName: "Generated image", transform: { position: { x: target.layer.transform.position.x - settings.outpaint.left * pixelScale.x, y: target.layer.transform.position.y - settings.outpaint.top * pixelScale.y, }, scale: { ...pixelScale }, rotation: target.layer.transform.rotation, }, }; } throw new Error("Inpaint output requires an inpaint placement bundle."); } function resolveTarget(document: ImageDocument, selection: SelectionState, artboardId: ArtboardId) { const layerId = selection.layerIds[0]; const index = createDocumentReadIndex(document); const layerInfo = layerId ? index.layerInfoById.get(layerId) : undefined; if (!layerInfo || layerInfo.artboardId !== artboardId || layerInfo.layer.type === "group") { throw new Error("Select one image or raster layer before placing generated output."); } const asset = index.assetById.get(layerInfo.layer.assetId); if (!asset) throw new Error("The selected layer is missing its source image."); return { layer: layerInfo.layer, asset }; } function scaleInpaintPlacement(bundle: InpaintBundle, intrinsicSize: Size): Transform { return { position: { ...bundle.placement.transform.position }, scale: { x: bundle.placement.transform.scale.x * bundle.width / Math.max(1, intrinsicSize.w), y: bundle.placement.transform.scale.y * bundle.height / Math.max(1, intrinsicSize.h), }, rotation: bundle.placement.transform.rotation, }; }