Update to 2.0.0
This commit is contained in:
123
main/display/lvgl_display/emoji_collection.cc
Normal file
123
main/display/lvgl_display/emoji_collection.cc
Normal 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));
|
||||
}
|
||||
34
main/display/lvgl_display/emoji_collection.h
Normal file
34
main/display/lvgl_display/emoji_collection.h
Normal 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
|
||||
2
main/display/lvgl_display/gif/LICENSE.txt
Normal file
2
main/display/lvgl_display/gif/LICENSE.txt
Normal 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.
|
||||
818
main/display/lvgl_display/gif/gifdec.c
Normal file
818
main/display/lvgl_display/gif/gifdec.c
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
68
main/display/lvgl_display/gif/gifdec.h
Normal file
68
main/display/lvgl_display/gif/gifdec.h
Normal 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 */
|
||||
140
main/display/lvgl_display/gif/gifdec_mve.h
Normal file
140
main/display/lvgl_display/gif/gifdec_mve.h
Normal 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*/
|
||||
207
main/display/lvgl_display/gif/lvgl_gif.cc
Normal file
207
main/display/lvgl_display/gif/lvgl_gif.cc
Normal 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_));
|
||||
}
|
||||
101
main/display/lvgl_display/gif/lvgl_gif.h
Normal file
101
main/display/lvgl_display/gif/lvgl_gif.h
Normal 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();
|
||||
};
|
||||
220
main/display/lvgl_display/lvgl_display.cc
Normal file
220
main/display/lvgl_display/lvgl_display.cc
Normal 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(¬ification_timer_args, ¬ification_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 ¬ification, 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");
|
||||
}
|
||||
}
|
||||
51
main/display/lvgl_display/lvgl_display.h
Normal file
51
main/display/lvgl_display/lvgl_display.h
Normal 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 ¬ification, 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
|
||||
13
main/display/lvgl_display/lvgl_font.cc
Normal file
13
main/display/lvgl_display/lvgl_font.cc
Normal 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_);
|
||||
}
|
||||
}
|
||||
31
main/display/lvgl_display/lvgl_font.h
Normal file
31
main/display/lvgl_display/lvgl_font.h
Normal 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_;
|
||||
};
|
||||
33
main/display/lvgl_display/lvgl_image.cc
Normal file
33
main/display/lvgl_display/lvgl_image.cc
Normal 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_);
|
||||
}
|
||||
}
|
||||
42
main/display/lvgl_display/lvgl_image.h
Normal file
42
main/display/lvgl_display/lvgl_image.h
Normal 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_;
|
||||
};
|
||||
30
main/display/lvgl_display/lvgl_theme.cc
Normal file
30
main/display/lvgl_display/lvgl_theme.cc
Normal 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;
|
||||
}
|
||||
94
main/display/lvgl_display/lvgl_theme.h
Normal file
94
main/display/lvgl_display/lvgl_theme.h
Normal 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_;
|
||||
};
|
||||
Reference in New Issue
Block a user