first commit

This commit is contained in:
lihansani
2026-07-20 13:16:17 +08:00
commit 7affeaa253
235 changed files with 42351 additions and 0 deletions

150
image_test/e2e_test.py Normal file
View 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正文段落使用偏大行高。
段落行高 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()