_bid_trim_figs.py 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. # -*- coding: utf-8 -*-
  2. """裁掉配图四周的纯白边,避免插入 Word 后出现大片留白。
  3. 原图统一备份到 _bid_assets/_raw/,重复执行时从备份重新裁剪,不会越裁越小。
  4. """
  5. import os
  6. import shutil
  7. from PIL import Image, ImageChops
  8. HERE = os.path.dirname(os.path.abspath(__file__))
  9. ASSETS = os.path.join(HERE, "_bid_assets")
  10. RAW = os.path.join(ASSETS, "_raw")
  11. os.makedirs(RAW, exist_ok=True)
  12. PAD = 12 # 裁剪后保留的白边像素
  13. # 个别抽取图自带「图 xxx」标题行,与 Word 图题重复,按比例切掉顶部
  14. TOP_CROP_RATIO = {"s0_master_dataflow.png": 0.055}
  15. def trim_box(im, tol=8):
  16. bg = Image.new("RGB", im.size, (255, 255, 255))
  17. diff = ImageChops.difference(im, bg).convert("L")
  18. box = diff.point(lambda v: 255 if v > tol else 0).getbbox()
  19. if not box:
  20. return im
  21. l, t, r, b = box
  22. return im.crop((max(0, l - PAD), max(0, t - PAD),
  23. min(im.width, r + PAD), min(im.height, b + PAD)))
  24. def trim(path, tol=8):
  25. """tol:接近纯白(每通道差值 ≤ tol)的像素一律视为背景。
  26. 抽取自蓝图文档的图并非纯白底,直接与 #FFFFFF 比对会判定整幅都是内容。
  27. """
  28. im = Image.open(path).convert("RGB")
  29. bg = Image.new("RGB", im.size, (255, 255, 255))
  30. diff = ImageChops.difference(im, bg).convert("L")
  31. box = diff.point(lambda v: 255 if v > tol else 0).getbbox()
  32. if not box:
  33. return None
  34. l, t, r, b = box
  35. l, t = max(0, l - PAD), max(0, t - PAD)
  36. r, b = min(im.width, r + PAD), min(im.height, b + PAD)
  37. if (l, t, r, b) == (0, 0, im.width, im.height):
  38. return None
  39. return im.crop((l, t, r, b))
  40. def main():
  41. # ui_* 是系统真实界面截图,边缘留白属于界面本身,不做裁剪
  42. names = sorted(n for n in os.listdir(ASSETS)
  43. if n.lower().endswith(".png") and not n.startswith("ui_"))
  44. print(f"共 {len(names)} 张配图")
  45. for n in names:
  46. src = os.path.join(ASSETS, n)
  47. raw = os.path.join(RAW, n)
  48. if not os.path.exists(raw):
  49. shutil.copy2(src, raw)
  50. before = Image.open(raw).size
  51. out = trim(raw)
  52. ratio = TOP_CROP_RATIO.get(n)
  53. if ratio:
  54. im = out if out is not None else Image.open(raw).convert("RGB")
  55. cut = int(im.height * ratio)
  56. out = trim_box(im.crop((0, cut, im.width, im.height)))
  57. if out is None:
  58. print(f" {n:<28} {before[0]}x{before[1]} 无需裁剪")
  59. continue
  60. out.save(src)
  61. print(f" {n:<28} {before[0]}x{before[1]} → {out.width}x{out.height}")
  62. if __name__ == "__main__":
  63. main()