Upgrade Playlist Features

This commit is contained in:
2025-12-09 17:20:01 +08:00
parent 577990de69
commit 8bd2780688
683 changed files with 91812 additions and 81260 deletions

View File

@@ -1,58 +1,100 @@
#include <esp_log.h>
#include <esp_err.h>
#include <string>
#include <cstdlib>
#include <cstring>
#include <font_awesome.h>
#include "display.h"
#include "board.h"
#include "application.h"
#include "audio_codec.h"
#include "settings.h"
#include "assets/lang_config.h"
#define TAG "Display"
Display::Display() {
}
Display::~Display() {
}
void Display::SetStatus(const char* status) {
ESP_LOGW(TAG, "SetStatus: %s", status);
}
void Display::ShowNotification(const std::string &notification, int duration_ms) {
ShowNotification(notification.c_str(), duration_ms);
}
void Display::ShowNotification(const char* notification, int duration_ms) {
ESP_LOGW(TAG, "ShowNotification: %s", notification);
}
void Display::UpdateStatusBar(bool update_all) {
}
void Display::SetEmotion(const char* emotion) {
ESP_LOGW(TAG, "SetEmotion: %s", emotion);
}
void Display::SetChatMessage(const char* role, const char* content) {
ESP_LOGW(TAG, "Role:%s", role);
ESP_LOGW(TAG, " %s", content);
}
void Display::SetTheme(Theme* theme) {
current_theme_ = theme;
if (theme != nullptr) {
Settings settings("display", true);
settings.SetString("theme", theme->name());
}
}
void Display::SetPowerSaveMode(bool on) {
ESP_LOGW(TAG, "SetPowerSaveMode: %d", on);
}
#include <esp_log.h>
#include <esp_err.h>
#include <string>
#include <cstdlib>
#include <cstring>
#include <font_awesome.h>
#include "display.h"
#include "board.h"
#include "application.h"
#include "audio_codec.h"
#include "settings.h"
#include "assets/lang_config.h"
#define TAG "Display"
Display::Display() {
}
Display::~Display() {
}
void Display::SetStatus(const char* status) {
ESP_LOGW(TAG, "SetStatus: %s", status);
}
void Display::ShowNotification(const std::string &notification, int duration_ms) {
ShowNotification(notification.c_str(), duration_ms);
}
void Display::ShowNotification(const char* notification, int duration_ms) {
ESP_LOGW(TAG, "ShowNotification: %s", notification);
}
void Display::UpdateStatusBar(bool update_all) {
}
void Display::SetEmotion(const char* emotion) {
ESP_LOGW(TAG, "SetEmotion: %s", emotion);
}
void Display::SetChatMessage(const char* role, const char* content) {
ESP_LOGW(TAG, "Role:%s", role);
ESP_LOGW(TAG, " %s", content);
}
void Display::SetTheme(Theme* theme) {
current_theme_ = theme;
Settings settings("display", true);
settings.SetString("theme", theme->name());
}
void Display::SetPowerSaveMode(bool on) {
ESP_LOGW(TAG, "SetPowerSaveMode: %d", on);
}
void Display::SetMusicInfo(const char* song_name) {
// 默认实现:对于非微信模式,将歌名显示在聊天消息标签中
DisplayLockGuard lock(this);
if (chat_message_label_ == nullptr) {
return;
}
if (song_name != nullptr && strlen(song_name) > 0) {
std::string music_text = "";
music_text += song_name;
lv_label_set_text(chat_message_label_, music_text.c_str());
SetEmotion(FONT_AWESOME_MUSIC);
} else {
lv_label_set_text(chat_message_label_, "");
SetEmotion(FONT_AWESOME_NEUTRAL);
}
}
void Display::SetMusicProgress(const char* song_name, int current_seconds, int total_seconds, float progress_percent) {
// 默认实现:简化显示,只显示歌名和时间
char time_info[64];
int current_min = current_seconds / 60;
int current_sec = current_seconds % 60;
int total_min = total_seconds / 60;
int total_sec = total_seconds % 60;
snprintf(time_info, sizeof(time_info), "%s %02d:%02d / %02d:%02d",
song_name ? song_name : "Unknown",
current_min, current_sec, total_min, total_sec);
DisplayLockGuard lock(this);
if (chat_message_label_ != nullptr) {
lv_label_set_text(chat_message_label_, time_info);
SetEmotion(FONT_AWESOME_MUSIC);
}
}
void Display::ClearMusicInfo() {
DisplayLockGuard lock(this);
if (chat_message_label_ != nullptr) {
lv_label_set_text(chat_message_label_, "");
SetEmotion(FONT_AWESOME_NEUTRAL);
}
}

View File

@@ -1,86 +1,98 @@
#ifndef DISPLAY_H
#define DISPLAY_H
#include "emoji_collection.h"
#ifdef LVGL_VERSION_MAJOR
#define HAVE_LVGL 1
#include <lvgl.h>
#endif
#include <esp_timer.h>
#include <esp_log.h>
#include <esp_pm.h>
#include <string>
#include <chrono>
class Theme {
public:
Theme(const std::string& name) : name_(name) {}
virtual ~Theme() = default;
inline std::string name() const { return name_; }
private:
std::string name_;
};
class Display {
public:
Display();
virtual ~Display();
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 SetEmotion(const char* emotion);
virtual void SetChatMessage(const char* role, const char* content);
virtual void SetTheme(Theme* theme);
virtual Theme* GetTheme() { return current_theme_; }
virtual void UpdateStatusBar(bool update_all = false);
virtual void SetPowerSaveMode(bool on);
// 音乐播放相关方法
virtual void SetMusicInfo(const char* info) {}
virtual void start() {}
virtual void stopFft() {}
inline int width() const { return width_; }
inline int height() const { return height_; }
protected:
int width_ = 0;
int height_ = 0;
Theme* current_theme_ = nullptr;
friend class DisplayLockGuard;
virtual bool Lock(int timeout_ms = 0) = 0;
virtual void Unlock() = 0;
};
class DisplayLockGuard {
public:
DisplayLockGuard(Display *display) : display_(display) {
if (!display_->Lock(30000)) {
ESP_LOGE("Display", "Failed to lock display");
}
}
~DisplayLockGuard() {
display_->Unlock();
}
private:
Display *display_;
};
class NoDisplay : public Display {
private:
virtual bool Lock(int timeout_ms = 0) override {
return true;
}
virtual void Unlock() override {}
};
#endif
#ifndef DISPLAY_H
#define DISPLAY_H
#include "emoji_collection.h"
#ifndef CONFIG_USE_EMOTE_MESSAGE_STYLE
#define HAVE_LVGL 1
#include <lvgl.h>
#endif
#include <esp_timer.h>
#include <esp_log.h>
#include <esp_pm.h>
#include <string>
#include <chrono>
class Theme {
public:
Theme(const std::string& name) : name_(name) {}
virtual ~Theme() = default;
inline std::string name() const { return name_; }
private:
std::string name_;
};
class Display {
public:
Display();
virtual ~Display();
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 SetEmotion(const char* emotion);
virtual void SetChatMessage(const char* role, const char* content);
virtual void SetTheme(Theme* theme);
virtual Theme* GetTheme() { return current_theme_; }
virtual void UpdateStatusBar(bool update_all = false);
virtual void SetPowerSaveMode(bool on);
virtual void SetMusicInfo(const char* song_name);
virtual void SetMusicProgress(const char* song_name, int current_seconds, int total_seconds, float progress_percent);
virtual void ClearMusicInfo();
// Alarm display on idle screen (for boards with idle screen support)
virtual void ShowAlarmOnIdleScreen(const char* alarm_message) {} // Default: do nothing
virtual void HideAlarmOnIdleScreen() {} // Default: do nothing
// State change and clock timer callbacks (for boards with idle screen support)
virtual void OnStateChanged() {} // Default: do nothing
virtual void OnClockTimer() {} // Default: do nothing, called every second
virtual void start() {}
virtual void clearScreen() {} // 清除FFT显示默认为空实现
virtual void stopFft() {} // 停止FFT显示默认为空实现
inline int width() const { return width_; }
inline int height() const { return height_; }
protected:
int width_ = 0;
int height_ = 0;
Theme* current_theme_ = nullptr;
friend class DisplayLockGuard;
virtual bool Lock(int timeout_ms = 0) = 0;
virtual void Unlock() = 0;
lv_obj_t* chat_message_label_ = nullptr;
lv_obj_t *emotion_label_ = nullptr;
};
class DisplayLockGuard {
public:
DisplayLockGuard(Display *display) : display_(display) {
if (!display_->Lock(30000)) {
ESP_LOGE("Display", "Failed to lock display");
}
}
~DisplayLockGuard() {
display_->Unlock();
}
private:
Display *display_;
};
class NoDisplay : public Display {
private:
virtual bool Lock(int timeout_ms = 0) override {
return true;
}
virtual void Unlock() override {}
};
#endif

View File

