Files
YuShiSheJiShi/backend/app/services/mock_generator.py
changyoutongxue e3ff55b4db docs(readme): 编写项目README文档,描述功能与架构
- 完整撰写玉宗珠宝设计大师项目README,介绍项目概况及核心功能
- 说明用户认证系统实现及优势,包含JWT鉴权和密码加密细节
- 详细描述品类管理系统,支持多流程类型和多种玉石品类
- 说明设计图生成方案及技术,包含Pillow生成示例及字体支持
- 介绍设计管理功能,支持分页浏览、预览、下载和删除设计
- 个人信息管理模块说明,涵盖昵称、手机号、密码的安全修改
- 绘制业务流程图和关键数据流图,清晰展现系统架构与数据流
- 提供详细API调用链路及参数说明,涵盖用户、品类、设计接口
- 列明技术栈及版本,包含前后端框架、ORM、认证、加密等工具
- 展示目录结构,标明后端与前端项目布局
- 规划本地开发环境与启动步骤,包括数据库初始化及运行命令
- 说明服务器部署流程和Nginx配置方案
- 详细数据库表结构说明及环境变量配置指导
- 汇总常用开发及测试命令,方便开发调试与部署管理
2026-03-27 13:10:17 +08:00

223 lines
6.5 KiB
Python

"""
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