fix(color): improve bitmap error handling (#7509)

This commit is contained in:
philmoz
2026-07-04 18:47:02 +10:00
committed by GitHub
parent 3da464f178
commit f503b11854
15 changed files with 105 additions and 86 deletions
+31 -21
View File
@@ -19,16 +19,17 @@
#include "bitmapbuffer.h"
#include <math.h>
#include <string>
#include "bitmaps.h"
#include "board.h"
#include "dma2d.h"
#include "fonts.h"
#include "lvgl/src/draw/sw/lv_draw_sw.h"
#include "edgetx_helpers.h"
#include "strhelpers.h"
#if !defined(BOOT)
#include "window.h"
#endif
BitmapBuffer::BitmapBuffer(uint8_t format, uint16_t width, uint16_t height) :
format(format),
@@ -44,12 +45,6 @@ BitmapBuffer::BitmapBuffer(uint8_t format, uint16_t width, uint16_t height) :
{
data = (uint16_t *)malloc(align32(width * height * sizeof(uint16_t)));
data_end = data + (width * height);
#if !defined(BOOT)
// Assume we need a canvas here
canvas = lv_canvas_create(nullptr);
lv_canvas_set_buffer(canvas, data, width, height, LV_IMG_CF_TRUE_COLOR);
#endif
}
BitmapBuffer::BitmapBuffer(uint8_t format, uint16_t width, uint16_t height,
@@ -71,15 +66,30 @@ BitmapBuffer::BitmapBuffer(uint8_t format, uint16_t width, uint16_t height,
BitmapBuffer::~BitmapBuffer()
{
DMAWait();
if (dataAllocated) {
#if !defined(BOOT)
lv_obj_del(canvas);
#endif
free(data);
}
}
#if !defined(BOOT)
lv_obj_t* BitmapBuffer::addCanvas(Window* parent, lv_img_cf_t fmt)
{
removeCanvas();
canvas = lv_canvas_create(parent ? parent->getLvObj() : nullptr);
lv_canvas_set_buffer(canvas, data, _width, _height, fmt);
lv_obj_center(canvas);
return canvas;
}
void BitmapBuffer::removeCanvas()
{
if (canvas) {
lv_obj_del(canvas);
canvas = nullptr;
}
}
#endif
void BitmapBuffer::setData(uint16_t *d)
{
if (!dataAllocated) {
@@ -151,12 +161,6 @@ bool BitmapBuffer::applyClippingRect(coord_t& x, coord_t& y, coord_t& w,
return data && h > 0 && w > 0;
}
void BitmapBuffer::setOffset(coord_t offsetX, coord_t offsetY)
{
this->offsetX = offsetX;
this->offsetY = offsetY;
}
void BitmapBuffer::drawBitmap(coord_t x, coord_t y, const BitmapBuffer *bmp,
coord_t srcx, coord_t srcy, coord_t srcw,
coord_t srch, float scale)
@@ -460,7 +464,13 @@ coord_t BitmapBuffer::drawSizedText(coord_t x, coord_t y, const char *s,
uint8_t len, LcdFlags flags)
{
if (!s) return x;
MOVE_OFFSET();
coord_t offsetX = this->offsetX;
x += offsetX;
this->offsetX = 0;
coord_t offsetY = this->offsetY;
y += offsetY;
this->offsetY = 0;
// LVGL does not handle non-null terminated strings
static char buffer[256];
@@ -522,8 +532,8 @@ coord_t BitmapBuffer::drawSizedText(coord_t x, coord_t y, const char *s,
}
#endif
RESTORE_OFFSET();
this->offsetX = offsetX;
this->offsetY = offsetY;
pos += p.x;
return ((flags & RIGHT) ? orig_pos : pos) - offsetX;
+14 -20
View File
@@ -26,31 +26,20 @@
struct MaskBitmap;
class TelemetryItem;
class Window;
constexpr uint8_t SOLID = 0xFF;
constexpr uint8_t DOTTED = 0x55;
constexpr uint8_t STASHED = 0x33;
#define MOVE_OFFSET() \
coord_t offsetX = this->offsetX; \
x += offsetX; \
this->offsetX = 0; \
coord_t offsetY = this->offsetY; \
y += offsetY; \
this->offsetY = 0
#define APPLY_OFFSET() \
x += this->offsetX; \
y += this->offsetY
#define RESTORE_OFFSET() this->offsetX = offsetX, this->offsetY = offsetY
#define MOVE_PIXEL_RIGHT(p, count) p += count
#define MOVE_TO_NEXT_RIGHT_PIXEL(p) MOVE_PIXEL_RIGHT(p, 1)
#define USE_STB
enum BitmapFormats { BMP_INVALID = -1, BMP_RGB565 = 0, BMP_ARGB4444 };
class BitmapBuffer
@@ -140,20 +129,18 @@ class BitmapBuffer
void clearClippingRect();
void setOffset(coord_t offsetX, coord_t offsetY);
inline void clearOffset() { setOffset(0, 0); }
inline void setOffset(coord_t offsetX, coord_t offsetY)
{
this->offsetX = offsetX;
this->offsetY = offsetY;
}
inline void reset()
{
clearOffset();
setOffset(0, 0);
clearClippingRect();
}
coord_t getOffsetX() const { return offsetX; }
coord_t getOffsetY() const { return offsetY; }
inline uint16_t width() const { return _width; }
inline uint16_t height() const { return _height; }
@@ -161,6 +148,11 @@ class BitmapBuffer
uint32_t getDataSize() const { return _width * _height * sizeof(pixel_t); }
#if !defined(BOOT)
lv_obj_t* addCanvas(Window* parent, lv_img_cf_t fmt = LV_IMG_CF_TRUE_COLOR);
void removeCanvas();
#endif
// Lua API functions
void setClippingRect(coord_t xmin, coord_t xmax, coord_t ymin, coord_t ymax);
void getClippingRect(coord_t& xmin, coord_t& xmax, coord_t& ymin,
@@ -259,6 +251,8 @@ class BitmapBuffer
#if defined(DEBUG)
bool leakReported;
#endif
#if !defined(BOOT)
lv_obj_t* canvas = nullptr;
#endif
lv_draw_ctx_t* draw_ctx = nullptr;
};
@@ -125,7 +125,9 @@ BitmapBuffer *BitmapBuffer::loadBitmap(const char *filename, BitmapFormats fmt)
}
BitmapBuffer *bmp = new BitmapBuffer(dst_fmt, w, h);
if (bmp == nullptr) {
if (bmp == nullptr || bmp->getData() == nullptr) {
if (bmp) delete bmp;
stbi_image_free(img);
TRACE_ERROR("loadBitmap: malloc failed\n");
return nullptr;
}
+12 -1
View File
@@ -30,5 +30,16 @@ FilePreview::FilePreview(Window *parent, const rect_t &rect) :
void FilePreview::setFile(const char *filename)
{
setSource(filename ? filename : "");
show(hasImage());
if (!hasImage() && filename) {
if (!errorIcon) {
errorIcon = new StaticIcon(this, 0, 0, ICON_ERROR, COLOR_THEME_PRIMARY1_INDEX);
lv_obj_center(errorIcon->getLvObj());
}
errorIcon->show();
show();
} else {
if (errorIcon)
errorIcon->hide();
show(hasImage());
}
}
@@ -33,4 +33,7 @@ class FilePreview : public StaticBitmap
#endif
void setFile(const char *filename);
protected:
StaticIcon* errorIcon = nullptr;
};
+6 -9
View File
@@ -44,9 +44,6 @@ MainWindow::MainWindow() : Window(nullptr, {0, 0, LCD_W, LCD_H})
setWindowFlag(OPAQUE);
etx_solid_bg(lvobj);
background = lv_canvas_create(lvobj);
lv_obj_center(background);
}
void MainWindow::emptyTrash()
@@ -105,9 +102,7 @@ void MainWindow::shutdown()
clear();
emptyTrash();
// Re-add background canvas
background = lv_canvas_create(lvobj);
lv_obj_center(background);
backgroundBitmap = nullptr;
}
bool MainWindow::setBackgroundImage(std::string& fileName)
@@ -115,14 +110,16 @@ bool MainWindow::setBackgroundImage(std::string& fileName)
if (fileName.empty()) return false;
// ensure you delete old bitmap
if (backgroundBitmap != nullptr) delete backgroundBitmap;
if (backgroundBitmap != nullptr) {
backgroundBitmap->removeCanvas();
delete backgroundBitmap;
}
// Try to load bitmap.
backgroundBitmap = BitmapBuffer::loadBitmap(fileName.c_str(), BMP_RGB565);
if (backgroundBitmap) {
lv_canvas_set_buffer(background, backgroundBitmap->getData(), backgroundBitmap->width(),
backgroundBitmap->height(), LV_IMG_CF_TRUE_COLOR);
lv_obj_move_background(backgroundBitmap->addCanvas(this));
return true;
}
+1 -2
View File
@@ -56,8 +56,7 @@ class MainWindow: public Window
void blockUntilClose(bool checkPwr, std::function<bool(void)> closeCondition, bool isError = false);
protected:
lv_obj_t* background = nullptr;
const BitmapBuffer *backgroundBitmap = nullptr;
BitmapBuffer *backgroundBitmap = nullptr;
bool widgetRefreshEnable = true;
static MainWindow * _instance;
+7 -10
View File
@@ -284,25 +284,22 @@ StaticBitmap::StaticBitmap(Window* parent, const rect_t& rect,
void StaticBitmap::setSource(const char *filename)
{
if (filename) {
if (img) delete img;
clearSource();
img = BitmapBuffer::loadBitmap(filename, BMP_ARGB4444);
if (img) {
img->resizeToLVGL(width(), height());
if (canvas) lv_obj_del(canvas);
canvas = lv_canvas_create(lvobj);
lv_obj_center(canvas);
lv_canvas_set_buffer(canvas, img->getData(), img->width(), img->height(),
LV_IMG_CF_TRUE_COLOR_ALPHA);
img->addCanvas(this, LV_IMG_CF_TRUE_COLOR_ALPHA);
}
}
}
void StaticBitmap::clearSource()
{
if (img) delete img;
if (img) {
img->removeCanvas();
delete img;
}
img = nullptr;
if (canvas) lv_obj_del(canvas);
canvas = nullptr;
}
StaticBitmap::~StaticBitmap()
@@ -312,7 +309,7 @@ StaticBitmap::~StaticBitmap()
bool StaticBitmap::hasImage() const
{
return img && canvas;
return img;
}
//-----------------------------------------------------------------------------
-1
View File
@@ -182,7 +182,6 @@ class StaticBitmap : public Window
bool hasImage() const;
protected:
lv_obj_t *canvas = nullptr;
BitmapBuffer *img = nullptr;
};
@@ -126,7 +126,6 @@ class FunctionSwitchesBase : public Page
void addQRCode();
protected:
BitmapBuffer* qrcode = nullptr;
StaticText* startupHeader = nullptr;
#if defined(FUNCTION_SWITCHES_RGB_LEDS)
Messaging previewMsg;
@@ -236,6 +236,7 @@ void RadioSdManagerPage::build(Window * window)
});
browser->setFileSelected([=](const char* path, const char* name, const char* fullpath, bool isDir) {
preview->setFile(nullptr);
loadPreview = 0;
loading->hide();
if (fullpath && !isDir) {
auto ext = getFileExtension(fullpath);
+1 -5
View File
@@ -152,17 +152,13 @@ StandaloneLuaWindow::StandaloneLuaWindow(bool useLvgl, int initFn, int runFn) :
lv_label_set_text(lbl, STR_LOADING);
} else {
lcdBuffer = new BitmapBuffer(BMP_RGB565, LCD_W, LCD_H);
lcdBuffer->addCanvas(this);
lcdBuffer->clear();
lcdBuffer->drawText(LCD_W / 2, LCD_H / 2 - EdgeTxStyles::STD_FONT_HEIGHT, STR_LOADING,
FONT(L) | COLOR_THEME_PRIMARY2 | CENTERED);
setWindowFlag(NO_FOCUS | NO_SCROLL);
auto canvas = lv_canvas_create(lvobj);
lv_obj_center(canvas);
lv_canvas_set_buffer(canvas, lcdBuffer->getData(),
lcdBuffer->width(), lcdBuffer->height(), LV_IMG_CF_TRUE_COLOR);
lv_group_add_obj(lv_group_get_default(), lvobj);
lv_group_set_editing(lv_group_get_default(), true);
}
+4 -10
View File
@@ -139,7 +139,6 @@ const int8_t bmp_shutdown_yo[] = {-SHUTDOWN_CIRCLE_RADIUS, 0, 0,
static Window* shutdownWindow = nullptr;
static StaticIcon* shutdownAnim[4] = {nullptr};
static BitmapBuffer* shutdownSplashImg = nullptr;
static lv_obj_t* shutdownCanvas = nullptr;
void drawSleepBitmap()
{
@@ -164,7 +163,6 @@ void cancelShutdownAnimation()
if (shutdownWindow) {
shutdownWindow->deleteLater();
shutdownWindow = nullptr;
shutdownCanvas = nullptr;
for (int i = 0; i < 4; i += 1) shutdownAnim[i] = nullptr;
}
}
@@ -180,17 +178,13 @@ void drawShutdownAnimation(uint32_t duration, uint32_t totalDuration,
shutdownWindow->setWindowFlag(OPAQUE);
etx_solid_bg(shutdownWindow->getLvObj(), COLOR_THEME_PRIMARY1_INDEX);
if (sdMounted() && !shutdownSplashImg)
if (sdMounted() && !shutdownSplashImg) {
shutdownSplashImg = BitmapBuffer::loadBitmap(
BITMAPS_PATH "/" SHUTDOWN_SPLASH_FILE, BMP_RGB565);
if (shutdownSplashImg) {
shutdownCanvas = lv_canvas_create(shutdownWindow->getLvObj());
lv_obj_center(shutdownCanvas);
lv_canvas_set_buffer(shutdownCanvas, shutdownSplashImg->getData(),
shutdownSplashImg->width(),
shutdownSplashImg->height(), LV_IMG_CF_TRUE_COLOR);
if (shutdownSplashImg)
shutdownSplashImg->addCanvas(shutdownWindow);
}
(new StaticIcon(shutdownWindow, 0, 0, ICON_SHUTDOWN, COLOR_THEME_PRIMARY2_INDEX))
->center(LCD_W, LCD_H);
+1 -4
View File
@@ -79,8 +79,6 @@ const char YAMLFILE_CHECKSUM_TAG_NAME[] = "checksum";
#define LOGS_EXT ".csv"
#define SOUNDS_EXT ".wav"
#define BMP_EXT ".bmp"
#define PNG_EXT ".png"
#define JPG_EXT ".jpg"
#define SCRIPT_EXT ".lua"
#define SCRIPT_BIN_EXT ".luac"
#define TEXT_EXT ".txt"
@@ -96,8 +94,7 @@ const char YAMLFILE_CHECKSUM_TAG_NAME[] = "checksum";
#define YAML_EXT ".yml"
#if defined(COLORLCD)
#define BITMAPS_EXT BMP_EXT JPG_EXT PNG_EXT
#define LEN_BITMAPS_EXT 4
#define BITMAPS_EXT BMP_EXT ".png" ".jpg" ".jpeg"
#else
#define BITMAPS_EXT BMP_EXT
#endif
+21 -1
View File
@@ -99,6 +99,7 @@ bool checkScreenshot_colorlcd(const BitmapBuffer* dc, const char* test)
TEST(Lcd_colorlcd, lines)
{
BitmapBuffer dc(BMP_RGB565, LCD_W, LCD_H);
dc.addCanvas(nullptr);
dc.clear(COLOR_THEME_SECONDARY3);
dc.setClippingRect(50, 400, 50, 230);
@@ -113,11 +114,13 @@ TEST(Lcd_colorlcd, lines)
dc.clearClippingRect();
EXPECT_TRUE(checkScreenshot_colorlcd(&dc, "lines"));
dc.removeCanvas();
}
TEST(Lcd_colorlcd, vline)
{
BitmapBuffer dc(BMP_RGB565, LCD_W, LCD_H);
dc.addCanvas(nullptr);
dc.clear(COLOR_THEME_SECONDARY3);
@@ -125,11 +128,13 @@ TEST(Lcd_colorlcd, vline)
dc.drawVerticalLine(x, x / 2, 12, SOLID, COLOR_THEME_SECONDARY1);
}
EXPECT_TRUE(checkScreenshot_colorlcd(&dc, "vline"));
dc.removeCanvas();
}
TEST(Lcd_colorlcd, primitives)
{
BitmapBuffer dc(BMP_RGB565, LCD_W, LCD_H);
dc.addCanvas(nullptr);
dc.clear(COLOR_THEME_SECONDARY3);
@@ -156,11 +161,13 @@ TEST(Lcd_colorlcd, primitives)
dc.drawHorizontalLine(30, 85, 70, SOLID, COLOR_THEME_SECONDARY1);
EXPECT_TRUE(checkScreenshot_colorlcd(&dc, "primitives_" TRANSLATIONS));
dc.removeCanvas();
}
TEST(Lcd_colorlcd, transparency)
{
BitmapBuffer dc(BMP_RGB565, LCD_W, LCD_H);
dc.addCanvas(nullptr);
dc.clear(COLOR_THEME_SECONDARY3);
@@ -198,6 +205,7 @@ TEST(Lcd_colorlcd, transparency)
}
EXPECT_TRUE(checkScreenshot_colorlcd(&dc, "transparency_" TRANSLATIONS));
dc.removeCanvas();
}
//
@@ -210,7 +218,8 @@ TEST(Lcd_colorlcd, transparency)
TEST(Lcd_colorlcd, fonts)
{
BitmapBuffer dc(BMP_RGB565, LCD_W, LCD_H);
dc.addCanvas(nullptr);
dc.clear(COLOR_THEME_SECONDARY3);
dc.drawText(8, 8, "The quick brown fox jumps over the lazy dog", COLOR_THEME_SECONDARY1);
@@ -226,12 +235,14 @@ TEST(Lcd_colorlcd, fonts)
dc.drawText(5, 205, "The quick brown fox jumps over the lazy dog", COLOR_THEME_SECONDARY1);
EXPECT_TRUE(checkScreenshot_colorlcd(&dc, "fonts_" TRANSLATIONS));
dc.removeCanvas();
}
#endif
TEST(Lcd_colorlcd, clipping)
{
BitmapBuffer dc(BMP_RGB565, LCD_W, LCD_H);
dc.addCanvas(nullptr);
dc.clear(COLOR_THEME_SECONDARY3);
@@ -259,11 +270,13 @@ TEST(Lcd_colorlcd, clipping)
dc.clearClippingRect();
EXPECT_TRUE(checkScreenshot_colorlcd(&dc, "clipping"));
dc.removeCanvas();
}
TEST(Lcd_colorlcd, bitmap)
{
BitmapBuffer dc(BMP_RGB565, LCD_W, LCD_H);
dc.addCanvas(nullptr);
dc.clear(COLOR_THEME_SECONDARY3);
@@ -275,6 +288,7 @@ TEST(Lcd_colorlcd, bitmap)
dc.clearClippingRect();
EXPECT_TRUE(checkScreenshot_colorlcd(&dc, "bitmap"));
dc.removeCanvas();
}
static const uint8_t mask_menu_radio[] = {
@@ -286,6 +300,7 @@ extern MaskBitmap* _decompressed_mask(const uint8_t* lz4_compressed);
TEST(Lcd_colorlcd, masks)
{
BitmapBuffer dc(BMP_RGB565, LCD_W, LCD_H);
dc.addCanvas(nullptr);
dc.clear(COLOR_THEME_SECONDARY3);
@@ -299,6 +314,7 @@ TEST(Lcd_colorlcd, masks)
EXPECT_TRUE(checkScreenshot_colorlcd(&dc, "masks"));
free(mask);
dc.removeCanvas();
}
#if 0
@@ -337,6 +353,7 @@ TEST(Lcd_colorlcd, masks)
TEST(Lcd_colorlcd, extra_font)
{
BitmapBuffer dc(BMP_RGB565, LCD_W, LCD_H);
dc.addCanvas(nullptr);
dc.clear(COLOR_THEME_SECONDARY3);
dc.drawText(10, 25, EXTRA_FULL, COLOR_THEME_SECONDARY1 | FONT(XXS));
@@ -347,6 +364,7 @@ TEST(Lcd_colorlcd, extra_font)
dc.drawText(10, 184, EXTRA_TEST2, COLOR_THEME_SECONDARY1 | FONT(XXL));
EXPECT_TRUE(checkScreenshot_colorlcd(&dc, "extra"));
dc.removeCanvas();
}
constexpr coord_t LBM_USB_PLUGGED_W = 211;
@@ -359,6 +377,7 @@ const uint8_t LBM_USB_PLUGGED[] = {
TEST(Lcd_colorlcd, darkmode)
{
BitmapBuffer dc(BMP_RGB565, LCD_W, LCD_H);
dc.addCanvas(nullptr);
dc.clear(COLOR_BLACK);
@@ -370,6 +389,7 @@ TEST(Lcd_colorlcd, darkmode)
COLOR2FLAGS(RGB(0, 0, 0xFF)));
EXPECT_TRUE(checkScreenshot_colorlcd(&dc, "darkmode_" TRANSLATIONS));
dc.removeCanvas();
}
#endif