Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions radio/src/gui/colorlcd/libui/bitmapbuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ class BitmapBuffer

inline uint16_t width() const { return _width; }
inline uint16_t height() const { return _height; }
inline uint8_t getFormat() const { return format; }

inline pixel_t* getData() const { return data; }

Expand Down
2 changes: 1 addition & 1 deletion radio/src/gui/colorlcd/libui/file_preview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ FilePreview::FilePreview(Window *parent, const rect_t &rect) :

void FilePreview::setFile(const char *filename)
{
setSource(filename ? filename : "");
setSource(filename ? filename : "", BMP_ARGB4444);
if (!hasImage() && filename) {
if (!errorIcon) {
errorIcon = new StaticIcon(this, 0, 0, ICON_ERROR, COLOR_THEME_PRIMARY1_INDEX);
Expand Down
34 changes: 25 additions & 9 deletions radio/src/gui/colorlcd/libui/static.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -273,28 +273,44 @@ void StaticImage::setZoom()
// Display image from file system with software scaling
// - uglier but much faster than LVGL scaling
StaticBitmap::StaticBitmap(Window* parent, const rect_t& rect,
const char* filename) :
const char* filename, BitmapFormats fmt) :
Window(parent, rect)
{
setWindowFlag(NO_FOCUS | NO_CLICK);

setSource(filename);
setSource(filename, fmt);

onClosing([=]() {
if (img) delete img;
img = nullptr;
});
}

void StaticBitmap::setSource(const char *filename)
void StaticBitmap::setSource(const char *filename, BitmapFormats fmt)
{
if (filename) {
clearSource();
img = BitmapBuffer::loadBitmap(filename, BMP_ARGB4444);
if (img) {
img->resizeToLVGL(width(), height());
img->addCanvas(this, LV_IMG_CF_TRUE_COLOR_ALPHA);
if (!filename) return;

clearSource();
img = BitmapBuffer::loadBitmap(filename, fmt);
if (!img) return;

if (img->getFormat() == BMP_RGB565) {
// Scale to fit the frame, as resizeToLVGL does for images with alpha
float scale = min(float(width()) / img->width(),
float(height()) / img->height());
coord_t w = img->width() * scale;
coord_t h = img->height() * scale;
if (w != img->width() || h != img->height()) {
auto scaled = new BitmapBuffer(BMP_RGB565, w, h);
scaled->clear();
scaled->drawScaledBitmap(img, 0, 0, w, h);
delete img;
img = scaled;
}
img->addCanvas(this, LV_IMG_CF_TRUE_COLOR);
} else {
img->resizeToLVGL(width(), height());
img->addCanvas(this, LV_IMG_CF_TRUE_COLOR_ALPHA);
}
}

Expand Down
8 changes: 6 additions & 2 deletions radio/src/gui/colorlcd/libui/static.h
Original file line number Diff line number Diff line change
Expand Up @@ -169,13 +169,17 @@ class StaticBitmap : public Window
{
public:
StaticBitmap(Window *parent, const rect_t &rect,
const char *filename = nullptr);
const char *filename = nullptr, BitmapFormats fmt = BMP_INVALID);

#if defined(DEBUG_WINDOWS)
std::string getName() const override { return "StaticBitmap"; }
#endif

void setSource(const char *filename);
// Without a format, the image format is chosen from the file: RGB565 for
// opaque images, ARGB4444 for images with alpha.
void setSource(const char *filename) { setSource(filename, BMP_INVALID); }
// Forcing BMP_RGB565 drops the alpha channel of images that have one.
void setSource(const char *filename, BitmapFormats fmt);
void clearSource();
bool hasImage() const;

Expand Down
2 changes: 1 addition & 1 deletion radio/src/gui/colorlcd/model/model_select.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ class ModelButton : public Button
if (modelLayouts[layout].hasImage) {
if (modelCell->modelBitmap[0]) {
GET_FILENAME(filename, BITMAPS_PATH, modelCell->modelBitmap, "");
auto bitmap = new StaticBitmap(this, {PAD_TINY, PAD_TINY, w, h}, filename);
auto bitmap = new StaticBitmap(this, {PAD_TINY, PAD_TINY, w, h}, filename, BMP_ARGB4444);
lv_obj_move_background(bitmap->getLvObj());
bitmap->show(bitmap->hasImage());
if (bitmap->hasImage()) {
Expand Down
2 changes: 1 addition & 1 deletion radio/src/gui/colorlcd/widgets/modelbmp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ class ModelBitmapWidget : public Widget
std::string fullpath =
std::string(BITMAPS_PATH PATH_SEPARATOR) + filename;

image->setSource(fullpath.c_str());
image->setSource(fullpath.c_str(), BMP_ARGB4444);
} else {
image->clearSource();
}
Expand Down
27 changes: 27 additions & 0 deletions radio/src/lua/api_colorlcd_lvgl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ class LvglWidgetParams
if (strcasecmp(s, "triangle") == 0) return ETX_TRIANGLE;
if (strcasecmp(s, "image") == 0) return ETX_IMAGE;
if (strcasecmp(s, "qrcode") == 0) return ETX_QRCODE;
if (strcasecmp(s, "bitmap") == 0) return ETX_BITMAP;
if (strcasecmp(s, "box") == 0) return ETX_BOX;
if (strcasecmp(s, "button") == 0) return ETX_BUTTON;
if (strcasecmp(s, "momentaryButton") == 0) return ETX_MOMENTARY_BUTTON;
Expand Down Expand Up @@ -312,6 +313,9 @@ static void buildLvgl(lua_State *L, int srcIndex, int refIndex)
case ETX_QRCODE:
obj = new LvglWidgetQRCode();
break;
case ETX_BITMAP:
obj = new LvglWidgetBitmap();
break;
case ETX_BOX:
obj = new LvglWidgetBox();
break;
Expand Down Expand Up @@ -852,6 +856,27 @@ LROT_BEGIN(lvgllib, NULL, 0)
@status current Introduced in 2.11.0, floating added in 2.11.6
*/
LROT_FUNCENTRY(qrcode, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetQRCode(); }); })
/*luadoc
@function lvgl.bitmap([parent, ]params)

Display an image that is decoded and scaled to the frame when its source changes. Unlike `lvgl.image` the decoded pixels belong to the object, so they stay valid for as long as the object keeps its source and are not evicted by other images. Use this for sets of images that must all stay decoded, such as map tiles. The object keeps its pixels while it is hidden.

@param parent (object) optional LVGL object to attach the new object to; if omitted the object is created in the top-level script window

@param params (table):
@commonparams lvgl_object
* `file` (string or function) full path to the image file on the SD card, defaults to an empty string; an empty string clears the image

@retval table LVGL object

@notice `w` and `h` are required. The image is scaled to fit entirely within the frame at load time, keeping its aspect ratio, and centered. Changing the frame size reloads the file.
@notice The file is only decoded when the source changes. Replacing it with the same path does nothing. A file that cannot be loaded leaves the object blank.
@notice If `file` is a function it is only called while the object is visible.
@notice While it holds an image, each object uses 2 bytes per pixel for opaque images (such as JPEG) and 3 bytes per pixel for images with transparency, measured at the scaled size. This memory is not counted against the Lua bitmap memory limit. Images with transparency are reduced to 4 bits per color channel.

@status current Introduced in 3.0.0
*/
LROT_FUNCENTRY(bitmap, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetBitmap(); }); })
// Objects - standalone scripts and full screen widgets only
/*luadoc
@function lvgl.button([parent, ]params)
Expand Down Expand Up @@ -1416,6 +1441,7 @@ LROT_BEGIN(lvgllib, NULL, 0)
LROT_NUMENTRY(TRIANGLE, ETX_TRIANGLE)
LROT_NUMENTRY(IMAGE, ETX_IMAGE)
LROT_NUMENTRY(QRCODE, ETX_QRCODE)
LROT_NUMENTRY(BITMAP, ETX_BITMAP)
LROT_NUMENTRY(BOX, ETX_BOX)
LROT_NUMENTRY(BUTTON, ETX_BUTTON)
LROT_NUMENTRY(MOMENTARY_BUTTON, ETX_MOMENTARY_BUTTON)
Expand Down Expand Up @@ -1463,6 +1489,7 @@ LROT_BEGIN(lvgl_mt, NULL, LROT_MASK_GC_INDEX)
LROT_FUNCENTRY(arc, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetArc(); }); })
LROT_FUNCENTRY(image, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetImage(); }); })
LROT_FUNCENTRY(qrcode, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetQRCode(); }); })
LROT_FUNCENTRY(bitmap, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetBitmap(); }); })
// Objects - standalone scripts and full screen widgets only
LROT_FUNCENTRY(button, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetTextButton(); }, true); })
LROT_FUNCENTRY(momentaryButton, [](lua_State* L) { return luaLvglObjEx(L, []() { return new LvglWidgetMomentaryButton(); }, true); })
Expand Down
74 changes: 74 additions & 0 deletions radio/src/lua/lua_lvgl_widget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "edgetx.h"
#include "filechoice.h"
#include "keyboard_base.h"
#include "lib_file.h"
#include "lua_event.h"
#include "menu.h"
#include "numberedit.h"
Expand Down Expand Up @@ -2032,6 +2033,79 @@ void LvglWidgetImage::build(lua_State *L)

//-----------------------------------------------------------------------------

void LvglWidgetBitmap::parseParam(lua_State *L, const char *key)
{
if (!strcmp(key, "file")) {
// A new file replaces any previous source, string or function
filename.clearRef(L);
filename.parse(L);
} else {
LvglWidgetObject::parseParam(L, key);
}
}

void LvglWidgetBitmap::setSource(const char *s)
{
loaded = s;
auto bmp = (StaticBitmap*)window;
if (loaded.empty()) {
bmp->clearSource();
return;
}
char path[FF_MAX_LFN + 1];
etxNormalizePath(s, path, sizeof(path));
bmp->setSource(path);
}

// Pixels are scaled to the frame when decoded, so a new size needs a reload
void LvglWidgetBitmap::setSize(coord_t w, coord_t h)
{
bool resized = window && (window->width() != w || window->height() != h);
LvglWidgetObject::setSize(w, h);
if (resized && !loaded.empty()) {
std::string file = loaded;
setSource(file.c_str());
}
}

bool LvglWidgetBitmap::callRefs(lua_State *L)
{
if (!LvglWidgetObject::callRefs(L)) return false;

if (isVisible() && filename.function != LUA_REFNIL) {
int t = lua_gettop(L);
if (!pcallFunc(L, filename.function, 1)) return false;
const char *s = luaL_checkstring(L, -1);
if (loaded != s) setSource(s);
lua_settop(L, t);
}

return true;
}

void LvglWidgetBitmap::refresh()
{
LvglWidgetObject::refresh();
if (filename.function == LUA_REFNIL && loaded != filename.chars())
setSource(filename.chars());
}

void LvglWidgetBitmap::clearRefs(lua_State *L)
{
filename.clearRef(L);
LvglWidgetObject::clearRefs(L);
}

void LvglWidgetBitmap::build(lua_State *L)
{
window = new StaticBitmap(lvglManager->getCurrentParent(), {x, y, w, h});
setFloating(floating);
if (filename.function == LUA_REFNIL && !filename.txt.empty())
setSource(filename.chars());
}

//-----------------------------------------------------------------------------

void LvglWidgetQRCode::parseParam(lua_State *L, const char *key)
{
if (!strcmp(key, "data")) {
Expand Down
26 changes: 26 additions & 0 deletions radio/src/lua/lua_lvgl_widget.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ enum LuaLvglType
ETX_TRIANGLE,
ETX_IMAGE,
ETX_QRCODE,
ETX_BITMAP,

// Contianers
ETX_BOX,
Expand Down Expand Up @@ -682,6 +683,31 @@ class LvglWidgetImage : public LvglWidgetObject

//-----------------------------------------------------------------------------

// Image decoded and scaled when the source changes. The decoded pixels are
// owned by the object, so they never depend on the LVGL image cache.
class LvglWidgetBitmap : public LvglWidgetObject
{
public:
LvglWidgetBitmap() : LvglWidgetObject(LVGL_SIMPLEMETATABLE) {}

void setSize(coord_t w, coord_t h) override;

bool callRefs(lua_State *L) override;
void clearRefs(lua_State *L) override;

protected:
LvglParamFuncOrString filename = { .function = LUA_REFNIL, .txt = ""};
std::string loaded;

void build(lua_State *L) override;
void refresh() override;
void parseParam(lua_State *L, const char *key) override;

void setSource(const char *s);
};

//-----------------------------------------------------------------------------

class LvglWidgetQRCode : public LvglWidgetObject
{
public:
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading