fix: preserve PSD layer order in template rendering
This commit is contained in:
parent
510b0c13a6
commit
9965ead31a
@ -87,6 +87,11 @@ interface PsdLayerNodeEntry {
|
||||
layer: ParsedPsdTemplateLayer;
|
||||
}
|
||||
|
||||
export interface PsdTemplateSourceLayerEntry {
|
||||
layer: ParsedPsdTemplateLayer;
|
||||
composite: () => Promise<Uint8ClampedArray>;
|
||||
}
|
||||
|
||||
function normalizeLayerName(value: unknown, fallback: string) {
|
||||
const text = String(value || "").trim();
|
||||
return text || fallback;
|
||||
@ -308,6 +313,23 @@ export async function parsePsdTemplateFileFromUrl(fileUrl: string) {
|
||||
return parsePsdTemplateFile(buffer);
|
||||
}
|
||||
|
||||
export async function loadPsdTemplateSourceFromUrl(fileUrl: string) {
|
||||
const buffer = await fetchRemoteBuffer(fileUrl);
|
||||
const psd = await parsePsdBuffer(buffer);
|
||||
const layerEntries = collectPsdLayerEntries(Array.isArray(psd.children) ? psd.children : []);
|
||||
|
||||
return {
|
||||
width: Math.max(1, Math.round(Number(psd.width || 0))),
|
||||
height: Math.max(1, Math.round(Number(psd.height || 0))),
|
||||
layers: layerEntries
|
||||
.filter((entry) => entry.node && typeof entry.node.composite === "function")
|
||||
.map((entry) => ({
|
||||
layer: entry.layer,
|
||||
composite: () => entry.node.composite!(),
|
||||
})) satisfies PsdTemplateSourceLayerEntry[],
|
||||
};
|
||||
}
|
||||
|
||||
async function createTransparentCanvasBuffer(width: number, height: number) {
|
||||
const sharp = await getSharp();
|
||||
const canvasWidth = Math.max(1, Math.round(Number(width || 0)));
|
||||
@ -491,7 +513,7 @@ export async function preparePsdTemplateRenderAssets(params: {
|
||||
|
||||
const baseCompositeInputs: Array<{ input: Buffer; left: number; top: number }> = [];
|
||||
|
||||
for (const entry of layerEntries) {
|
||||
for (const entry of [...layerEntries].reverse()) {
|
||||
if (editableLayerIdSet.has(entry.layer.layerId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import "server-only";
|
||||
|
||||
import { uploadFile } from "@/lib/storage/service";
|
||||
import { loadPsdTemplateSourceFromUrl } from "@/lib/agents/psd-template-parser";
|
||||
import {
|
||||
isAgentPsdImageLayerSlot,
|
||||
isAgentPsdTextLayerSlot,
|
||||
@ -26,6 +27,19 @@ async function fetchRemoteBuffer(url: string) {
|
||||
return Buffer.from(await response.arrayBuffer());
|
||||
}
|
||||
|
||||
async function rgbaToPngBuffer(rgba: Uint8ClampedArray, width: number, height: number) {
|
||||
const sharp = await getSharp();
|
||||
return sharp(Buffer.from(rgba), {
|
||||
raw: {
|
||||
width,
|
||||
height,
|
||||
channels: 4,
|
||||
},
|
||||
})
|
||||
.png()
|
||||
.toBuffer();
|
||||
}
|
||||
|
||||
async function fitOverlayBufferIntoCanvas(params: {
|
||||
input: Buffer;
|
||||
left: number;
|
||||
@ -285,12 +299,12 @@ export async function renderAgentPsdTemplate(params: {
|
||||
}) {
|
||||
const { template, imageAssignments, textAssignments, fonts } = params;
|
||||
const sharp = await getSharp();
|
||||
const baseBuffer = await fetchRemoteBuffer(template.renderBaseUrl || template.previewUrl);
|
||||
const baseMetadata = await sharp(baseBuffer).metadata();
|
||||
const canvasWidth = Math.max(1, Math.round(Number(baseMetadata.width || template.width || 1)));
|
||||
const canvasHeight = Math.max(1, Math.round(Number(baseMetadata.height || template.height || 1)));
|
||||
const source = await loadPsdTemplateSourceFromUrl(template.fileUrl);
|
||||
const canvasWidth = Math.max(1, Math.round(Number(source.width || template.width || 1)));
|
||||
const canvasHeight = Math.max(1, Math.round(Number(source.height || template.height || 1)));
|
||||
const remoteBufferCache = new Map<string, Buffer>();
|
||||
const fontMap = new Map(fonts.map((font) => [font.id, font]));
|
||||
const sourceLayerMap = new Map(source.layers.map((entry) => [entry.layer.layerId, entry]));
|
||||
|
||||
async function readCachedBuffer(url: string) {
|
||||
const normalizedUrl = String(url || "").trim();
|
||||
@ -307,12 +321,40 @@ export async function renderAgentPsdTemplate(params: {
|
||||
|
||||
const compositeInputs: Array<{ input: Buffer; left: number; top: number }> = [];
|
||||
|
||||
for (const layer of template.layers) {
|
||||
if (!layer.isEditable) continue;
|
||||
async function appendOriginalLayer(layerId: string) {
|
||||
const sourceLayer = sourceLayerMap.get(layerId);
|
||||
if (!sourceLayer) return;
|
||||
const rgba = await sourceLayer.composite();
|
||||
const overlayBuffer = await rgbaToPngBuffer(
|
||||
rgba,
|
||||
sourceLayer.layer.width,
|
||||
sourceLayer.layer.height
|
||||
);
|
||||
const fittedOverlay = await fitOverlayBufferIntoCanvas({
|
||||
input: overlayBuffer,
|
||||
left: sourceLayer.layer.x,
|
||||
top: sourceLayer.layer.y,
|
||||
width: sourceLayer.layer.width,
|
||||
height: sourceLayer.layer.height,
|
||||
canvasWidth,
|
||||
canvasHeight,
|
||||
});
|
||||
if (!fittedOverlay) return;
|
||||
compositeInputs.push(fittedOverlay);
|
||||
}
|
||||
|
||||
for (const layer of [...template.layers].reverse()) {
|
||||
if (!layer.isEditable) {
|
||||
await appendOriginalLayer(layer.layerId);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isAgentPsdImageLayerSlot(layer)) {
|
||||
const assignmentUrl = String(imageAssignments[layer.id] || "").trim();
|
||||
if (!assignmentUrl) continue;
|
||||
if (!assignmentUrl) {
|
||||
await appendOriginalLayer(layer.layerId);
|
||||
continue;
|
||||
}
|
||||
|
||||
const sourceBuffer = await readCachedBuffer(assignmentUrl);
|
||||
const fit =
|
||||
@ -368,7 +410,11 @@ export async function renderAgentPsdTemplate(params: {
|
||||
|
||||
if (isAgentPsdTextLayerSlot(layer)) {
|
||||
const text = String(textAssignments[layer.id] || layer.defaultText || "").trim();
|
||||
if (!text) continue;
|
||||
const defaultText = String(layer.defaultText || "").trim();
|
||||
if (!text || text === defaultText) {
|
||||
await appendOriginalLayer(layer.layerId);
|
||||
continue;
|
||||
}
|
||||
|
||||
const font = layer.fontId ? fontMap.get(layer.fontId) : undefined;
|
||||
const fontBuffer = font?.fileUrl ? await readCachedBuffer(font.fileUrl) : undefined;
|
||||
@ -407,7 +453,23 @@ export async function renderAgentPsdTemplate(params: {
|
||||
}
|
||||
}
|
||||
|
||||
const resultBuffer = await sharp(baseBuffer)
|
||||
const transparentCanvas = await sharp({
|
||||
create: {
|
||||
width: canvasWidth,
|
||||
height: canvasHeight,
|
||||
channels: 4,
|
||||
background: {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
alpha: 0,
|
||||
},
|
||||
},
|
||||
})
|
||||
.png()
|
||||
.toBuffer();
|
||||
|
||||
const resultBuffer = await sharp(transparentCanvas)
|
||||
.composite(compositeInputs)
|
||||
.png()
|
||||
.toBuffer();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user