Update to 2.0.0

This commit is contained in:
2025-09-13 23:40:38 +08:00
parent 5a929f5b06
commit 63e404d610
247 changed files with 13586 additions and 11497 deletions

View File

@@ -0,0 +1,123 @@
#include "emoji_collection.h"
#include <esp_log.h>
#include <unordered_map>
#include <string>
#define TAG "EmojiCollection"
void EmojiCollection::AddEmoji(const std::string& name, LvglImage* image) {
emoji_collection_[name] = image;
}
const LvglImage* EmojiCollection::GetEmojiImage(const char* name) {
auto it = emoji_collection_.find(name);
if (it != emoji_collection_.end()) {
return it->second;
}
ESP_LOGW(TAG, "Emoji not found: %s", name);
return nullptr;
}
EmojiCollection::~EmojiCollection() {
for (auto it = emoji_collection_.begin(); it != emoji_collection_.end(); ++it) {
delete it->second;
}
emoji_collection_.clear();
}
// These are declared in xiaozhi-fonts/src/font_emoji_32.c
extern const lv_image_dsc_t emoji_1f636_32; // neutral
extern const lv_image_dsc_t emoji_1f642_32; // happy
extern const lv_image_dsc_t emoji_1f606_32; // laughing
extern const lv_image_dsc_t emoji_1f602_32; // funny
extern const lv_image_dsc_t emoji_1f614_32; // sad
extern const lv_image_dsc_t emoji_1f620_32; // angry
extern const lv_image_dsc_t emoji_1f62d_32; // crying
extern const lv_image_dsc_t emoji_1f60d_32; // loving
extern const lv_image_dsc_t emoji_1f633_32; // embarrassed
extern const lv_image_dsc_t emoji_1f62f_32; // surprised
extern const lv_image_dsc_t emoji_1f631_32; // shocked
extern const lv_image_dsc_t emoji_1f914_32; // thinking
extern const lv_image_dsc_t emoji_1f609_32; // winking
extern const lv_image_dsc_t emoji_1f60e_32; // cool
extern const lv_image_dsc_t emoji_1f60c_32; // relaxed
extern const lv_image_dsc_t emoji_1f924_32; // delicious
extern const lv_image_dsc_t emoji_1f618_32; // kissy
extern const lv_image_dsc_t emoji_1f60f_32; // confident
extern const lv_image_dsc_t emoji_1f634_32; // sleepy
extern const lv_image_dsc_t emoji_1f61c_32; // silly
extern const lv_image_dsc_t emoji_1f644_32; // confused
Twemoji32::Twemoji32() {
AddEmoji("neutral", new LvglSourceImage(&emoji_1f636_32));
AddEmoji("happy", new LvglSourceImage(&emoji_1f642_32));
AddEmoji("laughing", new LvglSourceImage(&emoji_1f606_32));
AddEmoji("funny", new LvglSourceImage(&emoji_1f602_32));
AddEmoji("sad", new LvglSourceImage(&emoji_1f614_32));
AddEmoji("angry", new LvglSourceImage(&emoji_1f620_32));
AddEmoji("crying", new LvglSourceImage(&emoji_1f62d_32));
AddEmoji("loving", new LvglSourceImage(&emoji_1f60d_32));
AddEmoji("embarrassed", new LvglSourceImage(&emoji_1f633_32));
AddEmoji("surprised", new LvglSourceImage(&emoji_1f62f_32));
AddEmoji("shocked", new LvglSourceImage(&emoji_1f631_32));
AddEmoji("thinking", new LvglSourceImage(&emoji_1f914_32));
AddEmoji("winking", new LvglSourceImage(&emoji_1f609_32));
AddEmoji("cool", new LvglSourceImage(&emoji_1f60e_32));
AddEmoji("relaxed", new LvglSourceImage(&emoji_1f60c_32));
AddEmoji("delicious", new LvglSourceImage(&emoji_1f924_32));
AddEmoji("kissy", new LvglSourceImage(&emoji_1f618_32));
AddEmoji("confident", new LvglSourceImage(&emoji_1f60f_32));
AddEmoji("sleepy", new LvglSourceImage(&emoji_1f634_32));
AddEmoji("silly", new LvglSourceImage(&emoji_1f61c_32));
AddEmoji("confused", new LvglSourceImage(&emoji_1f644_32));
}
// These are declared in xiaozhi-fonts/src/font_emoji_64.c
extern const lv_image_dsc_t emoji_1f636_64; // neutral
extern const lv_image_dsc_t emoji_1f642_64; // happy
extern const lv_image_dsc_t emoji_1f606_64; // laughing
extern const lv_image_dsc_t emoji_1f602_64; // funny
extern const lv_image_dsc_t emoji_1f614_64; // sad
extern const lv_image_dsc_t emoji_1f620_64; // angry
extern const lv_image_dsc_t emoji_1f62d_64; // crying
extern const lv_image_dsc_t emoji_1f60d_64; // loving
extern const lv_image_dsc_t emoji_1f633_64; // embarrassed
extern const lv_image_dsc_t emoji_1f62f_64; // surprised
extern const lv_image_dsc_t emoji_1f631_64; // shocked
extern const lv_image_dsc_t emoji_1f914_64; // thinking
extern const lv_image_dsc_t emoji_1f609_64; // winking
extern const lv_image_dsc_t emoji_1f60e_64; // cool
extern const lv_image_dsc_t emoji_1f60c_64; // relaxed
extern const lv_image_dsc_t emoji_1f924_64; // delicious
extern const lv_image_dsc_t emoji_1f618_64; // kissy
extern const lv_image_dsc_t emoji_1f60f_64; // confident
extern const lv_image_dsc_t emoji_1f634_64; // sleepy
extern const lv_image_dsc_t emoji_1f61c_64; // silly
extern const lv_image_dsc_t emoji_1f644_64; // confused
Twemoji64::Twemoji64() {
AddEmoji("neutral", new LvglSourceImage(&emoji_1f636_64));
AddEmoji("happy", new LvglSourceImage(&emoji_1f642_64));
AddEmoji("laughing", new LvglSourceImage(&emoji_1f606_64));
AddEmoji("funny", new LvglSourceImage(&emoji_1f602_64));
AddEmoji("sad", new LvglSourceImage(&emoji_1f614_64));
AddEmoji("angry", new LvglSourceImage(&emoji_1f620_64));
AddEmoji("crying", new LvglSourceImage(&emoji_1f62d_64));
AddEmoji("loving", new LvglSourceImage(&emoji_1f60d_64));
AddEmoji("embarrassed", new LvglSourceImage(&emoji_1f633_64));
AddEmoji("surprised", new LvglSourceImage(&emoji_1f62f_64));
AddEmoji("shocked", new LvglSourceImage(&emoji_1f631_64));
AddEmoji("thinking", new LvglSourceImage(&emoji_1f914_64));
AddEmoji("winking", new LvglSourceImage(&emoji_1f609_64));
AddEmoji("cool", new LvglSourceImage(&emoji_1f60e_64));
AddEmoji("relaxed", new LvglSourceImage(&emoji_1f60c_64));
AddEmoji("delicious", new LvglSourceImage(&emoji_1f924_64));
AddEmoji("kissy", new LvglSourceImage(&emoji_1f618_64));
AddEmoji("confident", new LvglSourceImage(&emoji_1f60f_64));
AddEmoji("sleepy", new LvglSourceImage(&emoji_1f634_64));
AddEmoji("silly", new LvglSourceImage(&emoji_1f61c_64));
AddEmoji("confused", new LvglSourceImage(&emoji_1f644_64));
}

View File

