Refresh bottom nav with 0909 enlarged icons and aligned label art.
Co-authored-by: Cursor <cursoragent@cursor.com>
|
|
@ -966,37 +966,32 @@ void UI_DrawSubPageHeader(const uint8_t *title_gbk)
|
|||
|
||||
void Draw_Bottom_Bar(uint8_t sel, uint8_t first_back)
|
||||
{
|
||||
/* 需求稿四键:万能 | 普通 | 专业 | 设置;first_back 时设置位仍高亮(返回用触控逻辑) */
|
||||
/* 四键合成图(图标+原稿文字);统一 32 高,文字底边对齐 */
|
||||
const uint8_t *icons[4];
|
||||
uint8_t ws[4], hs[4];
|
||||
/* 0904:四键中心约 40 / 100 / 160 / 210 */
|
||||
uint8_t ws[4];
|
||||
const uint16_t cx[4] = { 40, 100, 160, 210 };
|
||||
const uint8_t icon_h = 32;
|
||||
const uint16_t icon_y = 282;
|
||||
uint8_t i;
|
||||
|
||||
(void)first_back;
|
||||
/* 从段落钮底边之下清屏,勿切掉按钮底圆角(钮 y=231..264) */
|
||||
LCD_FillByColor(0, (uint16_t)(UI0902_SECTION_Y + UI0902_SECTION_BTN_H),
|
||||
240, 320, UI0902_BG_COLOR);
|
||||
/* mode.png:段落钮与底栏之间横线分隔(与顶栏同色同厚) */
|
||||
LCD_FillByColor(0, UI0902_SEP_LINE_Y, 240,
|
||||
(uint16_t)(UI0902_SEP_LINE_Y + UI0902_SEP_LINE_THICK),
|
||||
UI0902_SEP_LINE_COLOR);
|
||||
|
||||
if (sel == UI0902_NAV_UNIVERSAL) { icons[0] = gImage_Nav_Universal_Sel; ws[0] = 36; hs[0] = 28; }
|
||||
else { icons[0] = gImage_Nav_Universal_Not; ws[0] = 36; hs[0] = 28; }
|
||||
|
||||
if (sel == UI0902_NAV_NORMAL) { icons[1] = gImage_Nav_Normal_Sel; ws[1] = 35; hs[1] = 29; }
|
||||
else { icons[1] = gImage_Nav_Normal_Not; ws[1] = 35; hs[1] = 28; }
|
||||
|
||||
if (sel == UI0902_NAV_EXPERT) { icons[2] = gImage_Nav_Expert_Sel; ws[2] = 35; hs[2] = 27; }
|
||||
else { icons[2] = gImage_Nav_Expert_Not; ws[2] = 35; hs[2] = 27; }
|
||||
|
||||
if (sel == UI0902_NAV_SETTING) { icons[3] = gImage_Nav_Setting_Sel; ws[3] = 18; hs[3] = 28; }
|
||||
else { icons[3] = gImage_Nav_Setting_Not; ws[3] = 18; hs[3] = 28; }
|
||||
icons[0] = (sel == UI0902_NAV_UNIVERSAL) ? gImage_Nav_Universal_Sel : gImage_Nav_Universal_Not;
|
||||
icons[1] = (sel == UI0902_NAV_NORMAL) ? gImage_Nav_Normal_Sel : gImage_Nav_Normal_Not;
|
||||
icons[2] = (sel == UI0902_NAV_EXPERT) ? gImage_Nav_Expert_Sel : gImage_Nav_Expert_Not;
|
||||
icons[3] = (sel == UI0902_NAV_SETTING) ? gImage_Nav_Setting_Sel : gImage_Nav_Setting_Not;
|
||||
ws[0] = ws[1] = ws[2] = 40;
|
||||
ws[3] = 22;
|
||||
|
||||
for (i = 0; i < 4; i++)
|
||||
{
|
||||
LCD_WR_PIC_Trans(cx[i] - ws[i] / 2, 283, ws[i], hs[i], icons[i], BLACK);
|
||||
LCD_WR_PIC_Trans((uint16_t)(cx[i] - ws[i] / 2), icon_y,
|
||||
ws[i], icon_h, icons[i], BLACK);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -183,15 +183,15 @@ void CallUI_RestoreSelect(void);
|
|||
#define UI0902_NAV_MIXER UI0902_NAV_NORMAL
|
||||
#define UI0902_NAV_MODE UI0902_NAV_EXPERT
|
||||
|
||||
/* 底部图标实际内容(生成脚本曾把 万能/普通/专业/设置 误命名为 Setting/Mixer/Mode) */
|
||||
#define gImage_Nav_Universal_Not gImage_UI0902_TabSetting_Sel_36x28 /* 底部-10 白 */
|
||||
#define gImage_Nav_Universal_Sel gImage_UI0902_TabMode_Sel_36x28 /* 底部-14 蓝 */
|
||||
#define gImage_Nav_Normal_Not gImage_UI0902_TabSetting_Not_35x28 /* 底部-11 白 */
|
||||
#define gImage_Nav_Normal_Sel gImage_UI0902_TabMode_Not_35x29 /* 底部-15 蓝 */
|
||||
#define gImage_Nav_Expert_Not gImage_UI0902_TabMixer_Sel_35x27 /* 底部-12 白 */
|
||||
#define gImage_Nav_Expert_Sel gImage_UI0902_TabBack_Sel_35x27 /* 底部-16 蓝 */
|
||||
#define gImage_Nav_Setting_Not gImage_UI0902_TabMixer_Not_18x28 /* 底部-13 白 */
|
||||
#define gImage_Nav_Setting_Sel gImage_UI0902_TabBack_Not_18x28 /* 底部-17 蓝 */
|
||||
/* 底部图标:0909 加大图标 + 原稿文字条(合成图);生成脚本曾误命名 Setting/Mixer/Mode */
|
||||
#define gImage_Nav_Universal_Not gImage_UI0902_TabSetting_Sel_40x32 /* 底部-10 白 */
|
||||
#define gImage_Nav_Universal_Sel gImage_UI0902_TabMode_Sel_40x32 /* 底部-14 蓝 */
|
||||
#define gImage_Nav_Normal_Not gImage_UI0902_TabSetting_Not_40x32 /* 底部-11 白 */
|
||||
#define gImage_Nav_Normal_Sel gImage_UI0902_TabMode_Not_40x32 /* 底部-15 蓝 */
|
||||
#define gImage_Nav_Expert_Not gImage_UI0902_TabMixer_Sel_40x32 /* 底部-12 白 */
|
||||
#define gImage_Nav_Expert_Sel gImage_UI0902_TabBack_Sel_40x32 /* 底部-16 蓝 */
|
||||
#define gImage_Nav_Setting_Not gImage_UI0902_TabMixer_Not_22x32 /* 底部-13 白 */
|
||||
#define gImage_Nav_Setting_Sel gImage_UI0902_TabBack_Not_22x32 /* 底部-17 蓝 */
|
||||
/* 旧几何电池(Draw_Battery_Icon)最左起点;0902 图电池用 Draw_Status_Battery_Icon */
|
||||
#define UI_STATUS_BATTERY_X 150
|
||||
#define UI_STATUS_BATTERY_H 11
|
||||
|
|
|
|||
|
|
@ -7,15 +7,15 @@ extern const unsigned char gImage_UI0902_BatteryOutline_29x12[];
|
|||
extern const unsigned char gImage_UI0902_Volume_20x15[600];
|
||||
extern const unsigned char gImage_UI0902_Bluetooth_11x17[374];
|
||||
extern const unsigned char gImage_UI0902_BatteryFill_22x9[396];
|
||||
extern const unsigned char gImage_UI0902_TabSetting_Sel_36x28[2016];
|
||||
extern const unsigned char gImage_UI0902_TabSetting_Not_35x28[1960];
|
||||
extern const unsigned char gImage_UI0902_TabMixer_Sel_35x27[1890];
|
||||
extern const unsigned char gImage_UI0902_TabMixer_Not_18x28[1008];
|
||||
extern const unsigned char gImage_UI0902_TabSetting_Sel_40x32[2560];
|
||||
extern const unsigned char gImage_UI0902_TabSetting_Not_40x32[2560];
|
||||
extern const unsigned char gImage_UI0902_TabMixer_Sel_40x32[2560];
|
||||
extern const unsigned char gImage_UI0902_TabMixer_Not_22x32[1408];
|
||||
extern const unsigned char gImage_UI0902_TabMixer_Not_35x28[1960];
|
||||
extern const unsigned char gImage_UI0902_TabMode_Sel_36x28[2016];
|
||||
extern const unsigned char gImage_UI0902_TabMode_Not_35x29[2030];
|
||||
extern const unsigned char gImage_UI0902_TabBack_Sel_35x27[1890];
|
||||
extern const unsigned char gImage_UI0902_TabBack_Not_18x28[1008];
|
||||
extern const unsigned char gImage_UI0902_TabMode_Sel_40x32[2560];
|
||||
extern const unsigned char gImage_UI0902_TabMode_Not_40x32[2560];
|
||||
extern const unsigned char gImage_UI0902_TabBack_Sel_40x32[2560];
|
||||
extern const unsigned char gImage_UI0902_TabBack_Not_22x32[1408];
|
||||
|
||||
extern const unsigned char gImage_UI0902_BtnMinus_23x22[1012];
|
||||
extern const unsigned char gImage_UI0902_BtnPlus_23x22[1012];
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1017 B |
|
Before Width: | Height: | Size: 902 B After Width: | Height: | Size: 845 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 982 B After Width: | Height: | Size: 1.1 KiB |
|
|
@ -137,14 +137,14 @@ INTERNAL = [
|
|||
("gImage_UI0902_Volume_20x15", "02_顶部图标/音量图标-10.png"),
|
||||
("gImage_UI0902_Bluetooth_11x17", "02_顶部图标/蓝牙图标-10.png"),
|
||||
# BatteryFill generated by make_battery_fill() — not from mockup 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"),
|
||||
("gImage_UI0902_TabSetting_Sel_40x32", "03_底部图标/底部图标-10.png"), # 万能白
|
||||
("gImage_UI0902_TabSetting_Not_40x32", "03_底部图标/底部图标-11.png"), # 普通白
|
||||
("gImage_UI0902_TabMixer_Sel_40x32", "03_底部图标/底部图标-12.png"), # 专业白
|
||||
("gImage_UI0902_TabMixer_Not_22x32", "03_底部图标/底部图标-13.png"), # 设置白
|
||||
("gImage_UI0902_TabMode_Sel_40x32", "03_底部图标/底部图标-14.png"), # 万能蓝
|
||||
("gImage_UI0902_TabMode_Not_40x32", "03_底部图标/底部图标-15.png"), # 普通蓝
|
||||
("gImage_UI0902_TabBack_Sel_40x32", "03_底部图标/底部图标-16.png"), # 专业蓝
|
||||
("gImage_UI0902_TabBack_Not_22x32", "03_底部图标/底部图标-17.png"), # 设置蓝
|
||||
]
|
||||
|
||||
# ---------------- external flash (big images / text strips) ----------------
|
||||
|
|
|
|||
|
|
@ -0,0 +1,206 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Bottom nav: 0909 enlarged icons + original design text strips, fixed baseline."""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import subprocess
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image
|
||||
|
||||
PROJ = Path(__file__).resolve().parents[1]
|
||||
ROOT = PROJ.parent.parent
|
||||
SRC_DIR = next(
|
||||
p
|
||||
for p in (ROOT / "Doc" / "UI" / "K1标准界面图 0904").iterdir()
|
||||
if p.is_dir() and "0909" in p.name
|
||||
)
|
||||
ASSETS = PROJ / "tools" / "assets_0902" / "03_底部图标"
|
||||
DOC_ASSETS = ROOT / "Doc" / "UI" / "k1_ui_png 0902" / "03_底部图标"
|
||||
OUT_C = PROJ / "device" / "LCD_ILI9341" / "Drv_ILI9341_Lcd_Image_UI0902.c"
|
||||
OUT_H = PROJ / "device" / "LCD_ILI9341" / "Drv_ILI9341_Lcd_Image_UI0902.h"
|
||||
|
||||
ROW_BG = (36, 43, 58)
|
||||
CANVAS_H = 32
|
||||
ICON_AREA_H = 20
|
||||
TEXT_TOP = 22 # all labels share this top row
|
||||
|
||||
# png_idx, src_glyph_num, symbol_base, width
|
||||
MAPPING = [
|
||||
(10, 21, "gImage_UI0902_TabSetting_Sel", 40),
|
||||
(11, 23, "gImage_UI0902_TabSetting_Not", 40),
|
||||
(12, 25, "gImage_UI0902_TabMixer_Sel", 40),
|
||||
(13, 27, "gImage_UI0902_TabMixer_Not", 22),
|
||||
(14, 20, "gImage_UI0902_TabMode_Sel", 40),
|
||||
(15, 22, "gImage_UI0902_TabMode_Not", 40),
|
||||
(16, 24, "gImage_UI0902_TabBack_Sel", 40),
|
||||
(17, 26, "gImage_UI0902_TabBack_Not", 22),
|
||||
]
|
||||
|
||||
|
||||
def git_png(rel: str) -> Image.Image:
|
||||
data = subprocess.check_output(["git", "show", f"HEAD:{rel}"], cwd=PROJ)
|
||||
return Image.open(BytesIO(data)).convert("RGBA")
|
||||
|
||||
|
||||
def clear_bg(im: Image.Image) -> Image.Image:
|
||||
im = im.convert("RGBA")
|
||||
px = im.load()
|
||||
for y in range(im.height):
|
||||
for x in range(im.width):
|
||||
r, g, b, a = px[x, y]
|
||||
if a < 40 or (r < 12 and g < 12 and b < 12):
|
||||
px[x, y] = (0, 0, 0, 0)
|
||||
return im
|
||||
|
||||
|
||||
def row_has_ink(im: Image.Image, y: int) -> bool:
|
||||
w = im.width
|
||||
for x in range(w):
|
||||
r, g, b, a = im.getpixel((x, y))
|
||||
if a > 40 and (r + g + b) > 40:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def extract_text(im: Image.Image) -> Image.Image:
|
||||
"""Crop label band below the icon/gap from original composite."""
|
||||
im = clear_bg(im)
|
||||
h = im.height
|
||||
# find last empty gap row in upper half, then text starts after
|
||||
empty = [y for y in range(h) if not row_has_ink(im, y)]
|
||||
# text region: from first non-empty after mid-gap
|
||||
mid_empties = [y for y in empty if 10 <= y <= 20]
|
||||
if mid_empties:
|
||||
text_y0 = mid_empties[-1] + 1
|
||||
else:
|
||||
text_y0 = h // 2 + 2
|
||||
while text_y0 < h and not row_has_ink(im, text_y0):
|
||||
text_y0 += 1
|
||||
text_y1 = h - 1
|
||||
while text_y1 > text_y0 and not row_has_ink(im, text_y1):
|
||||
text_y1 -= 1
|
||||
band = im.crop((0, text_y0, im.width, text_y1 + 1))
|
||||
# trim horizontal transparent
|
||||
bbox = band.getbbox()
|
||||
if bbox:
|
||||
band = band.crop(bbox)
|
||||
return clear_bg(band)
|
||||
|
||||
|
||||
def fit_icon(im: Image.Image) -> Image.Image:
|
||||
im = clear_bg(im)
|
||||
nw, nh = im.size
|
||||
if nw > 24 or nh > ICON_AREA_H:
|
||||
scale = min(24 / nw, ICON_AREA_H / nh)
|
||||
nw = max(1, int(round(nw * scale)))
|
||||
nh = max(1, int(round(nh * scale)))
|
||||
im = clear_bg(im.resize((nw, nh), Image.Resampling.NEAREST))
|
||||
nw, nh = im.size
|
||||
return im
|
||||
|
||||
|
||||
def rgba_to_rgb565(im: Image.Image) -> tuple[bytes, int, int]:
|
||||
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] = out[i + 1] = 0
|
||||
elif (
|
||||
abs(r - ROW_BG[0]) <= 18
|
||||
and abs(g - ROW_BG[1]) <= 18
|
||||
and abs(b - ROW_BG[2]) <= 18
|
||||
):
|
||||
out[i] = 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 emit_c_array(name: str, data: bytes, w: int, h: int) -> str:
|
||||
lines = [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("")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def replace_c_array(text: str, old_name: str, new_block: str) -> str:
|
||||
pat = re.compile(
|
||||
rf"const unsigned char {re.escape(old_name)}\[\d+\] = \{{.*?\n\}};\n?",
|
||||
re.S,
|
||||
)
|
||||
if not pat.search(text):
|
||||
raise SystemExit(f"array not found: {old_name}")
|
||||
return pat.sub(new_block, text, count=1)
|
||||
|
||||
|
||||
def replace_h_decl(text: str, old_name: str, new_name: str, nbytes: int) -> str:
|
||||
pat = re.compile(rf"extern const unsigned char {re.escape(old_name)}\[\d+\];")
|
||||
if not pat.search(text):
|
||||
raise SystemExit(f"decl not found: {old_name}")
|
||||
return pat.sub(
|
||||
f"extern const unsigned char {new_name}[{nbytes}];", text, count=1
|
||||
)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
by_num = {int(f.stem.rsplit("-", 1)[-1]): f for f in SRC_DIR.glob("*.png")}
|
||||
c_text = OUT_C.read_text(encoding="utf-8")
|
||||
h_text = OUT_H.read_text(encoding="utf-8")
|
||||
sizes: dict[int, tuple[int, int]] = {}
|
||||
|
||||
for out_idx, src_num, base, width in MAPPING:
|
||||
icon = fit_icon(Image.open(by_num[src_num]))
|
||||
old = git_png(f"tools/assets_0902/03_底部图标/底部图标-{out_idx}.png")
|
||||
text = extract_text(old)
|
||||
|
||||
# widen canvas if text wider
|
||||
cw = max(width, icon.width, text.width)
|
||||
canvas = Image.new("RGBA", (cw, CANVAS_H), (0, 0, 0, 0))
|
||||
iy = (ICON_AREA_H - icon.height) // 2
|
||||
canvas.paste(icon, ((cw - icon.width) // 2, max(0, iy)), icon)
|
||||
# pin text BOTTOM to same row so labels sit on one baseline
|
||||
text_bottom = CANVAS_H - 1
|
||||
ty = text_bottom - text.height + 1
|
||||
if ty < TEXT_TOP:
|
||||
ty = TEXT_TOP
|
||||
canvas.paste(text, ((cw - text.width) // 2, ty), text)
|
||||
|
||||
out = ASSETS / f"底部图标-{out_idx}.png"
|
||||
canvas.save(out)
|
||||
if DOC_ASSETS.exists():
|
||||
canvas.save(DOC_ASSETS / f"底部图标-{out_idx}.png")
|
||||
|
||||
data, w, h = rgba_to_rgb565(canvas)
|
||||
new_name = f"{base}_{w}x{h}"
|
||||
m = re.search(rf"{re.escape(base)}_\d+x\d+", c_text)
|
||||
if not m:
|
||||
raise SystemExit(f"cannot locate {base}")
|
||||
old_name = m.group(0)
|
||||
c_text = replace_c_array(c_text, old_name, emit_c_array(new_name, data, w, h))
|
||||
h_text = replace_h_decl(h_text, old_name, new_name, len(data))
|
||||
sizes[out_idx] = (w, h)
|
||||
print(f"{out_idx}: {w}x{h} text={text.size} -> {new_name}")
|
||||
|
||||
OUT_C.write_text(c_text, encoding="utf-8")
|
||||
OUT_H.write_text(h_text, encoding="utf-8")
|
||||
print("SIZES", sizes)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||