106 lines
3.8 KiB
TypeScript
106 lines
3.8 KiB
TypeScript
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 !== "image" && layerInfo.layer.type !== "raster")) {
|
|
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,
|
|
};
|
|
}
|