72 lines
2.4 KiB
Python
72 lines
2.4 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
生成深色站反白版 logo(logo-light.png):
|
||
1. flood-fill 镂空深蓝底(容差 84 = v1 验证成功参数,能吃掉 #000020 底色)
|
||
2. 不透明内容亮度反转 L' = 1 - L(黑字→白字,红/青/绿提亮,色相保留)
|
||
深色页黑字看不清、白底块又突兀,反白是深色站标准解法。
|
||
用法: uv run --with pillow python3 tools/make_logo_light.py
|
||
"""
|
||
import colorsys
|
||
from collections import deque
|
||
|
||
from PIL import Image
|
||
|
||
SRC = 'assets/img/logo.png'
|
||
OUT = 'assets/img/logo-light.png'
|
||
TOL = 84 # 曼哈顿距离容差(v1 实测:清除 83%、红色主体完整保留)
|
||
|
||
im = Image.open(SRC).convert('RGBA')
|
||
w, h = im.size
|
||
px = im.load()
|
||
|
||
# --- flood-fill 清底 ---
|
||
corners = [px[0, 0], px[w-1, 0], px[0, h-1], px[w-1, h-1]]
|
||
bg = tuple(sum(c[i] for c in corners) // 4 for i in range(3))
|
||
|
||
def is_bg(p):
|
||
if p[3] < 10:
|
||
return True
|
||
return abs(p[0]-bg[0]) + abs(p[1]-bg[1]) + abs(p[2]-bg[2]) <= TOL
|
||
|
||
seen = [[False]*w for _ in range(h)]
|
||
q = deque()
|
||
for x in range(w):
|
||
q.extend(((x, 0), (x, h-1)))
|
||
for y in range(h):
|
||
q.extend(((0, y), (w-1, y)))
|
||
cleared = 0
|
||
while q:
|
||
x, y = q.popleft()
|
||
if x < 0 or x >= w or y < 0 or y >= h or seen[y][x]:
|
||
continue
|
||
seen[y][x] = True
|
||
p = px[x, y]
|
||
if not is_bg(p):
|
||
continue
|
||
px[x, y] = (0, 0, 0, 0)
|
||
cleared += 1
|
||
q.extend(((x+1, y), (x-1, y), (x, y+1), (x, y-1)))
|
||
ratio = cleared / (w * h)
|
||
assert 0.10 < ratio < 0.92, f'镂空异常: 清除 {ratio:.0%}'
|
||
|
||
# --- 暗部提亮、亮部保留(logo 含 ~20% 浅色元素,全量反转会把它们变黑) ---
|
||
# L' = max(L, 1-L):暗像素翻到亮侧(黑→白),亮像素不动,色相全保留
|
||
opaque = [px[x, y] for y in range(h) for x in range(w) if px[x, y][3] > 128]
|
||
assert opaque, '镂空后内容为空'
|
||
|
||
for y in range(h):
|
||
for x in range(w):
|
||
r, g, b, a = px[x, y]
|
||
if a == 0:
|
||
continue
|
||
hh, ll, ss = colorsys.rgb_to_hls(r / 255, g / 255, b / 255)
|
||
r2, g2, b2 = colorsys.hls_to_rgb(hh, max(ll, 1 - ll), ss)
|
||
px[x, y] = (round(r2 * 255), round(g2 * 255), round(b2 * 255), a)
|
||
|
||
im.save(OUT, 'PNG', optimize=True)
|
||
|
||
# 验证:反白后所有不透明像素亮度下限
|
||
final = [px[x, y] for y in range(h) for x in range(w) if px[x, y][3] > 128]
|
||
dark = sum(1 for p in final if 0.299*p[0]+0.587*p[1]+0.114*p[2] < 90) / len(final)
|
||
print(f'完成: {OUT} 镂空 {ratio:.0%},暗部像素剩 {dark:.1%}(应为 0)')
|