编程 照片编辑器预览与导出不一致?用版本化渲染计划统一构图决策

2026-09-07 17:12:12

让照片编辑器的预览和导出保持一致:TypeScript 渲染管线的版本化设计

作者开发 Photocard.ai(自定义 photo card 制作工具),Maker 管线把用户上传的照片和模板美术组合,涉及照片摆放、背景移除、装饰层和多种输出尺寸。最有用的架构决策是:让构图决策显式化,并保存成版本化的渲染计划(render plan),预览和高分辨率渲染消费同一份决策。

目标是跨分辨率的构图一致。轻量浏览器预览在头发细节、滤镜、边缘质量上和最终渲染有差异——这些是独立要验证的东西,但"布局决策"必须一致。

保存产生图片的决策

模板缩略图显示预期外观,但它无法告诉渲染器:每个槽位放哪张源图、裁哪里、哪层装饰在人前面。Maker 实现里,模板 manifest 描述这些规则,渲染计划为特定的一组上传解析它们:

type Rect = { x: number; y: number; width: number; height: number };

type RenderSlot = {
  sourceIndex: number;
  sourceCrop: Rect;          // 相对旋转后的源图
  destination: Rect;         // 相对输出画布
  zIndex: number;
  subjectAlphaKey?: string;
  refinedForegroundKey?: string;
  effectSeed: number;
};

type RenderPlan = {
  version: 2;
  templateVersionId: string;
  assetPackageVersion: string;
  outputVariant: string;
  slots: RenderSlot[];
};

原则很小:保存解释这个构图所需的信息。导出开始时加载已保存的裁剪——再跑一次自动裁剪检测器就多一次选错人脸位置的机会。装饰效果同理:如果纸张边缘处理用随机性,保存它的 seed,避免另一次渲染选不同图案。版本号背后要有具体的东西:只要旧卡仍可编辑,对应的美术和处理行为就必须仍可获得——版本字符串救不回被覆盖的资源。

让每个模板槽位自己选源

人像放在相框里可能需要完整原图,贴纸构图可能需要透明人像。manifest 让这个选择显式:sourceComposition: "original_photo" | "cutout_subject"。这防止一种微妙失败:仅仅因为缓存里恰好存在 mask,就对槽位应用背景移除。全图路径保留场景;cutout 槽位缺必需 mask 是校验错误。显式编辑功能(如轮廓模式)可以引入自己的规则,但可选资源的存在性绝不该意外决定视觉策略。群组合照尤其如此:单人 mask 和原群组合照包含不同主体,选错就改变卡片内容。

分离源坐标与画布坐标

裁剪操作里有两个矩形:源裁剪选择上传图的区域,目标框把它放到输出画布上。两者都可以用归一化坐标——x=0.1 表示相关图像宽度的 10%。源矩形相对旋转后的上传图,目标矩形相对画布。

同样的 {x:0.1, y:0.1, width:0.8, height:0.8} 在 600×900 画布上是 480×720,在 2400×3600 上是 1920×2880——布局无需新决策就能缩放。在渲染边界转成像素,并做完整的矩形合法性校验(数值有限、坐标非负、尺寸非零、不越界),圆整边界让矩形边缘显式,比较不同输出分辨率时允许圆整差异。

工程要点

这套设计的核心:构图是数据,不是过程。把"选源、裁剪、分层、随机种子"固化成版本化文档,预览与导出共享同一决策源,就消除了"预览批准了、导出变了"这类最让用户恼火的 bug 类别。对任何有渲染链的产品(图片、文档、视频),把布局决策与渲染执行解耦、给决策加版本,是性价比极高的架构投资。

来源:Keeping Photo Editor Previews and Exports in Sync with TypeScript and Sharp - DEV Community

复制全文 生成海报 TypeScript 渲染 架构设计 前端

推荐文章

程序员茄子在线接单