""" Mock 图片生成服务 使用 Pillow 生成带文字的占位设计图 """ import os from typing import Optional, Tuple, Union from PIL import Image, ImageDraw, ImageFont # 颜色映射表(中文颜色名 -> 十六进制) COLOR_MAP = { # 和田玉国标色种 "白玉": "#FEFEF2", "青白玉": "#E8EDE4", "青玉": "#7A8B6E", "碧玉": "#2D5F2D", "翠青": "#6BAF8D", "黄玉": "#D4A843", "糖玉": "#C4856C", "墨玉": "#2C2C2C", "藕粉": "#E8B4B8", "烟紫": "#8B7D9B", # 原有颜色 "糖白": "#F5F0E8", # 通用颜色 "白色": "#FFFFFF", "黑色": "#333333", "红色": "#C41E3A", "绿色": "#228B22", "蓝色": "#4169E1", "黄色": "#FFD700", "紫色": "#9370DB", "粉色": "#FFB6C1", "橙色": "#FF8C00", } # 默认背景色(浅灰) DEFAULT_BG_COLOR = "#E8E4DF" def hex_to_rgb(hex_color: str) -> Tuple[int, int, int]: """将十六进制颜色转换为 RGB 元组""" hex_color = hex_color.lstrip('#') return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4)) def get_contrast_text_color(bg_color: str) -> str: """根据背景色计算合适的文字颜色(黑或白)""" r, g, b = hex_to_rgb(bg_color) # 使用亮度公式 brightness = (r * 299 + g * 587 + b * 114) / 1000 return "#333333" if brightness > 128 else "#FFFFFF" def get_font(size: int = 24) -> Union[ImageFont.FreeTypeFont, ImageFont.ImageFont]: """ 获取字体,优先使用系统中文字体 """ # 常见中文字体路径 font_paths = [ # macOS "/System/Library/Fonts/PingFang.ttc", "/System/Library/Fonts/STHeiti Light.ttc", "/System/Library/Fonts/Supplemental/Arial Unicode.ttf", "/Library/Fonts/Arial Unicode.ttf", # Linux "/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf", "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc", "/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc", # Windows "C:\\Windows\\Fonts\\msyh.ttc", "C:\\Windows\\Fonts\\simsun.ttc", ] for font_path in font_paths: if os.path.exists(font_path): try: return ImageFont.truetype(font_path, size) except Exception: continue # 回退到默认字体 return ImageFont.load_default() def generate_mock_design( category_name: str, sub_type_name: Optional[str], color_name: Optional[str], prompt: str, save_path: str, carving_technique: Optional[str] = None, design_style: Optional[str] = None, motif: Optional[str] = None, size_spec: Optional[str] = None, surface_finish: Optional[str] = None, usage_scene: Optional[str] = None, ) -> str: """ 生成 Mock 设计图 Args: category_name: 品类名称 sub_type_name: 子类型名称(可选) color_name: 颜色名称(可选) prompt: 用户设计需求 save_path: 保存路径 Returns: 相对 URL 路径,如 /uploads/designs/1001.png """ # 确定背景色 if color_name and color_name in COLOR_MAP: bg_color = COLOR_MAP[color_name] elif color_name: # 尝试直接使用颜色名(可能是十六进制) bg_color = color_name if color_name.startswith("#") else DEFAULT_BG_COLOR else: bg_color = DEFAULT_BG_COLOR # 创建图片 width, height = 800, 800 bg_rgb = hex_to_rgb(bg_color) image = Image.new("RGB", (width, height), bg_rgb) draw = ImageDraw.Draw(image) # 获取文字颜色(与背景对比) text_color = get_contrast_text_color(bg_color) text_rgb = hex_to_rgb(text_color) # 获取字体 title_font = get_font(48) info_font = get_font(32) prompt_font = get_font(28) # 绘制标题 title = "玉宗设计" draw.text((width // 2, 100), title, font=title_font, fill=text_rgb, anchor="mm") # 绘制分隔线 line_y = 160 draw.line([(100, line_y), (700, line_y)], fill=text_rgb, width=2) # 绘制品类信息 y_position = 220 info_lines = [f"品类: {category_name}"] if sub_type_name: info_lines.append(f"类型: {sub_type_name}") if color_name: info_lines.append(f"颜色: {color_name}") if carving_technique: info_lines.append(f"工艺: {carving_technique}") if design_style: info_lines.append(f"风格: {design_style}") if motif: info_lines.append(f"题材: {motif}") if size_spec: info_lines.append(f"尺寸: {size_spec}") if surface_finish: info_lines.append(f"表面: {surface_finish}") if usage_scene: info_lines.append(f"用途: {usage_scene}") for line in info_lines: draw.text((width // 2, y_position), line, font=info_font, fill=text_rgb, anchor="mm") y_position += 50 # 绘制分隔线 y_position += 20 draw.line([(100, y_position), (700, y_position)], fill=text_rgb, width=1) y_position += 40 # 绘制用户需求标题 draw.text((width // 2, y_position), "设计需求:", font=info_font, fill=text_rgb, anchor="mm") y_position += 50 # 绘制用户需求文本(自动换行) max_chars_per_line = 20 prompt_lines = [] current_line = "" for char in prompt: current_line += char if len(current_line) >= max_chars_per_line: prompt_lines.append(current_line) current_line = "" if current_line: prompt_lines.append(current_line) # 限制最多显示 5 行 for line in prompt_lines[:5]: draw.text((width // 2, y_position), line, font=prompt_font, fill=text_rgb, anchor="mm") y_position += 40 if len(prompt_lines) > 5: draw.text((width // 2, y_position), "...", font=prompt_font, fill=text_rgb, anchor="mm") # 绘制底部装饰 draw.rectangle([(50, 720), (750, 750)], outline=text_rgb, width=2) draw.text((width // 2, 735), "AI Generated Mock Design", font=get_font(20), fill=text_rgb, anchor="mm") # 确保目录存在 os.makedirs(os.path.dirname(save_path), exist_ok=True) # 保存图片 image.save(save_path, "PNG") # 返回相对 URL 路径 # save_path 格式类似 uploads/designs/1001.png # 需要转换为 /uploads/designs/1001.png relative_path = save_path.replace("\\", "/") if not relative_path.startswith("/"): relative_path = "/" + relative_path return relative_path