1
0
Files
PPTX/utils/image_utils.py
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

150 lines
4.7 KiB
Python
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.
"""
图片处理工具模块
提供图片适配模式处理、像素与英寸转换等功能。
"""
from PIL import Image, ImageOps
from typing import Tuple, Optional
def inches_to_pixels(inches: float, dpi: int = 96) -> float:
"""
将英寸转换为像素
Args:
inches: 英寸值
dpi: DPI每英寸像素数默认 96
Returns:
像素值
"""
return inches * dpi
def pixels_to_inches(pixels: float, dpi: int = 96) -> float:
"""
将像素转换为英寸
Args:
pixels: 像素值
dpi: DPI每英寸像素数默认 96
Returns:
英寸值
"""
return pixels / dpi
def create_canvas_with_background(size: Tuple[int, int], background_color: str) -> Image.Image:
"""
创建带背景色的画布
Args:
size: 画布尺寸 (width, height)
background_color: 背景颜色(#RRGGBB 或 #RGB 格式)
Returns:
PIL Image 对象
"""
# 验证颜色格式
import re
if not isinstance(background_color, str):
raise ValueError(f"无效的颜色格式: {background_color}")
pattern = r'^#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$'
if not re.match(pattern, background_color):
raise ValueError(f"无效的颜色格式: {background_color}")
# 创建 RGB 模式的画布
canvas = Image.new('RGB', size, background_color)
return canvas
def apply_fit_mode(
img: Image.Image,
box_size: Tuple[int, int],
fit: Optional[str] = None,
background: Optional[str] = None
) -> Image.Image:
"""
应用图片适配模式
Args:
img: PIL Image 对象
box_size: 目标尺寸 (width, height) 像素
fit: 适配模式stretch, contain, cover, center默认 stretch
background: 背景颜色(#RRGGBB 或 #RGB 格式),仅对 contain 和 center 模式有效
Returns:
处理后的 PIL Image 对象
"""
if fit is None or fit == 'stretch':
# stretch 模式:直接拉伸到目标尺寸
return img.resize(box_size, Image.Resampling.LANCZOS)
elif fit == 'contain':
# contain 模式:保持宽高比,完整显示在 box 内
# 如果图片比 box 小,保持原始尺寸;如果比 box 大,等比缩小
img_width, img_height = img.size
box_width, box_height = box_size
# 检查图片是否需要缩小
if img_width <= box_width and img_height <= box_height:
# 图片比 box 小,保持原始尺寸
result = img
else:
# 图片比 box 大,使用 contain 缩小
result = ImageOps.contain(img, box_size, Image.Resampling.LANCZOS)
# 如果指定了背景色,创建画布并居中粘贴
if background:
canvas = create_canvas_with_background(box_size, background)
# 计算居中位置
offset_x = (box_size[0] - result.width) // 2
offset_y = (box_size[1] - result.height) // 2
canvas.paste(result, (offset_x, offset_y))
return canvas
return result
elif fit == 'cover':
# cover 模式:保持宽高比,填满 box裁剪超出部分
result = ImageOps.cover(img, box_size, Image.Resampling.LANCZOS)
# ImageOps.cover 可能返回比 box_size 大的图片,需要裁剪到精确尺寸
if result.size != box_size:
# 从中心裁剪到目标尺寸
left = (result.width - box_size[0]) // 2
top = (result.height - box_size[1]) // 2
result = result.crop((left, top, left + box_size[0], top + box_size[1]))
return result
elif fit == 'center':
# center 模式:不缩放,居中显示,超出部分裁剪
img_width, img_height = img.size
box_width, box_height = box_size
# 如果图片比 box 大,需要裁剪
if img_width > box_width or img_height > box_height:
# 计算裁剪区域(从中心裁剪)
left = max(0, (img_width - box_width) // 2)
top = max(0, (img_height - box_height) // 2)
right = min(img_width, left + box_width)
bottom = min(img_height, top + box_height)
result = img.crop((left, top, right, bottom))
else:
result = img
# 如果指定了背景色,创建画布并居中粘贴
if background:
canvas = create_canvas_with_background(box_size, background)
# 计算居中位置
offset_x = (box_size[0] - result.width) // 2
offset_y = (box_size[1] - result.height) // 2
canvas.paste(result, (offset_x, offset_y))
return canvas
return result
else:
raise ValueError(f"不支持的 fit 模式: {fit}")