feat: 沙香科技官网 v2.2 基线(深色科技风/双语/局域网+公网双部署)
This commit is contained in:
119
tools/process_images.py
Normal file
119
tools/process_images.py
Normal file
@@ -0,0 +1,119 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
从 PPT 提取的原始素材生成网站用图:
|
||||
1. logo 与 7 张黑底产品图:边缘 flood-fill 镂空(背景变透明,适配深色网页)
|
||||
2. 水培架图背景复杂(39% 均匀度),不镂空,走统一描边容器展示
|
||||
3. 截图/场景/照片转 WebP 压缩;二维码保持 PNG 无损
|
||||
用法: uv run --with pillow python3 tools/process_images.py <pptx解压目录>/ppt/media <输出目录>
|
||||
"""
|
||||
import sys
|
||||
from collections import deque
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image
|
||||
|
||||
src = Path(sys.argv[1])
|
||||
out = Path(sys.argv[2])
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
RENAME = {
|
||||
'image2.png': 'logo.png', # 原样无损输出(见 PASS_THROUGH)
|
||||
'image1.jpeg': 'hero-bg.webp', # v2 起不再做 hero 背景,仅作 og:image
|
||||
'image3.png': 'about-main.webp', # v2 起弃用(PPT 拼贴图有白缝)
|
||||
'image4.png': 'about-base.webp',
|
||||
'image5.png': 'collar-sg03-mini.webp',
|
||||
'image13.png': 'collar-sg03.webp',
|
||||
'image10.png': 'ear-tag-st01.webp',
|
||||
'image11.png': 'base-station-bg01.webp',
|
||||
'image12.png': 'grazing-scene-1.webp',
|
||||
'image18.png': 'grazing-scene-2.webp',
|
||||
'image19.png': 'hydroponics-hs01.webp',
|
||||
'image20.png': 'planting-scene.webp',
|
||||
'image31.png': 'terminal-bb03.webp',
|
||||
'image32.png': 'uwb-tag-sl01.webp',
|
||||
'image33.png': 'bt-tag-st02.webp',
|
||||
'image29.png': 'asset-scene-1.webp',
|
||||
'image30.jpeg': 'asset-scene-2.webp',
|
||||
'image34.png': 'platform-grazing-1.webp',
|
||||
'image35.png': 'platform-grazing-app.webp',
|
||||
'image36.png': 'platform-planting-1.webp',
|
||||
'image37.png': 'platform-planting-2.webp',
|
||||
'image38.png': 'platform-asset-1.webp',
|
||||
'image39.png': 'platform-asset-2.webp',
|
||||
'image40.png': 'custom-liangchang-1.webp',
|
||||
'image41.png': 'custom-liangchang-2.webp',
|
||||
'image42.png': 'custom-simulation.webp',
|
||||
'image43.png': 'custom-agent.webp',
|
||||
'image44.png': 'qrcode.png',
|
||||
}
|
||||
|
||||
# 这些图做边缘 flood-fill 镂空(检测过:边缘色 96-100% 均匀的纯黑底)
|
||||
# 注意:logo 不镂空——其底色 #000020 与四角检测色差超容差,v2 曾清成斑驳残留导致看不清
|
||||
CUTOUT = {
|
||||
'image5.png', 'image13.png', 'image10.png', 'image11.png',
|
||||
'image31.png', 'image32.png', 'image33.png',
|
||||
}
|
||||
CUTOUT_TOL = 30 # 曼哈顿距离阈值,宁小勿大:黑底纯度高,产品暗部 (#20 系) 距离约 96 不会被误清
|
||||
|
||||
def cutout_bg(path: Path, tol: int) -> Image.Image:
|
||||
"""从画布四边缘 flood-fill 清除均匀底色,只清与边缘连通的背景,不动被包围的内容"""
|
||||
im = Image.open(path).convert('RGBA')
|
||||
w, h = im.size
|
||||
px = im.load()
|
||||
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)
|
||||
# 镂空比例健全性检查:太低=没清干净,太高=误伤内容
|
||||
if not 0.10 < ratio < 0.92:
|
||||
raise SystemExit(f'镂空异常 {path.name}: 清除 {ratio:.0%},需人工检查')
|
||||
return im
|
||||
|
||||
# 原样无损拷贝:logo 色块文字经不起有损压缩,二维码必须无损
|
||||
PASS_THROUGH = {'image2.png', 'image44.png'}
|
||||
|
||||
for src_name, dst_name in RENAME.items():
|
||||
src_path = src / src_name
|
||||
if not src_path.exists():
|
||||
print(f' 跳过缺失: {src_name}')
|
||||
continue
|
||||
dst_path = out / dst_name
|
||||
if src_name in PASS_THROUGH:
|
||||
Image.open(src_path).save(dst_path)
|
||||
elif src_name in CUTOUT:
|
||||
im = cutout_bg(src_path, CUTOUT_TOL)
|
||||
# 镂空图必须带 alpha,webp 无损存 alpha(有损会弄脏半透明边缘)
|
||||
im.save(dst_path, 'WEBP', quality=100, lossless=True, method=6)
|
||||
else:
|
||||
im = Image.open(src_path).convert('RGB')
|
||||
if im.width > 1600:
|
||||
im = im.resize((1600, round(im.height * 1600 / im.width)), Image.LANCZOS)
|
||||
im.save(dst_path, 'WEBP', quality=82, method=6)
|
||||
kb = dst_path.stat().st_size // 1024
|
||||
tag = ' [镂空]' if src_name in CUTOUT else ''
|
||||
print(f' {src_name:14s} -> {dst_name:28s} {kb}KB{tag}')
|
||||
|
||||
print(f'完成,共 {len(RENAME)} 张 -> {out}')
|
||||
Reference in New Issue
Block a user