Files
juzi/h5/assets/mock/build-fixture.py
T
Ubuntu 6c634e3a66 feat: 真机反馈 11 项(下拉层级/侧脸蒙版/报告改版/护理建议 V2.3/水分图)
1. 城市下拉浮层 z-index 提至 50,不再被肤质轮播圆点压盖
2. 倒计时音频已更新(用户重生成,解码验证 2.62s)
3. 侧脸步蒙版鼻向修正:2/3 右脸鼻朝右、3/3 左脸鼻朝左(原与设计稿 06/07 相反)
4. 无进展提示上移至 bottom:24%(引导 chips 上方,不再压快门)
5. 相册选择器「拍照」项为 iOS/安卓系统行为,web 无法移除(详见回复)
6. 报告页顶栏移除,返回/历史改悬浮
7. 肌龄小字按产品要求不再展示(钳制留痕仍随数据返回)
8. 关注行对齐设计稿:前缀灰 + 维度名深色 500
9. 三维度排版对齐设计稿:去掉档位徽章,最低分标红
10. 肤色水润图改用 water_area(接口水分图),REPORT_MAPS 与 mock fixture 同步更新
11. 报告「综合分析」「生活方式建议」通栏移除,新增「护理建议」卡(V2.3 §8:肤质定制/最差2分类/精华/生活方式 五段,§8.5.1 无内部标注;skin-score 实现 8.6/8.7 全量文案库,回归新增 §8.8 两组样例逐字断言 86/86 绿)
e2e 全量绿(含真实 API:水分图/红区合成图挂载、护理建议五段)
2026-09-27 11:15:54 +08:00

105 lines
4.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# ─── 报告页可视化调试 fixture 生成器(仅本目录工具,需 Pillow) ───
# 数据来源:reference/facepp-skinanalyze/examples/ref_3view_raw.json(案例1 完整原始返回)
# + reference/facepp-skinanalyze/examples/images/C3_180.jpg(案例1 正脸 4000×3000)
# 产物:report-fixture.js → window.MOCK_FIXTURE = { result, face_rectangle, side, photos:[正脸dataURL,null,null] }
# 生产不变量:照片与 face_maps 统一缩到宽 1280,marks 坐标同比例缩放(坐标空间 = 照片空间)
# 用法:python3 build-fixture.py
import base64
import io
import json
import os
from PIL import Image
ROOT = os.path.dirname(os.path.abspath(__file__))
REF = os.path.join(ROOT, '..', '..', '..', 'reference', 'facepp-skinanalyze', 'examples')
TARGET_W = 1280
# 与服务端 REPORT_MAPS 白名单一致(h5/server.js)
MAP_KEYS = ['red_area', 'brown_area', 'water_area', 'texture_enhanced_lines', 'texture_enhanced_pores', 'texture_enhanced_blackheads']
def to_data_url(img, fmt):
buf = io.BytesIO()
if fmt == 'JPEG':
img.convert('RGB').save(buf, 'JPEG', quality=85)
mime = 'image/jpeg'
else:
img.save(buf, 'PNG', optimize=True)
mime = 'image/png'
return 'data:%s;base64,%s' % (mime, base64.b64encode(buf.getvalue()).decode('ascii'))
def scale_coords(node, s):
"""递归缩放 result 内全部坐标:{left,top,width,height} 矩形与 {x,y} 点,与原数据同为整数。"""
if isinstance(node, dict):
ks = node.keys()
if {'left', 'top', 'width', 'height'} <= ks and all(isinstance(node[k], (int, float)) for k in ('left', 'top', 'width', 'height')):
node['left'] = round(node['left'] * s)
node['top'] = round(node['top'] * s)
node['width'] = max(1, round(node['width'] * s))
node['height'] = max(1, round(node['height'] * s))
return
if {'x', 'y'} <= ks and isinstance(node['x'], (int, float)) and isinstance(node['y'], (int, float)):
node['x'] = round(node['x'] * s)
node['y'] = round(node['y'] * s)
return
for v in node.values():
scale_coords(v, s)
elif isinstance(node, list):
for v in node:
scale_coords(v, s)
def main():
raw = json.load(open(os.path.join(REF, 'ref_3view_raw.json'), encoding='utf-8'))
result = raw['result']
src_w, src_h = Image.open(os.path.join(REF, 'images', 'C3_180.jpg')).size
s = TARGET_W / src_w # 4000 → 1280:0.32
target_h = round(src_h * s)
# 正脸照片
photo = Image.open(os.path.join(REF, 'images', 'C3_180.jpg')).convert('RGB')
photo = photo.resize((TARGET_W, target_h), Image.LANCZOS)
photo_url = to_data_url(photo, 'JPEG')
# face_maps:白名单图层,同比例缩放;格式保持原样(jpeg 图层出 jpeg,透明叠加层出 png,与服务端魔数判定一致)
maps = {}
for k in MAP_KEYS:
b64 = result.get('face_maps', {}).get(k)
if not b64:
continue
im = Image.open(io.BytesIO(base64.b64decode(b64)))
fmt = im.format # resize 后 format 丢失,先取
im = im.resize((TARGET_W, target_h), Image.LANCZOS)
maps[k] = to_data_url(im, 'JPEG' if fmt == 'JPEG' else 'PNG')
# marks 坐标随照片同比例缩放(face_maps 已替换为缩放后 dataURL,不参与坐标缩放)
result['face_maps'] = maps
scale_coords(result, s)
face_rect = raw.get('face_rectangle')
if face_rect:
scale_coords(face_rect, s)
fixture = {
'result': result,
'face_rectangle': face_rect,
'side': {'left': raw.get('left_side_result'), 'right': raw.get('right_side_result')},
'photos': [photo_url, None, None] # 与 CAM_STEPS 同序:[0]正脸;侧脸图未纳入 fixture
}
out = os.path.join(ROOT, 'report-fixture.js')
with open(out, 'w', encoding='utf-8') as f:
f.write('/* 报告页可视化 mock fixture — 由 build-fixture.py 生成,勿手改\n')
f.write(' 来源:ref_3view_raw.json(案例1)+ C3_180.jpg;照片与 face_maps 统一宽 %d,marks 坐标同比例缩放(×%.2f)\n' % (TARGET_W, s))
f.write(' 仅在 ?mock=1 时由 index.html 按需注入,生产零额外请求 */\n')
f.write('window.MOCK_FIXTURE = ')
json.dump(fixture, f, ensure_ascii=False, separators=(',', ':'))
f.write(';\n')
print('[build-fixture] %s %.1f KB(照片 %d×%d,图层 %d 张)' % (
out, os.path.getsize(out) / 1024, TARGET_W, target_h, len(maps)))
if __name__ == '__main__':
main()