first commit
This commit is contained in:
150
image_test/e2e_test.py
Normal file
150
image_test/e2e_test.py
Normal file
@@ -0,0 +1,150 @@
|
||||
"""端到端验证: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,正文段落使用偏大行高。
|
||||
|
||||
段落行高 2cm(lineRule=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()
|
||||
Reference in New Issue
Block a user