fix: keep PSD clipped replacements in target layer order
This commit is contained in:
parent
97cc9e4a0a
commit
6b72771701
@ -4210,11 +4210,6 @@ export function AgentPsdTemplateEditorDialog({
|
||||
if (isAgentPsdImageLayerSlot(layer)) {
|
||||
const assignedUrl =
|
||||
effectiveImageAssignments[layer.id];
|
||||
const isDefaultAssignedImageLayer =
|
||||
layer.id ===
|
||||
getDefaultSourceImageLayer(
|
||||
selectedTemplateLayers
|
||||
)?.id;
|
||||
const layerImageUrl =
|
||||
assignedUrl ||
|
||||
getPsdLayerPreviewSourceUrl(layer);
|
||||
@ -4251,15 +4246,14 @@ export function AgentPsdTemplateEditorDialog({
|
||||
),
|
||||
}
|
||||
: layerBoxStyle;
|
||||
const assignedFrameBaseZIndex =
|
||||
layerStackZIndexById.get(
|
||||
assignedFrameLayer.id
|
||||
) || baseZIndex;
|
||||
const previewZIndex = assignedUrl
|
||||
? Math.max(
|
||||
baseZIndex,
|
||||
renderBaseZIndex + 1,
|
||||
isDefaultAssignedImageLayer
|
||||
? (orderedPreviewLayers.length + 1) *
|
||||
PSD_LAYER_Z_INDEX_STEP
|
||||
: 0
|
||||
)
|
||||
? assignedFrameLayer.id === layer.id
|
||||
? Math.max(baseZIndex, renderBaseZIndex + 1)
|
||||
: assignedFrameBaseZIndex + 1
|
||||
: baseZIndex;
|
||||
|
||||
return (
|
||||
|
||||
@ -912,6 +912,195 @@ export async function renderAgentPsdTemplate(params: {
|
||||
}
|
||||
}
|
||||
|
||||
function getAssignedImageRenderFrameLayer(layer: AgentPsdTemplateImageLayerSlot) {
|
||||
if (!layer.forceClipMask || !layer.clipMaskLayerId) {
|
||||
return layer;
|
||||
}
|
||||
|
||||
return layerMap.get(layer.clipMaskLayerId) || layer;
|
||||
}
|
||||
|
||||
const assignedImageLayersByFrameId = new Map<string, AgentPsdTemplateImageLayerSlot[]>();
|
||||
for (const layer of orderedTemplateLayers) {
|
||||
if (
|
||||
hiddenLayerIdSet.has(layer.id) ||
|
||||
!isAgentPsdImageLayerSlot(layer) ||
|
||||
!String(imageAssignments[layer.id] || "").trim()
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const frameLayer = getAssignedImageRenderFrameLayer(layer);
|
||||
const currentLayers = assignedImageLayersByFrameId.get(frameLayer.id) || [];
|
||||
currentLayers.push(layer);
|
||||
assignedImageLayersByFrameId.set(frameLayer.id, currentLayers);
|
||||
}
|
||||
|
||||
const appendedImageAssignmentLayerIds = new Set<string>();
|
||||
async function appendAssignedImageLayer(layer: AgentPsdTemplateImageLayerSlot) {
|
||||
if (appendedImageAssignmentLayerIds.has(layer.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isDefaultSourceImageLayer = layer.id === defaultSourceImageLayerId;
|
||||
const assignmentUrl = String(imageAssignments[layer.id] || "").trim();
|
||||
if (!assignmentUrl || hiddenLayerIdSet.has(layer.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
appendedImageAssignmentLayerIds.add(layer.id);
|
||||
const sourceBuffer = await readCachedBuffer(assignmentUrl, "PSD 套版素材");
|
||||
const clipMaskLayer =
|
||||
layer.forceClipMask && layer.clipMaskLayerId
|
||||
? layerMap.get(layer.clipMaskLayerId)
|
||||
: null;
|
||||
const renderFrameLayer = clipMaskLayer || layer;
|
||||
const renderFrameContentBounds =
|
||||
clipMaskLayer && isAgentPsdImageLayerSlot(clipMaskLayer)
|
||||
? clipMaskLayer.contentBounds
|
||||
: layer.contentBounds;
|
||||
const sourceImage = sharp(sourceBuffer, { failOn: "none" }).rotate();
|
||||
const metadata = await sourceImage.clone().metadata();
|
||||
const placementFrame = resolveAgentPsdImagePlacementFrame({
|
||||
containerWidth: renderFrameLayer.width,
|
||||
containerHeight: renderFrameLayer.height,
|
||||
imageWidth: Math.max(1, Number(metadata.width || 1)),
|
||||
imageHeight: Math.max(1, Number(metadata.height || 1)),
|
||||
replaceMode: layer.replaceMode,
|
||||
contentBounds: renderFrameContentBounds,
|
||||
placement: imagePlacements[layer.id],
|
||||
});
|
||||
|
||||
const resizedOverlayBuffer = await sourceImage
|
||||
.clone()
|
||||
.resize({
|
||||
width: Math.max(1, Math.round(placementFrame.width)),
|
||||
height: Math.max(1, Math.round(placementFrame.height)),
|
||||
fit:
|
||||
layer.replaceMode === "contain"
|
||||
? "contain"
|
||||
: layer.replaceMode === "stretch"
|
||||
? "fill"
|
||||
: "cover",
|
||||
background: {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
alpha: 0,
|
||||
},
|
||||
})
|
||||
.png(PSD_RENDER_PNG_OPTIONS)
|
||||
.toBuffer();
|
||||
|
||||
const slotOverlay = await fitOverlayBufferIntoCanvas({
|
||||
input: resizedOverlayBuffer,
|
||||
left: placementFrame.left,
|
||||
top: placementFrame.top,
|
||||
width: placementFrame.width,
|
||||
height: placementFrame.height,
|
||||
canvasWidth: renderFrameLayer.width,
|
||||
canvasHeight: renderFrameLayer.height,
|
||||
});
|
||||
|
||||
if (!slotOverlay) {
|
||||
return;
|
||||
}
|
||||
|
||||
const layerPositionOffset = getLayerPositionOffset(renderFrameLayer.id);
|
||||
const shouldApplyMask = Boolean(layer.maskUrl || clipMaskLayer);
|
||||
if (!shouldApplyMask) {
|
||||
if (isDefaultSourceImageLayer) {
|
||||
await appendRenderBaseOverlay();
|
||||
}
|
||||
await appendBufferOverlay({
|
||||
input: slotOverlay.input,
|
||||
left: renderFrameLayer.x + layerPositionOffset.offsetX + slotOverlay.left,
|
||||
top: renderFrameLayer.y + layerPositionOffset.offsetY + slotOverlay.top,
|
||||
width: slotOverlay.width,
|
||||
height: slotOverlay.height,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let overlayBuffer = await sharp({
|
||||
create: {
|
||||
width: Math.max(1, Math.round(renderFrameLayer.width)),
|
||||
height: Math.max(1, Math.round(renderFrameLayer.height)),
|
||||
channels: 4,
|
||||
background: {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
alpha: 0,
|
||||
},
|
||||
},
|
||||
})
|
||||
.composite([slotOverlay])
|
||||
.png(PSD_RENDER_PNG_OPTIONS)
|
||||
.toBuffer();
|
||||
|
||||
try {
|
||||
const rawMaskBuffer = layer.maskUrl
|
||||
? await readCachedBuffer(layer.maskUrl, "PSD 蒙版资源")
|
||||
: await readReusableLayerBuffer(clipMaskLayer!);
|
||||
if (!rawMaskBuffer) {
|
||||
throw new Error("Missing PSD layer mask source");
|
||||
}
|
||||
const maskBuffer = await sharp(rawMaskBuffer)
|
||||
.resize({
|
||||
width: Math.max(1, Math.round(renderFrameLayer.width)),
|
||||
height: Math.max(1, Math.round(renderFrameLayer.height)),
|
||||
fit: "fill",
|
||||
background: {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
alpha: 0,
|
||||
},
|
||||
})
|
||||
.png(PSD_RENDER_PNG_OPTIONS)
|
||||
.toBuffer();
|
||||
overlayBuffer = await sharp(overlayBuffer)
|
||||
.composite([
|
||||
{
|
||||
input: maskBuffer,
|
||||
blend: "dest-in",
|
||||
},
|
||||
])
|
||||
.png(PSD_RENDER_PNG_OPTIONS)
|
||||
.toBuffer();
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
"[PSD Template] failed to apply layer mask during render:",
|
||||
{
|
||||
templateId: template.id,
|
||||
layerId: layer.layerId,
|
||||
error,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (isDefaultSourceImageLayer) {
|
||||
await appendRenderBaseOverlay();
|
||||
}
|
||||
|
||||
await appendBufferOverlay({
|
||||
input: overlayBuffer,
|
||||
left: renderFrameLayer.x + layerPositionOffset.offsetX,
|
||||
top: renderFrameLayer.y + layerPositionOffset.offsetY,
|
||||
width: renderFrameLayer.width,
|
||||
height: renderFrameLayer.height,
|
||||
});
|
||||
}
|
||||
|
||||
async function appendAssignedImagesForFrameLayer(frameLayerId: string) {
|
||||
for (const assignedLayer of assignedImageLayersByFrameId.get(frameLayerId) || []) {
|
||||
if (assignedLayer.id !== frameLayerId) {
|
||||
await appendAssignedImageLayer(assignedLayer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
shouldUseRenderBaseFallback &&
|
||||
(!defaultSourceImageLayerId || hiddenLayerIdSet.has(defaultSourceImageLayerId))
|
||||
@ -930,9 +1119,11 @@ export async function renderAgentPsdTemplate(params: {
|
||||
|
||||
if (!isAgentPsdTemplateLayerUserEditable(layer)) {
|
||||
if (shouldUseRenderBaseFallback || shouldUseRenderSegments) {
|
||||
await appendAssignedImagesForFrameLayer(layer.id);
|
||||
continue;
|
||||
}
|
||||
await appendStoredLayer(layer, { optional: shouldUseRenderBaseFallback });
|
||||
await appendAssignedImagesForFrameLayer(layer.id);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -946,164 +1137,15 @@ export async function renderAgentPsdTemplate(params: {
|
||||
if (isDefaultSourceImageLayer) {
|
||||
await appendRenderBaseOverlay();
|
||||
}
|
||||
await appendAssignedImagesForFrameLayer(layer.id);
|
||||
continue;
|
||||
}
|
||||
|
||||
const sourceBuffer = await readCachedBuffer(
|
||||
assignmentUrl,
|
||||
"PSD 套版素材"
|
||||
);
|
||||
const clipMaskLayer =
|
||||
layer.forceClipMask && layer.clipMaskLayerId
|
||||
? layerMap.get(layer.clipMaskLayerId)
|
||||
: null;
|
||||
const renderFrameLayer = clipMaskLayer || layer;
|
||||
const renderFrameContentBounds =
|
||||
clipMaskLayer && isAgentPsdImageLayerSlot(clipMaskLayer)
|
||||
? clipMaskLayer.contentBounds
|
||||
: layer.contentBounds;
|
||||
const sourceImage = sharp(sourceBuffer, { failOn: "none" }).rotate();
|
||||
const metadata = await sourceImage.clone().metadata();
|
||||
const placementFrame = resolveAgentPsdImagePlacementFrame({
|
||||
containerWidth: renderFrameLayer.width,
|
||||
containerHeight: renderFrameLayer.height,
|
||||
imageWidth: Math.max(1, Number(metadata.width || 1)),
|
||||
imageHeight: Math.max(1, Number(metadata.height || 1)),
|
||||
replaceMode: layer.replaceMode,
|
||||
contentBounds: renderFrameContentBounds,
|
||||
placement: imagePlacements[layer.id],
|
||||
});
|
||||
|
||||
const resizedOverlayBuffer = await sourceImage
|
||||
.clone()
|
||||
.resize({
|
||||
width: Math.max(1, Math.round(placementFrame.width)),
|
||||
height: Math.max(1, Math.round(placementFrame.height)),
|
||||
fit:
|
||||
layer.replaceMode === "contain"
|
||||
? "contain"
|
||||
: layer.replaceMode === "stretch"
|
||||
? "fill"
|
||||
: "cover",
|
||||
background: {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
alpha: 0,
|
||||
},
|
||||
})
|
||||
.png(PSD_RENDER_PNG_OPTIONS)
|
||||
.toBuffer();
|
||||
|
||||
const slotOverlay = await fitOverlayBufferIntoCanvas({
|
||||
input: resizedOverlayBuffer,
|
||||
left: placementFrame.left,
|
||||
top: placementFrame.top,
|
||||
width: placementFrame.width,
|
||||
height: placementFrame.height,
|
||||
canvasWidth: renderFrameLayer.width,
|
||||
canvasHeight: renderFrameLayer.height,
|
||||
});
|
||||
|
||||
if (!slotOverlay) {
|
||||
continue;
|
||||
const renderFrameLayer = getAssignedImageRenderFrameLayer(layer);
|
||||
if (renderFrameLayer.id === layer.id) {
|
||||
await appendAssignedImageLayer(layer);
|
||||
}
|
||||
|
||||
const layerPositionOffset = getLayerPositionOffset(renderFrameLayer.id);
|
||||
const shouldApplyMask = Boolean(layer.maskUrl || clipMaskLayer);
|
||||
if (!shouldApplyMask) {
|
||||
if (isDefaultSourceImageLayer) {
|
||||
await appendRenderBaseOverlay();
|
||||
}
|
||||
const didAppend = await appendBufferOverlay({
|
||||
input: slotOverlay.input,
|
||||
left:
|
||||
renderFrameLayer.x +
|
||||
layerPositionOffset.offsetX +
|
||||
slotOverlay.left,
|
||||
top:
|
||||
renderFrameLayer.y +
|
||||
layerPositionOffset.offsetY +
|
||||
slotOverlay.top,
|
||||
width: slotOverlay.width,
|
||||
height: slotOverlay.height,
|
||||
});
|
||||
if (!didAppend) continue;
|
||||
continue;
|
||||
}
|
||||
|
||||
let overlayBuffer = await sharp({
|
||||
create: {
|
||||
width: Math.max(1, Math.round(renderFrameLayer.width)),
|
||||
height: Math.max(1, Math.round(renderFrameLayer.height)),
|
||||
channels: 4,
|
||||
background: {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
alpha: 0,
|
||||
},
|
||||
},
|
||||
})
|
||||
.composite([slotOverlay])
|
||||
.png(PSD_RENDER_PNG_OPTIONS)
|
||||
.toBuffer();
|
||||
|
||||
if (shouldApplyMask) {
|
||||
try {
|
||||
const rawMaskBuffer = layer.maskUrl
|
||||
? await readCachedBuffer(layer.maskUrl, "PSD 蒙版资源")
|
||||
: await readReusableLayerBuffer(clipMaskLayer!);
|
||||
if (!rawMaskBuffer) {
|
||||
throw new Error("Missing PSD layer mask source");
|
||||
}
|
||||
const maskBuffer = await sharp(rawMaskBuffer)
|
||||
.resize({
|
||||
width: Math.max(1, Math.round(renderFrameLayer.width)),
|
||||
height: Math.max(1, Math.round(renderFrameLayer.height)),
|
||||
fit: "fill",
|
||||
background: {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
alpha: 0,
|
||||
},
|
||||
})
|
||||
.png(PSD_RENDER_PNG_OPTIONS)
|
||||
.toBuffer();
|
||||
overlayBuffer = await sharp(overlayBuffer)
|
||||
.composite([
|
||||
{
|
||||
input: maskBuffer,
|
||||
blend: "dest-in",
|
||||
},
|
||||
])
|
||||
.png(PSD_RENDER_PNG_OPTIONS)
|
||||
.toBuffer();
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
"[PSD Template] failed to apply layer mask during render:",
|
||||
{
|
||||
templateId: template.id,
|
||||
layerId: layer.layerId,
|
||||
error,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (isDefaultSourceImageLayer) {
|
||||
await appendRenderBaseOverlay();
|
||||
}
|
||||
|
||||
const didAppend = await appendBufferOverlay({
|
||||
input: overlayBuffer,
|
||||
left: renderFrameLayer.x + layerPositionOffset.offsetX,
|
||||
top: renderFrameLayer.y + layerPositionOffset.offsetY,
|
||||
width: renderFrameLayer.width,
|
||||
height: renderFrameLayer.height,
|
||||
});
|
||||
if (!didAppend) continue;
|
||||
await appendAssignedImagesForFrameLayer(layer.id);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -1127,6 +1169,7 @@ export async function renderAgentPsdTemplate(params: {
|
||||
optional: shouldUseRenderBaseFallback,
|
||||
});
|
||||
}
|
||||
await appendAssignedImagesForFrameLayer(layer.id);
|
||||
continue;
|
||||
}
|
||||
const textAlign = resolveAgentPsdTextLayerEffectiveAlign(layer);
|
||||
@ -1236,6 +1279,7 @@ export async function renderAgentPsdTemplate(params: {
|
||||
height: overlayFrame.height,
|
||||
});
|
||||
if (!didAppend) continue;
|
||||
await appendAssignedImagesForFrameLayer(layer.id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user