@@ -0,0 +1,655 @@
#include "emote_display.h"
// Standard C++ headers
#include <cstring>
#include <memory>
#include <unordered_map>
#include <tuple>
// Standard C headers
#include <sys/time.h>
#include <time.h>
// ESP-IDF headers
#include <esp_log.h>
#include <esp_lcd_panel_io.h>
#include <esp_timer.h>
// FreeRTOS headers
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
// Project headers
#include "assets.h"
#include "assets/lang_config.h"
#include "board.h"
#include "gfx.h"
LV_FONT_DECLARE(BUILTIN_TEXT_FONT);
namespace emote {
// ============================================================================
// Constants and Type Definitions
// ============================================================================
static const char* TAG = "EmoteDisplay";
// UI Element Names - Centralized Management
#define UI_ELEMENT_EYE_ANIM "eye_anim"
#define UI_ELEMENT_TOAST_LABEL "toast_label"
#define UI_ELEMENT_CLOCK_LABEL "clock_label"
#define UI_ELEMENT_LISTEN_ANIM "listen_anim"
#define UI_ELEMENT_STATUS_ICON "status_icon"
// Icon Names - Centralized Management
#define ICON_MIC "icon_mic"
#define ICON_BATTERY "icon_Battery"
#define ICON_SPEAKER_ZZZ "icon_speaker_zzz"
#define ICON_WIFI_FAILED "icon_WiFi_failed"
#define ICON_WIFI_OK "icon_wifi"
#define ICON_LISTEN "listen"
using FlushIoReadyCallback = std::function<bool(esp_lcd_panel_io_handle_t, esp_lcd_panel_io_event_data_t*, void*)>;
using FlushCallback = std::function<void(gfx_handle_t, int, int, int, int, const void*)>;
// ============================================================================
// Global Variables
// ============================================================================
// UI element management
static gfx_obj_t* g_obj_label_toast = nullptr;
static gfx_obj_t* g_obj_label_clock = nullptr;
static gfx_obj_t* g_obj_anim_eye = nullptr;
static gfx_obj_t* g_obj_anim_listen = nullptr;
static gfx_obj_t* g_obj_img_status = nullptr;
// Track current icon to determine when to show time
static std::string g_current_icon_type = ICON_WIFI_FAILED;
static gfx_image_dsc_t g_icon_img_dsc;
// ============================================================================
// Forward Declarations
// ============================================================================
class EmoteDisplay;
class EmoteEngine;
enum class UIDisplayMode : uint8_t {
SHOW_LISTENING = 1, // Show g_obj_anim_listen
SHOW_TIME = 2, // Show g_obj_label_clock
SHOW_TIPS = 3 // Show g_obj_label_toast
};
// ============================================================================
// Helper Functions
// ============================================================================
// Function to convert align string to GFX_ALIGN enum value
char StringToGfxAlign(const std::string &align_str)
{
static const std::unordered_map<std::string, char> align_map = {
{"GFX_ALIGN_DEFAULT", GFX_ALIGN_DEFAULT},
{"GFX_ALIGN_TOP_LEFT", GFX_ALIGN_TOP_LEFT},
{"GFX_ALIGN_TOP_MID", GFX_ALIGN_TOP_MID},
{"GFX_ALIGN_TOP_RIGHT", GFX_ALIGN_TOP_RIGHT},
{"GFX_ALIGN_LEFT_MID", GFX_ALIGN_LEFT_MID},
{"GFX_ALIGN_CENTER", GFX_ALIGN_CENTER},
{"GFX_ALIGN_RIGHT_MID", GFX_ALIGN_RIGHT_MID},
{"GFX_ALIGN_BOTTOM_LEFT", GFX_ALIGN_BOTTOM_LEFT},
{"GFX_ALIGN_BOTTOM_MID", GFX_ALIGN_BOTTOM_MID},
{"GFX_ALIGN_BOTTOM_RIGHT", GFX_ALIGN_BOTTOM_RIGHT},
{"GFX_ALIGN_OUT_TOP_LEFT", GFX_ALIGN_OUT_TOP_LEFT},
{"GFX_ALIGN_OUT_TOP_MID", GFX_ALIGN_OUT_TOP_MID},
{"GFX_ALIGN_OUT_TOP_RIGHT", GFX_ALIGN_OUT_TOP_RIGHT},
{"GFX_ALIGN_OUT_LEFT_TOP", GFX_ALIGN_OUT_LEFT_TOP},
{"GFX_ALIGN_OUT_LEFT_MID", GFX_ALIGN_OUT_LEFT_MID},
{"GFX_ALIGN_OUT_LEFT_BOTTOM", GFX_ALIGN_OUT_LEFT_BOTTOM},
{"GFX_ALIGN_OUT_RIGHT_TOP", GFX_ALIGN_OUT_RIGHT_TOP},
{"GFX_ALIGN_OUT_RIGHT_MID", GFX_ALIGN_OUT_RIGHT_MID},
{"GFX_ALIGN_OUT_RIGHT_BOTTOM", GFX_ALIGN_OUT_RIGHT_BOTTOM},
{"GFX_ALIGN_OUT_BOTTOM_LEFT", GFX_ALIGN_OUT_BOTTOM_LEFT},
{"GFX_ALIGN_OUT_BOTTOM_MID", GFX_ALIGN_OUT_BOTTOM_MID},
{"GFX_ALIGN_OUT_BOTTOM_RIGHT", GFX_ALIGN_OUT_BOTTOM_RIGHT}
};
const auto it = align_map.find(align_str);
if (it != align_map.cend()) {
return it->second;
}
ESP_LOGW(TAG, "Unknown align string: %s, using GFX_ALIGN_DEFAULT", align_str.c_str());
return GFX_ALIGN_DEFAULT;
}
// ============================================================================
// EmoteEngine Class Declaration
// ============================================================================
class EmoteEngine {
public:
EmoteEngine(const esp_lcd_panel_handle_t panel, const esp_lcd_panel_io_handle_t panel_io,
const int width, const int height, EmoteDisplay* const display);
~EmoteEngine();
void SetEyes(const std::string &emoji_name, const bool repeat, const int fps, EmoteDisplay* const display);
void SetIcon(const std::string &icon_name, EmoteDisplay* const display);
void* GetEngineHandle() const
{
return engine_handle_;
}
// Callback functions (public to be accessible from static helper functions)
static bool OnFlushIoReady(const esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t* const edata, void* const user_ctx);
static void OnFlush(const gfx_handle_t handle, const int x_start, const int y_start, const int x_end, const int y_end, const void* const color_data);
private:
gfx_handle_t engine_handle_;
};
// ============================================================================
// UI Management Functions
// ============================================================================
static void SetUIDisplayMode(const UIDisplayMode mode, EmoteDisplay* const display)
{
if (!display) {
ESP_LOGE(TAG, "SetUIDisplayMode: display is nullptr");
return;
}
gfx_obj_set_visible(g_obj_anim_listen, false);
gfx_obj_set_visible(g_obj_label_clock, false);
gfx_obj_set_visible(g_obj_label_toast, false);
// Show the selected control
switch (mode) {
case UIDisplayMode::SHOW_LISTENING: {
gfx_obj_set_visible(g_obj_anim_listen, true);
const AssetData emoji_data = display->GetIconData(ICON_LISTEN);
if (emoji_data.data) {
gfx_anim_set_src(g_obj_anim_listen, emoji_data.data, emoji_data.size);
gfx_anim_set_segment(g_obj_anim_listen, 0, 0xFFFF, 20, true);
gfx_anim_start(g_obj_anim_listen);
}
break;
}
case UIDisplayMode::SHOW_TIME:
gfx_obj_set_visible(g_obj_label_clock, true);
break;
case UIDisplayMode::SHOW_TIPS:
gfx_obj_set_visible(g_obj_label_toast, true);
break;
}
}
// ============================================================================
// Graphics Initialization Functions
// ============================================================================
static void InitializeGraphics(const esp_lcd_panel_handle_t panel, gfx_handle_t* const engine_handle,
const int width, const int height)
{
if (!panel || !engine_handle) {
ESP_LOGE(TAG, "InitializeGraphics: Invalid parameters");
return;
}
gfx_core_config_t gfx_cfg = {
.flush_cb = EmoteEngine::OnFlush,
.user_data = panel,
.flags = {
.swap = true,
.double_buffer = true,
.buff_dma = true,
},
.h_res = static_cast<uint32_t>(width),
.v_res = static_cast<uint32_t>(height),
.fps = 30,
.buffers = {
.buf1 = nullptr,
.buf2 = nullptr,
.buf_pixels = static_cast<size_t>(width * 16),
},
.task = GFX_EMOTE_INIT_CONFIG()
};
gfx_cfg.task.task_stack_caps = MALLOC_CAP_DEFAULT;
gfx_cfg.task.task_affinity = 0;
gfx_cfg.task.task_priority = 5;
gfx_cfg.task.task_stack = 8 * 1024;
*engine_handle = gfx_emote_init(&gfx_cfg);
}
static void SetupUI(const gfx_handle_t engine_handle, EmoteDisplay* const display)
{
if (!display) {
ESP_LOGE(TAG, "SetupUI: display is nullptr");
return;
}
gfx_emote_set_bg_color(engine_handle, GFX_COLOR_HEX(0x000000));
g_obj_anim_eye = gfx_anim_create(engine_handle);
gfx_obj_align(g_obj_anim_eye, GFX_ALIGN_LEFT_MID, 10, 30);
gfx_anim_set_auto_mirror(g_obj_anim_eye, true);
gfx_obj_set_visible(g_obj_anim_eye, false);
g_obj_label_toast = gfx_label_create(engine_handle);
gfx_obj_align(g_obj_label_toast, GFX_ALIGN_TOP_MID, 0, 20);
gfx_obj_set_size(g_obj_label_toast, 200, 40);
gfx_label_set_text(g_obj_label_toast, Lang::Strings::INITIALIZING);
gfx_label_set_color(g_obj_label_toast, GFX_COLOR_HEX(0xFFFFFF));
gfx_label_set_text_align(g_obj_label_toast, GFX_TEXT_ALIGN_CENTER);
gfx_label_set_long_mode(g_obj_label_toast, GFX_LABEL_LONG_SCROLL);
gfx_label_set_scroll_speed(g_obj_label_toast, 20);
gfx_label_set_scroll_loop(g_obj_label_toast, true);
gfx_label_set_font(g_obj_label_toast, (gfx_font_t)&BUILTIN_TEXT_FONT);
g_obj_label_clock = gfx_label_create(engine_handle);
gfx_obj_align(g_obj_label_clock, GFX_ALIGN_TOP_MID, 0, 15);
gfx_obj_set_size(g_obj_label_clock, 200, 50);
gfx_label_set_text(g_obj_label_clock, "--:--");
gfx_label_set_color(g_obj_label_clock, GFX_COLOR_HEX(0xFFFFFF));
gfx_label_set_text_align(g_obj_label_clock, GFX_TEXT_ALIGN_CENTER);
gfx_label_set_font(g_obj_label_clock, (gfx_font_t)&BUILTIN_TEXT_FONT);
g_obj_anim_listen = gfx_anim_create(engine_handle);
gfx_obj_align(g_obj_anim_listen, GFX_ALIGN_TOP_MID, 0, 5);
gfx_anim_start(g_obj_anim_listen);
gfx_obj_set_visible(g_obj_anim_listen, false);
g_obj_img_status = gfx_img_create(engine_handle);
gfx_obj_align(g_obj_img_status, GFX_ALIGN_TOP_MID, -120, 18);
SetUIDisplayMode(UIDisplayMode::SHOW_TIPS, display);
}
static void RegisterCallbacks(const esp_lcd_panel_io_handle_t panel_io, const gfx_handle_t engine_handle)
{
if (!panel_io) {
ESP_LOGE(TAG, "RegisterCallbacks: panel_io is nullptr");
return;
}
const esp_lcd_panel_io_callbacks_t cbs = {
.on_color_trans_done = EmoteEngine::OnFlushIoReady,
};
esp_lcd_panel_io_register_event_callbacks(panel_io, &cbs, engine_handle);
}
// ============================================================================
// EmoteEngine Class Implementation
// ============================================================================
EmoteEngine::EmoteEngine(const esp_lcd_panel_handle_t panel, const esp_lcd_panel_io_handle_t panel_io,
const int width, const int height, EmoteDisplay* const display)
{
InitializeGraphics(panel, &engine_handle_, width, height);
if (display) {
gfx_emote_lock(engine_handle_);
SetupUI(engine_handle_, display);
gfx_emote_unlock(engine_handle_);
}
RegisterCallbacks(panel_io, engine_handle_);
}
EmoteEngine::~EmoteEngine()
{
if (engine_handle_) {
gfx_emote_deinit(engine_handle_);
engine_handle_ = nullptr;
}
}
void EmoteEngine::SetEyes(const std::string &emoji_name, const bool repeat, const int fps, EmoteDisplay* const display)
{
if (!engine_handle_) {
ESP_LOGE(TAG, "SetEyes: engine_handle_ is nullptr");
return;
}
if (!display) {
ESP_LOGE(TAG, "SetEyes: display is nullptr");
return;
}
const AssetData emoji_data = display->GetEmojiData(emoji_name);
if (emoji_data.data) {
DisplayLockGuard lock(display);
gfx_anim_set_src(g_obj_anim_eye, emoji_data.data, emoji_data.size);
gfx_anim_set_segment(g_obj_anim_eye, 0, 0xFFFF, fps, repeat);
gfx_obj_set_visible(g_obj_anim_eye, true);
gfx_anim_start(g_obj_anim_eye);
} else {
ESP_LOGW(TAG, "SetEyes: No emoji data found for %s", emoji_name.c_str());
}
}
void EmoteEngine::SetIcon(const std::string &icon_name, EmoteDisplay* const display)
{
if (!engine_handle_) {
ESP_LOGE(TAG, "SetIcon: engine_handle_ is nullptr");
return;
}
if (!display) {
ESP_LOGE(TAG, "SetIcon: display is nullptr");
return;
}
const AssetData icon_data = display->GetIconData(icon_name);
if (icon_data.data) {
DisplayLockGuard lock(display);
std::memcpy(&g_icon_img_dsc.header, icon_data.data, sizeof(gfx_image_header_t));
g_icon_img_dsc.data = static_cast<const uint8_t*>(icon_data.data) + sizeof(gfx_image_header_t);
g_icon_img_dsc.data_size = icon_data.size - sizeof(gfx_image_header_t);
gfx_img_set_src(g_obj_img_status, &g_icon_img_dsc);
} else {
ESP_LOGW(TAG, "SetIcon: No icon data found for %s", icon_name.c_str());
}
g_current_icon_type = icon_name;
}
bool EmoteEngine::OnFlushIoReady(const esp_lcd_panel_io_handle_t panel_io,
esp_lcd_panel_io_event_data_t* const edata,
void* const user_ctx)
{
return true;
}
void EmoteEngine::OnFlush(const gfx_handle_t handle, const int x_start, const int y_start,
const int x_end, const int y_end, const void* const color_data)
{
auto* const panel = static_cast<esp_lcd_panel_handle_t>(gfx_emote_get_user_data(handle));
if (panel) {
esp_lcd_panel_draw_bitmap(panel, x_start, y_start, x_end, y_end, color_data);
}
gfx_emote_flush_ready(handle, true);
}
// ============================================================================
// EmoteDisplay Class Implementation
// ============================================================================
EmoteDisplay::EmoteDisplay(const esp_lcd_panel_handle_t panel, const esp_lcd_panel_io_handle_t panel_io,
const int width, const int height)
{
InitializeEngine(panel, panel_io, width, height);
}
EmoteDisplay::~EmoteDisplay() = default;
void EmoteDisplay::SetEmotion(const char* const emotion)
{
if (!emotion) {
ESP_LOGE(TAG, "SetEmotion: emotion is nullptr");
return;
}
ESP_LOGI(TAG, "SetEmotion: %s", emotion);
if (!engine_) {
return;
}
const AssetData emoji_data = GetEmojiData(emotion);
bool repeat = emoji_data.loop;
int fps = emoji_data.fps > 0 ? emoji_data.fps : 20;
if (std::strcmp(emotion, "idle") == 0 || std::strcmp(emotion, "neutral") == 0) {
repeat = false;
}
DisplayLockGuard lock(this);
engine_->SetEyes(emotion, repeat, fps, this);
}
void EmoteDisplay::SetChatMessage(const char* const role, const char* const content)
{
if (!engine_) {
return;
}
DisplayLockGuard lock(this);
if (content && strlen(content) > 0) {
gfx_label_set_text(g_obj_label_toast, content);
SetUIDisplayMode(UIDisplayMode::SHOW_TIPS, this);
}
}
void EmoteDisplay::SetStatus(const char* const status)
{
if (!status) {
ESP_LOGE(TAG, "SetStatus: status is nullptr");
return;
}
if (!engine_) {
return;
}
DisplayLockGuard lock(this);
if (std::strcmp(status, Lang::Strings::LISTENING) == 0) {
SetUIDisplayMode(UIDisplayMode::SHOW_LISTENING, this);
engine_->SetEyes("happy", true, 20, this);
engine_->SetIcon(ICON_MIC, this);
} else if (std::strcmp(status, Lang::Strings::STANDBY) == 0) {
SetUIDisplayMode(UIDisplayMode::SHOW_TIME, this);
engine_->SetIcon(ICON_BATTERY, this);
} else if (std::strcmp(status, Lang::Strings::SPEAKING) == 0) {
SetUIDisplayMode(UIDisplayMode::SHOW_TIPS, this);
engine_->SetIcon(ICON_SPEAKER_ZZZ, this);
} else if (std::strcmp(status, Lang::Strings::ERROR) == 0) {
SetUIDisplayMode(UIDisplayMode::SHOW_TIPS, this);
engine_->SetIcon(ICON_WIFI_FAILED, this);
}
if (std::strcmp(status, Lang::Strings::CONNECTING) != 0) {
gfx_label_set_text(g_obj_label_toast, status);
}
}
void EmoteDisplay::ShowNotification(const char* notification, int duration_ms)
{
if (!notification || !engine_) {
return;
}
ESP_LOGI(TAG, "ShowNotification: %s", notification);
DisplayLockGuard lock(this);
gfx_label_set_text(g_obj_label_toast, notification);
SetUIDisplayMode(UIDisplayMode::SHOW_TIPS, this);
}
void EmoteDisplay::UpdateStatusBar(bool update_all)
{
if (!engine_) {
return;
}
// Only display time when battery icon is shown
DisplayLockGuard lock(this);
if (g_current_icon_type == ICON_BATTERY) {
time_t now;
struct tm timeinfo;
time(&now);
setenv("TZ", "GMT+0", 1);
tzset();
localtime_r(&now, &timeinfo);
char time_str[6];
snprintf(time_str, sizeof(time_str), "%02d:%02d", timeinfo.tm_hour, timeinfo.tm_min);
DisplayLockGuard lock(this);
gfx_label_set_text(g_obj_label_clock, time_str);
SetUIDisplayMode(UIDisplayMode::SHOW_TIME, this);
}
}
void EmoteDisplay::SetPowerSaveMode(bool on)
{
if (!engine_) {
return;
}
DisplayLockGuard lock(this);
ESP_LOGI(TAG, "SetPowerSaveMode: %s", on ? "ON" : "OFF");
if (on) {
gfx_anim_stop(g_obj_anim_eye);
} else {
gfx_anim_start(g_obj_anim_eye);
}
}
void EmoteDisplay::SetPreviewImage(const void* image)
{
if (image) {
ESP_LOGI(TAG, "SetPreviewImage: Preview image not supported, using default icon");
if (engine_) {
}
}
}
void EmoteDisplay::SetTheme(Theme* const theme)
{
ESP_LOGI(TAG, "SetTheme: %p", theme);
}
void EmoteDisplay::AddEmojiData(const std::string &name, const void* const data, const size_t size,
uint8_t fps, bool loop, bool lack)
{
emoji_data_map_[name] = AssetData(data, size, fps, loop, lack);
ESP_LOGD(TAG, "Added emoji data: %s, size: %d, fps: %d, loop: %s, lack: %s",
name.c_str(), size, fps, loop ? "true" : "false", lack ? "true" : "false");
DisplayLockGuard lock(this);
if (name == "happy") {
engine_->SetEyes("happy", loop, fps > 0 ? fps : 20, this);
}
}
void EmoteDisplay::AddIconData(const std::string &name, const void* const data, const size_t size)
{
icon_data_map_[name] = AssetData(data, size);
ESP_LOGD(TAG, "Added icon data: %s, size: %d", name.c_str(), size);
DisplayLockGuard lock(this);
if (name == ICON_WIFI_FAILED) {
SetUIDisplayMode(UIDisplayMode::SHOW_TIPS, this);
engine_->SetIcon(ICON_WIFI_FAILED, this);
}
}
void EmoteDisplay::AddLayoutData(const std::string &name, const std::string &align_str,
const int x, const int y, const int width, const int height)
{
const char align_enum = StringToGfxAlign(align_str);
ESP_LOGI(TAG, "layout: %-12s | %-20s(%d) | %4d, %4d | %4dx%-4d",
name.c_str(), align_str.c_str(), align_enum, x, y, width, height);
struct UIElement {
gfx_obj_t* obj;
const char* name;
};
const UIElement elements[] = {
{g_obj_anim_eye, UI_ELEMENT_EYE_ANIM},
{g_obj_label_toast, UI_ELEMENT_TOAST_LABEL},
{g_obj_label_clock, UI_ELEMENT_CLOCK_LABEL},
{g_obj_anim_listen, UI_ELEMENT_LISTEN_ANIM},
{g_obj_img_status, UI_ELEMENT_STATUS_ICON}
};
DisplayLockGuard lock(this);
for (const auto &element : elements) {
if (name == element.name && element.obj) {
gfx_obj_align(element.obj, align_enum, x, y);
if (width > 0 && height > 0) {
gfx_obj_set_size(element.obj, width, height);
}
return;
}
}
ESP_LOGW(TAG, "AddLayoutData: UI element '%s' not found", name.c_str());
}
void EmoteDisplay::AddTextFont(std::shared_ptr<LvglFont> text_font)
{
if (!text_font) {
ESP_LOGW(TAG, "AddTextFont: text_font is nullptr");
return;
}
text_font_ = text_font;
ESP_LOGD(TAG, "AddTextFont: Text font added successfully");
DisplayLockGuard lock(this);
if (g_obj_label_toast && text_font_) {
gfx_label_set_font(g_obj_label_toast, const_cast<void*>(static_cast<const void*>(text_font_->font())));
}
if (g_obj_label_clock && text_font_) {
gfx_label_set_font(g_obj_label_clock, const_cast<void*>(static_cast<const void*>(text_font_->font())));
}
}
AssetData EmoteDisplay::GetEmojiData(const std::string &name) const
{
const auto it = emoji_data_map_.find(name);
if (it != emoji_data_map_.cend()) {
return it->second;
}
return AssetData();
}
AssetData EmoteDisplay::GetIconData(const std::string &name) const
{
const auto it = icon_data_map_.find(name);
if (it != icon_data_map_.cend()) {
return it->second;
}
return AssetData();
}
EmoteEngine* EmoteDisplay::GetEngine() const
{
return engine_.get();
}
void* EmoteDisplay::GetEngineHandle() const
{
return engine_ ? engine_->GetEngineHandle() : nullptr;
}
void EmoteDisplay::InitializeEngine(const esp_lcd_panel_handle_t panel, const esp_lcd_panel_io_handle_t panel_io,
const int width, const int height)
{
engine_ = std::make_unique<EmoteEngine>(panel, panel_io, width, height, this);
}
bool EmoteDisplay::Lock(const int timeout_ms)
{
if (engine_ && engine_->GetEngineHandle()) {
gfx_emote_lock(engine_->GetEngineHandle());
return true;
}
return false;
}
void EmoteDisplay::Unlock()
{
if (engine_ && engine_->GetEngineHandle()) {
gfx_emote_unlock(engine_->GetEngineHandle());
}
}
} // namespace emote

View File