@@ -0,0 +1,34 @@
#ifndef EMOJI_COLLECTION_H
#define EMOJI_COLLECTION_H
#include "lvgl_image.h"
#include <lvgl.h>
#include <map>
#include <string>
#include <memory>
// Define interface for emoji collection
class EmojiCollection {
public:
virtual void AddEmoji(const std::string& name, LvglImage* image);
virtual const LvglImage* GetEmojiImage(const char* name);
virtual ~EmojiCollection();
private:
std::map<std::string, LvglImage*> emoji_collection_;
};
class Twemoji32 : public EmojiCollection {
public:
Twemoji32();
};
class Twemoji64 : public EmojiCollection {
public:
Twemoji64();
};
#endif

View File

@@ -0,0 +1,2 @@
All of the source code and documentation for gifdec is released into the
public domain and provided without warranty of any kind.

View File

@@ -0,0 +1,818 @@
#include "gifdec.h"
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#define MIN(A, B) ((A) < (B) ? (A) : (B))
#define MAX(A, B) ((A) > (B) ? (A) : (B))
typedef struct Entry {
uint16_t length;
uint16_t prefix;
uint8_t suffix;
} Entry;
typedef struct Table {
int bulk;
int nentries;
Entry * entries;
} Table;
#if LV_GIF_CACHE_DECODE_DATA
#define LZW_MAXBITS 12
#define LZW_TABLE_SIZE (1 << LZW_MAXBITS)
#define LZW_CACHE_SIZE (LZW_TABLE_SIZE * 4)
#endif
static gd_GIF * gif_open(gd_GIF * gif);
static bool f_gif_open(gd_GIF * gif, const void * path, bool is_file);
static void f_gif_read(gd_GIF * gif, void * buf, size_t len);
static int f_gif_seek(gd_GIF * gif, size_t pos, int k);
static void f_gif_close(gd_GIF * gif);
#if LV_USE_DRAW_SW_ASM == LV_DRAW_SW_ASM_HELIUM
#include "gifdec_mve.h"
#endif
static uint16_t
read_num(gd_GIF * gif)
{
uint8_t bytes[2];
f_gif_read(gif, bytes, 2);
return bytes[0] + (((uint16_t) bytes[1]) << 8);
}
gd_GIF *
gd_open_gif_file(const char * fname)
{
gd_GIF gif_base;
memset(&gif_base, 0, sizeof(gif_base));
bool res = f_gif_open(&gif_base, fname, true);
if(!res) return NULL;
return gif_open(&gif_base);
}
gd_GIF *
gd_open_gif_data(const void * data)
{
gd_GIF gif_base;
memset(&gif_base, 0, sizeof(gif_base));
bool res = f_gif_open(&gif_base, data, false);
if(!res) return NULL;
return gif_open(&gif_base);
}
static gd_GIF * gif_open(gd_GIF * gif_base)
{
uint8_t sigver[3];
uint16_t width, height, depth;
uint8_t fdsz, bgidx, aspect;
uint8_t * bgcolor;
int gct_sz;
gd_GIF * gif = NULL;
/* Header */
f_gif_read(gif_base, sigver, 3);
if(memcmp(sigver, "GIF", 3) != 0) {
LV_LOG_WARN("invalid signature");
goto fail;
}
/* Version */
f_gif_read(gif_base, sigver, 3);
if(memcmp(sigver, "89a", 3) != 0) {
LV_LOG_WARN("invalid version");
goto fail;
}
/* Width x Height */
width = read_num(gif_base);
height = read_num(gif_base);
/* FDSZ */
f_gif_read(gif_base, &fdsz, 1);
/* Presence of GCT */
if(!(fdsz & 0x80)) {
LV_LOG_WARN("no global color table");
goto fail;
}
/* Color Space's Depth */
depth = ((fdsz >> 4) & 7) + 1;
/* Ignore Sort Flag. */
/* GCT Size */
gct_sz = 1 << ((fdsz & 0x07) + 1);
/* Background Color Index */
f_gif_read(gif_base, &bgidx, 1);
/* Aspect Ratio */
f_gif_read(gif_base, &aspect, 1);
/* Create gd_GIF Structure. */
if(0 == width || 0 == height){
LV_LOG_WARN("Zero size image");
goto fail;
}
#if LV_GIF_CACHE_DECODE_DATA
if(0 == (INT_MAX - sizeof(gd_GIF) - LZW_CACHE_SIZE) / width / height / 5){
LV_LOG_WARN("Image dimensions are too large");
goto fail;
}
gif = lv_malloc(sizeof(gd_GIF) + 5 * width * height + LZW_CACHE_SIZE);
#else
if(0 == (INT_MAX - sizeof(gd_GIF)) / width / height / 5){
LV_LOG_WARN("Image dimensions are too large");
goto fail;
}
gif = lv_malloc(sizeof(gd_GIF) + 5 * width * height);
#endif
if(!gif) goto fail;
memcpy(gif, gif_base, sizeof(gd_GIF));
gif->width = width;
gif->height = height;
gif->depth = depth;
/* Read GCT */
gif->gct.size = gct_sz;
f_gif_read(gif, gif->gct.colors, 3 * gif->gct.size);
gif->palette = &gif->gct;
gif->bgindex = bgidx;
gif->canvas = (uint8_t *) &gif[1];
gif->frame = &gif->canvas[4 * width * height];
if(gif->bgindex) {
memset(gif->frame, gif->bgindex, gif->width * gif->height);
}
bgcolor = &gif->palette->colors[gif->bgindex * 3];
#if LV_GIF_CACHE_DECODE_DATA
gif->lzw_cache = gif->frame + width * height;
#endif
#ifdef GIFDEC_FILL_BG
GIFDEC_FILL_BG(gif->canvas, gif->width * gif->height, 1, gif->width * gif->height, bgcolor, 0x00);
#else
for(int i = 0; i < gif->width * gif->height; i++) {
gif->canvas[i * 4 + 0] = *(bgcolor + 2);
gif->canvas[i * 4 + 1] = *(bgcolor + 1);
gif->canvas[i * 4 + 2] = *(bgcolor + 0);
gif->canvas[i * 4 + 3] = 0x00; // 初始化为透明,让第一帧根据自己的透明度设置来渲染
}
#endif
gif->anim_start = f_gif_seek(gif, 0, LV_FS_SEEK_CUR);
gif->loop_count = -1;
goto ok;
fail:
f_gif_close(gif_base);
ok:
return gif;
}
static void
discard_sub_blocks(gd_GIF * gif)
{
uint8_t size;
do {
f_gif_read(gif, &size, 1);
f_gif_seek(gif, size, LV_FS_SEEK_CUR);
} while(size);
}
static void
read_plain_text_ext(gd_GIF * gif)
{
if(gif->plain_text) {
uint16_t tx, ty, tw, th;
uint8_t cw, ch, fg, bg;
size_t sub_block;
f_gif_seek(gif, 1, LV_FS_SEEK_CUR); /* block size = 12 */
tx = read_num(gif);
ty = read_num(gif);
tw = read_num(gif);
th = read_num(gif);
f_gif_read(gif, &cw, 1);
f_gif_read(gif, &ch, 1);
f_gif_read(gif, &fg, 1);
f_gif_read(gif, &bg, 1);
sub_block = f_gif_seek(gif, 0, LV_FS_SEEK_CUR);
gif->plain_text(gif, tx, ty, tw, th, cw, ch, fg, bg);
f_gif_seek(gif, sub_block, LV_FS_SEEK_SET);
}
else {
/* Discard plain text metadata. */
f_gif_seek(gif, 13, LV_FS_SEEK_CUR);
}
/* Discard plain text sub-blocks. */
discard_sub_blocks(gif);
}
static void
read_graphic_control_ext(gd_GIF * gif)
{
uint8_t rdit;
/* Discard block size (always 0x04). */
f_gif_seek(gif, 1, LV_FS_SEEK_CUR);
f_gif_read(gif, &rdit, 1);
gif->gce.disposal = (rdit >> 2) & 3;
gif->gce.input = rdit & 2;
gif->gce.transparency = rdit & 1;
gif->gce.delay = read_num(gif);
f_gif_read(gif, &gif->gce.tindex, 1);
/* Skip block terminator. */
f_gif_seek(gif, 1, LV_FS_SEEK_CUR);
}
static void
read_comment_ext(gd_GIF * gif)
{
if(gif->comment) {
size_t sub_block = f_gif_seek(gif, 0, LV_FS_SEEK_CUR);
gif->comment(gif);
f_gif_seek(gif, sub_block, LV_FS_SEEK_SET);
}
/* Discard comment sub-blocks. */
discard_sub_blocks(gif);
}
static void
read_application_ext(gd_GIF * gif)
{
char app_id[8];
char app_auth_code[3];
uint16_t loop_count;
/* Discard block size (always 0x0B). */
f_gif_seek(gif, 1, LV_FS_SEEK_CUR);
/* Application Identifier. */
f_gif_read(gif, app_id, 8);
/* Application Authentication Code. */
f_gif_read(gif, app_auth_code, 3);
if(!strncmp(app_id, "NETSCAPE", sizeof(app_id))) {
/* Discard block size (0x03) and constant byte (0x01). */
f_gif_seek(gif, 2, LV_FS_SEEK_CUR);
loop_count = read_num(gif);
if(gif->loop_count < 0) {
if(loop_count == 0) {
gif->loop_count = 0;
}
else {
gif->loop_count = loop_count + 1;
}
}
/* Skip block terminator. */
f_gif_seek(gif, 1, LV_FS_SEEK_CUR);
}
else if(gif->application) {
size_t sub_block = f_gif_seek(gif, 0, LV_FS_SEEK_CUR);
gif->application(gif, app_id, app_auth_code);
f_gif_seek(gif, sub_block, LV_FS_SEEK_SET);
discard_sub_blocks(gif);
}
else {
discard_sub_blocks(gif);
}
}
static void
read_ext(gd_GIF * gif)
{
uint8_t label;
f_gif_read(gif, &label, 1);
switch(label) {
case 0x01:
read_plain_text_ext(gif);
break;
case 0xF9:
read_graphic_control_ext(gif);
break;
case 0xFE:
read_comment_ext(gif);
break;
case 0xFF:
read_application_ext(gif);
break;
default:
LV_LOG_WARN("unknown extension: %02X\n", label);
}
}
static uint16_t
get_key(gd_GIF *gif, int key_size, uint8_t *sub_len, uint8_t *shift, uint8_t *byte)
{
int bits_read;
int rpad;
int frag_size;
uint16_t key;
key = 0;
for (bits_read = 0; bits_read < key_size; bits_read += frag_size) {
rpad = (*shift + bits_read) % 8;
if (rpad == 0) {
/* Update byte. */
if (*sub_len == 0) {
f_gif_read(gif, sub_len, 1); /* Must be nonzero! */
if (*sub_len == 0) return 0x1000;
}
f_gif_read(gif, byte, 1);
(*sub_len)--;
}
frag_size = MIN(key_size - bits_read, 8 - rpad);
key |= ((uint16_t) ((*byte) >> rpad)) << bits_read;
}
/* Clear extra bits to the left. */
key &= (1 << key_size) - 1;
*shift = (*shift + key_size) % 8;
return key;
}
#if LV_GIF_CACHE_DECODE_DATA
/* Decompress image pixels.
* Return 0 on success or -1 on out-of-memory (w.r.t. LZW code table) or parse error. */
static int
read_image_data(gd_GIF *gif, int interlace)
{
uint8_t sub_len, shift, byte;
int ret = 0;
int key_size;
int y, pass, linesize;
uint8_t *ptr = NULL;
uint8_t *ptr_row_start = NULL;
uint8_t *ptr_base = NULL;
size_t start, end;
uint16_t key, clear_code, stop_code, curr_code;
int frm_off, frm_size,curr_size,top_slot,new_codes,slot;
/* The first value of the value sequence corresponding to key */
int first_value;
int last_key;
uint8_t *sp = NULL;
uint8_t *p_stack = NULL;
uint8_t *p_suffix = NULL;
uint16_t *p_prefix = NULL;
/* get initial key size and clear code, stop code */
f_gif_read(gif, &byte, 1);
key_size = (int) byte;
clear_code = 1 << key_size;
stop_code = clear_code + 1;
key = 0;
start = f_gif_seek(gif, 0, LV_FS_SEEK_CUR);
discard_sub_blocks(gif);
end = f_gif_seek(gif, 0, LV_FS_SEEK_CUR);
f_gif_seek(gif, start, LV_FS_SEEK_SET);
linesize = gif->width;
ptr_base = &gif->frame[gif->fy * linesize + gif->fx];
ptr_row_start = ptr_base;
ptr = ptr_row_start;
sub_len = shift = 0;
/* decoder */
pass = 0;
y = 0;
p_stack = gif->lzw_cache;
p_suffix = gif->lzw_cache + LZW_TABLE_SIZE;
p_prefix = (uint16_t*)(gif->lzw_cache + LZW_TABLE_SIZE * 2);
frm_off = 0;
frm_size = gif->fw * gif->fh;
curr_size = key_size + 1;
top_slot = 1 << curr_size;
new_codes = clear_code + 2;
slot = new_codes;
first_value = -1;
last_key = -1;
sp = p_stack;
while (frm_off < frm_size) {
/* copy data to frame buffer */
while (sp > p_stack) {
if(frm_off >= frm_size){
LV_LOG_WARN("LZW table token overflows the frame buffer");
return -1;
}
*ptr++ = *(--sp);
frm_off += 1;
/* read one line */
if ((ptr - ptr_row_start) == gif->fw) {
if (interlace) {
switch(pass) {
case 0:
case 1:
y += 8;
ptr_row_start += linesize * 8;
break;
case 2:
y += 4;
ptr_row_start += linesize * 4;
break;
case 3:
y += 2;
ptr_row_start += linesize * 2;
break;
default:
break;
}
while (y >= gif->fh) {
y = 4 >> pass;
ptr_row_start = ptr_base + linesize * y;
pass++;
}
} else {
ptr_row_start += linesize;
}
ptr = ptr_row_start;
}
}
key = get_key(gif, curr_size, &sub_len, &shift, &byte);
if (key == stop_code || key >= LZW_TABLE_SIZE)
break;
if (key == clear_code) {
curr_size = key_size + 1;
slot = new_codes;
top_slot = 1 << curr_size;
first_value = last_key = -1;
sp = p_stack;
continue;
}
curr_code = key;
/*
* If the current code is a code that will be added to the decoding
* dictionary, it is composed of the data list corresponding to the
* previous key and its first data.
* */
if (curr_code == slot && first_value >= 0) {
*sp++ = first_value;
curr_code = last_key;
}else if(curr_code >= slot)
break;
while (curr_code >= new_codes) {
*sp++ = p_suffix[curr_code];
curr_code = p_prefix[curr_code];
}
*sp++ = curr_code;
/* Add code to decoding dictionary */
if (slot < top_slot && last_key >= 0) {
p_suffix[slot] = curr_code;
p_prefix[slot++] = last_key;
}
first_value = curr_code;
last_key = key;
if (slot >= top_slot) {
if (curr_size < LZW_MAXBITS) {
top_slot <<= 1;
curr_size += 1;
}
}
}
if (key == stop_code) f_gif_read(gif, &sub_len, 1); /* Must be zero! */
f_gif_seek(gif, end, LV_FS_SEEK_SET);
return ret;
}
#else
static Table *
new_table(int key_size)
{
int key;
int init_bulk = MAX(1 << (key_size + 1), 0x100);
Table * table = lv_malloc(sizeof(*table) + sizeof(Entry) * init_bulk);
if(table) {
table->bulk = init_bulk;
table->nentries = (1 << key_size) + 2;
table->entries = (Entry *) &table[1];
for(key = 0; key < (1 << key_size); key++)
table->entries[key] = (Entry) {
1, 0xFFF, key
};
}
return table;
}
/* Add table entry. Return value:
* 0 on success
* +1 if key size must be incremented after this addition
* -1 if could not realloc table */
static int
add_entry(Table ** tablep, uint16_t length, uint16_t prefix, uint8_t suffix)
{
Table * table = *tablep;
if(table->nentries == table->bulk) {
table->bulk *= 2;
table = lv_realloc(table, sizeof(*table) + sizeof(Entry) * table->bulk);
if(!table) return -1;
table->entries = (Entry *) &table[1];
*tablep = table;
}
table->entries[table->nentries] = (Entry) {
length, prefix, suffix
};
table->nentries++;
if((table->nentries & (table->nentries - 1)) == 0)
return 1;
return 0;
}
/* Compute output index of y-th input line, in frame of height h. */
static int
interlaced_line_index(int h, int y)
{
int p; /* number of lines in current pass */
p = (h - 1) / 8 + 1;
if(y < p) /* pass 1 */
return y * 8;
y -= p;
p = (h - 5) / 8 + 1;
if(y < p) /* pass 2 */
return y * 8 + 4;
y -= p;
p = (h - 3) / 4 + 1;
if(y < p) /* pass 3 */
return y * 4 + 2;
y -= p;
/* pass 4 */
return y * 2 + 1;
}
/* Decompress image pixels.
* Return 0 on success or -1 on out-of-memory (w.r.t. LZW code table) or parse error. */
static int
read_image_data(gd_GIF * gif, int interlace)
{
uint8_t sub_len, shift, byte;
int init_key_size, key_size, table_is_full = 0;
int frm_off, frm_size, str_len = 0, i, p, x, y;
uint16_t key, clear, stop;
int ret;
Table * table;
Entry entry = {0};
size_t start, end;
f_gif_read(gif, &byte, 1);
key_size = (int) byte;
start = f_gif_seek(gif, 0, LV_FS_SEEK_CUR);
discard_sub_blocks(gif);
end = f_gif_seek(gif, 0, LV_FS_SEEK_CUR);
f_gif_seek(gif, start, LV_FS_SEEK_SET);
clear = 1 << key_size;
stop = clear + 1;
table = new_table(key_size);
key_size++;
init_key_size = key_size;
sub_len = shift = 0;
key = get_key(gif, key_size, &sub_len, &shift, &byte); /* clear code */
frm_off = 0;
ret = 0;
frm_size = gif->fw * gif->fh;
while(frm_off < frm_size) {
if(key == clear) {
key_size = init_key_size;
table->nentries = (1 << (key_size - 1)) + 2;
table_is_full = 0;
}
else if(!table_is_full) {
ret = add_entry(&table, str_len + 1, key, entry.suffix);
if(ret == -1) {
lv_free(table);
return -1;
}
if(table->nentries == 0x1000) {
ret = 0;
table_is_full = 1;
}
}
key = get_key(gif, key_size, &sub_len, &shift, &byte);
if(key == clear) continue;
if(key == stop || key == 0x1000) break;
if(ret == 1) key_size++;
entry = table->entries[key];
str_len = entry.length;
if(frm_off + str_len > frm_size){
LV_LOG_WARN("LZW table token overflows the frame buffer");
lv_free(table);
return -1;
}
for(i = 0; i < str_len; i++) {
p = frm_off + entry.length - 1;
x = p % gif->fw;
y = p / gif->fw;
if(interlace)
y = interlaced_line_index((int) gif->fh, y);
gif->frame[(gif->fy + y) * gif->width + gif->fx + x] = entry.suffix;
if(entry.prefix == 0xFFF)
break;
else
entry = table->entries[entry.prefix];
}
frm_off += str_len;
if(key < table->nentries - 1 && !table_is_full)
table->entries[table->nentries - 1].suffix = entry.suffix;
}
lv_free(table);
if(key == stop) f_gif_read(gif, &sub_len, 1); /* Must be zero! */
f_gif_seek(gif, end, LV_FS_SEEK_SET);
return 0;
}
#endif
/* Read image.
* Return 0 on success or -1 on out-of-memory (w.r.t. LZW code table) or parse error. */
static int
read_image(gd_GIF * gif)
{
uint8_t fisrz;
int interlace;
/* Image Descriptor. */
gif->fx = read_num(gif);
gif->fy = read_num(gif);
gif->fw = read_num(gif);
gif->fh = read_num(gif);
if(gif->fx + (uint32_t)gif->fw > gif->width || gif->fy + (uint32_t)gif->fh > gif->height){
LV_LOG_WARN("Frame coordinates out of image bounds");
return -1;
}
f_gif_read(gif, &fisrz, 1);
interlace = fisrz & 0x40;
/* Ignore Sort Flag. */
/* Local Color Table? */
if(fisrz & 0x80) {
/* Read LCT */
gif->lct.size = 1 << ((fisrz & 0x07) + 1);
f_gif_read(gif, gif->lct.colors, 3 * gif->lct.size);
gif->palette = &gif->lct;
}
else
gif->palette = &gif->gct;
/* Image Data. */
return read_image_data(gif, interlace);
}
static void
render_frame_rect(gd_GIF * gif, uint8_t * buffer)
{
int i = gif->fy * gif->width + gif->fx;
#ifdef GIFDEC_RENDER_FRAME
GIFDEC_RENDER_FRAME(&buffer[i * 4], gif->fw, gif->fh, gif->width,
&gif->frame[i], gif->palette->colors,
gif->gce.transparency ? gif->gce.tindex : 0x100);
#else
int j, k;
uint8_t index, * color;
for(j = 0; j < gif->fh; j++) {
for(k = 0; k < gif->fw; k++) {
index = gif->frame[(gif->fy + j) * gif->width + gif->fx + k];
color = &gif->palette->colors[index * 3];
if(!gif->gce.transparency || index != gif->gce.tindex) {
buffer[(i + k) * 4 + 0] = *(color + 2);
buffer[(i + k) * 4 + 1] = *(color + 1);
buffer[(i + k) * 4 + 2] = *(color + 0);
buffer[(i + k) * 4 + 3] = 0xFF;
}
}
i += gif->width;
}
#endif
}
static void
dispose(gd_GIF * gif)
{
int i;
uint8_t * bgcolor;
switch(gif->gce.disposal) {
case 2: /* Restore to background color. */
bgcolor = &gif->palette->colors[gif->bgindex * 3];
uint8_t opa = 0xff;
if(gif->gce.transparency) opa = 0x00;
i = gif->fy * gif->width + gif->fx;
#ifdef GIFDEC_FILL_BG
GIFDEC_FILL_BG(&(gif->canvas[i * 4]), gif->fw, gif->fh, gif->width, bgcolor, opa);
#else
int j, k;
for(j = 0; j < gif->fh; j++) {
for(k = 0; k < gif->fw; k++) {
gif->canvas[(i + k) * 4 + 0] = *(bgcolor + 2);
gif->canvas[(i + k) * 4 + 1] = *(bgcolor + 1);
gif->canvas[(i + k) * 4 + 2] = *(bgcolor + 0);
gif->canvas[(i + k) * 4 + 3] = opa;
}
i += gif->width;
}
#endif
break;
case 3: /* Restore to previous, i.e., don't update canvas.*/
break;
default:
/* Add frame non-transparent pixels to canvas. */
render_frame_rect(gif, gif->canvas);
}
}
/* Return 1 if got a frame; 0 if got GIF trailer; -1 if error. */
int
gd_get_frame(gd_GIF * gif)
{
char sep;
dispose(gif);
f_gif_read(gif, &sep, 1);
while(sep != ',') {
if(sep == ';') {
f_gif_seek(gif, gif->anim_start, LV_FS_SEEK_SET);
if(gif->loop_count == 1 || gif->loop_count < 0) {
return 0;
}
else if(gif->loop_count > 1) {
gif->loop_count--;
}
}
else if(sep == '!')
read_ext(gif);
else return -1;
f_gif_read(gif, &sep, 1);
}
if(read_image(gif) == -1)
return -1;
return 1;
}
void
gd_render_frame(gd_GIF * gif, uint8_t * buffer)
{
render_frame_rect(gif, buffer);
}
void
gd_rewind(gd_GIF * gif)
{
gif->loop_count = -1;
f_gif_seek(gif, gif->anim_start, LV_FS_SEEK_SET);
}
void
gd_close_gif(gd_GIF * gif)
{
f_gif_close(gif);
lv_free(gif);
}
static bool f_gif_open(gd_GIF * gif, const void * path, bool is_file)
{
gif->f_rw_p = 0;
gif->data = NULL;
gif->is_file = is_file;
if(is_file) {
lv_fs_res_t res = lv_fs_open(&gif->fd, path, LV_FS_MODE_RD);
if(res != LV_FS_RES_OK) return false;
else return true;
}
else {
gif->data = path;
return true;
}
}
static void f_gif_read(gd_GIF * gif, void * buf, size_t len)
{
if(gif->is_file) {
lv_fs_read(&gif->fd, buf, len, NULL);
}
else {
memcpy(buf, &gif->data[gif->f_rw_p], len);
gif->f_rw_p += len;
}
}
static int f_gif_seek(gd_GIF * gif, size_t pos, int k)
{
if(gif->is_file) {
lv_fs_seek(&gif->fd, pos, k);
uint32_t x;
lv_fs_tell(&gif->fd, &x);
return x;
}
else {
if(k == LV_FS_SEEK_CUR) gif->f_rw_p += pos;
else if(k == LV_FS_SEEK_SET) gif->f_rw_p = pos;
return gif->f_rw_p;
}
}
static void f_gif_close(gd_GIF * gif)
{
if(gif->is_file) {
lv_fs_close(&gif->fd);
}
}

