Skip to content

Commit

Permalink
Bangle.js 2: Increase reported bit depth from 3->16 and perform bayer…
Browse files Browse the repository at this point in the history
… dithering when rendering

              Makes icons/old apps way more usable (plus provides an easy way to get >3bpp)
  • Loading branch information
gfwilliams committed Sep 28, 2021
1 parent 46534a3 commit cd1f1da
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 66 deletions.
2 changes: 2 additions & 0 deletions ChangeLog
Expand Up @@ -19,6 +19,8 @@
Graphics: Fix .asBMP for 4 bit images
Graphics: switch RGB order for palette in asBMP for 4/8 bit images
Bangle.js 2: Idle power consumption down from 1.3mA to 0.9mA (pullups on HRM disabled when off)
Bangle.js 2: Increase reported bit depth from 3->16 and perform bayer dithering when rendering
Makes icons/old apps way more usable (plus provides an easy way to get >3bpp)

2v10 : Bangle.js: Improved HRM calculations - swapped autocorrelation for bandpass filter
Bangle.js: Significantly improved step counting algorithm using bandpass filter (fix #1846)
Expand Down
2 changes: 1 addition & 1 deletion boards/BANGLEJS2.py
Expand Up @@ -53,7 +53,7 @@
'DEFINES+=-DBLUETOOTH_NAME_PREFIX=\'"Bangle.js"\'',
'DEFINES+=-DCUSTOM_GETBATTERY=jswrap_banglejs_getBattery',
'DEFINES+=-DDUMP_IGNORE_VARIABLES=\'"g\\0"\'',
'DEFINES+=-DUSE_FONT_6X8 -DGRAPHICS_PALETTED_IMAGES -DESPR_GRAPHICS_3BIT',
'DEFINES+=-DUSE_FONT_6X8 -DGRAPHICS_PALETTED_IMAGES',
'DEFINES+=-DNO_DUMP_HARDWARE_INITIALISATION', # don't dump hardware init - not used and saves 1k of flash
'INCLUDE += -I$(ROOT)/libs/banglejs -I$(ROOT)/libs/misc',
'WRAPPERSOURCES += libs/banglejs/jswrap_bangle.c',
Expand Down
15 changes: 8 additions & 7 deletions libs/banglejs/jswrap_bangle.c
Expand Up @@ -2648,13 +2648,13 @@ NO_INLINE void jswrap_banglejs_init() {
graphicsTheme.fgH = 0xFFFF;
graphicsTheme.bgH = 0x02F7;
graphicsTheme.dark = true;
#else
graphicsTheme.fg = 0;
graphicsTheme.bg = 7;
graphicsTheme.fg2 = 1;
graphicsTheme.bg2 = 3;
graphicsTheme.fgH = 0;
graphicsTheme.bgH = 3;
#else // still 16 bit, we just want it inverted
graphicsTheme.fg = GRAPHICS_COL_3_TO_16(0);
graphicsTheme.bg = GRAPHICS_COL_3_TO_16(7);
graphicsTheme.fg2 = GRAPHICS_COL_3_TO_16(1);
graphicsTheme.bg2 = GRAPHICS_COL_3_TO_16(3);
graphicsTheme.fgH = GRAPHICS_COL_3_TO_16(0);
graphicsTheme.bgH = GRAPHICS_COL_3_TO_16(3);
graphicsTheme.dark = false;
#endif
//
Expand All @@ -2680,6 +2680,7 @@ NO_INLINE void jswrap_banglejs_init() {
graphicsStructInit(&gfx, LCD_WIDTH, LCD_HEIGHT, LCD_BPP);
#ifdef LCD_CONTROLLER_LPM013M126
gfx.data.type = JSGRAPHICSTYPE_MEMLCD;
gfx.data.bpp = 16; // hack - so we can dither we pretend we're 16 bit
#endif
#ifdef LCD_CONTROLLER_ST7789_8BIT
gfx.data.type = JSGRAPHICSTYPE_ST7789_8BIT;
Expand Down
7 changes: 4 additions & 3 deletions libs/graphics/graphics.h
Expand Up @@ -136,9 +136,7 @@ typedef struct JsGraphics {
typedef void (*JsGraphicsSetPixelFn)(struct JsGraphics *gfx, int x, int y, unsigned int col);

#ifdef GRAPHICS_THEME
#if LCD_BPP && LCD_BPP<=8
typedef unsigned char JsGraphicsThemeColor;
#elif LCD_BPP && LCD_BPP<=16
#if LCD_BPP && LCD_BPP<=16
typedef unsigned short JsGraphicsThemeColor;
#else
typedef unsigned int JsGraphicsThemeColor;
Expand Down Expand Up @@ -214,4 +212,7 @@ void graphicsSplash(JsGraphics *gfx); ///< splash screen

void graphicsIdle(); ///< called when idling

#define GRAPHICS_COL_16_TO_3(x) ((((x)&0x8000)?4:0)|(((x)&0x0400)?2:0)|(((x)&0x0010)?1:0))
#define GRAPHICS_COL_3_TO_16(x) ((((x)&4)?0xF800:0)|(((x)&2)?0x07E0:0)|(((x)&1)?0x001F:0))

#endif // GRAPHICS_H
76 changes: 29 additions & 47 deletions libs/graphics/jswrap_graphics.c
Expand Up @@ -79,10 +79,6 @@ const uint16_t PALETTE_8BIT[256] = {
const uint16_t PALETTE_4BIT_TO_8BIT[16] = { 0, 43, 129, 172, 121, 78, 12, 18, 23, 4, 39, 183, 144, 192, 210, 215 };
#endif

#ifdef ESPR_GRAPHICS_3BIT
#define CONVERT_COL_16_TO_3(x) ((((x)&0x8000)?4:0)|(((x)&0x0400)?2:0)|(((x)&0x0010)?1:0))
#endif


/*JSON{
"type" : "class",
Expand Down Expand Up @@ -945,10 +941,6 @@ unsigned int jswrap_graphics_toColor(JsVar *parent, JsVar *r, JsVar *g, JsVar *b
color=(unsigned int)i;
}
}
#endif
#ifdef ESPR_GRAPHICS_3BIT
} else if (gfx.data.bpp==3) {
color = (unsigned int)((bi>>7) | (gi>>7)<<1 | (ri>>7)<<2);
#endif
} else
color = (unsigned int)(((ri+gi+bi)>=384) ? 0xFFFFFFFF : 0);
Expand Down Expand Up @@ -2043,9 +2035,6 @@ typedef struct {
int bufferOffset; // start offset in imageBuffer
const uint16_t *palettePtr;
uint32_t paletteMask;
#ifdef ESPR_GRAPHICS_3BIT
bool is16Bit; // are we expecting 16 bit colour?
#endif
unsigned int bitMask;
unsigned int pixelsPerByteMask;
int stride; // bytes per line
Expand Down Expand Up @@ -2089,9 +2078,6 @@ static bool _jswrap_graphics_parseImage(JsGraphics *gfx, JsVar *image, GfxDrawIm
jsvUnLock(v);
if (l==2 || l==4 || l==8 || l==16 || l==256) {
info->paletteMask = (uint32_t)(l-1);
#ifdef ESPR_GRAPHICS_3BIT
info->is16Bit = true; // we assume 16 bit if a palette is supplied
#endif
} else {
info->palettePtr = 0;
}
Expand Down Expand Up @@ -2128,9 +2114,6 @@ static bool _jswrap_graphics_parseImage(JsGraphics *gfx, JsVar *image, GfxDrawIm
info->bpp = info->bpp&63;
int paletteEntries = 1<<info->bpp;
info->paletteMask = paletteEntries-1;
#ifdef ESPR_GRAPHICS_3BIT
info->is16Bit = true; // we assume 16 bit if a palette is supplied
#endif
if (info->bpp <= 2) {
// if it'll fit, put the palette data in _simplePalette
int n = info->bufferOffset;
Expand Down Expand Up @@ -2188,16 +2171,6 @@ static bool _jswrap_graphics_parseImage(JsGraphics *gfx, JsVar *image, GfxDrawIm
} else if (gfx->data.bpp==8 && info->bpp==4) {
info->palettePtr = PALETTE_4BIT_TO_8BIT;
info->paletteMask = 15;
#ifdef ESPR_GRAPHICS_3BIT
} else if (gfx->data.bpp==3 && PALETTE_BPP==16 && info->bpp==4) {
info->palettePtr = PALETTE_4BIT;
info->paletteMask = 15;
info->is16Bit = true;
} else if (gfx->data.bpp==3 && PALETTE_BPP==16 && info->bpp==8) {
info->palettePtr = PALETTE_8BIT;
info->paletteMask = 255;
info->is16Bit = true;
#endif
#endif
}
}
Expand Down Expand Up @@ -2433,9 +2406,6 @@ JsVar *jswrap_graphics_drawImage(JsVar *parent, JsVar *image, int xPos, int yPos
// Try and write pixel!
if (img.transparentCol!=col) {
if (img.palettePtr) col = img.palettePtr[col&img.paletteMask];
#ifdef ESPR_GRAPHICS_3BIT
if (img.is16Bit) col = CONVERT_COL_16_TO_3(col);
#endif
setPixel(&gfx, x, y, col);
}
}
Expand Down Expand Up @@ -2502,9 +2472,6 @@ JsVar *jswrap_graphics_drawImage(JsVar *parent, JsVar *image, int xPos, int yPos
// Try and write pixel!
if (img.transparentCol!=col && yp>=gfx.data.clipRect.y1 && yp<=gfx.data.clipRect.y2) {
if (img.palettePtr) col = img.palettePtr[col&img.paletteMask];
#ifdef ESPR_GRAPHICS_3BIT
if (img.is16Bit) col = CONVERT_COL_16_TO_3(col);
#endif
for (int ix=0;ix<s;ix++) {
if (xp>=gfx.data.clipRect.x1 && xp<=gfx.data.clipRect.x2)
gfx.setPixel(&gfx, xp, yp, col);
Expand Down Expand Up @@ -2543,9 +2510,6 @@ JsVar *jswrap_graphics_drawImage(JsVar *parent, JsVar *image, int xPos, int yPos
_jswrap_drawImageLayerStartX(&l);
for (x = x1; x <= x2 ; x++) {
if (_jswrap_drawImageLayerGetPixel(&l, &colData)) {
#ifdef ESPR_GRAPHICS_3BIT
if (l.img.is16Bit) colData = CONVERT_COL_16_TO_3(colData);
#endif
setPixel(&gfx, x, y, colData);
}
_jswrap_drawImageLayerNextX(&l);
Expand Down Expand Up @@ -2671,9 +2635,6 @@ JsVar *jswrap_graphics_drawImages(JsVar *parent, JsVar *layersVar, JsVar *option
unsigned int colData = 0;
for (i=layerCount-1;i>=0;i--) {
if (_jswrap_drawImageLayerGetPixel(&layers[i], &colData)) {
#ifdef ESPR_GRAPHICS_3BIT
if (layers[i].img.is16Bit) colData = CONVERT_COL_16_TO_3(colData);
#endif
solid = true;
break;
}
Expand Down Expand Up @@ -2742,6 +2703,11 @@ JsVar *jswrap_graphics_asImage(JsVar *parent, JsVar *imgType) {
int w = jswrap_graphics_getWidthOrHeight(parent,false);
int h = jswrap_graphics_getWidthOrHeight(parent,true);
int bpp = gfx.data.bpp;
#ifdef LCD_CONTROLLER_LPM013M126
// memory LCD reports bit depth as 16 so it can do dithering
// but when we get an image we only want it the real bit depth (3!)
if (gfx.data.type==JSGRAPHICSTYPE_MEMLCD) bpp=3;
#endif
int len = (w*h*bpp+7)>>3;

JsVar *img = 0;
Expand Down Expand Up @@ -2779,7 +2745,12 @@ JsVar *jswrap_graphics_asImage(JsVar *parent, JsVar *imgType) {
jsvStringIteratorSetCharAndNext(&it, (char)(uint8_t)bpp);
}
while (jsvStringIteratorHasChar(&it)) {
pixelBits = (pixelBits<<bpp) | graphicsGetPixel(&gfx, x, y);
unsigned int pixel = graphicsGetPixel(&gfx, x, y);
#ifdef LCD_CONTROLLER_LPM013M126
if (gfx.data.type==JSGRAPHICSTYPE_MEMLCD)
pixel = GRAPHICS_COL_16_TO_3(pixel);
#endif
pixelBits = (pixelBits<<bpp) | pixel;
pixelBitCnt += (unsigned)bpp;
x++;
if (x>=w) {
Expand Down Expand Up @@ -2969,7 +2940,13 @@ JsVar *jswrap_graphics_asBMP(JsVar *parent) {
JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return 0;
int width = graphicsGetWidth(&gfx);
int height = graphicsGetHeight(&gfx);
int bpp = gfx.data.bpp;
int realBPP = gfx.data.bpp;
#ifdef LCD_CONTROLLER_LPM013M126
// memory LCD reports bit depth as 16 so it can do dithering
// but when we get a bitmap we only want it the real bit depth (3!)
if (gfx.data.type==JSGRAPHICSTYPE_MEMLCD) realBPP=3;
#endif
int bpp = realBPP;
if (bpp>1 && bpp<4) bpp=4;
else if (bpp>4 && bpp<8) bpp=8;
bool hasPalette = bpp<=8;
Expand Down Expand Up @@ -3002,21 +2979,21 @@ JsVar *jswrap_graphics_asBMP(JsVar *parent) {
imgPtr[27]=255;
imgPtr[28]=255;
} else {
if (gfx.data.bpp==3) {
if (realBPP==3) {
for (int i=0;i<paletteEntries;i++) {
imgPtr[26 + (i*3)] = (i&1) ? 255 : 0;
imgPtr[27 + (i*3)] = (i&2) ? 255 : 0;
imgPtr[28 + (i*3)] = (i&4) ? 255 : 0;
}
#if defined(GRAPHICS_PALETTED_IMAGES)
} else if (gfx.data.bpp==4) {
} else if (realBPP==4) {
for (int i=0;i<16;i++) {
int p = PALETTE_4BIT[i];
imgPtr[26 + (i*3)] = (p<<3)&0xF8;
imgPtr[27 + (i*3)] = (p>>3)&0xFC;
imgPtr[28 + (i*3)] = (p>>8)&0xF8;
}
} else if (gfx.data.bpp==8) {
} else if (realBPP==8) {
for (int i=0;i<255;i++) {
int p = PALETTE_8BIT[i];
imgPtr[26 + (i*3)] = (p<<3)&0xF8;
Expand All @@ -3025,8 +3002,8 @@ JsVar *jswrap_graphics_asBMP(JsVar *parent) {
}
#endif
} else { // otherwise default to greyscale
for (int i=0;i<(1<<gfx.data.bpp);i++) {
int c = 255 * i / (1<<gfx.data.bpp);
for (int i=0;i<(1<<realBPP);i++) {
int c = 255 * i / (1<<realBPP);
imgPtr[26 + (i*3)] = c;
imgPtr[27 + (i*3)] = c;
imgPtr[28 + (i*3)] = c;
Expand All @@ -3043,7 +3020,12 @@ JsVar *jswrap_graphics_asBMP(JsVar *parent) {
for (int x=0;x<width;) {
unsigned int b = 0;
for (int i=0;i<pixelsPerByte;i++) {
b = (b<<bpp)|(graphicsGetPixel(&gfx, x++, y)&pixelMask);
unsigned int pixel = graphicsGetPixel(&gfx, x++, y);
#ifdef LCD_CONTROLLER_LPM013M126
if (gfx.data.type==JSGRAPHICSTYPE_MEMLCD)
pixel = GRAPHICS_COL_16_TO_3(pixel);
#endif
b = (b<<bpp)|(pixel&pixelMask);
}
imgPtr[idx++] = (unsigned char)b;
}
Expand Down
32 changes: 24 additions & 8 deletions libs/graphics/lcd_memlcd.c
Expand Up @@ -27,8 +27,20 @@
unsigned char lcdBuffer[LCD_STRIDE*LCD_HEIGHT +2/*2 bytes end of transfer*/ +4/*allow extra for fast scroll*/];
bool isBacklightOn;



// bayer dithering pattern
#define BAYER_RGBSHIFT(b) (b<<13) | (b<<8) | (b<<2)
const unsigned short BAYER2[2][2] = {
{ BAYER_RGBSHIFT(1), BAYER_RGBSHIFT(5) },
{ BAYER_RGBSHIFT(7), BAYER_RGBSHIFT(3) }
};

static unsigned int lcdMemLCD_convert16to3(unsigned int c, int x, int y) {
c = (c&0b1110011100011100) + BAYER2[y&1][x&1];
return
((c&0x10000)?4:0) |
((c&0x00800)?2:0) |
((c&0x00020)?1:0);
}

// ======================================================================

Expand All @@ -37,27 +49,30 @@ unsigned int lcdMemLCD_getPixel(JsGraphics *gfx, int x, int y) {
int bitaddr = LCD_ROWHEADER*8 + (x*3) + (y*LCD_STRIDE*8);
int bit = bitaddr&7;
uint16_t b = __builtin_bswap16(*(uint16_t*)&lcdBuffer[bitaddr>>3]); // get in MSB format
return ((b<<bit) & 0xE000) >> 13;
unsigned int c = ((b<<bit) & 0xE000) >> 13;
#endif
#if LCD_BPP==4
int addr = LCD_ROWHEADER + (x>>1) + (y*LCD_STRIDE);
unsigned char b = lcdBuffer[addr];
return (x&1) ? ((b>>1)&7) : (b>>5);
unsigned int c = (x&1) ? ((b>>1)&7) : (b>>5);
#endif
return GRAPHICS_COL_3_TO_16(c);
}


void lcdMemLCD_setPixel(JsGraphics *gfx, int x, int y, unsigned int col) {

col = lcdMemLCD_convert16to3(col,x,y);
#if LCD_BPP==3
int bitaddr = LCD_ROWHEADER*8 + (x*3) + (y*LCD_STRIDE*8);
int bit = bitaddr&7;
uint16_t b = __builtin_bswap16(*(uint16_t*)&lcdBuffer[bitaddr>>3]);
b = (b & (0xFF1FFF>>bit)) | ((col&7)<<(13-bit));
b = (b & (0xFF1FFF>>bit)) | (col<<(13-bit));
*(uint16_t*)&lcdBuffer[bitaddr>>3] = __builtin_bswap16(b);
#endif
#if LCD_BPP==4
int addr = LCD_ROWHEADER + (x>>1) + (y*LCD_STRIDE);
if (x&1) lcdBuffer[addr] = (lcdBuffer[addr] & 0xF0) | ((col&7)<<1);
if (x&1) lcdBuffer[addr] = (lcdBuffer[addr] & 0xF0) | (col<<1);
else lcdBuffer[addr] = (lcdBuffer[addr] & 0x0F) | (col << 5);
#endif
}
Expand All @@ -68,8 +83,9 @@ void lcdMemLCD_fillRect(struct JsGraphics *gfx, int x1, int y1, int x2, int y2,
int bitaddr = LCD_ROWHEADER*8 + (x1*3) + (y*LCD_STRIDE*8);
for (int x=x1;x<=x2;x++) {
int bit = bitaddr&7;
unsigned int c = lcdMemLCD_convert16to3(col,x,y);
uint16_t b = __builtin_bswap16(*(uint16_t*)&lcdBuffer[bitaddr>>3]);
b = (b & (0xFF1FFF>>bit)) | ((col&7)<<(13-bit));
b = (b & (0xFF1FFF>>bit)) | (c<<(13-bit));
*(uint16_t*)&lcdBuffer[bitaddr>>3] = __builtin_bswap16(b);
bitaddr += 3;
}
Expand Down Expand Up @@ -145,7 +161,7 @@ void lcdMemLCD_flip(JsGraphics *gfx) {
void lcdMemLCD_init(JsGraphics *gfx) {
gfx->data.width = LCD_WIDTH;
gfx->data.height = LCD_HEIGHT;
gfx->data.bpp = 3; // always 3 regardless of if we use 3 or 4 for storage
gfx->data.bpp = 16; // take color as 16 bit even though we only use 3
memset(lcdBuffer,0,sizeof(lcdBuffer));
for (int y=0;y<LCD_HEIGHT;y++) {
#if LCD_BPP==3
Expand Down

0 comments on commit cd1f1da

Please sign in to comment.