@@ -0,0 +1,102 @@
#pragma once
#include "display.h"
#include "lvgl_font.h"
#include <memory>
#include <functional>
#include <map>
#include <string>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
namespace emote {
// Simple data structure for storing asset data without LVGL dependency
struct AssetData {
const void* data;
size_t size;
union {
uint8_t flags; // 1 byte for all animation flags
struct {
uint8_t fps : 6; // FPS (0-63) - 6 bits
uint8_t loop : 1; // Loop animation - 1 bit
uint8_t lack : 1; // Lack animation - 1 bit
};
};
AssetData() : data(nullptr), size(0), flags(0) {}
AssetData(const void* d, size_t s) : data(d), size(s), flags(0) {}
AssetData(const void* d, size_t s, uint8_t f, bool l, bool k)
: data(d), size(s)
{
fps = f > 63 ? 63 : f; // 限制 FPS 到 6 位范围
loop = l;
lack = k;
}
};
// Layout element data structure
struct LayoutData {
char align; // Store as char instead of string
int x;
int y;
int width;
int height;
bool has_size;
LayoutData() : align(0), x(0), y(0), width(0), height(0), has_size(false) {}
LayoutData(char a, int x_pos, int y_pos, int w = 0, int h = 0)
: align(a), x(x_pos), y(y_pos), width(w), height(h), has_size(w > 0 && h > 0) {}
};
// Function to convert align string to GFX_ALIGN enum value
char StringToGfxAlign(const std::string &align_str);
class EmoteEngine;
class EmoteDisplay : public Display {
public:
EmoteDisplay(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io, int width, int height);
virtual ~EmoteDisplay();
virtual void SetEmotion(const char* emotion) override;
virtual void SetStatus(const char* status) override;
virtual void SetChatMessage(const char* role, const char* content) override;
virtual void SetTheme(Theme* theme) override;
virtual void ShowNotification(const char* notification, int duration_ms = 3000) override;
virtual void UpdateStatusBar(bool update_all = false) override;
virtual void SetPowerSaveMode(bool on) override;
virtual void SetPreviewImage(const void* image);
void AddEmojiData(const std::string &name, const void* data, size_t size, uint8_t fps = 0, bool loop = false, bool lack = false);
void AddIconData(const std::string &name, const void* data, size_t size);
void AddLayoutData(const std::string &name, const std::string &align_str, int x, int y, int width = 0, int height = 0);
void AddTextFont(std::shared_ptr<LvglFont> text_font);
AssetData GetEmojiData(const std::string &name) const;
AssetData GetIconData(const std::string &name) const;
EmoteEngine* GetEngine() const;
void* GetEngineHandle() const;
inline std::shared_ptr<LvglFont> text_font() const
{
return text_font_;
}
private:
void InitializeEngine(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io, int width, int height);
virtual bool Lock(int timeout_ms = 0) override;
virtual void Unlock() override;
std::unique_ptr<EmoteEngine> engine_;
// Font management
std::shared_ptr<LvglFont> text_font_ = nullptr;
// Non-LVGL asset data storage
std::map<std::string, AssetData> emoji_data_map_;
std::map<std::string, AssetData> icon_data_map_;
};
} // namespace emote

View File

@@ -1,57 +0,0 @@
#include "esplog_display.h"
#include "esp_log.h"
#define TAG "Display2Log"
EspLogDisplay::EspLogDisplay()
{}
EspLogDisplay::~EspLogDisplay()
{}
void EspLogDisplay::SetStatus(const char* status)
{
ESP_LOGW(TAG, "SetStatus: %s", status);
}
void EspLogDisplay::ShowNotification(const char* notification, int duration_ms)
{
ESP_LOGW(TAG, "ShowNotification: %s", notification);
}
void EspLogDisplay::ShowNotification(const std::string &notification, int duration_ms)
{
ShowNotification(notification.c_str(), duration_ms);
}
void EspLogDisplay::SetEmotion(const char* emotion)
{
ESP_LOGW(TAG, "SetEmotion: %s", emotion);
}
void EspLogDisplay::SetChatMessage(const char* role, const char* content)
{
ESP_LOGW(TAG, "Role:%s", role);
ESP_LOGW(TAG, " %s", content);
}
// 音乐播放相关用日志模拟UI行为
// 显示当前播放的歌曲信息
void EspLogDisplay::SetMusicInfo(const char* info)
{
ESP_LOGW(TAG, "MusicInfo: %s", info ? info : "");
}
// 启动频谱显示(此处仅打印日志)
void EspLogDisplay::start()
{
ESP_LOGW(TAG, "Spectrum start");
}
// 停止频谱显示(此处仅打印日志)
void EspLogDisplay::stopFft()
{
ESP_LOGW(TAG, "Spectrum stop");
}

View File

@@ -1,33 +0,0 @@
#ifndef ESPLOG_DISPLAY_H_
#define ESPLOG_DISPLAY_H_
#include "display.h"
#include <string>
class EspLogDisplay : public Display {
public:
EspLogDisplay();
~EspLogDisplay();
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 SetEmotion(const char* emotion) override;
virtual void SetChatMessage(const char* role, const char* content) override;
// 音乐播放相关(无屏版本用日志模拟)
virtual void SetMusicInfo(const char* info) override;
virtual void start() override;
virtual void stopFft() override;
virtual inline void SetPreviewImage(const lv_img_dsc_t* image) {}
virtual inline void SetTheme(const std::string& theme_name) {}
virtual inline void UpdateStatusBar(bool update_all = false) override {}
protected:
virtual inline bool Lock(int timeout_ms = 0) override { return true; }
virtual inline void Unlock() override {}
};
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -1,79 +1,97 @@
#ifndef LCD_DISPLAY_H
#define LCD_DISPLAY_H
#include "lvgl_display.h"
#include "gif/lvgl_gif.h"
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include <font_emoji.h>
#include <atomic>
#include <memory>
#define PREVIEW_IMAGE_DURATION_MS 5000
class LcdDisplay : public LvglDisplay {
protected:
esp_lcd_panel_io_handle_t panel_io_ = nullptr;
esp_lcd_panel_handle_t panel_ = nullptr;
lv_draw_buf_t draw_buf_;
lv_obj_t* status_bar_ = nullptr;
lv_obj_t* content_ = nullptr;
lv_obj_t* container_ = nullptr;
lv_obj_t* side_bar_ = nullptr;
lv_obj_t* preview_image_ = nullptr;
lv_obj_t* emoji_label_ = nullptr;
lv_obj_t* emoji_image_ = nullptr;
std::unique_ptr<LvglGif> gif_controller_ = nullptr;
lv_obj_t* emoji_box_ = nullptr;
lv_obj_t* chat_message_label_ = nullptr;
esp_timer_handle_t preview_timer_ = nullptr;
std::unique_ptr<LvglImage> preview_image_cached_ = nullptr;
void InitializeLcdThemes();
void SetupUI();
virtual bool Lock(int timeout_ms = 0) override;
virtual void Unlock() override;
protected:
// 添加protected构造函数
LcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, int width, int height);
public:
~LcdDisplay();
virtual void SetEmotion(const char* emotion) override;
virtual void SetChatMessage(const char* role, const char* content) override;
virtual void SetPreviewImage(std::unique_ptr<LvglImage> image) override;
// Add theme switching function
virtual void SetTheme(Theme* theme) override;
};
// SPI LCD显示器
class SpiLcdDisplay : public LcdDisplay {
public:
SpiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
int width, int height, int offset_x, int offset_y,
bool mirror_x, bool mirror_y, bool swap_xy);
};
// RGB LCD显示器
class RgbLcdDisplay : public LcdDisplay {
public:
RgbLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
int width, int height, int offset_x, int offset_y,
bool mirror_x, bool mirror_y, bool swap_xy);
};
// MIPI LCD显示器
class MipiLcdDisplay : public LcdDisplay {
public:
MipiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
int width, int height, int offset_x, int offset_y,
bool mirror_x, bool mirror_y, bool swap_xy);
};
#endif // LCD_DISPLAY_H
#ifndef LCD_DISPLAY_H
#define LCD_DISPLAY_H
#include "lvgl_display.h"
#include "gif/lvgl_gif.h"
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include <font_emoji.h>
#include <atomic>
#include <memory>
#define PREVIEW_IMAGE_DURATION_MS 5000
class LcdDisplay : public LvglDisplay {
protected:
esp_lcd_panel_io_handle_t panel_io_ = nullptr;
esp_lcd_panel_handle_t panel_ = nullptr;
lv_draw_buf_t draw_buf_;
lv_obj_t* status_bar_ = nullptr;
lv_obj_t* content_ = nullptr;
lv_obj_t* container_ = nullptr;
lv_obj_t* side_bar_ = nullptr;
lv_obj_t* preview_image_ = nullptr;
lv_obj_t* emoji_label_ = nullptr;
lv_obj_t* emoji_image_ = nullptr;
std::unique_ptr<LvglGif> gif_controller_ = nullptr;
lv_obj_t* emoji_box_ = nullptr;
lv_obj_t* chat_message_label_ = nullptr;
esp_timer_handle_t preview_timer_ = nullptr;
std::unique_ptr<LvglImage> preview_image_cached_ = nullptr;
// 音乐播放界面UI元素
lv_obj_t* music_panel_ = nullptr; // 音乐播放面板
lv_obj_t* music_title_label_ = nullptr; // 歌曲名称标签
lv_obj_t* music_time_label_ = nullptr; // 时间显示标签
lv_obj_t* music_progress_bar_ = nullptr; // 进度条
lv_obj_t* music_progress_bg_ = nullptr; // 进度条背景
lv_obj_t* music_vinyl_record_ = nullptr; // 旋转唱片
lv_obj_t* music_vinyl_center_ = nullptr; // 唱片中心圆点
lv_obj_t* music_vinyl_arm_ = nullptr; // 唱片臂(可选)
lv_anim_t* vinyl_rotation_anim_ = nullptr; // 旋转动画
bool music_panel_visible_ = false; // 音乐面板是否可见
void InitializeLcdThemes();
void SetupUI();
void SetupMusicPanel(); // 初始化音乐播放面板
virtual bool Lock(int timeout_ms = 0) override;
virtual void Unlock() override;
protected:
// 添加protected构造函数
LcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, int width, int height);
public:
~LcdDisplay();
virtual void SetEmotion(const char* emotion) override;
virtual void SetChatMessage(const char* role, const char* content) override;
virtual void SetPreviewImage(std::unique_ptr<LvglImage> image) override;
// Add theme switching function
virtual void SetTheme(Theme* theme) override;
// Add music display functions
virtual void SetMusicInfo(const char* song_name) override;
virtual void SetMusicProgress(const char* song_name, int current_seconds, int total_seconds, float progress_percent) override;
virtual void ClearMusicInfo() override;
};
// SPI LCD显示器
class SpiLcdDisplay : public LcdDisplay {
public:
SpiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
int width, int height, int offset_x, int offset_y,
bool mirror_x, bool mirror_y, bool swap_xy);
};
// RGB LCD显示器
class RgbLcdDisplay : public LcdDisplay {
public:
RgbLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
int width, int height, int offset_x, int offset_y,
bool mirror_x, bool mirror_y, bool swap_xy);
};
// MIPI LCD显示器
class MipiLcdDisplay : public LcdDisplay {
public:
MipiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
int width, int height, int offset_x, int offset_y,
bool mirror_x, bool mirror_y, bool swap_xy);
};
#endif // LCD_DISPLAY_H

View File

@@ -1,123 +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));
}
#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

@@ -1,34 +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
#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

@@ -1,2 +1,2 @@
All of the source code and documentation for gifdec is released into the
public domain and provided without warranty of any kind.
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,17 @@
# 说明 / Description
## 中文
本目录代码移植自 LVGL 的 GIF 程序。
主要修复和改进:
- 修复了透明背景问题
- 兼容了 87a 版本的 GIF 格式
## English
The code in this directory is ported from LVGL's GIF program.
Main fixes and improvements:
- Fixed transparent background issues
- Added compatibility for GIF 87a version format

File diff suppressed because it is too large Load Diff

View File

@@ -1,68 +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 */
#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

