Files
SignatureSystem/image_test/e2e_test.py
2026-07-20 13:16:17 +08:00

151 lines
4.8 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.
"""端到端验证process_image 输出 vs 实际写入 docx 的图片字节。
目的:
用户反馈"从文档复制出来的图底部有 1/4 空白",但独立测试 process_image
输出已是紧致裁剪。本脚本走完整 sign_word 链路,把生成的 docx 解开来看
word/media/ 下真实的图片字节,对比 process_image 的输出,定位空白来源:
- 若字节完全一致 → 空白来自 Word 段落渲染(行高/对齐),不是图片本身
- 若字节不一致 → 我们的链路在某个环节给图片加了 padding需进一步定位
使用:
python image_test/e2e_test.py
"""
import io
import sys
import zipfile
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent
PROJECT_ROOT = BASE_DIR.parent
INPUT_IMAGE = BASE_DIR / "input" / "test1.jpg"
OUT_DIR = BASE_DIR / "output" / "e2e"
DOCX_OUT = OUT_DIR / "signed.docx"
def _ensure_project_on_path():
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
def _make_test_docx(path):
"""构造一份带 SET USER_test 域代码的 docx正文段落使用偏大行高。
段落行高 2cmlineRule=exact模拟用户文档里的固定行高场景。
"""
from docx import Document
from docx.shared import Cm, Pt
from docx.enum.text import WD_LINE_SPACING
from lib import field_codes
doc = Document()
p = doc.add_paragraph()
p.paragraph_format.line_spacing_rule = WD_LINE_SPACING.EXACTLY
p.paragraph_format.line_spacing = Cm(2.0)
p.add_run("正文前 ")
runs = field_codes.build_set_field_runs("test")
for r in runs:
p._element.append(r)
p.add_run(" 正文中 ")
p.add_run("正文后")
doc.save(str(path))
def _image_stats(data):
from PIL import Image, ImageChops
img = Image.open(io.BytesIO(data))
if img.mode != 'RGBA':
img = img.convert('RGBA')
r, g, b, a = img.split()
luma = Image.merge('RGB', (r, g, b)).convert('L')
alpha_mask = a.point(lambda p: 255 if p > 30 else 0)
luma_mask = luma.point(lambda p: 255 if p < 210 else 0)
combined = ImageChops.darker(alpha_mask, luma_mask)
total = img.size[0] * img.size[1] or 1
return {
'size': img.size,
'mode': img.mode,
'content_pct': 100.0 * sum(combined.histogram()[1:]) / total,
'alpha_pct': 100.0 * sum(alpha_mask.histogram()[1:]) / total,
'bbox': combined.getbbox(),
'sha256': __import__('hashlib').sha256(data).hexdigest()[:16],
}
def main():
_ensure_project_on_path()
if not INPUT_IMAGE.exists():
print(f"测试图片不存在: {INPUT_IMAGE}")
sys.exit(1)
OUT_DIR.mkdir(parents=True, exist_ok=True)
src_docx = OUT_DIR / "input.docx"
_make_test_docx(src_docx)
with open(src_docx, 'rb') as f:
docx_data = f.read()
with open(INPUT_IMAGE, 'rb') as f:
image_bytes = f.read()
from lib.image_processor import process_image
from lib.word_signer import sign_word
processed = process_image(
image_bytes, is_signature=True,
noise_level=0, rotate_min=0, rotate_max=0,
log_source='e2e测试',
)
params = {
'docx_name': 'input.docx',
'match_mode': 'upload',
'stamps': [{
'marker': 'USER_test',
'image_base64': __import__('base64').b64encode(image_bytes).decode(),
'image_filename': 'test1.jpg',
'is_signature': True,
}],
'use_global_height': True,
'height': 2.5,
}
result, err = sign_word(docx_data, params, log_source='e2e测试', trace_id='e2e')
if err:
print(f"sign_word 失败: {err}")
sys.exit(1)
with open(DOCX_OUT, 'wb') as f:
f.write(result['data'])
print(f"已生成: {DOCX_OUT}")
print(f"warnings: {result.get('warnings', [])}")
print(f"success_count: {result.get('success_count', 0)}")
print()
print("=== process_image 直出 ===")
s1 = _image_stats(processed)
print(f" 尺寸={s1['size']} 内容={s1['content_pct']:.1f}% "
f"alpha={s1['alpha_pct']:.1f}% bbox={s1['bbox']} sha={s1['sha256']}")
print()
print("=== docx 内 word/media/ 实际图片 ===")
with zipfile.ZipFile(DOCX_OUT, 'r') as z:
medias = sorted(n for n in z.namelist() if n.startswith('word/media/'))
if not medias:
print(" 未找到任何图片!")
return
for name in medias:
data = z.read(name)
s2 = _image_stats(data)
same = "一致" if data == processed else "不一致"
print(f" [{name}] 尺寸={s2['size']} 内容={s2['content_pct']:.1f}% "
f"alpha={s2['alpha_pct']:.1f}% bbox={s2['bbox']} sha={s2['sha256']} 字节={same}")
print(f" 原始 {len(processed)} bytes vs docx {len(data)} bytes")
if __name__ == '__main__':
main()