add some code

This commit is contained in:
2025-09-05 13:25:11 +08:00
parent 9ff0a99e7a
commit 3cf1229a85
8911 changed files with 2535396 additions and 0 deletions

View File

@@ -0,0 +1,178 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_log.h"
#include "esp_err.h"
#include "core/gfx_types.h"
#include "core/gfx_obj.h"
#define OPA_MAX 253 /*Opacities above this will fully cover*/
#define OPA_TRANSP 0
#define OPA_COVER 0xFF
#define FILL_NORMAL_MASK_PX(color, swap) \
if(*mask == OPA_COVER) *dest_buf = color; \
else *dest_buf = gfx_blend_color_mix(color, *dest_buf, *mask, swap); \
mask++; \
dest_buf++;
gfx_color_t gfx_blend_color_mix(gfx_color_t c1, gfx_color_t c2, uint8_t mix, bool swap)
{
gfx_color_t ret;
if (swap) {
c1.full = c1.full << 8 | c1.full >> 8;
c2.full = c2.full << 8 | c2.full >> 8;
}
/*Source: https://stackoverflow.com/a/50012418/1999969*/
mix = (uint32_t)((uint32_t)mix + 4) >> 3;
uint32_t bg = (uint32_t)((uint32_t)c2.full | ((uint32_t)c2.full << 16)) &
0x7E0F81F; /*0b00000111111000001111100000011111*/
uint32_t fg = (uint32_t)((uint32_t)c1.full | ((uint32_t)c1.full << 16)) & 0x7E0F81F;
uint32_t result = ((((fg - bg) * mix) >> 5) + bg) & 0x7E0F81F;
ret.full = (uint16_t)((result >> 16) | result);
if (swap) {
ret.full = ret.full << 8 | ret.full >> 8;
}
return ret;
}
void gfx_sw_blend_draw(gfx_color_t *dest_buf, gfx_coord_t dest_stride,
gfx_color_t color, gfx_opa_t opa,
const gfx_opa_t *mask, gfx_area_t *clip_area, gfx_coord_t mask_stride, bool swap)
{
int32_t w = clip_area->x2 - clip_area->x1;
int32_t h = clip_area->y2 - clip_area->y1;
int32_t x, y;
uint32_t c32 = color.full + ((uint32_t)color.full << 16);
/*Only the mask matters*/
if (opa >= OPA_MAX) {
int32_t x_end4 = w - 4;
for (y = 0; y < h; y++) {
for (x = 0; x < w && ((unsigned int)(mask) & 0x3); x++) {
FILL_NORMAL_MASK_PX(color, swap)
}
for (; x <= x_end4; x += 4) {
uint32_t mask32 = *((uint32_t *)mask);
if (mask32 == 0xFFFFFFFF) {
if ((unsigned int)dest_buf & 0x3) {/*dest_buf is not 4-byte aligned*/
*(dest_buf + 0) = color;
uint32_t * d = (uint32_t *)(dest_buf + 1);
*d = c32;
*(dest_buf + 3) = color;
} else {
uint32_t * d = (uint32_t *)dest_buf;
*d = c32;
*(d + 1) = c32;
}
dest_buf += 4;
mask += 4;
} else if (mask32) {
FILL_NORMAL_MASK_PX(color, swap)
FILL_NORMAL_MASK_PX(color, swap)
FILL_NORMAL_MASK_PX(color, swap)
FILL_NORMAL_MASK_PX(color, swap)
} else {
mask += 4;
dest_buf += 4;
}
}
for (; x < w ; x++) {
FILL_NORMAL_MASK_PX(color, swap)
}
dest_buf += (dest_stride - w);
mask += (mask_stride - w);
}
} else { /*With opacity*/
/*Buffer the result color to avoid recalculating the same color*/
gfx_color_t last_dest_color;
gfx_color_t last_res_color;
gfx_opa_t last_mask = OPA_TRANSP;
last_dest_color.full = dest_buf[0].full;
last_res_color.full = dest_buf[0].full;
gfx_opa_t opa_tmp = OPA_TRANSP;
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
if (*mask) {
if (*mask != last_mask) opa_tmp = *mask == OPA_COVER ? opa :
(uint32_t)((uint32_t)(*mask) * opa) >> 8;
if (*mask != last_mask || last_dest_color.full != dest_buf[x].full) {
if (opa_tmp == OPA_COVER) {
last_res_color = color;
} else {
last_res_color = gfx_blend_color_mix(color, dest_buf[x], opa_tmp, swap);
}
last_mask = *mask;
last_dest_color.full = dest_buf[x].full;
}
dest_buf[x] = last_res_color;
}
mask++;
}
dest_buf += dest_stride;
mask += (mask_stride - w);
}
}
}
void gfx_sw_blend_img_draw(gfx_color_t *dest_buf, gfx_coord_t dest_stride,
const gfx_color_t *src_buf, gfx_coord_t src_stride,
const gfx_opa_t *mask, gfx_coord_t mask_stride,
gfx_area_t *clip_area, gfx_opa_t opa, bool swap)
{
int32_t w = clip_area->x2 - clip_area->x1;
int32_t h = clip_area->y2 - clip_area->y1;
int32_t x, y;
gfx_color_t last_dest_color;
gfx_color_t last_res_color;
gfx_color_t last_src_color;
gfx_opa_t last_mask = OPA_TRANSP;
last_dest_color.full = dest_buf[0].full;
last_res_color.full = dest_buf[0].full;
last_src_color.full = src_buf[0].full;
gfx_opa_t opa_tmp = OPA_TRANSP;
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
if (mask == NULL || *mask) {
if (mask && *mask != last_mask) {
opa_tmp = (*mask == OPA_COVER) ? opa : (uint32_t)((uint32_t)(*mask) * opa) >> 8;
}
if (mask == NULL || *mask != last_mask || last_dest_color.full != dest_buf[x].full || last_src_color.full != src_buf[x].full) {
if (opa_tmp == OPA_COVER) {
last_res_color = src_buf[x];
} else {
last_res_color = gfx_blend_color_mix(src_buf[x], dest_buf[x], opa_tmp, swap);
}
if (mask) {
last_mask = *mask;
}
last_dest_color.full = dest_buf[x].full;
last_src_color.full = src_buf[x].full;
}
dest_buf[x] = last_res_color;
}
if (mask) {
mask++;
}
}
dest_buf += dest_stride;
src_buf += src_stride;
if (mask) {
mask += (mask_stride - w);
}
}
}

View File

@@ -0,0 +1,30 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "core/gfx_types.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
gfx_color_t gfx_color_hex(uint32_t c)
{
gfx_color_t r;
r.full = (uint16_t)(((c & 0xF80000) >> 8) | ((c & 0xFC00) >> 5) | ((c & 0xFF) >> 3));
return r;
}

View File