@@ -1,140 +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*/
/**
* @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

@@ -1,208 +1,208 @@
#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");
return;
}
// 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_LOGD(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_LOGD(TAG, "GIF animation started");
}
}
void LvglGif::Pause() {
if (timer_) {
playing_ = false;
lv_timer_pause(timer_);
ESP_LOGD(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_LOGD(TAG, "GIF animation resumed");
}
}
void LvglGif::Stop() {
if (timer_) {
playing_ = false;
lv_timer_pause(timer_);
}
if (gif_) {
gd_rewind(gif_);
NextFrame();
ESP_LOGD(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_LOGD(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_));
}
#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");
return;
}
// 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_LOGD(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_LOGD(TAG, "GIF animation started");
}
}
void LvglGif::Pause() {
if (timer_) {
playing_ = false;
lv_timer_pause(timer_);
ESP_LOGD(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_LOGD(TAG, "GIF animation resumed");
}
}
void LvglGif::Stop() {
if (timer_) {
playing_ = false;
lv_timer_pause(timer_);
}
if (gif_) {
gd_rewind(gif_);
NextFrame();
ESP_LOGD(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_LOGD(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

@@ -1,101 +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();
};
#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,17 @@
# 说明 / Description
## 中文
本目录代码移植自 https://github.com/espressif/esp32-camera/blob/master/conversions/jpge.cpp
由于原版本使用了 8KB 静态全局变量,会导致程序加载后长期占用 SRAM。
本版本改为类成员变量,仅在使用时从堆内存申请,代码由 Cursor 重新生成。
## English
The code in this directory is ported from https://github.com/espressif/esp32-camera/blob/master/conversions/jpge.cpp
The original version used 8KB static global variables, which would cause long-term SRAM occupation after program loading.
This version has been changed to class member variables, which are only allocated from heap memory when in use. The code has been regenerated by Cursor.

View File

@@ -0,0 +1,228 @@
// 基于原版to_jpg.cpp替换为使用jpeg_encoder以节省SRAM
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
#include <stddef.h>
#include <string.h>
#include <memory>
#include <esp_attr.h>
#include <esp_heap_caps.h>
#include <esp_log.h>
#include "jpeg_encoder.h" // 使用新的JPEG编码器
#include "image_to_jpeg.h"
#define TAG "image_to_jpeg"
static void *_malloc(size_t size)
{
void * res = malloc(size);
if(res) {
return res;
}
// check if SPIRAM is enabled and is allocatable
#if (CONFIG_SPIRAM_SUPPORT && (CONFIG_SPIRAM_USE_CAPS_ALLOC || CONFIG_SPIRAM_USE_MALLOC))
return heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
#endif
return NULL;
}
static IRAM_ATTR void convert_line_format(uint8_t * src, pixformat_t format, uint8_t * dst, size_t width, size_t in_channels, size_t line)
{
int i=0, o=0, l=0;
if(format == PIXFORMAT_GRAYSCALE) {
memcpy(dst, src + line * width, width);
} else if(format == PIXFORMAT_RGB888) {
l = width * 3;
src += l * line;
for(i=0; i<l; i+=3) {
dst[o++] = src[i+2];
dst[o++] = src[i+1];
dst[o++] = src[i];
}
} else if(format == PIXFORMAT_RGB565) {
l = width * 2;
src += l * line;
for(i=0; i<l; i+=2) {
dst[o++] = src[i] & 0xF8;
dst[o++] = (src[i] & 0x07) << 5 | (src[i+1] & 0xE0) >> 3;
dst[o++] = (src[i+1] & 0x1F) << 3;
}
} else if(format == PIXFORMAT_YUV422) {
// YUV422转RGB的简化实现
l = width * 2;
src += l * line;
for(i=0; i<l; i+=4) {
int y0 = src[i];
int u = src[i+1];
int y1 = src[i+2];
int v = src[i+3];
// 简化的YUV到RGB转换
int c = y0 - 16;
int d = u - 128;
int e = v - 128;
int r = (298 * c + 409 * e + 128) >> 8;
int g = (298 * c - 100 * d - 208 * e + 128) >> 8;
int b = (298 * c + 516 * d + 128) >> 8;
dst[o++] = (r < 0) ? 0 : ((r > 255) ? 255 : r);
dst[o++] = (g < 0) ? 0 : ((g > 255) ? 255 : g);
dst[o++] = (b < 0) ? 0 : ((b > 255) ? 255 : b);
// Y1像素
c = y1 - 16;
r = (298 * c + 409 * e + 128) >> 8;
g = (298 * c - 100 * d - 208 * e + 128) >> 8;
b = (298 * c + 516 * d + 128) >> 8;
dst[o++] = (r < 0) ? 0 : ((r > 255) ? 255 : r);
dst[o++] = (g < 0) ? 0 : ((g > 255) ? 255 : g);
dst[o++] = (b < 0) ? 0 : ((b > 255) ? 255 : b);
}
}
}
// 回调流实现 - 用于回调版本的JPEG编码
class callback_stream : public jpge2_simple::output_stream {
protected:
jpg_out_cb ocb;
void * oarg;
size_t index;
public:
callback_stream(jpg_out_cb cb, void * arg) : ocb(cb), oarg(arg), index(0) { }
virtual ~callback_stream() { }
virtual bool put_buf(const void* data, int len)
{
index += ocb(oarg, index, data, len);
return true;
}
virtual jpge2_simple::uint get_size() const
{
return static_cast<jpge2_simple::uint>(index);
}
};
// 内存流实现 - 用于直接内存输出
class memory_stream : public jpge2_simple::output_stream {
protected:
uint8_t *out_buf;
size_t max_len, index;
public:
memory_stream(void *pBuf, uint buf_size) : out_buf(static_cast<uint8_t*>(pBuf)), max_len(buf_size), index(0) { }
virtual ~memory_stream() { }
virtual bool put_buf(const void* pBuf, int len)
{
if (!pBuf) {
//end of image
return true;
}
if ((size_t)len > (max_len - index)) {
//ESP_LOGW(TAG, "JPG output overflow: %d bytes (%d,%d,%d)", len - (max_len - index), len, index, max_len);
len = max_len - index;
}
if (len) {
memcpy(out_buf + index, pBuf, len);
index += len;
}
return true;
}
virtual jpge2_simple::uint get_size() const
{
return static_cast<jpge2_simple::uint>(index);
}
};
// 使用优化的JPEG编码器进行图像转换必须在堆上创建编码器
static bool convert_image(uint8_t *src, uint16_t width, uint16_t height, pixformat_t format, uint8_t quality, jpge2_simple::output_stream *dst_stream)
{
int num_channels = 3;
jpge2_simple::subsampling_t subsampling = jpge2_simple::H2V2;
if(format == PIXFORMAT_GRAYSCALE) {
num_channels = 1;
subsampling = jpge2_simple::Y_ONLY;
}
if(!quality) {
quality = 1;
} else if(quality > 100) {
quality = 100;
}
jpge2_simple::params comp_params = jpge2_simple::params();
comp_params.m_subsampling = subsampling;
comp_params.m_quality = quality;
// ⚠️ 关键必须在堆上创建编码器约8KB内存从堆分配
auto dst_image = std::make_unique<jpge2_simple::jpeg_encoder>();
if (!dst_image->init(dst_stream, width, height, num_channels, comp_params)) {
ESP_LOGE(TAG, "JPG encoder init failed");
return false;
}
uint8_t* line = (uint8_t*)_malloc(width * num_channels);
if(!line) {
ESP_LOGE(TAG, "Scan line malloc failed");
return false;
}
for (int i = 0; i < height; i++) {
convert_line_format(src, format, line, width, num_channels, i);
if (!dst_image->process_scanline(line)) {
ESP_LOGE(TAG, "JPG process line %u failed", i);
free(line);
return false;
}
}
free(line);
if (!dst_image->process_scanline(NULL)) {
ESP_LOGE(TAG, "JPG image finish failed");
return false;
}
// dst_image会在unique_ptr销毁时自动释放内存
return true;
}
// 🚀 主要函数高效的图像到JPEG转换实现节省8KB SRAM
bool image_to_jpeg(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t quality, uint8_t ** out, size_t * out_len)
{
ESP_LOGI(TAG, "Using optimized JPEG encoder (saves ~8KB SRAM)");
// 分配JPEG输出缓冲区这个大小对于大多数图像应该足够
int jpg_buf_len = 128*1024;
uint8_t * jpg_buf = (uint8_t *)_malloc(jpg_buf_len);
if(jpg_buf == NULL) {
ESP_LOGE(TAG, "JPG buffer malloc failed");
return false;
}
memory_stream dst_stream(jpg_buf, jpg_buf_len);
if(!convert_image(src, width, height, format, quality, &dst_stream)) {
free(jpg_buf);
return false;
}
*out = jpg_buf;
*out_len = dst_stream.get_size();
return true;
}
// 🚀 回调版本使用回调函数处理JPEG数据流适合流式传输
bool image_to_jpeg_cb(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t quality, jpg_out_cb cb, void *arg)
{
callback_stream dst_stream(cb, arg);
return convert_image(src, width, height, format, quality, &dst_stream);
}

View File

@@ -0,0 +1,68 @@
// image_to_jpeg.h - 图像到JPEG转换的高效编码接口
// 节省约8KB SRAM的JPEG编码实现
#ifndef IMAGE_TO_JPEG_H
#define IMAGE_TO_JPEG_H
#include <stdint.h>
#include <stddef.h>
#include <esp_camera.h> // 包含ESP32相机驱动的定义避免重复定义pixformat_t和camera_fb_t
#ifdef __cplusplus
extern "C" {
#endif
// JPEG输出回调函数类型
// arg: 用户自定义参数, index: 当前数据索引, data: JPEG数据块, len: 数据块长度
// 返回: 实际处理的字节数
typedef size_t (*jpg_out_cb)(void *arg, size_t index, const void *data, size_t len);
/**
* @brief 将图像格式高效转换为JPEG
*
* 这个函数使用优化的JPEG编码器进行编码主要特点
* - 节省约8KB的SRAM使用静态变量改为堆分配
* - 支持多种图像格式输入
* - 高质量JPEG输出
*
* @param src 源图像数据
* @param src_len 源图像数据长度
* @param width 图像宽度
* @param height 图像高度
* @param format 图像格式 (PIXFORMAT_RGB565, PIXFORMAT_RGB888, 等)
* @param quality JPEG质量 (1-100)
* @param out 输出JPEG数据指针 (需要调用者释放)
* @param out_len 输出JPEG数据长度
*
* @return true 成功, false 失败
*/
bool image_to_jpeg(uint8_t *src, size_t src_len, uint16_t width, uint16_t height,
pixformat_t format, uint8_t quality, uint8_t **out, size_t *out_len);
/**
* @brief 将图像格式转换为JPEG回调版本
*
* 使用回调函数处理JPEG输出数据适合流式传输或分块处理
* - 节省约8KB的SRAM使用静态变量改为堆分配
* - 支持流式输出,无需预分配大缓冲区
* - 通过回调函数逐块处理JPEG数据
*
* @param src 源图像数据
* @param src_len 源图像数据长度
* @param width 图像宽度
* @param height 图像高度
* @param format 图像格式
* @param quality JPEG质量 (1-100)
* @param cb 输出回调函数
* @param arg 传递给回调函数的用户参数
*
* @return true 成功, false 失败
*/
bool image_to_jpeg_cb(uint8_t *src, size_t src_len, uint16_t width, uint16_t height,
pixformat_t format, uint8_t quality, jpg_out_cb cb, void *arg);
#ifdef __cplusplus
}
#endif
#endif /* IMAGE_TO_JPEG_H */

View File