View File

@@ -0,0 +1,68 @@
#ifndef GIFDEC_H
#define GIFDEC_H
#ifdef __cplusplus
extern "C" {
#endif
#include <lvgl.h>
#include <stdint.h>
typedef struct _gd_Palette {
int size;
uint8_t colors[0x100 * 3];
} gd_Palette;
typedef struct _gd_GCE {
uint16_t delay;
uint8_t tindex;
uint8_t disposal;
int input;
int transparency;
} gd_GCE;
typedef struct _gd_GIF {
lv_fs_file_t fd;
const char * data;
uint8_t is_file;
uint32_t f_rw_p;
int32_t anim_start;
uint16_t width, height;
uint16_t depth;
int32_t loop_count;
gd_GCE gce;
gd_Palette * palette;
gd_Palette lct, gct;
void (*plain_text)(
struct _gd_GIF * gif, uint16_t tx, uint16_t ty,
uint16_t tw, uint16_t th, uint8_t cw, uint8_t ch,
uint8_t fg, uint8_t bg
);
void (*comment)(struct _gd_GIF * gif);
void (*application)(struct _gd_GIF * gif, char id[8], char auth[3]);
uint16_t fx, fy, fw, fh;
uint8_t bgindex;
uint8_t * canvas, * frame;
#if LV_GIF_CACHE_DECODE_DATA
uint8_t *lzw_cache;
#endif
} gd_GIF;
gd_GIF * gd_open_gif_file(const char * fname);
gd_GIF * gd_open_gif_data(const void * data);
void gd_render_frame(gd_GIF * gif, uint8_t * buffer);
int gd_get_frame(gd_GIF * gif);
void gd_rewind(gd_GIF * gif);
void gd_close_gif(gd_GIF * gif);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* GIFDEC_H */

View File

@@ -0,0 +1,140 @@
/**
* @file gifdec_mve.h
*
*/
#ifndef GIFDEC_MVE_H
#define GIFDEC_MVE_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include <stdint.h>
#include "../../misc/lv_color.h"
/*********************
* DEFINES
*********************/
#define GIFDEC_FILL_BG(dst, w, h, stride, color, opa) \
_gifdec_fill_bg_mve(dst, w, h, stride, color, opa)
#define GIFDEC_RENDER_FRAME(dst, w, h, stride, frame, pattern, tindex) \
_gifdec_render_frame_mve(dst, w, h, stride, frame, pattern, tindex)
/**********************
* MACROS
**********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* GLOBAL PROTOTYPES
**********************/
static inline void _gifdec_fill_bg_mve(uint8_t * dst, uint16_t w, uint16_t h, uint16_t stride, uint8_t * color,
uint8_t opa)
{
lv_color32_t c = lv_color32_make(*(color + 0), *(color + 1), *(color + 2), opa);
uint32_t color_32 = *(uint32_t *)&c;
__asm volatile(
".p2align 2 \n"
"vdup.32 q0, %[src] \n"
"3: \n"
"mov r0, %[dst] \n"
"wlstp.32 lr, %[w], 1f \n"
"2: \n"
"vstrw.32 q0, [r0], #16 \n"
"letp lr, 2b \n"
"1: \n"
"add %[dst], %[iTargetStride] \n"
"subs %[h], #1 \n"
"bne 3b \n"
: [dst] "+r"(dst),
[h] "+r"(h)
: [src] "r"(color_32),
[w] "r"(w),
[iTargetStride] "r"(stride * sizeof(uint32_t))
: "r0", "q0", "memory", "r14", "cc");
}
static inline void _gifdec_render_frame_mve(uint8_t * dst, uint16_t w, uint16_t h, uint16_t stride, uint8_t * frame,
uint8_t * pattern, uint16_t tindex)
{
if(w == 0 || h == 0) {
return;
}
__asm volatile(
"vmov.u16 q3, #255 \n"
"vshl.u16 q3, q3, #8 \n" /* left shift 8 for a*/
"mov r0, #2 \n"
"vidup.u16 q6, r0, #4 \n" /* [2, 6, 10, 14, 18, 22, 26, 30] */
"mov r0, #0 \n"
"vidup.u16 q7, r0, #4 \n" /* [0, 4, 8, 12, 16, 20, 24, 28] */
"3: \n"
"mov r1, %[dst] \n"
"mov r2, %[frame] \n"
"wlstp.16 lr, %[w], 1f \n"
"2: \n"
"mov r0, #3 \n"
"vldrb.u16 q4, [r2], #8 \n"
"vmul.u16 q5, q4, r0 \n"
"mov r0, #1 \n"
"vldrb.u16 q2, [%[pattern], q5] \n" /* load 8 pixel r*/
"vadd.u16 q5, q5, r0 \n"
"vldrb.u16 q1, [%[pattern], q5] \n" /* load 8 pixel g*/
"vadd.u16 q5, q5, r0 \n"
"vldrb.u16 q0, [%[pattern], q5] \n" /* load 8 pixel b*/
"vshl.u16 q1, q1, #8 \n" /* left shift 8 for g*/
"vorr.u16 q0, q0, q1 \n" /* make 8 pixel gb*/
"vorr.u16 q1, q2, q3 \n" /* make 8 pixel ar*/
"vcmp.i16 ne, q4, %[tindex] \n"
"vpstt \n"
"vstrht.16 q0, [r1, q7] \n"
"vstrht.16 q1, [r1, q6] \n"
"add r1, r1, #32 \n"
"letp lr, 2b \n"
"1: \n"
"mov r0, %[stride], LSL #2 \n"
"add %[dst], r0 \n"
"add %[frame], %[stride] \n"
"subs %[h], #1 \n"
"bne 3b \n"
: [dst] "+r"(dst),
[frame] "+r"(frame),
[h] "+r"(h)
: [pattern] "r"(pattern),
[w] "r"(w),
[stride] "r"(stride),
[tindex] "r"(tindex)
: "r0", "r1", "r2", "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7", "memory", "r14", "cc");
}
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*GIFDEC_MVE_H*/

View File

@@ -0,0 +1,207 @@
#include "lvgl_gif.h"
#include <esp_log.h>
#include <cstring>
#define TAG "LvglGif"
LvglGif::LvglGif(const lv_img_dsc_t* img_dsc)
: gif_(nullptr), timer_(nullptr), last_call_(0), playing_(false), loaded_(false) {
if (!img_dsc || !img_dsc->data) {
ESP_LOGE(TAG, "Invalid image descriptor");
return;
}
gif_ = gd_open_gif_data(img_dsc->data);
if (!gif_) {
ESP_LOGE(TAG, "Failed to open GIF from image descriptor");
}
// Setup LVGL image descriptor
memset(&img_dsc_, 0, sizeof(img_dsc_));
img_dsc_.header.magic = LV_IMAGE_HEADER_MAGIC;
img_dsc_.header.flags = LV_IMAGE_FLAGS_MODIFIABLE;
img_dsc_.header.cf = LV_COLOR_FORMAT_ARGB8888;
img_dsc_.header.w = gif_->width;
img_dsc_.header.h = gif_->height;
img_dsc_.header.stride = gif_->width * 4;
img_dsc_.data = gif_->canvas;
img_dsc_.data_size = gif_->width * gif_->height * 4;
// Render first frame
if (gif_->canvas) {
gd_render_frame(gif_, gif_->canvas);
}
loaded_ = true;
ESP_LOGI(TAG, "GIF loaded from image descriptor: %dx%d", gif_->width, gif_->height);
}
// Destructor
LvglGif::~LvglGif() {
Cleanup();
}
// LvglImage interface implementation
const lv_img_dsc_t* LvglGif::image_dsc() const {
if (!loaded_) {
return nullptr;
}
return &img_dsc_;
}
// Animation control methods
void LvglGif::Start() {
if (!loaded_ || !gif_) {
ESP_LOGW(TAG, "GIF not loaded, cannot start");
return;
}
if (!timer_) {
timer_ = lv_timer_create([](lv_timer_t* timer) {
LvglGif* gif_obj = static_cast<LvglGif*>(lv_timer_get_user_data(timer));
gif_obj->NextFrame();
}, 10, this);
}
if (timer_) {
playing_ = true;
last_call_ = lv_tick_get();
lv_timer_resume(timer_);
lv_timer_reset(timer_);
// Render first frame
NextFrame();
ESP_LOGI(TAG, "GIF animation started");
}
}
void LvglGif::Pause() {
if (timer_) {
playing_ = false;
lv_timer_pause(timer_);
ESP_LOGI(TAG, "GIF animation paused");
}
}
void LvglGif::Resume() {
if (!loaded_ || !gif_) {
ESP_LOGW(TAG, "GIF not loaded, cannot resume");
return;
}
if (timer_) {
playing_ = true;
lv_timer_resume(timer_);
ESP_LOGI(TAG, "GIF animation resumed");
}
}
void LvglGif::Stop() {
if (timer_) {
playing_ = false;
lv_timer_pause(timer_);
}
if (gif_) {
gd_rewind(gif_);
NextFrame();
ESP_LOGI(TAG, "GIF animation stopped and rewound");
}
}
bool LvglGif::IsPlaying() const {
return playing_;
}
bool LvglGif::IsLoaded() const {
return loaded_;
}
int32_t LvglGif::GetLoopCount() const {
if (!loaded_ || !gif_) {
return -1;
}
return gif_->loop_count;
}
void LvglGif::SetLoopCount(int32_t count) {
if (!loaded_ || !gif_) {
ESP_LOGW(TAG, "GIF not loaded, cannot set loop count");
return;
}
gif_->loop_count = count;
}
uint16_t LvglGif::width() const {
if (!loaded_ || !gif_) {
return 0;
}
return gif_->width;
}
uint16_t LvglGif::height() const {
if (!loaded_ || !gif_) {
return 0;
}
return gif_->height;
}
void LvglGif::SetFrameCallback(std::function<void()> callback) {
frame_callback_ = callback;
}
void LvglGif::NextFrame() {
if (!loaded_ || !gif_ || !playing_) {
return;
}
// Check if enough time has passed for the next frame
uint32_t elapsed = lv_tick_elaps(last_call_);
if (elapsed < gif_->gce.delay * 10) {
return;
}
last_call_ = lv_tick_get();
// Get next frame
int has_next = gd_get_frame(gif_);
if (has_next == 0) {
// Animation finished, pause timer
playing_ = false;
if (timer_) {
lv_timer_pause(timer_);
}
ESP_LOGI(TAG, "GIF animation completed");
}
// Render current frame
if (gif_->canvas) {
gd_render_frame(gif_, gif_->canvas);
// Call frame callback if set
if (frame_callback_) {
frame_callback_();
}
}
}
void LvglGif::Cleanup() {
// Stop and delete timer
if (timer_) {
lv_timer_delete(timer_);
timer_ = nullptr;
}
// Close GIF decoder
if (gif_) {
gd_close_gif(gif_);
gif_ = nullptr;
}
playing_ = false;
loaded_ = false;
// Clear image descriptor
memset(&img_dsc_, 0, sizeof(img_dsc_));
}

View File

@@ -0,0 +1,101 @@
#pragma once
#include "../lvgl_image.h"
#include "gifdec.h"
#include <lvgl.h>
#include <memory>
#include <functional>
/**
* C++ implementation of LVGL GIF widget
* Provides GIF animation functionality using gifdec library
*/
class LvglGif {
public:
explicit LvglGif(const lv_img_dsc_t* img_dsc);
virtual ~LvglGif();
// LvglImage interface implementation
virtual const lv_img_dsc_t* image_dsc() const;
/**
* Start/restart GIF animation
*/
void Start();
/**
* Pause GIF animation
*/
void Pause();
/**
* Resume GIF animation
*/
void Resume();
/**
* Stop GIF animation and rewind to first frame
*/
void Stop();
/**
* Check if GIF is currently playing
*/
bool IsPlaying() const;
/**
* Check if GIF was loaded successfully
*/
bool IsLoaded() const;
/**
* Get loop count
*/
int32_t GetLoopCount() const;
/**
* Set loop count
*/
void SetLoopCount(int32_t count);
/**
* Get GIF dimensions
*/
uint16_t width() const;
uint16_t height() const;
/**
* Set frame update callback
*/
void SetFrameCallback(std::function<void()> callback);
private:
// GIF decoder instance
gd_GIF* gif_;
// LVGL image descriptor
lv_img_dsc_t img_dsc_;
// Animation timer
lv_timer_t* timer_;
// Last frame update time
uint32_t last_call_;
// Animation state
bool playing_;
bool loaded_;
// Frame update callback
std::function<void()> frame_callback_;
/**
* Update to next frame
*/
void NextFrame();
/**
* Cleanup resources
*/
void Cleanup();
};

View File

@@ -0,0 +1,220 @@
#include <esp_log.h>
#include <esp_err.h>
#include <string>
#include <cstdlib>
#include <cstring>
#include <font_awesome.h>
#include "lvgl_display.h"
#include "board.h"
#include "application.h"
#include "audio_codec.h"
#include "settings.h"
#include "assets/lang_config.h"
#define TAG "Display"
LvglDisplay::LvglDisplay() {
// Notification timer
esp_timer_create_args_t notification_timer_args = {
.callback = [](void *arg) {
LvglDisplay *display = static_cast<LvglDisplay*>(arg);
DisplayLockGuard lock(display);
lv_obj_add_flag(display->notification_label_, LV_OBJ_FLAG_HIDDEN);
lv_obj_remove_flag(display->status_label_, LV_OBJ_FLAG_HIDDEN);
},
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
.name = "notification_timer",
.skip_unhandled_events = false,
};
ESP_ERROR_CHECK(esp_timer_create(&notification_timer_args, &notification_timer_));
// Create a power management lock
auto ret = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "display_update", &pm_lock_);
if (ret == ESP_ERR_NOT_SUPPORTED) {
ESP_LOGI(TAG, "Power management not supported");
} else {
ESP_ERROR_CHECK(ret);
}
}
LvglDisplay::~LvglDisplay() {
if (notification_timer_ != nullptr) {
esp_timer_stop(notification_timer_);
esp_timer_delete(notification_timer_);
}
if (network_label_ != nullptr) {
lv_obj_del(network_label_);
}
if (notification_label_ != nullptr) {
lv_obj_del(notification_label_);
}
if (status_label_ != nullptr) {
lv_obj_del(status_label_);
}
if (mute_label_ != nullptr) {
lv_obj_del(mute_label_);
}
if (battery_label_ != nullptr) {
lv_obj_del(battery_label_);
}
if( low_battery_popup_ != nullptr ) {
lv_obj_del(low_battery_popup_);
}
if (pm_lock_ != nullptr) {
esp_pm_lock_delete(pm_lock_);
}
}
void LvglDisplay::SetStatus(const char* status) {
DisplayLockGuard lock(this);
if (status_label_ == nullptr) {
return;
}
lv_label_set_text(status_label_, status);
lv_obj_remove_flag(status_label_, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN);
last_status_update_time_ = std::chrono::system_clock::now();
}
void LvglDisplay::ShowNotification(const std::string &notification, int duration_ms) {
ShowNotification(notification.c_str(), duration_ms);
}
void LvglDisplay::ShowNotification(const char* notification, int duration_ms) {
DisplayLockGuard lock(this);
if (notification_label_ == nullptr) {
return;
}
lv_label_set_text(notification_label_, notification);
lv_obj_remove_flag(notification_label_, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(status_label_, LV_OBJ_FLAG_HIDDEN);
esp_timer_stop(notification_timer_);
ESP_ERROR_CHECK(esp_timer_start_once(notification_timer_, duration_ms * 1000));
}
void LvglDisplay::UpdateStatusBar(bool update_all) {
auto& app = Application::GetInstance();
auto& board = Board::GetInstance();
auto codec = board.GetAudioCodec();
// Update mute icon
{
DisplayLockGuard lock(this);
if (mute_label_ == nullptr) {
return;
}
// 如果静音状态改变,则更新图标
if (codec->output_volume() == 0 && !muted_) {
muted_ = true;
lv_label_set_text(mute_label_, FONT_AWESOME_VOLUME_XMARK);
} else if (codec->output_volume() > 0 && muted_) {
muted_ = false;
lv_label_set_text(mute_label_, "");
}
}
// Update time
if (app.GetDeviceState() == kDeviceStateIdle) {
if (last_status_update_time_ + std::chrono::seconds(10) < std::chrono::system_clock::now()) {
// Set status to clock "HH:MM"
time_t now = time(NULL);
struct tm* tm = localtime(&now);
// Check if the we have already set the time
if (tm->tm_year >= 2025 - 1900) {
char time_str[16];
strftime(time_str, sizeof(time_str), "%H:%M ", tm);
SetStatus(time_str);
} else {
ESP_LOGW(TAG, "System time is not set, tm_year: %d", tm->tm_year);
}
}
}
esp_pm_lock_acquire(pm_lock_);
// 更新电池图标
int battery_level;
bool charging, discharging;
const char* icon = nullptr;
if (board.GetBatteryLevel(battery_level, charging, discharging)) {
if (charging) {
icon = FONT_AWESOME_BATTERY_BOLT;
} else {
const char* levels[] = {
FONT_AWESOME_BATTERY_EMPTY, // 0-19%
FONT_AWESOME_BATTERY_QUARTER, // 20-39%
FONT_AWESOME_BATTERY_HALF, // 40-59%
FONT_AWESOME_BATTERY_THREE_QUARTERS, // 60-79%
FONT_AWESOME_BATTERY_FULL, // 80-99%
FONT_AWESOME_BATTERY_FULL, // 100%
};
icon = levels[battery_level / 20];
}
DisplayLockGuard lock(this);
if (battery_label_ != nullptr && battery_icon_ != icon) {
battery_icon_ = icon;
lv_label_set_text(battery_label_, battery_icon_);
}
if (low_battery_popup_ != nullptr) {
if (strcmp(icon, FONT_AWESOME_BATTERY_EMPTY) == 0 && discharging) {
if (lv_obj_has_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN)) { // 如果低电量提示框隐藏,则显示
lv_obj_remove_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN);
app.PlaySound(Lang::Sounds::OGG_LOW_BATTERY);
}
} else {
// Hide the low battery popup when the battery is not empty
if (!lv_obj_has_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN)) { // 如果低电量提示框显示,则隐藏
lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN);
}
}
}
}
// 每 10 秒更新一次网络图标
static int seconds_counter = 0;
if (update_all || seconds_counter++ % 10 == 0) {
// 升级固件时,不读取 4G 网络状态,避免占用 UART 资源
auto device_state = Application::GetInstance().GetDeviceState();
static const std::vector<DeviceState> allowed_states = {
kDeviceStateIdle,
kDeviceStateStarting,
kDeviceStateWifiConfiguring,
kDeviceStateListening,
kDeviceStateActivating,
};
if (std::find(allowed_states.begin(), allowed_states.end(), device_state) != allowed_states.end()) {
icon = board.GetNetworkStateIcon();
if (network_label_ != nullptr && icon != nullptr && network_icon_ != icon) {
DisplayLockGuard lock(this);
network_icon_ = icon;
lv_label_set_text(network_label_, network_icon_);
}
}
}
esp_pm_lock_release(pm_lock_);
}
void LvglDisplay::SetPreviewImage(const lv_img_dsc_t* image) {
// Do nothing but free the image
if (image != nullptr) {
heap_caps_free((void*)image->data);
heap_caps_free((void*)image);
}
}
void LvglDisplay::SetPowerSaveMode(bool on) {
if (on) {
SetChatMessage("system", "");
SetEmotion("sleepy");
} else {
SetChatMessage("system", "");
SetEmotion("neutral");
}
}

