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:
149
utils/image_utils.py
Normal file
149
utils/image_utils.py
Normal file
@@ -0,0 +1,149 @@
|
||||
"""
|
||||
图片处理工具模块
|
||||
|
||||
提供图片适配模式处理、像素与英寸转换等功能。
|
||||
"""
|
||||
|
||||
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}")
|
||||
Reference in New Issue
Block a user