From a63db2af6b0dbbe4f77b4edfb43e1d71c28589c6 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 2 Apr 2026 10:40:26 +0800 Subject: [PATCH] fix: streamline psd editor layout and scaling --- .../agents/AgentPsdTemplateEditorDialog.tsx | 168 ++++++++++-------- lib/agents/psd-image-placement.ts | 52 ++++-- lib/agents/psd-template-renderer.ts | 6 +- 3 files changed, 131 insertions(+), 95 deletions(-) diff --git a/components/agents/AgentPsdTemplateEditorDialog.tsx b/components/agents/AgentPsdTemplateEditorDialog.tsx index 9f5f92061..f12336d91 100644 --- a/components/agents/AgentPsdTemplateEditorDialog.tsx +++ b/components/agents/AgentPsdTemplateEditorDialog.tsx @@ -43,7 +43,7 @@ import { Textarea } from "@/components/ui/textarea"; import { getDefaultAgentPsdImagePlacement, normalizeAgentPsdImagePlacement, - resolveAgentPsdImagePlacementFrame, + resolveAgentPsdImageBaseFrame, type AgentPsdImagePlacement, type AgentPsdImageSize, } from "@/lib/agents/psd-image-placement"; @@ -344,22 +344,25 @@ function getPlacedAssignedImageStyle(params: { placement: AgentPsdImagePlacement; }): CSSProperties { const { layer, imageSize, placement } = params; - const frame = resolveAgentPsdImagePlacementFrame({ + const normalizedPlacement = normalizeAgentPsdImagePlacement(placement); + const baseFrame = resolveAgentPsdImageBaseFrame({ containerWidth: layer.width, containerHeight: layer.height, imageWidth: imageSize.width, imageHeight: imageSize.height, replaceMode: layer.replaceMode, contentBounds: layer.contentBounds, - placement, }); return { position: "absolute", - left: frame.left, - top: frame.top, - width: frame.width, - height: frame.height, + left: baseFrame.centerX + normalizedPlacement.offsetX, + top: baseFrame.centerY + normalizedPlacement.offsetY, + width: baseFrame.width, + height: baseFrame.height, + transform: `translate(-50%, -50%) scale(${normalizedPlacement.scale})`, + transformOrigin: "center center", + willChange: "transform", }; } @@ -442,6 +445,7 @@ export function AgentPsdTemplateEditorDialog({ const [uploadingSlotId, setUploadingSlotId] = useState(null); const [isRendering, setIsRendering] = useState(false); const [renderedResultUrl, setRenderedResultUrl] = useState(""); + const [rightPanelView, setRightPanelView] = useState<"editor" | "export">("editor"); const [stageScale, setStageScale] = useState(1); const previewViewportRef = useRef(null); @@ -639,6 +643,7 @@ export function AgentPsdTemplateEditorDialog({ useEffect(() => { if (!open) return; setRenderedResultUrl(""); + setRightPanelView("editor"); const initialState = createInitialState(selectedTemplate, sourceImageUrl || ""); setImageAssignments(initialState.imageAssignments); setImagePlacements(initialState.imagePlacements); @@ -978,6 +983,7 @@ export function AgentPsdTemplateEditorDialog({ } setRenderedResultUrl(payload.result.url); + setRightPanelView("export"); toast.success("模板替换结果已生成"); } catch (error) { toast.error(error instanceof Error ? error.message : "PSD 模板渲染失败"); @@ -1544,7 +1550,37 @@ export function AgentPsdTemplateEditorDialog({
-
当前选中图层
+
+
+ {renderedResultUrl && rightPanelView === "export" ? "导出结果" : "当前选中图层"} +
+ {renderedResultUrl ? ( +
+ + +
+ ) : null} +
-
-
+ {renderedResultUrl && rightPanelView === "export" ? ( +
+
+
+
+ PSD 模板导出结果 +
+
+ + +
+
+ ) : ( +
{selectedImageLayer ? ( -
+
可替换图片
{selectedImageLayer.layerName}
@@ -1570,7 +1631,7 @@ export function AgentPsdTemplateEditorDialog({
{selectedImageLayer.layerPath}
-
+
{effectiveImageAssignments[selectedImageLayer.id] ? ( { const newScale = clampImageScale(value[0] || 1); - // offset 本身就是相对中心位移,缩放时不应按比例放大,否则会导致画面偏移看起来像“变形”。 updateImagePlacement(selectedImageLayer.id, (current) => ({ ...current, scale: newScale, @@ -1658,29 +1718,27 @@ export function AgentPsdTemplateEditorDialog({
) : null} -
+
{sourceImageUrl ? ( @@ -1728,7 +1786,7 @@ export function AgentPsdTemplateEditorDialog({
) : selectedTextLayer ? ( -
+
可编辑文字
{selectedTextLayer.layerName}
@@ -1741,7 +1799,7 @@ export function AgentPsdTemplateEditorDialog({ rows={4} value={effectiveTextAssignments[selectedTextLayer.id] || ""} placeholder={selectedTextLayer.placeholder || "请输入文字"} - className="min-h-[150px] resize-none xl:min-h-[132px]" + className="min-h-[180px] flex-1 resize-none xl:min-h-[200px]" onChange={(event) => setTextAssignments((current) => ({ ...current, @@ -1817,49 +1875,7 @@ export function AgentPsdTemplateEditorDialog({
)}
- -
-
导出结果
- {renderedResultUrl ? ( -
-
-
- PSD 模板导出结果 -
-
- - -
- ) : ( -
-
- {selectedTextLayer ? ( - - ) : ( - - )} -
-
还未导出成品图
-
- 在模板画布里完成替换后,点击“应用模板并导出”,这里会显示最终图片。 -
-
- )} -
-
+ )}
diff --git a/lib/agents/psd-image-placement.ts b/lib/agents/psd-image-placement.ts index 61978e646..2f1d3afe6 100644 --- a/lib/agents/psd-image-placement.ts +++ b/lib/agents/psd-image-placement.ts @@ -14,6 +14,15 @@ export interface AgentPsdImageSize { height: number; } +export interface AgentPsdImageFrame { + width: number; + height: number; + left: number; + top: number; + centerX: number; + centerY: number; +} + export function getDefaultAgentPsdImagePlacement(): AgentPsdImagePlacement { return { scale: 1, @@ -106,17 +115,15 @@ function normalizeContainerBounds(params: { }; } -export function resolveAgentPsdImagePlacementFrame(params: { +export function resolveAgentPsdImageBaseFrame(params: { containerWidth: number; containerHeight: number; imageWidth: number; imageHeight: number; replaceMode: AgentPsdTemplateReplaceMode; contentBounds?: AgentPsdTemplateImageContentBounds; - placement?: Partial | null; -}) { +}): AgentPsdImageFrame { const { containerWidth, containerHeight, imageWidth, imageHeight, replaceMode } = params; - const placement = normalizeAgentPsdImagePlacement(params.placement); const contentBounds = normalizeContainerBounds({ containerWidth, containerHeight, @@ -130,23 +137,36 @@ export function resolveAgentPsdImagePlacementFrame(params: { replaceMode, }); + return { + width: baseFrame.width, + height: baseFrame.height, + left: contentBounds.left + baseFrame.left, + top: contentBounds.top + baseFrame.top, + centerX: contentBounds.left + baseFrame.left + baseFrame.width / 2, + centerY: contentBounds.top + baseFrame.top + baseFrame.height / 2, + }; +} + +export function resolveAgentPsdImagePlacementFrame(params: { + containerWidth: number; + containerHeight: number; + imageWidth: number; + imageHeight: number; + replaceMode: AgentPsdTemplateReplaceMode; + contentBounds?: AgentPsdTemplateImageContentBounds; + placement?: Partial | null; +}): AgentPsdImageFrame { + const placement = normalizeAgentPsdImagePlacement(params.placement); + const baseFrame = resolveAgentPsdImageBaseFrame(params); const width = baseFrame.width * placement.scale; const height = baseFrame.height * placement.scale; - const left = - contentBounds.left + - baseFrame.left - - (width - baseFrame.width) / 2 + - placement.offsetX; - const top = - contentBounds.top + - baseFrame.top - - (height - baseFrame.height) / 2 + - placement.offsetY; return { width, height, - left, - top, + left: baseFrame.centerX + placement.offsetX - width / 2, + top: baseFrame.centerY + placement.offsetY - height / 2, + centerX: baseFrame.centerX + placement.offsetX, + centerY: baseFrame.centerY + placement.offsetY, }; } diff --git a/lib/agents/psd-template-renderer.ts b/lib/agents/psd-template-renderer.ts index d28c0dfbb..ed2dfcca8 100644 --- a/lib/agents/psd-template-renderer.ts +++ b/lib/agents/psd-template-renderer.ts @@ -459,7 +459,8 @@ export async function renderAgentPsdTemplate(params: { } const sourceBuffer = await readCachedBuffer(assignmentUrl); - const metadata = await sharp(sourceBuffer).metadata(); + const normalizedSourceBuffer = await sharp(sourceBuffer).rotate().png().toBuffer(); + const metadata = await sharp(normalizedSourceBuffer).metadata(); const placementFrame = resolveAgentPsdImagePlacementFrame({ containerWidth: layer.width, containerHeight: layer.height, @@ -470,8 +471,7 @@ export async function renderAgentPsdTemplate(params: { placement: imagePlacements[layer.id], }); - const resizedOverlayBuffer = await sharp(sourceBuffer) - .rotate() + const resizedOverlayBuffer = await sharp(normalizedSourceBuffer) .resize({ width: Math.max(1, Math.round(placementFrame.width)), height: Math.max(1, Math.round(placementFrame.height)),