View File

@@ -0,0 +1,51 @@
#ifndef LVGL_DISPLAY_H
#define LVGL_DISPLAY_H
#include "display.h"
#include <lvgl.h>
#include <esp_timer.h>
#include <esp_log.h>
#include <esp_pm.h>
#include <string>
#include <chrono>
class LvglDisplay : public Display {
public:
LvglDisplay();
virtual ~LvglDisplay();
virtual void SetStatus(const char* status);
virtual void ShowNotification(const char* notification, int duration_ms = 3000);
virtual void ShowNotification(const std::string &notification, int duration_ms = 3000);
virtual void SetPreviewImage(const lv_img_dsc_t* image);
virtual void UpdateStatusBar(bool update_all = false);
virtual void SetPowerSaveMode(bool on);
protected:
esp_pm_lock_handle_t pm_lock_ = nullptr;
lv_display_t *display_ = nullptr;
lv_obj_t *network_label_ = nullptr;
lv_obj_t *status_label_ = nullptr;
lv_obj_t *notification_label_ = nullptr;
lv_obj_t *mute_label_ = nullptr;
lv_obj_t *battery_label_ = nullptr;
lv_obj_t* low_battery_popup_ = nullptr;
lv_obj_t* low_battery_label_ = nullptr;
const char* battery_icon_ = nullptr;
const char* network_icon_ = nullptr;
bool muted_ = false;
std::chrono::system_clock::time_point last_status_update_time_;
esp_timer_handle_t notification_timer_ = nullptr;
friend class DisplayLockGuard;
virtual bool Lock(int timeout_ms = 0) = 0;
virtual void Unlock() = 0;
};
#endif

