feat(platform): migrate LCD assets to PNG and expand flash partitions
@@ -13,6 +13,9 @@ set(platform_srcs
|
||||
"src/platform_io_effects.c"
|
||||
"src/lcd_direct.c"
|
||||
"src/lcd_direct_assets.c"
|
||||
"third_party/lodepng/lodepng.cpp"
|
||||
"third_party/lodepng/lodepng_esp_alloc.cpp"
|
||||
"third_party/lodepng/lodepng_esp_bridge.cpp"
|
||||
"src/multimodal/audio_pipeline.c"
|
||||
"src/multimodal/transport_ws.c"
|
||||
"src/wifi_portal_bridge.cpp"
|
||||
@@ -56,10 +59,17 @@ idf_component_register(
|
||||
"include"
|
||||
PRIV_INCLUDE_DIRS
|
||||
"internal"
|
||||
"third_party/lodepng"
|
||||
PRIV_REQUIRES
|
||||
${platform_priv_requires}
|
||||
)
|
||||
|
||||
set_source_files_properties(
|
||||
"third_party/lodepng/lodepng.cpp"
|
||||
PROPERTIES
|
||||
COMPILE_DEFINITIONS "LODEPNG_NO_COMPILE_ENCODER;LODEPNG_NO_COMPILE_DISK;LODEPNG_NO_COMPILE_CPP;LODEPNG_NO_COMPILE_ALLOCATORS"
|
||||
)
|
||||
|
||||
set(platform_audio_prompt_src_dir "${CMAKE_CURRENT_SOURCE_DIR}/assets/audio_prompts")
|
||||
set(platform_lcd_asset_src_dir "${CMAKE_CURRENT_SOURCE_DIR}/assets/lcd")
|
||||
set(platform_asset_image_dir "${CMAKE_CURRENT_BINARY_DIR}/asset_image")
|
||||
@@ -67,7 +77,7 @@ set(platform_asset_stage_stamp "${CMAKE_CURRENT_BINARY_DIR}/asset_image.stamp")
|
||||
file(GLOB_RECURSE platform_audio_prompt_files CONFIGURE_DEPENDS
|
||||
"${platform_audio_prompt_src_dir}/*.opus"
|
||||
)
|
||||
set(platform_lcd_flash_assets
|
||||
set(platform_lcd_png_assets
|
||||
buyunqian1
|
||||
jichubiaoqing0
|
||||
jichubiaoqing1
|
||||
@@ -110,10 +120,10 @@ set(platform_lcd_flash_assets
|
||||
icon_tu0
|
||||
icon_jin0
|
||||
)
|
||||
set(platform_lcd_flash_asset_files)
|
||||
foreach(lcd_rle_asset IN LISTS platform_lcd_flash_assets)
|
||||
list(APPEND platform_lcd_flash_asset_files
|
||||
"${platform_lcd_asset_src_dir}/${lcd_rle_asset}.rle"
|
||||
set(platform_lcd_png_asset_files)
|
||||
foreach(lcd_png_asset IN LISTS platform_lcd_png_assets)
|
||||
list(APPEND platform_lcd_png_asset_files
|
||||
"${platform_lcd_asset_src_dir}/${lcd_png_asset}.png"
|
||||
)
|
||||
endforeach()
|
||||
|
||||
@@ -127,12 +137,12 @@ foreach(audio_file IN LISTS platform_audio_prompt_files)
|
||||
"${platform_asset_image_dir}/audio_prompts/${audio_rel_path}"
|
||||
)
|
||||
endforeach()
|
||||
foreach(lcd_rle_asset IN LISTS platform_lcd_flash_assets)
|
||||
foreach(lcd_png_asset IN LISTS platform_lcd_png_assets)
|
||||
list(APPEND platform_asset_stage_commands
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${platform_asset_image_dir}/lcd"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"${platform_lcd_asset_src_dir}/${lcd_rle_asset}.rle"
|
||||
"${platform_asset_image_dir}/lcd/${lcd_rle_asset}.rle"
|
||||
"${platform_lcd_asset_src_dir}/${lcd_png_asset}.png"
|
||||
"${platform_asset_image_dir}/lcd/${lcd_png_asset}.png"
|
||||
)
|
||||
endforeach()
|
||||
|
||||
@@ -144,7 +154,7 @@ add_custom_command(
|
||||
COMMAND ${CMAKE_COMMAND} -E touch "${platform_asset_stage_stamp}"
|
||||
DEPENDS
|
||||
${platform_audio_prompt_files}
|
||||
${platform_lcd_flash_asset_files}
|
||||
${platform_lcd_png_asset_files}
|
||||
)
|
||||
|
||||
littlefs_create_partition_image(
|
||||
|
||||
|
After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 283 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 283 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 283 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 284 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 283 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 285 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 287 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 289 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 286 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 285 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 283 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 285 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 286 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 283 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 284 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 285 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 283 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 282 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 282 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 291 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 283 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 294 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 281 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 287 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 271 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 248 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 249 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 250 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 290 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 281 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 374 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 281 KiB |
@@ -8,22 +8,11 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
uint16_t color;
|
||||
uint16_t count;
|
||||
} lcd_direct_rle_run_t;
|
||||
|
||||
typedef struct {
|
||||
const lcd_direct_rle_run_t *runs;
|
||||
uint32_t run_count;
|
||||
const char *path;
|
||||
} lcd_direct_image_t;
|
||||
|
||||
typedef struct {
|
||||
const lcd_direct_rle_run_t *runs;
|
||||
uint32_t run_count;
|
||||
const char *path;
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
} lcd_direct_icon_t;
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
Copyright (c) 2005-2018 Lode Vandevenne
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
||||
@@ -0,0 +1,32 @@
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "esp_heap_caps.h"
|
||||
#include "soc/soc_caps.h"
|
||||
|
||||
void *lodepng_malloc(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);
|
||||
}
|
||||
|
||||
void *lodepng_realloc(void *ptr, size_t new_size)
|
||||
{
|
||||
#if SOC_SPIRAM_SUPPORTED
|
||||
void *new_ptr = heap_caps_realloc(ptr, new_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
||||
if (new_ptr != NULL) {
|
||||
return new_ptr;
|
||||
}
|
||||
#endif
|
||||
return realloc(ptr, new_size);
|
||||
}
|
||||
|
||||
void lodepng_free(void *ptr)
|
||||
{
|
||||
free(ptr);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#include <stddef.h>
|
||||
|
||||
#include "lodepng.h"
|
||||
|
||||
extern "C" unsigned lodepng_esp_decode32(unsigned char **out,
|
||||
unsigned *w,
|
||||
unsigned *h,
|
||||
const unsigned char *in,
|
||||
size_t insize)
|
||||
{
|
||||
return lodepng_decode32(out, w, h, in, insize);
|
||||
}
|
||||
|
||||
extern "C" const char *lodepng_esp_error_text(unsigned code)
|
||||
{
|
||||
return lodepng_error_text(code);
|
||||
}
|
||||