Instruction file imported from pospynow/pindou (
.github/instructions/backend.instructions.md). Copyright stays with the author.
后端开发规范
目录结构约定
backend/app/
├── main.py # FastAPI app 实例、中间件、路由注册
├── routers/
│ ├── convert.py # POST /api/v1/convert
│ └── palettes.py # GET /api/v1/palettes[/{id}]
├── services/
│ ├── image_processor.py # 图像缩放、裁剪逻辑
│ └── color_mapper.py # Lab 色差计算、颜色量化
├── models/
│ └── schemas.py # Pydantic 请求/响应模型
└── data/palettes/ # 颜色板 JSON 文件
图像处理核心算法
颜色映射(必须用 CIE Lab)
# color_mapper.py 参考实现
from PIL import Image
import numpy as np
from skimage.color import rgb2lab # pip install scikit-image
def build_lab_palette(colors: list[dict]) -> np.ndarray:
"""将 RGB 颜色板预计算为 Lab 数组,shape: (N, 3)"""
rgb = np.array([[c['rgb'][0], c['rgb'][1], c['rgb'][2]] for c in colors], dtype=np.float32) / 255.0
return rgb2lab(rgb.reshape(1, -1, 3)).reshape(-1, 3)
def map_pixels_to_palette(img_rgb: np.ndarray, lab_palette: np.ndarray) -> np.ndarray:
"""img_rgb shape: (H, W, 3) uint8 → 返回 palette 索引数组 (H, W)"""
h, w = img_rgb.shape[:2]
img_lab = rgb2lab(img_rgb.astype(np.float32) / 255.0) # (H, W, 3)
flat = img_lab.reshape(-1, 3) # (H*W, 3)
# 向量化 ΔE:广播计算每像素到每个颜色的距离
diff = flat[:, np.newaxis, :] - lab_palette[np.newaxis, :, :] # (H*W, N, 3)
dist = np.linalg.norm(diff, axis=-1) # (H*W, N)
return dist.argmin(axis=1).reshape(h, w)
- 不要用 RGB 欧氏距离,视觉误差大
- 颜色板 Lab 数组在服务启动时预计算并缓存,不要每次请求重算
图像缩放流程
def resize_to_grid(img: Image.Image, grid_w: int, grid_h: int) -> Image.Image:
return img.convert("RGB").resize((grid_w, grid_h), Image.Resampling.LANCZOS)
文件上传安全规范
import magic # pip install python-magic-bin(Windows)或 python-magic
ALLOWED_MIME = {"image/jpeg", "image/png", "image/webp", "image/gif"}
MAX_SIZE = 10 * 1024 * 1024 # 10MB
async def validate_upload(file: UploadFile):
data = await file.read()
if len(data) > MAX_SIZE:
raise HTTPException(400, "文件过大,最大 10MB")
mime = magic.from_buffer(data[:2048], mime=True)
if mime not in ALLOWED_MIME:
raise HTTPException(400, f"不支持的文件类型: {mime}")
return data
- 用
python-magic检测真实 MIME(不信任扩展名和 Content-Type) - 图片数据在内存中处理,不写磁盘临时文件
API 响应格式
所有端点统一返回:
# 成功
{"success": True, "data": {...}}
# 失败(用 HTTPException,FastAPI 自动处理)
{"success": False, "error": "错误说明"}
转换成功响应的 data 结构:
class ConvertResponse(BaseModel):
grid: list[list[str]] # 颜色 hex 值二维数组,如 [["#FF0000", ...], ...]
colorStats: dict[str, int] # {hex: 用到的格子数}
previewBase64: str # base64 编码的预览图(png)
CORS 配置
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:5173"], # 开发;生产改为实际域名
allow_methods=["*"],
allow_headers=["*"],
)
依赖版本(requirements.txt 基准)
fastapi>=0.111
uvicorn[standard]>=0.29
pillow>=10.3
numpy>=1.26
scikit-image>=0.23
python-magic-bin>=0.4.14 # Windows;Linux 用 python-magic
pydantic>=2.6
pytest>=8
httpx>=0.27 # 测试用
测试规范
- 测试文件放
backend/tests/,命名test_*.py - 使用
httpx.AsyncClient+ FastAPITestClient测试 API - 颜色映射算法单独单元测试(纯函数,易测试)