@@ -0,0 +1,722 @@
// jpeg_encoder.cpp - C++ class for JPEG compression with class member arrays.
// 简单版本:直接使用类成员变量,必须在堆上创建实例
// Modified from jpge.cpp to use class member variables instead of static variables
// Public domain, Rich Geldreich <richgel99@gmail.com>
#include "jpeg_encoder.h"
#include <stdint.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include "esp_heap_caps.h"
#define JPGE_MAX(a,b) (((a)>(b))?(a):(b))
#define JPGE_MIN(a,b) (((a)<(b))?(a):(b))
namespace jpge2_simple {
static inline void *jpge_malloc(size_t nSize) {
void * b = malloc(nSize);
if(b){
return b;
}
// check if SPIRAM is enabled and allocate on SPIRAM if allocatable
#if (CONFIG_SPIRAM_SUPPORT && (CONFIG_SPIRAM_USE_CAPS_ALLOC || CONFIG_SPIRAM_USE_MALLOC))
return heap_caps_malloc(nSize, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
#else
return NULL;
#endif
}
static inline void jpge_free(void *p) { free(p); }
// Various JPEG enums and tables.
enum { M_SOF0 = 0xC0, M_DHT = 0xC4, M_SOI = 0xD8, M_EOI = 0xD9, M_SOS = 0xDA, M_DQT = 0xDB, M_APP0 = 0xE0 };
enum { DC_LUM_CODES = 12, AC_LUM_CODES = 256, DC_CHROMA_CODES = 12, AC_CHROMA_CODES = 256, MAX_HUFF_SYMBOLS = 257, MAX_HUFF_CODESIZE = 32 };
static const uint8 s_zag[64] = { 0,1,8,16,9,2,3,10,17,24,32,25,18,11,4,5,12,19,26,33,40,48,41,34,27,20,13,6,7,14,21,28,35,42,49,56,57,50,43,36,29,22,15,23,30,37,44,51,58,59,52,45,38,31,39,46,53,60,61,54,47,55,62,63 };
static const int16 s_std_lum_quant[64] = { 16,11,12,14,12,10,16,14,13,14,18,17,16,19,24,40,26,24,22,22,24,49,35,37,29,40,58,51,61,60,57,51,56,55,64,72,92,78,64,68,87,69,55,56,80,109,81,87,95,98,103,104,103,62,77,113,121,112,100,120,92,101,103,99 };
static const int16 s_std_croma_quant[64] = { 17,18,18,24,21,24,47,26,26,47,99,66,56,66,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99 };
static const uint8 s_dc_lum_bits[17] = { 0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0 };
static const uint8 s_dc_lum_val[DC_LUM_CODES] = { 0,1,2,3,4,5,6,7,8,9,10,11 };
static const uint8 s_ac_lum_bits[17] = { 0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d };
static const uint8 s_ac_lum_val[AC_LUM_CODES] = {
0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,
0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,
0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89,
0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,
0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,
0xf9,0xfa
};
static const uint8 s_dc_chroma_bits[17] = { 0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0 };
static const uint8 s_dc_chroma_val[DC_CHROMA_CODES] = { 0,1,2,3,4,5,6,7,8,9,10,11 };
static const uint8 s_ac_chroma_bits[17] = { 0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77 };
static const uint8 s_ac_chroma_val[AC_CHROMA_CODES] = {
0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,
0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,
0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87,
0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,
0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,
0xf9,0xfa
};
const int YR = 19595, YG = 38470, YB = 7471, CB_R = -11059, CB_G = -21709, CB_B = 32768, CR_R = 32768, CR_G = -27439, CR_B = -5329;
static inline uint8 clamp(int i) {
if (i < 0) {
i = 0;
} else if (i > 255){
i = 255;
}
return static_cast<uint8>(i);
}
static void RGB_to_YCC(uint8* pDst, const uint8 *pSrc, int num_pixels) {
for ( ; num_pixels; pDst += 3, pSrc += 3, num_pixels--) {
const int r = pSrc[0], g = pSrc[1], b = pSrc[2];
pDst[0] = static_cast<uint8>((r * YR + g * YG + b * YB + 32768) >> 16);
pDst[1] = clamp(128 + ((r * CB_R + g * CB_G + b * CB_B + 32768) >> 16));
pDst[2] = clamp(128 + ((r * CR_R + g * CR_G + b * CR_B + 32768) >> 16));
}
}
static void RGB_to_Y(uint8* pDst, const uint8 *pSrc, int num_pixels) {
for ( ; num_pixels; pDst++, pSrc += 3, num_pixels--) {
pDst[0] = static_cast<uint8>((pSrc[0] * YR + pSrc[1] * YG + pSrc[2] * YB + 32768) >> 16);
}
}
static void Y_to_YCC(uint8* pDst, const uint8* pSrc, int num_pixels) {
for( ; num_pixels; pDst += 3, pSrc++, num_pixels--) {
pDst[0] = pSrc[0];
pDst[1] = 128;
pDst[2] = 128;
}
}
// Forward DCT - DCT derived from jfdctint.
enum { CONST_BITS = 13, ROW_BITS = 2 };
#define DCT_DESCALE(x, n) (((x) + (((int32)1) << ((n) - 1))) >> (n))
#define DCT_MUL(var, c) (static_cast<int16>(var) * static_cast<int32>(c))
#define DCT1D(s0, s1, s2, s3, s4, s5, s6, s7) \
int32 t0 = s0 + s7, t7 = s0 - s7, t1 = s1 + s6, t6 = s1 - s6, t2 = s2 + s5, t5 = s2 - s5, t3 = s3 + s4, t4 = s3 - s4; \
int32 t10 = t0 + t3, t13 = t0 - t3, t11 = t1 + t2, t12 = t1 - t2; \
int32 u1 = DCT_MUL(t12 + t13, 4433); \
s2 = u1 + DCT_MUL(t13, 6270); \
s6 = u1 + DCT_MUL(t12, -15137); \
u1 = t4 + t7; \
int32 u2 = t5 + t6, u3 = t4 + t6, u4 = t5 + t7; \
int32 z5 = DCT_MUL(u3 + u4, 9633); \
t4 = DCT_MUL(t4, 2446); t5 = DCT_MUL(t5, 16819); \
t6 = DCT_MUL(t6, 25172); t7 = DCT_MUL(t7, 12299); \
u1 = DCT_MUL(u1, -7373); u2 = DCT_MUL(u2, -20995); \
u3 = DCT_MUL(u3, -16069); u4 = DCT_MUL(u4, -3196); \
u3 += z5; u4 += z5; \
s0 = t10 + t11; s1 = t7 + u1 + u4; s3 = t6 + u2 + u3; s4 = t10 - t11; s5 = t5 + u2 + u4; s7 = t4 + u1 + u3;
static void DCT2D(int32 *p) {
int32 c, *q = p;
for (c = 7; c >= 0; c--, q += 8) {
int32 s0 = q[0], s1 = q[1], s2 = q[2], s3 = q[3], s4 = q[4], s5 = q[5], s6 = q[6], s7 = q[7];
DCT1D(s0, s1, s2, s3, s4, s5, s6, s7);
q[0] = s0 << ROW_BITS; q[1] = DCT_DESCALE(s1, CONST_BITS-ROW_BITS); q[2] = DCT_DESCALE(s2, CONST_BITS-ROW_BITS); q[3] = DCT_DESCALE(s3, CONST_BITS-ROW_BITS);
q[4] = s4 << ROW_BITS; q[5] = DCT_DESCALE(s5, CONST_BITS-ROW_BITS); q[6] = DCT_DESCALE(s6, CONST_BITS-ROW_BITS); q[7] = DCT_DESCALE(s7, CONST_BITS-ROW_BITS);
}
for (q = p, c = 7; c >= 0; c--, q++) {
int32 s0 = q[0*8], s1 = q[1*8], s2 = q[2*8], s3 = q[3*8], s4 = q[4*8], s5 = q[5*8], s6 = q[6*8], s7 = q[7*8];
DCT1D(s0, s1, s2, s3, s4, s5, s6, s7);
q[0*8] = DCT_DESCALE(s0, ROW_BITS+3); q[1*8] = DCT_DESCALE(s1, CONST_BITS+ROW_BITS+3); q[2*8] = DCT_DESCALE(s2, CONST_BITS+ROW_BITS+3); q[3*8] = DCT_DESCALE(s3, CONST_BITS+ROW_BITS+3);
q[4*8] = DCT_DESCALE(s4, ROW_BITS+3); q[5*8] = DCT_DESCALE(s5, CONST_BITS+ROW_BITS+3); q[6*8] = DCT_DESCALE(s6, CONST_BITS+ROW_BITS+3); q[7*8] = DCT_DESCALE(s7, CONST_BITS+ROW_BITS+3);
}
}
// Compute the actual canonical Huffman codes/code sizes given the JPEG huff bits and val arrays.
// 简化版本:直接使用成员变量,不需要动态分配
void jpeg_encoder::compute_huffman_table(uint *codes, uint8 *code_sizes, uint8 *bits, uint8 *val)
{
int i, l, last_p, si;
uint8 *huff_size = m_huff_size_temp; // 直接使用成员变量
uint *huff_code = m_huff_code_temp; // 直接使用成员变量
uint code;
int p = 0;
for (l = 1; l <= 16; l++) {
for (i = 1; i <= bits[l]; i++) {
huff_size[p++] = (char)l;
}
}
huff_size[p] = 0;
last_p = p; // write sentinel
code = 0; si = huff_size[0]; p = 0;
while (huff_size[p]) {
while (huff_size[p] == si) {
huff_code[p++] = code++;
}
code <<= 1;
si++;
}
memset(codes, 0, sizeof(codes[0])*256);
memset(code_sizes, 0, sizeof(code_sizes[0])*256);
for (p = 0; p < last_p; p++) {
codes[val[p]] = huff_code[p];
code_sizes[val[p]] = huff_size[p];
}
}
void jpeg_encoder::flush_output_buffer()
{
if (m_out_buf_left != JPGE_OUT_BUF_SIZE) {
m_all_stream_writes_succeeded = m_all_stream_writes_succeeded && m_pStream->put_buf(m_out_buf, JPGE_OUT_BUF_SIZE - m_out_buf_left);
}
m_pOut_buf = m_out_buf;
m_out_buf_left = JPGE_OUT_BUF_SIZE;
}
void jpeg_encoder::emit_byte(uint8 i)
{
*m_pOut_buf++ = i;
if (--m_out_buf_left == 0) {
flush_output_buffer();
}
}
void jpeg_encoder::put_bits(uint bits, uint len)
{
uint8 c = 0;
m_bit_buffer |= ((uint32)bits << (24 - (m_bits_in += len)));
while (m_bits_in >= 8) {
c = (uint8)((m_bit_buffer >> 16) & 0xFF);
emit_byte(c);
if (c == 0xFF) {
emit_byte(0);
}
m_bit_buffer <<= 8;
m_bits_in -= 8;
}
}
void jpeg_encoder::emit_word(uint i)
{
emit_byte(uint8(i >> 8)); emit_byte(uint8(i & 0xFF));
}
// JPEG marker generation.
void jpeg_encoder::emit_marker(int marker)
{
emit_byte(uint8(0xFF)); emit_byte(uint8(marker));
}
// Emit JFIF marker
void jpeg_encoder::emit_jfif_app0()
{
emit_marker(M_APP0);
emit_word(2 + 4 + 1 + 2 + 1 + 2 + 2 + 1 + 1);
emit_byte(0x4A); emit_byte(0x46); emit_byte(0x49); emit_byte(0x46); /* Identifier: ASCII "JFIF" */
emit_byte(0);
emit_byte(1); /* Major version */
emit_byte(1); /* Minor version */
emit_byte(0); /* Density unit */
emit_word(1);
emit_word(1);
emit_byte(0); /* No thumbnail image */
emit_byte(0);
}
// Emit quantization tables
void jpeg_encoder::emit_dqt()
{
for (int i = 0; i < ((m_num_components == 3) ? 2 : 1); i++)
{
emit_marker(M_DQT);
emit_word(64 + 1 + 2);
emit_byte(static_cast<uint8>(i));
for (int j = 0; j < 64; j++)
emit_byte(static_cast<uint8>(m_quantization_tables[i][j]));
}
}
// Emit start of frame marker
void jpeg_encoder::emit_sof()
{
emit_marker(M_SOF0); /* baseline */
emit_word(3 * m_num_components + 2 + 5 + 1);
emit_byte(8); /* precision */
emit_word(m_image_y);
emit_word(m_image_x);
emit_byte(m_num_components);
for (int i = 0; i < m_num_components; i++)
{
emit_byte(static_cast<uint8>(i + 1)); /* component ID */
emit_byte((m_comp_h_samp[i] << 4) + m_comp_v_samp[i]); /* h and v sampling */
emit_byte(i > 0); /* quant. table num */
}
}
// Emit Huffman table.
void jpeg_encoder::emit_dht(uint8 *bits, uint8 *val, int index, bool ac_flag)
{
emit_marker(M_DHT);
int length = 0;
for (int i = 1; i <= 16; i++)
length += bits[i];
emit_word(length + 2 + 1 + 16);
emit_byte(static_cast<uint8>(index + (ac_flag << 4)));
for (int i = 1; i <= 16; i++)
emit_byte(bits[i]);
for (int i = 0; i < length; i++)
emit_byte(val[i]);
}
// Emit all Huffman tables.
void jpeg_encoder::emit_dhts()
{
emit_dht(m_huff_bits[0+0], m_huff_val[0+0], 0, false);
emit_dht(m_huff_bits[2+0], m_huff_val[2+0], 0, true);
if (m_num_components == 3) {
emit_dht(m_huff_bits[0+1], m_huff_val[0+1], 1, false);
emit_dht(m_huff_bits[2+1], m_huff_val[2+1], 1, true);
}
}
// emit start of scan
void jpeg_encoder::emit_sos()
{
emit_marker(M_SOS);
emit_word(2 * m_num_components + 2 + 1 + 3);
emit_byte(m_num_components);
for (int i = 0; i < m_num_components; i++)
{
emit_byte(static_cast<uint8>(i + 1));
if (i == 0)
emit_byte((0 << 4) + 0);
else
emit_byte((1 << 4) + 1);
}
emit_byte(0); /* spectral selection */
emit_byte(63);
emit_byte(0);
}
void jpeg_encoder::load_block_8_8_grey(int x)
{
uint8 *pSrc;
sample_array_t *pDst = m_sample_array;
x <<= 3;
for (int i = 0; i < 8; i++, pDst += 8)
{
pSrc = m_mcu_lines[i] + x;
pDst[0] = pSrc[0] - 128; pDst[1] = pSrc[1] - 128; pDst[2] = pSrc[2] - 128; pDst[3] = pSrc[3] - 128;
pDst[4] = pSrc[4] - 128; pDst[5] = pSrc[5] - 128; pDst[6] = pSrc[6] - 128; pDst[7] = pSrc[7] - 128;
}
}
void jpeg_encoder::load_block_8_8(int x, int y, int c)
{
uint8 *pSrc;
sample_array_t *pDst = m_sample_array;
x = (x * (8 * 3)) + c;
y <<= 3;
for (int i = 0; i < 8; i++, pDst += 8)
{
pSrc = m_mcu_lines[y + i] + x;
pDst[0] = pSrc[0 * 3] - 128; pDst[1] = pSrc[1 * 3] - 128; pDst[2] = pSrc[2 * 3] - 128; pDst[3] = pSrc[3 * 3] - 128;
pDst[4] = pSrc[4 * 3] - 128; pDst[5] = pSrc[5 * 3] - 128; pDst[6] = pSrc[6 * 3] - 128; pDst[7] = pSrc[7 * 3] - 128;
}
}
void jpeg_encoder::load_block_16_8(int x, int c)
{
uint8 *pSrc1, *pSrc2;
sample_array_t *pDst = m_sample_array;
x = (x * (16 * 3)) + c;
int a = 0, b = 2;
for (int i = 0; i < 16; i += 2, pDst += 8)
{
pSrc1 = m_mcu_lines[i + 0] + x;
pSrc2 = m_mcu_lines[i + 1] + x;
pDst[0] = ((pSrc1[ 0 * 3] + pSrc1[ 1 * 3] + pSrc2[ 0 * 3] + pSrc2[ 1 * 3] + a) >> 2) - 128; pDst[1] = ((pSrc1[ 2 * 3] + pSrc1[ 3 * 3] + pSrc2[ 2 * 3] + pSrc2[ 3 * 3] + b) >> 2) - 128;
pDst[2] = ((pSrc1[ 4 * 3] + pSrc1[ 5 * 3] + pSrc2[ 4 * 3] + pSrc2[ 5 * 3] + a) >> 2) - 128; pDst[3] = ((pSrc1[ 6 * 3] + pSrc1[ 7 * 3] + pSrc2[ 6 * 3] + pSrc2[ 7 * 3] + b) >> 2) - 128;
pDst[4] = ((pSrc1[ 8 * 3] + pSrc1[ 9 * 3] + pSrc2[ 8 * 3] + pSrc2[ 9 * 3] + a) >> 2) - 128; pDst[5] = ((pSrc1[10 * 3] + pSrc1[11 * 3] + pSrc2[10 * 3] + pSrc2[11 * 3] + b) >> 2) - 128;
pDst[6] = ((pSrc1[12 * 3] + pSrc1[13 * 3] + pSrc2[12 * 3] + pSrc2[13 * 3] + a) >> 2) - 128; pDst[7] = ((pSrc1[14 * 3] + pSrc1[15 * 3] + pSrc2[14 * 3] + pSrc2[15 * 3] + b) >> 2) - 128;
int temp = a; a = b; b = temp;
}
}
void jpeg_encoder::load_block_16_8_8(int x, int c)
{
uint8 *pSrc1;
sample_array_t *pDst = m_sample_array;
x = (x * (16 * 3)) + c;
for (int i = 0; i < 8; i++, pDst += 8)
{
pSrc1 = m_mcu_lines[i + 0] + x;
pDst[0] = ((pSrc1[ 0 * 3] + pSrc1[ 1 * 3]) >> 1) - 128; pDst[1] = ((pSrc1[ 2 * 3] + pSrc1[ 3 * 3]) >> 1) - 128;
pDst[2] = ((pSrc1[ 4 * 3] + pSrc1[ 5 * 3]) >> 1) - 128; pDst[3] = ((pSrc1[ 6 * 3] + pSrc1[ 7 * 3]) >> 1) - 128;
pDst[4] = ((pSrc1[ 8 * 3] + pSrc1[ 9 * 3]) >> 1) - 128; pDst[5] = ((pSrc1[10 * 3] + pSrc1[11 * 3]) >> 1) - 128;
pDst[6] = ((pSrc1[12 * 3] + pSrc1[13 * 3]) >> 1) - 128; pDst[7] = ((pSrc1[14 * 3] + pSrc1[15 * 3]) >> 1) - 128;
}
}
void jpeg_encoder::load_quantized_coefficients(int component_num)
{
int32 *q = m_quantization_tables[component_num > 0];
int16 *pDst = m_coefficient_array;
for (int i = 0; i < 64; i++)
{
sample_array_t j = m_sample_array[s_zag[i]];
if (j < 0)
{
if ((j = -j + (*q >> 1)) < *q)
*pDst++ = 0;
else
*pDst++ = static_cast<int16>(-(j / *q));
}
else
{
if ((j = j + (*q >> 1)) < *q)
*pDst++ = 0;
else
*pDst++ = static_cast<int16>((j / *q));
}
q++;
}
}
void jpeg_encoder::code_coefficients_pass_two(int component_num)
{
int i, j, run_len, nbits, temp1, temp2;
int16 *pSrc = m_coefficient_array;
uint *codes[2];
uint8 *code_sizes[2];
if (component_num == 0)
{
codes[0] = m_huff_codes[0 + 0]; codes[1] = m_huff_codes[2 + 0];
code_sizes[0] = m_huff_code_sizes[0 + 0]; code_sizes[1] = m_huff_code_sizes[2 + 0];
}
else
{
codes[0] = m_huff_codes[0 + 1]; codes[1] = m_huff_codes[2 + 1];
code_sizes[0] = m_huff_code_sizes[0 + 1]; code_sizes[1] = m_huff_code_sizes[2 + 1];
}
temp1 = temp2 = pSrc[0] - m_last_dc_val[component_num];
m_last_dc_val[component_num] = pSrc[0];
if (temp1 < 0)
{
temp1 = -temp1; temp2--;
}
nbits = 0;
while (temp1)
{
nbits++; temp1 >>= 1;
}
put_bits(codes[0][nbits], code_sizes[0][nbits]);
if (nbits) put_bits(temp2 & ((1 << nbits) - 1), nbits);
for (run_len = 0, i = 1; i < 64; i++)
{
if ((temp1 = m_coefficient_array[i]) == 0)
run_len++;
else
{
while (run_len >= 16)
{
put_bits(codes[1][0xF0], code_sizes[1][0xF0]);
run_len -= 16;
}
if ((temp2 = temp1) < 0)
{
temp1 = -temp1;
temp2--;
}
nbits = 1;
while (temp1 >>= 1)
nbits++;
j = (run_len << 4) + nbits;
put_bits(codes[1][j], code_sizes[1][j]);
put_bits(temp2 & ((1 << nbits) - 1), nbits);
run_len = 0;
}
}
if (run_len)
put_bits(codes[1][0], code_sizes[1][0]);
}
void jpeg_encoder::code_block(int component_num)
{
DCT2D(m_sample_array);
load_quantized_coefficients(component_num);
code_coefficients_pass_two(component_num);
}
void jpeg_encoder::process_mcu_row()
{
if (m_num_components == 1)
{
for (int i = 0; i < m_mcus_per_row; i++)
{
load_block_8_8_grey(i); code_block(0);
}
}
else if ((m_comp_h_samp[0] == 1) && (m_comp_v_samp[0] == 1))
{
for (int i = 0; i < m_mcus_per_row; i++)
{
load_block_8_8(i, 0, 0); code_block(0); load_block_8_8(i, 0, 1); code_block(1); load_block_8_8(i, 0, 2); code_block(2);
}
}
else if ((m_comp_h_samp[0] == 2) && (m_comp_v_samp[0] == 1))
{
for (int i = 0; i < m_mcus_per_row; i++)
{
load_block_8_8(i * 2 + 0, 0, 0); code_block(0); load_block_8_8(i * 2 + 1, 0, 0); code_block(0);
load_block_16_8_8(i, 1); code_block(1); load_block_16_8_8(i, 2); code_block(2);
}
}
else if ((m_comp_h_samp[0] == 2) && (m_comp_v_samp[0] == 2))
{
for (int i = 0; i < m_mcus_per_row; i++)
{
load_block_8_8(i * 2 + 0, 0, 0); code_block(0); load_block_8_8(i * 2 + 1, 0, 0); code_block(0);
load_block_8_8(i * 2 + 0, 1, 0); code_block(0); load_block_8_8(i * 2 + 1, 1, 0); code_block(0);
load_block_16_8(i, 1); code_block(1); load_block_16_8(i, 2); code_block(2);
}
}
}
void jpeg_encoder::load_mcu(const void *pSrc)
{
const uint8* Psrc = reinterpret_cast<const uint8*>(pSrc);
uint8* pDst = m_mcu_lines[m_mcu_y_ofs]; // OK to write up to m_image_bpl_xlt bytes to pDst
if (m_num_components == 1) {
if (m_image_bpp == 3)
RGB_to_Y(pDst, Psrc, m_image_x);
else
memcpy(pDst, Psrc, m_image_x);
} else {
if (m_image_bpp == 3)
RGB_to_YCC(pDst, Psrc, m_image_x);
else
Y_to_YCC(pDst, Psrc, m_image_x);
}
// Possibly duplicate pixels at end of scanline if not a multiple of 8 or 16
if (m_num_components == 1)
memset(m_mcu_lines[m_mcu_y_ofs] + m_image_bpl_xlt, pDst[m_image_bpl_xlt - 1], m_image_x_mcu - m_image_x);
else
{
const uint8 y = pDst[m_image_bpl_xlt - 3 + 0], cb = pDst[m_image_bpl_xlt - 3 + 1], cr = pDst[m_image_bpl_xlt - 3 + 2];
uint8 *q = m_mcu_lines[m_mcu_y_ofs] + m_image_bpl_xlt;
for (int i = m_image_x; i < m_image_x_mcu; i++)
{
*q++ = y; *q++ = cb; *q++ = cr;
}
}
if (++m_mcu_y_ofs == m_mcu_y)
{
process_mcu_row();
m_mcu_y_ofs = 0;
}
}
// Quantization table generation.
void jpeg_encoder::compute_quant_table(int32 *pDst, const int16 *pSrc)
{
int32 q;
if (m_params.m_quality < 50)
q = 5000 / m_params.m_quality;
else
q = 200 - m_params.m_quality * 2;
for (int i = 0; i < 64; i++)
{
int32 j = *pSrc++; j = (j * q + 50L) / 100L;
*pDst++ = JPGE_MIN(JPGE_MAX(j, 1), 255);
}
}
// Higher-level methods.
bool jpeg_encoder::jpg_open(int p_x_res, int p_y_res, int src_channels)
{
m_num_components = 3;
switch (m_params.m_subsampling)
{
case Y_ONLY:
{
m_num_components = 1;
m_comp_h_samp[0] = 1; m_comp_v_samp[0] = 1;
m_mcu_x = 8; m_mcu_y = 8;
break;
}
case H1V1:
{
m_comp_h_samp[0] = 1; m_comp_v_samp[0] = 1;
m_comp_h_samp[1] = 1; m_comp_v_samp[1] = 1;
m_comp_h_samp[2] = 1; m_comp_v_samp[2] = 1;
m_mcu_x = 8; m_mcu_y = 8;
break;
}
case H2V1:
{
m_comp_h_samp[0] = 2; m_comp_v_samp[0] = 1;
m_comp_h_samp[1] = 1; m_comp_v_samp[1] = 1;
m_comp_h_samp[2] = 1; m_comp_v_samp[2] = 1;
m_mcu_x = 16; m_mcu_y = 8;
break;
}
case H2V2:
{
m_comp_h_samp[0] = 2; m_comp_v_samp[0] = 2;
m_comp_h_samp[1] = 1; m_comp_v_samp[1] = 1;
m_comp_h_samp[2] = 1; m_comp_v_samp[2] = 1;
m_mcu_x = 16; m_mcu_y = 16;
}
}
m_image_x = p_x_res; m_image_y = p_y_res;
m_image_bpp = src_channels;
m_image_bpl = m_image_x * src_channels;
m_image_x_mcu = (m_image_x + m_mcu_x - 1) & (~(m_mcu_x - 1));
m_image_y_mcu = (m_image_y + m_mcu_y - 1) & (~(m_mcu_y - 1));
m_image_bpl_xlt = m_image_x * m_num_components;
m_image_bpl_mcu = m_image_x_mcu * m_num_components;
m_mcus_per_row = m_image_x_mcu / m_mcu_x;
if ((m_mcu_lines[0] = static_cast<uint8*>(jpge_malloc(m_image_bpl_mcu * m_mcu_y))) == NULL) {
return false;
}
for (int i = 1; i < m_mcu_y; i++)
m_mcu_lines[i] = m_mcu_lines[i-1] + m_image_bpl_mcu;
if(m_last_quality != m_params.m_quality){
m_last_quality = m_params.m_quality;
compute_quant_table(m_quantization_tables[0], s_std_lum_quant);
compute_quant_table(m_quantization_tables[1], s_std_croma_quant);
}
if(!m_huff_initialized){
m_huff_initialized = true;
memcpy(m_huff_bits[0+0], s_dc_lum_bits, 17); memcpy(m_huff_val[0+0], s_dc_lum_val, DC_LUM_CODES);
memcpy(m_huff_bits[2+0], s_ac_lum_bits, 17); memcpy(m_huff_val[2+0], s_ac_lum_val, AC_LUM_CODES);
memcpy(m_huff_bits[0+1], s_dc_chroma_bits, 17); memcpy(m_huff_val[0+1], s_dc_chroma_val, DC_CHROMA_CODES);
memcpy(m_huff_bits[2+1], s_ac_chroma_bits, 17); memcpy(m_huff_val[2+1], s_ac_chroma_val, AC_CHROMA_CODES);
compute_huffman_table(m_huff_codes[0+0], m_huff_code_sizes[0+0], m_huff_bits[0+0], m_huff_val[0+0]);
compute_huffman_table(m_huff_codes[2+0], m_huff_code_sizes[2+0], m_huff_bits[2+0], m_huff_val[2+0]);
compute_huffman_table(m_huff_codes[0+1], m_huff_code_sizes[0+1], m_huff_bits[0+1], m_huff_val[0+1]);
compute_huffman_table(m_huff_codes[2+1], m_huff_code_sizes[2+1], m_huff_bits[2+1], m_huff_val[2+1]);
}
m_out_buf_left = JPGE_OUT_BUF_SIZE;
m_pOut_buf = m_out_buf;
m_bit_buffer = 0;
m_bits_in = 0;
m_mcu_y_ofs = 0;
m_pass_num = 2;
memset(m_last_dc_val, 0, 3 * sizeof(m_last_dc_val[0]));
// Emit all markers at beginning of image file.
emit_marker(M_SOI);
emit_jfif_app0();
emit_dqt();
emit_sof();
emit_dhts();
emit_sos();
return m_all_stream_writes_succeeded;
}
bool jpeg_encoder::process_end_of_image()
{
if (m_mcu_y_ofs) {
if (m_mcu_y_ofs < 16) { // check here just to shut up static analysis
for (int i = m_mcu_y_ofs; i < m_mcu_y; i++) {
memcpy(m_mcu_lines[i], m_mcu_lines[m_mcu_y_ofs - 1], m_image_bpl_mcu);
}
}
process_mcu_row();
}
put_bits(0x7F, 7);
emit_marker(M_EOI);
flush_output_buffer();
m_all_stream_writes_succeeded = m_all_stream_writes_succeeded && m_pStream->put_buf(NULL, 0);
m_pass_num++; // purposely bump up m_pass_num, for debugging
return true;
}
void jpeg_encoder::clear()
{
m_mcu_lines[0] = NULL;
m_pass_num = 0;
m_all_stream_writes_succeeded = true;
// 简单版本:成员变量自动初始化,不需要额外处理
m_last_quality = 0;
m_huff_initialized = false;
}
jpeg_encoder::jpeg_encoder()
{
clear();
}
jpeg_encoder::~jpeg_encoder()
{
deinit();
}
bool jpeg_encoder::init(output_stream *pStream, int width, int height, int src_channels, const params &comp_params)
{
deinit();
if (((!pStream) || (width < 1) || (height < 1)) || ((src_channels != 1) && (src_channels != 3) && (src_channels != 4)) || (!comp_params.check())) return false;
// 简单版本:不需要动态分配内存,成员变量已经存在
m_pStream = pStream;
m_params = comp_params;
return jpg_open(width, height, src_channels);
}
void jpeg_encoder::deinit()
{
jpge_free(m_mcu_lines[0]);
clear();
// 简单版本:不需要释放成员变量内存
}
bool jpeg_encoder::process_scanline(const void* pScanline)
{
if ((m_pass_num < 1) || (m_pass_num > 2)) {
return false;
}
if (m_all_stream_writes_succeeded) {
if (!pScanline) {
if (!process_end_of_image()) {
return false;
}
} else {
load_mcu(pScanline);
}
}
return m_all_stream_writes_succeeded;
}
} // namespace jpge2_simple

View File

@@ -0,0 +1,119 @@
// jpeg_encoder.h - 使用类成员变量的简单版本
// 这个版本直接在类中声明数组,要求必须在堆上创建实例
#ifndef JPEG_ENCODER_H
#define JPEG_ENCODER_H
namespace jpge2_simple
{
typedef unsigned char uint8;
typedef signed short int16;
typedef signed int int32;
typedef unsigned short uint16;
typedef unsigned int uint32;
typedef unsigned int uint;
enum subsampling_t { Y_ONLY = 0, H1V1 = 1, H2V1 = 2, H2V2 = 3 };
struct params {
inline params() : m_quality(85), m_subsampling(H2V2) { }
inline bool check() const {
if ((m_quality < 1) || (m_quality > 100)) return false;
if ((uint)m_subsampling > (uint)H2V2) return false;
return true;
}
int m_quality;
subsampling_t m_subsampling;
};
class output_stream {
public:
virtual ~output_stream() { };
virtual bool put_buf(const void* Pbuf, int len) = 0;
virtual uint get_size() const = 0;
};
// 简单版本:直接在类中声明数组
// 警告:必须在堆上创建实例!(使用 new
class jpeg_encoder {
public:
jpeg_encoder();
~jpeg_encoder();
bool init(output_stream *pStream, int width, int height, int src_channels, const params &comp_params = params());
bool process_scanline(const void* pScanline);
void deinit();
private:
jpeg_encoder(const jpeg_encoder &);
jpeg_encoder &operator =(const jpeg_encoder &);
typedef int32 sample_array_t;
enum { JPGE_OUT_BUF_SIZE = 512 };
output_stream *m_pStream;
params m_params;
uint8 m_num_components;
uint8 m_comp_h_samp[3], m_comp_v_samp[3];
int m_image_x, m_image_y, m_image_bpp, m_image_bpl;
int m_image_x_mcu, m_image_y_mcu;
int m_image_bpl_xlt, m_image_bpl_mcu;
int m_mcus_per_row;
int m_mcu_x, m_mcu_y;
uint8 *m_mcu_lines[16];
uint8 m_mcu_y_ofs;
sample_array_t m_sample_array[64];
int16 m_coefficient_array[64];
int m_last_dc_val[3];
uint8 m_out_buf[JPGE_OUT_BUF_SIZE];
uint8 *m_pOut_buf;
uint m_out_buf_left;
uint32 m_bit_buffer;
uint m_bits_in;
uint8 m_pass_num;
bool m_all_stream_writes_succeeded;
// 直接声明为类成员变量约8KB
int32 m_last_quality;
int32 m_quantization_tables[2][64]; // 512 bytes
bool m_huff_initialized;
uint m_huff_codes[4][256]; // 4096 bytes
uint8 m_huff_code_sizes[4][256]; // 1024 bytes
uint8 m_huff_bits[4][17]; // 68 bytes
uint8 m_huff_val[4][256]; // 1024 bytes
// compute_huffman_table的临时缓冲区也作为成员变量
uint8 m_huff_size_temp[257]; // 257 bytes
uint m_huff_code_temp[257]; // 1028 bytes
bool jpg_open(int p_x_res, int p_y_res, int src_channels);
void flush_output_buffer();
void put_bits(uint bits, uint len);
void emit_byte(uint8 i);
void emit_word(uint i);
void emit_marker(int marker);
void emit_jfif_app0();
void emit_dqt();
void emit_sof();
void emit_dht(uint8 *bits, uint8 *val, int index, bool ac_flag);
void emit_dhts();
void emit_sos();
void compute_quant_table(int32 *dst, const int16 *src);
void load_quantized_coefficients(int component_num);
void load_block_8_8_grey(int x);
void load_block_8_8(int x, int y, int c);
void load_block_16_8(int x, int c);
void load_block_16_8_8(int x, int c);
void code_coefficients_pass_two(int component_num);
void code_block(int component_num);
void process_mcu_row();
bool process_end_of_image();
void load_mcu(const void* src);
void clear();
void compute_huffman_table(uint *codes, uint8 *code_sizes, uint8 *bits, uint8 *val);
};
} // namespace jpge2_simple
#endif // JPEG_ENCODER_H

View File

@@ -1,242 +1,258 @@
#include <esp_log.h>
#include <esp_err.h>
#include <string>
#include <cstdlib>
#include <cstring>
#include <font_awesome.h>
#include <img_converters.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(std::unique_ptr<LvglImage> image) {
}
void LvglDisplay::SetPowerSaveMode(bool on) {
if (on) {
SetChatMessage("system", "");
SetEmotion("sleepy");
} else {
SetChatMessage("system", "");
SetEmotion("neutral");
}
}
bool LvglDisplay::SnapshotToJpeg(uint8_t*& jpeg_output_data, size_t& jpeg_output_data_size, int quality) {
DisplayLockGuard lock(this);
lv_obj_t* screen = lv_screen_active();
lv_draw_buf_t* draw_buffer = lv_snapshot_take(screen, LV_COLOR_FORMAT_RGB565);
if (draw_buffer == nullptr) {
return false;
}
// swap bytes
uint16_t* data = (uint16_t*)draw_buffer->data;
size_t pixel_count = draw_buffer->data_size / 2;
for (size_t i = 0; i < pixel_count; i++) {
data[i] = __builtin_bswap16(data[i]);
}
if (!fmt2jpg(draw_buffer->data, draw_buffer->data_size, draw_buffer->header.w, draw_buffer->header.h,
PIXFORMAT_RGB565, quality, &jpeg_output_data, &jpeg_output_data_size)) {
lv_draw_buf_destroy(draw_buffer);
return false;
}
lv_draw_buf_destroy(draw_buffer);
return true;
}
#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"
#include "jpg/image_to_jpeg.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(std::unique_ptr<LvglImage> image) {
}
void LvglDisplay::SetPowerSaveMode(bool on) {
if (on) {
SetChatMessage("system", "");
SetEmotion("sleepy");
} else {
SetChatMessage("system", "");
SetEmotion("neutral");
}
}
bool LvglDisplay::SnapshotToJpeg(std::string& jpeg_data, int quality) {
#if CONFIG_LV_USE_SNAPSHOT
DisplayLockGuard lock(this);
lv_obj_t* screen = lv_screen_active();
lv_draw_buf_t* draw_buffer = lv_snapshot_take(screen, LV_COLOR_FORMAT_RGB565);
if (draw_buffer == nullptr) {
ESP_LOGE(TAG, "Failed to take snapshot, draw_buffer is nullptr");
return false;
}
// swap bytes
uint16_t* data = (uint16_t*)draw_buffer->data;
size_t pixel_count = draw_buffer->data_size / 2;
for (size_t i = 0; i < pixel_count; i++) {
data[i] = __builtin_bswap16(data[i]);
}
// 清空输出字符串并使用回调版本,避免预分配大内存块
jpeg_data.clear();
// 🚀 使用回调版本的JPEG编码器进一步节省内存
bool ret = image_to_jpeg_cb(draw_buffer->data, draw_buffer->data_size, draw_buffer->header.w, draw_buffer->header.h, PIXFORMAT_RGB565, quality,
[](void *arg, size_t index, const void *data, size_t len) -> size_t {
std::string* output = static_cast<std::string*>(arg);
if (data && len > 0) {
output->append(static_cast<const char*>(data), len);
}
return len;
}, &jpeg_data);
if (!ret) {
ESP_LOGE(TAG, "Failed to convert image to JPEG");
}
lv_draw_buf_destroy(draw_buffer);
return ret;
#else
ESP_LOGE(TAG, "LV_USE_SNAPSHOT is not enabled");
return false;
#endif
}