@@ -0,0 +1,645 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include <string.h>
#include "esp_timer.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_check.h"
#include "core/gfx_core.h"
#include "core/gfx_core_internal.h"
#include "core/gfx_timer.h"
#include "core/gfx_timer_internal.h"
#include "core/gfx_obj.h"
#include "widget/gfx_img.h"
#include "widget/gfx_img_internal.h"
#include "widget/gfx_label.h"
#include "widget/gfx_anim.h"
#include "decoder/gfx_img_decoder.h"
#include "core/gfx_types.h"
#include "widget/gfx_font_internal.h"
#include "widget/gfx_label_internal.h"
#include "widget/gfx_anim_internal.h"
static const char *TAG = "gfx_core";
// Forward declarations
static bool gfx_refr_handler(gfx_core_context_t *ctx);
static bool gfx_event_handler(gfx_core_context_t *ctx);
static bool gfx_object_handler(gfx_core_context_t *ctx);
static esp_err_t gfx_buf_init_frame(gfx_core_context_t *ctx, const gfx_core_config_t *cfg);
static void gfx_buf_free_frame(gfx_core_context_t *ctx);
static uint32_t gfx_calculate_task_delay(uint32_t timer_delay);
/**
* @brief Calculate task delay based on timer delay and system tick rate
* @param timer_delay Timer delay in milliseconds
* @return Calculated task delay in milliseconds
*/
static uint32_t gfx_calculate_task_delay(uint32_t timer_delay)
{
// Dynamic delay calculation based on tick rate
// Ensure minimum delay to prevent busy waiting
uint32_t min_delay_ms = (1000 / configTICK_RATE_HZ) + 1; // At least one tick + 1ms
if (timer_delay == ANIM_NO_TIMER_READY) {
return (min_delay_ms > 5) ? min_delay_ms : 5;
} else {
return (timer_delay < min_delay_ms) ? min_delay_ms : timer_delay;
}
}
/**
* @brief Handle system events and user requests
* @param ctx Player context
* @return true if event was handled, false otherwise
*/
static bool gfx_event_handler(gfx_core_context_t *ctx)
{
EventBits_t event_bits = xEventGroupWaitBits(ctx->sync.event_group,
NEED_DELETE, pdTRUE, pdFALSE, pdMS_TO_TICKS(0));
if (event_bits & NEED_DELETE) {
ESP_LOGW(TAG, "Player deletion requested");
xEventGroupSetBits(ctx->sync.event_group, DELETE_DONE);
vTaskDeleteWithCaps(NULL);
return true;
}
return false;
}
/**
* @brief Handle object updates and preprocessing
* @param ctx Player context
* @return true if objects need rendering, false otherwise
*/
static bool gfx_object_handler(gfx_core_context_t *ctx)
{
if (ctx->disp.child_list == NULL) {
return false;
}
bool needs_rendering = false;
gfx_core_child_t *child_node = ctx->disp.child_list;
while (child_node != NULL) {
gfx_obj_t *obj = (gfx_obj_t *)child_node->src;
if (obj->type == GFX_OBJ_TYPE_ANIMATION) {
gfx_anim_property_t *anim = (gfx_anim_property_t *)obj->src;
if (anim && anim->file_desc && anim->is_playing) {
needs_rendering = true;
if (!gfx_anim_preprocess_frame(anim)) {
continue;
}
}
}
child_node = child_node->next;
}
return needs_rendering;
}
/**
* @brief Calculate frame buffer height from buffer size
* @param ctx Player context
* @return Frame buffer height in pixels, or 0 if invalid
*/
static int gfx_buf_get_height(gfx_core_context_t *ctx)
{
if (ctx->disp.buf_pixels == 0 || ctx->display.h_res == 0) {
return 0;
}
return ctx->disp.buf_pixels / (ctx->display.h_res);
}
/**
* @brief Initialize frame buffers (internal or external)
* @param ctx Player context
* @param cfg Graphics configuration (includes buffer configuration)
* @return esp_err_t ESP_OK on success, otherwise error code
*/
static esp_err_t gfx_buf_init_frame(gfx_core_context_t *ctx, const gfx_core_config_t *cfg)
{
ESP_LOGD(TAG, "cfg.buffers.buf1=%p, cfg.buffers.buf2=%p", cfg->buffers.buf1, cfg->buffers.buf2);
if (cfg->buffers.buf1 != NULL) {
ctx->disp.buf1 = (uint16_t *)cfg->buffers.buf1;
ctx->disp.buf2 = (uint16_t *)cfg->buffers.buf2;
if (cfg->buffers.buf_pixels > 0) {
ctx->disp.buf_pixels = cfg->buffers.buf_pixels;
} else {
ESP_LOGW(TAG, "cfg.buffers.buf_pixels is 0, use default size");
ctx->disp.buf_pixels = ctx->display.h_res * ctx->display.v_res;
}
ctx->disp.ext_bufs = true;
} else {
// Allocate internal buffers
uint32_t buff_caps = 0;
#if SOC_PSRAM_DMA_CAPABLE == 0
if (cfg->flags.buff_dma && cfg->flags.buff_spiram) {
ESP_LOGW(TAG, "Alloc DMA capable buffer in SPIRAM is not supported!");
return ESP_ERR_NOT_SUPPORTED;
}
#endif
if (cfg->flags.buff_dma) {
buff_caps |= MALLOC_CAP_DMA;
}
if (cfg->flags.buff_spiram) {
buff_caps |= MALLOC_CAP_SPIRAM;
}
if (buff_caps == 0) {
buff_caps |= MALLOC_CAP_DEFAULT;
}
size_t buf_pixels = cfg->buffers.buf_pixels > 0 ? cfg->buffers.buf_pixels : ctx->display.h_res * ctx->display.v_res;
ctx->disp.buf1 = (uint16_t *)heap_caps_malloc(buf_pixels * sizeof(uint16_t), buff_caps);
if (!ctx->disp.buf1) {
ESP_LOGE(TAG, "Failed to allocate frame buffer 1");
return ESP_ERR_NO_MEM;
}
if (cfg->flags.double_buffer) {
ctx->disp.buf2 = (uint16_t *)heap_caps_malloc(buf_pixels * sizeof(uint16_t), buff_caps);
if (!ctx->disp.buf2) {
ESP_LOGE(TAG, "Failed to allocate frame buffer 2");
free(ctx->disp.buf1);
ctx->disp.buf1 = NULL;
return ESP_ERR_NO_MEM;
}
}
ctx->disp.buf_pixels = buf_pixels;
ctx->disp.ext_bufs = false;
}
ESP_LOGD(TAG, "Use frame buffers: buf1=%p, buf2=%p, size=%zu, ext_bufs=%d",
ctx->disp.buf1, ctx->disp.buf2, ctx->disp.buf_pixels, ctx->disp.ext_bufs);
ctx->disp.buf_act = ctx->disp.buf1;
ctx->disp.bg_color.full = 0x0000;
return ESP_OK;
}
/**
* @brief Free frame buffers (only internal buffers)
* @param ctx Player context
*/
static void gfx_buf_free_frame(gfx_core_context_t *ctx)
{
// Only free buffers if they were internally allocated
if (!ctx->disp.ext_bufs) {
if (ctx->disp.buf1) {
free(ctx->disp.buf1);
ctx->disp.buf1 = NULL;
}
if (ctx->disp.buf2) {
free(ctx->disp.buf2);
ctx->disp.buf2 = NULL;
}
ESP_LOGI(TAG, "Freed internal frame buffers");
} else {
ESP_LOGI(TAG, "External buffers provided by user, not freeing");
}
ctx->disp.buf_pixels = 0;
ctx->disp.ext_bufs = false;
}
gfx_ft_lib_handle_t gfx_get_font_lib(gfx_handle_t handle)
{
gfx_core_context_t *ctx = (gfx_core_context_t *)handle;
if (ctx == NULL) {
ESP_LOGE(TAG, "gfx_get_font_lib: ctx is NULL");
return NULL;
}
return ctx->disp.font_lib;
}
void gfx_draw_child(gfx_core_context_t *ctx, int x1, int y1, int x2, int y2, const void *dest_buf)
{
if (ctx->disp.child_list == NULL) {
ESP_LOGD(TAG, "no child objects");
return;
}
gfx_core_child_t *child_node = ctx->disp.child_list;
bool swap = ctx->display.flags.swap;
while (child_node != NULL) {
gfx_obj_t *obj = (gfx_obj_t *)child_node->src;
// Skip rendering if object is not visible
if (!obj->is_visible) {
child_node = child_node->next;
continue;
}
if (obj->type == GFX_OBJ_TYPE_LABEL) {
gfx_draw_label(obj, x1, y1, x2, y2, dest_buf, swap);
} else if (obj->type == GFX_OBJ_TYPE_IMAGE) {
gfx_draw_img(obj, x1, y1, x2, y2, dest_buf, swap);
} else if (obj->type == GFX_OBJ_TYPE_ANIMATION) {
gfx_draw_animation(obj, x1, y1, x2, y2, dest_buf, swap);
}
child_node = child_node->next;
}
}
static void gfx_core_task(void *arg)
{
gfx_core_context_t *ctx = (gfx_core_context_t *)arg;
uint32_t timer_delay = 1; // Default delay
while (1) {
if (ctx->sync.lock_mutex && xSemaphoreTakeRecursive(ctx->sync.lock_mutex, portMAX_DELAY) == pdTRUE) {
if (gfx_event_handler(ctx)) {
// Event was handled (e.g., deletion request), task will be deleted
xSemaphoreGiveRecursive(ctx->sync.lock_mutex);
break;
}
timer_delay = gfx_timer_handler(&ctx->timer.timer_mgr);
if (ctx->disp.child_list != NULL) {
gfx_refr_handler(ctx);
}
uint32_t task_delay = gfx_calculate_task_delay(timer_delay);
xSemaphoreGiveRecursive(ctx->sync.lock_mutex);
vTaskDelay(pdMS_TO_TICKS(task_delay));
} else {
ESP_LOGW(TAG, "Failed to acquire mutex, retrying...");
vTaskDelay(pdMS_TO_TICKS(1));
}
}
}
bool gfx_emote_flush_ready(gfx_handle_t handle, bool swap_act_buf)
{
gfx_core_context_t *ctx = (gfx_core_context_t *)handle;
if (ctx == NULL) {
return false;
}
if (xPortInIsrContext()) {
BaseType_t pxHigherPriorityTaskWoken = pdFALSE;
ctx->disp.swap_act_buf = swap_act_buf;
bool result = xEventGroupSetBitsFromISR(ctx->sync.event_group, WAIT_FLUSH_DONE, &pxHigherPriorityTaskWoken);
if (pxHigherPriorityTaskWoken == pdTRUE) {
portYIELD_FROM_ISR();
}
return result;
} else {
ctx->disp.swap_act_buf = swap_act_buf;
return xEventGroupSetBits(ctx->sync.event_group, WAIT_FLUSH_DONE);
}
}
void *gfx_emote_get_user_data(gfx_handle_t handle)
{
gfx_core_context_t *ctx = (gfx_core_context_t *)handle;
if (ctx == NULL) {
ESP_LOGE(TAG, "Invalid graphics context");
return NULL;
}
return ctx->callbacks.user_data;
}
esp_err_t gfx_emote_get_screen_size(gfx_handle_t handle, uint32_t *width, uint32_t *height)
{
gfx_core_context_t *ctx = (gfx_core_context_t *)handle;
if (ctx == NULL) {
ESP_LOGE(TAG, "Invalid graphics context");
return ESP_ERR_INVALID_ARG;
}
if (width == NULL || height == NULL) {
ESP_LOGE(TAG, "Invalid parameters");
return ESP_ERR_INVALID_ARG;
}
*width = ctx->display.h_res;
*height = ctx->display.v_res;
return ESP_OK;
}
gfx_handle_t gfx_emote_init(const gfx_core_config_t *cfg)
{
if (!cfg) {
ESP_LOGE(TAG, "Invalid configuration");
return NULL;
}
gfx_core_context_t *disp_ctx = malloc(sizeof(gfx_core_context_t));
if (!disp_ctx) {
ESP_LOGE(TAG, "Failed to allocate player context");
return NULL;
}
// Initialize all fields to zero/NULL
memset(disp_ctx, 0, sizeof(gfx_core_context_t));
disp_ctx->display.v_res = cfg->v_res;
disp_ctx->display.h_res = cfg->h_res;
disp_ctx->display.flags.swap = cfg->flags.swap;
disp_ctx->callbacks.flush_cb = cfg->flush_cb;
disp_ctx->callbacks.update_cb = cfg->update_cb;
disp_ctx->callbacks.user_data = cfg->user_data;
disp_ctx->sync.event_group = xEventGroupCreate();
disp_ctx->disp.child_list = NULL;
// Initialize frame buffers (internal or external)
esp_err_t buffer_ret = gfx_buf_init_frame(disp_ctx, cfg);
if (buffer_ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize frame buffers");
vEventGroupDelete(disp_ctx->sync.event_group);
free(disp_ctx);
return NULL;
}
// Initialize timer manager
gfx_timer_manager_init(&disp_ctx->timer.timer_mgr, cfg->fps);
// Create recursive render mutex for protecting rendering operations
disp_ctx->sync.lock_mutex = xSemaphoreCreateRecursiveMutex();
if (disp_ctx->sync.lock_mutex == NULL) {
ESP_LOGE(TAG, "Failed to create recursive render mutex");
gfx_buf_free_frame(disp_ctx);
vEventGroupDelete(disp_ctx->sync.event_group);
free(disp_ctx);
return NULL;
}
esp_err_t font_ret = gfx_ft_lib_create(&disp_ctx->disp.font_lib);
if (font_ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to create font library");
gfx_buf_free_frame(disp_ctx);
vSemaphoreDelete(disp_ctx->sync.lock_mutex);
vEventGroupDelete(disp_ctx->sync.event_group);
free(disp_ctx);
return NULL;
}
// Initialize image decoder system
esp_err_t decoder_ret = gfx_image_decoder_init();
if (decoder_ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize image decoder system");
gfx_ft_lib_cleanup(disp_ctx->disp.font_lib);
gfx_buf_free_frame(disp_ctx);
vSemaphoreDelete(disp_ctx->sync.lock_mutex);
vEventGroupDelete(disp_ctx->sync.event_group);
free(disp_ctx);
return NULL;
}
const uint32_t stack_caps = cfg->task.task_stack_caps ? cfg->task.task_stack_caps : MALLOC_CAP_DEFAULT; // caps cannot be zero
if (cfg->task.task_affinity < 0) {
xTaskCreateWithCaps(gfx_core_task, "gfx_core", cfg->task.task_stack, disp_ctx, cfg->task.task_priority, NULL, stack_caps);
} else {
xTaskCreatePinnedToCoreWithCaps(gfx_core_task, "gfx_core", cfg->task.task_stack, disp_ctx, cfg->task.task_priority, NULL, cfg->task.task_affinity, stack_caps);
}
return (gfx_handle_t)disp_ctx;
}
void gfx_emote_deinit(gfx_handle_t handle)
{
gfx_core_context_t *ctx = (gfx_core_context_t *)handle;
if (ctx == NULL) {
ESP_LOGE(TAG, "Invalid graphics context");
return;
}
xEventGroupSetBits(ctx->sync.event_group, NEED_DELETE);
xEventGroupWaitBits(ctx->sync.event_group, DELETE_DONE, pdTRUE, pdFALSE, portMAX_DELAY);
// Free all child nodes
gfx_core_child_t *child_node = ctx->disp.child_list;
while (child_node != NULL) {
gfx_core_child_t *next_child = child_node->next;
free(child_node);
child_node = next_child;
}
ctx->disp.child_list = NULL;
// Clean up timers
gfx_timer_manager_deinit(&ctx->timer.timer_mgr);
// Free frame buffers
gfx_buf_free_frame(ctx);
// Delete font library
if (ctx->disp.font_lib) {
gfx_ft_lib_cleanup(ctx->disp.font_lib);
ctx->disp.font_lib = NULL;
}
// Delete mutex
if (ctx->sync.lock_mutex) {
vSemaphoreDelete(ctx->sync.lock_mutex);
ctx->sync.lock_mutex = NULL;
}
// Delete event group
if (ctx->sync.event_group) {
vEventGroupDelete(ctx->sync.event_group);
ctx->sync.event_group = NULL;
}
// Deinitialize image decoder system
gfx_image_decoder_deinit();
// Free context
free(ctx);
}
esp_err_t gfx_emote_add_chlid(gfx_handle_t handle, int type, void *src)
{
gfx_core_context_t *ctx = (gfx_core_context_t *)handle;
if (ctx == NULL || src == NULL) {
ESP_LOGE(TAG, "Invalid parameters");
return ESP_ERR_INVALID_ARG;
}
gfx_core_child_t *new_child = (gfx_core_child_t *)malloc(sizeof(gfx_core_child_t));
if (new_child == NULL) {
ESP_LOGE(TAG, "Failed to allocate child node");
return ESP_ERR_NO_MEM;
}
new_child->type = type;
new_child->src = src;
new_child->next = NULL;
// Add to child list
if (ctx->disp.child_list == NULL) {
ctx->disp.child_list = new_child;
} else {
gfx_core_child_t *current = ctx->disp.child_list;
while (current->next != NULL) {
current = current->next;
}
current->next = new_child;
}
ESP_LOGD(TAG, "Added child object of type %d", type);
return ESP_OK;
}
esp_err_t gfx_emote_remove_child(gfx_handle_t handle, void *src)
{
gfx_core_context_t *ctx = (gfx_core_context_t *)handle;
if (ctx == NULL || src == NULL) {
ESP_LOGE(TAG, "Invalid parameters");
return ESP_ERR_INVALID_ARG;
}
gfx_core_child_t *current = ctx->disp.child_list;
gfx_core_child_t *prev = NULL;
while (current != NULL) {
if (current->src == src) {
if (prev == NULL) {
ctx->disp.child_list = current->next;
} else {
prev->next = current->next;
}
free(current);
ESP_LOGD(TAG, "Removed child object from list");
return ESP_OK;
}
prev = current;
current = current->next;
}
ESP_LOGW(TAG, "Child object not found in list");
return ESP_ERR_NOT_FOUND;
}
esp_err_t gfx_emote_lock(gfx_handle_t handle)
{
gfx_core_context_t *ctx = (gfx_core_context_t *)handle;
if (ctx == NULL || ctx->sync.lock_mutex == NULL) {
ESP_LOGE(TAG, "Invalid graphics context or mutex");
return ESP_ERR_INVALID_ARG;
}
if (xSemaphoreTakeRecursive(ctx->sync.lock_mutex, portMAX_DELAY) != pdTRUE) {
ESP_LOGE(TAG, "Failed to acquire graphics lock");
return ESP_ERR_TIMEOUT;
}
return ESP_OK;
}
esp_err_t gfx_emote_unlock(gfx_handle_t handle)
{
gfx_core_context_t *ctx = (gfx_core_context_t *)handle;
if (ctx == NULL || ctx->sync.lock_mutex == NULL) {
ESP_LOGE(TAG, "Invalid graphics context or mutex");
return ESP_ERR_INVALID_ARG;
}
if (xSemaphoreGiveRecursive(ctx->sync.lock_mutex) != pdTRUE) {
ESP_LOGE(TAG, "Failed to release graphics lock");
return ESP_ERR_INVALID_STATE;
}
return ESP_OK;
}
esp_err_t gfx_emote_set_bg_color(gfx_handle_t handle, gfx_color_t color)
{
gfx_core_context_t *ctx = (gfx_core_context_t *)handle;
if (ctx == NULL) {
ESP_LOGE(TAG, "Invalid graphics context");
return ESP_ERR_INVALID_ARG;
}
ctx->disp.bg_color = color;
ESP_LOGD(TAG, "Set background color to 0x%04X", color.full);
return ESP_OK;
}
bool gfx_emote_is_flushing_last(gfx_handle_t handle)
{
gfx_core_context_t *ctx = (gfx_core_context_t *)handle;
if (ctx == NULL) {
ESP_LOGE(TAG, "Invalid graphics context");
return false;
}
return ctx->disp.flushing_last;
}
/**
* @brief Handle rendering of all objects in the scene
* @param ctx Player context
* @return true if rendering was performed, false otherwise
*/
static bool gfx_refr_handler(gfx_core_context_t *ctx)
{
bool needs_rendering = gfx_object_handler(ctx);
if (!needs_rendering) {
// return false;
}
int block_height = gfx_buf_get_height(ctx);
if (block_height == 0) {
ESP_LOGE(TAG, "Invalid frame buffer size");
return false;
}
int v_res = ctx->display.v_res;
int h_res = ctx->display.h_res;
int total_blocks = (v_res + block_height - 1) / block_height;
for (int block_idx = 0; block_idx < total_blocks; block_idx++) {
int x1 = 0;
int x2 = h_res;
int y1 = block_idx * block_height;
int y2 = ((block_idx + 1) * block_height > v_res) ? v_res : (block_idx + 1) * block_height;
// Set flag for last block
ctx->disp.flushing_last = (block_idx == total_blocks - 1);
uint16_t *buf_act = ctx->disp.buf_act;
uint16_t bg_color = ctx->disp.bg_color.full;
size_t pixels = ctx->disp.buf_pixels;
for (size_t i = 0; i < pixels; i++) {//影响帧率
buf_act[i] = bg_color;
}
gfx_draw_child(ctx, x1, y1, x2, y2, buf_act);
if (ctx->callbacks.flush_cb) {
xEventGroupClearBits(ctx->sync.event_group, WAIT_FLUSH_DONE);
ctx->callbacks.flush_cb(ctx, x1, y1, x2, y2, buf_act);
xEventGroupWaitBits(ctx->sync.event_group, WAIT_FLUSH_DONE, pdTRUE, pdFALSE, pdMS_TO_TICKS(20));
}
if ((ctx->disp.flushing_last || ctx->disp.swap_act_buf) && ctx->disp.buf2 != NULL) {
if (ctx->disp.buf_act == ctx->disp.buf1) {
ctx->disp.buf_act = ctx->disp.buf2;
} else {
ctx->disp.buf_act = ctx->disp.buf1;
}
ctx->disp.swap_act_buf = false;
}
}
return true;
}

View File

