_bid_fix_shot_footer.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. # -*- coding: utf-8 -*-
  2. """把系统界面截图页脚的版权署名改为投标主体名称。
  3. 截图取自已交付系统,页脚原署名为关联主体,与本项目投标主体不一致,
  4. 易引起评审对主体关系的疑问,故统一改为投标主体名称。
  5. 仅改动页脚一行文字,界面内容不作任何修改。
  6. """
  7. import glob
  8. import io
  9. import os
  10. import shutil
  11. import sys
  12. from PIL import Image, ImageDraw, ImageFont
  13. sys.stdout.reconfigure(encoding="utf-8", errors="replace")
  14. HERE = os.path.dirname(os.path.abspath(__file__))
  15. ASSETS = os.path.join(HERE, "_bid_assets")
  16. BACKUP = os.path.join(ASSETS, "_orig_footer")
  17. NEW_TEXT = "Copyright © 2026 北京智造易科技有限公司 Ai-DOP 企业智慧运营管理系统"
  18. FONT_CANDIDATES = [r"C:\Windows\Fonts\msyh.ttc", r"C:\Windows\Fonts\msyh.ttf",
  19. r"C:\Windows\Fonts\simhei.ttf"]
  20. SCAN_FROM = 0.90 # 只在图片下方 10% 内寻找页脚文字
  21. SCAN_X = (0.30, 0.72) # 横向只扫内容区中段,避开左侧菜单同高的菜单项
  22. DARK = 205 # 低于该灰度视为文字像素
  23. def load_font(size):
  24. for p in FONT_CANDIDATES:
  25. if os.path.exists(p):
  26. try:
  27. return ImageFont.truetype(p, size)
  28. except OSError:
  29. continue
  30. raise SystemExit("未找到可用中文字体")
  31. def find_footer(im):
  32. """返回页脚文字的包围盒与背景色。"""
  33. w, h = im.size
  34. px = im.load()
  35. y_start = int(h * SCAN_FROM)
  36. xa, xb = int(w * SCAN_X[0]), int(w * SCAN_X[1])
  37. rows = []
  38. for y in range(y_start, h):
  39. dark = [x for x in range(xa, xb, 2)
  40. if sum(px[x, y][:3]) / 3 < DARK]
  41. if dark:
  42. rows.append((y, min(dark), max(dark)))
  43. if not rows:
  44. return None, None
  45. # 扫描区内可能同时包含分页栏等内容,按空白行切分后只取最底部那一行文字
  46. runs, cur = [], [rows[0]]
  47. for r in rows[1:]:
  48. if r[0] - cur[-1][0] <= 3:
  49. cur.append(r)
  50. else:
  51. runs.append(cur)
  52. cur = [r]
  53. runs.append(cur)
  54. rows = runs[-1]
  55. y0, y1 = rows[0][0], rows[-1][0]
  56. x0 = min(r[1] for r in rows)
  57. x1 = max(r[2] for r in rows) + 2
  58. # 背景色取内容区页脚内文字左侧的空白处
  59. bg_x = xa + 2 if x0 > xa + 8 else max(xa - 40, 0)
  60. bg = px[bg_x, (y0 + y1) // 2][:3]
  61. # 文字色取包围盒内最深像素
  62. fg = min((px[x, y][:3] for y, a, b in rows for x in range(a, b + 1, 3)),
  63. key=lambda c: sum(c))
  64. return (x0, y0, x1, y1), (bg, fg)
  65. def fix(path, log):
  66. im = Image.open(path).convert("RGB")
  67. box, colors = find_footer(im)
  68. if box is None:
  69. log.write(f"{os.path.basename(path)}\t未找到页脚文字,跳过\n")
  70. return False
  71. (x0, y0, x1, y1), (bg, fg) = box, colors
  72. d = ImageDraw.Draw(im)
  73. d.rectangle((x0 - 6, y0 - 6, x1 + 6, y1 + 6), fill=bg)
  74. # 用原文字高度反推字号,保证与界面其它文字观感一致
  75. size = max(12, round((y1 - y0) * 1.15))
  76. font = load_font(size)
  77. tw = d.textlength(NEW_TEXT, font=font)
  78. cx = (x0 + x1) / 2
  79. d.text((cx - tw / 2, y0 - 2), NEW_TEXT, font=font, fill=fg)
  80. im.save(path)
  81. log.write(f"{os.path.basename(path)}\t页脚 y={y0}~{y1} 字号={size} 已替换\n")
  82. return True
  83. def main():
  84. os.makedirs(BACKUP, exist_ok=True)
  85. names = sorted(glob.glob(os.path.join(ASSETS, "ui_*.png")))
  86. log = io.open(os.path.join(HERE, "_bid_footer_fix.log"), "w",
  87. encoding="utf-8")
  88. done = 0
  89. for p in names:
  90. bak = os.path.join(BACKUP, os.path.basename(p))
  91. if not os.path.exists(bak):
  92. shutil.copy2(p, bak) # 保留原图,便于回退与复核
  93. else:
  94. shutil.copy2(bak, p) # 重跑时从原图开始,避免叠加
  95. if fix(p, log):
  96. done += 1
  97. log.close()
  98. print(f"共处理 {done}/{len(names)} 张,原图备份于 {BACKUP}")
  99. if __name__ == "__main__":
  100. main()