fix: anchor PSD replacements to clip targets

This commit is contained in:
Your Name 2026-06-01 20:38:45 +08:00
parent 154ec44a4c
commit 97cc9e4a0a
2 changed files with 87 additions and 28 deletions

View File

@ -1498,6 +1498,7 @@ function renderTextLayerPreviewContent(
function getAssignedImageMaskStyle(
layer: AgentPsdTemplateImageLayerSlot,
frameLayer: AgentPsdTemplateLayerSlot,
layers: AgentPsdTemplateLayerSlot[]
): CSSProperties {
const clipMaskLayer = layer.clipMaskLayerId
@ -1512,7 +1513,7 @@ function getAssignedImageMaskStyle(
).trim();
const hasMask = Boolean(normalizedMaskUrl);
const maskImageUrl = hasMask
? `url(${buildPsdLayerMaskUrl(normalizedMaskUrl, layer.width)})`
? `url(${buildPsdLayerMaskUrl(normalizedMaskUrl, frameLayer.width)})`
: undefined;
return {
WebkitMaskImage: maskImageUrl,
@ -1529,18 +1530,22 @@ function getAssignedImageMaskStyle(
function getPlacedAssignedImageStyle(params: {
layer: AgentPsdTemplateImageLayerSlot;
frameLayer?: AgentPsdTemplateLayerSlot | null;
imageSize: AgentPsdImageSize;
placement: AgentPsdImagePlacement;
}): CSSProperties {
const { layer, imageSize, placement } = params;
const frameLayer = params.frameLayer || layer;
const normalizedPlacement = normalizeAgentPsdImagePlacement(placement);
const frameContentBounds =
isAgentPsdImageLayerSlot(frameLayer) ? frameLayer.contentBounds : undefined;
const baseFrame = resolveAgentPsdImageBaseFrame({
containerWidth: layer.width,
containerHeight: layer.height,
containerWidth: frameLayer.width,
containerHeight: frameLayer.height,
imageWidth: imageSize.width,
imageHeight: imageSize.height,
replaceMode: layer.replaceMode,
contentBounds: layer.contentBounds,
contentBounds: frameContentBounds,
});
return {
@ -1558,6 +1563,17 @@ function getPlacedAssignedImageStyle(params: {
};
}
function getAssignedImageFrameLayer(
layer: AgentPsdTemplateImageLayerSlot,
layers: AgentPsdTemplateLayerSlot[]
) {
if (!layer.forceClipMask || !layer.clipMaskLayerId) {
return layer;
}
return layers.find((item) => item.id === layer.clipMaskLayerId) || layer;
}
function hasEditedTextValue(
layer: AgentPsdTemplateTextLayerSlot,
value: string
@ -3432,15 +3448,18 @@ export function AgentPsdTemplateEditorDialog({
event.preventDefault();
event.stopPropagation();
setSelectedLayerId(layer.id);
const shouldDragAssignedImageContent =
isAgentPsdImageLayerSlot(layer) &&
Boolean(String(effectiveImageAssignments[layer.id] || "").trim());
layerDragStateRef.current = {
layerId: layer.id,
pointerId: event.pointerId,
startClientX: event.clientX,
startClientY: event.clientY,
startOffset: normalizeLayerPositionOffset(
effectiveLayerPositionOffsets[layer.id]
),
startOffset: shouldDragAssignedImageContent
? normalizeLayerPositionOffset(effectiveImagePlacements[layer.id])
: normalizeLayerPositionOffset(effectiveLayerPositionOffsets[layer.id]),
};
const nextTarget = event.currentTarget;
@ -3459,6 +3478,19 @@ export function AgentPsdTemplateEditorDialog({
return;
}
if (shouldDragAssignedImageContent) {
updateImagePlacement(layer.id, (current) => ({
...current,
offsetX:
dragState.startOffset.offsetX +
(moveEvent.clientX - dragState.startClientX) / stageScale,
offsetY:
dragState.startOffset.offsetY +
(moveEvent.clientY - dragState.startClientY) / stageScale,
}));
return;
}
updateLayerPositionOffset(layer.id, () => ({
offsetX:
dragState.startOffset.offsetX +
@ -4189,11 +4221,32 @@ export function AgentPsdTemplateEditorDialog({
if (!layerImageUrl) {
return null;
}
const assignedFrameLayer = assignedUrl
? getAssignedImageFrameLayer(
layer,
selectedTemplateLayers
)
: layer;
const assignedFrameOffset =
effectiveLayerPositionOffsets[
assignedFrameLayer.id
] || normalizeLayerPositionOffset();
const assignedFrameBoxStyle = {
left:
assignedFrameLayer.x +
assignedFrameOffset.offsetX,
top:
assignedFrameLayer.y +
assignedFrameOffset.offsetY,
width: assignedFrameLayer.width,
height: assignedFrameLayer.height,
} satisfies CSSProperties;
const assignedImageContainerStyle = assignedUrl
? {
...layerBoxStyle,
...assignedFrameBoxStyle,
...getAssignedImageMaskStyle(
layer,
assignedFrameLayer,
selectedTemplateLayers
),
}
@ -4263,6 +4316,7 @@ export function AgentPsdTemplateEditorDialog({
assignedUrl && imageSizes[layer.id]
? getPlacedAssignedImageStyle({
layer,
frameLayer: assignedFrameLayer,
imageSize: imageSizes[layer.id],
placement:
effectiveImagePlacements[

View File

@ -953,15 +953,24 @@ export async function renderAgentPsdTemplate(params: {
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: layer.width,
containerHeight: layer.height,
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: layer.contentBounds,
contentBounds: renderFrameContentBounds,
placement: imagePlacements[layer.id],
});
@ -992,19 +1001,15 @@ export async function renderAgentPsdTemplate(params: {
top: placementFrame.top,
width: placementFrame.width,
height: placementFrame.height,
canvasWidth: layer.width,
canvasHeight: layer.height,
canvasWidth: renderFrameLayer.width,
canvasHeight: renderFrameLayer.height,
});
if (!slotOverlay) {
continue;
}
const layerPositionOffset = getLayerPositionOffset(layer.id);
const clipMaskLayer =
layer.forceClipMask && layer.clipMaskLayerId
? layerMap.get(layer.clipMaskLayerId)
: null;
const layerPositionOffset = getLayerPositionOffset(renderFrameLayer.id);
const shouldApplyMask = Boolean(layer.maskUrl || clipMaskLayer);
if (!shouldApplyMask) {
if (isDefaultSourceImageLayer) {
@ -1013,11 +1018,11 @@ export async function renderAgentPsdTemplate(params: {
const didAppend = await appendBufferOverlay({
input: slotOverlay.input,
left:
layer.x +
renderFrameLayer.x +
layerPositionOffset.offsetX +
slotOverlay.left,
top:
layer.y +
renderFrameLayer.y +
layerPositionOffset.offsetY +
slotOverlay.top,
width: slotOverlay.width,
@ -1029,8 +1034,8 @@ export async function renderAgentPsdTemplate(params: {
let overlayBuffer = await sharp({
create: {
width: Math.max(1, Math.round(layer.width)),
height: Math.max(1, Math.round(layer.height)),
width: Math.max(1, Math.round(renderFrameLayer.width)),
height: Math.max(1, Math.round(renderFrameLayer.height)),
channels: 4,
background: {
r: 0,
@ -1054,8 +1059,8 @@ export async function renderAgentPsdTemplate(params: {
}
const maskBuffer = await sharp(rawMaskBuffer)
.resize({
width: Math.max(1, Math.round(layer.width)),
height: Math.max(1, Math.round(layer.height)),
width: Math.max(1, Math.round(renderFrameLayer.width)),
height: Math.max(1, Math.round(renderFrameLayer.height)),
fit: "fill",
background: {
r: 0,
@ -1093,10 +1098,10 @@ export async function renderAgentPsdTemplate(params: {
const didAppend = await appendBufferOverlay({
input: overlayBuffer,
left: layer.x + layerPositionOffset.offsetX,
top: layer.y + layerPositionOffset.offsetY,
width: layer.width,
height: layer.height,
left: renderFrameLayer.x + layerPositionOffset.offsetX,
top: renderFrameLayer.y + layerPositionOffset.offsetY,
width: renderFrameLayer.width,
height: renderFrameLayer.height,
});
if (!didAppend) continue;
continue;