#!/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()