RLE for greyscale lcd (gives more firmware space!) (#1142)

* use RLE encodes bitmaps on 212x64 pixel radios

* fix model image display
fix icon display

* implement RLE bitmaps in companion for splash image on 212x64 radios
reserve 3kB for RLE bitmaps in firmware

* fix size check

* use defines instead of magic numbers

* fix display of splash screens on 212x64 radios

* add warning message if compressed image is larger than the reserved space
This commit is contained in:
Malte Langermann
2021-12-04 15:06:23 +11:00
committed by GitHub
parent 995e41469e
commit ec01fa8df9
12 changed files with 317 additions and 33 deletions
+9 -3
View File
@@ -28,10 +28,14 @@
//*** Side Class ***
Side::Side(){
Side::Side(QDialog* parent):
parent(parent)
{
imageLabel = 0;
fileNameEdit = 0;
saveButton = 0;
invertButton = 0;
libraryButton = 0;
loadFwButton=0;
loadPictButton = 0;
loadProfileButton = 0;
@@ -62,7 +66,7 @@ bool Side::displayImage(QString fileName, Source pictSource)
// Determine which picture format to use
if (pictSource == FW) {
FirmwareInterface firmware(fileName);
FirmwareInterface firmware(fileName, parent);
if (!firmware.hasSplash())
return false;
else
@@ -120,7 +124,7 @@ bool Side::saveImage()
{
if (*source == FW )
{
FirmwareInterface firmware(*saveToFileName);
FirmwareInterface firmware(*saveToFileName, parent);
if (!firmware.hasSplash()) {
return false;
}
@@ -152,6 +156,8 @@ bool Side::saveImage()
CustomizeSplashDialog::CustomizeSplashDialog(QWidget *parent) :
QDialog(parent),
left(this),
right(this),
ui(new Ui::CustomizeSplashDialog)
{
ui->setupUi(this);
+2 -1
View File
@@ -40,7 +40,7 @@ enum LCDFormat {LCD9X, LCDTARANIS};
class Side
{
public:
Side();
Side(QDialog* parent);
void markSourceButton();
void copyImage( Side );
bool displayImage( QString fileName, Source source );
@@ -59,6 +59,7 @@ public:
QString *saveToFileName;
private:
QDialog* parent;
Source *source;
LCDFormat *format;
};
+169 -13
View File
@@ -25,6 +25,8 @@
#include "storage.h"
#include <QFile>
#include <QMessageBox>
#include <vector>
#define FW_MARK "FW"
#define VERS_MARK "VERS"
@@ -33,7 +35,131 @@
#define EEPR_MARK "EEPR"
#define FSIZE_MAX Boards::getFlashSize(Board::BOARD_UNKNOWN)
FirmwareInterface::FirmwareInterface(const QString & filename):
class RleBitmap
{
public:
RleBitmap(const uint8_t *src, size_t offset) :
state(RLE_FIRST_BYTE), src(src), curPtr(src), byte(0), curCount(0), pos(0)
{
width = *curPtr++;
rawRows = *curPtr++;
rows = (rawRows +1)/2;
skip(offset);
}
void skip(size_t count)
{
while(count)
{
count--;
getNext();
}
}
uint8_t getNext()
{
pos++;
switch(state)
{
case RLE_FIRST_BYTE:
byte = *curPtr++;
if(byte == *curPtr)
state = RLE_SECOND_BYTE;
break;
case RLE_SECOND_BYTE:
byte = *curPtr++;
curCount = (*curPtr++)+1;
state = RLE_CONTINUE;
// fall through
case RLE_CONTINUE:
curCount--;
if(!curCount)
state = RLE_FIRST_BYTE;
break;
}
return byte;
}
bool to(uint8_t* buf, size_t size)
{
while(pos<size)
*buf++ = getNext();
return pos == size;
}
uint8_t getWidth() const { return width; }
uint8_t getRows() const { return rows; }
uint8_t getRawRows() const { return rawRows; }
void goToNextRow()
{
size_t offset = pos%width;
if(offset)
skip(width - pos%width);
}
static size_t encode(uint8_t *buf, size_t bytes)
{
uint8_t* readPtr = buf;
uint8_t* writePtr = buf;
uint8_t state = 0;
uint8_t count = 0;
uint16_t prevByte = 0xFFFF;
while(bytes)
{
bytes--;
uint8_t byte = *readPtr++;
if(state == 0)
{
*writePtr++ = byte;;
if(prevByte == byte)
{
state = 1;
count = 0;
} else {
prevByte = byte;
}
} else {
if(prevByte == byte)
{
count++;
if(count == 255)
{
*writePtr++ = count;
prevByte = 0xFFFF;
state = 0;
}
} else {
*writePtr++ = count;
*writePtr++ = byte;
prevByte = byte;
state = 0;
}
}
}
if(state != 0)
*writePtr++ = count;
return writePtr - buf;
}
private:
enum State {RLE_FIRST_BYTE, RLE_SECOND_BYTE, RLE_CONTINUE} state;
const uint8_t* src;
const uint8_t* curPtr;
uint8_t width;
uint8_t rows;
uint8_t rawRows;
uint8_t byte;
uint16_t curCount;
size_t pos;
};
FirmwareInterface::FirmwareInterface(const QString & filename, QDialog* parentDialog):
parentDialog(parentDialog),
flash(FSIZE_MAX, 0),
flashSize(0),
eepromVersion(0),
@@ -220,7 +346,7 @@ void FirmwareInterface::seekSplash()
return;
}
if (seekSplash(QByteArray(OTX_SPS_TARANIS, OTX_SPS_SIZE), QByteArray(OTX_SPE, OTX_SPE_SIZE), 6784)) {
if (seekSplash(QByteArray(OTX_SPS_TARANIS, OTX_SPS_SIZE), QByteArray(OTX_SPE, OTX_SPE_SIZE), RLE_SPLASH_MAX_SIZE)) {
splashWidth = SPLASHX9D_WIDTH;
splashHeight = SPLASHX9D_HEIGHT;
splash_format = QImage::Format_Indexed8;
@@ -243,7 +369,7 @@ bool FirmwareInterface::setSplash(const QImage & newsplash)
return false;
}
char b[SPLASH_SIZE_MAX] = {0};
uint8_t b[SPLASH_SIZE_MAX] = {0};
QColor color;
QByteArray splash;
if (splash_format == QImage::Format_Indexed8) {
@@ -256,6 +382,15 @@ bool FirmwareInterface::setSplash(const QImage & newsplash)
b[idx] |= z;
}
}
if(splashWidth == SPLASHX9D_WIDTH && splashHeight == SPLASHX9D_HEIGHT)
{
splashSize = RleBitmap::encode(b, SPLASH_SIZE_MAX);
if(splashSize > RLE_SPLASH_MAX_SIZE){
if(parentDialog)
QMessageBox::critical(parentDialog, CPN_STR_TTL_ERROR, parentDialog->tr("Compressed image size exceeds reserved space."));
return false;
}
}
}
else {
QColor black = QColor(0,0,0);
@@ -268,7 +403,7 @@ bool FirmwareInterface::setSplash(const QImage & newsplash)
}
}
splash.clear();
splash.append(b, splashSize);
splash.append((char *)b, splashSize);
flash.replace(splashOffset, splashSize, splash);
return true;
}
@@ -288,6 +423,7 @@ QImage::Format FirmwareInterface::getSplashFormat()
return splash_format;
}
QImage FirmwareInterface::getSplash()
{
if (splashOffset == 0 || splashSize == 0) {
@@ -296,15 +432,35 @@ QImage FirmwareInterface::getSplash()
if (splash_format == QImage::Format_Indexed8) {
QImage image(splashWidth, splashHeight, QImage::Format_RGB888);
if (splashOffset > 0) {
for (unsigned int y=0; y<splashHeight; y++) {
unsigned int idx = (y/2)*splashWidth;
for (unsigned int x=0; x<splashWidth; x++, idx++) {
uint8_t byte = flash.at(splashOffset+idx);
unsigned int z = (y & 1) ? (byte >> 4) : (byte & 0x0F);
z = 255-(z*255)/15;
QRgb rgb = qRgb(z, z, z);
image.setPixel(x, y, rgb);
if(splashWidth == SPLASHX9D_WIDTH && splashHeight == SPLASHX9D_HEIGHT)
{
std::vector<uint8_t> data;
data.resize(splashWidth * splashHeight);
RleBitmap img((uint8_t*)flash.data() + splashOffset - 2, 0);
img.to(&data[0], data.size());
if (splashOffset > 0) {
for (unsigned int y=0; y<splashHeight; y++) {
unsigned int idx = (y/2)*splashWidth;
for (unsigned int x=0; x<splashWidth; x++, idx++) {
uint8_t byte = data[idx];
unsigned int z = (y & 1) ? (byte >> 4) : (byte & 0x0F);
z = 255-(z*255)/15;
QRgb rgb = qRgb(z, z, z);
image.setPixel(x, y, rgb);
}
}
}
} else {
if (splashOffset > 0) {
for (unsigned int y=0; y<splashHeight; y++) {
unsigned int idx = (y/2)*splashWidth;
for (unsigned int x=0; x<splashWidth; x++, idx++) {
uint8_t byte = flash.at(splashOffset+idx);
unsigned int z = (y & 1) ? (byte >> 4) : (byte & 0x0F);
z = 255-(z*255)/15;
QRgb rgb = qRgb(z, z, z);
image.setPixel(x, y, rgb);
}
}
}
}
+5 -1
View File
@@ -36,10 +36,13 @@
#define ERSKY9X_SPE "SPE"
#define ERSKY9X_OFFSET (7)
// 3kB - width and height bytes
#define RLE_SPLASH_MAX_SIZE (3070)
class FirmwareInterface
{
public:
FirmwareInterface(const QString & filename);
FirmwareInterface(const QString & filename, QDialog* parentDialog = nullptr);
inline QString getDate() { return date; }
inline QString getTime() { return time; }
int getSize() { return flashSize; }
@@ -59,6 +62,7 @@ class FirmwareInterface
bool isValid();
private:
QDialog* parentDialog;
QByteArray flash;
uint flashSize;
QString seekString(const QString & string);
+1 -1
View File
@@ -6,5 +6,5 @@ set(STICKS_ARGS --rows 4)
add_bitmaps_target(taranis_xbm ${RADIO_SRC_DIR}/bitmaps/sticks.xbm 1bit "${STICKS_ARGS}") # 1bit format, 1byte size, split into 4 rows
add_fonts_target(taranis_fonts ${RADIO_SRC_DIR}/fonts/${FONT_DIR}/*.png)
add_bitmaps_target(taranis_bitmaps ${RADIO_SRC_DIR}/bitmaps/212x64/*.png 4bits "")
add_bitmaps_target(taranis_bitmaps ${RADIO_SRC_DIR}/bitmaps/212x64/*.png 4bits "--rle")
add_dependencies(taranis_bitmaps taranis_fonts taranis_xbm)
+5 -1
View File
@@ -204,6 +204,10 @@ bool loadModelBitmap(char * name, uint8_t * bitmap)
}
// In all error cases, we set the default logo
memcpy(bitmap, logo_taranis, MODEL_BITMAP_SIZE);
RleBitmap pic(logo_taranis, 0);
*bitmap++ = pic.getWidth();
*bitmap++ = pic.getRawRows();
for(int i=0; i < MODEL_BITMAP_SIZE-2; i++)
*bitmap++ = pic.getNext();
return false;
}
+35
View File
@@ -976,4 +976,39 @@ void lcdDrawBitmap(coord_t x, coord_t y, const uint8_t * img, coord_t offset, co
}
}
}
void lcdDrawRleBitmap(coord_t x, coord_t y, const uint8_t * img, coord_t offset, coord_t width)
{
RleBitmap pic(img, offset);
uint8_t w = pic.getWidth();
if (!width || width > w) {
width = w;
}
if (x+width > LCD_W) {
if (x >= LCD_W ) return;
width = LCD_W-x;
}
uint8_t rows = pic.getRows();
for (uint8_t row=0; row<rows; row++) {
uint8_t *p = &displayBuf[(row + (y/2)) * LCD_W + x];
for (coord_t i=0; i<width; i++) {
if (p >= DISPLAY_END) return;
uint8_t b = pic.getNext();
if (y & 1) {
*p = (*p & 0x0f) + ((b & 0x0f) << 4);
if ((p+LCD_W) < DISPLAY_END) {
*(p+LCD_W) = (*(p+LCD_W) & 0xf0) + ((b & 0xf0) >> 4);
}
}
else {
*p = b;
}
p++;
}
pic.goToNextRow();
pic.skip(offset);
}
}
#endif
+74 -1
View File
@@ -168,7 +168,9 @@ void drawTelemetryTopBar();
void lcdDraw1bitBitmap(coord_t x, coord_t y, const unsigned char * img, uint8_t idx, LcdFlags att=0);
void lcdDrawBitmap(coord_t x, coord_t y, const uint8_t * img, coord_t offset=0, coord_t width=0);
#define LCD_ICON(x, y, icon) lcdDrawBitmap(x, y, icons, icon)
void lcdDrawRleBitmap(coord_t x, coord_t y, const uint8_t * img, coord_t offset=0, coord_t width=0);
#define LCD_ICON(x, y, icon) lcdDrawRleBitmap(x, y, icons, icon)
void lcdClear();
@@ -193,4 +195,75 @@ inline pixel_t getPixel(unsigned int x, unsigned int y)
uint8_t getTextWidth(const char * s, uint8_t len=0, LcdFlags flags=0);
class RleBitmap
{
public:
RleBitmap(const uint8_t *src, coord_t offset) :
state(RLE_FIRST_BYTE), src(src), curPtr(src), byte(0), curCount(0), pos(0)
{
width = *curPtr++;
rawRows = *curPtr++;
rows = (rawRows +1)/2;
skip(offset);
}
void skip(coord_t count)
{
while(count)
{
count--;
getNext();
}
}
uint8_t getNext()
{
pos++;
switch(state)
{
case RLE_FIRST_BYTE:
byte = *curPtr++;
if(byte == *curPtr)
state = RLE_SECOND_BYTE;
break;
case RLE_SECOND_BYTE:
byte = *curPtr++;
curCount = (*curPtr++)+1;
state = RLE_CONTINUE;
// fall through
case RLE_CONTINUE:
curCount--;
if(!curCount)
state = RLE_FIRST_BYTE;
break;
}
return byte;
}
uint8_t getWidth() const { return width; }
uint8_t getRows() const { return rows; }
uint8_t getRawRows() const { return rawRows; }
void goToNextRow()
{
coord_t offset = pos%width;
if(offset)
skip(width - pos%width);
}
private:
enum State {RLE_FIRST_BYTE, RLE_SECOND_BYTE, RLE_CONTINUE} state;
const uint8_t* src;
const uint8_t* curPtr;
uint8_t width;
uint8_t rows;
uint8_t rawRows;
uint8_t byte;
uint16_t curCount;
coord_t pos;
};
#endif // _LCD_H_
+1 -1
View File
@@ -28,7 +28,7 @@ const unsigned char ASTERISK_BITMAP[] = {
void drawAlertBox(const char * title, const char * text, const char * action)
{
lcdClear();
lcdDrawBitmap(0, 0, ASTERISK_BITMAP);
lcdDrawRleBitmap(0, 0, ASTERISK_BITMAP);
#define MESSAGE_LCD_OFFSET 60
+11 -6
View File
@@ -22,16 +22,21 @@
#include "opentx.h"
#if defined(SPLASH)
const unsigned char splashdata[] = {
'S','P','S',0,
#include "bitmaps/212x64/splash.lbm"
'S','P','E',0 };
const unsigned char * const splash_lbm = splashdata+4;
#define MAXIMUM_SPLASH_IMAGE_SIZE 3072
struct {
const uint8_t __magic_prefix[4] = { 'S','P','S',0 };
const uint8_t splashdata[MAXIMUM_SPLASH_IMAGE_SIZE] = {
#include "bitmaps/212x64/splash.lbm"
};
const uint8_t __magic_suffix[4] = { 'S','P','E',0 };
} splash_struct;
static_assert(sizeof(splash_struct.splashdata) <= MAXIMUM_SPLASH_IMAGE_SIZE, "");
const unsigned char * const splash_lbm = splash_struct.splashdata;
void drawSplash()
{
lcdClear();
lcdDrawBitmap(0, 0, splash_lbm);
lcdDrawRleBitmap(0, 0, splash_lbm);
#if MENUS_LOCK == 1
if (readonly == false) {
+4 -4
View File
@@ -52,9 +52,9 @@ void drawStartupAnimation(uint32_t duration, uint32_t totalDuration)
lcdClear();
if (index == 0)
lcdDrawBitmap((LCD_W - ANIMATIONS_BITMAP_WIDTH) / 2, (LCD_H - ANIMATIONS_BITMAP_HEIGHT) / 2, bmp_lock, 0, ANIMATIONS_BITMAP_WIDTH);
lcdDrawRleBitmap((LCD_W - ANIMATIONS_BITMAP_WIDTH) / 2, (LCD_H - ANIMATIONS_BITMAP_HEIGHT) / 2, bmp_lock, 0, ANIMATIONS_BITMAP_WIDTH);
else
lcdDrawBitmap((LCD_W - ANIMATIONS_BITMAP_WIDTH) / 2, (LCD_H - ANIMATIONS_BITMAP_HEIGHT) / 2, bmp_startup, (index - 1) * ANIMATIONS_BITMAP_WIDTH, ANIMATIONS_BITMAP_WIDTH);
lcdDrawRleBitmap((LCD_W - ANIMATIONS_BITMAP_WIDTH) / 2, (LCD_H - ANIMATIONS_BITMAP_HEIGHT) / 2, bmp_startup, (index - 1) * ANIMATIONS_BITMAP_WIDTH, ANIMATIONS_BITMAP_WIDTH);
lcdRefresh();
}
@@ -69,7 +69,7 @@ void drawShutdownAnimation(uint32_t duration, uint32_t totalDuration, const char
lcdRefreshWait();
lcdClear();
lcdDrawBitmap((LCD_W - ANIMATIONS_BITMAP_WIDTH) / 2, (LCD_H - ANIMATIONS_BITMAP_HEIGHT) / 2, bmp_shutdown, (3 - index) * ANIMATIONS_BITMAP_WIDTH, ANIMATIONS_BITMAP_WIDTH);
lcdDrawRleBitmap((LCD_W - ANIMATIONS_BITMAP_WIDTH) / 2, (LCD_H - ANIMATIONS_BITMAP_HEIGHT) / 2, bmp_shutdown, (3 - index) * ANIMATIONS_BITMAP_WIDTH, ANIMATIONS_BITMAP_WIDTH);
if (message) {
lcdDrawText((LCD_W - getTextWidth(message)) / 2, LCD_H - 2*FH, message);
}
@@ -81,7 +81,7 @@ void drawSleepBitmap()
{
lcdRefreshWait();
lcdClear();
lcdDrawBitmap((LCD_W - ANIMATIONS_BITMAP_WIDTH) / 2, (LCD_H - ANIMATIONS_BITMAP_HEIGHT) / 2, bmp_sleep, 0);
lcdDrawRleBitmap((LCD_W - ANIMATIONS_BITMAP_WIDTH) / 2, (LCD_H - ANIMATIONS_BITMAP_HEIGHT) / 2, bmp_sleep, 0);
lcdRefresh();
lcdRefreshWait();
}
+1 -1
View File
@@ -203,7 +203,7 @@ void displayTopBarGauge(coord_t x, int count, bool blinking=false)
}
#define LCD_NOTIF_ICON(x, icon) \
lcdDrawBitmap(x, BAR_Y, icons, icon); \
lcdDrawRleBitmap(x, BAR_Y, icons, icon); \
lcdDrawSolidHorizontalLine(x, BAR_Y+8, 11)
void displayTopBar()