From bfef3a3974fd587b9bd1fcad5902a472b613b86d Mon Sep 17 00:00:00 2001 From: jinye_huang Date: Mon, 4 Aug 2025 13:47:36 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E6=99=BA=E8=83=BD?= =?UTF-8?q?=E8=A3=81=E5=89=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/services/poster.py | 112 +++++++++++++++++++++++++++++++++++------ 1 file changed, 96 insertions(+), 16 deletions(-) diff --git a/api/services/poster.py b/api/services/poster.py index 3d7eeb1..19045fd 100644 --- a/api/services/poster.py +++ b/api/services/poster.py @@ -229,7 +229,7 @@ class PosterService: # raise ValueError("无法获取指定的图片") - # # 3. 图片解码 + # # 3. 图片解码 images = None # 获取模板的默认尺寸,如果获取不到则使用标准尺寸 template_size = getattr(template_handler, 'size', (900, 1200)) @@ -289,27 +289,30 @@ class PosterService: image_io.seek(0) images = Image.open(image_io) - logger.info(f"图片解码成功,格式: {images.format}, 尺寸: {images.size}, 模式: {images.mode}") + logger.info(f"✅ 图片解码成功,格式: {images.format}, 原始尺寸: {images.size}, 模式: {images.mode}") + + # 处理图片尺寸,调整到目标尺寸 1350x1800 + images = self._resize_and_crop_image(images, target_width=1350, target_height=1800) except binascii.Error as e: - logger.error(f"Base64解码失败: {e}") - logger.error(f"问题数据长度: {len(images_base64) if 'images_base64' in locals() else 'unknown'}") - # 创建一个与目标大小一致的透明底图 - images = Image.new('RGBA', template_size, color=(0, 0, 0, 0)) - logger.info(f"创建默认透明背景图,尺寸: {template_size}") + logger.error(f"❌ Base64解码失败: {e}") + logger.error(f"问题数据长度: {len(first_image_base64) if 'first_image_base64' in locals() else 'unknown'}") + # 创建一个与目标大小一致的透明底图 (1350x1800) + images = Image.new('RGBA', (1350, 1800), color=(0, 0, 0, 0)) + logger.info(f"🔧 创建默认透明背景图,尺寸: {images.size}") except Exception as e: - logger.error(f"图片处理失败: {e}") + logger.error(f"❌ 图片处理失败: {e}") logger.error(f"错误类型: {type(e).__name__}") if 'image_bytes' in locals(): logger.error(f"图片数据大小: {len(image_bytes)} bytes, 前20字节: {image_bytes[:20].hex()}") - # 创建一个与目标大小一致的透明底图 - images = Image.new('RGBA', template_size, color=(0, 0, 0, 0)) - logger.info(f"创建默认透明背景图,尺寸: {template_size}") + # 创建一个与目标大小一致的透明底图 (1350x1800) + images = Image.new('RGBA', (1350, 1800), color=(0, 0, 0, 0)) + logger.info(f"🔧 创建默认透明背景图,尺寸: {images.size}") else: - logger.warning("未提供图片数据,使用默认透明背景图") - # 创建一个与目标大小一致的透明底图 - images = Image.new('RGBA', template_size, color=(0, 0, 0, 0)) - logger.info(f"创建默认透明背景图,尺寸: {template_size}") + logger.warning("⚠️ 未提供图片数据,使用默认透明背景图") + # 创建一个与目标大小一致的透明底图 (1350x1800) + images = Image.new('RGBA', (1350, 1800), color=(0, 0, 0, 0)) + logger.info(f"🔧 创建默认透明背景图,尺寸: {images.size}") # 4. 调用模板生成海报 try: @@ -2033,4 +2036,81 @@ class PosterService: text_objects.append(subtitle_obj) logger.info(f"创建VibrantTemplate精确文本布局: {len(text_objects)}个对象") - return text_objects \ No newline at end of file + return text_objects + + def _resize_and_crop_image(self, image: Image.Image, target_width: int, target_height: int) -> Image.Image: + """ + 智能调整图片尺寸到目标比例,保持最佳清晰度 + + Args: + image: 原始PIL图像 + target_width: 目标宽度(1350) + target_height: 目标高度(1800) + + Returns: + 调整后的PIL图像 + """ + original_width, original_height = image.size + target_ratio = target_width / target_height # 1350/1800 = 0.75 + original_ratio = original_width / original_height + + logger.info(f"📐 图片尺寸处理 - 原始: {original_width}x{original_height} (比例: {original_ratio:.3f}), 目标: {target_width}x{target_height} (比例: {target_ratio:.3f})") + + # 如果尺寸已经匹配,直接返回 + if original_width == target_width and original_height == target_height: + logger.info("✅ 图片尺寸已经匹配目标尺寸,无需调整") + return image + + # 计算缩放策略 + if abs(original_ratio - target_ratio) < 0.01: + # 比例接近,直接缩放 + logger.info("📏 图片比例接近目标比例,直接缩放") + resized_image = image.resize((target_width, target_height), Image.LANCZOS) + logger.info(f"✅ 直接缩放完成: {resized_image.size}") + return resized_image + + # 需要裁剪的情况 + if original_ratio > target_ratio: + # 原图更宽,以高度为准进行缩放 + scale_factor = target_height / original_height + new_width = int(original_width * scale_factor) + new_height = target_height + + logger.info(f"🔧 原图偏宽,以高度为准缩放: {scale_factor:.3f}x -> {new_width}x{new_height}") + + # 等比例缩放 + scaled_image = image.resize((new_width, new_height), Image.LANCZOS) + + # 水平居中裁剪 + crop_x = (new_width - target_width) // 2 + crop_box = (crop_x, 0, crop_x + target_width, target_height) + cropped_image = scaled_image.crop(crop_box) + + logger.info(f"✂️ 水平居中裁剪: 从({crop_x}, 0)到({crop_x + target_width}, {target_height})") + + else: + # 原图更高,以宽度为准进行缩放 + scale_factor = target_width / original_width + new_width = target_width + new_height = int(original_height * scale_factor) + + logger.info(f"🔧 原图偏高,以宽度为准缩放: {scale_factor:.3f}x -> {new_width}x{new_height}") + + # 等比例缩放 + scaled_image = image.resize((new_width, new_height), Image.LANCZOS) + + # 垂直居中裁剪 + crop_y = (new_height - target_height) // 2 + crop_box = (0, crop_y, target_width, crop_y + target_height) + cropped_image = scaled_image.crop(crop_box) + + logger.info(f"✂️ 垂直居中裁剪: 从(0, {crop_y})到({target_width}, {crop_y + target_height})") + + # 确保最终尺寸正确 + final_width, final_height = cropped_image.size + if final_width != target_width or final_height != target_height: + logger.warning(f"⚠️ 裁剪后尺寸不匹配,强制调整: {final_width}x{final_height} -> {target_width}x{target_height}") + cropped_image = cropped_image.resize((target_width, target_height), Image.LANCZOS) + + logger.info(f"✅ 图片尺寸处理完成: {original_width}x{original_height} -> {cropped_image.size}") + return cropped_image \ No newline at end of file