feat(lcd): use Montserrat clock and battery icon assets
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
#include "lcd_direct.h"
|
||||
#include "lcd_direct_assets.h"
|
||||
#include "platform_asset_bundle.h"
|
||||
#include "lcd_status_assets.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
@@ -87,17 +88,16 @@
|
||||
#define LCD_DIRECT_BACKLIGHT_PWM_TIMER LEDC_TIMER_0
|
||||
#define LCD_DIRECT_BACKLIGHT_PWM_CHANNEL LEDC_CHANNEL_0
|
||||
#define LCD_DIRECT_BACKLIGHT_LOCK_TIMEOUT_MS 100U
|
||||
/* 5x7 点阵放大4倍,24像素步进为字形留出间隔;颜色为 RGB565 白字黑描边。 */
|
||||
/* Montserrat 14 子集直接合成,无 label 字形缓存或新增动态缓冲。 */
|
||||
#define LCD_DIRECT_CLOCK_X 12U
|
||||
#define LCD_DIRECT_CLOCK_Y 10U
|
||||
#define LCD_DIRECT_CLOCK_GLYPH_WIDTH 5U
|
||||
#define LCD_DIRECT_CLOCK_GLYPH_HEIGHT 7U
|
||||
#define LCD_DIRECT_CLOCK_GLYPH_SCALE 4U
|
||||
#define LCD_DIRECT_CLOCK_GLYPH_ADVANCE 24U
|
||||
#define LCD_DIRECT_CLOCK_Y 14U
|
||||
#define LCD_DIRECT_CLOCK_TEXT_LENGTH 5U
|
||||
#define LCD_DIRECT_CLOCK_OUTLINE_PX 1
|
||||
#define LCD_DIRECT_CLOCK_COLOR 0xFFFFU
|
||||
#define LCD_DIRECT_CLOCK_OUTLINE_COLOR 0x0000U
|
||||
#define LCD_DIRECT_CLOCK_COLOR 0xFFFFU
|
||||
#define LCD_DIRECT_BATTERY_WIDTH 40U
|
||||
#define LCD_DIRECT_BATTERY_HEIGHT 20U
|
||||
#define LCD_DIRECT_BATTERY_X (LCD_DIRECT_WIDTH - LCD_DIRECT_BATTERY_WIDTH - 8U)
|
||||
#define LCD_DIRECT_BATTERY_Y 12U
|
||||
|
||||
#if CONFIG_TQ_LCD_EMOTE_GFX_TEST
|
||||
#define LCD_DIRECT_HUD_ROWS 40U
|
||||
@@ -202,21 +202,6 @@ static const lcd_direct_init_cmd_t s_st7789_init_cmds[] = {
|
||||
{0, {0}, 0xFF},
|
||||
};
|
||||
|
||||
/* Five-bit rows, most-significant displayed column first. Kept in flash. */
|
||||
static const uint8_t s_clock_glyph_rows[][LCD_DIRECT_CLOCK_GLYPH_HEIGHT] = {
|
||||
{0x0E, 0x11, 0x13, 0x15, 0x19, 0x11, 0x0E}, /* 0 */
|
||||
{0x04, 0x0C, 0x04, 0x04, 0x04, 0x04, 0x0E}, /* 1 */
|
||||
{0x0E, 0x11, 0x01, 0x02, 0x04, 0x08, 0x1F}, /* 2 */
|
||||
{0x1E, 0x01, 0x01, 0x0E, 0x01, 0x01, 0x1E}, /* 3 */
|
||||
{0x02, 0x06, 0x0A, 0x12, 0x1F, 0x02, 0x02}, /* 4 */
|
||||
{0x1F, 0x10, 0x10, 0x1E, 0x01, 0x01, 0x1E}, /* 5 */
|
||||
{0x0E, 0x10, 0x10, 0x1E, 0x11, 0x11, 0x0E}, /* 6 */
|
||||
{0x1F, 0x01, 0x02, 0x04, 0x08, 0x08, 0x08}, /* 7 */
|
||||
{0x0E, 0x11, 0x11, 0x0E, 0x11, 0x11, 0x0E}, /* 8 */
|
||||
{0x0E, 0x11, 0x11, 0x0F, 0x01, 0x01, 0x0E}, /* 9 */
|
||||
{0x00, 0x04, 0x04, 0x00, 0x04, 0x04, 0x00}, /* : */
|
||||
{0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00}, /* - */
|
||||
};
|
||||
|
||||
static esp_lcd_panel_io_handle_t s_panel_io;
|
||||
static esp_lcd_panel_handle_t s_panel;
|
||||
@@ -646,7 +631,7 @@ static bool lcd_direct_should_stop_idle(void)
|
||||
return stop;
|
||||
}
|
||||
|
||||
/* 调用方持有 GFX 锁;字形复用 PNG 路径,无字体包和额外 PNG 解码。 */
|
||||
/* 调用方持有 GFX 锁;两种后端共用 Montserrat 子集和电池资产,无动态解码缓存。 */
|
||||
static esp_err_t lcd_direct_emote_update_hud(bool force, bool *changed)
|
||||
{
|
||||
if (changed != NULL) {
|
||||
@@ -1745,7 +1730,7 @@ static size_t lcd_direct_append_status_icons(const lcd_direct_image_t *image,
|
||||
.y = LCD_DIRECT_LOW_BATTERY_ICON_Y,
|
||||
};
|
||||
}
|
||||
/* 充电状态改由右上角程序绘制的电池图标表示,避免和百分比重叠。 */
|
||||
/* 充电状态由右上角新电池素材表示;保留底部低电量提醒。 */
|
||||
return icon_count;
|
||||
}
|
||||
|
||||
@@ -1837,131 +1822,92 @@ static void lcd_direct_clock_text(char text[LCD_DIRECT_CLOCK_TEXT_LENGTH + 1U])
|
||||
text[5] = '\0';
|
||||
}
|
||||
|
||||
static void lcd_direct_draw_clock_pass(uint16_t *pixel_buffer,
|
||||
uint16_t row_start,
|
||||
uint16_t row_count,
|
||||
const char text[LCD_DIRECT_CLOCK_TEXT_LENGTH + 1U],
|
||||
int x_offset,
|
||||
int y_offset,
|
||||
uint16_t color)
|
||||
/* 输入/输出沿用 PNG 行缓冲字节序;GFX HUD 在提交前统一转换。 */
|
||||
static void lcd_direct_hud_blend_pixel(uint16_t *pixels, uint16_t row_start,
|
||||
uint16_t rows, int x, int y,
|
||||
uint16_t color, uint8_t alpha)
|
||||
{
|
||||
const int row_end = (int)row_start + (int)row_count;
|
||||
for (size_t character_index = 0; character_index < LCD_DIRECT_CLOCK_TEXT_LENGTH;
|
||||
++character_index) {
|
||||
const uint8_t *glyph =
|
||||
s_clock_glyph_rows[lcd_direct_clock_glyph_index(text[character_index])];
|
||||
const int glyph_x = (int)LCD_DIRECT_CLOCK_X +
|
||||
(int)(character_index * LCD_DIRECT_CLOCK_GLYPH_ADVANCE) +
|
||||
x_offset;
|
||||
const int glyph_y = (int)LCD_DIRECT_CLOCK_Y + y_offset;
|
||||
|
||||
for (uint16_t source_y = 0; source_y < LCD_DIRECT_CLOCK_GLYPH_HEIGHT; ++source_y) {
|
||||
for (uint16_t scale_y = 0; scale_y < LCD_DIRECT_CLOCK_GLYPH_SCALE; ++scale_y) {
|
||||
const int destination_y = glyph_y +
|
||||
(int)(source_y * LCD_DIRECT_CLOCK_GLYPH_SCALE + scale_y);
|
||||
if (destination_y < (int)row_start || destination_y >= row_end) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (uint16_t source_x = 0; source_x < LCD_DIRECT_CLOCK_GLYPH_WIDTH;
|
||||
++source_x) {
|
||||
if ((glyph[source_y] &
|
||||
(1U << (LCD_DIRECT_CLOCK_GLYPH_WIDTH - 1U - source_x))) == 0U) {
|
||||
continue;
|
||||
}
|
||||
for (uint16_t scale_x = 0; scale_x < LCD_DIRECT_CLOCK_GLYPH_SCALE;
|
||||
++scale_x) {
|
||||
const int destination_x = glyph_x +
|
||||
(int)(source_x * LCD_DIRECT_CLOCK_GLYPH_SCALE + scale_x);
|
||||
if (destination_x < 0 || destination_x >= (int)LCD_DIRECT_WIDTH) {
|
||||
continue;
|
||||
}
|
||||
const uint32_t destination_index =
|
||||
(uint32_t)(destination_y - (int)row_start) * LCD_DIRECT_WIDTH +
|
||||
(uint32_t)destination_x;
|
||||
pixel_buffer[destination_index] = lcd_direct_swap_color(color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void lcd_direct_overlay_clock(uint16_t *pixel_buffer,
|
||||
uint16_t row_start,
|
||||
uint16_t row_count,
|
||||
const char text[LCD_DIRECT_CLOCK_TEXT_LENGTH + 1U])
|
||||
{
|
||||
/* 时钟作为最终覆盖层绘制,保证 presentation 与状态图标不会遮住时间。 */
|
||||
if (pixel_buffer == NULL || text == NULL) {
|
||||
if (pixels == NULL || alpha == 0 || x < 0 || x >= (int)LCD_DIRECT_WIDTH ||
|
||||
y < (int)row_start || y >= (int)row_start + (int)rows) {
|
||||
return;
|
||||
}
|
||||
|
||||
lcd_direct_draw_clock_pass(pixel_buffer,
|
||||
row_start,
|
||||
row_count,
|
||||
text,
|
||||
LCD_DIRECT_CLOCK_OUTLINE_PX,
|
||||
LCD_DIRECT_CLOCK_OUTLINE_PX,
|
||||
LCD_DIRECT_CLOCK_OUTLINE_COLOR);
|
||||
lcd_direct_draw_clock_pass(pixel_buffer,
|
||||
row_start,
|
||||
row_count,
|
||||
text,
|
||||
0,
|
||||
0,
|
||||
LCD_DIRECT_CLOCK_COLOR);
|
||||
const size_t index = (size_t)(y - row_start) * LCD_DIRECT_WIDTH + (size_t)x;
|
||||
const uint16_t background = lcd_direct_swap_color(pixels[index]);
|
||||
pixels[index] = lcd_direct_swap_color(lcd_direct_blend_rgb565(background, color, alpha));
|
||||
}
|
||||
|
||||
/* 小型电池 + 数字百分比,沿用 5x7 点阵;所有写入都裁剪到当前行块。 */
|
||||
static void lcd_direct_hud_pixel(uint16_t *pixels, uint16_t row_start,
|
||||
uint16_t rows, int x, int y, uint16_t color)
|
||||
static void lcd_direct_draw_clock_pass(uint16_t *pixels, uint16_t row_start,
|
||||
uint16_t rows,
|
||||
const char text[LCD_DIRECT_CLOCK_TEXT_LENGTH + 1U],
|
||||
int x_offset, int y_offset, uint16_t color)
|
||||
{
|
||||
if (x >= 0 && x < LCD_DIRECT_WIDTH && y >= row_start && y < row_start + rows) {
|
||||
pixels[(y - row_start) * LCD_DIRECT_WIDTH + x] = lcd_direct_swap_color(color);
|
||||
int cursor = LCD_DIRECT_CLOCK_X;
|
||||
for (size_t i = 0; i < LCD_DIRECT_CLOCK_TEXT_LENGTH; ++i) {
|
||||
const lcd_status_glyph_t *glyph = &lcd_status_font_glyphs[lcd_direct_clock_glyph_index(text[i])];
|
||||
/* 原字体 line_height=16、base_line=3;不缩放,保留 4-bit 抗锯齿。 */
|
||||
const int top = (int)LCD_DIRECT_CLOCK_Y + 16 - 3 - glyph->height - glyph->y;
|
||||
for (unsigned y = 0; y < glyph->height; ++y) {
|
||||
for (unsigned x = 0; x < glyph->width; ++x) {
|
||||
const size_t pixel = y * glyph->width + x;
|
||||
const uint8_t packed = lcd_status_font_bitmap[glyph->offset + pixel / 2];
|
||||
const uint8_t alpha = ((pixel & 1U) ? packed & 15U : packed >> 4) * 17U;
|
||||
lcd_direct_hud_blend_pixel(pixels, row_start, rows,
|
||||
cursor + glyph->x + x_offset + (int)x,
|
||||
top + y_offset + (int)y, color, alpha);
|
||||
}
|
||||
}
|
||||
cursor += glyph->advance;
|
||||
}
|
||||
}
|
||||
|
||||
static void lcd_direct_overlay_clock(uint16_t *pixels, uint16_t row_start,
|
||||
uint16_t rows,
|
||||
const char text[LCD_DIRECT_CLOCK_TEXT_LENGTH + 1U])
|
||||
{
|
||||
if (pixels == NULL || text == NULL ||
|
||||
(int)row_start >= (int)LCD_DIRECT_CLOCK_Y + 17 ||
|
||||
(int)row_start + rows <= (int)LCD_DIRECT_CLOCK_Y) {
|
||||
return;
|
||||
}
|
||||
lcd_direct_draw_clock_pass(pixels, row_start, rows, text, 1, 1, LCD_DIRECT_CLOCK_OUTLINE_COLOR);
|
||||
lcd_direct_draw_clock_pass(pixels, row_start, rows, text, 0, 0, LCD_DIRECT_CLOCK_COLOR);
|
||||
}
|
||||
|
||||
/* 档位向上分组:0..10→1,11..25→25,26..50→50,51..75→75,76..100→100。 */
|
||||
static unsigned lcd_direct_battery_icon_index(uint8_t percent, bool charging)
|
||||
{
|
||||
unsigned level = percent <= 10 ? 0 : percent <= 25 ? 1 :
|
||||
percent <= 50 ? 2 : percent <= 75 ? 3 : 4;
|
||||
return level + (charging ? 5U : 0U);
|
||||
}
|
||||
|
||||
static void lcd_direct_overlay_battery(uint16_t *pixels, uint16_t row_start, uint16_t rows)
|
||||
{
|
||||
platform_lcd_status_overlay_t status;
|
||||
portENTER_CRITICAL(&s_state_lock);
|
||||
status = s_status_overlay;
|
||||
portEXIT_CRITICAL(&s_state_lock);
|
||||
const uint16_t color = status.charging_visible ? 0x07E0U :
|
||||
status.low_battery_visible ? 0xF800U : 0xFFFFU;
|
||||
const int left = LCD_DIRECT_WIDTH - 108;
|
||||
char text[5];
|
||||
if (status.battery_valid) {
|
||||
snprintf(text, sizeof(text), "%u%%", (unsigned)status.battery_percent);
|
||||
} else {
|
||||
memcpy(text, "--%", 4);
|
||||
/* 素材没有未知电量图标;读取有效电量前不显示,避免暗示错误档位。 */
|
||||
if (pixels == NULL || !status.battery_valid ||
|
||||
(int)row_start >= (int)(LCD_DIRECT_BATTERY_Y + LCD_DIRECT_BATTERY_HEIGHT) ||
|
||||
(int)row_start + rows <= (int)LCD_DIRECT_BATTERY_Y) {
|
||||
return;
|
||||
}
|
||||
static const uint8_t percent[7] = {0x19, 0x1A, 0x02, 0x04, 0x08, 0x0B, 0x13};
|
||||
for (size_t i = 0; text[i] != '\0'; ++i) {
|
||||
const uint8_t *glyph = text[i] == '%' ? percent :
|
||||
s_clock_glyph_rows[lcd_direct_clock_glyph_index(text[i])];
|
||||
for (int y = 0; y < 21; ++y) {
|
||||
for (int x = 0; x < 15; ++x) {
|
||||
if (glyph[y / 3] & (1U << (4 - x / 3))) {
|
||||
lcd_direct_hud_pixel(pixels, row_start, rows, left + i * 18 + x, 13 + y, color);
|
||||
}
|
||||
const lcd_status_icon_t *icon = &lcd_status_battery_icons[
|
||||
lcd_direct_battery_icon_index(status.battery_percent, status.charging_visible)];
|
||||
size_t pixel = 0;
|
||||
/* 从 Flash 流式读 palette/RLE,保留源图 Alpha,不创建解码图缓存。 */
|
||||
for (size_t run = 0; run + 1 < icon->run_bytes; run += 2) {
|
||||
const uint8_t *value = icon->palette + (size_t)icon->runs[run + 1] * 3;
|
||||
const uint16_t color = ((uint16_t)value[0] << 8) | value[1];
|
||||
for (unsigned count = 0; count < icon->runs[run]; ++count, ++pixel) {
|
||||
if (pixel >= LCD_DIRECT_BATTERY_WIDTH * LCD_DIRECT_BATTERY_HEIGHT) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
const int icon_x = LCD_DIRECT_WIDTH - 32;
|
||||
const int fill = status.battery_valid ? status.battery_percent * 16 / 100 : 0;
|
||||
for (int y = 0; y < 20; ++y) {
|
||||
for (int x = 0; x < 24; ++x) {
|
||||
bool on = x < 22 && (x < 2 || x >= 20 || y < 2 || y >= 18);
|
||||
on |= x >= 22 && y >= 6 && y < 14;
|
||||
on |= status.battery_valid && x >= 3 && x < 3 + fill && y >= 3 && y < 17;
|
||||
/* 充电时在电池中挖出黑色闪电,绿色外框仍可辨识。 */
|
||||
if (status.charging_visible && y >= 3 && y < 17 &&
|
||||
x >= 11 - (y - 3) / 3 && x <= 14 - (y - 3) / 3) {
|
||||
on = false;
|
||||
}
|
||||
lcd_direct_hud_pixel(pixels, row_start, rows, icon_x + x, 14 + y, on ? color : 0);
|
||||
lcd_direct_hud_blend_pixel(pixels, row_start, rows,
|
||||
LCD_DIRECT_BATTERY_X + pixel % LCD_DIRECT_BATTERY_WIDTH,
|
||||
LCD_DIRECT_BATTERY_Y + pixel / LCD_DIRECT_BATTERY_WIDTH,
|
||||
color, value[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user