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 模式
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-03-04
|
||||
@@ -0,0 +1,260 @@
|
||||
# 图片适配模式技术设计
|
||||
|
||||
## 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),不向用户暴露配置选项。
|
||||
|
||||
**理由:** 演示文稿场景下图片质量优先于处理速度,使用最高质量算法避免用户困惑。
|
||||
@@ -0,0 +1,44 @@
|
||||
# 图片适配模式支持
|
||||
|
||||
## Why
|
||||
|
||||
当前图片元素渲染仅支持简单的拉伸模式,图片会被强制缩放到 box 指定的尺寸,导致图片变形或宽高比失真。实际使用中,用户需要保持图片宽高比、居中显示、填充裁剪等多种适配模式,以满足不同场景的视觉需求。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 新增图片 `fit` 参数,支持四种适配模式:`stretch`(拉伸)、`contain`(包含)、`cover`(覆盖)、`center`(居中)
|
||||
- 新增图片 `background` 参数,支持指定留白区域的填充颜色(默认透明)
|
||||
- 新增文档级 `dpi` 配置(`metadata.dpi`),用于像素与英寸的转换,默认值为 96
|
||||
- 引入 Pillow 库进行图片处理,利用其 ImageOps 模块实现各种适配模式
|
||||
- 保持向后兼容:未指定 `fit` 时默认使用 `stretch` 模式(当前行为)
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `image-fit-modes`: 图片元素适配模式处理,支持 stretch、contain、cover、center 四种模式,以及背景色填充和 DPI 配置
|
||||
|
||||
### Modified Capabilities
|
||||
- `element-rendering`: 扩展图片元素渲染能力,新增 `fit` 和 `background` 参数支持
|
||||
- `html-rendering`: 同步扩展 HTML 预览的图片渲染能力,支持 `fit` 和 `background` 参数,使用 CSS object-fit 实现
|
||||
|
||||
## Impact
|
||||
|
||||
### 依赖变更
|
||||
- 新增 Pillow 依赖(不指定版本号,使用最新版)
|
||||
|
||||
### 代码变更
|
||||
- `core/elements.py`: ImageElement 新增 `fit` 和 `background` 字段
|
||||
- `renderers/pptx_renderer.py`: 重写 `_render_image()` 方法,集成 Pillow 图片处理
|
||||
- `renderers/html_renderer.py`: 更新 `_render_image()` 方法,使用 CSS object-fit 实现适配模式
|
||||
- `validators/`: 新增图片参数验证器(fit 值校验、background 颜色校验)
|
||||
- `utils/image_utils.py`: 新增图片处理工具模块(像素转换、尺寸计算、居中定位)
|
||||
|
||||
### API 变更
|
||||
- YAML 语法扩展:
|
||||
- `metadata` 层级新增可选的 `dpi` 字段
|
||||
- 图片元素新增可选的 `fit` 字段
|
||||
- 图片元素新增可选的 `background` 字段
|
||||
|
||||
### 文档变更
|
||||
- `README.md`: 新增图片适配模式使用说明和示例
|
||||
- `README_DEV.md`: 新增图片处理架构说明
|
||||
@@ -0,0 +1,76 @@
|
||||
# Element Rendering - Delta Spec
|
||||
|
||||
本 spec 是对 `openspec/specs/element-rendering/spec.md` 的增量修改。
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 系统必须支持图片元素渲染
|
||||
|
||||
系统 SHALL 将 YAML 中定义的图片元素渲染为 PPTX 图片对象,支持 `fit` 和 `background` 参数控制图片适配模式。
|
||||
|
||||
#### Scenario: 渲染本地图片
|
||||
|
||||
- **WHEN** 元素定义为 `{type: image, src: "images/logo.png", box: [2, 3, 4, 3]}`
|
||||
- **THEN** 系统从指定路径加载图片,在 (2, 3) 位置渲染为 4×3 英寸大小
|
||||
- **AND** 使用默认的 `stretch` 模式(向后兼容)
|
||||
|
||||
#### Scenario: 使用 fit 模式渲染图片
|
||||
|
||||
- **WHEN** 元素定义为 `{type: image, src: "photo.jpg", box: [1, 1, 4, 3], fit: contain}`
|
||||
- **THEN** 系统使用 `contain` 模式渲染图片
|
||||
- **AND** 保持图片宽高比,完整显示在 box 内
|
||||
|
||||
#### Scenario: 使用背景色渲染图片
|
||||
|
||||
- **WHEN** 元素定义为 `{type: image, src: "photo.png", box: [1, 1, 4, 3], fit: contain, background: "#f0f0f0"}`
|
||||
- **THEN** 系统使用 `contain` 模式渲染图片
|
||||
- **AND** 用 #f0f0f0 颜色填充留白区域
|
||||
|
||||
#### Scenario: 图片文件不存在时报错
|
||||
|
||||
- **WHEN** 图片 src 指向不存在的文件路径
|
||||
- **THEN** 系统抛出错误,明确指出图片文件未找到
|
||||
|
||||
#### Scenario: 图片格式不支持时报错
|
||||
|
||||
- **WHEN** 图片文件格式不被 Pillow 支持
|
||||
- **THEN** 系统抛出错误,提示图片格式不支持,并列出支持的格式
|
||||
|
||||
#### Scenario: 图片处理失败时报错
|
||||
|
||||
- **WHEN** Pillow 处理图片时发生异常(如文件损坏)
|
||||
- **THEN** 系统抛出 ERROR 级别错误,不降级到其他模式
|
||||
|
||||
#### Scenario: 相对路径处理
|
||||
|
||||
- **WHEN** 图片 src 使用相对路径 `"assets/images/logo.png"`
|
||||
- **THEN** 系统基于演示文稿文件所在目录解析相对路径
|
||||
|
||||
#### Scenario: 使用 DPI 配置渲染图片
|
||||
|
||||
- **WHEN** metadata 定义了 `dpi: 120` 且图片需要处理
|
||||
- **THEN** 系统使用该 DPI 值进行像素与英寸的转换
|
||||
|
||||
#### Scenario: fit 参数值无效时报错
|
||||
|
||||
- **WHEN** `fit` 参数值不是 stretch、contain、cover、center 之一
|
||||
- **THEN** 系统抛出 ERROR,并列出有效值
|
||||
|
||||
#### Scenario: background 参数颜色格式无效时报错
|
||||
|
||||
- **WHEN** `background` 值不是有效的颜色格式
|
||||
- **THEN** 系统抛出 ERROR,提示颜色格式应为 #RRGGBB 或 #RGB
|
||||
|
||||
### Requirement: 图片元素的 box 参数必须存在
|
||||
|
||||
系统 SHALL 要求图片元素必须包含 `box` 参数,否则验证失败。
|
||||
|
||||
#### Scenario: 缺少 box 参数时报错
|
||||
|
||||
- **WHEN** 图片元素未定义 `box` 参数
|
||||
- **THEN** 系统抛出 ERROR,提示 box 参数为必需
|
||||
|
||||
#### Scenario: box 参数格式正确
|
||||
|
||||
- **WHEN** 图片元素定义了 `box: [1, 2, 4, 3]`
|
||||
- **THEN** 系统验证通过,将 box 用于图片定位和尺寸
|
||||
@@ -0,0 +1,83 @@
|
||||
# HTML Rendering - Delta Spec
|
||||
|
||||
本 spec 是对 `openspec/specs/html-rendering/spec.md` 的增量修改。
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: 系统必须渲染图片元素
|
||||
|
||||
系统 SHALL 将 YAML 中的图片元素转换为 HTML `<img>` 标签,支持 `fit` 和 `background` 参数,使用 CSS object-fit 实现适配模式。
|
||||
|
||||
#### Scenario: 渲染本地图片
|
||||
|
||||
- **WHEN** 元素定义为 `{type: image, src: "images/logo.png", box: [2, 3, 4, 3]}`
|
||||
- **THEN** 系统生成 `<img>` 标签,src 为图片的文件路径,位置为 (192px, 288px),尺寸为 384x288 像素
|
||||
- **AND** 使用默认的 CSS 样式 `object-fit: fill`(等同于 stretch 模式)
|
||||
|
||||
#### Scenario: 使用 fit 模式渲染图片
|
||||
|
||||
- **WHEN** 元素定义为 `{type: image, src: "photo.jpg", box: [1, 1, 4, 3], fit: contain}`
|
||||
- **THEN** 系统应用 CSS `object-fit: contain`
|
||||
- **AND** 保持图片宽高比,完整显示在 box 内
|
||||
|
||||
#### Scenario: fit 模式与 CSS object-fit 映射
|
||||
|
||||
- **WHEN** `fit` 参数为 `stretch`
|
||||
- **THEN** 系统应用 CSS `object-fit: fill`
|
||||
|
||||
- **WHEN** `fit` 参数为 `contain`
|
||||
- **THEN** 系统应用 CSS `object-fit: contain`
|
||||
|
||||
- **WHEN** `fit` 参数为 `cover`
|
||||
- **THEN** 系统应用 CSS `object-fit: cover`
|
||||
|
||||
- **WHEN** `fit` 参数为 `center`
|
||||
- **THEN** 系统应用 CSS `object-fit: none` 和 `object-position: center`
|
||||
|
||||
#### Scenario: 使用背景色渲染图片
|
||||
|
||||
- **WHEN** 元素定义为 `{type: image, src: "photo.png", box: [1, 1, 4, 3], fit: contain, background: "#f0f0f0"}`
|
||||
- **THEN** 系统为图片容器添加 CSS `background-color: #f0f0f0`
|
||||
- **AND** 应用 CSS `object-fit: contain`
|
||||
|
||||
#### Scenario: 图片容器支持背景色
|
||||
|
||||
- **WHEN** 图片指定了 `background` 参数
|
||||
- **THEN** 系统创建包装容器,应用背景色到容器
|
||||
- **AND** 图片在容器内使用 object-fit 定位
|
||||
|
||||
#### Scenario: 处理相对路径
|
||||
|
||||
- **WHEN** 图片 src 使用相对路径 `"assets/logo.png"`
|
||||
- **THEN** 系统基于 YAML 文件所在目录解析相对路径
|
||||
|
||||
#### Scenario: 图片不存在时显示占位符
|
||||
|
||||
- **WHEN** 图片文件不存在
|
||||
- **THEN** 系统显示占位符或错误提示,而不是崩溃
|
||||
|
||||
#### Scenario: 使用 DPI 配置渲染图片
|
||||
|
||||
- **WHEN** metadata 定义了 `dpi: 120`
|
||||
- **THEN** 系统使用该 DPI 值进行英寸到像素的转换
|
||||
- **AND** box: [1, 1, 4, 3] 转换为 CSS: left: 120px; top: 120px; width: 480px; height: 360px
|
||||
|
||||
#### Scenario: HTML 渲染与 PPTX 渲染效果一致
|
||||
|
||||
- **WHEN** 同一图片元素使用相同的 fit 和 background 参数
|
||||
- **THEN** HTML 预览和 PPTX 输出的视觉效果应保持一致
|
||||
- **AND** 图片位置、尺寸、适配方式相同
|
||||
|
||||
### Requirement: 图片元素的 box 参数必须存在
|
||||
|
||||
系统 SHALL 要求图片元素必须包含 `box` 参数,否则验证失败。
|
||||
|
||||
#### Scenario: 缺少 box 参数时报错
|
||||
|
||||
- **WHEN** 图片元素未定义 `box` 参数
|
||||
- **THEN** 系统抛出 ERROR,提示 box 参数为必需
|
||||
|
||||
#### Scenario: box 参数转换为像素
|
||||
|
||||
- **WHEN** 图片元素定义了 `box: [1, 2, 4, 3]` 且 DPI 为 96
|
||||
- **THEN** 系统转换为 CSS:left: 96px; top: 192px; width: 384px; height: 288px
|
||||
@@ -0,0 +1,201 @@
|
||||
# Image Fit Modes
|
||||
|
||||
## Purpose
|
||||
|
||||
图片适配模式能力为图片元素提供多种适配策略,允许用户控制图片在指定区域内的显示方式,包括拉伸、保持比例、填充裁剪和居中显示。同时支持 DPI 配置和背景色填充,满足不同场景的视觉需求。
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: 系统必须支持图片 fit 参数
|
||||
|
||||
系统 SHALL 支持图片元素的 `fit` 参数,允许用户指定图片适配模式。
|
||||
|
||||
#### Scenario: fit 参数支持四种模式
|
||||
|
||||
- **WHEN** 图片元素定义了 `fit` 参数
|
||||
- **THEN** 系统支持 `stretch`、`contain`、`cover`、`center` 四种模式
|
||||
|
||||
#### Scenario: fit 参数默认值为 stretch
|
||||
|
||||
- **WHEN** 图片元素未指定 `fit` 参数
|
||||
- **THEN** 系统使用 `stretch` 模式(向后兼容)
|
||||
|
||||
#### Scenario: fit 参数值无效时报错
|
||||
|
||||
- **WHEN** `fit` 参数值不是四种有效模式之一
|
||||
- **THEN** 系统抛出 ERROR,并列出有效值(stretch、contain、cover、center)
|
||||
|
||||
### Requirement: 系统必须支持 stretch 模式
|
||||
|
||||
系统 SHALL 在 `stretch` 模式下将图片强制缩放到 box 指定的尺寸,不考虑宽高比。
|
||||
|
||||
#### Scenario: stretch 模式拉伸图片
|
||||
|
||||
- **WHEN** 图片元素定义了 `fit: stretch` 或未指定 `fit`
|
||||
- **THEN** 系统将图片拉伸到 box 的宽高尺寸
|
||||
- **AND** 图片可能变形
|
||||
|
||||
#### Scenario: stretch 模式不考虑背景色
|
||||
|
||||
- **WHEN** 图片使用 `fit: stretch` 并指定了 `background`
|
||||
- **THEN** 背景色参数被忽略(无留白区域)
|
||||
|
||||
### Requirement: 系统必须支持 contain 模式
|
||||
|
||||
系统 SHALL 在 `contain` 模式下保持图片宽高比,完整显示图片在 box 内,可能有留白。
|
||||
|
||||
#### Scenario: contain 模式保持宽高比
|
||||
|
||||
- **WHEN** 图片元素定义了 `fit: contain`
|
||||
- **THEN** 系统缩放图片使其完整显示在 box 内
|
||||
- **AND** 保持原始宽高比
|
||||
- **AND** 图片尺寸不超过 box 尺寸
|
||||
|
||||
#### Scenario: contain 模式图片居中
|
||||
|
||||
- **WHEN** 图片使用 `fit: contain` 且小于 box 尺寸
|
||||
- **THEN** 系统将图片居中显示在 box 内
|
||||
|
||||
#### Scenario: contain 模式支持背景色
|
||||
|
||||
- **WHEN** 图片使用 `fit: contain` 并指定了 `background`
|
||||
- **THEN** 系统用指定颜色填充 box 内的留白区域
|
||||
|
||||
#### Scenario: contain 模式图片比 box 大
|
||||
|
||||
- **WHEN** 图片原始尺寸大于 box 尺寸
|
||||
- **THEN** 系统等比缩小图片使其完整显示在 box 内
|
||||
|
||||
#### Scenario: contain 模式图片比 box 小
|
||||
|
||||
- **WHEN** 图片原始尺寸小于 box 尺寸
|
||||
- **THEN** 系统保持原始尺寸,居中显示在 box 内
|
||||
|
||||
### Requirement: 系统必须支持 cover 模式
|
||||
|
||||
系统 SHALL 在 `cover` 模式下保持图片宽高比,填充整个 box,裁剪超出部分。
|
||||
|
||||
#### Scenario: cover 模式保持宽高比
|
||||
|
||||
- **WHEN** 图片元素定义了 `fit: cover`
|
||||
- **THEN** 系统缩放图片使其填满 box
|
||||
- **AND** 保持原始宽高比
|
||||
- **AND** 裁剪超出 box 的部分
|
||||
|
||||
#### Scenario: cover 模式图片居中裁剪
|
||||
|
||||
- **WHEN** 图片使用 `fit: cover` 且需要裁剪
|
||||
- **THEN** 系统从图片中心进行裁剪
|
||||
|
||||
#### Scenario: cover 模式不考虑背景色
|
||||
|
||||
- **WHEN** 图片使用 `fit: cover` 并指定了 `background`
|
||||
- **THEN** 背景色参数被忽略(无留白区域)
|
||||
|
||||
#### Scenario: cover 模式图片比 box 大
|
||||
|
||||
- **WHEN** 图片原始尺寸大于 box 尺寸
|
||||
- **THEN** 系统等比缩小图片并裁剪超出部分
|
||||
|
||||
#### Scenario: cover 模式图片比 box 小
|
||||
|
||||
- **WHEN** 图片原始尺寸小于 box 尺寸
|
||||
- **THEN** 系统等比放大图片并裁剪超出部分
|
||||
|
||||
### Requirement: 系统必须支持 center 模式
|
||||
|
||||
系统 SHALL 在 `center` 模式下按原始尺寸居中显示图片,不缩放,超出 box 的部分被裁剪。
|
||||
|
||||
#### Scenario: center 模式不缩放图片
|
||||
|
||||
- **WHEN** 图片元素定义了 `fit: center`
|
||||
- **THEN** 系统保持图片原始尺寸,不进行缩放
|
||||
|
||||
#### Scenario: center 模式图片居中
|
||||
|
||||
- **WHEN** 图片使用 `fit: center`
|
||||
- **THEN** 系统将图片居中显示在 box 内
|
||||
|
||||
#### Scenario: center 模式裁剪超出部分
|
||||
|
||||
- **WHEN** 图片原始尺寸大于 box 尺寸
|
||||
- **THEN** 系统裁剪超出 box 的部分(从中心裁剪)
|
||||
|
||||
#### Scenario: center 模式支持背景色
|
||||
|
||||
- **WHEN** 图片使用 `fit: center` 并指定了 `background`
|
||||
- **THEN** 系统用指定颜色填充 box 内的留白区域
|
||||
|
||||
### Requirement: 系统必须支持 background 参数
|
||||
|
||||
系统 SHALL 支持图片元素的 `background` 参数,允许用户指定留白区域的填充颜色。
|
||||
|
||||
#### Scenario: background 参数默认透明
|
||||
|
||||
- **WHEN** 图片元素未指定 `background` 参数
|
||||
- **THEN** 留白区域保持透明
|
||||
|
||||
#### Scenario: background 参数支持纯色
|
||||
|
||||
- **WHEN** 图片元素指定了 `background: "#ff0000"`
|
||||
- **THEN** 系统使用指定颜色填充留白区域
|
||||
|
||||
#### Scenario: background 参数不支持渐变
|
||||
|
||||
- **WHEN** 图片元素指定了渐变色(如 `"linear-gradient(...)"`)
|
||||
- **THEN** 系统抛出 ERROR,提示仅支持纯色
|
||||
|
||||
#### Scenario: background 参数颜色格式验证
|
||||
|
||||
- **WHEN** `background` 值不是有效的颜色格式
|
||||
- **THEN** 系统抛出 ERROR,提示颜色格式应为 #RRGGBB 或 #RGB
|
||||
|
||||
### Requirement: 系统必须支持文档级 DPI 配置
|
||||
|
||||
系统 SHALL 支持在 `metadata.dpi` 中配置 DPI 值,用于像素与英寸的转换。
|
||||
|
||||
#### Scenario: DPI 默认值为 96
|
||||
|
||||
- **WHEN** metadata 未指定 `dpi` 参数
|
||||
- **THEN** 系统使用默认值 96
|
||||
|
||||
#### Scenario: DPI 配置影响所有图片
|
||||
|
||||
- **WHEN** metadata 指定了 `dpi: 120`
|
||||
- **THEN** 系统使用该值进行所有图片的像素与英寸转换
|
||||
|
||||
#### Scenario: DPI 值验证
|
||||
|
||||
- **WHEN** `dpi` 值超出合理范围(如小于 72 或大于 300)
|
||||
- **THEN** 系统发出 WARNING,提示 DPI 值可能不合适
|
||||
|
||||
### Requirement: 系统必须在图片处理失败时抛出错误
|
||||
|
||||
系统 SHALL 在图片处理失败时抛出 ERROR 级别错误,不提供降级方案。
|
||||
|
||||
#### Scenario: 损坏的图片文件
|
||||
|
||||
- **WHEN** Pillow 无法读取图片文件(文件损坏或格式不支持)
|
||||
- **THEN** 系统抛出 ERROR,明确指出图片文件问题
|
||||
- **AND** 不降级到其他模式
|
||||
|
||||
#### Scenario: 图片处理异常
|
||||
|
||||
- **WHEN** Pillow 处理图片时发生异常(如内存不足)
|
||||
- **THEN** 系统抛出 ERROR,包含异常信息
|
||||
- **AND** 不降级到其他模式
|
||||
|
||||
### Requirement: 系统必须使用最高质量的图片处理算法
|
||||
|
||||
系统 SHALL 使用 Pillow 的最高质量重采样算法(LANCZOS)进行图片缩放。
|
||||
|
||||
#### Scenario: 图片缩放使用 LANCZOS
|
||||
|
||||
- **WHEN** 系统需要缩放图片(contain、cover 模式)
|
||||
- **THEN** 使用 Pillow 的 LANCZOS 重采样算法
|
||||
- **AND** 不向用户暴露质量配置选项
|
||||
|
||||
#### Scenario: 图片裁剪保持质量
|
||||
|
||||
- **WHEN** 系统需要裁剪图片(cover、center 模式)
|
||||
- **THEN** 裁剪操作不损失图片质量
|
||||
114
openspec/changes/archive/2026-03-04-add-image-fit-modes/tasks.md
Normal file
114
openspec/changes/archive/2026-03-04-add-image-fit-modes/tasks.md
Normal file
@@ -0,0 +1,114 @@
|
||||
# 图片适配模式实现任务清单
|
||||
|
||||
## 1. 依赖配置
|
||||
|
||||
- [x] 1.1 在 pyproject.toml 中添加 Pillow 依赖(不指定版本号)
|
||||
- [x] 1.2 运行 `uv sync` 安装新增依赖
|
||||
|
||||
## 2. 核心元素扩展
|
||||
|
||||
- [x] 2.1 更新 `core/elements.py` 的 ImageElement 类,添加 `fit` 字段(可选,默认 None)
|
||||
- [x] 2.2 更新 `core/elements.py` 的 ImageElement 类,添加 `background` 字段(可选,默认 None)
|
||||
- [x] 2.3 更新 `core/elements.py` 的 ImageElement.__post_init__,验证 box 参数为必需
|
||||
- [x] 2.4 更新 `core/elements.py` 的 ImageElement.validate 方法,添加 fit 和 background 验证
|
||||
|
||||
## 3. 图片处理工具模块
|
||||
|
||||
- [x] 3.1 创建 `utils/image_utils.py` 模块
|
||||
- [x] 3.2 实现 `inches_to_pixels(inches, dpi) -> float` 函数
|
||||
- [x] 3.3 实现 `pixels_to_inches(pixels, dpi) -> float` 函数
|
||||
- [x] 3.4 实现 `apply_fit_mode(img, box_size, fit, background) -> PIL.Image` 函数,支持四种模式
|
||||
- [x] 3.5 实现 `create_canvas_with_background(size, background_color) -> PIL.Image` 函数
|
||||
- [x] 3.6 在 `apply_fit_mode` 中使用 Pillow.ImageOps.contain 实现 contain 模式
|
||||
- [x] 3.7 在 `apply_fit_mode` 中使用 Pillow.ImageOps.cover 实现 cover 模式
|
||||
- [x] 3.8 在 `apply_fit_mode` 中实现 center 模式(不缩放,居中裁剪)
|
||||
- [x] 3.9 在 `apply_fit_mode` 中使用 LANCZOS 重采样算法保证图片质量
|
||||
|
||||
## 4. 验证器
|
||||
|
||||
- [x] 4.1 创建 `validators/image_config.py` 模块
|
||||
- [x] 4.2 实现 `validate_fit_value(fit) -> List[ValidationIssue]` 函数,验证 fit 为有效值
|
||||
- [x] 4.3 实现 `validate_background_color(color) -> List[ValidationIssue]` 函数,验证颜色格式
|
||||
- [x] 4.4 实现 `validate_dpi_value(dpi) -> List[ValidationIssue]` 函数,验证 DPI 在合理范围
|
||||
- [x] 4.5 在主验证流程中集成图片配置验证器
|
||||
|
||||
## 5. PPTX 渲染器更新
|
||||
|
||||
- [x] 5.1 更新 `renderers/pptx_renderer.py`,导入 Pillow 和 image_utils
|
||||
- [x] 5.2 重写 `_render_image` 方法,读取 metadata.dpi 配置
|
||||
- [x] 5.3 在 `_render_image` 中实现 stretch 模式(直接使用 python-pptx)
|
||||
- [x] 5.4 在 `_render_image` 中实现 contain 模式(使用 Pillow 处理)
|
||||
- [x] 5.5 在 `_render_image` 中实现 cover 模式(使用 Pillow 处理)
|
||||
- [x] 5.6 在 `_render_image` 中实现 center 模式(使用 Pillow 处理)
|
||||
- [x] 5.7 在 `_render_image` 中实现背景色填充(创建画布并粘贴图片)
|
||||
- [x] 5.8 在 `_render_image` 中添加图片处理失败的错误处理(抛出 ERROR)
|
||||
- [x] 5.9 更新 PptxGenerator 类,传递 dpi 参数到渲染方法
|
||||
|
||||
## 6. HTML 渲染器更新
|
||||
|
||||
- [x] 6.1 更新 `renderers/html_renderer.py` 的 `_render_image` 方法
|
||||
- [x] 6.2 实现 fit 模式到 CSS object-fit 的映射(stretch->fill, contain->contain, cover->cover, center->none)
|
||||
- [x] 6.3 在 HTML 渲染中添加 object-position: center 样式(center 模式)
|
||||
- [x] 6.4 实现背景色支持(创建包装容器,应用 background-color)
|
||||
- [x] 6.5 更新 HTML 渲染器读取 metadata.dpi 配置
|
||||
- [x] 6.6 确保 HTML 和 PPTX 渲染效果一致
|
||||
|
||||
## 7. 加载器和配置支持
|
||||
|
||||
- [x] 7.1 更新 `loaders/yaml_loader.py`,解析 metadata.dpi 配置
|
||||
- [x] 7.2 将 dpi 配置传递到 Presentation 对象
|
||||
- [x] 7.3 在 PptxGenerator 和 HtmlRenderer 中访问 dpi 配置
|
||||
|
||||
## 8. 单元测试
|
||||
|
||||
- [x] 8.1 创建 `tests/unit/test_image_utils.py`
|
||||
- [x] 8.2 测试 `inches_to_pixels` 和 `pixels_to_inches` 函数
|
||||
- [x] 8.3 测试 `apply_fit_mode` 函数的四种模式
|
||||
- [x] 8.4 测试 `create_canvas_with_background` 函数
|
||||
- [x] 8.5 创建 `tests/unit/test_validators/test_image_config.py`
|
||||
- [x] 8.6 测试 `validate_fit_value` 函数(有效值和无效值)
|
||||
- [x] 8.7 测试 `validate_background_color` 函数(有效颜色和无效颜色)
|
||||
- [x] 8.8 测试 `validate_dpi_value` 函数(合理范围和超出范围)
|
||||
- [x] 8.9 更新 `tests/unit/test_elements.py`,测试 ImageElement 新字段
|
||||
|
||||
## 9. 集成测试
|
||||
|
||||
- [x] 9.1 创建 `tests/integration/test_image_fit_modes.py`
|
||||
- [x] 9.2 测试 stretch 模式的 PPTX 渲染
|
||||
- [x] 9.3 测试 contain 模式的 PPTX 渲染(图片比 box 大)
|
||||
- [x] 9.4 测试 contain 模式的 PPTX 渲染(图片比 box 小)
|
||||
- [x] 9.5 测试 contain 模式的 PPTX 渲染(带背景色)
|
||||
- [x] 9.6 测试 cover 模式的 PPTX 渲染(图片比 box 大)
|
||||
- [x] 9.7 测试 cover 模式的 PPTX 渲染(图片比 box 小)
|
||||
- [x] 9.8 测试 center 模式的 PPTX 渲染(带背景色)
|
||||
- [x] 9.9 测试不同 DPI 配置的渲染结果
|
||||
- [x] 9.10 测试图片处理失败时的错误处理
|
||||
- [x] 9.11 测试 HTML 渲染器的四种 fit 模式
|
||||
- [x] 9.12 测试 HTML 渲染器的背景色支持
|
||||
- [x] 9.13 对比 HTML 和 PPTX 渲染效果的一致性
|
||||
|
||||
## 10. 端到端测试
|
||||
|
||||
- [x] 10.1 创建测试 YAML 文件,包含所有 fit 模式
|
||||
- [x] 10.2 创建测试 YAML 文件,包含背景色配置
|
||||
- [x] 10.3 创建测试 YAML 文件,包含 DPI 配置
|
||||
- [x] 10.4 创建测试 YAML 文件,包含无效参数(测试验证)
|
||||
- [x] 10.5 运行 `check` 命令,验证错误检测
|
||||
- [x] 10.6 运行 `convert` 命令,验证 PPTX 生成
|
||||
- [x] 10.7 运行 `preview` 命令,验证 HTML 预览
|
||||
|
||||
## 11. 文档更新
|
||||
|
||||
- [x] 11.1 更新 `README.md`,添加图片适配模式章节
|
||||
- [x] 11.2 在 README.md 中添加 fit 参数说明和示例
|
||||
- [x] 11.3 在 README.md 中添加 background 参数说明和示例
|
||||
- [x] 11.4 在 README.md 中添加 metadata.dpi 配置说明
|
||||
- [x] 11.5 更新 `README_DEV.md`,添加图片处理架构说明
|
||||
- [x] 11.6 在 README_DEV.md 中说明 Pillow 依赖和用途
|
||||
- [x] 11.7 在 README_DEV.md 中说明 image_utils 模块的设计
|
||||
|
||||
## 12. 向后兼容性验证
|
||||
|
||||
- [x] 12.1 测试现有 YAML 文件(无 fit 参数)仍能正常转换
|
||||
- [x] 12.2 测试现有 YAML 文件的渲染结果与之前一致
|
||||
- [x] 12.3 验证未指定 fit 时默认使用 stretch 模式
|
||||
Reference in New Issue
Block a user