View File

@@ -0,0 +1,13 @@
#include "lvgl_font.h"
#include <cbin_font.h>
LvglCBinFont::LvglCBinFont(void* data) {
font_ = cbin_font_create(static_cast<uint8_t*>(data));
}
LvglCBinFont::~LvglCBinFont() {
if (font_ != nullptr) {
cbin_font_delete(font_);
}
}

View File

@@ -0,0 +1,31 @@
#pragma once
#include <lvgl.h>
class LvglFont {
public:
virtual const lv_font_t* font() const = 0;
virtual ~LvglFont() = default;
};
// Built-in font
class LvglBuiltInFont : public LvglFont {
public:
LvglBuiltInFont(const lv_font_t* font) : font_(font) {}
virtual const lv_font_t* font() const override { return font_; }
private:
const lv_font_t* font_;
};
class LvglCBinFont : public LvglFont {
public:
LvglCBinFont(void* data);
virtual ~LvglCBinFont();
virtual const lv_font_t* font() const override { return font_; }
private:
lv_font_t* font_;
};

View File

@@ -0,0 +1,33 @@
#include "lvgl_image.h"
#include <cbin_font.h>
#include <esp_log.h>
#include <cstring>
#define TAG "LvglImage"
LvglRawImage::LvglRawImage(void* data, size_t size) {
bzero(&image_dsc_, sizeof(image_dsc_));
image_dsc_.header.magic = LV_IMAGE_HEADER_MAGIC;
image_dsc_.header.cf = LV_COLOR_FORMAT_RAW_ALPHA;
image_dsc_.header.w = 0;
image_dsc_.header.h = 0;
image_dsc_.data_size = size;
image_dsc_.data = static_cast<uint8_t*>(data);
}
bool LvglRawImage::IsGif() const {
auto ptr = (const uint8_t*)image_dsc_.data;
return ptr[0] == 'G' && ptr[1] == 'I' && ptr[2] == 'F';
}
LvglCBinImage::LvglCBinImage(void* data) {
image_dsc_ = cbin_img_dsc_create(static_cast<uint8_t*>(data));
}
LvglCBinImage::~LvglCBinImage() {
if (image_dsc_ != nullptr) {
cbin_img_dsc_delete(image_dsc_);
}
}

