feat(platform): migrate LCD assets to PNG and expand flash partitions

This commit is contained in:
chenjintao
2026-08-10 16:04:23 +08:00
parent 42187ba21d
commit d9660164a6
105 changed files with 10103 additions and 527 deletions
+525 -242
View File
@@ -24,10 +24,12 @@
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_log.h"
#include "esp_heap_caps.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#include "soc/soc_caps.h"
#define LCD_DIRECT_SPI_HOST SPI2_HOST
#define LCD_DIRECT_NATIVE_WIDTH 240U
@@ -43,12 +45,24 @@
#define LCD_DIRECT_PIXELS_PER_TRANSFER (LCD_DIRECT_WIDTH * LCD_DIRECT_ROWS_PER_TRANSFER)
#define LCD_DIRECT_PRESENTATION_WAIT_POLL_MS 20U
#define LCD_DIRECT_TRANSFER_TIMEOUT_MS 100U
#define LCD_DIRECT_FRAME_CACHE_MAX 4U
#define LCD_DIRECT_PATH_MAX 96U
#define LCD_DIRECT_LAST_FRAME_EXTRA_HOLD_MS 200U
#define LCD_DIRECT_BASIC_FACE_FRAME_HOLD_MS 300U
#define LCD_DIRECT_BASIC_FACE_LAST_FRAME_HOLD_MS 300U
#define LCD_CMD_MADCTL 0x36
#define LCD_CMD_RAMCTRL 0xB0
static const char *TAG = "lcd_direct";
unsigned lodepng_esp_decode32(unsigned char **out,
unsigned *w,
unsigned *h,
const unsigned char *in,
size_t insize);
const char *lodepng_esp_error_text(unsigned code);
typedef struct {
platform_lcd_presentation_kind_t kind;
uint8_t priority;
@@ -64,10 +78,25 @@ typedef struct {
size_t step_count;
} lcd_direct_request_t;
typedef struct lcd_direct_decoded_image_t {
bool valid;
bool alpha_valid;
char path[LCD_DIRECT_PATH_MAX];
uint16_t width;
uint16_t height;
uint16_t *pixels;
uint8_t *alpha;
uint32_t last_used;
bool pinned;
} lcd_direct_decoded_image_t;
typedef struct {
lcd_direct_icon_draw_t draw;
const lcd_direct_icon_t *icon;
lcd_direct_rle_run_t *loaded_runs;
const lcd_direct_decoded_image_t *decoded;
lcd_direct_decoded_image_t *cached;
lcd_direct_decoded_image_t transient;
bool owns_transient;
} lcd_direct_prepared_icon_t;
typedef struct {
@@ -107,7 +136,8 @@ static lcd_direct_request_t s_pending;
static uint32_t s_completion_count;
static portMUX_TYPE s_state_lock = portMUX_INITIALIZER_UNLOCKED;
static uint16_t s_line_buffer[LCD_DIRECT_PIXELS_PER_TRANSFER];
static lcd_direct_rle_run_t s_rle_file_chunk[128];
static lcd_direct_decoded_image_t s_frame_cache[LCD_DIRECT_FRAME_CACHE_MAX];
static uint32_t s_cache_use_counter;
static void lcd_direct_signal_completion(void);
@@ -160,6 +190,264 @@ static esp_err_t lcd_direct_draw_rows(uint16_t row_start, uint16_t row_count)
return ESP_OK;
}
static void *lcd_direct_alloc_psram(size_t size)
{
#if SOC_SPIRAM_SUPPORTED
void *ptr = heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (ptr != NULL) {
return ptr;
}
#endif
return malloc(size);
}
static uint16_t lcd_direct_rgb888_to_rgb565(uint8_t red, uint8_t green, uint8_t blue)
{
return (uint16_t)(((red & 0xF8U) << 8) | ((green & 0xFCU) << 3) | (blue >> 3));
}
static uint16_t lcd_direct_blend_rgb565(uint16_t dst, uint16_t src, uint8_t alpha)
{
if (alpha == 0U) {
return dst;
}
if (alpha == 255U) {
return src;
}
const uint8_t src_r = (uint8_t)(((src >> 11) & 0x1FU) << 3);
const uint8_t src_g = (uint8_t)(((src >> 5) & 0x3FU) << 2);
const uint8_t src_b = (uint8_t)((src & 0x1FU) << 3);
const uint8_t dst_r = (uint8_t)(((dst >> 11) & 0x1FU) << 3);
const uint8_t dst_g = (uint8_t)(((dst >> 5) & 0x3FU) << 2);
const uint8_t dst_b = (uint8_t)((dst & 0x1FU) << 3);
const uint8_t out_r = (uint8_t)(((uint16_t)src_r * alpha + (uint16_t)dst_r * (255U - alpha)) / 255U);
const uint8_t out_g = (uint8_t)(((uint16_t)src_g * alpha + (uint16_t)dst_g * (255U - alpha)) / 255U);
const uint8_t out_b = (uint8_t)(((uint16_t)src_b * alpha + (uint16_t)dst_b * (255U - alpha)) / 255U);
return lcd_direct_rgb888_to_rgb565(out_r, out_g, out_b);
}
static esp_err_t lcd_direct_read_file_psram(const char *path, unsigned char **out_data, size_t *out_size)
{
ESP_RETURN_ON_FALSE(path != NULL && out_data != NULL && out_size != NULL,
ESP_ERR_INVALID_ARG,
TAG,
"invalid PNG read request");
*out_data = NULL;
*out_size = 0;
errno = 0;
int fd = open(path, O_RDONLY);
if (fd < 0) {
ESP_LOGE(TAG, "open PNG asset failed path=%s errno=%d", path, errno);
return ESP_ERR_NOT_FOUND;
}
off_t file_size = lseek(fd, 0, SEEK_END);
if (file_size <= 0 || lseek(fd, 0, SEEK_SET) < 0) {
ESP_LOGE(TAG, "stat PNG asset failed path=%s errno=%d", path, errno);
close(fd);
return ESP_FAIL;
}
unsigned char *data = (unsigned char *)lcd_direct_alloc_psram((size_t)file_size);
if (data == NULL) {
ESP_LOGE(TAG, "allocate PNG file buffer failed path=%s size=%u", path, (unsigned)file_size);
close(fd);
return ESP_ERR_NO_MEM;
}
size_t offset = 0;
while (offset < (size_t)file_size) {
ssize_t bytes_read = read(fd, data + offset, (size_t)file_size - offset);
if (bytes_read <= 0) {
ESP_LOGE(TAG, "read PNG asset failed path=%s errno=%d", path, errno);
free(data);
close(fd);
return ESP_FAIL;
}
offset += (size_t)bytes_read;
}
close(fd);
*out_data = data;
*out_size = (size_t)file_size;
return ESP_OK;
}
static void lcd_direct_release_decoded_image(lcd_direct_decoded_image_t *decoded)
{
if (decoded == NULL) {
return;
}
free(decoded->pixels);
free(decoded->alpha);
memset(decoded, 0, sizeof(*decoded));
}
static esp_err_t lcd_direct_decode_png(const char *path,
bool keep_alpha,
lcd_direct_decoded_image_t *out_decoded)
{
ESP_RETURN_ON_FALSE(path != NULL && out_decoded != NULL,
ESP_ERR_INVALID_ARG,
TAG,
"invalid PNG decode request");
memset(out_decoded, 0, sizeof(*out_decoded));
unsigned char *png_data = NULL;
size_t png_size = 0;
esp_err_t read_err = lcd_direct_read_file_psram(path, &png_data, &png_size);
if (read_err != ESP_OK) {
return read_err;
}
unsigned char *rgba = NULL;
unsigned width = 0;
unsigned height = 0;
unsigned decode_err = lodepng_esp_decode32(&rgba, &width, &height, png_data, png_size);
free(png_data);
if (decode_err != 0U) {
ESP_LOGE(TAG, "decode PNG failed path=%s error=%u text=%s",
path,
decode_err,
lodepng_esp_error_text(decode_err));
return ESP_FAIL;
}
if (width == 0U || height == 0U || width > UINT16_MAX || height > UINT16_MAX) {
ESP_LOGE(TAG, "unsupported PNG dimensions path=%s width=%u height=%u", path, width, height);
free(rgba);
return ESP_ERR_INVALID_SIZE;
}
const size_t pixel_count = (size_t)width * (size_t)height;
uint16_t *pixels = (uint16_t *)lcd_direct_alloc_psram(pixel_count * sizeof(uint16_t));
if (pixels == NULL) {
ESP_LOGE(TAG, "allocate RGB565 PNG cache failed path=%s pixels=%u", path, (unsigned)pixel_count);
free(rgba);
return ESP_ERR_NO_MEM;
}
uint8_t *alpha = NULL;
bool alpha_needed = false;
if (keep_alpha) {
alpha = (uint8_t *)lcd_direct_alloc_psram(pixel_count);
if (alpha == NULL) {
ESP_LOGE(TAG, "allocate PNG alpha cache failed path=%s pixels=%u", path, (unsigned)pixel_count);
free(pixels);
free(rgba);
return ESP_ERR_NO_MEM;
}
}
for (size_t i = 0; i < pixel_count; ++i) {
const unsigned char *src = &rgba[i * 4U];
pixels[i] = lcd_direct_rgb888_to_rgb565(src[0], src[1], src[2]);
if (alpha != NULL) {
alpha[i] = src[3];
if (src[3] != 255U) {
alpha_needed = true;
}
}
}
free(rgba);
strlcpy(out_decoded->path, path, sizeof(out_decoded->path));
out_decoded->width = (uint16_t)width;
out_decoded->height = (uint16_t)height;
out_decoded->pixels = pixels;
out_decoded->alpha = alpha_needed ? alpha : NULL;
out_decoded->alpha_valid = alpha_needed;
out_decoded->valid = true;
if (!alpha_needed) {
free(alpha);
}
ESP_LOGI(TAG,
"PNG decoded path=%s size=%ux%u bytes=%u alpha=%d",
path,
width,
height,
(unsigned)(pixel_count * sizeof(uint16_t)),
alpha_needed);
return ESP_OK;
}
static lcd_direct_decoded_image_t *lcd_direct_find_cached_image(const char *path, bool require_alpha)
{
if (path == NULL) {
return NULL;
}
for (size_t i = 0; i < LCD_DIRECT_FRAME_CACHE_MAX; ++i) {
lcd_direct_decoded_image_t *entry = &s_frame_cache[i];
if (entry->valid && strcmp(entry->path, path) == 0) {
(void)require_alpha;
entry->last_used = ++s_cache_use_counter;
return entry;
}
}
return NULL;
}
static lcd_direct_decoded_image_t *lcd_direct_find_cache_slot(void)
{
lcd_direct_decoded_image_t *oldest = NULL;
for (size_t i = 0; i < LCD_DIRECT_FRAME_CACHE_MAX; ++i) {
if (!s_frame_cache[i].valid) {
return &s_frame_cache[i];
}
if (!s_frame_cache[i].pinned &&
(oldest == NULL || s_frame_cache[i].last_used < oldest->last_used)) {
oldest = &s_frame_cache[i];
}
}
if (oldest != NULL) {
lcd_direct_release_decoded_image(oldest);
}
return oldest;
}
static esp_err_t lcd_direct_get_png_image(const char *path,
bool keep_alpha,
bool cache,
lcd_direct_decoded_image_t **out_image,
lcd_direct_decoded_image_t *transient)
{
ESP_RETURN_ON_FALSE(path != NULL && out_image != NULL,
ESP_ERR_INVALID_ARG,
TAG,
"invalid PNG image request");
*out_image = NULL;
lcd_direct_decoded_image_t *cached = lcd_direct_find_cached_image(path, keep_alpha);
if (cached != NULL) {
*out_image = cached;
return ESP_OK;
}
lcd_direct_decoded_image_t *target = NULL;
if (cache) {
target = lcd_direct_find_cache_slot();
}
if (target == NULL) {
ESP_RETURN_ON_FALSE(transient != NULL, ESP_ERR_NO_MEM, TAG, "PNG cache full and no transient target");
target = transient;
}
esp_err_t err = lcd_direct_decode_png(path, keep_alpha, target);
if (err != ESP_OK) {
return err;
}
if (target != transient) {
target->last_used = ++s_cache_use_counter;
}
*out_image = target;
return ESP_OK;
}
static esp_err_t lcd_direct_gpio_init(void)
{
uint64_t pin_mask = (1ULL << CONFIG_TQ_SCREEN_BACKLIGHT_GPIO) |
@@ -319,48 +607,6 @@ static esp_err_t lcd_direct_draw_test_pattern(const lcd_direct_request_t *reques
return ESP_OK;
}
static lcd_direct_rle_run_t *lcd_direct_load_icon_runs(const lcd_direct_icon_t *icon)
{
if (icon == NULL || icon->path == NULL || icon->run_count == 0U) {
return NULL;
}
const size_t bytes_to_read = icon->run_count * sizeof(lcd_direct_rle_run_t);
lcd_direct_rle_run_t *runs = (lcd_direct_rle_run_t *)malloc(bytes_to_read);
if (runs == NULL) {
ESP_LOGW(TAG, "allocate LCD icon failed path=%s bytes=%u", icon->path, (unsigned)bytes_to_read);
return NULL;
}
errno = 0;
int fd = open(icon->path, O_RDONLY);
if (fd < 0) {
ESP_LOGW(TAG, "open LCD icon failed path=%s errno=%d", icon->path, errno);
free(runs);
return NULL;
}
size_t offset = 0;
while (offset < bytes_to_read) {
ssize_t bytes_read = read(fd, ((uint8_t *)runs) + offset, bytes_to_read - offset);
if (bytes_read <= 0) {
ESP_LOGW(TAG,
"read LCD icon failed path=%s requested=%u read=%d errno=%d",
icon->path,
(unsigned)bytes_to_read,
(int)bytes_read,
errno);
close(fd);
free(runs);
return NULL;
}
offset += (size_t)bytes_read;
}
close(fd);
return runs;
}
static size_t lcd_direct_prepare_icons(const lcd_direct_icon_draw_t *icons,
size_t icon_count,
lcd_direct_prepared_icon_t *prepared,
@@ -374,26 +620,35 @@ static size_t lcd_direct_prepare_icons(const lcd_direct_icon_draw_t *icons,
for (size_t i = 0; i < icon_count && prepared_count < prepared_capacity; ++i) {
const lcd_direct_icon_draw_t *draw = &icons[i];
const lcd_direct_icon_t *icon = lcd_direct_icon(draw->icon_id);
if (icon == NULL || icon->width == 0U || icon->height == 0U) {
if (icon == NULL || icon->path == NULL) {
continue;
}
if (draw->x >= LCD_DIRECT_WIDTH || draw->y >= LCD_DIRECT_HEIGHT) {
continue;
}
lcd_direct_rle_run_t *loaded_runs = NULL;
if (icon->runs == NULL) {
loaded_runs = lcd_direct_load_icon_runs(icon);
if (loaded_runs == NULL) {
continue;
}
lcd_direct_prepared_icon_t *slot = &prepared[prepared_count];
lcd_direct_decoded_image_t *decoded = NULL;
esp_err_t decode_err = lcd_direct_get_png_image(icon->path,
true,
true,
&decoded,
&slot->transient);
if (decode_err != ESP_OK || decoded == NULL) {
lcd_direct_release_decoded_image(&slot->transient);
ESP_LOGW(TAG, "prepare icon PNG failed path=%s err=%s", icon->path, esp_err_to_name(decode_err));
continue;
}
if (decoded == &slot->transient) {
slot->owns_transient = true;
} else {
decoded->pinned = true;
slot->cached = decoded;
}
prepared[prepared_count] = (lcd_direct_prepared_icon_t) {
.draw = *draw,
.icon = icon,
.loaded_runs = loaded_runs,
};
slot->draw = *draw;
slot->icon = icon;
slot->decoded = decoded;
prepared_count++;
}
@@ -407,8 +662,15 @@ static void lcd_direct_release_prepared_icons(lcd_direct_prepared_icon_t *prepar
}
for (size_t i = 0; i < prepared_count; ++i) {
free(prepared[i].loaded_runs);
prepared[i].loaded_runs = NULL;
if (prepared[i].cached != NULL) {
prepared[i].cached->pinned = false;
prepared[i].cached = NULL;
}
if (prepared[i].owns_transient) {
lcd_direct_release_decoded_image(&prepared[i].transient);
prepared[i].owns_transient = false;
}
prepared[i].decoded = NULL;
}
}
@@ -425,19 +687,14 @@ static void lcd_direct_overlay_icons(uint16_t *pixel_buffer,
const uint16_t row_end = row_start + row_count;
for (size_t icon_index = 0; icon_index < icon_count; ++icon_index) {
const lcd_direct_icon_draw_t *draw = &icons[icon_index].draw;
const lcd_direct_icon_t *icon = icons[icon_index].icon;
if (icon == NULL || icon->width == 0U || icon->height == 0U) {
const lcd_direct_decoded_image_t *icon = icons[icon_index].decoded;
if (icon == NULL || !icon->valid || icon->pixels == NULL || icon->width == 0U || icon->height == 0U) {
continue;
}
if (draw->x >= LCD_DIRECT_WIDTH || draw->y >= LCD_DIRECT_HEIGHT) {
continue;
}
const lcd_direct_rle_run_t *runs = icon->runs != NULL ? icon->runs : icons[icon_index].loaded_runs;
if (runs == NULL) {
continue;
}
uint16_t draw_width = icon->width;
uint16_t draw_height = icon->height;
if (draw_width > LCD_DIRECT_WIDTH - draw->x) {
@@ -460,175 +717,21 @@ static void lcd_direct_overlay_icons(uint16_t *pixel_buffer,
const uint16_t image_y = y - draw->y;
const uint32_t icon_pixel_start = (uint32_t)image_y * icon->width;
const uint32_t icon_pixel_end = icon_pixel_start + draw_width;
uint32_t icon_pixel_pos = 0;
for (uint32_t run_index = 0;
run_index < icon->run_count && icon_pixel_pos < icon_pixel_end;
++run_index) {
const uint32_t run_start = icon_pixel_pos;
const uint32_t run_end = run_start + runs[run_index].count;
if (run_end > icon_pixel_start && run_start < icon_pixel_end) {
const uint32_t copy_start = run_start > icon_pixel_start ? run_start : icon_pixel_start;
const uint32_t copy_end = run_end < icon_pixel_end ? run_end : icon_pixel_end;
const uint16_t color = lcd_direct_swap_color(runs[run_index].color);
for (uint32_t x = copy_start; x < copy_end; ++x) {
pixel_buffer[(uint32_t)local_y * LCD_DIRECT_WIDTH + draw->x + (x - icon_pixel_start)] = color;
}
for (uint32_t x = icon_pixel_start; x < icon_pixel_end; ++x) {
const uint32_t dst_index = (uint32_t)local_y * LCD_DIRECT_WIDTH + draw->x + (x - icon_pixel_start);
uint16_t color = icon->pixels[x];
if (icon->alpha_valid && icon->alpha != NULL) {
const uint16_t dst = lcd_direct_swap_color(pixel_buffer[dst_index]);
color = lcd_direct_blend_rgb565(dst, color, icon->alpha[x]);
}
icon_pixel_pos = run_end;
pixel_buffer[dst_index] = lcd_direct_swap_color(color);
}
}
}
}
static esp_err_t lcd_direct_draw_rle_memory_image(const lcd_direct_image_t *image,
const lcd_direct_prepared_icon_t *icons,
size_t icon_count)
{
ESP_RETURN_ON_FALSE(image != NULL && image->runs != NULL,
ESP_ERR_INVALID_ARG,
TAG,
"invalid image");
uint32_t run_index = 0;
uint32_t run_remaining = 0;
uint16_t run_color = 0;
for (uint16_t row_start = 0; row_start < LCD_DIRECT_HEIGHT; row_start += LCD_DIRECT_ROWS_PER_TRANSFER) {
uint16_t row_count = LCD_DIRECT_HEIGHT - row_start;
if (row_count > LCD_DIRECT_ROWS_PER_TRANSFER) {
row_count = LCD_DIRECT_ROWS_PER_TRANSFER;
}
const uint32_t pixels_needed = (uint32_t)LCD_DIRECT_WIDTH * row_count;
uint32_t buffered_pixels = 0;
while (buffered_pixels < pixels_needed) {
if (run_remaining == 0U) {
if (run_index >= image->run_count) {
break;
}
run_color = lcd_direct_swap_color(image->runs[run_index].color);
run_remaining = image->runs[run_index].count;
run_index++;
}
uint32_t pixels_to_copy = run_remaining;
const uint32_t pixels_left = pixels_needed - buffered_pixels;
if (pixels_to_copy > pixels_left) {
pixels_to_copy = pixels_left;
}
for (uint32_t i = 0; i < pixels_to_copy; ++i) {
s_line_buffer[buffered_pixels + i] = run_color;
}
buffered_pixels += pixels_to_copy;
run_remaining -= pixels_to_copy;
}
while (buffered_pixels < pixels_needed) {
s_line_buffer[buffered_pixels++] = 0;
}
lcd_direct_overlay_icons(s_line_buffer, row_start, row_count, icons, icon_count);
ESP_RETURN_ON_ERROR(lcd_direct_draw_rows(row_start, row_count),
TAG,
"write image pixels failed");
}
return ESP_OK;
}
static esp_err_t lcd_direct_draw_rle_file_image(const lcd_direct_image_t *image,
const lcd_direct_prepared_icon_t *icons,
size_t icon_count)
{
ESP_RETURN_ON_FALSE(image != NULL && image->path != NULL,
ESP_ERR_INVALID_ARG,
TAG,
"invalid file image");
errno = 0;
int fd = open(image->path, O_RDONLY);
if (fd < 0) {
ESP_LOGE(TAG, "open LCD asset failed path=%s errno=%d", image->path, errno);
return ESP_ERR_NOT_FOUND;
}
uint32_t run_index = 0;
uint32_t chunk_index = 0;
uint32_t chunk_count = 0;
uint32_t run_remaining = 0;
uint16_t run_color = 0;
esp_err_t result = ESP_OK;
for (uint16_t row_start = 0; row_start < LCD_DIRECT_HEIGHT; row_start += LCD_DIRECT_ROWS_PER_TRANSFER) {
uint16_t row_count = LCD_DIRECT_HEIGHT - row_start;
if (row_count > LCD_DIRECT_ROWS_PER_TRANSFER) {
row_count = LCD_DIRECT_ROWS_PER_TRANSFER;
}
const uint32_t pixels_needed = (uint32_t)LCD_DIRECT_WIDTH * row_count;
uint32_t buffered_pixels = 0;
while (buffered_pixels < pixels_needed) {
if (run_remaining == 0U) {
if (run_index >= image->run_count) {
break;
}
if (chunk_index >= chunk_count) {
const size_t runs_left = image->run_count - run_index;
const size_t runs_to_read = runs_left < (sizeof(s_rle_file_chunk) / sizeof(s_rle_file_chunk[0])) ?
runs_left :
(sizeof(s_rle_file_chunk) / sizeof(s_rle_file_chunk[0]));
const size_t bytes_to_read = runs_to_read * sizeof(s_rle_file_chunk[0]);
ssize_t bytes_read = read(fd, s_rle_file_chunk, bytes_to_read);
if (bytes_read != (ssize_t)bytes_to_read) {
ESP_LOGE(TAG,
"read LCD asset failed path=%s requested=%u read=%d errno=%d",
image->path,
(unsigned)bytes_to_read,
(int)bytes_read,
errno);
result = ESP_FAIL;
goto out;
}
chunk_index = 0;
chunk_count = runs_to_read;
}
run_color = lcd_direct_swap_color(s_rle_file_chunk[chunk_index].color);
run_remaining = s_rle_file_chunk[chunk_index].count;
chunk_index++;
run_index++;
}
uint32_t pixels_to_copy = run_remaining;
const uint32_t pixels_left = pixels_needed - buffered_pixels;
if (pixels_to_copy > pixels_left) {
pixels_to_copy = pixels_left;
}
for (uint32_t i = 0; i < pixels_to_copy; ++i) {
s_line_buffer[buffered_pixels + i] = run_color;
}
buffered_pixels += pixels_to_copy;
run_remaining -= pixels_to_copy;
}
while (buffered_pixels < pixels_needed) {
s_line_buffer[buffered_pixels++] = 0;
}
lcd_direct_overlay_icons(s_line_buffer, row_start, row_count, icons, icon_count);
result = lcd_direct_draw_rows(row_start, row_count);
if (result != ESP_OK) {
ESP_LOGE(TAG, "write image pixels failed path=%s", image->path);
goto out;
}
}
out:
close(fd);
return result;
}
static esp_err_t lcd_direct_draw_rle_image(const lcd_direct_image_t *image,
static esp_err_t lcd_direct_draw_png_image(const lcd_direct_image_t *image,
const lcd_direct_icon_draw_t *icons,
size_t icon_count)
{
@@ -636,21 +739,164 @@ static esp_err_t lcd_direct_draw_rle_image(const lcd_direct_image_t *image,
ESP_ERR_INVALID_ARG,
TAG,
"invalid image");
lcd_direct_prepared_icon_t prepared_icons[LCD_DIRECT_DEFAULT_ICON_COUNT];
ESP_RETURN_ON_FALSE(image->path != NULL,
ESP_ERR_INVALID_ARG,
TAG,
"invalid image path");
lcd_direct_decoded_image_t transient = {0};
lcd_direct_decoded_image_t *decoded = NULL;
ESP_RETURN_ON_ERROR(lcd_direct_get_png_image(image->path, false, true, &decoded, &transient),
TAG,
"load PNG image failed");
ESP_RETURN_ON_FALSE(decoded != NULL && decoded->pixels != NULL,
ESP_FAIL,
TAG,
"decoded PNG image is invalid");
if (decoded != &transient) {
decoded->pinned = true;
}
lcd_direct_prepared_icon_t prepared_icons[LCD_DIRECT_DEFAULT_ICON_COUNT] = {0};
size_t prepared_icon_count = lcd_direct_prepare_icons(icons,
icon_count,
prepared_icons,
sizeof(prepared_icons) / sizeof(prepared_icons[0]));
esp_err_t err = ESP_OK;
if (image->runs != NULL) {
err = lcd_direct_draw_rle_memory_image(image, prepared_icons, prepared_icon_count);
} else {
err = lcd_direct_draw_rle_file_image(image, prepared_icons, prepared_icon_count);
for (uint16_t row_start = 0; row_start < LCD_DIRECT_HEIGHT; row_start += LCD_DIRECT_ROWS_PER_TRANSFER) {
uint16_t row_count = LCD_DIRECT_HEIGHT - row_start;
if (row_count > LCD_DIRECT_ROWS_PER_TRANSFER) {
row_count = LCD_DIRECT_ROWS_PER_TRANSFER;
}
for (uint16_t y = 0; y < row_count; ++y) {
const uint32_t dst_row_offset = (uint32_t)y * LCD_DIRECT_WIDTH;
const uint32_t src_y = row_start + y;
for (uint16_t x = 0; x < LCD_DIRECT_WIDTH; ++x) {
uint16_t color = 0;
if (x < decoded->width && src_y < decoded->height) {
color = decoded->pixels[(uint32_t)src_y * decoded->width + x];
}
s_line_buffer[dst_row_offset + x] = lcd_direct_swap_color(color);
}
}
lcd_direct_overlay_icons(s_line_buffer, row_start, row_count, prepared_icons, prepared_icon_count);
err = lcd_direct_draw_rows(row_start, row_count);
if (err != ESP_OK) {
ESP_LOGE(TAG, "write PNG image pixels failed path=%s", image->path);
break;
}
}
lcd_direct_release_prepared_icons(prepared_icons, prepared_icon_count);
if (decoded == &transient) {
lcd_direct_release_decoded_image(&transient);
} else {
decoded->pinned = false;
}
return err;
}
static esp_err_t lcd_direct_preheat_image(const lcd_direct_image_t *image)
{
ESP_RETURN_ON_FALSE(image != NULL && image->path != NULL,
ESP_ERR_INVALID_ARG,
TAG,
"invalid preheat image");
lcd_direct_decoded_image_t transient = {0};
lcd_direct_decoded_image_t *decoded = NULL;
esp_err_t err = lcd_direct_get_png_image(image->path, false, true, &decoded, &transient);
if (decoded == &transient) {
lcd_direct_release_decoded_image(&transient);
}
return err;
}
static esp_err_t lcd_direct_preheat_asset_sequence(const lcd_direct_request_t *request)
{
ESP_RETURN_ON_FALSE(request != NULL,
ESP_ERR_INVALID_ARG,
TAG,
"invalid preheat request");
const size_t frame_count = request->frame_order_count > 0U ? request->frame_order_count : 1U;
for (size_t i = 0; i < frame_count; ++i) {
const uint8_t frame_index = request->frame_order_count > 0U ? request->frame_order[i] : (uint8_t)i;
const lcd_direct_image_t *frame = lcd_direct_asset_frame(request->asset_dir, frame_index);
if (frame == NULL) {
return ESP_ERR_NOT_FOUND;
}
ESP_RETURN_ON_ERROR(lcd_direct_preheat_image(frame), TAG, "preheat PNG frame failed");
}
return ESP_OK;
}
static esp_err_t lcd_direct_preheat_basic_face(void)
{
const lcd_direct_request_t request = {
.kind = PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE,
.asset_dir = "jichubiaoqing",
.frame_order = {0, 1, 2},
.frame_order_count = 3,
};
return lcd_direct_preheat_asset_sequence(&request);
}
static uint32_t lcd_direct_asset_sequence_frame_ms(const lcd_direct_request_t *request)
{
return (request != NULL && request->frame_ms > 0U) ? request->frame_ms : 100U;
}
static uint32_t lcd_direct_asset_sequence_hold_ms(const lcd_direct_request_t *request,
size_t frame_index,
size_t frame_count)
{
const uint32_t frame_ms = lcd_direct_asset_sequence_frame_ms(request);
if (request != NULL && strcmp(request->asset_dir, "jichubiaoqing") == 0) {
return (frame_index + 1U == frame_count) ?
LCD_DIRECT_BASIC_FACE_LAST_FRAME_HOLD_MS :
LCD_DIRECT_BASIC_FACE_FRAME_HOLD_MS;
}
if (frame_index + 1U != frame_count) {
return frame_ms;
}
return frame_ms + LCD_DIRECT_LAST_FRAME_EXTRA_HOLD_MS;
}
static uint32_t lcd_direct_asset_sequence_duration_ms(const lcd_direct_request_t *request)
{
if (request == NULL || request->duration_ms == 0U) {
return 0U;
}
const size_t frame_count = request->frame_order_count > 0U ? request->frame_order_count : 1U;
const uint32_t frame_ms = lcd_direct_asset_sequence_frame_ms(request);
const uint64_t base_cycle_ms = (uint64_t)frame_count * (uint64_t)frame_ms;
if (base_cycle_ms == 0U) {
return request->duration_ms;
}
const uint64_t completed_cycles = (uint64_t)request->duration_ms / base_cycle_ms;
uint32_t last_frame_extra_ms = LCD_DIRECT_LAST_FRAME_EXTRA_HOLD_MS;
if (strcmp(request->asset_dir, "jichubiaoqing") == 0) {
const uint64_t base_cycle_ms = (uint64_t)frame_count * (uint64_t)frame_ms;
const uint64_t adjusted_cycle_ms = frame_count > 0U ?
((uint64_t)(frame_count - 1U) *
(uint64_t)LCD_DIRECT_BASIC_FACE_FRAME_HOLD_MS) +
(uint64_t)LCD_DIRECT_BASIC_FACE_LAST_FRAME_HOLD_MS :
base_cycle_ms;
last_frame_extra_ms = adjusted_cycle_ms > base_cycle_ms ?
(uint32_t)(adjusted_cycle_ms - base_cycle_ms) :
0U;
}
uint64_t adjusted_duration_ms = (uint64_t)request->duration_ms +
(completed_cycles * (uint64_t)last_frame_extra_ms);
if (adjusted_duration_ms > UINT32_MAX) {
adjusted_duration_ms = UINT32_MAX;
}
return (uint32_t)adjusted_duration_ms;
}
static esp_err_t lcd_direct_draw_asset_sequence_once(const lcd_direct_request_t *request)
{
ESP_RETURN_ON_FALSE(request != NULL,
@@ -677,12 +923,12 @@ static esp_err_t lcd_direct_draw_asset_sequence_once(const lcd_direct_request_t
request->asset_dir,
(unsigned)frame_index);
ESP_RETURN_ON_ERROR(lcd_direct_draw_rle_image(frame, NULL, 0),
ESP_RETURN_ON_ERROR(lcd_direct_draw_png_image(frame, NULL, 0),
TAG,
"draw asset frame failed");
const uint32_t frame_ms = request->frame_ms > 0U ? request->frame_ms : 100U;
vTaskDelay(pdMS_TO_TICKS(frame_ms));
const uint32_t hold_ms = lcd_direct_asset_sequence_hold_ms(request, i, frame_count);
vTaskDelay(pdMS_TO_TICKS(hold_ms));
}
static TickType_t s_last_draw_success_tick;
@@ -728,7 +974,7 @@ static esp_err_t lcd_direct_draw_asset_sequence_for_duration(const lcd_direct_re
request->asset_dir,
(unsigned)frame_index);
ESP_RETURN_ON_ERROR(lcd_direct_draw_rle_image(frame, NULL, 0),
ESP_RETURN_ON_ERROR(lcd_direct_draw_png_image(frame, NULL, 0),
TAG,
"draw timed asset frame failed");
@@ -738,8 +984,8 @@ static esp_err_t lcd_direct_draw_asset_sequence_for_duration(const lcd_direct_re
return ESP_OK;
}
const uint32_t frame_ms = request->frame_ms > 0U ? request->frame_ms : 100U;
const TickType_t frame_ticks = pdMS_TO_TICKS(frame_ms);
const uint32_t hold_ms = lcd_direct_asset_sequence_hold_ms(request, i, frame_count);
const TickType_t frame_ticks = pdMS_TO_TICKS(hold_ms);
const TickType_t remaining_ticks = duration_ticks - elapsed_ticks;
vTaskDelay(frame_ticks < remaining_ticks ? frame_ticks : remaining_ticks);
}
@@ -776,7 +1022,7 @@ static esp_err_t lcd_direct_play_asset_sequence(const lcd_direct_request_t *requ
if (request->duration_ms > 0U) {
const TickType_t start_tick = xTaskGetTickCount();
const TickType_t duration_ticks = pdMS_TO_TICKS(request->duration_ms);
const TickType_t duration_ticks = pdMS_TO_TICKS(lcd_direct_asset_sequence_duration_ms(request));
esp_err_t err = lcd_direct_draw_asset_sequence_for_duration(request, start_tick, duration_ticks);
if (err != ESP_OK) {
return err;
@@ -838,7 +1084,7 @@ static esp_err_t lcd_direct_play_step_sequence(const lcd_direct_request_t *reque
icon_count = 1;
}
ESP_RETURN_ON_ERROR(lcd_direct_draw_rle_image(frame, icons, icon_count),
ESP_RETURN_ON_ERROR(lcd_direct_draw_png_image(frame, icons, icon_count),
TAG,
"draw step frame failed");
@@ -956,6 +1202,10 @@ void lcd_direct_start_render_loop(void)
portEXIT_CRITICAL(&s_state_lock);
ESP_LOGI(TAG, "Direct LCD render loop started");
esp_err_t preheat_err = lcd_direct_preheat_basic_face();
if (preheat_err != ESP_OK) {
ESP_LOGW(TAG, "Default LCD face PNG preheat failed: %s", esp_err_to_name(preheat_err));
}
while (true) {
bool stop = false;
@@ -1122,8 +1372,41 @@ esp_err_t lcd_direct_request_presentation(const platform_lcd_presentation_reques
esp_err_t lcd_direct_preheat_presentation(const platform_lcd_presentation_request_t *request)
{
(void)request;
return s_initialized ? ESP_OK : ESP_ERR_INVALID_STATE;
if (!s_initialized || request == NULL) {
return ESP_ERR_INVALID_STATE;
}
lcd_direct_request_t copied = {
.kind = request->kind,
.priority = request->priority,
.interrupt_current = request->interrupt_current,
.return_to_idle = request->return_to_idle,
.repeat_count = request->repeat_count,
.frame_ms = request->frame_ms,
.duration_ms = request->duration_ms,
};
if (request->asset_dir != NULL) {
strlcpy(copied.asset_dir, request->asset_dir, sizeof(copied.asset_dir));
}
if (request->frame_order != NULL && request->frame_order_count > 0U) {
copied.frame_order_count = request->frame_order_count;
if (copied.frame_order_count > PLATFORM_LCD_PRESENTATION_FRAME_ORDER_MAX) {
copied.frame_order_count = PLATFORM_LCD_PRESENTATION_FRAME_ORDER_MAX;
}
memcpy(copied.frame_order, request->frame_order, copied.frame_order_count);
}
if (xSemaphoreTake(s_lock, pdMS_TO_TICKS(1000)) != pdTRUE) {
return ESP_ERR_TIMEOUT;
}
esp_err_t err = ESP_ERR_INVALID_ARG;
if (copied.kind == PLATFORM_LCD_PRESENTATION_IDLE) {
err = lcd_direct_preheat_basic_face();
} else if (copied.kind == PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE) {
err = lcd_direct_preheat_asset_sequence(&copied);
}
xSemaphoreGive(s_lock);
return err;
}
uint32_t lcd_direct_presentation_completion_count(void)
+42 -93
View File
@@ -1,226 +1,175 @@
#include "lcd_direct_assets.h"
#include "lcd_direct_assets.h"
#include <string.h>
#define LCD_ASSET_BASE_PATH "/littlefs/lcd/"
static const lcd_direct_image_t s_default_image = {
.path = LCD_ASSET_BASE_PATH "buyunqian1.rle",
.run_count = 72552U,
.path = LCD_ASSET_BASE_PATH "buyunqian1.png",
};
static const lcd_direct_image_t s_basic_face_frames[] = {
{
.path = LCD_ASSET_BASE_PATH "jichubiaoqing0.rle",
.run_count = 5411U,
.path = LCD_ASSET_BASE_PATH "jichubiaoqing0.png",
},
{
.path = LCD_ASSET_BASE_PATH "jichubiaoqing1.rle",
.run_count = 4099U,
.path = LCD_ASSET_BASE_PATH "jichubiaoqing1.png",
},
{
.path = LCD_ASSET_BASE_PATH "jichubiaoqing2.rle",
.run_count = 3169U,
.path = LCD_ASSET_BASE_PATH "jichubiaoqing2.png",
},
};
static const lcd_direct_image_t s_muyu_frames[] = {
{
.path = LCD_ASSET_BASE_PATH "muyu0.rle",
.run_count = 63459U,
.path = LCD_ASSET_BASE_PATH "muyu0.png",
},
{
.path = LCD_ASSET_BASE_PATH "muyu1.rle",
.run_count = 63665U,
.path = LCD_ASSET_BASE_PATH "muyu1.png",
},
{
.path = LCD_ASSET_BASE_PATH "muyu2.rle",
.run_count = 63894U,
.path = LCD_ASSET_BASE_PATH "muyu2.png",
},
};
static const lcd_direct_image_t s_chouqian_qianzhi_frames[] = {
{
.path = LCD_ASSET_BASE_PATH "chouqian_qianzhi0.rle",
.run_count = 74056U,
.path = LCD_ASSET_BASE_PATH "chouqian_qianzhi0.png",
},
{
.path = LCD_ASSET_BASE_PATH "chouqian_qianzhi1.rle",
.run_count = 73259U,
.path = LCD_ASSET_BASE_PATH "chouqian_qianzhi1.png",
},
{
.path = LCD_ASSET_BASE_PATH "chouqian_qianzhi2.rle",
.run_count = 72906U,
.path = LCD_ASSET_BASE_PATH "chouqian_qianzhi2.png",
},
};
static const lcd_direct_image_t s_chouqian_buyun_frames[] = {
{
.path = LCD_ASSET_BASE_PATH "chouqian_buyun0.rle",
.run_count = 72370U,
.path = LCD_ASSET_BASE_PATH "chouqian_buyun0.png",
},
{
.path = LCD_ASSET_BASE_PATH "chouqian_buyun1.rle",
.run_count = 72552U,
.path = LCD_ASSET_BASE_PATH "chouqian_buyun1.png",
},
{
.path = LCD_ASSET_BASE_PATH "chouqian_buyun2.rle",
.run_count = 72757U,
.path = LCD_ASSET_BASE_PATH "chouqian_buyun2.png",
},
};
static const lcd_direct_image_t s_chouqian_heping_frames[] = {
{
.path = LCD_ASSET_BASE_PATH "chouqian_heping0.rle",
.run_count = 72370U,
.path = LCD_ASSET_BASE_PATH "chouqian_heping0.png",
},
{
.path = LCD_ASSET_BASE_PATH "chouqian_heping1.rle",
.run_count = 73049U,
.path = LCD_ASSET_BASE_PATH "chouqian_heping1.png",
},
{
.path = LCD_ASSET_BASE_PATH "chouqian_heping2.rle",
.run_count = 73365U,
.path = LCD_ASSET_BASE_PATH "chouqian_heping2.png",
},
};
static const lcd_direct_image_t s_chouqian_shangshang_frames[] = {
{
.path = LCD_ASSET_BASE_PATH "chouqian_shangshang0.rle",
.run_count = 72370U,
.path = LCD_ASSET_BASE_PATH "chouqian_shangshang0.png",
},
{
.path = LCD_ASSET_BASE_PATH "chouqian_shangshang1.rle",
.run_count = 72944U,
.path = LCD_ASSET_BASE_PATH "chouqian_shangshang1.png",
},
{
.path = LCD_ASSET_BASE_PATH "chouqian_shangshang2.rle",
.run_count = 73190U,
.path = LCD_ASSET_BASE_PATH "chouqian_shangshang2.png",
},
};
static const lcd_direct_image_t s_chouqian_xiaoji_frames[] = {
{
.path = LCD_ASSET_BASE_PATH "chouqian_xiaoji0.rle",
.run_count = 72370U,
.path = LCD_ASSET_BASE_PATH "chouqian_xiaoji0.png",
},
{
.path = LCD_ASSET_BASE_PATH "chouqian_xiaoji1.rle",
.run_count = 72827U,
.path = LCD_ASSET_BASE_PATH "chouqian_xiaoji1.png",
},
{
.path = LCD_ASSET_BASE_PATH "chouqian_xiaoji2.rle",
.run_count = 73006U,
.path = LCD_ASSET_BASE_PATH "chouqian_xiaoji2.png",
},
};
static const lcd_direct_image_t s_chouqian_zhongshang_frames[] = {
{
.path = LCD_ASSET_BASE_PATH "chouqian_zhongshang0.rle",
.run_count = 72370U,
.path = LCD_ASSET_BASE_PATH "chouqian_zhongshang0.png",
},
{
.path = LCD_ASSET_BASE_PATH "chouqian_zhongshang1.rle",
.run_count = 72135U,
.path = LCD_ASSET_BASE_PATH "chouqian_zhongshang1.png",
},
{
.path = LCD_ASSET_BASE_PATH "chouqian_zhongshang2.rle",
.run_count = 72292U,
.path = LCD_ASSET_BASE_PATH "chouqian_zhongshang2.png",
},
};
static const lcd_direct_image_t s_qianzhiwuxing_frames[] = {
{
.path = LCD_ASSET_BASE_PATH "qianzhiwuxing0.rle",
.run_count = 4157U,
.path = LCD_ASSET_BASE_PATH "qianzhiwuxing0.png",
},
};
static const lcd_direct_image_t s_shui_frames[] = {
{
.path = LCD_ASSET_BASE_PATH "shui0.rle",
.run_count = 74146U,
.path = LCD_ASSET_BASE_PATH "shui0.png",
},
{
.path = LCD_ASSET_BASE_PATH "shui1.rle",
.run_count = 71868U,
.path = LCD_ASSET_BASE_PATH "shui1.png",
},
};
static const lcd_direct_image_t s_mu_frames[] = {
{
.path = LCD_ASSET_BASE_PATH "mu0.rle",
.run_count = 73537U,
.path = LCD_ASSET_BASE_PATH "mu0.png",
},
{
.path = LCD_ASSET_BASE_PATH "mu1.rle",
.run_count = 69337U,
.path = LCD_ASSET_BASE_PATH "mu1.png",
},
};
static const lcd_direct_image_t s_huo_frames[] = {
{
.path = LCD_ASSET_BASE_PATH "huo0.rle",
.run_count = 74455U,
.path = LCD_ASSET_BASE_PATH "huo0.png",
},
{
.path = LCD_ASSET_BASE_PATH "huo1.rle",
.run_count = 72474U,
.path = LCD_ASSET_BASE_PATH "huo1.png",
},
};
static const lcd_direct_image_t s_tu_frames[] = {
{
.path = LCD_ASSET_BASE_PATH "tu0.rle",
.run_count = 95829U,
.path = LCD_ASSET_BASE_PATH "tu0.png",
},
{
.path = LCD_ASSET_BASE_PATH "tu1.rle",
.run_count = 71964U,
.path = LCD_ASSET_BASE_PATH "tu1.png",
},
};
static const lcd_direct_image_t s_jin_frames[] = {
{
.path = LCD_ASSET_BASE_PATH "jin0.rle",
.run_count = 75151U,
.path = LCD_ASSET_BASE_PATH "jin0.png",
},
{
.path = LCD_ASSET_BASE_PATH "jin1.rle",
.run_count = 72012U,
.path = LCD_ASSET_BASE_PATH "jin1.png",
},
};
static const lcd_direct_icon_t s_wuxing_icons[] = {
{
.path = LCD_ASSET_BASE_PATH "icon_shui0.rle",
.run_count = 549U,
.width = 60U,
.height = 56U,
.path = LCD_ASSET_BASE_PATH "icon_shui0.png",
},
{
.path = LCD_ASSET_BASE_PATH "icon_mu0.rle",
.run_count = 1005U,
.width = 86U,
.height = 50U,
.path = LCD_ASSET_BASE_PATH "icon_mu0.png",
},
{
.path = LCD_ASSET_BASE_PATH "icon_huo0.rle",
.run_count = 565U,
.width = 56U,
.height = 58U,
.path = LCD_ASSET_BASE_PATH "icon_huo0.png",
},
{
.path = LCD_ASSET_BASE_PATH "icon_tu0.rle",
.run_count = 754U,
.width = 75U,
.height = 42U,
.path = LCD_ASSET_BASE_PATH "icon_tu0.png",
},
{
.path = LCD_ASSET_BASE_PATH "icon_jin0.rle",
.run_count = 605U,
.width = 65U,
.height = 44U,
.path = LCD_ASSET_BASE_PATH "icon_jin0.png",
},
};