1
0
Files
PPTX/openspec/changes/archive/2026-03-04-add-image-fit-modes/design.md
lanyuanxiaoyao 19d6661381 feat: 添加图片适配模式支持
- 支持四种图片适配模式:stretch、contain、cover、center
- 支持背景色填充功能(contain 和 center 模式)
- 支持文档级 DPI 配置(metadata.dpi)
- PPTX 渲染器集成 Pillow 实现高质量图片处理
- HTML 渲染器使用 CSS object-fit 实现相同效果
- 添加完整的单元测试、集成测试和端到端测试
- 更新 README 文档和架构文档
- 模块化设计:utils/image_utils.py 图片处理工具模块
- 添加图片配置验证器:validators/image_config.py
- 向后兼容:未指定 fit 时默认使用 stretch 模式
2026-03-04 10:29:21 +08:00

261 lines
9.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 图片适配模式技术设计
## Context
### 当前状态
当前系统图片渲染逻辑非常简单:直接使用 python-pptx 的 `add_picture()` 方法,传入 box 的宽高参数。python-pptx 会自动将图片拉伸到指定尺寸,无法保持宽高比,也无法进行居中或裁剪处理。
```python
# 当前实现 (pptx_renderer.py)
def _render_image(self, slide, elem: ImageElement, base_path):
x, y, w, h = [Inches(v) for v in elem.box]
slide.shapes.add_picture(str(img_path), x, y, width=w, height=h)
```
### 约束条件
- 必须保持向后兼容:未指定 `fit` 参数时,行为与当前一致
- PPTX 渲染使用英寸单位,图片处理需要像素单位
- HTML 预览需要与 PPTX 渲染保持一致的视觉效果
- DPI 配置需要同时影响两个渲染器
### 利益相关者
- 最终用户:需要多样化的图片适配选项
- HTML 预览用户:期望预览效果与最终 PPTX 一致
## Goals / Non-Goals
**Goals:**
- 支持四种图片适配模式stretch、contain、cover、center
- 支持留白区域的背景色填充
- 支持可配置的 DPI 转换
- HTML 预览与 PPTX 渲染效果一致
- 向后兼容现有 YAML 文件
**Non-Goals:**
- 不支持图片的旋转、翻转等变换
- 不支持图片滤镜、水印等高级效果
- 不支持 fill平铺模式
- 不支持自适应 box 尺寸box 必需,不是可选)
## Decisions
### 决策 1: 使用 Pillow 作为图片处理库
**选择理由:**
- Pillow 是 Python 生态中最成熟的图片处理库,从 PIL 发展而来
- 内置 `ImageOps` 模块,直接提供 `contain()``cover()` 方法,与我们的需求完全匹配
- API 简洁直观,社区活跃,文档完善
- 轻量级,专注图片处理,不像 OpenCV 那样重量级
**替代方案考虑:**
| 方案 | 优势 | 劣势 | 结论 |
|------|------|------|------|
| Pillow | API 简洁ImageOps 直接匹配需求 | 需要额外依赖 | ✅ 选择 |
| OpenCV | 功能强大,性能优异 | API 复杂BGR 颜色顺序需转换,过度设计 | ❌ |
| Wand | 功能丰富 | 依赖 ImageMagick安装复杂跨平台问题多 | ❌ |
| 原生实现 | 无额外依赖 | 需要手动实现所有算法,容易出错 | ❌ |
### 决策 2: DPI 作为文档级配置
**选择:**`metadata.dpi` 中配置,默认值 96
**理由:**
- DPI 影响整个文档的所有图片,不应在元素级别配置
- 96 是 Web 标准 DPI与当前 HTML 渲染器一致
- 简洁的配置方式,符合 YAML 声明式风格
**替代方案:** `metadata.image.dpi`(更结构化,但过于复杂)
### 决策 3: PPTX 和 HTML 使用不同的实现方式
**PPTX 实现:**
- 使用 Pillow 处理图片(缩放、裁剪)
- 计算居中位置
- 添加背景色画布(如有需要)
- 使用 python-pptx 添加处理后的图片
**HTML 实现:**
- 使用 CSS `object-fit` 属性
- `stretch``object-fit: fill`
- `contain``object-fit: contain`
- `cover``object-fit: cover`
- `center``object-fit: none` + `object-position: center`
- 背景色使用 CSS `background-color`
**理由:** 两个平台的原生能力不同,使用各自的最佳实践,但确保视觉效果一致
### 决策 4: 图片适配模式算法设计
```
┌─────────────────────────────────────────────────────────────────────┐
│ 图片适配算法流程 │
└─────────────────────────────────────────────────────────────────────┘
输入: img (PIL.Image), box_size (width, height), fit, background
1. stretch 模式
─────────────────────────────────────────────────────────────────
直接使用 img.resize(box_size),不考虑宽高比
2. contain 模式
─────────────────────────────────────────────────────────────────
result = ImageOps.contain(img, box_size)
# result.size <= box_size保持宽高比
if background:
canvas = 创建 box_size 画布,填充背景色
canvas.paste(result, 居中位置)
result = canvas
3. cover 模式
─────────────────────────────────────────────────────────────────
result = ImageOps.cover(img, box_size)
# result.size == box_size保持宽高比裁剪超出部分
4. center 模式
─────────────────────────────────────────────────────────────────
result = img # 不缩放
if img.width > box_size.width or img.height > box_size.height:
result = 裁剪到 box_size居中裁剪
if background:
canvas = 创建 box_size 画布,填充背景色
canvas.paste(result, 居中位置)
result = canvas
输出: result (PIL.Image), display_size, display_position
```
### 决策 6: 模块化设计
```
utils/image_utils.py
├── inches_to_pixels(inches, dpi) -> float
├── pixels_to_inches(pixels, dpi) -> float
├── calculate_contain_size(img_size, box_size) -> tuple
├── calculate_cover_size(img_size, box_size) -> tuple
├── calculate_center_offset(img_size, box_size) -> tuple
└── apply_fit_mode(img, box_size, fit, background) -> PIL.Image
validators/image_config.py (新建)
├── validate_fit_value(fit) -> List[ValidationIssue]
└── validate_fit_box_dependency(elem) -> List[ValidationIssue]
```
**理由:** 单一职责原则,图片处理逻辑与渲染逻辑分离,便于测试和复用
### 决策 5: 模块化设计
```
utils/image_utils.py
├── inches_to_pixels(inches, dpi) -> float
├── pixels_to_inches(pixels, dpi) -> float
├── calculate_contain_size(img_size, box_size) -> tuple
├── calculate_cover_size(img_size, box_size) -> tuple
├── calculate_center_offset(img_size, box_size) -> tuple
└── apply_fit_mode(img, box_size, fit, background) -> PIL.Image
validators/image_config.py (新建)
├── validate_fit_value(fit) -> List[ValidationIssue]
└── validate_background_color(color) -> List[ValidationIssue]
```
**理由:** 单一职责原则,图片处理逻辑与渲染逻辑分离,便于测试和复用
**注意:** 由于 box 参数为必填,不存在"没有指定 box"的情况,因此 `validate_fit_box_dependency` 验证器已移除。
## Risks / Trade-offs
### 风险 1: Pillow 依赖增加
**风险:** 新增外部依赖可能增加安装复杂度
**缓解措施:**
- Pillow 是 Python 生态标准库,安装简单(`pip install pillow`
- 在 pyproject.toml 中不指定版本号,使用最新稳定版
- 在 README 中明确说明依赖变更
### 风险 2: PPTX 和 HTML 渲染效果不完全一致
**风险:** 两个平台实现方式不同,可能在边缘情况下效果有差异
**缓解措施:**
- 核心算法(尺寸计算)使用相同的 Python 函数
- 编写集成测试,对比两种渲染器的输出
- 在文档中说明已知差异(如抗锯齿算法不同)
### 风险 3: 大图片处理性能问题
**风险:** Pillow 处理大图片可能较慢,占用内存
**缓解措施:**
- 仅在需要时才处理图片contain/cover/center 模式)
- stretch 模式直接使用 python-pptx 的原生处理,不经过 Pillow
- 文档中建议用户使用适当尺寸的图片
### 风险 4: DPI 配置不当导致尺寸错误
**风险:** 用户设置的 DPI 与实际使用场景不符,导致图片尺寸不符合预期
**缓解措施:**
- 默认值 96 适用于大多数场景
- 在 README 中说明 DPI 的含义和影响
- 验证器检查 DPI 值是否合理(如 72-300 之间)
## Migration Plan
### 部署步骤
1. **代码变更**
- 添加 Pillow 依赖到 pyproject.toml
- 创建 `utils/image_utils.py`
- 创建 `validators/image_config.py`
- 更新 `core/elements.py` 的 ImageElement
- 更新 `renderers/pptx_renderer.py`
- 更新 `renderers/html_renderer.py`
2. **测试**
- 单元测试image_utils 的各个函数
- 单元测试validators 的图片配置验证
- 集成测试:四种 fit 模式的 PPTX 渲染
- 集成测试:四种 fit 模式的 HTML 渲染
- 端到端测试:完整 YAML 转换流程
3. **文档更新**
- 更新 README.md添加图片适配模式说明
- 更新 README_DEV.md添加架构说明
### 回滚策略
- 如果发现严重问题,可以回退到之前版本
- 向后兼容设计确保未指定 fit 参数的 YAML 文件仍能正常工作
- 回滚后用户只需删除 fit 和 background 参数即可
## Open Questions
### Q1: 是否需要在图片处理失败时提供降级方案?
**决策:** 图片处理失败时抛出 ERROR 级别错误,让用户修复图片问题,不提供降级方案。
**理由:** 保持简单明确,图片问题应该由用户在源头解决,而不是掩盖问题。
### Q2: background 参数是否支持渐变色?
**决策:** background 参数仅支持纯色,不支持渐变色。
**理由:** 简化实现,满足绝大多数使用场景。如有后续需求,可以作为独立功能添加。
### Q3: 是否需要支持图片质量设置?
**决策:** 使用 Pillow 的最高质量重采样算法LANCZOS不向用户暴露配置选项。
**理由:** 演示文稿场景下图片质量优先于处理速度,使用最高质量算法避免用户困惑。