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:
xsl
2026-07-13 23:38:55 +08:00
parent 28255ef7c2
commit 95c6a2d929
7 changed files with 81 additions and 40 deletions
+16 -5
View File
@@ -57,6 +57,10 @@
.level-cell .level-label { font-size: 12px; font-weight: 600; color: #fff; background: rgba(0,0,0,.55); padding: 4px 8px; }
.level-cell img { width: 100%; max-height: 380px; object-fit: contain; display: block; }
.level-cell .level-none { color: #9ca3af; font-size: 13px; padding: 28px 0; text-align: center; }
/* 原图 + 透明 PNG 叠加(三档叠图用) */
.level-cell .img-stack { position: relative; line-height: 0; }
.level-cell .img-stack .layer-base { width: 100%; max-height: 380px; object-fit: contain; display: block; }
.level-cell .img-stack .layer-anno { position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: contain; }
@media (max-width: 700px) { .level-row { grid-template-columns: repeat(2, 1fr); } }
/* 坐标高亮 */
@@ -224,7 +228,14 @@ async function submitTest() {
function renderGrid() {
if (!_images.length) { $('resultsGrid').innerHTML = '<span style="color:#9ca3af">无数据</span>'; return; }
let h = '';
const cell = (label, url) => url
// 叠图档:原图打底 + 透明 PNG 叠加(无原图或无叠图 → 显示占位)
const overlayCell = (label, url) => (url && _origUrl)
? '<div class="level-cell"><div class="level-label">'+label+'</div><div class="img-stack">'+
'<img class="layer-base" src="'+_origUrl+'" alt="原图">'+
'<img class="layer-anno" src="'+url+'" alt="'+label+'"></div></div>'
: '<div class="level-cell"><div class="level-label">'+label+'</div><div class="level-none">无</div></div>';
// 生发图:独立 img(已是 ComfyUI 完整人像照片)
const grownCell = (label, url) => url
? '<div class="level-cell"><div class="level-label">'+label+'</div><img src="'+url+'" alt="'+label+'"></div>'
: '<div class="level-cell"><div class="level-label">'+label+'</div><div class="level-none">无</div></div>';
_images.forEach(function(it) {
@@ -232,10 +243,10 @@ function renderGrid() {
'<div class="hair-block-title">#' + (it.order||'—') + ' ' + (it.hairline_type||'') +
(it.grown_image_url ? ' &nbsp;<span style="font-size:11px;color:#7c3aed">含生发图</span>' : '') + '</div>' +
'<div class="level-row">' +
cell('middle', it.image_middle_url) +
cell('high', it.image_high_url) +
cell('low', it.image_low_url) +
cell('生发图', it.grown_image_url) +
overlayCell('middle', it.image_middle_url) +
overlayCell('high', it.image_high_url) +
overlayCell('low', it.image_low_url) +
grownCell('生发图', it.grown_image_url) +
'</div>' +
'</div>';
});