海报生成方案 - Fabric.js 前端渲染
背景
当前方案在 Python 端下载图片并渲染海报,存在以下问题:
- 图片下载耗时长
- 输出静态 PNG,用户无法编辑
- 服务端渲染压力大
新方案概述
Python 端只生成文案和布局参数,输出 fabric.js JSON,前端负责渲染和合成。
┌─────────────┐ JSON ┌─────────────┐ Canvas ┌─────────────┐
│ Python AI │ ─────────▶ │ Frontend │ ──────────▶ │ 用户编辑 │
│ 文案生成 │ │ fabric.js │ │ 导出图片 │
└─────────────┘ └─────────────┘ └─────────────┘
数据流
1. 输入 (用户请求)
{
"category": "民宿",
"name": "山舍云端民宿",
"description": "藏在莫干山的治愈系民宿",
"price": "458元/晚",
"location": "莫干山",
"features": "独立庭院, 手冲咖啡, 山景露台",
"image_url": "https://example.com/image.jpg"
}
2. Python 输出 (Fabric.js JSON)
{
"version": "1.0.0",
"canvas": {
"width": 1080,
"height": 1440,
"backgroundColor": "#FFF5F0"
},
"layout": "hero_bottom",
"theme": "sunset",
"content": {
"title": "去了莫干山才知道...",
"subtitle": "藏在山里的治愈系民宿,推开窗便是云海",
"highlights": ["独立庭院", "手冲咖啡", "山景露台"],
"details": ["管家式服务超贴心", "有机早餐太惊艳"],
"price": "¥458",
"price_suffix": "/晚",
"tags": ["莫干山民宿", "周末度假"]
},
"objects": [
{
"id": "background_image",
"type": "image",
"src": "https://example.com/image.jpg",
"left": 0,
"top": 0,
"width": 1080,
"height": 1440,
"scaleX": 1,
"scaleY": 1
},
{
"id": "gradient_overlay",
"type": "rect",
"left": 0,
"top": 800,
"width": 1080,
"height": 640,
"fill": {
"type": "linear",
"coords": {"x1": 0, "y1": 0, "x2": 0, "y2": 640},
"colorStops": [
{"offset": 0, "color": "rgba(89,66,52,0)"},
{"offset": 0.5, "color": "rgba(89,66,52,0.8)"},
{"offset": 1, "color": "rgba(89,66,52,1)"}
]
}
},
{
"id": "title",
"type": "textbox",
"text": "去了莫干山才知道...",
"left": 48,
"top": 880,
"width": 984,
"fontSize": 80,
"fontFamily": "PingFang SC",
"fontWeight": "bold",
"fill": "#FFFFFF",
"shadow": "rgba(0,0,0,0.3) 2px 2px 4px"
},
{
"id": "subtitle",
"type": "textbox",
"text": "藏在山里的治愈系民宿,推开窗便是云海",
"left": 48,
"top": 980,
"width": 984,
"fontSize": 32,
"fontFamily": "PingFang SC",
"fill": "rgba(255,255,255,0.85)"
},
{
"id": "price_bg",
"type": "rect",
"left": 36,
"top": 1280,
"width": 180,
"height": 60,
"rx": 12,
"ry": 12,
"fill": "rgba(255,255,255,0.2)"
},
{
"id": "price",
"type": "text",
"text": "¥458",
"left": 48,
"top": 1290,
"fontSize": 48,
"fontFamily": "PingFang SC",
"fontWeight": "bold",
"fill": "#FFFFFF"
},
{
"id": "price_suffix",
"type": "text",
"text": "/晚",
"left": 160,
"top": 1308,
"fontSize": 24,
"fontFamily": "PingFang SC",
"fill": "rgba(255,255,255,0.8)"
}
]
}
架构设计
Python 端
domain/aigc/engines/
├── poster_smart_v1.py # 当前:AI文案 + Python渲染
└── poster_smart_v2.py # 新增:AI文案 + Fabric JSON输出
PosterSmartEngineV2 职责
- AI 文案生成 - 调用 LLM 生成标题、副标题、亮点等
- 布局推荐 - 根据内容类型推荐布局
- 主题推荐 - 根据内容类型推荐配色
- Fabric JSON 生成 - 输出标准 fabric.js 格式
class PosterSmartEngineV2(BaseEngine):
"""智能海报引擎 V2 - 输出 Fabric.js JSON"""
async def execute(self, params: dict) -> EngineResult:
# 1. AI 生成文案
content = await self._generate_content(params)
# 2. 选择布局和主题
layout = content.get('suggested_layout') or self._recommend_layout(params)
theme = content.get('suggested_theme') or self._recommend_theme(params)
# 3. 生成 Fabric JSON
fabric_json = self._generate_fabric_json(content, layout, theme, params)
return EngineResult(
success=True,
data={
'fabric_json': fabric_json,
'layout': layout,
'theme': theme,
'content': content
}
)
前端
components/
├── PosterEditor/
│ ├── index.tsx # 主组件
│ ├── FabricCanvas.tsx # fabric.js 画布
│ ├── LayerPanel.tsx # 图层面板
│ ├── PropertyPanel.tsx # 属性面板
│ └── ExportButton.tsx # 导出按钮
前端职责
- 加载 Fabric JSON - 解析 Python 返回的 JSON
- 渲染画布 - fabric.js 渲染所有对象
- 用户编辑 - 支持移动、缩放、修改文字
- 导出图片 - 导出 PNG/JPG
布局模板
5 种布局的 Fabric 结构
| 布局 |
结构 |
| hero_bottom |
图片全屏 + 底部渐变 + 文字叠加 |
| overlay_center |
图片全屏 + 暗化遮罩 + 居中文字 |
| overlay_bottom |
图片上半 + 毛玻璃卡片底部 |
| split_vertical |
左侧渐变/图片 + 右侧文字 |
| card_float |
图片全屏 + 悬浮白色卡片 |
布局模板文件
poster_v2/templates/
├── hero_bottom.json
├── overlay_center.json
├── overlay_bottom.json
├── split_vertical.json
└── card_float.json
主题配置
THEMES = {
"sunset": {
"primary": "#594234",
"secondary": "#FFF5F0",
"accent": "#D4A574",
"text": "#FFFFFF",
"text_dark": "#333333",
"gradient": ["#FFE4D6", "#D4A574"]
},
# ... 其他主题
}
API 设计
请求
POST /api/v2/aigc/execute
Content-Type: application/json
{
"engine": "poster_smart_v2",
"params": {
"category": "民宿",
"name": "山舍云端民宿",
"description": "藏在莫干山的治愈系民宿",
"price": "458元/晚",
"image_url": "https://example.com/image.jpg"
}
}
响应 (双输出)
{
"success": true,
"data": {
"preview_base64": "data:image/png;base64,...",
"fabric_json": {
"version": "1.0.0",
"canvas": { "width": 1080, "height": 1440 },
"objects": [...]
},
"layout": "hero_bottom",
"theme": "sunset",
"content": {
"title": "去了莫干山才知道...",
"subtitle": "...",
"highlights": [...],
"price": "¥458"
}
}
}
输出说明
| 字段 |
说明 |
preview_base64 |
预览 PNG - 无底图,使用主题渐变色背景,快速预览文案排版 |
fabric_json |
Fabric JSON - 完整的编辑数据,含图片占位,前端加载后可编辑 |
layout |
推荐布局 |
theme |
推荐主题 |
content |
AI 生成的文案内容 |
实现步骤
Phase 1: Python 端 (2-3天)
- 创建
poster_smart_v2.py 引擎
- 实现 5 种布局的 Fabric JSON 生成器
- 复用现有的 AI 文案生成逻辑
- 添加 API 路由
Phase 2: 前端 (3-5天)
- 集成 fabric.js
- 实现 FabricCanvas 组件
- 实现图层面板
- 实现属性编辑
- 实现导出功能
Phase 3: 优化 (1-2天)
- 字体加载优化
- 图片懒加载
- 撤销/重做
- 模板保存
兼容性
- 保留
poster_smart_v1 引擎,支持服务端渲染
- 新增
poster_smart_v2 引擎,支持 Fabric JSON 输出
- 前端根据需求选择调用哪个引擎
优势
| 方面 |
收益 |
| 性能 |
服务端不再处理图片,响应更快 |
| 可编辑 |
用户可自由调整文字、位置、颜色 |
| 灵活性 |
前端可扩展更多编辑功能 |
| 复用性 |
Fabric JSON 可保存为模板复用 |
风险与对策
| 风险 |
对策 |
| 字体不一致 |
前端加载相同字体文件 |
| 跨域图片 |
配置 CORS 或使用代理 |
| 复杂效果 |
渐变、阴影用 fabric.js 内置功能 |