View File

@@ -1,53 +1,53 @@
#ifndef LVGL_DISPLAY_H
#define LVGL_DISPLAY_H
#include "display.h"
#include "lvgl_image.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(std::unique_ptr<LvglImage> image);
virtual void UpdateStatusBar(bool update_all = false);
virtual void SetPowerSaveMode(bool on);
virtual bool SnapshotToJpeg(uint8_t*& jpeg_output_data, size_t& jpeg_output_size, int quality = 80);
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
#ifndef LVGL_DISPLAY_H
#define LVGL_DISPLAY_H
#include "display.h"
#include "lvgl_image.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(std::unique_ptr<LvglImage> image);
virtual void UpdateStatusBar(bool update_all = false);
virtual void SetPowerSaveMode(bool on);
virtual bool SnapshotToJpeg(std::string& jpeg_data, int quality = 80);
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

@@ -1,13 +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_);
}
#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

@@ -1,31 +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_;
};
#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

@@ -1,64 +1,64 @@
#include "lvgl_image.h"
#include <cbin_font.h>
#include <esp_log.h>
#include <stdexcept>
#include <cstring>
#include <esp_heap_caps.h>
#define TAG "LvglImage"
LvglRawImage::LvglRawImage(void* data, size_t size) {
bzero(&image_dsc_, sizeof(image_dsc_));
image_dsc_.data_size = size;
image_dsc_.data = static_cast<uint8_t*>(data);
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;
}
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_);
}
}
LvglAllocatedImage::LvglAllocatedImage(void* data, size_t size) {
bzero(&image_dsc_, sizeof(image_dsc_));
image_dsc_.data_size = size;
image_dsc_.data = static_cast<uint8_t*>(data);
if (lv_image_decoder_get_info(&image_dsc_, &image_dsc_.header) != LV_RESULT_OK) {
ESP_LOGE(TAG, "Failed to get image info, data: %p size: %u", data, size);
throw std::runtime_error("Failed to get image info");
}
}
LvglAllocatedImage::LvglAllocatedImage(void* data, size_t size, int width, int height, int stride, int color_format) {
bzero(&image_dsc_, sizeof(image_dsc_));
image_dsc_.data_size = size;
image_dsc_.data = static_cast<uint8_t*>(data);
image_dsc_.header.magic = LV_IMAGE_HEADER_MAGIC;
image_dsc_.header.cf = color_format;
image_dsc_.header.w = width;
image_dsc_.header.h = height;
image_dsc_.header.stride = stride;
}
LvglAllocatedImage::~LvglAllocatedImage() {
if (image_dsc_.data) {
heap_caps_free((void*)image_dsc_.data);
image_dsc_.data = nullptr;
}
#include "lvgl_image.h"
#include <cbin_font.h>
#include <esp_log.h>
#include <stdexcept>
#include <cstring>
#include <esp_heap_caps.h>
#define TAG "LvglImage"
LvglRawImage::LvglRawImage(void* data, size_t size) {
bzero(&image_dsc_, sizeof(image_dsc_));
image_dsc_.data_size = size;
image_dsc_.data = static_cast<uint8_t*>(data);
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;
}
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_);
}
}
LvglAllocatedImage::LvglAllocatedImage(void* data, size_t size) {
bzero(&image_dsc_, sizeof(image_dsc_));
image_dsc_.data_size = size;
image_dsc_.data = static_cast<uint8_t*>(data);
if (lv_image_decoder_get_info(&image_dsc_, &image_dsc_.header) != LV_RESULT_OK) {
ESP_LOGE(TAG, "Failed to get image info, data: %p size: %u", data, size);
throw std::runtime_error("Failed to get image info");
}
}
LvglAllocatedImage::LvglAllocatedImage(void* data, size_t size, int width, int height, int stride, int color_format) {
bzero(&image_dsc_, sizeof(image_dsc_));
image_dsc_.data_size = size;
image_dsc_.data = static_cast<uint8_t*>(data);
image_dsc_.header.magic = LV_IMAGE_HEADER_MAGIC;
image_dsc_.header.cf = color_format;
image_dsc_.header.w = width;
image_dsc_.header.h = height;
image_dsc_.header.stride = stride;
}
LvglAllocatedImage::~LvglAllocatedImage() {
if (image_dsc_.data) {
heap_caps_free((void*)image_dsc_.data);
image_dsc_.data = nullptr;
}
}