@@ -0,0 +1,762 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include <string.h>
#include "esp_err.h"
#include "esp_log.h"
#include "esp_check.h"
#include "core/gfx_core.h"
#include "core/gfx_core_internal.h"
#include "core/gfx_timer.h"
#include "core/gfx_obj.h"
#include "widget/gfx_img.h"
#include "widget/gfx_img_internal.h"
#include "widget/gfx_label.h"
#include "widget/gfx_label_internal.h"
#include "widget/gfx_anim.h"
#include "core/gfx_types.h"
#include "decoder/gfx_aaf_dec.h"
#include "widget/gfx_anim_internal.h"
#include "widget/gfx_font_internal.h"
#include "decoder/gfx_aaf_format.h"
#include "decoder/gfx_img_decoder.h"
static const char *TAG = "gfx_obj";
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
/*=====================
* Object creation
*====================*/
gfx_obj_t * gfx_img_create(gfx_handle_t handle)
{
gfx_obj_t *obj = (gfx_obj_t *)malloc(sizeof(gfx_obj_t));
if (obj == NULL) {
ESP_LOGE(TAG, "Failed to allocate memory for image object");
return NULL;
}
memset(obj, 0, sizeof(gfx_obj_t));
obj->type = GFX_OBJ_TYPE_IMAGE;
obj->parent_handle = handle;
obj->is_visible = true; // Default to hidden
gfx_emote_add_chlid(handle, GFX_OBJ_TYPE_IMAGE, obj);
ESP_LOGD(TAG, "Created image object");
return obj;
}
gfx_obj_t * gfx_label_create(gfx_handle_t handle)
{
gfx_obj_t *obj = (gfx_obj_t *)malloc(sizeof(gfx_obj_t));
if (obj == NULL) {
ESP_LOGE(TAG, "Failed to allocate memory for label object");
return NULL;
}
memset(obj, 0, sizeof(gfx_obj_t));
obj->type = GFX_OBJ_TYPE_LABEL;
obj->parent_handle = handle;
obj->is_visible = true; // Default to hidden
gfx_label_property_t *label = (gfx_label_property_t *)malloc(sizeof(gfx_label_property_t));
if (label == NULL) {
ESP_LOGE(TAG, "Failed to allocate memory for label object");
free(obj);
return NULL;
}
memset(label, 0, sizeof(gfx_label_property_t));
// Apply default font configuration
gfx_font_t default_font;
uint16_t default_size;
gfx_color_t bg_color;
gfx_opa_t default_opa;
// Get default font configuration from internal function
gfx_get_default_font_config(&default_font, &default_size, &bg_color, &default_opa);
label->font_size = default_size;
label->color = bg_color;
label->opa = default_opa;
label->mask = NULL;
label->bg_color = (gfx_color_t) {
.full = 0x0000
}; // Default: black background
label->bg_enable = false; // Default: background disabled
label->bg_dirty = false; // Default: background not dirty
label->text_align = GFX_TEXT_ALIGN_LEFT; // Initialize with default left alignment
label->long_mode = GFX_LABEL_LONG_CLIP; // Default to clipping
label->line_spacing = 2;
// Initialize horizontal scroll properties
label->scroll_offset = 0;
label->scroll_speed_ms = 50; // Default: 50ms per pixel
label->scroll_loop = true;
label->scroll_active = false;
label->scroll_dirty = false; // Default: scroll position not dirty
label->scroll_timer = NULL;
label->text_width = 0;
// Initialize cached line data
label->cached_lines = NULL;
label->cached_line_count = 0;
label->cached_line_widths = NULL;
// Set default font automatically
if (default_font) {
label->face = (void *)default_font;
}
obj->src = label;
gfx_emote_add_chlid(handle, GFX_OBJ_TYPE_LABEL, obj);
ESP_LOGD(TAG, "Created label object with default font config");
return obj;
}
/*=====================
* Setter functions
*====================*/
gfx_obj_t * gfx_img_set_src(gfx_obj_t *obj, void *src)
{
if (obj == NULL) {
ESP_LOGE(TAG, "Object is NULL");
return NULL;
}
if (obj->type != GFX_OBJ_TYPE_IMAGE) {
ESP_LOGE(TAG, "Object is not an image type");
return NULL;
}
obj->src = src;
// Update object size based on image data using unified decoder
if (src != NULL) {
gfx_image_header_t header;
gfx_image_decoder_dsc_t dsc = {
.src = src,
};
esp_err_t ret = gfx_image_decoder_info(&dsc, &header);
if (ret == ESP_OK) {
obj->width = header.w;
obj->height = header.h;
} else {
ESP_LOGE(TAG, "Failed to get image info from source");
}
}
ESP_LOGD(TAG, "Set image source, size: %dx%d", obj->width, obj->height);
return obj;
}
void gfx_obj_set_pos(gfx_obj_t *obj, gfx_coord_t x, gfx_coord_t y)
{
if (obj == NULL) {
ESP_LOGE(TAG, "Object is NULL");
return;
}
obj->x = x;
obj->y = y;
obj->use_align = false;
ESP_LOGD(TAG, "Set object position: (%d, %d)", x, y);
}
void gfx_obj_set_size(gfx_obj_t *obj, uint16_t w, uint16_t h)
{
if (obj == NULL) {
ESP_LOGE(TAG, "Object is NULL");
return;
}
if (obj->type == GFX_OBJ_TYPE_ANIMATION || obj->type == GFX_OBJ_TYPE_IMAGE) {
ESP_LOGW(TAG, "Set size for animation or image is not allowed");
} else {
obj->width = w;
obj->height = h;
}
ESP_LOGD(TAG, "Set object size: %dx%d", w, h);
}
void gfx_obj_align(gfx_obj_t *obj, uint8_t align, gfx_coord_t x_ofs, gfx_coord_t y_ofs)
{
if (obj == NULL) {
ESP_LOGE(TAG, "Object is NULL");
return;
}
if (obj->parent_handle == NULL) {
ESP_LOGE(TAG, "Object has no parent handle");
return;
}
// Validate alignment type
if (align > GFX_ALIGN_OUT_BOTTOM_RIGHT) {
ESP_LOGW(TAG, "Unknown alignment type: %d", align);
return;
}
// Set alignment properties instead of directly setting position
obj->align_type = align;
obj->align_x_ofs = x_ofs;
obj->align_y_ofs = y_ofs;
obj->use_align = true;
ESP_LOGD(TAG, "Set object alignment: type=%d, offset=(%d, %d)", align, x_ofs, y_ofs);
}
void gfx_obj_set_visible(gfx_obj_t *obj, bool visible)
{
if (obj == NULL) {
ESP_LOGE(TAG, "Object is NULL");
return;
}
obj->is_visible = visible;
ESP_LOGD(TAG, "Set object visibility: %s", visible ? "visible" : "hidden");
}
bool gfx_obj_get_visible(gfx_obj_t *obj)
{
if (obj == NULL) {
ESP_LOGE(TAG, "Object is NULL");
return false;
}
return obj->is_visible;
}
/*=====================
* Static helper functions
*====================*/
void gfx_obj_calculate_aligned_position(gfx_obj_t *obj, uint32_t parent_width, uint32_t parent_height, gfx_coord_t *x, gfx_coord_t *y)
{
if (obj == NULL || x == NULL || y == NULL) {
return;
}
if (!obj->use_align) {
// Use absolute position if alignment is not enabled
*x = obj->x;
*y = obj->y;
return;
}
gfx_coord_t calculated_x = 0;
gfx_coord_t calculated_y = 0;
// Calculate position based on alignment
switch (obj->align_type) {
case GFX_ALIGN_TOP_LEFT:
calculated_x = obj->align_x_ofs;
calculated_y = obj->align_y_ofs;
break;
case GFX_ALIGN_TOP_MID:
calculated_x = ((gfx_coord_t)parent_width - obj->width) / 2 + obj->align_x_ofs;
calculated_y = obj->align_y_ofs;
break;
case GFX_ALIGN_TOP_RIGHT:
calculated_x = (gfx_coord_t)parent_width - obj->width + obj->align_x_ofs;
calculated_y = obj->align_y_ofs;
break;
case GFX_ALIGN_LEFT_MID:
calculated_x = obj->align_x_ofs;
calculated_y = ((gfx_coord_t)parent_height - obj->height) / 2 + obj->align_y_ofs;
break;
case GFX_ALIGN_CENTER:
calculated_x = ((gfx_coord_t)parent_width - obj->width) / 2 + obj->align_x_ofs;
calculated_y = ((gfx_coord_t)parent_height - obj->height) / 2 + obj->align_y_ofs;
break;
case GFX_ALIGN_RIGHT_MID:
calculated_x = (gfx_coord_t)parent_width - obj->width + obj->align_x_ofs;
calculated_y = ((gfx_coord_t)parent_height - obj->height) / 2 + obj->align_y_ofs;
break;
case GFX_ALIGN_BOTTOM_LEFT:
calculated_x = obj->align_x_ofs;
calculated_y = (gfx_coord_t)parent_height - obj->height + obj->align_y_ofs;
break;
case GFX_ALIGN_BOTTOM_MID:
calculated_x = ((gfx_coord_t)parent_width - obj->width) / 2 + obj->align_x_ofs;
calculated_y = (gfx_coord_t)parent_height - obj->height + obj->align_y_ofs;
break;
case GFX_ALIGN_BOTTOM_RIGHT:
calculated_x = (gfx_coord_t)parent_width - obj->width + obj->align_x_ofs;
calculated_y = (gfx_coord_t)parent_height - obj->height + obj->align_y_ofs;
break;
case GFX_ALIGN_OUT_TOP_LEFT:
calculated_x = obj->align_x_ofs;
calculated_y = -obj->height + obj->align_y_ofs;
break;
case GFX_ALIGN_OUT_TOP_MID:
calculated_x = ((gfx_coord_t)parent_width - obj->width) / 2 + obj->align_x_ofs;
calculated_y = -obj->height + obj->align_y_ofs;
break;
case GFX_ALIGN_OUT_TOP_RIGHT:
calculated_x = (gfx_coord_t)parent_width + obj->align_x_ofs;
calculated_y = -obj->height + obj->align_y_ofs;
break;
case GFX_ALIGN_OUT_LEFT_TOP:
calculated_x = -obj->width + obj->align_x_ofs;
calculated_y = obj->align_y_ofs;
break;
case GFX_ALIGN_OUT_LEFT_MID:
calculated_x = -obj->width + obj->align_x_ofs;
calculated_y = ((gfx_coord_t)parent_height - obj->height) / 2 + obj->align_y_ofs;
break;
case GFX_ALIGN_OUT_LEFT_BOTTOM:
calculated_x = -obj->width + obj->align_x_ofs;
calculated_y = (gfx_coord_t)parent_height + obj->align_y_ofs;
break;
case GFX_ALIGN_OUT_RIGHT_TOP:
calculated_x = (gfx_coord_t)parent_width + obj->align_x_ofs;
calculated_y = obj->align_y_ofs;
break;
case GFX_ALIGN_OUT_RIGHT_MID:
calculated_x = (gfx_coord_t)parent_width + obj->align_x_ofs;
calculated_y = ((gfx_coord_t)parent_height - obj->height) / 2 + obj->align_y_ofs;
break;
case GFX_ALIGN_OUT_RIGHT_BOTTOM:
calculated_x = (gfx_coord_t)parent_width + obj->align_x_ofs;
calculated_y = (gfx_coord_t)parent_height + obj->align_y_ofs;
break;
case GFX_ALIGN_OUT_BOTTOM_LEFT:
calculated_x = obj->align_x_ofs;
calculated_y = (gfx_coord_t)parent_height + obj->align_y_ofs;
break;
case GFX_ALIGN_OUT_BOTTOM_MID:
calculated_x = ((gfx_coord_t)parent_width - obj->width) / 2 + obj->align_x_ofs;
calculated_y = (gfx_coord_t)parent_height + obj->align_y_ofs;
break;
case GFX_ALIGN_OUT_BOTTOM_RIGHT:
calculated_x = (gfx_coord_t)parent_width + obj->align_x_ofs;
calculated_y = (gfx_coord_t)parent_height + obj->align_y_ofs;
break;
default:
ESP_LOGW(TAG, "Unknown alignment type: %d", obj->align_type);
// Fall back to absolute position
calculated_x = obj->x;
calculated_y = obj->y;
break;
}
*x = calculated_x;
*y = calculated_y;
}
/*=====================
* Getter functions
*====================*/
void gfx_obj_get_pos(gfx_obj_t *obj, gfx_coord_t *x, gfx_coord_t *y)
{
if (obj == NULL || x == NULL || y == NULL) {
ESP_LOGE(TAG, "Invalid parameters");
return;
}
*x = obj->x;
*y = obj->y;
}
void gfx_obj_get_size(gfx_obj_t *obj, uint16_t *w, uint16_t *h)
{
if (obj == NULL || w == NULL || h == NULL) {
ESP_LOGE(TAG, "Invalid parameters");
return;
}
*w = obj->width;
*h = obj->height;
}
/*=====================
* Other functions
*====================*/
void gfx_obj_delete(gfx_obj_t *obj)
{
if (obj == NULL) {
ESP_LOGE(TAG, "Object is NULL");
return;
}
ESP_LOGD(TAG, "Deleting object type: %d", obj->type);
// Remove object from parent's child list first
if (obj->parent_handle != NULL) {
gfx_emote_remove_child(obj->parent_handle, obj);
}
if (obj->type == GFX_OBJ_TYPE_LABEL) {
gfx_label_property_t *label = (gfx_label_property_t *)obj->src;
if (label) {
// Clean up scroll timer
if (label->scroll_timer) {
gfx_timer_delete(obj->parent_handle, label->scroll_timer);
label->scroll_timer = NULL;
}
// Clean up cached line data
if (label->cached_lines) {
for (int i = 0; i < label->cached_line_count; i++) {
if (label->cached_lines[i]) {
free(label->cached_lines[i]);
}
}
free(label->cached_lines);
label->cached_lines = NULL;
label->cached_line_count = 0;
}
// Clean up cached line widths
if (label->cached_line_widths) {
free(label->cached_line_widths);
label->cached_line_widths = NULL;
}
if (label->text) {
free(label->text);
}
if (label->mask) {
free(label->mask);
}
free(label);
}
} else if (obj->type == GFX_OBJ_TYPE_ANIMATION) {
gfx_anim_property_t *anim = (gfx_anim_property_t *)obj->src;
if (anim) {
// Stop animation if playing
if (anim->is_playing) {
gfx_anim_stop(obj);
}
// Delete timer
if (anim->timer != NULL) {
gfx_timer_delete((void *)obj->parent_handle, anim->timer);
anim->timer = NULL;
}
// Free frame processing information
gfx_anim_free_frame_info(&anim->frame);
// Free file descriptor
if (anim->file_desc) {
gfx_aaf_format_deinit(anim->file_desc);
}
free(anim);
}
}
free(obj);
}
/*=====================
* Animation object functions
*====================*/
static void gfx_anim_timer_callback(void *arg)
{
gfx_obj_t *obj = (gfx_obj_t *)arg;
gfx_anim_property_t *anim = (gfx_anim_property_t *)obj->src;
if (!anim || !anim->is_playing) {
ESP_LOGD(TAG, "anim is NULL or not playing");
return;
}
anim->current_frame++;
ESP_LOGD("anim cb", " %lu (%lu / %lu)", anim->current_frame, anim->start_frame, anim->end_frame);
// Check if we've reached the end
if (anim->current_frame > anim->end_frame) {
if (anim->repeat) {
ESP_LOGD(TAG, "REPEAT");
anim->current_frame = anim->start_frame;
} else {
ESP_LOGD(TAG, "STOP");
anim->is_playing = false;
return;
}
}
// Mark object as dirty to trigger redraw
obj->is_dirty = true;
}
gfx_obj_t * gfx_anim_create(gfx_handle_t handle)
{
gfx_obj_t *obj = (gfx_obj_t *)malloc(sizeof(gfx_obj_t));
if (obj == NULL) {
ESP_LOGE(TAG, "Failed to allocate memory for animation object");
return NULL;
}
memset(obj, 0, sizeof(gfx_obj_t));
obj->parent_handle = handle;
obj->is_visible = true; // Default to hidden
gfx_anim_property_t *anim = (gfx_anim_property_t *)malloc(sizeof(gfx_anim_property_t));
if (anim == NULL) {
ESP_LOGE(TAG, "Failed to allocate memory for animation property");
free(obj);
return NULL;
}
memset(anim, 0, sizeof(gfx_anim_property_t));
// Initialize animation properties
anim->file_desc = NULL;
anim->start_frame = 0;
anim->end_frame = 0;
anim->current_frame = 0;
anim->fps = 30; // Default FPS
anim->repeat = true;
anim->is_playing = false;
// Create timer during object creation
uint32_t period_ms = 1000 / anim->fps; // Convert FPS to period in milliseconds
anim->timer = gfx_timer_create((void *)obj->parent_handle, gfx_anim_timer_callback, period_ms, obj);
if (anim->timer == NULL) {
ESP_LOGE(TAG, "Failed to create animation timer");
free(anim);
free(obj);
return NULL;
}
// Initialize pre-parsed header fields
memset(&anim->frame.header, 0, sizeof(gfx_aaf_header_t));
// Initialize pre-fetched frame data fields
anim->frame.frame_data = NULL;
anim->frame.frame_size = 0;
// Initialize pre-allocated parsing resources fields
anim->frame.block_offsets = NULL;
anim->frame.pixel_buffer = NULL;
anim->frame.color_palette = NULL;
// Initialize decoding state
anim->frame.last_block = -1;
// Initialize widget-specific display properties
anim->mirror_enabled = false;
anim->mirror_offset = 0;
obj->src = anim;
obj->type = GFX_OBJ_TYPE_ANIMATION;
gfx_emote_add_chlid(handle, GFX_OBJ_TYPE_ANIMATION, obj);
return obj;
}
esp_err_t gfx_anim_set_src(gfx_obj_t *obj, const void *src_data, size_t src_len)
{
if (obj == NULL || src_data == NULL) {
ESP_LOGE(TAG, "Invalid parameters");
return ESP_ERR_INVALID_ARG;
}
if (obj->type != GFX_OBJ_TYPE_ANIMATION) {
ESP_LOGE(TAG, "Object is not an animation type");
return ESP_ERR_INVALID_ARG;
}
gfx_anim_property_t *anim = (gfx_anim_property_t *)obj->src;
if (anim == NULL) {
ESP_LOGE(TAG, "Animation property is NULL");
return ESP_ERR_INVALID_STATE;
}
// Stop current animation if playing
if (anim->is_playing) {
ESP_LOGD(TAG, "stop current animation");
gfx_anim_stop(obj);
}
// Free previously parsed header if exists
if (anim->frame.header.width > 0) { // Check if header data exists instead of header_valid flag
gfx_aaf_free_header(&anim->frame.header);
memset(&anim->frame.header, 0, sizeof(gfx_aaf_header_t)); // Clear header data
}
// Clear pre-fetched frame data
anim->frame.frame_data = NULL;
anim->frame.frame_size = 0;
gfx_aaf_format_handle_t new_desc;
gfx_aaf_format_init(src_data, src_len, &new_desc);
if (new_desc == NULL) {
ESP_LOGE(TAG, "Failed to initialize asset parser");
return ESP_FAIL;
}
//delete old file_desc
if (anim->file_desc) {
gfx_aaf_format_deinit(anim->file_desc);
anim->file_desc = NULL;
}
anim->file_desc = new_desc;
anim->start_frame = 0;
anim->current_frame = 0;
anim->end_frame = gfx_aaf_format_get_total_frames(new_desc) - 1;
ESP_LOGD(TAG, "set src, start: %lu, end: %lu, file_desc: %p", anim->start_frame, anim->end_frame, anim->file_desc);
return ESP_OK;
}
esp_err_t gfx_anim_set_segment(gfx_obj_t *obj, uint32_t start, uint32_t end, uint32_t fps, bool repeat)
{
if (obj == NULL) {
ESP_LOGE(TAG, "Object is NULL");
return ESP_ERR_INVALID_ARG;
}
if (obj->type != GFX_OBJ_TYPE_ANIMATION) {
ESP_LOGE(TAG, "Object is not an animation type");
return ESP_ERR_INVALID_ARG;
}
gfx_anim_property_t *anim = (gfx_anim_property_t *)obj->src;
if (anim == NULL) {
ESP_LOGE(TAG, "Animation property is NULL");
return ESP_ERR_INVALID_STATE;
}
int total_frames = gfx_aaf_format_get_total_frames(anim->file_desc);
anim->start_frame = start;
anim->end_frame = (end > total_frames - 1) ? (total_frames - 1) : end;
anim->current_frame = start;
if (anim->fps != fps) {
ESP_LOGI(TAG, "FPS changed from %lu to %lu, updating timer period", anim->fps, fps);
anim->fps = fps;
if (anim->timer != NULL) {
uint32_t new_period_ms = 1000 / fps;
gfx_timer_set_period(anim->timer, new_period_ms);
ESP_LOGI(TAG, "Animation timer period updated to %lu ms for %lu FPS", new_period_ms, fps);
}
}
anim->repeat = repeat;
ESP_LOGD(TAG, "Set animation segment: %lu -> %lu, fps: %lu, repeat: %d", start, end, fps, repeat);
return ESP_OK;
}
esp_err_t gfx_anim_start(gfx_obj_t *obj)
{
if (obj == NULL) {
ESP_LOGE(TAG, "Object is NULL");
return ESP_ERR_INVALID_ARG;
}
if (obj->type != GFX_OBJ_TYPE_ANIMATION) {
ESP_LOGE(TAG, "Object is not an animation type");
return ESP_ERR_INVALID_ARG;
}
gfx_anim_property_t *anim = (gfx_anim_property_t *)obj->src;
if (anim == NULL) {
ESP_LOGE(TAG, "Animation property is NULL");
return ESP_ERR_INVALID_STATE;
}
if (anim->file_desc == NULL) {
ESP_LOGE(TAG, "Animation source not set");
return ESP_ERR_INVALID_STATE;
}
if (anim->is_playing) {
ESP_LOGD(TAG, "Animation is already playing");
return ESP_OK;
}
anim->is_playing = true;
anim->current_frame = anim->start_frame;
ESP_LOGD(TAG, "Started animation");
return ESP_OK;
}
esp_err_t gfx_anim_stop(gfx_obj_t *obj)
{
if (obj == NULL) {
ESP_LOGE(TAG, "Object is NULL");
return ESP_ERR_INVALID_ARG;
}
if (obj->type != GFX_OBJ_TYPE_ANIMATION) {
ESP_LOGE(TAG, "Object is not an animation type");
return ESP_ERR_INVALID_ARG;
}
gfx_anim_property_t *anim = (gfx_anim_property_t *)obj->src;
if (anim == NULL) {
ESP_LOGE(TAG, "Animation property is NULL");
return ESP_ERR_INVALID_STATE;
}
if (!anim->is_playing) {
ESP_LOGD(TAG, "Animation is not playing");
return ESP_OK;
}
anim->is_playing = false;
ESP_LOGD(TAG, "Stopped animation");
return ESP_OK;
}
esp_err_t gfx_anim_set_mirror(gfx_obj_t *obj, bool enabled, int16_t offset)
{
if (obj == NULL) {
ESP_LOGE(TAG, "Object is NULL");
return ESP_ERR_INVALID_ARG;
}
if (obj->type != GFX_OBJ_TYPE_ANIMATION) {
ESP_LOGE(TAG, "Object is not an animation type");
return ESP_ERR_INVALID_ARG;
}
gfx_anim_property_t *anim = (gfx_anim_property_t *)obj->src;
if (anim == NULL) {
ESP_LOGE(TAG, "Animation property is NULL");
return ESP_ERR_INVALID_STATE;
}
// Set mirror properties
anim->mirror_enabled = enabled;
anim->mirror_offset = offset;
ESP_LOGD(TAG, "Set animation mirror: enabled=%s, offset=%d", enabled ? "true" : "false", offset);
return ESP_OK;
}

