Files
MeowOS-SDK/sdk/tools/gen_action_icons.py
T
AskaEth 4b094d62a5 sdk: 增量同步主仓新模块(box/anim/scroll/dispatch/listvirt/str/sb_icons/img)
subtree pull sdk 来自主仓 pub/sdk(主仓 commit 68ce46a 等)。新增:
- 布局引擎 meow_ui_box / 动画基座 meow_ui_anim / 滚动物理 meow_ui_scroll
- §6.4 事件分发形式化 / W_LISTVIEW 虚拟化多行 / meow_str 字符串资源 i18n
- sb_icons 动作图标扩充 + topbar 位图图标 / meow_img_downscale
- tools/ 图标生成脚本与 SVG 源
- 对应宿主 test_*.c 与 demo_box_scroll.c

build_sdk.sh 冲突解析:保留公开仓自包含版(vendored meow_font + -Iinclude -I.),
OBJS 手工并入 4 个新模块(meow_ui_box/anim/scroll/str)。保留 vendored meow_font.c/
meow_ipc.h/meow_font.h/meow_notif.h,剔除宿主测试二进制与编译产物。
2026-09-02 09:08:51 +08:00

203 lines
9.6 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.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""gen_action_icons.py — 从 Material Symbols Rounded 官方 SVG 生成 sb_icons.h 位图动作图标。
背景:MeowOS SDK topbar(W_TOPAPPBAR)动作此前用字体码点(⚙↻⌕)绘制,4k 字库无这些码点→豆腐。
正确路线 = 位图图标:与状态栏同一张 sb_icons[](Material Symbols Rounded 32x32 单通道 alpha),
动作项经 meow_topappbar_set_item_icon(w, idx, MEOW_ICON_*) 指定索引后即画位图。
版权/来源:图标位图源自 google/material-design-icons 的 Material Symbols Rounded(Apache-2.0)。
本脚本只做下载(或读缓存)+ 栅格化成 32x32 单通道 alpha + 拼接 C 数组。
生成方式:svg(24px 网格,viewBox 0 -960 960 960)→ cairosvg 512x512 → LANCZOS 降到 32x32
取 alpha 通道(每像素 0..255 亮度/alpha,与状态栏同源——绘制颜色由调用方主题表提供)。
用 FILL=1(实心)以匹配现有状态栏图标的实心风格(WiFi/BT/BATT 均为实心)。
可复现性:SVG 缓存于本目录 icons_src/{symbol}_fill1_24px.svg(缺则联网下载,命中缓存即断网可跑);
栅格参数(512/32/LANCZOS/FILL=1)固定 → 重跑输出一致。
用法(在 patch_v2/sdk/ 下):
python3 tools/gen_action_icons.py # 用 venv 的 cairosvg 运行
依赖:Python3 + Pillow + cairosvg(isolated venv:python3 -m venv <v> && <v>/bin/pip install cairosvg)。
输出:重写 patch_v2/sdk/sb_icons.h——保留现有 15 个状态栏位图(0-14,原样),追加 15 个动作
图标(15-29),索引兼容(既有索引不变,只追加)。
"""
import io
import os
import sys
import time
import urllib.request
try:
import cairosvg
from PIL import Image
except Exception: # noqa
sys.exit("缺少 cairosvg / Pillow,请先安装:python3 -m venv <v> && <v>/bin/pip install cairosvg")
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
SDK_DIR = os.path.abspath(os.path.join(THIS_DIR, os.pardir))
SB_ICONS_H = os.path.join(SDK_DIR, "sb_icons.h")
SRC_DIR = os.path.join(THIS_DIR, "icons_src")
OUT_RES = 512 # cairosvg 栅格目标边长(先大后小 → 抗锯齿 alpha)
TARGET = 32 # 最终位图边长(= SB_ICON_SIZE)
FILL = 1 # Material Symbols FILL 轴:1=实心(与状态栏一致)
BASE_URL = "https://raw.githubusercontent.com/google/material-design-icons/master/symbols/web/"
UA = {"User-Agent": "Mozilla/5.0"}
# 现有状态栏图标 C 数组名(0-14,按 sb_icons[] 现有顺序;勿改序)。
# 本脚本不改动它们(原样保留),仅在生成表格/枚举时按此顺序引用。
BASE_ICONS = [
"SB_ICON_WIFI", "SB_ICON_BT", "SB_ICON_BATT", "SB_ICON_VOL", "SB_ICON_BRIGHT",
"SB_ICON_WIFI_STRONG", "SB_ICON_WIFI_MEDIUM", "SB_ICON_WIFI_WEAK",
"SB_ICON_WIFI_NO", "SB_ICON_WIFI_OFF", "SB_ICON_BT_NO", "SB_ICON_BT_CONN",
"SB_ICON_BT_DIS", "SB_ICON_BT_SEARCH", "SB_ICON_BATT_CHG",
]
# 动作图标:c_array_name, enum_name, material_symbols 图标名。索引 15-29,勿改序。
ACTIONS = [
("SB_ICON_SETTINGS", "SB_I_SETTINGS", "settings"),
("SB_ICON_REFRESH", "SB_I_REFRESH", "refresh"),
("SB_ICON_SEARCH", "SB_I_SEARCH", "search"),
("SB_ICON_PLAY_ARROW", "SB_I_PLAY_ARROW", "play_arrow"),
("SB_ICON_PAUSE", "SB_I_PAUSE", "pause"),
("SB_ICON_SKIP_NEXT", "SB_I_SKIP_NEXT", "skip_next"),
("SB_ICON_SKIP_PREV", "SB_I_SKIP_PREV", "skip_previous"),
("SB_ICON_ARROW_BACK", "SB_I_ARROW_BACK", "arrow_back"),
("SB_ICON_CLOSE", "SB_I_CLOSE", "close"),
("SB_ICON_ADD", "SB_I_ADD", "add"),
("SB_ICON_CHECK", "SB_I_CHECK", "check"),
("SB_ICON_LYRICS", "SB_I_LYRICS", "lyrics"),
("SB_ICON_QUEUE_MUSIC", "SB_I_QUEUE_MUSIC", "queue_music"),
("SB_ICON_DOWNLOAD", "SB_I_DOWNLOAD", "download"),
("SB_ICON_HEART", "SB_I_HEART", "favorite"), # Material Symbols 用 "favorite" 表心形
]
HEADER_COMMENT = """/* sb_icons.h — 状态栏 + 动作位图图标(Material Symbols Rounded, MD3),单通道 alpha 位图 32x32。
* 生成自 material-symbols rounded(google/material-design-icons,Apache-2.0)SVG 栅格化:
* 图标族 = FILL=1 实心(与状态栏 WiFi/BT/BATT 现有实心风格一致);每像素 0..255(亮度/alpha)。
* ★ 为日/夜间预留:位图只存亮度;绘制颜色由调用方主题表提供,换主题仅改配色。
* 索引布局(兼容):状态栏 SB_I_WIFI=0..SB_I_BATT_CHG=14(原样保留);TopAppBar 动作
* MEOW_ICON_SETTINGS=15..MEOW_ICON_HEART=29(只追加,既有索引不变)。动作图标为 15-29,
* 经 meow_topappbar_set_item_icon(w, idx, MEOW_ICON_*) 使 W_TOPAPPBAR 动作项画位图。
* 重新生成:python3 tools/gen_action_icons.py(SVG 缓存 tools/icons_src/,可复现)。 */
#ifndef SB_ICONS_H
#define SB_ICONS_H
#define SB_ICON_SIZE 32
typedef struct { const unsigned char *data; } sb_icon_t;
"""
ACTION_BLOCK_MARKER = "/* ===== TopAppBar \u52a8\u4f5c\u56fe\u6807" # "/* ===== TopAppBar 动作图标"
def fetch(url, path, retries=6):
"""带重试的下载(网络不稳):命中缓存直接返回,失败退避重试。"""
if os.path.exists(path) and os.path.getsize(path) > 200:
return open(path, "rb").read()
for k in range(retries):
try:
req = urllib.request.Request(url, headers=UA)
data = urllib.request.urlopen(req, timeout=25).read()
if len(data) < 200:
raise ValueError("too small %d" % len(data))
open(path, "wb").write(data)
return data
except Exception as e: # noqa
sys.stderr.write(" retry %d %s: %s\n" % (k, path, repr(e)[:80]))
time.sleep(1.5 * (k + 1))
raise RuntimeError("download failed: " + url)
def svg_path_for(symbol):
return os.path.join(SRC_DIR, symbol + "_fill%d_24px.svg" % FILL)
def ensure_svg(symbol):
path = svg_path_for(symbol)
if not (os.path.exists(path) and os.path.getsize(path) > 200):
url = BASE_URL + symbol + "/materialsymbolsrounded/" + symbol + "_fill%d_24px.svg" % FILL
fetch(url, path)
return path
def rasterize_alpha32(symbol):
"""SVG → 32x32 单通道 alpha(0..255)。512 渲大图再 LANCZOS 降采样,得到平滑边缘。"""
svg = open(ensure_svg(symbol), "rb").read()
png = cairosvg.svg2png(bytestring=svg, output_width=OUT_RES, output_height=OUT_RES)
alpha = Image.open(io.BytesIO(png)).convert("RGBA").getchannel("A")
return alpha.resize((TARGET, TARGET), Image.LANCZOS)
def fmt_alpha_array(name, alpha):
"""把 32x32 alpha 排成 C 数组(每行 32 个像素,便于目检)。"""
lines = ["static const unsigned char %s[%d]={" % (name, TARGET * TARGET)]
for y in range(TARGET):
row = ",".join(str(alpha.getpixel((x, y))) for x in range(TARGET))
lines.append(row + ",")
lines.append("};")
return "\n".join(lines)
def build_action_block():
outs = [ACTION_BLOCK_MARKER + "(Material Symbols Rounded, MD3, Apache-2.0)====\n"]
outs.append(" * 复用状态栏同源 32x32 单通道 alpha;绘制颜色/按压态由 meow_ui_widget.c 主题表提供。 */\n")
for cname, _enum, symbol in ACTIONS:
a = rasterize_alpha32(symbol)
outs.append(fmt_alpha_array(cname, a))
return "\n".join(outs)
def build_table_and_enum():
table = ["static const sb_icon_t sb_icons[] = {"]
for name in BASE_ICONS:
table.append(" { %s }," % name)
for cname, _enum, _symbol in ACTIONS:
table.append(" { %s }," % cname)
table.append("};")
table = "\n".join(table)
enum = ["/* 枚举索引(与 sb_icons[] 对应)。状态栏 0-14(勿改序);动作 15-29(TopAppBar 用)。 */"]
enum.append("enum { SB_I_WIFI=0, SB_I_BT=1, SB_I_BATT=2, SB_I_VOL=3, SB_I_BRIGHT=4,")
enum.append(" SB_I_WIFI_STRONG=5, SB_I_WIFI_MEDIUM=6, SB_I_WIFI_WEAK=7,")
enum.append(" SB_I_WIFI_NO=8, SB_I_WIFI_OFF=9, SB_I_BT_NO=10, SB_I_BT_CONN=11,")
enum.append(" SB_I_BT_DIS=12, SB_I_BT_SEARCH=13, SB_I_BATT_CHG=14,")
# 动作枚举从 15 起,与 meow_ui_widgets.h 的 MEOW_ICON_* 同值(勿改序)
for i, (_c, e, _s) in enumerate(ACTIONS):
last = (i == len(ACTIONS) - 1)
enum.append(" %s=%d%s" % (e, 15 + i, " }; " if last else ","))
return table + "\n\n" + "\n".join(enum) + "\n\n#endif /* SB_ICONS_H */\n"
def regenerate_sb_icons_h():
old = open(SB_ICONS_H, encoding="utf-8").read()
# body = 现有状态栏数组(原样保留)。HEADER_COMMENT 已提供 ifndef/define/size/typedef,
# 故从首个状态栏数组起切;终点:优先动作块标记(幂等重跑),缺失则 sb_icons[] 表格(首跑)。
body_start = old.find("static const unsigned char " + BASE_ICONS[0] + "[1024]")
if body_start < 0:
sys.exit("sb_icons.h 缺少首个状态栏数组,拒绝重写")
pr = old.find(ACTION_BLOCK_MARKER)
if pr < 0:
pr = old.find("static const sb_icon_t sb_icons[]")
if pr < 0 or pr <= body_start:
sys.exit("sb_icons.h 缺少 sb_icons[] 表格,拒绝重写")
body = old[body_start:pr]
out = HEADER_COMMENT + body.rstrip("\n") + "\n\n"
out += build_action_block() + "\n\n"
out += build_table_and_enum()
open(SB_ICONS_H, "w", encoding="utf-8").write(out)
print("wrote %s (%d bytes)" % (SB_ICONS_H, len(out)))
def main():
os.makedirs(SRC_DIR, exist_ok=True)
regenerate_sb_icons_h()
if __name__ == "__main__":
main()