View File

@@ -0,0 +1,42 @@
#pragma once
#include <lvgl.h>
// Wrap around lv_img_dsc_t
class LvglImage {
public:
virtual const lv_img_dsc_t* image_dsc() const = 0;
virtual bool IsGif() const { return false; }
virtual ~LvglImage() = default;
};
class LvglRawImage : public LvglImage {
public:
LvglRawImage(void* data, size_t size);
virtual const lv_img_dsc_t* image_dsc() const override { return &image_dsc_; }
virtual bool IsGif() const;
private:
lv_img_dsc_t image_dsc_;
};
class LvglCBinImage : public LvglImage {
public:
LvglCBinImage(void* data);
virtual ~LvglCBinImage();
virtual const lv_img_dsc_t* image_dsc() const override { return image_dsc_; }
private:
lv_img_dsc_t* image_dsc_ = nullptr;
};
class LvglSourceImage : public LvglImage {
public:
LvglSourceImage(const lv_img_dsc_t* image_dsc) : image_dsc_(image_dsc) {}
virtual const lv_img_dsc_t* image_dsc() const override { return image_dsc_; }
private:
const lv_img_dsc_t* image_dsc_;
};

View File

@@ -0,0 +1,30 @@
#include "lvgl_theme.h"
LvglTheme::LvglTheme(const std::string& name) : Theme(name) {
}
lv_color_t LvglTheme::ParseColor(const std::string& color) {
if (color.find("#") == 0) {
// Convert #112233 to lv_color_t
uint8_t r = strtol(color.substr(1, 2).c_str(), nullptr, 16);
uint8_t g = strtol(color.substr(3, 2).c_str(), nullptr, 16);
uint8_t b = strtol(color.substr(5, 2).c_str(), nullptr, 16);
return lv_color_make(r, g, b);
}
return lv_color_black();
}
LvglThemeManager::LvglThemeManager() {
}
LvglTheme* LvglThemeManager::GetTheme(const std::string& theme_name) {
auto it = themes_.find(theme_name);
if (it != themes_.end()) {
return it->second;
}
return nullptr;
}
void LvglThemeManager::RegisterTheme(const std::string& theme_name, LvglTheme* theme) {
themes_[theme_name] = theme;
}