View File

@@ -0,0 +1,278 @@
#include "freertos/FreeRTOS.h"
#include "esp_timer.h"
#include "esp_err.h"
#include "esp_log.h"
#include "core/gfx_core.h"
#include "core/gfx_core_internal.h"
#include "core/gfx_timer.h"
#include "core/gfx_timer_internal.h"
static const char *TAG = "gfx_timer";
#define GFX_NO_TIMER_READY 0xFFFFFFFF
uint32_t gfx_timer_tick_get(void)
{
return (uint32_t)(esp_timer_get_time() / 1000); // Convert microseconds to milliseconds
}
uint32_t gfx_timer_tick_elaps(uint32_t prev_tick)
{
uint32_t act_time = gfx_timer_tick_get();
/*If there is no overflow in sys_time simple subtract*/
if (act_time >= prev_tick) {
prev_tick = act_time - prev_tick;
} else {
prev_tick = UINT32_MAX - prev_tick + 1;
prev_tick += act_time;
}
return prev_tick;
}
bool gfx_timer_exec(gfx_timer_t *timer)
{
if (timer == NULL || timer->paused) {
ESP_LOGD(TAG, "timer is NULL or paused");
return false;
}
// Don't execute if repeat_count is 0 (timer completed)
if (timer->repeat_count == 0) {
return false;
}
uint32_t time_elapsed = gfx_timer_tick_elaps(timer->last_run);
if (time_elapsed >= timer->period) {
timer->last_run = gfx_timer_tick_get() - (time_elapsed % timer->period);
if (timer->timer_cb) {
timer->timer_cb(timer->user_data);
}
if (timer->repeat_count > 0) {
timer->repeat_count--;
}
return true;
}
return false;
}
uint32_t gfx_timer_handler(gfx_timer_manager_t *timer_mgr)
{
static uint32_t fps_sample_count = 0;
static uint32_t fps_total_time = 0;
uint32_t next_timer_delay = GFX_NO_TIMER_READY;
gfx_timer_t *timer_node = timer_mgr->timer_list;
gfx_timer_t *next_timer = NULL;
while (timer_node != NULL) {
next_timer = timer_node->next;
// Execute timer (will return false if repeat_count is 0)
gfx_timer_exec(timer_node);
// Calculate time until next execution for this timer
if (!timer_node->paused && timer_node->repeat_count != 0) {
uint32_t elapsed_time = gfx_timer_tick_elaps(timer_node->last_run);
uint32_t remaining_time = (elapsed_time >= timer_node->period) ? 0 : (timer_node->period - elapsed_time);
if (remaining_time < next_timer_delay) {
next_timer_delay = remaining_time;
}
}
timer_node = next_timer;
}
uint32_t schedule_elapsed = gfx_timer_tick_elaps(timer_mgr->last_tick);
timer_mgr->last_tick = gfx_timer_tick_get();
uint32_t schedule_period_ms = (timer_mgr->fps > 0) ? (1000 / timer_mgr->fps) : 30;
uint32_t schedule_remaining = (schedule_elapsed >= schedule_period_ms) ? 0 : (schedule_period_ms - schedule_elapsed);
uint32_t final_delay;
if (next_timer_delay == GFX_NO_TIMER_READY) {
final_delay = schedule_remaining;
} else {
final_delay = (next_timer_delay < schedule_remaining) ? next_timer_delay : schedule_remaining;
}
fps_sample_count++;
fps_total_time += (schedule_elapsed > schedule_period_ms) ? schedule_elapsed : schedule_period_ms;
if (fps_sample_count >= 100) {
timer_mgr->actual_fps = 1000 / (fps_total_time / fps_sample_count);
// ESP_LOGI(TAG, "average fps: %lu(%lu)", timer_mgr->actual_fps, timer_mgr->fps);
fps_sample_count = 0;
fps_total_time = 0;
}
if (final_delay == 0) {
final_delay = 1;
}
timer_mgr->time_until_next = final_delay;
return final_delay;
}
gfx_timer_handle_t gfx_timer_create(void *handle, gfx_timer_cb_t timer_cb, uint32_t period, void *user_data)
{
if (handle == NULL || timer_cb == NULL) {
return NULL;
}
gfx_core_context_t *ctx = (gfx_core_context_t *)handle;
gfx_timer_manager_t *timer_mgr = &ctx->timer.timer_mgr;
gfx_timer_t *new_timer = (gfx_timer_t *)malloc(sizeof(gfx_timer_t));
if (new_timer == NULL) {
ESP_LOGE(TAG, "Failed to allocate timer");
return NULL;
}
new_timer->period = period;
new_timer->timer_cb = timer_cb;
new_timer->user_data = user_data;
new_timer->repeat_count = -1; // Infinite repeat
new_timer->paused = false;
new_timer->last_run = gfx_timer_tick_get();
new_timer->next = NULL;
// Add to timer list
if (timer_mgr->timer_list == NULL) {
timer_mgr->timer_list = new_timer;
} else {
// Add to end of list
gfx_timer_t *current_timer = timer_mgr->timer_list;
while (current_timer->next != NULL) {
current_timer = current_timer->next;
}
current_timer->next = new_timer;
}
return (gfx_timer_handle_t)new_timer;
}
void gfx_timer_delete(void *handle, gfx_timer_handle_t timer_handle)
{
if (handle == NULL || timer_handle == NULL) {
return;
}
gfx_timer_t *timer = (gfx_timer_t *)timer_handle;
gfx_core_context_t *ctx = (gfx_core_context_t *)handle;
gfx_timer_manager_t *timer_mgr = &ctx->timer.timer_mgr;
// Remove from timer list
gfx_timer_t *current_timer = timer_mgr->timer_list;
gfx_timer_t *prev_timer = NULL;
while (current_timer != NULL && current_timer != timer) {
prev_timer = current_timer;
current_timer = current_timer->next;
}
if (current_timer == timer) {
if (prev_timer == NULL) {
timer_mgr->timer_list = timer->next;
} else {
prev_timer->next = timer->next;
}
free(timer);
ESP_LOGD(TAG, "Deleted timer");
}
}
void gfx_timer_pause(gfx_timer_handle_t timer_handle)
{
if (timer_handle != NULL) {
gfx_timer_t *timer = (gfx_timer_t *)timer_handle;
timer->paused = true;
}
}
void gfx_timer_resume(gfx_timer_handle_t timer_handle)
{
if (timer_handle != NULL) {
gfx_timer_t *timer = (gfx_timer_t *)timer_handle;
timer->paused = false;
timer->last_run = gfx_timer_tick_get();
// If timer was completed (repeat_count = 0), restore infinite repeat
if (timer->repeat_count == 0) {
timer->repeat_count = -1;
}
}
}
void gfx_timer_set_repeat_count(gfx_timer_handle_t timer_handle, int32_t repeat_count)
{
if (timer_handle != NULL) {
gfx_timer_t *timer = (gfx_timer_t *)timer_handle;
timer->repeat_count = repeat_count;
}
}
void gfx_timer_set_period(gfx_timer_handle_t timer_handle, uint32_t period)
{
if (timer_handle != NULL) {
gfx_timer_t *timer = (gfx_timer_t *)timer_handle;
timer->period = period;
}
}
void gfx_timer_reset(gfx_timer_handle_t timer_handle)
{
if (timer_handle != NULL) {
gfx_timer_t *timer = (gfx_timer_t *)timer_handle;
timer->last_run = gfx_timer_tick_get();
}
}
void gfx_timer_manager_init(gfx_timer_manager_t *timer_mgr, uint32_t fps)
{
if (timer_mgr != NULL) {
timer_mgr->timer_list = NULL;
timer_mgr->time_until_next = GFX_NO_TIMER_READY;
timer_mgr->last_tick = gfx_timer_tick_get();
timer_mgr->fps = fps; // Store FPS directly
timer_mgr->actual_fps = 0; // Initialize actual FPS
ESP_LOGI(TAG, "Timer manager initialized with FPS: %lu (period: %lu ms)", fps, (fps > 0) ? (1000 / fps) : 30);
esp_cpu_set_watchpoint(0, timer_mgr->timer_list, 4, ESP_CPU_WATCHPOINT_STORE);
}
}
void gfx_timer_manager_deinit(gfx_timer_manager_t *timer_mgr)
{
if (timer_mgr == NULL) {
return;
}
// Free all timers
gfx_timer_t *timer_node = timer_mgr->timer_list;
while (timer_node != NULL) {
gfx_timer_t *next_timer = timer_node->next;
free(timer_node);
timer_node = next_timer;
}
timer_mgr->timer_list = NULL;
}
uint32_t gfx_timer_get_actual_fps(void *handle)
{
if (handle == NULL) {
return 0;
}
gfx_core_context_t *ctx = (gfx_core_context_t *)handle;
gfx_timer_manager_t *timer_mgr = &ctx->timer.timer_mgr;
return timer_mgr->actual_fps;
}

