在给可能实验室(大梦餐饮)制作午市特惠、台风天畅饮、球赛之夜等一系列商业促销海报时,我做了一个决定:不打开 Photoshop,全部用纯 HTML + CSS + Python 脚本来做

用 Web 技术做海报有极大的优势:布局可以用 Flexbox/Grid 极速调整、文案改动只需改一行字符串、多尺寸适配通过 CSS 变量瞬间完成,而且全套物料可以纳入 Git 版本管理。

但在真正借助 Claude Fable 5Grok Imagine 图像模型 将网页无损导出为 300DPI 商业级印刷海报的过程中,我们踩遍了前端渲染、字体子集化、图像抠图与色彩空间的几乎所有暗坑。

这篇文章把这些工程细节与解决方案彻底拆解,给同样想用代码做设计的朋友一份避坑指南。


一、 导出引擎的演进:从 html2canvas 到 Chrome Headless

网页在浏览器里看着很美,但要导出一张像素完美(Pixel Perfect)的 1600×2400 高清大图,首先要选对渲染引擎。

1. html2canvas 的「翻车」表现

最初我们尝试了老牌的 html2canvas,结果导出的图片与原页面差异巨大:

  • filter: drop-shadow(...) 阴影几乎全丢。
  • 径向渐变(Radial Gradient)在 Canvas 绘制时变成了生硬的同心圆硬聚光。
  • 精细的金边暗纹和半透明毛玻璃背景变得惨白模糊。

2. html-to-image(SVG foreignObject 路线)

随后我们切换到了 html-to-image 库。它利用浏览器的 <foreignObject> 将 DOM 结构直接转化为 SVG,再绘制成 PNG,真正做到了「浏览器里看什么,导出来就是什么」。

3. 最强终局方案:Chrome Headless 命令行直出

在批量生成物料时,最稳健的做法甚至不需要在页面里挂载任何导出 JS,直接用本地 Google Chrome 的无头模式截图:

1
2
3
4
5
6
7
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
  --headless=new \
  --screenshot="海报_1600x2400.png" \
  --window-size=800,1200 \
  --force-device-scale-factor=2 \
  --hide-scrollbars \
  "file://$PWD/午餐套餐海报.html"

通过 --force-device-scale-factor=2,浏览器会以 Retina 双倍像素密度渲染,零网络依赖,瞬间得到 1600×2400 的高保真图片。


二、 字体子集化与「豆腐块□」幽灵 Bug

为了保证海报在任何离线环境下打开都拥有精美的衬线字体(如 Noto Serif SC 与 Cormorant Garamond),我们将字体通过 Google Fonts text= API 抓取字形子集,转为 Base64 内嵌进 HTML。

但这里埋下了两个极其隐蔽的排版陷阱:

1. 改动文案后的「豆腐块」危机

当我们将菜名从原先的通用名改为「泰式打抛猪肉饭」、「夏威夷菠萝牛肉饭」后,网页在浏览器里渲染正常(触发了本地系统字体兜底),但导出的 PNG 却在「泰、式、夏、菠」等新字上全部显示为方块豆腐□

根因:内嵌的 Base64 字体子集只包含老版文案的文字。 解法:编写自动化脚本,每次修改文案后,自动提取 HTML 中全部可见汉字、英文字母与标点符号,利用 fonttoolsbrotli 重新生成最小化的 woff2 子集并热替换到 CSS 中。

2. macOS 宋体 900 缺失「¥」符号的渲染 Bug

在制作价格标签时,标题采用 Songti SC(宋体)搭配 font-weight: 900。我们发现页面上的价格符号 ¥ 莫名其妙地消失了,变成了一片空白

排查发现:macOS 自带的宋体在字重为 900(Heavy)时,其字符映射表中标准半角人民币符号 U+00A5 (¥) 存在字形空白缺失的缺陷。 解决方案:文案中强制使用全角人民币符号 U+FFE5 (¥),或在 CSS 中设置字体回退链优先使用 PingFang SC 渲染金额符号。


三、 OpenCV GrabCut 菜盘抠图与等面积对齐

在午市套餐海报中,需要展示三款主食(打抛饭、番茄肉酱饭、菠萝牛肉饭)的高清盘装实拍图。

直接给实拍照片抠图并排版,会遇到一系列算法与视觉问题:

[实拍带背景菜品] ──► [OpenCV GrabCut 保留盘沿] ──► [几何均值面积等比缩放] ──► [统一暖白平衡调色] ──► [内嵌 WebP]

1. 为什么 AI 抠图模型(rembg / BiRefNet)会失效?

  • rembg(基于 u2net/isnet):会将浅色/米白色的陶瓷盘子误判为背景,抠完只剩下一堆悬空的肉末和米饭,完全失去了餐饮出品的质感。
  • BiRefNet:边缘识别精细,但在 CPU 上处理单张图耗时超过 12 分钟,无法进行快速迭代。

2. 解决方案:OpenCV GrabCut 智能保留整盘

我们改用 OpenCV 的 GrabCut 算法(矩形初始化,边框 Inset 3%)

  • 对于盘子与阴影同色的疑难图片(如灰盘配灰色桌面),先对盘芯做腐蚀运算(Erosion),拟合出精确的椭圆轮廓(cv2.fitEllipse),再等比放大回盘沿,完美裁掉外部多余投影,完整保留陶瓷盘子的边缘光泽。

3. 三张主食图的「等面积对齐」

三张菜品如果简单地以正方形长轴缩放,扁平的椭圆盘子在视觉上会显得极其单薄瘦小。

我们计算了三张盘子的几何均值面积($\text{Geomean} = \sqrt{W \times H}$),按等面积缩放后贴入统一的 860×647 画布底部居中。这样在 CSS 中只需固定 max-width: 206px,三款主食在视觉体量上就达到了完美的平衡。

4. 颜色与方向标准化

  • 白平衡校准:提取每张照片盘子的最高白点,通过色彩矩阵统一映射到暖白基准值 (236, 232, 223),并统一增加 7% 对比度与自然饱和度。
  • PIL EXIF 方向坑:手机拍摄的原图常带有 Orientation: 6(顺时针 90° 旋转),PIL 的 Image.open 默认不会自动应用 EXIF 旋转矩阵。处理前必须显式调用 ImageOps.exif_transpose(im),否则抠出来的菜品全部是倒挂的。

结语

从一行 HTML 标签,到一张可以直接送到印刷厂或挂在商场 LED 巨幕上的高清海报,中间横跨了排版引擎、字体编码、色彩校准与图像算法的诸多细节。

代码赋予设计的,不仅是像素级别的精准控制力,更是一种可复现、可自动化、可规模化交付的工程确定性


🛠️ 项目环境与模型署名

  • 主导架构与排版代码生成:Anthropic Claude Fable 5(桌面端默认模型)
  • 商业海报主题背景生成:xAI Grok Imagine(grok.com/imagine 网页版 + grok-imagine-image-2.0 API)
  • 客户端环境Claude Desktop 桌面端
  • 图像与排版工具链:OpenCV 4 (GrabCut, fitEllipse) + Python Pillow (ImageOps.exif_transpose) + Google Fonts API (fonttools, brotli) + html-to-image + Chrome Headless 截图管线