Files
bid-doctor/docs/design/logo/scripts/process_seal.py
YANG JIANKUAN 5753b51131 docs(logo): 新增 logo v5.1 正式资产、使用规范与构建脚本
- 白文印「标」(AI 生成原图经脚本抠图、统一为 #D9291C、矢量化)
- 字标:思源宋体 SemiBold + Libre Caslon;比例 H2,英文视觉内缩 C2
- svg/、png/、favicon:横版、竖版、反白、单色、印章、各尺寸图标
- scripts/:logo_lib.py 统一排版参数,build_assets.sh 一键生成
- logo-spec.md、logo-sheet.html、字体许可证(OFL)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:28:18 +08:00

98 lines
4.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""把位图生成的印章处理成 logo 资产:抠图、统一颜色、矢量化、小尺寸版本。
用法:.work/venv/bin/python scripts/process_seal.py <源图> <输出目录> [品牌红 HEX]
"""
import sys
from pathlib import Path
import numpy as np
import potrace
from PIL import Image
from scipy import ndimage
src, out_dir = Path(sys.argv[1]), Path(sys.argv[2])
brand = sys.argv[3] if len(sys.argv) > 3 else "#D9291C"
out_dir.mkdir(parents=True, exist_ok=True)
rgb = tuple(int(brand[i:i + 2], 16) for i in (1, 3, 5))
im = np.asarray(Image.open(src).convert("RGB")).astype(float)
redness = (im[..., 0] - (im[..., 1] + im[..., 2]) / 2) / 255
# 印泥浓淡 → 透明度:保留颗粒和毛边,背景完全透明
alpha = np.clip((redness - 0.12) / (0.55 - 0.12), 0, 1)
alpha = alpha * alpha * (3 - 2 * alpha) # smoothstep,让颗粒边缘柔和
ys, xs = np.where(redness > 0.35)
y0, y1, x0, x1 = ys.min(), ys.max() + 1, xs.min(), xs.max() + 1
side = max(y1 - y0, x1 - x0)
pad = round(side * 0.02)
canvas = side + 2 * pad
cy, cx = (y0 + y1) // 2, (x0 + x1) // 2
half = canvas // 2
def crop(a):
out = np.zeros((canvas, canvas), dtype=a.dtype)
sy0, sx0 = cy - half, cx - half
ys_, xs_ = slice(max(sy0, 0), min(sy0 + canvas, a.shape[0])), slice(max(sx0, 0), min(sx0 + canvas, a.shape[1]))
out[ys_.start - sy0:ys_.stop - sy0, xs_.start - sx0:xs_.stop - sx0] = a[ys_, xs_]
return out
alpha_c = crop(alpha)
red_c = crop(redness)
# 1. 质感主图(透明 PNG)
rgba = np.zeros((canvas, canvas, 4), dtype=np.uint8)
rgba[..., :3] = rgb
rgba[..., 3] = (alpha_c * 255).round().astype(np.uint8)
Image.fromarray(rgba).save(out_dir / "seal-textured.png")
on_white = np.full((canvas, canvas, 3), 255.0)
on_white = on_white * (1 - alpha_c[..., None]) + np.array(rgb) * alpha_c[..., None]
Image.fromarray(on_white.round().astype(np.uint8)).save(out_dir / "seal-textured-on-white.png")
def trace(mask, name, turdsize, alphamax=1.0, fill=brand):
bm = potrace.Bitmap(~mask) # potracer 把 False 当作前景,这里取反
path = bm.trace(turdsize=turdsize, alphamax=alphamax, opticurve=True, opttolerance=0.2)
parts = []
for curve in path:
s = curve.start_point
d = [f"M{s.x:.1f} {s.y:.1f}"]
for seg in curve.segments:
if seg.is_corner:
d.append(f"L{seg.c.x:.1f} {seg.c.y:.1f}L{seg.end_point.x:.1f} {seg.end_point.y:.1f}")
else:
d.append(f"C{seg.c1.x:.1f} {seg.c1.y:.1f} {seg.c2.x:.1f} {seg.c2.y:.1f} {seg.end_point.x:.1f} {seg.end_point.y:.1f}")
d.append("Z")
parts.append("".join(d))
svg = (f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {canvas} {canvas}">'
f'<path fill="{fill}" fill-rule="evenodd" d="{"".join(parts)}"/></svg>')
(out_dir / name).write_text(svg)
return len(svg)
# 2. 质感矢量版:放大 2 倍再描摹,保留颗粒
big = np.asarray(Image.fromarray((alpha_c * 255).astype(np.uint8)).resize((canvas * 2, canvas * 2), Image.LANCZOS)) / 255
n_tex = trace(big > 0.5, "seal-textured-2x.svg", turdsize=6)
# 描摹坐标是 2 倍的,修正 viewBox
p = out_dir / "seal-textured-2x.svg"
p.write_text(p.read_text().replace(f'viewBox="0 0 {canvas} {canvas}"', f'viewBox="0 0 {canvas * 2} {canvas * 2}"'))
p.rename(out_dir / "seal-textured.svg")
# 3. 干净版:先模糊去掉颗粒,再二值化描摹,得到平滑的印面和字形
solid = ndimage.gaussian_filter((red_c > 0.35).astype(float), sigma=canvas * 0.006) > 0.5
solid = ndimage.binary_fill_holes(solid) & solid # 保留白字(它们与外部不连通时是「洞」)
solid = ndimage.binary_opening(solid, iterations=2)
n_clean = trace(solid, "seal-clean.svg", turdsize=canvas * 0.0005 * canvas, alphamax=1.1)
# 4. 小尺寸版:白色笔画加粗(墨色腐蚀),保证 32px 下笔画不消失
thick = ndimage.binary_erosion(solid, iterations=max(2, round(canvas * 0.012)))
n_small = trace(thick, "seal-small.svg", turdsize=canvas * 0.0008 * canvas, alphamax=1.1)
# 5. 纸底:印面外轮廓(不含白字),向内收一点,深色背景上垫在印章下面,让白字和颗粒显示为纸色
paper = ndimage.binary_erosion(ndimage.binary_fill_holes(solid), iterations=max(2, round(canvas * 0.006)))
trace(paper, "seal-paper.svg", turdsize=canvas * 0.001 * canvas, alphamax=1.2, fill="#FFFFFF")
print(f"canvas={canvas}px brand={brand} textured.svg={n_tex // 1024}KB clean.svg={n_clean // 1024}KB small.svg={n_small // 1024}KB")