View File

@@ -0,0 +1,94 @@
#pragma once
#include "display.h"
#include "lvgl_image.h"
#include "lvgl_font.h"
#include "emoji_collection.h"
#include <lvgl.h>
#include <memory>
#include <map>
#include <string>
class LvglTheme : public Theme {
public:
static lv_color_t ParseColor(const std::string& color);
LvglTheme(const std::string& name);
// Properties
inline lv_color_t background_color() const { return background_color_; }
inline lv_color_t text_color() const { return text_color_; }
inline lv_color_t chat_background_color() const { return chat_background_color_; }
inline lv_color_t user_bubble_color() const { return user_bubble_color_; }
inline lv_color_t assistant_bubble_color() const { return assistant_bubble_color_; }
inline lv_color_t system_bubble_color() const { return system_bubble_color_; }
inline lv_color_t system_text_color() const { return system_text_color_; }
inline lv_color_t border_color() const { return border_color_; }
inline lv_color_t low_battery_color() const { return low_battery_color_; }
inline std::shared_ptr<LvglImage> background_image() const { return background_image_; }
inline std::shared_ptr<EmojiCollection> emoji_collection() const { return emoji_collection_; }
inline std::shared_ptr<LvglFont> text_font() const { return text_font_; }
inline std::shared_ptr<LvglFont> icon_font() const { return icon_font_; }
inline std::shared_ptr<LvglFont> large_icon_font() const { return large_icon_font_; }
inline int spacing(int scale) const { return spacing_ * scale; }
inline void set_background_color(lv_color_t background) { background_color_ = background; }
inline void set_text_color(lv_color_t text) { text_color_ = text; }
inline void set_chat_background_color(lv_color_t chat_background) { chat_background_color_ = chat_background; }
inline void set_user_bubble_color(lv_color_t user_bubble) { user_bubble_color_ = user_bubble; }
inline void set_assistant_bubble_color(lv_color_t assistant_bubble) { assistant_bubble_color_ = assistant_bubble; }
inline void set_system_bubble_color(lv_color_t system_bubble) { system_bubble_color_ = system_bubble; }
inline void set_system_text_color(lv_color_t system_text) { system_text_color_ = system_text; }
inline void set_border_color(lv_color_t border) { border_color_ = border; }
inline void set_low_battery_color(lv_color_t low_battery) { low_battery_color_ = low_battery; }
inline void set_background_image(std::shared_ptr<LvglImage> background_image) { background_image_ = background_image; }
inline void set_emoji_collection(std::shared_ptr<EmojiCollection> emoji_collection) { emoji_collection_ = emoji_collection; }
inline void set_text_font(std::shared_ptr<LvglFont> text_font) { text_font_ = text_font; }
inline void set_icon_font(std::shared_ptr<LvglFont> icon_font) { icon_font_ = icon_font; }
inline void set_large_icon_font(std::shared_ptr<LvglFont> large_icon_font) { large_icon_font_ = large_icon_font; }
private:
int spacing_ = 2;
// Colors
lv_color_t background_color_;
lv_color_t text_color_;
lv_color_t chat_background_color_;
lv_color_t user_bubble_color_;
lv_color_t assistant_bubble_color_;
lv_color_t system_bubble_color_;
lv_color_t system_text_color_;
lv_color_t border_color_;
lv_color_t low_battery_color_;
// Background image
std::shared_ptr<LvglImage> background_image_ = nullptr;
// fonts
std::shared_ptr<LvglFont> text_font_ = nullptr;
std::shared_ptr<LvglFont> icon_font_ = nullptr;
std::shared_ptr<LvglFont> large_icon_font_ = nullptr;
// Emoji collection
std::shared_ptr<EmojiCollection> emoji_collection_ = nullptr;
};
class LvglThemeManager {
public:
static LvglThemeManager& GetInstance() {
static LvglThemeManager instance;
return instance;
}
void RegisterTheme(const std::string& theme_name, LvglTheme* theme);
LvglTheme* GetTheme(const std::string& theme_name);
private:
LvglThemeManager();
void InitializeDefaultThemes();
std::map<std::string, LvglTheme*> themes_;
};