View File

@@ -1,53 +1,53 @@
#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_;
};
class LvglAllocatedImage : public LvglImage {
public:
LvglAllocatedImage(void* data, size_t size);
LvglAllocatedImage(void* data, size_t size, int width, int height, int stride, int color_format);
virtual ~LvglAllocatedImage();
virtual const lv_img_dsc_t* image_dsc() const override { return &image_dsc_; }
private:
lv_img_dsc_t image_dsc_;
#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_;
};
class LvglAllocatedImage : public LvglImage {
public:
LvglAllocatedImage(void* data, size_t size);
LvglAllocatedImage(void* data, size_t size, int width, int height, int stride, int color_format);
virtual ~LvglAllocatedImage();
virtual const lv_img_dsc_t* image_dsc() const override { return &image_dsc_; }
private:
lv_img_dsc_t image_dsc_;
};

View File

@@ -1,30 +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;
}
#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

@@ -1,94 +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_;
};
#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_;
};

View File

@@ -1,361 +1,361 @@
#include "oled_display.h"
#include "assets/lang_config.h"
#include "lvgl_theme.h"
#include "lvgl_font.h"
#include <string>
#include <algorithm>
#include <esp_log.h>
#include <esp_err.h>
#include <esp_lvgl_port.h>
#include <font_awesome.h>
#define TAG "OledDisplay"
LV_FONT_DECLARE(LVGL_TEXT_FONT);
LV_FONT_DECLARE(LVGL_ICON_FONT);
LV_FONT_DECLARE(font_awesome_30_1);
OledDisplay::OledDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
int width, int height, bool mirror_x, bool mirror_y)
: panel_io_(panel_io), panel_(panel) {
width_ = width;
height_ = height;
auto text_font = std::make_shared<LvglBuiltInFont>(&LVGL_TEXT_FONT);
auto icon_font = std::make_shared<LvglBuiltInFont>(&LVGL_ICON_FONT);
auto large_icon_font = std::make_shared<LvglBuiltInFont>(&font_awesome_30_1);
auto dark_theme = new LvglTheme("dark");
dark_theme->set_text_font(text_font);
dark_theme->set_icon_font(icon_font);
dark_theme->set_large_icon_font(large_icon_font);
auto& theme_manager = LvglThemeManager::GetInstance();
theme_manager.RegisterTheme("dark", dark_theme);
current_theme_ = dark_theme;
ESP_LOGI(TAG, "Initialize LVGL");
lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG();
port_cfg.task_priority = 1;
port_cfg.task_stack = 6144;
#if CONFIG_SOC_CPU_CORES_NUM > 1
port_cfg.task_affinity = 1;
#endif
lvgl_port_init(&port_cfg);
ESP_LOGI(TAG, "Adding OLED display");
const lvgl_port_display_cfg_t display_cfg = {
.io_handle = panel_io_,
.panel_handle = panel_,
.control_handle = nullptr,
.buffer_size = static_cast<uint32_t>(width_ * height_),
.double_buffer = false,
.trans_size = 0,
.hres = static_cast<uint32_t>(width_),
.vres = static_cast<uint32_t>(height_),
.monochrome = true,
.rotation = {
.swap_xy = false,
.mirror_x = mirror_x,
.mirror_y = mirror_y,
},
.flags = {
.buff_dma = 1,
.buff_spiram = 0,
.sw_rotate = 0,
.full_refresh = 0,
.direct_mode = 0,
},
};
display_ = lvgl_port_add_disp(&display_cfg);
if (display_ == nullptr) {
ESP_LOGE(TAG, "Failed to add display");
return;
}
if (height_ == 64) {
SetupUI_128x64();
} else {
SetupUI_128x32();
}
}
OledDisplay::~OledDisplay() {
if (content_ != nullptr) {
lv_obj_del(content_);
}
if (status_bar_ != nullptr) {
lv_obj_del(status_bar_);
}
if (side_bar_ != nullptr) {
lv_obj_del(side_bar_);
}
if (container_ != nullptr) {
lv_obj_del(container_);
}
if (panel_ != nullptr) {
esp_lcd_panel_del(panel_);
}
if (panel_io_ != nullptr) {
esp_lcd_panel_io_del(panel_io_);
}
lvgl_port_deinit();
}
bool OledDisplay::Lock(int timeout_ms) {
return lvgl_port_lock(timeout_ms);
}
void OledDisplay::Unlock() {
lvgl_port_unlock();
}
void OledDisplay::SetChatMessage(const char* role, const char* content) {
DisplayLockGuard lock(this);
if (chat_message_label_ == nullptr) {
return;
}
// Replace all newlines with spaces
std::string content_str = content;
std::replace(content_str.begin(), content_str.end(), '\n', ' ');
if (content_right_ == nullptr) {
lv_label_set_text(chat_message_label_, content_str.c_str());
} else {
if (content == nullptr || content[0] == '\0') {
lv_obj_add_flag(content_right_, LV_OBJ_FLAG_HIDDEN);
} else {
lv_label_set_text(chat_message_label_, content_str.c_str());
lv_obj_remove_flag(content_right_, LV_OBJ_FLAG_HIDDEN);
}
}
}
void OledDisplay::SetupUI_128x64() {
DisplayLockGuard lock(this);
auto lvgl_theme = static_cast<LvglTheme*>(current_theme_);
auto text_font = lvgl_theme->text_font()->font();
auto icon_font = lvgl_theme->icon_font()->font();
auto large_icon_font = lvgl_theme->large_icon_font()->font();
auto screen = lv_screen_active();
lv_obj_set_style_text_font(screen, text_font, 0);
lv_obj_set_style_text_color(screen, lv_color_black(), 0);
/* Container */
container_ = lv_obj_create(screen);
lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES);
lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_COLUMN);
lv_obj_set_style_pad_all(container_, 0, 0);
lv_obj_set_style_border_width(container_, 0, 0);
lv_obj_set_style_pad_row(container_, 0, 0);
/* Status bar */
status_bar_ = lv_obj_create(container_);
lv_obj_set_size(status_bar_, LV_HOR_RES, 16);
lv_obj_set_style_border_width(status_bar_, 0, 0);
lv_obj_set_style_pad_all(status_bar_, 0, 0);
lv_obj_set_style_radius(status_bar_, 0, 0);
/* Content */
content_ = lv_obj_create(container_);
lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF);
lv_obj_set_style_radius(content_, 0, 0);
lv_obj_set_style_pad_all(content_, 0, 0);
lv_obj_set_width(content_, LV_HOR_RES);
lv_obj_set_flex_grow(content_, 1);
lv_obj_set_flex_flow(content_, LV_FLEX_FLOW_ROW);
lv_obj_set_style_flex_main_place(content_, LV_FLEX_ALIGN_CENTER, 0);
// 创建左侧固定宽度的容器
content_left_ = lv_obj_create(content_);
lv_obj_set_size(content_left_, 32, LV_SIZE_CONTENT); // 固定宽度32像素
lv_obj_set_style_pad_all(content_left_, 0, 0);
lv_obj_set_style_border_width(content_left_, 0, 0);
emotion_label_ = lv_label_create(content_left_);
lv_obj_set_style_text_font(emotion_label_, large_icon_font, 0);
lv_label_set_text(emotion_label_, FONT_AWESOME_MICROCHIP_AI);
lv_obj_center(emotion_label_);
lv_obj_set_style_pad_top(emotion_label_, 8, 0);
// 创建右侧可扩展的容器
content_right_ = lv_obj_create(content_);
lv_obj_set_size(content_right_, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
lv_obj_set_style_pad_all(content_right_, 0, 0);
lv_obj_set_style_border_width(content_right_, 0, 0);
lv_obj_set_flex_grow(content_right_, 1);
lv_obj_add_flag(content_right_, LV_OBJ_FLAG_HIDDEN);
chat_message_label_ = lv_label_create(content_right_);
lv_label_set_text(chat_message_label_, "");
lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_SCROLL_CIRCULAR);
lv_obj_set_style_text_align(chat_message_label_, LV_TEXT_ALIGN_LEFT, 0);
lv_obj_set_width(chat_message_label_, width_ - 32);
lv_obj_set_style_pad_top(chat_message_label_, 14, 0);
// 延迟一定的时间后开始滚动字幕
static lv_anim_t a;
lv_anim_init(&a);
lv_anim_set_delay(&a, 1000);
lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
lv_obj_set_style_anim(chat_message_label_, &a, LV_PART_MAIN);
lv_obj_set_style_anim_duration(chat_message_label_, lv_anim_speed_clamped(60, 300, 60000), LV_PART_MAIN);
/* Status bar */
lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW);
lv_obj_set_style_pad_all(status_bar_, 0, 0);
lv_obj_set_style_border_width(status_bar_, 0, 0);
lv_obj_set_style_pad_column(status_bar_, 0, 0);
network_label_ = lv_label_create(status_bar_);
lv_label_set_text(network_label_, "");
lv_obj_set_style_text_font(network_label_, icon_font, 0);
notification_label_ = lv_label_create(status_bar_);
lv_obj_set_flex_grow(notification_label_, 1);
lv_obj_set_style_text_align(notification_label_, LV_TEXT_ALIGN_CENTER, 0);
lv_label_set_text(notification_label_, "");
lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN);
status_label_ = lv_label_create(status_bar_);
lv_obj_set_flex_grow(status_label_, 1);
lv_label_set_text(status_label_, Lang::Strings::INITIALIZING);
lv_obj_set_style_text_align(status_label_, LV_TEXT_ALIGN_CENTER, 0);
mute_label_ = lv_label_create(status_bar_);
lv_label_set_text(mute_label_, "");
lv_obj_set_style_text_font(mute_label_, icon_font, 0);
battery_label_ = lv_label_create(status_bar_);
lv_label_set_text(battery_label_, "");
lv_obj_set_style_text_font(battery_label_, icon_font, 0);
low_battery_popup_ = lv_obj_create(screen);
lv_obj_set_scrollbar_mode(low_battery_popup_, LV_SCROLLBAR_MODE_OFF);
lv_obj_set_size(low_battery_popup_, LV_HOR_RES * 0.9, text_font->line_height * 2);
lv_obj_align(low_battery_popup_, LV_ALIGN_BOTTOM_MID, 0, 0);
lv_obj_set_style_bg_color(low_battery_popup_, lv_color_black(), 0);
lv_obj_set_style_radius(low_battery_popup_, 10, 0);
low_battery_label_ = lv_label_create(low_battery_popup_);
lv_label_set_text(low_battery_label_, Lang::Strings::BATTERY_NEED_CHARGE);
lv_obj_set_style_text_color(low_battery_label_, lv_color_white(), 0);
lv_obj_center(low_battery_label_);
lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN);
}
void OledDisplay::SetupUI_128x32() {
DisplayLockGuard lock(this);
auto lvgl_theme = static_cast<LvglTheme*>(current_theme_);
auto text_font = lvgl_theme->text_font()->font();
auto icon_font = lvgl_theme->icon_font()->font();
auto large_icon_font = lvgl_theme->large_icon_font()->font();
auto screen = lv_screen_active();
lv_obj_set_style_text_font(screen, text_font, 0);
/* Container */
container_ = lv_obj_create(screen);
lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES);
lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_ROW);
lv_obj_set_style_pad_all(container_, 0, 0);
lv_obj_set_style_border_width(container_, 0, 0);
lv_obj_set_style_pad_column(container_, 0, 0);
/* Emotion label on the left side */
content_ = lv_obj_create(container_);
lv_obj_set_size(content_, 32, 32);
lv_obj_set_style_pad_all(content_, 0, 0);
lv_obj_set_style_border_width(content_, 0, 0);
lv_obj_set_style_radius(content_, 0, 0);
emotion_label_ = lv_label_create(content_);
lv_obj_set_style_text_font(emotion_label_, large_icon_font, 0);
lv_label_set_text(emotion_label_, FONT_AWESOME_MICROCHIP_AI);
lv_obj_center(emotion_label_);
/* Right side */
side_bar_ = lv_obj_create(container_);
lv_obj_set_size(side_bar_, width_ - 32, 32);
lv_obj_set_flex_flow(side_bar_, LV_FLEX_FLOW_COLUMN);
lv_obj_set_style_pad_all(side_bar_, 0, 0);
lv_obj_set_style_border_width(side_bar_, 0, 0);
lv_obj_set_style_radius(side_bar_, 0, 0);
lv_obj_set_style_pad_row(side_bar_, 0, 0);
/* Status bar */
status_bar_ = lv_obj_create(side_bar_);
lv_obj_set_size(status_bar_, width_ - 32, 16);
lv_obj_set_style_radius(status_bar_, 0, 0);
lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW);
lv_obj_set_style_pad_all(status_bar_, 0, 0);
lv_obj_set_style_border_width(status_bar_, 0, 0);
lv_obj_set_style_pad_column(status_bar_, 0, 0);
status_label_ = lv_label_create(status_bar_);
lv_obj_set_flex_grow(status_label_, 1);
lv_obj_set_style_pad_left(status_label_, 2, 0);
lv_label_set_text(status_label_, Lang::Strings::INITIALIZING);
notification_label_ = lv_label_create(status_bar_);
lv_obj_set_flex_grow(notification_label_, 1);
lv_obj_set_style_pad_left(notification_label_, 2, 0);
lv_label_set_text(notification_label_, "");
lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN);
mute_label_ = lv_label_create(status_bar_);
lv_label_set_text(mute_label_, "");
lv_obj_set_style_text_font(mute_label_, icon_font, 0);
network_label_ = lv_label_create(status_bar_);
lv_label_set_text(network_label_, "");
lv_obj_set_style_text_font(network_label_, icon_font, 0);
battery_label_ = lv_label_create(status_bar_);
lv_label_set_text(battery_label_, "");
lv_obj_set_style_text_font(battery_label_, icon_font, 0);
chat_message_label_ = lv_label_create(side_bar_);
lv_obj_set_size(chat_message_label_, width_ - 32, LV_SIZE_CONTENT);
lv_obj_set_style_pad_left(chat_message_label_, 2, 0);
lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_SCROLL_CIRCULAR);
lv_label_set_text(chat_message_label_, "");
// 延迟一定的时间后开始滚动字幕
static lv_anim_t a;
lv_anim_init(&a);
lv_anim_set_delay(&a, 1000);
lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
lv_obj_set_style_anim(chat_message_label_, &a, LV_PART_MAIN);
lv_obj_set_style_anim_duration(chat_message_label_, lv_anim_speed_clamped(60, 300, 60000), LV_PART_MAIN);
}
void OledDisplay::SetEmotion(const char* emotion) {
const char* utf8 = font_awesome_get_utf8(emotion);
DisplayLockGuard lock(this);
if (emotion_label_ == nullptr) {
return;
}
if (utf8 != nullptr) {
lv_label_set_text(emotion_label_, utf8);
} else {
lv_label_set_text(emotion_label_, FONT_AWESOME_NEUTRAL);
}
}
void OledDisplay::SetTheme(Theme* theme) {
DisplayLockGuard lock(this);
auto lvgl_theme = static_cast<LvglTheme*>(theme);
auto text_font = lvgl_theme->text_font()->font();
auto screen = lv_screen_active();
lv_obj_set_style_text_font(screen, text_font, 0);
}
#include "oled_display.h"
#include "assets/lang_config.h"
#include "lvgl_theme.h"
#include "lvgl_font.h"
#include <string>
#include <algorithm>
#include <esp_log.h>
#include <esp_err.h>
#include <esp_lvgl_port.h>
#include <font_awesome.h>
#define TAG "OledDisplay"
LV_FONT_DECLARE(BUILTIN_TEXT_FONT);
LV_FONT_DECLARE(BUILTIN_ICON_FONT);
LV_FONT_DECLARE(font_awesome_30_1);
OledDisplay::OledDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
int width, int height, bool mirror_x, bool mirror_y)
: panel_io_(panel_io), panel_(panel) {
width_ = width;
height_ = height;
auto text_font = std::make_shared<LvglBuiltInFont>(&BUILTIN_TEXT_FONT);
auto icon_font = std::make_shared<LvglBuiltInFont>(&BUILTIN_ICON_FONT);
auto large_icon_font = std::make_shared<LvglBuiltInFont>(&font_awesome_30_1);
auto dark_theme = new LvglTheme("dark");
dark_theme->set_text_font(text_font);
dark_theme->set_icon_font(icon_font);
dark_theme->set_large_icon_font(large_icon_font);
auto& theme_manager = LvglThemeManager::GetInstance();
theme_manager.RegisterTheme("dark", dark_theme);
current_theme_ = dark_theme;
ESP_LOGI(TAG, "Initialize LVGL");
lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG();
port_cfg.task_priority = 1;
port_cfg.task_stack = 6144;
#if CONFIG_SOC_CPU_CORES_NUM > 1
port_cfg.task_affinity = 1;
#endif
lvgl_port_init(&port_cfg);
ESP_LOGI(TAG, "Adding OLED display");
const lvgl_port_display_cfg_t display_cfg = {
.io_handle = panel_io_,
.panel_handle = panel_,
.control_handle = nullptr,
.buffer_size = static_cast<uint32_t>(width_ * height_),
.double_buffer = false,
.trans_size = 0,
.hres = static_cast<uint32_t>(width_),
.vres = static_cast<uint32_t>(height_),
.monochrome = true,
.rotation = {
.swap_xy = false,
.mirror_x = mirror_x,
.mirror_y = mirror_y,
},
.flags = {
.buff_dma = 1,
.buff_spiram = 0,
.sw_rotate = 0,
.full_refresh = 0,
.direct_mode = 0,
},
};
display_ = lvgl_port_add_disp(&display_cfg);
if (display_ == nullptr) {
ESP_LOGE(TAG, "Failed to add display");
return;
}
if (height_ == 64) {
SetupUI_128x64();
} else {
SetupUI_128x32();
}
}
OledDisplay::~OledDisplay() {
if (content_ != nullptr) {
lv_obj_del(content_);
}
if (status_bar_ != nullptr) {
lv_obj_del(status_bar_);
}
if (side_bar_ != nullptr) {
lv_obj_del(side_bar_);
}
if (container_ != nullptr) {
lv_obj_del(container_);
}
if (panel_ != nullptr) {
esp_lcd_panel_del(panel_);
}
if (panel_io_ != nullptr) {
esp_lcd_panel_io_del(panel_io_);
}
lvgl_port_deinit();
}
bool OledDisplay::Lock(int timeout_ms) {
return lvgl_port_lock(timeout_ms);
}
void OledDisplay::Unlock() {
lvgl_port_unlock();
}
void OledDisplay::SetChatMessage(const char* role, const char* content) {
DisplayLockGuard lock(this);
if (chat_message_label_ == nullptr) {
return;
}
// Replace all newlines with spaces
std::string content_str = content;
std::replace(content_str.begin(), content_str.end(), '\n', ' ');
if (content_right_ == nullptr) {
lv_label_set_text(chat_message_label_, content_str.c_str());
} else {
if (content == nullptr || content[0] == '\0') {
lv_obj_add_flag(content_right_, LV_OBJ_FLAG_HIDDEN);
} else {
lv_label_set_text(chat_message_label_, content_str.c_str());
lv_obj_remove_flag(content_right_, LV_OBJ_FLAG_HIDDEN);
}
}
}
void OledDisplay::SetupUI_128x64() {
DisplayLockGuard lock(this);
auto lvgl_theme = static_cast<LvglTheme*>(current_theme_);
auto text_font = lvgl_theme->text_font()->font();
auto icon_font = lvgl_theme->icon_font()->font();
auto large_icon_font = lvgl_theme->large_icon_font()->font();
auto screen = lv_screen_active();
lv_obj_set_style_text_font(screen, text_font, 0);
lv_obj_set_style_text_color(screen, lv_color_black(), 0);
/* Container */
container_ = lv_obj_create(screen);
lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES);
lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_COLUMN);
lv_obj_set_style_pad_all(container_, 0, 0);
lv_obj_set_style_border_width(container_, 0, 0);
lv_obj_set_style_pad_row(container_, 0, 0);
/* Status bar */
status_bar_ = lv_obj_create(container_);
lv_obj_set_size(status_bar_, LV_HOR_RES, 16);
lv_obj_set_style_border_width(status_bar_, 0, 0);
lv_obj_set_style_pad_all(status_bar_, 0, 0);
lv_obj_set_style_radius(status_bar_, 0, 0);
/* Content */
content_ = lv_obj_create(container_);
lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF);
lv_obj_set_style_radius(content_, 0, 0);
lv_obj_set_style_pad_all(content_, 0, 0);
lv_obj_set_width(content_, LV_HOR_RES);
lv_obj_set_flex_grow(content_, 1);
lv_obj_set_flex_flow(content_, LV_FLEX_FLOW_ROW);
lv_obj_set_style_flex_main_place(content_, LV_FLEX_ALIGN_CENTER, 0);
// 创建左侧固定宽度的容器
content_left_ = lv_obj_create(content_);
lv_obj_set_size(content_left_, 32, LV_SIZE_CONTENT); // 固定宽度32像素
lv_obj_set_style_pad_all(content_left_, 0, 0);
lv_obj_set_style_border_width(content_left_, 0, 0);
emotion_label_ = lv_label_create(content_left_);
lv_obj_set_style_text_font(emotion_label_, large_icon_font, 0);
lv_label_set_text(emotion_label_, FONT_AWESOME_MICROCHIP_AI);
lv_obj_center(emotion_label_);
lv_obj_set_style_pad_top(emotion_label_, 8, 0);
// 创建右侧可扩展的容器
content_right_ = lv_obj_create(content_);
lv_obj_set_size(content_right_, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
lv_obj_set_style_pad_all(content_right_, 0, 0);
lv_obj_set_style_border_width(content_right_, 0, 0);
lv_obj_set_flex_grow(content_right_, 1);
lv_obj_add_flag(content_right_, LV_OBJ_FLAG_HIDDEN);
chat_message_label_ = lv_label_create(content_right_);
lv_label_set_text(chat_message_label_, "");
lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_SCROLL_CIRCULAR);
lv_obj_set_style_text_align(chat_message_label_, LV_TEXT_ALIGN_LEFT, 0);
lv_obj_set_width(chat_message_label_, width_ - 32);
lv_obj_set_style_pad_top(chat_message_label_, 14, 0);
// 延迟一定的时间后开始滚动字幕
static lv_anim_t a;
lv_anim_init(&a);
lv_anim_set_delay(&a, 1000);
lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
lv_obj_set_style_anim(chat_message_label_, &a, LV_PART_MAIN);
lv_obj_set_style_anim_duration(chat_message_label_, lv_anim_speed_clamped(60, 300, 60000), LV_PART_MAIN);
/* Status bar */
lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW);
lv_obj_set_style_pad_all(status_bar_, 0, 0);
lv_obj_set_style_border_width(status_bar_, 0, 0);
lv_obj_set_style_pad_column(status_bar_, 0, 0);
network_label_ = lv_label_create(status_bar_);
lv_label_set_text(network_label_, "");
lv_obj_set_style_text_font(network_label_, icon_font, 0);
notification_label_ = lv_label_create(status_bar_);
lv_obj_set_flex_grow(notification_label_, 1);
lv_obj_set_style_text_align(notification_label_, LV_TEXT_ALIGN_CENTER, 0);
lv_label_set_text(notification_label_, "");
lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN);
status_label_ = lv_label_create(status_bar_);
lv_obj_set_flex_grow(status_label_, 1);
lv_label_set_text(status_label_, Lang::Strings::INITIALIZING);
lv_obj_set_style_text_align(status_label_, LV_TEXT_ALIGN_CENTER, 0);
mute_label_ = lv_label_create(status_bar_);
lv_label_set_text(mute_label_, "");
lv_obj_set_style_text_font(mute_label_, icon_font, 0);
battery_label_ = lv_label_create(status_bar_);
lv_label_set_text(battery_label_, "");
lv_obj_set_style_text_font(battery_label_, icon_font, 0);
low_battery_popup_ = lv_obj_create(screen);
lv_obj_set_scrollbar_mode(low_battery_popup_, LV_SCROLLBAR_MODE_OFF);
lv_obj_set_size(low_battery_popup_, LV_HOR_RES * 0.9, text_font->line_height * 2);
lv_obj_align(low_battery_popup_, LV_ALIGN_BOTTOM_MID, 0, 0);
lv_obj_set_style_bg_color(low_battery_popup_, lv_color_black(), 0);
lv_obj_set_style_radius(low_battery_popup_, 10, 0);
low_battery_label_ = lv_label_create(low_battery_popup_);
lv_label_set_text(low_battery_label_, Lang::Strings::BATTERY_NEED_CHARGE);
lv_obj_set_style_text_color(low_battery_label_, lv_color_white(), 0);
lv_obj_center(low_battery_label_);
lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN);
}
void OledDisplay::SetupUI_128x32() {
DisplayLockGuard lock(this);
auto lvgl_theme = static_cast<LvglTheme*>(current_theme_);
auto text_font = lvgl_theme->text_font()->font();
auto icon_font = lvgl_theme->icon_font()->font();
auto large_icon_font = lvgl_theme->large_icon_font()->font();
auto screen = lv_screen_active();
lv_obj_set_style_text_font(screen, text_font, 0);
/* Container */
container_ = lv_obj_create(screen);
lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES);
lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_ROW);
lv_obj_set_style_pad_all(container_, 0, 0);
lv_obj_set_style_border_width(container_, 0, 0);
lv_obj_set_style_pad_column(container_, 0, 0);
/* Emotion label on the left side */
content_ = lv_obj_create(container_);
lv_obj_set_size(content_, 32, 32);
lv_obj_set_style_pad_all(content_, 0, 0);
lv_obj_set_style_border_width(content_, 0, 0);
lv_obj_set_style_radius(content_, 0, 0);
emotion_label_ = lv_label_create(content_);
lv_obj_set_style_text_font(emotion_label_, large_icon_font, 0);
lv_label_set_text(emotion_label_, FONT_AWESOME_MICROCHIP_AI);
lv_obj_center(emotion_label_);
/* Right side */
side_bar_ = lv_obj_create(container_);
lv_obj_set_size(side_bar_, width_ - 32, 32);
lv_obj_set_flex_flow(side_bar_, LV_FLEX_FLOW_COLUMN);
lv_obj_set_style_pad_all(side_bar_, 0, 0);
lv_obj_set_style_border_width(side_bar_, 0, 0);
lv_obj_set_style_radius(side_bar_, 0, 0);
lv_obj_set_style_pad_row(side_bar_, 0, 0);
/* Status bar */
status_bar_ = lv_obj_create(side_bar_);
lv_obj_set_size(status_bar_, width_ - 32, 16);
lv_obj_set_style_radius(status_bar_, 0, 0);
lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW);
lv_obj_set_style_pad_all(status_bar_, 0, 0);
lv_obj_set_style_border_width(status_bar_, 0, 0);
lv_obj_set_style_pad_column(status_bar_, 0, 0);
status_label_ = lv_label_create(status_bar_);
lv_obj_set_flex_grow(status_label_, 1);
lv_obj_set_style_pad_left(status_label_, 2, 0);
lv_label_set_text(status_label_, Lang::Strings::INITIALIZING);
notification_label_ = lv_label_create(status_bar_);
lv_obj_set_flex_grow(notification_label_, 1);
lv_obj_set_style_pad_left(notification_label_, 2, 0);
lv_label_set_text(notification_label_, "");
lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN);
mute_label_ = lv_label_create(status_bar_);
lv_label_set_text(mute_label_, "");
lv_obj_set_style_text_font(mute_label_, icon_font, 0);
network_label_ = lv_label_create(status_bar_);
lv_label_set_text(network_label_, "");
lv_obj_set_style_text_font(network_label_, icon_font, 0);
battery_label_ = lv_label_create(status_bar_);
lv_label_set_text(battery_label_, "");
lv_obj_set_style_text_font(battery_label_, icon_font, 0);
chat_message_label_ = lv_label_create(side_bar_);
lv_obj_set_size(chat_message_label_, width_ - 32, LV_SIZE_CONTENT);
lv_obj_set_style_pad_left(chat_message_label_, 2, 0);
lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_SCROLL_CIRCULAR);
lv_label_set_text(chat_message_label_, "");
// 延迟一定的时间后开始滚动字幕
static lv_anim_t a;
lv_anim_init(&a);
lv_anim_set_delay(&a, 1000);
lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
lv_obj_set_style_anim(chat_message_label_, &a, LV_PART_MAIN);
lv_obj_set_style_anim_duration(chat_message_label_, lv_anim_speed_clamped(60, 300, 60000), LV_PART_MAIN);
}
void OledDisplay::SetEmotion(const char* emotion) {
const char* utf8 = font_awesome_get_utf8(emotion);
DisplayLockGuard lock(this);
if (emotion_label_ == nullptr) {
return;
}
if (utf8 != nullptr) {
lv_label_set_text(emotion_label_, utf8);
} else {
lv_label_set_text(emotion_label_, FONT_AWESOME_NEUTRAL);
}
}
void OledDisplay::SetTheme(Theme* theme) {
DisplayLockGuard lock(this);
auto lvgl_theme = static_cast<LvglTheme*>(theme);
auto text_font = lvgl_theme->text_font()->font();
auto screen = lv_screen_active();
lv_obj_set_style_text_font(screen, text_font, 0);
}

