# -*- coding: utf-8 -*- """裁掉配图四周的纯白边,避免插入 Word 后出现大片留白。 原图统一备份到 _bid_assets/_raw/,重复执行时从备份重新裁剪,不会越裁越小。 """ import os import shutil from PIL import Image, ImageChops HERE = os.path.dirname(os.path.abspath(__file__)) ASSETS = os.path.join(HERE, "_bid_assets") RAW = os.path.join(ASSETS, "_raw") os.makedirs(RAW, exist_ok=True) PAD = 12 # 裁剪后保留的白边像素 # 个别抽取图自带「图 xxx」标题行,与 Word 图题重复,按比例切掉顶部 TOP_CROP_RATIO = {"s0_master_dataflow.png": 0.055} def trim_box(im, tol=8): bg = Image.new("RGB", im.size, (255, 255, 255)) diff = ImageChops.difference(im, bg).convert("L") box = diff.point(lambda v: 255 if v > tol else 0).getbbox() if not box: return im l, t, r, b = box return im.crop((max(0, l - PAD), max(0, t - PAD), min(im.width, r + PAD), min(im.height, b + PAD))) def trim(path, tol=8): """tol:接近纯白(每通道差值 ≤ tol)的像素一律视为背景。 抽取自蓝图文档的图并非纯白底,直接与 #FFFFFF 比对会判定整幅都是内容。 """ im = Image.open(path).convert("RGB") bg = Image.new("RGB", im.size, (255, 255, 255)) diff = ImageChops.difference(im, bg).convert("L") box = diff.point(lambda v: 255 if v > tol else 0).getbbox() if not box: return None l, t, r, b = box l, t = max(0, l - PAD), max(0, t - PAD) r, b = min(im.width, r + PAD), min(im.height, b + PAD) if (l, t, r, b) == (0, 0, im.width, im.height): return None return im.crop((l, t, r, b)) def main(): # ui_* 是系统真实界面截图,边缘留白属于界面本身,不做裁剪 names = sorted(n for n in os.listdir(ASSETS) if n.lower().endswith(".png") and not n.startswith("ui_")) print(f"共 {len(names)} 张配图") for n in names: src = os.path.join(ASSETS, n) raw = os.path.join(RAW, n) if not os.path.exists(raw): shutil.copy2(src, raw) before = Image.open(raw).size out = trim(raw) ratio = TOP_CROP_RATIO.get(n) if ratio: im = out if out is not None else Image.open(raw).convert("RGB") cut = int(im.height * ratio) out = trim_box(im.crop((0, cut, im.width, im.height))) if out is None: print(f" {n:<28} {before[0]}x{before[1]} 无需裁剪") continue out.save(src) print(f" {n:<28} {before[0]}x{before[1]} → {out.width}x{out.height}") if __name__ == "__main__": main()