K1Guitar/tools/gen_ui0902_assets.py

264 lines
13 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""K1 0902 UI assets -> RGB565 C arrays (MCU flash) + packed bin (W25Q128 external flash).
Outputs:
device/LCD_ILI9341/Drv_ILI9341_Lcd_Image_UI0902.c/.h small icons (internal flash)
project/inc/UI0902_Res_Addr.h external flash address map
tools/out/ui0902_res.bin packed images, base UI0902_RES_BASE
tools/out/preview_*.png page previews
"""
from PIL import Image, ImageDraw, ImageFont
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SRC = ROOT / "tools" / "assets_0902"
OUT = ROOT / "tools" / "out"
OUT.mkdir(exist_ok=True)
OUT_C = ROOT / "device" / "LCD_ILI9341" / "Drv_ILI9341_Lcd_Image_UI0902.c"
OUT_H = ROOT / "device" / "LCD_ILI9341" / "Drv_ILI9341_Lcd_Image_UI0902.h"
OUT_ADDR = ROOT / "project" / "inc" / "UI0902_Res_Addr.h"
OUT_BIN = OUT / "ui0902_res.bin"
RES_BASE = 0x00100000 # W25Q128 offset; avoids logo(0x0)/charge(0xCB70)/preset(<~0xA8B60) regions
ALIGN = 0x1000
# 模式选择页无底栏:三行按设计稿 0902-01 撑满(约 y=43/135/225高 88
ROW_RESIZE = (222, 88)
MISANS = r"c:\Users\qjyu\Documents\SoundWalker\一诺国际吉他\Doc\UI\MiSans\MiSans 字体\MiSans-Medium.ttf"
PAGE_BG = (0, 8, 19) # #000813
ROW_BG = (36, 43, 58) # #242B3A
SEL_BLUE = (3, 78, 213) # #034ED5
def rgba_to_rgb565(im):
im = im.convert("RGBA")
px = im.load()
w, h = im.size
out = bytearray(w * h * 2)
i = 0
for y in range(h):
for x in range(w):
r, g, b, a = px[x, y]
if a < 40 or (r < 12 and g < 12 and b < 12):
out[i] = 0
out[i + 1] = 0
else:
r = (r * a) // 255
g = (g * a) // 255
b = (b * a) // 255
v = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3)
out[i] = (v >> 8) & 0xFF
out[i + 1] = v & 0xFF
i += 2
return bytes(out), w, h
def rgba_to_rgb565_opaque(im):
"""Full-screen boot/charge: keep dark background pixels (no near-black punch-through)."""
im = im.convert("RGB")
px = im.load()
w, h = im.size
out = bytearray(w * h * 2)
i = 0
for y in range(h):
for x in range(w):
r, g, b = px[x, y]
v = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3)
out[i] = (v >> 8) & 0xFF
out[i + 1] = v & 0xFF
i += 2
return bytes(out), w, h
def emit_c_array(lines, name, data, w, h):
lines.append(f"const unsigned char {name}[{len(data)}] = {{ /* {w}x{h} RGB565 BE */")
for i in range(0, len(data), 16):
lines.append(",".join(f"0x{b:02X}" for b in data[i:i + 16]) + ",")
lines.append("};")
lines.append("")
def make_battery_outline():
"""电池图标-10 -> outline only (interior fill removed; runtime draws level fill)."""
im = Image.open(SRC / "02_顶部图标" / "电池图标-10.png").convert("RGBA")
px = im.load()
for y in range(2, 10):
for x in range(2, 27):
px[x, y] = (0, 0, 0, 0)
return im
def make_cancel_button():
"""Generate 取消 106x56 dialog button matching 控件-10 style."""
im = Image.new("RGBA", (106, 56), (0, 0, 0, 0))
d = ImageDraw.Draw(im)
d.rounded_rectangle([0, 0, 105, 55], radius=12, fill=ROW_BG + (255,))
f = ImageFont.truetype(MISANS, 24)
t = "取消"
bb = d.textbbox((0, 0), t, font=f)
d.text(((106 - (bb[2] - bb[0])) // 2 - bb[0], (56 - (bb[3] - bb[1])) // 2 - bb[1]),
t, fill=(255, 255, 255, 255), font=f)
return im
# ---------------- internal flash (small, high-frequency icons) ----------------
INTERNAL = [
("gImage_UI0902_Volume_20x15", "02_顶部图标/音量图标-10.png"),
("gImage_UI0902_Bluetooth_11x17", "02_顶部图标/蓝牙图标-10.png"),
("gImage_UI0902_BatteryFill_22x9", "02_顶部图标/电池电量-10-11.png"),
("gImage_UI0902_TabSetting_Sel_36x28", "03_底部图标/底部图标-10.png"),
("gImage_UI0902_TabSetting_Not_35x28", "03_底部图标/底部图标-11.png"),
("gImage_UI0902_TabMixer_Sel_35x27", "03_底部图标/底部图标-12.png"),
("gImage_UI0902_TabMixer_Not_18x28", "03_底部图标/底部图标-13.png"),
("gImage_UI0902_TabMode_Sel_36x28", "03_底部图标/底部图标-14.png"),
("gImage_UI0902_TabMode_Not_35x29", "03_底部图标/底部图标-15.png"),
("gImage_UI0902_TabBack_Sel_35x27", "03_底部图标/底部图标-16.png"),
("gImage_UI0902_TabBack_Not_18x28", "03_底部图标/底部图标-17.png"),
]
# ---------------- external flash (big images / text strips) ----------------
EXTERNAL = [
# Art numbering: 16/19=universal, 17/20=normal, 18/21=expert (not consecutive pairs)
("ROW_UNIVERSAL_NOT", "04_模式图标/完整版/模式界面栏背景-16.png"),
("ROW_UNIVERSAL_SEL", "04_模式图标/完整版/模式界面栏背景-19.png"),
("ROW_NORMAL_NOT", "04_模式图标/完整版/模式界面栏背景-17.png"),
("ROW_NORMAL_SEL", "04_模式图标/完整版/模式界面栏背景-20.png"),
("ROW_EXPERT_NOT", "04_模式图标/完整版/模式界面栏背景-18.png"),
("ROW_EXPERT_SEL", "04_模式图标/完整版/模式界面栏背景-21.png"),
("ICON_UNIVERSAL_SEL", "04_模式图标/万能模式图标-1.png"),
("ICON_UNIVERSAL_NOT", "04_模式图标/万能模式图标-2.png"),
("ICON_NORMAL_SEL", "04_模式图标/普通模式图标-3.png"),
("ICON_NORMAL_NOT", "04_模式图标/普通模式图标-4.png"),
("ICON_EXPERT_SEL", "04_模式图标/专业模式图标-5.png"),
("ICON_EXPERT_NOT", "04_模式图标/专业模式图标-6.png"),
("TXT_MODE_UNIVERSAL_NOT", "04_模式图标/完整版/文字/模式选择文字-10.png"),
("TXT_MODE_UNIVERSAL_SEL", "04_模式图标/完整版/文字/模式选择文字-11.png"),
("TXT_MODE_NORMAL_NOT", "04_模式图标/完整版/文字/模式选择文字-12.png"),
("TXT_MODE_NORMAL_SEL", "04_模式图标/完整版/文字/模式选择文字-13.png"),
("TXT_MODE_EXPERT_NOT", "04_模式图标/完整版/文字/模式选择文字-14.png"),
("TXT_MODE_EXPERT_SEL", "04_模式图标/完整版/文字/模式选择文字-15.png"),
("ICON_SET_BLUETOOTH", "05_设置图标/设置图标-01.png"),
("ICON_SET_GEAR", "05_设置图标/设置图标-02.png"),
("ICON_CHEVRON", "05_设置图标/设置图标-03.png"),
("ROW_SET_BLUETOOTH", "05_设置图标/设置图标 完整-10.png"),
("ROW_SET_SYSTEM", "05_设置图标/设置图标 完整-11.png"),
("ICON_SYS_LANG", "06_控件/系统设置/系统设置图标-10.png"),
("ICON_SYS_RESTORE", "06_控件/系统设置/系统设置图标-11.png"),
("ICON_SYS_ABOUT", "06_控件/系统设置/系统设置图标-12.png"),
("ICON_SYS_VERSION", "06_控件/系统设置/系统设置图标-13.png"),
("TXT_SET_LANG", "06_控件/系统设置/文字/设置界面文字-14.png"),
("TXT_SET_RESTORE", "06_控件/系统设置/文字/设置界面文字-15.png"),
("TXT_SET_ABOUT", "06_控件/系统设置/文字/设置界面文字-16.png"),
("TXT_SET_VERSION", "06_控件/系统设置/文字/设置界面文字-17.png"),
("TXT_SET2_A", "06_控件/系统设置/文字/设置界面文字 - 二级-14.png"),
("TXT_SET2_B", "06_控件/系统设置/文字/设置界面文字 - 二级-15.png"),
("TXT_SET2_C", "06_控件/系统设置/文字/设置界面文字 - 二级-16.png"),
("TXT_SET2_D", "06_控件/系统设置/文字/设置界面文字 - 二级-17.png"),
("BTN_TYPE_ACOUSTIC_SEL", "06_控件/控件 类型选择-10.png"),
("BTN_TYPE_SING_NOT", "06_控件/控件 类型选择-11.png"),
("BTN_TYPE_FINGER_NOT", "06_控件/控件 类型选择-12.png"),
("BTN_TYPE_STRUM_NOT", "06_控件/控件 类型选择-13.png"),
("BTN_MINUS_BLUE", "06_控件/控件-11.png"),
("BTN_PLUS_BLUE", "06_控件/控件-12.png"),
("BTN_MINUS_GRAY", "06_控件/控件-13.png"),
("BTN_PLUS_GRAY", "06_控件/控件-14.png"),
("SLIDER_WIDGET", "06_控件/控件-15.png"),
("BTN_LANG_SEL", "06_控件/控件-16.png"),
("BTN_LANG_CN1", "06_控件/控件-17.png"),
("BTN_LANG_CN2", "06_控件/控件-18.png"),
("BTN_LANG_EN", "06_控件/控件-19.png"),
("BTN_CANCEL_SM", "06_控件/控件 2-20.png"),
("BTN_CONFIRM_SM", "06_控件/控件 2-21.png"),
("BTN_CANCEL_SM2", "06_控件/控件 2-22.png"),
("BTN_CONFIRM_SM2", "06_控件/控件 2-23.png"),
("BTN_CONFIRM_BIG", "06_控件/控件-10.png"),
("MIXER_KNOB", "06_控件/调音台控件/调音台控件-1.png"),
("MIXER_KNOB2", "06_控件/调音台控件/调音台控件-2.png"),
("MIXER_TRACK", "06_控件/调音台控件/调音台控件-3.png"),
("TXT_MIXER_MIC", "06_控件/调音台控件/文字/调音台文字-13.png"),
("TXT_MIXER_GUITAR", "06_控件/调音台控件/文字/调音台文字-14.png"),
("TXT_MIXER_BAND", "06_控件/调音台控件/文字/调音台文字-15.png"),
("TXT_MIXER_VOCALFX", "06_控件/调音台控件/文字/调音台文字-16.png"),
("TXT_MIXER_GUITARFX", "06_控件/调音台控件/文字/调音台文字-17.png"),
("TXT_EFFECT_AMOUNT", "06_控件/文字/文字-10.png"),
("TXT_BAND_VOLUME", "06_控件/文字/文字-11.png"),
("TXT_MIC_VOLUME", "06_控件/文字/文字-12.png"),
("TXT_RESTORE_V1", "06_控件/文字/文字-24.png"),
("TXT_RESTORE_V2", "06_控件/文字/文字-25.png"),
("TXT_RESTORE_H", "06_控件/文字/文字-26.png"),
("TXT_RESTORE_CONFIRM", "06_控件/文字/文字-27.png"),
# 整屏开机 Logo避开旧 0x0 / 充电 0xCB70 区,挂在 ui0902 资源包末尾)
("BOOT_LOGO", "07_开机/开机界面-20.png"),
]
def main():
# ---- internal flash C arrays ----
c_lines = ['#include "Drv_ILI9341_Lcd_Image_UI0902.h"', "",
"/* Auto-generated by tools/gen_ui0902_assets.py. Do not hand-edit. */", ""]
h_lines = ["#ifndef __DRV_ILI9341_LCD_IMAGE_UI0902_H",
"#define __DRV_ILI9341_LCD_IMAGE_UI0902_H", "",
'#include "stdint.h"', ""]
total_int = 0
data, w, h = rgba_to_rgb565(make_battery_outline())
emit_c_array(c_lines, "gImage_UI0902_BatteryOutline_29x12", data, w, h)
h_lines.append("extern const unsigned char gImage_UI0902_BatteryOutline_29x12[];")
total_int += len(data)
for name, rel in INTERNAL:
data, w, h = rgba_to_rgb565(Image.open(SRC / rel))
emit_c_array(c_lines, name, data, w, h)
h_lines.append(f"extern const unsigned char {name}[{len(data)}];")
total_int += len(data)
h_lines += ["", "#endif", ""]
OUT_H.write_text("\n".join(h_lines), encoding="utf-8")
OUT_C.write_text("\n".join(c_lines), encoding="utf-8")
# ---- external flash bin ----
addr_lines = ["#ifndef __UI0902_RES_ADDR_H", "#define __UI0902_RES_ADDR_H", "",
"/* Auto-generated by tools/gen_ui0902_assets.py. W25Q128 resource map. */",
f"#define UI0902_RES_BASE 0x{RES_BASE:08X}UL", ""]
blob = bytearray()
offset = 0
entries = {}
for name, rel in EXTERNAL:
im = Image.open(SRC / rel)
if name in ("ROW_UNIVERSAL_NOT", "ROW_UNIVERSAL_SEL", "ROW_NORMAL_NOT", "ROW_NORMAL_SEL", "ROW_EXPERT_NOT", "ROW_EXPERT_SEL"):
im = im.resize(ROW_RESIZE, Image.LANCZOS)
data, w, h = rgba_to_rgb565(im)
elif name == "BOOT_LOGO":
if im.size[0] >= 241 and im.size[1] >= 321:
im = im.crop((0, 0, 240, 320))
elif im.size != (240, 320):
im = im.resize((240, 320), Image.LANCZOS)
data, w, h = rgba_to_rgb565_opaque(im)
else:
data, w, h = rgba_to_rgb565(im)
entries[name] = (offset, w, h, len(data))
blob += data
pad = (-len(blob)) % ALIGN
blob += b"\x00" * pad
offset += len(data) + pad
# derived cancel button
data, w, h = rgba_to_rgb565(make_cancel_button())
entries["BTN_CANCEL_BIG"] = (offset, w, h, len(data))
blob += data
for name, (off, w, h, size) in entries.items():
addr_lines.append(f"#define UI0902_{name}_ADDR (UI0902_RES_BASE + 0x{off:06X}UL) /* {size} bytes */")
addr_lines.append(f"#define UI0902_{name}_W {w}")
addr_lines.append(f"#define UI0902_{name}_H {h}")
addr_lines.append("")
addr_lines += ["#endif", ""]
OUT_ADDR.write_text("\n".join(addr_lines), encoding="utf-8")
OUT_BIN.write_bytes(bytes(blob))
print(f"internal flash payload: {total_int} bytes -> {OUT_C.name}")
print(f"external flash payload: {len(blob)} bytes ({len(entries)} images) -> {OUT_BIN.name}")
print(f"addr header -> {OUT_ADDR}")
if __name__ == "__main__":
main()