add some code
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user