""" 元素加载器 从RGBA格式的PNG图片加载元素,根据图片尺寸确定元素尺寸, 并根据alpha通道生成mask """ import os import numpy as np from PIL import Image from typing import Optional, Tuple from .utils.nodes import LeafNode, NodeType def resize_image_with_aspect_ratio(img: Image.Image, target_width: int, target_height: int) -> Tuple[Image.Image, Tuple[int, int]]: """ 保持横纵比resize图片,最短边对齐,不添加透明padding 例如:原始1024x1024,目标500x1000,结果500x500(保持1:1比例,最短边对齐) Args: img: PIL图片对象(RGBA格式) target_width: 目标宽度 target_height: 目标高度 Returns: (resized_image, actual_size): 调整后的图片和实际尺寸 """ original_width, original_height = img.size original_aspect = original_width / original_height print("original_aspect: ", original_aspect) print("target_width: ", target_width, "target_height: ", target_height) # 找到目标尺寸的较短边,以较短边为基准 if target_width <= target_height: # 目标宽度是较短边,以宽度为准 new_width = target_width new_height = int(original_height * (target_width / original_width)) else: # 目标高度是较短边,以高度为准 new_height = target_height new_width = int(original_width * (target_height / original_height)) # 确保不超过目标尺寸(双重检查) if new_width > target_width: new_width = target_width new_height = int(original_height * (target_width / original_width)) if new_height > target_height: new_height = target_height new_width = int(original_width * (target_height / original_height)) print("new_width: ", new_width, "new_height: ", new_height) # Resize图片(保持横纵比,不添加透明padding) resized_img = img.resize((new_width, new_height), Image.Resampling.LANCZOS) return resized_img, (new_width, new_height) class ElementLoader: """元素加载器""" def __init__(self, base_dir: Optional[str] = None): """ 初始化元素加载器 Args: base_dir: 图片文件的基础目录,如果提供,相对路径会基于此目录 """ self.base_dir = base_dir def load_from_image(self, image_path: str, node_id: str, node_type: NodeType, metadata: Optional[dict] = None, target_width: Optional[float] = None, target_height: Optional[float] = None) -> LeafNode: """ 从PNG图片加载元素 Args: image_path: 图片文件路径(可以是绝对路径或相对于base_dir的路径) node_id: 节点ID node_type: 节点类型 metadata: 额外的元数据 target_width: 目标宽度(可选,如果提供则resize图片) target_height: 目标高度(可选,如果提供则resize图片) Returns: LeafNode对象,包含图片尺寸和mask """ # 解析路径 full_path = self._resolve_path(image_path) # 加载图片 img = Image.open(full_path) # 确保是RGBA格式 if img.mode != 'RGBA': img = img.convert('RGBA') # 获取原始尺寸 original_width, original_height = img.size # 如果指定了目标尺寸,resize图片(保持横纵比,最短边对齐) if target_width is not None and target_height is not None: target_width = int(target_width) target_height = int(target_height) # 使用保持横纵比的resize函数(最短边对齐,不添加透明padding) img, (width, height) = resize_image_with_aspect_ratio( img, target_width, target_height ) else: width, height = original_width, original_height # 从alpha通道生成mask # mask是二值图像,alpha > 0 的像素为1,否则为0 alpha_channel = np.array(img.split()[3]) # 获取alpha通道 mask = (alpha_channel > 0).astype(np.uint8) * 255 # 创建元数据 node_metadata = metadata or {} node_metadata['image_path'] = image_path node_metadata['image_size'] = (width, height) node_metadata['original_image_size'] = (original_width, original_height) if target_width is not None and target_height is not None: node_metadata['resized'] = True # 创建叶子节点 node = LeafNode( node_id=node_id, node_type=node_type, width=float(width), height=float(height), mask=mask, metadata=node_metadata ) return node def _resolve_path(self, image_path: str) -> str: """解析图片路径""" if os.path.isabs(image_path): return image_path if self.base_dir: return os.path.join(self.base_dir, image_path) return image_path def load_chart(self, image_path: str, node_id: str, metadata: Optional[dict] = None) -> LeafNode: """加载图表元素""" return self.load_from_image(image_path, node_id, NodeType.CHART, metadata) def load_image(self, image_path: str, node_id: str, metadata: Optional[dict] = None) -> LeafNode: """加载图像元素""" return self.load_from_image(image_path, node_id, NodeType.IMAGE, metadata) def load_text(self, image_path: str, node_id: str, metadata: Optional[dict] = None) -> LeafNode: """加载文本元素(文本渲染为图片)""" return self.load_from_image(image_path, node_id, NodeType.TEXT, metadata) def load_shape(self, image_path: str, node_id: str, metadata: Optional[dict] = None) -> LeafNode: """加载形状元素""" return self.load_from_image(image_path, node_id, NodeType.SHAPE, metadata) def load_element_from_image(image_path: str, node_id: str, node_type: NodeType, base_dir: Optional[str] = None, metadata: Optional[dict] = None, target_width: Optional[float] = None, target_height: Optional[float] = None) -> LeafNode: """ 便捷函数:从图片加载元素 Args: image_path: 图片文件路径 node_id: 节点ID node_type: 节点类型 base_dir: 基础目录(可选) metadata: 额外的元数据(可选) target_width: 目标宽度(可选,如果提供则resize图片) target_height: 目标高度(可选,如果提供则resize图片) Returns: LeafNode对象 """ loader = ElementLoader(base_dir=base_dir) return loader.load_from_image(image_path, node_id, node_type, metadata, target_width, target_height)