View File

@@ -1,39 +1,39 @@
#ifndef OLED_DISPLAY_H
#define OLED_DISPLAY_H
#include "lvgl_display.h"
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
class OledDisplay : public LvglDisplay {
private:
esp_lcd_panel_io_handle_t panel_io_ = nullptr;
esp_lcd_panel_handle_t panel_ = nullptr;
lv_obj_t* status_bar_ = nullptr;
lv_obj_t* content_ = nullptr;
lv_obj_t* content_left_ = nullptr;
lv_obj_t* content_right_ = nullptr;
lv_obj_t* container_ = nullptr;
lv_obj_t* side_bar_ = nullptr;
lv_obj_t *emotion_label_ = nullptr;
lv_obj_t* chat_message_label_ = nullptr;
virtual bool Lock(int timeout_ms = 0) override;
virtual void Unlock() override;
void SetupUI_128x64();
void SetupUI_128x32();
public:
OledDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, int width, int height, bool mirror_x, bool mirror_y);
~OledDisplay();
virtual void SetChatMessage(const char* role, const char* content) override;
virtual void SetEmotion(const char* emotion) override;
virtual void SetTheme(Theme* theme) override;
};
#endif // OLED_DISPLAY_H
#ifndef OLED_DISPLAY_H
#define OLED_DISPLAY_H
#include "lvgl_display.h"
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
class OledDisplay : public LvglDisplay {
private:
esp_lcd_panel_io_handle_t panel_io_ = nullptr;
esp_lcd_panel_handle_t panel_ = nullptr;
lv_obj_t* status_bar_ = nullptr;
lv_obj_t* content_ = nullptr;
lv_obj_t* content_left_ = nullptr;
lv_obj_t* content_right_ = nullptr;
lv_obj_t* container_ = nullptr;
lv_obj_t* side_bar_ = nullptr;
lv_obj_t *emotion_label_ = nullptr;
lv_obj_t* chat_message_label_ = nullptr;
virtual bool Lock(int timeout_ms = 0) override;
virtual void Unlock() override;
void SetupUI_128x64();
void SetupUI_128x32();
public:
OledDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, int width, int height, bool mirror_x, bool mirror_y);
~OledDisplay();
virtual void SetChatMessage(const char* role, const char* content) override;
virtual void SetEmotion(const char* emotion) override;
virtual void SetTheme(Theme* theme) override;
};
#endif // OLED_DISPLAY_H