diff --git a/api/models/poster.py b/api/models/poster.py index d52d477..187643c 100644 --- a/api/models/poster.py +++ b/api/models/poster.py @@ -28,7 +28,7 @@ class PosterGenerateRequest(BaseModel): json_schema_extra = { "example": { "templateId": "vibrant", - "imagesBase64": "", + "imagesBase64": ["base64_encoded_image_1", "base64_encoded_image_2"], "numVariations": 1, "forceLlmGeneration":False, "generatePsd": True, diff --git a/api/services/poster.py b/api/services/poster.py index 6dd8c29..721b2ee 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)) @@ -302,27 +302,46 @@ class PosterService: image_io.seek(0) images = Image.open(image_io) +<<<<<<< HEAD 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) +>>>>>>> bfef3a3974fd587b9bd1fcad5902a472b613b86d except binascii.Error as e: logger.error(f"❌ Base64解码失败: {e}") logger.error(f"问题数据长度: {len(first_image_base64) if 'first_image_base64' in locals() else 'unknown'}") +<<<<<<< HEAD # 创建一个与目标大小一致的透明底图 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}") +>>>>>>> bfef3a3974fd587b9bd1fcad5902a472b613b86d except Exception as 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("⚠️ 未提供图片数据,使用默认透明背景图") +<<<<<<< HEAD # 创建一个与目标大小一致的透明底图 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}") +>>>>>>> bfef3a3974fd587b9bd1fcad5902a472b613b86d # 4. 调用模板生成海报 try: @@ -2046,4 +2065,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