""" 图片处理模块 负责图片文件的处理,包括图片读取、尺寸调整、格式转换和对齐设置等功能。 """ import os from typing import Tuple, Optional from PIL import Image from docx.shared import Inches from docx.enum.text import WD_ALIGN_PARAGRAPH from config import config class ImageProcessor: """图片处理器类""" @staticmethod def process_image(image_path: str) -> Tuple[Image.Image, float]: """ 处理图片,包括方向矫正和尺寸调整 Args: image_path: 图片文件路径 Returns: Tuple[Image.Image, float]: 处理后的图片对象和宽度(英寸) Raises: Exception: 处理图片失败时 """ if not os.path.exists(image_path): raise Exception(f"图片文件不存在: {image_path}") try: with Image.open(image_path) as img: # 处理图片方向(EXIF旋转信息) img = ImageProcessor._fix_image_orientation(img) # 调整图片尺寸 img, width_inches = ImageProcessor._resize_image(img) return img, width_inches except Exception as e: raise Exception(f"处理图片失败 {image_path}: {str(e)}") @staticmethod def _fix_image_orientation(img: Image.Image) -> Image.Image: """ 根据EXIF信息修正图片方向 Args: img: PIL图片对象 Returns: Image.Image: 方向修正后的图片 """ try: # 检查是否有EXIF数据 if hasattr(img, '_getexif'): exif = img._getexif() if exif is not None: # EXIF方向标签 orientation_tag = 274 if orientation_tag in exif: orientation = exif[orientation_tag] # 根据方向值进行旋转 if orientation == 3: img = img.rotate(180, expand=True) elif orientation == 6: img = img.rotate(270, expand=True) elif orientation == 8: img = img.rotate(90, expand=True) except Exception as e: print(f"修正图片方向时出错: {e}") return img @staticmethod def _resize_image(img: Image.Image) -> Tuple[Image.Image, float]: """ 根据配置调整图片尺寸 Args: img: PIL图片对象 Returns: Tuple[Image.Image, float]: 调整后的图片和宽度(英寸) """ if config.image_resize == "width" and config.image_width > 0: # 按指定宽度调整 target_width_px = config.image_width * 96 # 96 DPI width, height = img.size if width > target_width_px: ratio = target_width_px / width new_height = int(height * ratio) img = img.resize((int(target_width_px), new_height), Image.LANCZOS) return img, config.image_width else: # 不调整尺寸,计算当前宽度(英寸) width_inches = img.width / 96 # 假设96 DPI return img, width_inches @staticmethod def get_image_alignment(): """ 获取图片对齐方式的Word枚举值 Returns: WD_ALIGN_PARAGRAPH: Word对齐方式枚举 """ alignment_map = { "left": WD_ALIGN_PARAGRAPH.LEFT, "center": WD_ALIGN_PARAGRAPH.CENTER, "right": WD_ALIGN_PARAGRAPH.RIGHT } return alignment_map.get(config.image_alignment, WD_ALIGN_PARAGRAPH.CENTER) @staticmethod def validate_image(image_path: str) -> dict: """ 验证图片文件的有效性 Args: image_path: 图片文件路径 Returns: dict: 验证结果,包含有效性、错误信息和图片信息 """ result = { "valid": False, "error": None, "info": {} } if not os.path.exists(image_path): result["error"] = "文件不存在" return result try: with Image.open(image_path) as img: result["valid"] = True result["info"] = { "format": img.format, "mode": img.mode, "size": img.size, "width": img.width, "height": img.height } # 检查图片是否过大 if img.width > 10000 or img.height > 10000: result["error"] = "图片尺寸过大" result["valid"] = False except Exception as e: result["error"] = f"无法打开图片: {str(e)}" return result @staticmethod def get_supported_formats() -> list: """ 获取支持的图片格式列表 Returns: list: 支持的图片格式扩展名列表 """ return ['.jpg', '.jpeg', '.png', '.bmp', '.gif', '.webp', '.tiff'] @staticmethod def convert_image_format(image_path: str, target_format: str, output_path: str) -> bool: """ 转换图片格式 Args: image_path: 源图片路径 target_format: 目标格式(如'PNG', 'JPEG') output_path: 输出文件路径 Returns: bool: 是否转换成功 """ try: with Image.open(image_path) as img: # 如果是JPEG格式且原图有透明通道,转为RGB if target_format.upper() == 'JPEG' and img.mode in ('RGBA', 'LA'): rgb_img = Image.new('RGB', img.size, (255, 255, 255)) rgb_img.paste(img, mask=img.split()[-1] if img.mode == 'RGBA' else None) img = rgb_img img.save(output_path, format=target_format) return True except Exception as e: print(f"转换图片格式失败: {e}") return False @staticmethod def create_thumbnail(image_path: str, thumbnail_path: str, size: Tuple[int, int] = (200, 200)) -> bool: """ 创建图片缩略图 Args: image_path: 源图片路径 thumbnail_path: 缩略图保存路径 size: 缩略图尺寸(宽度, 高度) Returns: bool: 是否创建成功 """ try: with Image.open(image_path) as img: img.thumbnail(size, Image.LANCZOS) img.save(thumbnail_path) return True except Exception as e: print(f"创建缩略图失败: {e}") return False @staticmethod def get_image_info(image_path: str) -> Optional[dict]: """ 获取图片详细信息 Args: image_path: 图片文件路径 Returns: Optional[dict]: 图片信息字典,失败时返回None """ try: with Image.open(image_path) as img: info = { "filename": os.path.basename(image_path), "format": img.format, "mode": img.mode, "size": img.size, "width": img.width, "height": img.height, "file_size": os.path.getsize(image_path) } # 尝试获取EXIF信息 if hasattr(img, '_getexif'): exif = img._getexif() if exif: info["has_exif"] = True # 获取一些常用的EXIF信息 orientation = exif.get(274) # 方向 if orientation: info["orientation"] = orientation else: info["has_exif"] = False else: info["has_exif"] = False return info except Exception as e: print(f"获取图片信息失败: {e}") return None @staticmethod def batch_validate_images(image_paths: list) -> dict: """ 批量验证图片文件 Args: image_paths: 图片文件路径列表 Returns: dict: 验证结果统计 """ result = { "total": len(image_paths), "valid": 0, "invalid": 0, "errors": [] } for image_path in image_paths: validation = ImageProcessor.validate_image(image_path) if validation["valid"]: result["valid"] += 1 else: result["invalid"] += 1 result["errors"].append({ "path": image_path, "error": validation["error"] }) return result @staticmethod def optimize_image_for_docx(image_path: str, temp_dir: str) -> str: """ 优化图片以适合插入DOCX文档 Args: image_path: 原图片路径 temp_dir: 临时文件目录 Returns: str: 优化后的图片路径 """ try: # 确保临时目录存在 os.makedirs(temp_dir, exist_ok=True) with Image.open(image_path) as img: # 修正方向 img = ImageProcessor._fix_image_orientation(img) # 根据配置调整尺寸 img, _ = ImageProcessor._resize_image(img) # 生成临时文件路径 filename = os.path.basename(image_path) name, ext = os.path.splitext(filename) temp_path = os.path.join(temp_dir, f"{name}_optimized{ext}") # 保存优化后的图片 # 如果是PNG且没有透明通道,转为JPEG以减少文件大小 if img.format == 'PNG' and img.mode == 'RGB': temp_path = os.path.join(temp_dir, f"{name}_optimized.jpg") img.save(temp_path, 'JPEG', quality=85, optimize=True) else: img.save(temp_path, optimize=True) return temp_path except Exception as e: print(f"优化图片失败: {e}") return image_path # 返回原路径 # 创建全局图片处理器实例 image_processor = ImageProcessor() # 兼容旧接口的函数 def process_image(image_path: str) -> Tuple[Image.Image, float]: """处理图片(兼容旧接口)""" return ImageProcessor.process_image(image_path) def get_image_alignment(): """获取图片对齐方式(兼容旧接口)""" return ImageProcessor.get_image_alignment()