View File

@@ -0,0 +1,297 @@
/*
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "esp_err.h"
#include "esp_log.h"
#include "esp_check.h"
#include "decoder/gfx_aaf_dec.h"
#include "decoder/gfx_aaf_format.h"
static const char *TAG = "anim_decoder";
/**********************
* STATIC HELPER FUNCTIONS
**********************/
static Node* create_node()
{
Node* node = (Node*)calloc(1, sizeof(Node));
return node;
}
static void free_tree(Node* node)
{
if (!node) {
return;
}
free_tree(node->left);
free_tree(node->right);
free(node);
}
static esp_err_t decode_huffman_data(const uint8_t* data, size_t data_len,
const uint8_t* dict_bytes, size_t dict_len,
uint8_t* output, size_t* output_len)
{
if (!data || !dict_bytes || data_len == 0 || dict_len == 0) {
*output_len = 0;
return ESP_OK;
}
// Get padding
uint8_t padding = dict_bytes[0];
// printf("Padding bits: %u\n", padding);
size_t dict_pos = 1;
// Reconstruct Huffman Tree
Node* root = create_node();
Node* current = NULL;
while (dict_pos < dict_len) {
uint8_t byte_val = dict_bytes[dict_pos++];
uint8_t code_len = dict_bytes[dict_pos++];
size_t code_byte_len = (code_len + 7) / 8;
uint64_t code = 0;
for (size_t i = 0; i < code_byte_len; ++i) {
code = (code << 8) | dict_bytes[dict_pos++];
}
// Insert into tree
current = root;
for (int bit = code_len - 1; bit >= 0; --bit) {
int bit_val = (code >> bit) & 1;
if (bit_val == 0) {
if (!current->left) {
current->left = create_node();
}
current = current->left;
} else {
if (!current->right) {
current->right = create_node();
}
current = current->right;
}
}
current->is_leaf = 1;
current->value = byte_val;
}
// Convert bitstream
size_t total_bits = data_len * 8;
if (padding > 0) {
total_bits -= padding;
}
current = root;
size_t out_pos = 0;
// Process each bit
for (size_t bit_index = 0; bit_index < total_bits; bit_index++) {
size_t byte_idx = bit_index / 8;
int bit_offset = 7 - (bit_index % 8); // Most significant bit first
int bit = (data[byte_idx] >> bit_offset) & 1;
if (bit == 0) {
current = current->left;
} else {
current = current->right;
}
if (current == NULL) {
ESP_LOGE(TAG, "Invalid path in Huffman tree at bit %d", (int)bit_index);
break;
}
if (current->is_leaf) {
output[out_pos++] = current->value;
current = root;
}
}
*output_len = out_pos;
free_tree(root);
return ESP_OK;
}
/**********************
* HEADER FUNCTIONS
**********************/
gfx_aaf_format_t gfx_aaf_parse_header(const uint8_t *data, size_t data_len, gfx_aaf_header_t *header)
{
memset(header, 0, sizeof(gfx_aaf_header_t));
memcpy(header->format, data, 2);
header->format[2] = '\0';
if (strncmp(header->format, "_S", 2) == 0) {
memcpy(header->version, data + 3, 6);
header->bit_depth = data[9];
if (header->bit_depth != 4 && header->bit_depth != 8 && header->bit_depth != 24) {
ESP_LOGE(TAG, "Invalid bit depth: %d", header->bit_depth);
return GFX_AAF_FORMAT_INVALID;
}
header->width = *(uint16_t *)(data + 10);
header->height = *(uint16_t *)(data + 12);
header->blocks = *(uint16_t *)(data + 14);
header->block_height = *(uint16_t *)(data + 16);
header->block_len = (uint32_t *)malloc(header->blocks * sizeof(uint32_t));
if (header->block_len == NULL) {
ESP_LOGE(TAG, "Failed to allocate memory for split lengths");
return GFX_AAF_FORMAT_INVALID;
}
for (int i = 0; i < header->blocks; i++) {
header->block_len[i] = *(uint32_t *)(data + 18 + i * 4);
}
header->num_colors = 1 << header->bit_depth;
if (header->bit_depth == 24) {
header->num_colors = 0;
header->palette = NULL;
} else {
header->palette = (uint8_t *)malloc(header->num_colors * 4);
if (header->palette == NULL) {
ESP_LOGE(TAG, "Failed to allocate memory for palette");
free(header->block_len);
header->block_len = NULL;
return GFX_AAF_FORMAT_INVALID;
}
memcpy(header->palette, data + 18 + header->blocks * 4, header->num_colors * 4);
}
header->data_offset = 18 + header->blocks * 4 + header->num_colors * 4;
return GFX_AAF_FORMAT_SBMP;
} else if (strncmp(header->format, "_R", 2) == 0) {
uint8_t file_length = *(uint8_t *)(data + 2);
header->palette = (uint8_t *)malloc(file_length + 1);
if (header->palette == NULL) {
ESP_LOGE(TAG, "Failed to allocate memory for redirect filename");
return GFX_AAF_FORMAT_INVALID;
}
memcpy(header->palette, data + 3, file_length);
header->palette[file_length] = '\0';
header->num_colors = file_length + 1;
return GFX_AAF_FORMAT_REDIRECT;
} else {
ESP_LOGE(TAG, "Invalid format: %s", header->format);
printf("%02X %02X %02X\r\n", header->format[0], header->format[1], header->format[2]);
return GFX_AAF_FORMAT_INVALID;
}
}
void gfx_aaf_free_header(gfx_aaf_header_t *header)
{
if (header->block_len != NULL) {
free(header->block_len);
header->block_len = NULL;
}
if (header->palette != NULL) {
free(header->palette);
header->palette = NULL;
}
}
void gfx_aaf_calculate_offsets(const gfx_aaf_header_t *header, uint32_t *offsets)
{
offsets[0] = header->data_offset;
for (int i = 1; i < header->blocks; i++) {
offsets[i] = offsets[i - 1] + header->block_len[i - 1];
}
}
/**********************
* PALETTE FUNCTIONS
**********************/
gfx_color_t gfx_aaf_parse_palette(const gfx_aaf_header_t *header, uint8_t index, bool swap)
{
const uint8_t *color = &header->palette[index * 4];
// RGB888: R=color[2], G=color[1], B=color[0]
// RGB565:
// - R: (color[2] & 0xF8) << 8
// - G: (color[1] & 0xFC) << 3
// - B: (color[0] & 0xF8) >> 3
gfx_color_t result;
uint16_t color_value = swap ? __builtin_bswap16(((color[2] & 0xF8) << 8) | ((color[1] & 0xFC) << 3) | ((color[0] & 0xF8) >> 3)) : \
((color[2] & 0xF8) << 8) | ((color[1] & 0xFC) << 3) | ((color[0] & 0xF8) >> 3);
result.full = color_value;
return result;
}
/**********************
* DECODING FUNCTIONS
**********************/
esp_err_t gfx_aaf_rle_decode(const uint8_t *input, size_t input_len, uint8_t *output, size_t output_len)
{
size_t in_pos = 0;
size_t out_pos = 0;
while (in_pos + 1 <= input_len) {
uint8_t count = input[in_pos++];
uint8_t value = input[in_pos++];
if (out_pos + count > output_len) {
ESP_LOGE(TAG, "Output buffer overflow, %d > %d", out_pos + count, output_len);
return ESP_FAIL;
}
for (uint8_t i = 0; i < count; i++) {
output[out_pos++] = value;
}
}
return ESP_OK;
}
esp_err_t gfx_aaf_huffman_decode(const uint8_t* buffer, size_t buflen, uint8_t* output, size_t* output_len)
{
if (!buffer || buflen < 1 || !output || !output_len) {
ESP_LOGE(TAG, "Invalid parameters: buffer=%p, buflen=%d, output=%p, output_len=%p",
buffer, buflen, output, output_len);
return ESP_FAIL;
}
// First byte indicates encoding type (already checked in caller)
// Next two bytes contain dictionary length (big endian)
uint16_t dict_len = (buffer[2] << 8) | buffer[1];
if (buflen < 3 + dict_len) {
ESP_LOGE(TAG, "Buffer too short for dictionary");
return ESP_FAIL;
}
// Calculate data length
size_t data_len = buflen - 3 - dict_len;
if (data_len == 0) {
ESP_LOGE(TAG, "No data to decode");
return ESP_FAIL;
}
// Decode data
esp_err_t ret = decode_huffman_data(buffer + 3 + dict_len, data_len,
buffer + 3, dict_len,
output, output_len);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Huffman decoding failed: %d", ret);
return ESP_FAIL;
}
return ESP_OK;
}

View File

@@ -0,0 +1,167 @@
/*
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "esp_err.h"
#include "esp_log.h"
#include "esp_check.h"
#include "decoder/gfx_aaf_format.h"
static const char *TAG = "gfx_aaf_format";
/*
* AAF File Format Structure:
*
* Offset Size Description
* 0 1 Magic number (0x89)
* 1 3 Format string ("AAF")
* 4 4 Total number of frames
* 8 4 Checksum of table + data
* 12 4 Length of table + data
* 16 N Asset table (N = total_frames * 8)
* 16+N M Frame data (M = sum of all frame sizes)
*/
#define GFX_AAF_MAGIC_HEAD 0x5A5A
#define GFX_AAF_MAGIC_LEN 2
/* File format magic number: 0x89 "AAF" */
#define GFX_AAF_FORMAT_MAGIC 0x89
#define GFX_AAF_FORMAT_STR "AAF"
#define GFX_AAF_FORMAT_OFFSET 0
#define GFX_AAF_STR_OFFSET 1
#define GFX_AAF_NUM_OFFSET 4
#define GFX_AAF_CHECKSUM_OFFSET 8
#define GFX_AAF_TABLE_LEN 12
#define GFX_AAF_TABLE_OFFSET 16
/**
* @brief Asset table structure, contains detailed information for each asset.
*/
#pragma pack(1)
typedef struct {
uint32_t asset_size; /*!< Size of the asset */
uint32_t asset_offset; /*!< Offset of the asset */
} asset_table_entry_t;
#pragma pack()
typedef struct {
const char *asset_mem;
const asset_table_entry_t *table;
} asset_entry_t;
typedef struct {
asset_entry_t *entries;
int total_frames;
} gfx_aaf_format_ctx_t;
static uint32_t gfx_aaf_format_calc_checksum(const uint8_t *data, uint32_t length)
{
uint32_t checksum = 0;
for (uint32_t i = 0; i < length; i++) {
checksum += data[i];
}
return checksum;
}
esp_err_t gfx_aaf_format_init(const uint8_t *data, size_t data_len, gfx_aaf_format_handle_t *ret_parser)
{
esp_err_t ret = ESP_OK;
asset_entry_t *entries = NULL;
gfx_aaf_format_ctx_t *parser = (gfx_aaf_format_ctx_t *)calloc(1, sizeof(gfx_aaf_format_ctx_t));
ESP_GOTO_ON_FALSE(parser, ESP_ERR_NO_MEM, err, TAG, "no mem for parser handle");
// Check file format magic number: 0x89 "AAF"
ESP_GOTO_ON_FALSE(data[GFX_AAF_FORMAT_OFFSET] == GFX_AAF_FORMAT_MAGIC, ESP_ERR_INVALID_CRC, err, TAG, "bad file format magic");
ESP_GOTO_ON_FALSE(memcmp(data + GFX_AAF_STR_OFFSET, GFX_AAF_FORMAT_STR, 3) == 0, ESP_ERR_INVALID_CRC, err, TAG, "bad file format string");
int total_frames = *(int *)(data + GFX_AAF_NUM_OFFSET);
uint32_t stored_chk = *(uint32_t *)(data + GFX_AAF_CHECKSUM_OFFSET);
uint32_t stored_len = *(uint32_t *)(data + GFX_AAF_TABLE_LEN);
uint32_t calculated_chk = gfx_aaf_format_calc_checksum((uint8_t *)(data + GFX_AAF_TABLE_OFFSET), stored_len);
ESP_GOTO_ON_FALSE(calculated_chk == stored_chk, ESP_ERR_INVALID_CRC, err, TAG, "bad full checksum");
entries = (asset_entry_t *)malloc(sizeof(asset_entry_t) * total_frames);
asset_table_entry_t *table = (asset_table_entry_t *)(data + GFX_AAF_TABLE_OFFSET);
for (int i = 0; i < total_frames; i++) {
(entries + i)->table = (table + i);
(entries + i)->asset_mem = (void *)(data + GFX_AAF_TABLE_OFFSET + total_frames * sizeof(asset_table_entry_t) + table[i].asset_offset);
uint16_t *magic_ptr = (uint16_t *)(entries + i)->asset_mem;
ESP_GOTO_ON_FALSE(*magic_ptr == GFX_AAF_MAGIC_HEAD, ESP_ERR_INVALID_CRC, err, TAG, "bad file magic header");
}
parser->entries = entries;
parser->total_frames = total_frames;
*ret_parser = (gfx_aaf_format_handle_t)parser;
return ESP_OK;
err:
if (entries) {
free(entries);
}
if (parser) {
free(parser);
}
*ret_parser = NULL;
return ret;
}
esp_err_t gfx_aaf_format_deinit(gfx_aaf_format_handle_t handle)
{
assert(handle && "handle is invalid");
gfx_aaf_format_ctx_t *parser = (gfx_aaf_format_ctx_t *)(handle);
if (parser) {
if (parser->entries) {
free(parser->entries);
}
free(parser);
}
return ESP_OK;
}
int gfx_aaf_format_get_total_frames(gfx_aaf_format_handle_t handle)
{
assert(handle && "handle is invalid");
gfx_aaf_format_ctx_t *parser = (gfx_aaf_format_ctx_t *)(handle);
return parser->total_frames;
}
const uint8_t *gfx_aaf_format_get_frame_data(gfx_aaf_format_handle_t handle, int index)
{
assert(handle && "handle is invalid");
gfx_aaf_format_ctx_t *parser = (gfx_aaf_format_ctx_t *)(handle);
if (parser->total_frames > index) {
return (const uint8_t *)((parser->entries + index)->asset_mem + GFX_AAF_MAGIC_LEN);
} else {
ESP_LOGE(TAG, "Invalid index: %d. Maximum index is %d.", index, parser->total_frames);
return NULL;
}
}
int gfx_aaf_format_get_frame_size(gfx_aaf_format_handle_t handle, int index)
{
assert(handle && "handle is invalid");
gfx_aaf_format_ctx_t *parser = (gfx_aaf_format_ctx_t *)(handle);
if (parser->total_frames > index) {
return ((parser->entries + index)->table->asset_size - GFX_AAF_MAGIC_LEN);
} else {
ESP_LOGE(TAG, "Invalid index: %d. Maximum index is %d.", index, parser->total_frames);
return -1;
}
}

