K1Guitar/tools/gen_ui0902_assets.py

264 lines
13 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 -*-
"""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()