Added lvgl support to generate images. Made basic example, grayscale background + text displayed everytime we call /api/display/image.png
This commit is contained in:
106
Provider/main/lv_setup.cpp
Normal file
106
Provider/main/lv_setup.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
#include "lv_setup.hpp"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
#include "freertos/task.h"
|
||||
#include "types.hpp"
|
||||
|
||||
internal const char *kTagLvgl = "LVGL";
|
||||
|
||||
SemaphoreHandle_t g_LvglMutex = nullptr;
|
||||
lv_display_t *g_LvglDisplay = nullptr;
|
||||
uint8_t *g_LvglDrawBuffer = nullptr;
|
||||
|
||||
internal void lvgl_tick_task(void *arg)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
|
||||
if (xSemaphoreTake(g_LvglMutex, portMAX_DELAY) == pdTRUE)
|
||||
{
|
||||
lv_timer_handler();
|
||||
xSemaphoreGive(g_LvglMutex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal uint32_t my_tick_get_cb()
|
||||
{
|
||||
return (uint32_t)(esp_timer_get_time() / 1000);
|
||||
}
|
||||
|
||||
internal void lv_dummy_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)
|
||||
{
|
||||
// Headless display, so we don't actually flush to SPI/I2C.
|
||||
// We just tell LVGL that the "flush" is completed so it unblocks wait_for_flushing.
|
||||
lv_display_flush_ready(disp);
|
||||
}
|
||||
|
||||
internal void lv_draw_sample_ui()
|
||||
{
|
||||
lv_obj_t *scr = lv_screen_active();
|
||||
// Default background to white for the grayscale PNG
|
||||
lv_obj_set_style_bg_color(scr, lv_color_white(), 0);
|
||||
|
||||
lv_obj_t *label = lv_label_create(scr);
|
||||
lv_label_set_text(label, "Calendink Provider\nLVGL Headless Renderer");
|
||||
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
|
||||
}
|
||||
|
||||
void setup_lvgl()
|
||||
{
|
||||
ESP_LOGI(kTagLvgl, "Initializing LVGL");
|
||||
|
||||
g_LvglMutex = xSemaphoreCreateMutex();
|
||||
|
||||
lv_init();
|
||||
lv_tick_set_cb(my_tick_get_cb);
|
||||
|
||||
uint32_t width = CONFIG_CALENDINK_DISPLAY_WIDTH;
|
||||
uint32_t height = CONFIG_CALENDINK_DISPLAY_HEIGHT;
|
||||
|
||||
// Create a virtual display
|
||||
g_LvglDisplay = lv_display_create(width, height);
|
||||
lv_display_set_flush_cb(g_LvglDisplay, lv_dummy_flush_cb);
|
||||
|
||||
// Initialize LodePNG custom bump allocator
|
||||
lodepng_allocator_init();
|
||||
|
||||
// Allocate draw buffers in PSRAM
|
||||
// Using LV_COLOR_FORMAT_L8 (1 byte per pixel)
|
||||
size_t buf_size = LV_DRAW_BUF_SIZE(width, height, LV_COLOR_FORMAT_L8);
|
||||
|
||||
// Fallback to MALLOC_CAP_DEFAULT if we can't get SPIRAM (for debugging without it)
|
||||
void *buf1 = heap_caps_malloc(buf_size, MALLOC_CAP_SPIRAM);
|
||||
if (!buf1)
|
||||
{
|
||||
ESP_LOGW(kTagLvgl, "Failed to allocate LVGL draw buffer in PSRAM, falling back to internal RAM");
|
||||
buf1 = heap_caps_malloc(buf_size, MALLOC_CAP_DEFAULT);
|
||||
}
|
||||
|
||||
if (!buf1)
|
||||
{
|
||||
ESP_LOGE(kTagLvgl, "Failed to allocate LVGL draw buffer entirely.");
|
||||
return;
|
||||
}
|
||||
|
||||
g_LvglDrawBuffer = (uint8_t *)buf1;
|
||||
|
||||
lv_display_set_buffers(g_LvglDisplay, buf1, nullptr, buf_size, LV_DISPLAY_RENDER_MODE_FULL);
|
||||
|
||||
// Explicitly set the color format of the display if it's set in sdkconfig/driver
|
||||
lv_display_set_color_format(g_LvglDisplay, LV_COLOR_FORMAT_L8);
|
||||
|
||||
// Create the background task for the LVGL timer
|
||||
xTaskCreate(lvgl_tick_task, "LVGL Tick", 4096, nullptr, 5, nullptr);
|
||||
|
||||
// Draw the sample UI
|
||||
if (xSemaphoreTake(g_LvglMutex, portMAX_DELAY) == pdTRUE)
|
||||
{
|
||||
lv_draw_sample_ui();
|
||||
xSemaphoreGive(g_LvglMutex);
|
||||
}
|
||||
|
||||
ESP_LOGI(kTagLvgl, "LVGL fully initialized. Display %lux%lu created.", width, height);
|
||||
}
|
||||
Reference in New Issue
Block a user