View File

@@ -0,0 +1,308 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "esp_err.h"
#include "esp_log.h"
#include "esp_check.h"
#include "core/gfx_core.h"
#include "core/gfx_obj.h"
#include "widget/gfx_img.h"
#include "decoder/gfx_img_decoder.h"
#include "decoder/gfx_aaf_dec.h"
#include "decoder/gfx_jpeg_dec.h"
static const char *TAG = "gfx_img_decoder";
/*********************
* DEFINES
*********************/
#define MAX_DECODERS 8
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
static esp_err_t image_format_info_cb(gfx_image_decoder_t *decoder, gfx_image_decoder_dsc_t *dsc, gfx_image_header_t *header);
static esp_err_t image_format_open_cb(gfx_image_decoder_t *decoder, gfx_image_decoder_dsc_t *dsc);
static void image_format_close_cb(gfx_image_decoder_t *decoder, gfx_image_decoder_dsc_t *dsc);
static esp_err_t aaf_format_info_cb(gfx_image_decoder_t *decoder, gfx_image_decoder_dsc_t *dsc, gfx_image_header_t *header);
static esp_err_t aaf_format_open_cb(gfx_image_decoder_t *decoder, gfx_image_decoder_dsc_t *dsc);
static void aaf_format_close_cb(gfx_image_decoder_t *decoder, gfx_image_decoder_dsc_t *dsc);
/**********************
* STATIC VARIABLES
**********************/
static gfx_image_decoder_t *registered_decoders[MAX_DECODERS] = {NULL};
static uint8_t decoder_count = 0;
// Built-in decoders
static gfx_image_decoder_t image_decoder = {
.name = "IMAGE",
.info_cb = image_format_info_cb,
.open_cb = image_format_open_cb,
.close_cb = image_format_close_cb,
};
static gfx_image_decoder_t aaf_decoder = {
.name = "AAF",
.info_cb = aaf_format_info_cb,
.open_cb = aaf_format_open_cb,
.close_cb = aaf_format_close_cb,
};
/**********************
* GLOBAL FUNCTIONS
**********************/
/*=====================
* Image format detection
*====================*/
gfx_image_format_t gfx_image_detect_format(const void *src)
{
if (src == NULL) {
return GFX_IMAGE_FORMAT_UNKNOWN;
}
uint8_t *byte_ptr = (uint8_t *)src;
// Check for C_ARRAY format
if (byte_ptr[0] == C_ARRAY_HEADER_MAGIC) {
return GFX_IMAGE_FORMAT_C_ARRAY;
}
// Check for AAF format (0x89 "AAF" magic)
if (byte_ptr[0] == 0x89 && byte_ptr[1] == 'A' && byte_ptr[2] == 'A' && byte_ptr[3] == 'F') {
return GFX_IMAGE_FORMAT_AAF;
}
return GFX_IMAGE_FORMAT_UNKNOWN;
}
/*=====================
* Image decoder functions
*====================*/
esp_err_t gfx_image_decoder_register(gfx_image_decoder_t *decoder)
{
if (decoder == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (decoder_count >= MAX_DECODERS) {
ESP_LOGE(TAG, "Too many decoders registered");
return ESP_ERR_NO_MEM;
}
registered_decoders[decoder_count] = decoder;
decoder_count++;
ESP_LOGD(TAG, "Registered decoder: %s", decoder->name);
return ESP_OK;
}
esp_err_t gfx_image_decoder_info(gfx_image_decoder_dsc_t *dsc, gfx_image_header_t *header)
{
if (dsc == NULL || header == NULL) {
return ESP_ERR_INVALID_ARG;
}
// Try each registered decoder
for (int i = 0; i < decoder_count; i++) {
gfx_image_decoder_t *decoder = registered_decoders[i];
if (decoder && decoder->info_cb) {
esp_err_t ret = decoder->info_cb(decoder, dsc, header);
if (ret == ESP_OK) {
ESP_LOGD(TAG, "Decoder %s found format", decoder->name);
return ESP_OK;
}
}
}
ESP_LOGW(TAG, "No decoder found for image format");
return ESP_ERR_INVALID_ARG;
}
esp_err_t gfx_image_decoder_open(gfx_image_decoder_dsc_t *dsc)
{
if (dsc == NULL) {
return ESP_ERR_INVALID_ARG;
}
// Try each registered decoder
for (int i = 0; i < decoder_count; i++) {
gfx_image_decoder_t *decoder = registered_decoders[i];
if (decoder && decoder->open_cb) {
esp_err_t ret = decoder->open_cb(decoder, dsc);
if (ret == ESP_OK) {
ESP_LOGD(TAG, "Decoder %s opened image", decoder->name);
return ESP_OK;
}
}
}
ESP_LOGW(TAG, "No decoder could open image");
return ESP_ERR_INVALID_ARG;
}
void gfx_image_decoder_close(gfx_image_decoder_dsc_t *dsc)
{
if (dsc == NULL) {
return;
}
// Try each registered decoder
for (int i = 0; i < decoder_count; i++) {
gfx_image_decoder_t *decoder = registered_decoders[i];
if (decoder && decoder->close_cb) {
decoder->close_cb(decoder, dsc);
}
}
}
/*=====================
* Built-in decoder implementations
*====================*/
// C_ARRAY format decoder
static esp_err_t image_format_info_cb(gfx_image_decoder_t *decoder, gfx_image_decoder_dsc_t *dsc, gfx_image_header_t *header)
{
if (dsc->src == NULL) {
return ESP_ERR_INVALID_ARG;
}
gfx_image_format_t format = gfx_image_detect_format(dsc->src);
if (format != GFX_IMAGE_FORMAT_C_ARRAY) {
return ESP_ERR_INVALID_ARG;
}
gfx_image_dsc_t *image_desc = (gfx_image_dsc_t *)dsc->src;
memcpy(header, &image_desc->header, sizeof(gfx_image_header_t));
return ESP_OK;
}
static esp_err_t image_format_open_cb(gfx_image_decoder_t *decoder, gfx_image_decoder_dsc_t *dsc)
{
if (dsc->src == NULL) {
return ESP_ERR_INVALID_ARG;
}
gfx_image_format_t format = gfx_image_detect_format(dsc->src);
if (format != GFX_IMAGE_FORMAT_C_ARRAY) {
return ESP_ERR_INVALID_ARG;
}
gfx_image_dsc_t *image_desc = (gfx_image_dsc_t *)dsc->src;
dsc->data = image_desc->data;
dsc->data_size = image_desc->data_size;
return ESP_OK;
}
static void image_format_close_cb(gfx_image_decoder_t *decoder, gfx_image_decoder_dsc_t *dsc)
{
// Nothing to do for C_ARRAY format
}
// AAF format decoder
static esp_err_t aaf_format_info_cb(gfx_image_decoder_t *decoder, gfx_image_decoder_dsc_t *dsc, gfx_image_header_t *header)
{
if (dsc->src == NULL) {
return ESP_ERR_INVALID_ARG;
}
gfx_image_format_t format = gfx_image_detect_format(dsc->src);
if (format != GFX_IMAGE_FORMAT_AAF) {
return ESP_ERR_INVALID_ARG;
}
// // Parse AAF header to get basic info
// const uint8_t *aaf_data = (const uint8_t *)dsc->src;
// // Skip magic (4 bytes)
// uint32_t total_files = *(uint32_t *)(aaf_data + 4);
// uint32_t checksum = *(uint32_t *)(aaf_data + 8);
// uint32_t data_length = *(uint32_t *)(aaf_data + 12);
// For AAF, we can't easily determine width/height without parsing the first frame
// So we'll set default values and let the animation system handle the details
// header->magic = 0x89414146; // 0x89 "AAF"
// header->cf = GFX_COLOR_FORMAT_RGB565; // Default to RGB565
// header->w = 0; // Will be determined when frames are loaded
// header->h = 0; // Will be determined when frames are loaded
// header->stride = 0; // Will be calculated based on actual dimensions
// header->reserved = total_files; // Store frame count in reserved field
return ESP_OK;
}
static esp_err_t aaf_format_open_cb(gfx_image_decoder_t *decoder, gfx_image_decoder_dsc_t *dsc)
{
if (dsc->src == NULL) {
return ESP_ERR_INVALID_ARG;
}
gfx_image_format_t format = gfx_image_detect_format(dsc->src);
if (format != GFX_IMAGE_FORMAT_AAF) {
return ESP_ERR_INVALID_ARG;
}
// For AAF format, we return the entire file data
// The animation system will handle frame extraction
dsc->data = (const uint8_t *)dsc->src;
dsc->data_size = 0; // Size will be determined by the animation system
return ESP_OK;
}
static void aaf_format_close_cb(gfx_image_decoder_t *decoder, gfx_image_decoder_dsc_t *dsc)
{
// Nothing to do for AAF format
}
/*=====================
* Initialization
*====================*/
esp_err_t gfx_image_decoder_init(void)
{
// Register built-in decoders
esp_err_t ret = gfx_image_decoder_register(&image_decoder);
if (ret != ESP_OK) {
return ret;
}
ret = gfx_image_decoder_register(&aaf_decoder);
if (ret != ESP_OK) {
return ret;
}
ESP_LOGI(TAG, "Image decoder system initialized with %d decoders", decoder_count);
return ESP_OK;
}
esp_err_t gfx_image_decoder_deinit(void)
{
// Clear all registered decoders
for (int i = 0; i < decoder_count; i++) {
registered_decoders[i] = NULL;
}
// Reset decoder count
decoder_count = 0;
ESP_LOGI(TAG, "Image decoder system deinitialized");
return ESP_OK;
}

View File

@@ -0,0 +1,85 @@
/*
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "decoder/gfx_jpeg_dec.h"
#include "esp_log.h"
#include "esp_check.h"
#include "esp_jpeg_dec.h"
static const char *TAG = "gfx_jpeg_dec";
esp_err_t gfx_jpeg_decode(const uint8_t *in, uint32_t insize, uint8_t *out, size_t out_size, uint32_t *w, uint32_t *h, bool swap)
{
ESP_RETURN_ON_FALSE(in && out && w && h, ESP_ERR_INVALID_ARG, TAG, "Invalid arguments");
ESP_RETURN_ON_FALSE(out_size > 0, ESP_ERR_INVALID_SIZE, TAG, "Invalid output buffer size");
jpeg_error_t ret;
jpeg_dec_config_t config = {
.output_type = swap ? JPEG_PIXEL_FORMAT_RGB565_BE : JPEG_PIXEL_FORMAT_RGB565_LE,
.rotate = JPEG_ROTATE_0D,
};
jpeg_dec_handle_t jpeg_dec;
jpeg_dec_open(&config, &jpeg_dec);
if (!jpeg_dec) {
ESP_LOGE(TAG, "Failed to open jpeg decoder");
return ESP_FAIL;
}
jpeg_dec_io_t *jpeg_io = malloc(sizeof(jpeg_dec_io_t));
jpeg_dec_header_info_t *out_info = malloc(sizeof(jpeg_dec_header_info_t));
if (!jpeg_io || !out_info) {
if (jpeg_io) {
free(jpeg_io);
}
if (out_info) {
free(out_info);
}
jpeg_dec_close(jpeg_dec);
ESP_LOGE(TAG, "Failed to allocate memory for jpeg decoder");
return ESP_FAIL;
}
jpeg_io->inbuf = (unsigned char *)in;
jpeg_io->inbuf_len = insize;
ret = jpeg_dec_parse_header(jpeg_dec, jpeg_io, out_info);
if (ret == JPEG_ERR_OK) {
*w = out_info->width;
*h = out_info->height;
size_t required_size = out_info->width * out_info->height * 2; // RGB565 = 2 bytes per pixel
if (out_size < required_size) {
ESP_LOGE(TAG, "Output buffer too small: need %zu, got %zu", required_size, out_size);
free(jpeg_io);
free(out_info);
jpeg_dec_close(jpeg_dec);
return ESP_ERR_INVALID_SIZE;
}
jpeg_io->outbuf = out;
ret = jpeg_dec_process(jpeg_dec, jpeg_io);
if (ret != JPEG_ERR_OK) {
free(jpeg_io);
free(out_info);
jpeg_dec_close(jpeg_dec);
ESP_LOGE(TAG, "Failed to decode jpeg:[%d]", ret);
return ESP_FAIL;
}
} else {
free(jpeg_io);
free(out_info);
jpeg_dec_close(jpeg_dec);
ESP_LOGE(TAG, "Failed to parse jpeg header");
return ESP_FAIL;
}
free(jpeg_io);
free(out_info);
jpeg_dec_close(jpeg_dec);
return ESP_OK;
}

View File

@@ -0,0 +1,587 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_check.h"
#include "core/gfx_core.h"
#include "core/gfx_obj.h"
#include "widget/gfx_anim.h"
#include "widget/gfx_comm.h"
#include "decoder/gfx_aaf_dec.h"
#include "decoder/gfx_jpeg_dec.h"
#include "widget/gfx_font_internal.h"
#include "widget/gfx_anim_internal.h"
#include "core/gfx_obj_internal.h"
#include "decoder/gfx_aaf_format.h"
static const char *TAG = "gfx_anim";
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
static esp_err_t gfx_anim_decode_block(const uint8_t *data, const uint32_t *offsets, const gfx_aaf_header_t *header,
int block, uint8_t *decode_buffer, int width, int block_height, bool swap_color);
static void gfx_anim_render_4bit_pixels(gfx_color_t *dest_buf, gfx_coord_t dest_stride,
const uint8_t *src_buf, gfx_coord_t src_stride,
const gfx_aaf_header_t *header, uint32_t *palette_cache,
gfx_area_t *clip_area, bool swap_color,
bool mirror_enabled, int16_t mirror_offset, int dest_x_offset);
static void gfx_anim_render_8bit_pixels(gfx_color_t *dest_buf, gfx_coord_t dest_stride,
const uint8_t *src_buf, gfx_coord_t src_stride,
const gfx_aaf_header_t *header, uint32_t *palette_cache,
gfx_area_t *clip_area, bool swap_color,
bool mirror_enabled, int16_t mirror_offset, int dest_x_offset);
static void gfx_anim_render_24bit_pixels(gfx_color_t *dest_buf, gfx_coord_t dest_stride,
const uint8_t *src_buf, gfx_coord_t src_stride,
gfx_area_t *clip_area, bool swap_color,
bool mirror_enabled, int16_t mirror_offset, int dest_x_offset);
/**
* @brief Free frame processing information and allocated resources
* @param frame Frame processing information structure
*/
void gfx_anim_free_frame_info(gfx_anim_frame_info_t *frame)
{
// Free previously parsed header if exists
if (frame->header.width > 0) { // Check if header data is valid instead of header_valid flag
gfx_aaf_free_header(&frame->header);
memset(&frame->header, 0, sizeof(gfx_aaf_header_t)); // Clear header data
}
// Free previously allocated parsing resources if exists
if (frame->block_offsets) {
free(frame->block_offsets);
frame->block_offsets = NULL;
}
if (frame->pixel_buffer) {
free(frame->pixel_buffer);
frame->pixel_buffer = NULL;
}
if (frame->color_palette) {
free(frame->color_palette);
frame->color_palette = NULL;
}
// Clear frame data
frame->frame_data = NULL;
frame->frame_size = 0;
frame->last_block = -1;
}
/**
* @brief Preprocess animation frame data and allocate parsing resources
* @param anim Animation property structure
* @return true if preprocessing was successful, false otherwise
*/
bool gfx_anim_preprocess_frame(gfx_anim_property_t *anim)
{
// Free previous frame info and allocated resources
gfx_anim_free_frame_info(&anim->frame);
const void *frame_data = gfx_aaf_format_get_frame_data(anim->file_desc, anim->current_frame);
size_t frame_size = gfx_aaf_format_get_frame_size(anim->file_desc, anim->current_frame);
if (frame_data == NULL) {
ESP_LOGW(TAG, "Failed to get frame data for frame %lu", anim->current_frame);
return false;
}
anim->frame.frame_data = frame_data;
anim->frame.frame_size = frame_size;
gfx_aaf_format_t format = gfx_aaf_parse_header(frame_data, frame_size, &anim->frame.header);
if (format != GFX_AAF_FORMAT_SBMP) {
ESP_LOGW(TAG, "Failed to parse header for frame %lu, format: %d", anim->current_frame, format);
return false;
}
// Pre-allocate parsing resources and calculate offsets
const gfx_aaf_header_t *header = &anim->frame.header;
int aaf_blocks = header->blocks;
int block_height = header->block_height;
int width = header->width;
uint16_t color_depth = 0;
// Allocate offsets array
anim->frame.block_offsets = (uint32_t *)malloc(aaf_blocks * sizeof(uint32_t));
if (anim->frame.block_offsets == NULL) {
ESP_LOGE(TAG, "Failed to allocate memory for block offsets");
return false;
}
// Allocate decode buffer based on bit depth
if (header->bit_depth == 4) {
anim->frame.pixel_buffer = (uint8_t *)malloc(width * (block_height + (block_height % 2)) / 2);
} else if (header->bit_depth == 8) {
anim->frame.pixel_buffer = (uint8_t *)malloc(width * block_height);
} else if (header->bit_depth == 24) {
anim->frame.pixel_buffer = (uint8_t *)heap_caps_aligned_alloc(16, width * block_height * 2, MALLOC_CAP_DEFAULT);//esp_new_jpg needed aligned 16
}
if (anim->frame.pixel_buffer == NULL) {
ESP_LOGE(TAG, "Failed to allocate memory for pixel buffer, bit_depth: %d", header->bit_depth);
free(anim->frame.block_offsets);
anim->frame.block_offsets = NULL;
return false;
}
// Determine color depth and allocate palette cache
if (header->bit_depth == 4) {
color_depth = 16;
} else if (header->bit_depth == 8) {
color_depth = 256;
} else if (header->bit_depth == 24) {
color_depth = 0;
}
if (color_depth) {
anim->frame.color_palette = (uint32_t *)heap_caps_malloc(color_depth * sizeof(uint32_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
if (anim->frame.color_palette == NULL) {
ESP_LOGE(TAG, "Failed to allocate memory for color palette");
free(anim->frame.pixel_buffer);
free(anim->frame.block_offsets);
anim->frame.pixel_buffer = NULL;
anim->frame.block_offsets = NULL;
return false;
}
// Initialize palette cache
for (int i = 0; i < color_depth; i++) {
anim->frame.color_palette[i] = 0xFFFFFFFF; // Use 0xFFFFFFFF as sentinel value
}
}
// Calculate offsets
gfx_aaf_calculate_offsets(header, anim->frame.block_offsets);
ESP_LOGD(TAG, "Pre-allocated parsing resources for frame %lu", anim->current_frame);
return true;
}
/**********************
* GLOBAL FUNCTIONS
**********************/
void gfx_draw_animation(gfx_obj_t *obj, int x1, int y1, int x2, int y2, const void *dest_buf, bool swap_color)
{
if (obj == NULL || obj->src == NULL) {
ESP_LOGD(TAG, "Invalid object or source");
return;
}
if (obj->type != GFX_OBJ_TYPE_ANIMATION) {
ESP_LOGW(TAG, "Object is not an animation type");
return;
}
gfx_anim_property_t *anim = (gfx_anim_property_t *)obj->src;
if (anim->file_desc == NULL) {
ESP_LOGE(TAG, "Animation file descriptor is NULL");
return;
}
const void *frame_data;
if (anim->frame.frame_data != NULL) { // Check if frame data exists instead of data_ready flag
frame_data = anim->frame.frame_data;
} else {
ESP_LOGD(TAG, "Frame data not ready for frame %lu", anim->current_frame);
return;
}
if (frame_data == NULL) {
ESP_LOGD(TAG, "Failed to get frame data for frame %lu", anim->current_frame);
return;
}
// Get frame processing information
if (anim->frame.header.width == 0) { // Check if header is valid instead of header_valid flag
ESP_LOGD(TAG, "Header not valid for frame %lu", anim->current_frame);
return;
}
// Use pre-allocated parsing resources
uint8_t *decode_buffer = anim->frame.pixel_buffer;
uint32_t *offsets = anim->frame.block_offsets;
uint32_t *palette_cache = anim->frame.color_palette;
if (!offsets || !decode_buffer) {
ESP_LOGE(TAG, "Parsing resources not allocated for frame %lu", anim->current_frame);
return;
}
// Get header pointer for convenience
const gfx_aaf_header_t *header = &anim->frame.header;
// Get screen dimensions for alignment calculation
uint32_t parent_width, parent_height;
if (obj->parent_handle != NULL) {
esp_err_t ret = gfx_emote_get_screen_size(obj->parent_handle, &parent_width, &parent_height);
if (ret != ESP_OK) {
ESP_LOGW(TAG, "Failed to get screen size, using defaults");
parent_width = DEFAULT_SCREEN_WIDTH;
parent_height = DEFAULT_SCREEN_HEIGHT;
}
} else {
parent_width = DEFAULT_SCREEN_WIDTH;
parent_height = DEFAULT_SCREEN_HEIGHT;
}
gfx_coord_t obj_x = obj->x;
gfx_coord_t obj_y = obj->y;
obj->width = header->width;
obj->height = header->height;
gfx_obj_calculate_aligned_position(obj, parent_width, parent_height, &obj_x, &obj_y);
gfx_area_t clip_object;
clip_object.x1 = MAX(x1, obj_x);
clip_object.y1 = MAX(y1, obj_y);
clip_object.x2 = MIN(x2, obj_x + obj->width);
clip_object.y2 = MIN(y2, obj_y + obj->height);
if (clip_object.x1 >= clip_object.x2 || clip_object.y1 >= clip_object.y2) {
return;
}
// Decode the frame
int width = header->width;
int height = header->height;
int block_height = header->block_height;
int aaf_blocks = header->blocks;
int *last_block = &anim->frame.last_block;
// Process each AAF block that overlaps with the destination area
for (int block = 0; block < aaf_blocks; block++) {
int block_start_y = block * block_height;
int block_end_y = (block == aaf_blocks - 1) ? height : (block + 1) * block_height;
int block_start_x = 0;
int block_end_x = width;
block_start_y += obj_y;
block_end_y += obj_y;
block_start_x += obj_x;
block_end_x += obj_x;
gfx_area_t clip_block;
clip_block.x1 = MAX(clip_object.x1, block_start_x);
clip_block.y1 = MAX(clip_object.y1, block_start_y);
clip_block.x2 = MIN(clip_object.x2, block_end_x);
clip_block.y2 = MIN(clip_object.y2, block_end_y);
if (clip_block.x1 >= clip_block.x2 || clip_block.y1 >= clip_block.y2) {
continue;
}
// Calculate source buffer offset relative to the decoded block data
int src_offset_x = clip_block.x1 - block_start_x;
int src_offset_y = clip_block.y1 - block_start_y;
// Check if source offsets are within valid range before decoding
if (src_offset_x < 0 || src_offset_y < 0 ||
src_offset_x >= width || src_offset_y >= block_height) {
continue;
}
// Decode block if not already decoded
if (block != *last_block) {
esp_err_t decode_result = gfx_anim_decode_block(frame_data, offsets, header, block, decode_buffer, width, block_height, swap_color);
if (decode_result != ESP_OK) {
continue;
}
*last_block = block;
}
gfx_coord_t dest_buffer_stride = (x2 - x1);
gfx_coord_t source_buffer_stride = width;
uint8_t *source_pixels = NULL;
if (header->bit_depth == 24) {// RGB565
source_pixels = decode_buffer + src_offset_y * (source_buffer_stride * 2) + src_offset_x * 2;
} else if (header->bit_depth == 4) {
source_pixels = decode_buffer + src_offset_y * (source_buffer_stride / 2) + src_offset_x / 2;
} else {
source_pixels = decode_buffer + src_offset_y * source_buffer_stride + src_offset_x;
}
int dest_x_offset = clip_block.x1 - x1;
gfx_color_t *dest_pixels = (gfx_color_t *)dest_buf + (clip_block.y1 - y1) * dest_buffer_stride + dest_x_offset;
if (header->bit_depth == 4) {
gfx_anim_render_4bit_pixels(
dest_pixels,
dest_buffer_stride,
source_pixels,
source_buffer_stride,
header, palette_cache,
&clip_block,
swap_color, anim->mirror_enabled, anim->mirror_offset, dest_x_offset);
} else if (header->bit_depth == 8) {
gfx_anim_render_8bit_pixels(
dest_pixels,
dest_buffer_stride,
source_pixels,
source_buffer_stride,
header, palette_cache,
&clip_block,
swap_color,
anim->mirror_enabled, anim->mirror_offset, dest_x_offset);
} else if (header->bit_depth == 24) {
gfx_anim_render_24bit_pixels(
dest_pixels,
dest_buffer_stride,
source_pixels,
source_buffer_stride,
&clip_block,
swap_color,
anim->mirror_enabled, anim->mirror_offset, dest_x_offset);
} else {
ESP_LOGE(TAG, "Unsupported bit depth: %d", header->bit_depth);
continue;
}
}
obj->is_dirty = false;
}
/*=====================
* Static helper functions
*====================*/
static esp_err_t gfx_anim_decode_block(const uint8_t *data, const uint32_t *offsets, const gfx_aaf_header_t *header,
int block, uint8_t *decode_buffer, int width, int block_height, bool swap_color)
{
const uint8_t *block_data = data + offsets[block];
int block_len = header->block_len[block];
uint8_t encoding_type = block_data[0];
esp_err_t decode_result = ESP_FAIL;
if (encoding_type == GFX_AAF_ENCODING_RLE) {
size_t rle_out_len = width * block_height;
decode_result = gfx_aaf_rle_decode(block_data + 1, block_len - 1,
decode_buffer, rle_out_len);
} else if (encoding_type == GFX_AAF_ENCODING_HUFFMAN) {
size_t rle_out_len = width * block_height;
uint8_t *huffman_buffer = malloc(rle_out_len);
if (huffman_buffer == NULL) {
ESP_LOGE(TAG, "Failed to allocate memory for Huffman buffer");
return ESP_FAIL;
}
size_t huffman_out_len = 0;
gfx_aaf_huffman_decode(block_data, block_len, huffman_buffer, &huffman_out_len);
if (huffman_out_len > rle_out_len) {
ESP_LOGE(TAG, "Huffman output size mismatch: expected %d, got %d", rle_out_len, huffman_out_len);
free(huffman_buffer);
return ESP_FAIL;
}
decode_result = gfx_aaf_rle_decode(huffman_buffer, huffman_out_len,
decode_buffer, rle_out_len);
free(huffman_buffer);
} else if (encoding_type == GFX_AAF_ENCODING_HUFFMAN_DIRECT) {
size_t huffman_out_len = 0;
esp_err_t huffman_ret = gfx_aaf_huffman_decode(block_data, block_len, decode_buffer, &huffman_out_len);
if (huffman_ret != ESP_OK) {
ESP_LOGE(TAG, "Direct Huffman decode failed for block %d", block);
return ESP_FAIL;
}
if (huffman_out_len != width * block_height) {
ESP_LOGE(TAG, "Direct Huffman output size mismatch: expected %d, got %d", width * block_height, huffman_out_len);
return ESP_FAIL;
}
decode_result = ESP_OK;
} else if (encoding_type == GFX_AAF_ENCODING_JPEG) {
uint32_t w, h;
size_t jpg_out_len = width * block_height * 2; // RGB565 = 2 bytes per pixel
esp_err_t jpeg_ret = gfx_jpeg_decode(block_data + 1, block_len - 1, decode_buffer, jpg_out_len, &w, &h, swap_color);
if (jpeg_ret != ESP_OK) {
ESP_LOGE(TAG, "JPEG decode failed: %s", esp_err_to_name(jpeg_ret));
return ESP_FAIL;
}
decode_result = ESP_OK;
} else {
ESP_LOGE(TAG, "Unknown encoding type: %02X", encoding_type);
return ESP_FAIL;
}
if (decode_result != ESP_OK) {
ESP_LOGE(TAG, "Failed to decode block %d", block);
return ESP_FAIL;
}
return ESP_OK;
}
/**
* @brief Render 4-bit pixels directly to destination buffer
* @param dest_buf Destination buffer
* @param dest_stride Destination buffer stride
* @param src_buf Source buffer data
* @param src_stride Source buffer stride
* @param header Image header
* @param palette_cache Palette cache
* @param clip_area Clipping area
* @param swap_color Whether to swap color bytes
* @param mirror_enabled Whether mirror is enabled
* @param mirror_offset Mirror offset
* @param dest_x_offset Destination buffer x offset
*/
static void gfx_anim_render_4bit_pixels(gfx_color_t *dest_buf, gfx_coord_t dest_stride,
const uint8_t *src_buf, gfx_coord_t src_stride,
const gfx_aaf_header_t *header, uint32_t *palette_cache,
gfx_area_t *clip_area, bool swap_color,
bool mirror_enabled, int16_t mirror_offset, int dest_x_offset)
{
int width = header->width;
int clip_w = clip_area->x2 - clip_area->x1;
int clip_h = clip_area->y2 - clip_area->y1;
for (int y = 0; y < clip_h; y++) {
for (int x = 0; x < clip_w; x += 2) {
uint8_t packed_gray = src_buf[y * src_stride / 2 + (x / 2)];
uint8_t index1 = (packed_gray & 0xF0) >> 4;
uint8_t index2 = (packed_gray & 0x0F);
if (palette_cache[index1] == 0xFFFFFFFF) {
gfx_color_t color = gfx_aaf_parse_palette(header, index1, swap_color);
palette_cache[index1] = color.full;
}
gfx_color_t color_val1;
color_val1.full = (uint16_t)palette_cache[index1];
dest_buf[y * dest_stride + x] = color_val1;
// Sync write to mirror position if mirror is enabled
if (mirror_enabled) {
int mirror_x = width + mirror_offset + width - 1 - x;
if (mirror_x >= 0 && (dest_x_offset + mirror_x) < dest_stride) {
dest_buf[y * dest_stride + mirror_x] = color_val1;
}
}
if (palette_cache[index2] == 0xFFFFFFFF) {
gfx_color_t color = gfx_aaf_parse_palette(header, index2, swap_color);
palette_cache[index2] = color.full;
}
gfx_color_t color_val2;
color_val2.full = (uint16_t)palette_cache[index2];
dest_buf[y * dest_stride + x + 1] = color_val2;
if (mirror_enabled) {
int mirror_x = width + mirror_offset + width - 1 - (x + 1);
if (mirror_x >= 0 && (dest_x_offset + mirror_x) < dest_stride) {
dest_buf[y * dest_stride + mirror_x] = color_val1;
}
}
}
}
}
/**
* @brief Render 8-bit pixels directly to destination buffer
* @param dest_buf Destination buffer
* @param dest_stride Destination buffer stride
* @param src_buf Source buffer data
* @param src_stride Source buffer stride
* @param header Image header
* @param palette_cache Palette cache
* @param clip_area Clipping area
* @param swap_color Whether to swap color bytes
* @param mirror_enabled Whether mirror is enabled
* @param mirror_offset Mirror offset
* @param dest_x_offset Destination buffer x offset
*/
static void gfx_anim_render_8bit_pixels(gfx_color_t *dest_buf, gfx_coord_t dest_stride,
const uint8_t *src_buf, gfx_coord_t src_stride,
const gfx_aaf_header_t *header, uint32_t *palette_cache,
gfx_area_t *clip_area, bool swap_color,
bool mirror_enabled, int16_t mirror_offset, int dest_x_offset)
{
int32_t w = clip_area->x2 - clip_area->x1;
int32_t h = clip_area->y2 - clip_area->y1;
int32_t width = header->width;
for (int32_t y = 0; y < h; y++) {
for (int32_t x = 0; x < w; x++) {
uint8_t index = src_buf[y * src_stride + x];
if (palette_cache[index] == 0xFFFFFFFF) {
gfx_color_t color = gfx_aaf_parse_palette(header, index, swap_color);
palette_cache[index] = color.full;
}
gfx_color_t color_val;
color_val.full = (uint16_t)palette_cache[index];
dest_buf[y * dest_stride + x] = color_val;
// Sync write to mirror position if mirror is enabled
if (mirror_enabled) {
int mirror_x = width + mirror_offset + width - 1 - x;
// Check boundary for mirror position
if (mirror_x >= 0 && (dest_x_offset + mirror_x) < dest_stride) {
dest_buf[y * dest_stride + mirror_x] = color_val;
}
}
}
}
}
static void gfx_anim_render_24bit_pixels(gfx_color_t *dest_buf, gfx_coord_t dest_stride,
const uint8_t *src_buf, gfx_coord_t src_stride,
gfx_area_t *clip_area, bool swap_color,
bool mirror_enabled, int16_t mirror_offset, int dest_x_offset)
{
int32_t w = clip_area->x2 - clip_area->x1;
int32_t h = clip_area->y2 - clip_area->y1;
int32_t width = src_stride;
uint16_t *src_buf_16 = (uint16_t *)src_buf;
uint16_t *dest_buf_16 = (uint16_t *)dest_buf;
for (int32_t y = 0; y < h; y++) {
for (int32_t x = 0; x < w; x++) {
dest_buf_16[y * dest_stride + x] = src_buf_16[y * src_stride + x];
// Sync write to mirror position if mirror is enabled
if (mirror_enabled) {
int mirror_x = width + mirror_offset + width - 1 - x;
// Check boundary for mirror position
if (mirror_x >= 0 && (dest_x_offset + mirror_x) < dest_stride) {
dest_buf_16[y * dest_stride + mirror_x] = src_buf_16[y * src_stride + x];
}
}
}
}
}

View File

@@ -0,0 +1,150 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "esp_err.h"
#include "esp_log.h"
#include "esp_check.h"
#include "core/gfx_core.h"
#include "core/gfx_obj.h"
#include "widget/gfx_img.h"
#include "widget/gfx_img_internal.h"
#include "core/gfx_blend_internal.h"
#include "widget/gfx_comm.h"
#include "widget/gfx_font_internal.h"
#include "core/gfx_obj_internal.h"
#include "decoder/gfx_img_decoder.h"
static const char *TAG = "gfx_img";
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
void gfx_draw_img(gfx_obj_t *obj, int x1, int y1, int x2, int y2, const void *dest_buf, bool swap)
{
if (obj == NULL || obj->src == NULL) {
ESP_LOGD(TAG, "Invalid object or source");
return;
}
if (obj->type != GFX_OBJ_TYPE_IMAGE) {
ESP_LOGW(TAG, "Object is not an image type");
return;
}
// Use unified decoder to get image information
gfx_image_header_t header;
gfx_image_decoder_dsc_t dsc = {
.src = obj->src,
};
esp_err_t ret = gfx_image_decoder_info(&dsc, &header);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to get image info");
return;
}
uint16_t image_width = header.w;
uint16_t image_height = header.h;
uint8_t color_format = header.cf;
// Check color format - support RGB565A8 format
if (color_format != GFX_COLOR_FORMAT_RGB565A8) {
ESP_LOGW(TAG, "Unsupported color format: 0x%02X, only RGB565A8 (0x%02X) is supported",
color_format, GFX_COLOR_FORMAT_RGB565A8);
return;
}
// Get image data using unified decoder
gfx_image_decoder_dsc_t decoder_dsc = {
.src = obj->src,
.header = header,
.data = NULL,
.data_size = 0,
.user_data = NULL
};
ret = gfx_image_decoder_open(&decoder_dsc);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to open image decoder");
return;
}
const uint8_t *image_data = decoder_dsc.data;
if (image_data == NULL) {
ESP_LOGE(TAG, "No image data available");
gfx_image_decoder_close(&decoder_dsc);
return;
}
ESP_LOGD(TAG, "Drawing image: %dx%d, format: 0x%02X", image_width, image_height, color_format);
// Get parent container dimensions for alignment calculation
uint32_t parent_width, parent_height;
if (obj->parent_handle != NULL) {
esp_err_t ret = gfx_emote_get_screen_size(obj->parent_handle, &parent_width, &parent_height);
if (ret != ESP_OK) {
ESP_LOGW(TAG, "Failed to get screen size, using defaults");
parent_width = DEFAULT_SCREEN_WIDTH;
parent_height = DEFAULT_SCREEN_HEIGHT;
}
} else {
parent_width = DEFAULT_SCREEN_WIDTH;
parent_height = DEFAULT_SCREEN_HEIGHT;
}
gfx_coord_t obj_x = obj->x;
gfx_coord_t obj_y = obj->y;
gfx_obj_calculate_aligned_position(obj, parent_width, parent_height, &obj_x, &obj_y);
gfx_area_t clip_region;
clip_region.x1 = MAX(x1, obj_x);
clip_region.y1 = MAX(y1, obj_y);
clip_region.x2 = MIN(x2, obj_x + image_width);
clip_region.y2 = MIN(y2, obj_y + image_height);
// Check if there's any overlap
if (clip_region.x1 >= clip_region.x2 || clip_region.y1 >= clip_region.y2) {
gfx_image_decoder_close(&decoder_dsc);
return;
}
gfx_coord_t dest_buffer_stride = (x2 - x1);
gfx_coord_t source_buffer_stride = image_width;
// Calculate data pointers based on format
gfx_color_t *source_pixels = (gfx_color_t *)(image_data + (clip_region.y1 - obj_y) * source_buffer_stride * 2);
gfx_opa_t *alpha_mask = (gfx_opa_t *)(image_data + source_buffer_stride * image_height * 2 + (clip_region.y1 - obj_y) * source_buffer_stride);
gfx_color_t *dest_pixels = (gfx_color_t *)dest_buf + (clip_region.y1 - y1) * dest_buffer_stride + (clip_region.x1 - x1);
gfx_sw_blend_img_draw(
dest_pixels,
dest_buffer_stride,
source_pixels,
source_buffer_stride,
alpha_mask,
source_buffer_stride,
&clip_region,
255,
swap
);
// Close decoder
gfx_image_decoder_close(&decoder_dsc);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,224 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <stdlib.h>
#include "esp_log.h"
#include "esp_check.h"
#include "widget/gfx_font_parser.h"
/*********************
* DEFINES
*********************/
static const char *TAG = "gfx_font_parser";
/**********************
* STATIC FUNCTIONS
**********************/
/**
* Map unicode character to glyph index using LVGL character mapping
*/
static uint32_t gfx_font_get_glyph_index(const gfx_lvgl_font_t *font, uint32_t unicode)
{
if (!font || !font->dsc || !font->dsc->cmaps) {
return 0;
}
const gfx_font_fmt_txt_dsc_t *dsc = font->dsc;
for (uint16_t i = 0; i < dsc->cmap_num; i++) {
const gfx_font_cmap_t *cmap = &dsc->cmaps[i];
if (unicode < cmap->range_start) {
continue;
}
if (cmap->type == GFX_FONT_FMT_TXT_CMAP_FORMAT0_TINY) {
// Simple range mapping
if (unicode >= cmap->range_start &&
unicode < cmap->range_start + cmap->range_length) {
return cmap->glyph_id_start + (unicode - cmap->range_start);
}
}
// Add support for other mapping types as needed
}
return 0; // Glyph not found
}
/**
* Convert external LVGL font structures to internal format
*/
static esp_err_t convert_external_font_structures(const void *external_font, gfx_lvgl_font_t *internal_font)
{
// This is a simplified conversion - you may need to adjust based on your actual LVGL font structure
// The external font should have a similar structure but with potentially different field names
// For now, we'll assume the external font has the same structure as our internal format
// In practice, you might need to map fields differently
// Cast to a temporary structure that matches your external font format
typedef struct {
const void *get_glyph_dsc;
const void *get_glyph_bitmap;
uint16_t line_height;
uint16_t base_line;
uint8_t subpx;
int8_t underline_position;
uint8_t underline_thickness;
bool static_bitmap;
const void *dsc; // This points to the font descriptor
const void *fallback;
const void *user_data;
} external_lv_font_t;
const external_lv_font_t *ext_font = (const external_lv_font_t *)external_font;
// Copy the font properties
internal_font->get_glyph_dsc = ext_font->get_glyph_dsc;
internal_font->get_glyph_bitmap = ext_font->get_glyph_bitmap;
internal_font->line_height = ext_font->line_height;
internal_font->base_line = ext_font->base_line;
internal_font->subpx = ext_font->subpx;
internal_font->underline_position = ext_font->underline_position;
internal_font->underline_thickness = ext_font->underline_thickness;
internal_font->static_bitmap = ext_font->static_bitmap;
internal_font->fallback = ext_font->fallback;
internal_font->user_data = ext_font->user_data;
// Convert the font descriptor
if (ext_font->dsc) {
// Cast the external descriptor to our internal format
// Note: This assumes the structures are compatible
internal_font->dsc = (const gfx_font_fmt_txt_dsc_t *)ext_font->dsc;
} else {
internal_font->dsc = NULL;
}
return ESP_OK;
}
/**********************
* GLOBAL FUNCTIONS
**********************/
esp_err_t gfx_parse_lvgl_font(const gfx_lvgl_font_t *font_data, const char *font_name, gfx_font_handle_t **ret_handle)
{
ESP_RETURN_ON_FALSE(font_data && font_name && ret_handle, ESP_ERR_INVALID_ARG, TAG, "invalid arguments");
// Allocate font handle
gfx_font_handle_t *handle = (gfx_font_handle_t *)calloc(1, sizeof(gfx_font_handle_t));
ESP_RETURN_ON_FALSE(handle, ESP_ERR_NO_MEM, TAG, "no memory for font handle");
// Allocate name string
handle->name = strdup(font_name);
if (!handle->name) {
free(handle);
ESP_RETURN_ON_ERROR(ESP_ERR_NO_MEM, TAG, "no memory for font name");
}
// Set font type and data
handle->type = GFX_FONT_TYPE_LVGL_C;
handle->font.lvgl_font = font_data;
*ret_handle = handle;
ESP_LOGI(TAG, "Parsed LVGL font: %s", font_name);
return ESP_OK;
}
esp_err_t gfx_convert_external_lvgl_font(const void *external_font, const char *font_name, gfx_font_handle_t **ret_handle)
{
ESP_RETURN_ON_FALSE(external_font && font_name && ret_handle, ESP_ERR_INVALID_ARG, TAG, "invalid arguments");
// Allocate font handle
gfx_font_handle_t *handle = (gfx_font_handle_t *)calloc(1, sizeof(gfx_font_handle_t));
ESP_RETURN_ON_FALSE(handle, ESP_ERR_NO_MEM, TAG, "no memory for font handle");
// Allocate internal font structure
gfx_lvgl_font_t *internal_font = (gfx_lvgl_font_t *)calloc(1, sizeof(gfx_lvgl_font_t));
if (!internal_font) {
free(handle);
ESP_RETURN_ON_ERROR(ESP_ERR_NO_MEM, TAG, "no memory for internal font");
}
// Convert external font structure to internal format
esp_err_t ret = convert_external_font_structures(external_font, internal_font);
if (ret != ESP_OK) {
free(internal_font);
free(handle);
ESP_RETURN_ON_ERROR(ret, TAG, "failed to convert external font");
}
// Allocate name string
handle->name = strdup(font_name);
if (!handle->name) {
free(internal_font);
free(handle);
ESP_RETURN_ON_ERROR(ESP_ERR_NO_MEM, TAG, "no memory for font name");
}
// Set font type and data
handle->type = GFX_FONT_TYPE_LVGL_C;
handle->font.lvgl_font = internal_font;
*ret_handle = handle;
ESP_LOGI(TAG, "Converted external LVGL font: %s", font_name);
return ESP_OK;
}
bool gfx_lvgl_font_get_glyph_dsc(const gfx_lvgl_font_t *font, uint32_t unicode, gfx_font_glyph_dsc_t *glyph_dsc)
{
if (!font || !glyph_dsc || !font->dsc) {
return false;
}
uint32_t glyph_index = gfx_font_get_glyph_index(font, unicode);
if (glyph_index == 0) {
return false; // Glyph not found
}
const gfx_font_fmt_txt_dsc_t *dsc = font->dsc;
if (glyph_index >= 65536 || !dsc->glyph_dsc) { // Reasonable bounds check
return false;
}
// Copy glyph descriptor
// Note: We assume glyph_dsc structure is compatible between external and internal formats
const void *src_glyph = &dsc->glyph_dsc[glyph_index];
memcpy(glyph_dsc, src_glyph, sizeof(gfx_font_glyph_dsc_t));
return true;
}
const uint8_t *gfx_lvgl_font_get_glyph_bitmap(const gfx_lvgl_font_t *font, const gfx_font_glyph_dsc_t *glyph_dsc)
{
if (!font || !glyph_dsc || !font->dsc || !font->dsc->glyph_bitmap) {
return NULL;
}
return &font->dsc->glyph_bitmap[glyph_dsc->bitmap_index];
}
/**
* @brief Get advance width for a character from LVGL font
* @param font LVGL font structure
* @param unicode Unicode character
* @return Advance width in pixels (multiplied by 256 for sub-pixel precision)
*/
uint32_t gfx_lvgl_font_get_glyph_width(const gfx_lvgl_font_t *font, uint32_t unicode)
{
gfx_font_glyph_dsc_t glyph_dsc;
if (gfx_lvgl_font_get_glyph_dsc(font, unicode, &glyph_dsc)) {
return glyph_dsc.adv_w;
}
return 0;
}