feat: 接口2/5 发际线叠图改为透明 PNG(仅曲线),生发图不变
接口2(/api/v1/hair/grow)的 image_url 和接口5(/api/v1/hairline/generate) 的 image_middle/high/low_url 从「原图+白线合成 JPG」改为「透明底 PNG(仅含 发际线曲线)」,前端需叠加原图显示。grown_image_url 生发图保持不变(ComfyUI 完整人像照片)。 实现: - app.py 新增 _rgba_png_b64() 编码 RGBA 透明层为 PNG base64(保留 alpha) - hairline/service.py 接口2/5 改用 build_overlay_layer(返回 RGBA 透明层) 替代 render_hairline_overlay(合成到原图) - 网关零改动:rewrite_base64_to_url 已按 \x89PNG 魔数嗅探落盘为 .png 测试页:test_interface2/5.html 改为「原图打底 + 透明PNG 绝对定位叠加」显示 (复用 .img-stack 结构,固定叠加无开关)。 文档:接口文档.md / integration.html 更新接口2/5 图片字段说明。 测试:43 passed,接口2 image_base64 改断言为 PNG 魔数,接口5 三档叠图同。
This commit is contained in:
@@ -14,6 +14,7 @@ from typing import Any, List, Optional
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
from fastapi import FastAPI, File, Form, Request, UploadFile
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
@@ -229,6 +230,20 @@ def _jpg_b64(bgr) -> str:
|
||||
return base64.b64encode(buf.tobytes()).decode()
|
||||
|
||||
|
||||
def _rgba_png_b64(rgba) -> str:
|
||||
"""(H,W,4) float32/uint8 RGBA 透明层 → PNG base64(保留 alpha 通道)。
|
||||
|
||||
用于发际线叠图/预览图:只含发际线曲线像素、背景透明,前端叠加到原图上显示。
|
||||
"""
|
||||
arr = np.asarray(rgba)
|
||||
if arr.dtype != np.uint8:
|
||||
arr = np.clip(arr, 0, 255).astype(np.uint8)
|
||||
img = Image.fromarray(arr, mode="RGBA")
|
||||
buf = BytesIO()
|
||||
img.save(buf, format="PNG")
|
||||
return base64.b64encode(buf.getvalue()).decode()
|
||||
|
||||
|
||||
def _png_to_jpg_b64(png_bytes) -> str:
|
||||
"""ComfyUI 返回的 PNG 字节 → 重编码为 JPG base64;无法解码则原样透传。"""
|
||||
img = cv2.imdecode(np.frombuffer(png_bytes, np.uint8), cv2.IMREAD_COLOR)
|
||||
@@ -723,7 +738,7 @@ async def hair_grow(
|
||||
results = []
|
||||
for p in items:
|
||||
results.append({
|
||||
"image_base64": _jpg_b64(p["image_bgr"]), # 预览图 JPG
|
||||
"image_base64": _rgba_png_b64(p["overlay"]), # 发际线曲线透明 PNG
|
||||
"grown_image_base64": (_png_to_jpg_b64(p["grown_png"]) # 生发图 JPG
|
||||
if p["grown_png"] else None),
|
||||
"hairline_type": p["hairline_type"],
|
||||
@@ -1133,9 +1148,9 @@ async def hairline_generate(
|
||||
ov = it["overlays"]
|
||||
hairline_images.append({
|
||||
"hairline_type": it["hairline_type"],
|
||||
"image_middle_base64": _jpg_b64(ov["middle"]), # 发际线叠图 middle 档 JPG
|
||||
"image_high_base64": _jpg_b64(ov["high"]), # 发际线叠图 high 档 JPG
|
||||
"image_low_base64": _jpg_b64(ov["low"]), # 发际线叠图 low 档 JPG
|
||||
"image_middle_base64": _rgba_png_b64(ov["middle"]), # 发际线曲线透明 PNG middle 档
|
||||
"image_high_base64": _rgba_png_b64(ov["high"]), # 发际线曲线透明 PNG high 档
|
||||
"image_low_base64": _rgba_png_b64(ov["low"]), # 发际线曲线透明 PNG low 档
|
||||
"grown_image_base64": (_png_to_jpg_b64(it["grown_png"]) # 生发图 JPG(失败为 null)
|
||||
if it.get("grown_png") else None),
|
||||
"order": it["order"],
|
||||
|
||||
Reference in New Issue
Block a user