From 62824da7793164f2fa2c88746db87ffed0aa48fb Mon Sep 17 00:00:00 2001 From: Philippe G Date: Sat, 25 Jul 2020 16:57:10 -0700 Subject: [PATCH] add color display support + SSD1351 driver --- components/display/SH1106.c | 9 +- components/display/SSD1306.c | 9 +- components/display/SSD1322.c | 6 +- components/display/SSD132x.c | 17 +- components/display/SSD1351.c | 294 +++++++++++++ components/display/SSD1675.c | 5 +- components/display/core/gds.c | 54 ++- components/display/core/gds.h | 10 +- components/display/core/gds_draw.c | 81 +++- components/display/core/gds_image.c | 393 +++++++++++------- components/display/core/gds_image.h | 9 +- components/display/core/gds_private.h | 55 ++- .../display/core/ifaces/default_if_spi.c | 4 +- components/display/display.c | 5 +- components/squeezelite/display.c | 51 ++- 15 files changed, 784 insertions(+), 218 deletions(-) create mode 100644 components/display/SSD1351.c diff --git a/components/display/SH1106.c b/components/display/SH1106.c index 91e33a54..089efa1a 100644 --- a/components/display/SH1106.c +++ b/components/display/SH1106.c @@ -137,6 +137,10 @@ static const struct GDS_Device SH1106 = { .DisplayOn = DisplayOn, .DisplayOff = DisplayOff, .SetContrast = SetContrast, .SetVFlip = SetVFlip, .SetHFlip = SetHFlip, .Update = Update, .Init = Init, + .Depth = 1, +#if !defined SHADOW_BUFFER && defined USE_IRAM + .Alloc = GDS_ALLOC_IRAM_SPI; +#endif }; struct GDS_Device* SH1106_Detect(char *Driver, struct GDS_Device* Device) { @@ -144,10 +148,7 @@ struct GDS_Device* SH1106_Detect(char *Driver, struct GDS_Device* Device) { if (!Device) Device = calloc(1, sizeof(struct GDS_Device)); *Device = SH1106; - Device->Depth = 1; -#if !defined SHADOW_BUFFER && defined USE_IRAM - Device->Alloc = GDS_ALLOC_IRAM_SPI; -#endif + ESP_LOGI(TAG, "SH1106 driver"); return Device; diff --git a/components/display/SSD1306.c b/components/display/SSD1306.c index fec88996..90eb5afe 100644 --- a/components/display/SSD1306.c +++ b/components/display/SSD1306.c @@ -152,6 +152,10 @@ static const struct GDS_Device SSD1306 = { .DisplayOn = DisplayOn, .DisplayOff = DisplayOff, .SetContrast = SetContrast, .SetVFlip = SetVFlip, .SetHFlip = SetHFlip, .Update = Update, .Init = Init, + .Mode = GDS_MONO, .Depth = 1, +#if !defined SHADOW_BUFFER && defined USE_IRAM + .Alloc = GDS_ALLOC_IRAM_SPI, +#endif }; struct GDS_Device* SSD1306_Detect(char *Driver, struct GDS_Device* Device) { @@ -159,10 +163,7 @@ struct GDS_Device* SSD1306_Detect(char *Driver, struct GDS_Device* Device) { if (!Device) Device = calloc(1, sizeof(struct GDS_Device)); *Device = SSD1306; - Device->Depth = 1; -#if !defined SHADOW_BUFFER && defined USE_IRAM - Device->Alloc = GDS_ALLOC_IRAM_SPI; -#endif + ESP_LOGI(TAG, "SSD1306 driver"); return Device; diff --git a/components/display/SSD1322.c b/components/display/SSD1322.c index ea449aea..e2290604 100644 --- a/components/display/SSD1322.c +++ b/components/display/SSD1322.c @@ -128,7 +128,7 @@ static bool Init( struct GDS_Device* Device ) { // find a page size that is not too small is an integer of height Private->PageSize = min(8, PAGE_BLOCK / (Device->Width / 2)); - Private->PageSize = Device->Height / (Device->Height / Private->PageSize) ; + while (Private->PageSize && Device->Height != (Device->Height / Private->PageSize) * Private->PageSize) Private->PageSize--; #ifdef SHADOW_BUFFER Private->Shadowbuffer = malloc( Device->FramebufferSize ); @@ -189,6 +189,7 @@ static const struct GDS_Device SSD1322 = { .DisplayOn = DisplayOn, .DisplayOff = DisplayOff, .SetContrast = SetContrast, .SetVFlip = SetVFlip, .SetHFlip = SetHFlip, .Update = Update, .Init = Init, + .Mode = GDS_GRAYSCALE, .Depth = 4, }; struct GDS_Device* SSD1322_Detect(char *Driver, struct GDS_Device* Device) { @@ -197,7 +198,6 @@ struct GDS_Device* SSD1322_Detect(char *Driver, struct GDS_Device* Device) { if (!Device) Device = calloc(1, sizeof(struct GDS_Device)); *Device = SSD1322; - Device->Depth = 4; - + return Device; } \ No newline at end of file diff --git a/components/display/SSD132x.c b/components/display/SSD132x.c index e68a4d74..021e03a3 100644 --- a/components/display/SSD132x.c +++ b/components/display/SSD132x.c @@ -251,7 +251,7 @@ static bool Init( struct GDS_Device* Device ) { // find a page size that is not too small is an integer of height Private->PageSize = min(8, PAGE_BLOCK / (Device->Width / 2)); - Private->PageSize = Device->Height / (Device->Height / Private->PageSize) ; + while (Private->PageSize && Device->Height != (Device->Height / Private->PageSize) * Private->PageSize) Private->PageSize--; #ifdef SHADOW_BUFFER #ifdef USE_IRAM @@ -270,7 +270,6 @@ static bool Init( struct GDS_Device* Device ) { #ifdef USE_IRAM if (Device->Depth == 4 && Device->IF == GDS_IF_SPI) Private->iRAM = heap_caps_malloc( Private->PageSize * Device->Width / 2, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA ); #endif - #endif ESP_LOGI(TAG, "SSD1326/7 with bit depth %u, page %u, iRAM %p", Device->Depth, Private->PageSize, Private->iRAM); @@ -319,6 +318,7 @@ static const struct GDS_Device SSD132x = { .DisplayOn = DisplayOn, .DisplayOff = DisplayOff, .SetContrast = SetContrast, .SetVFlip = SetVFlip, .SetHFlip = SetHFlip, .Update = Update4, .Init = Init, + .Mode = GDS_GRAYSCALE, .Depth = 4, }; struct GDS_Device* SSD132x_Detect(char *Driver, struct GDS_Device* Device) { @@ -335,19 +335,18 @@ struct GDS_Device* SSD132x_Detect(char *Driver, struct GDS_Device* Device) { ((struct PrivateSpace*) Device->Private)->Model = Model; sscanf(Driver, "%*[^:]:%u", &Depth); - Device->Depth = Depth; - - if (Model == SSD1326 && Device->Depth == 1) { + + if (Model == SSD1326 && Depth == 1) { Device->Update = Update1; Device->DrawPixelFast = DrawPixel1Fast; Device->DrawBitmapCBR = DrawBitmapCBR; Device->ClearWindow = ClearWindow; + Device->Depth = 1; + Device->Mode = GDS_MONO; #if !defined SHADOW_BUFFER && defined USE_IRAM Device->Alloc = GDS_ALLOC_IRAM_SPI; #endif - } else { - Device->Depth = 4; - } - + } + return Device; } \ No newline at end of file diff --git a/components/display/SSD1351.c b/components/display/SSD1351.c new file mode 100644 index 00000000..b5231cdd --- /dev/null +++ b/components/display/SSD1351.c @@ -0,0 +1,294 @@ +/** + * Copyright (c) 2017-2018 Tara Keeling + * 2020 Philippe G. + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include +#include +#include +#include +#include +#include + +#include "gds.h" +#include "gds_private.h" + +#define SHADOW_BUFFER +#define USE_IRAM +#define PAGE_BLOCK 2048 +#define ENABLE_WRITE 0x5c + +#define min(a,b) (((a) < (b)) ? (a) : (b)) + +static char TAG[] = "SSD1351"; + +struct PrivateSpace { + uint8_t *iRAM, *Shadowbuffer; + uint8_t ReMap, PageSize; +}; + +// Functions are not declared to minimize # of lines + +static void WriteByte( struct GDS_Device* Device, uint8_t Data ) { + Device->WriteData( Device, &Data, 1 ); +} + +static void SetColumnAddress( struct GDS_Device* Device, uint8_t Start, uint8_t End ) { + Device->WriteCommand( Device, 0x15 ); + WriteByte( Device, Start ); + WriteByte( Device, End ); +} +static void SetRowAddress( struct GDS_Device* Device, uint8_t Start, uint8_t End ) { + Device->WriteCommand( Device, 0x75 ); + WriteByte( Device, Start ); + WriteByte( Device, End ); +} + +static void Update16( struct GDS_Device* Device ) { + struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private; + +#ifdef SHADOW_BUFFER + uint32_t *optr = (uint32_t*) Private->Shadowbuffer, *iptr = (uint32_t*) Device->Framebuffer; + int FirstCol = Device->Width / 2, LastCol = 0, FirstRow = -1, LastRow = 0; + + for (int r = 0; r < Device->Height; r++) { + // look for change and update shadow (cheap optimization = width is always a multiple of 2) + for (int c = 0; c < Device->Width / 2; c++, iptr++, optr++) { + if (*optr != *iptr) { + *optr = *iptr; + if (c < FirstCol) FirstCol = c; + if (c > LastCol) LastCol = c; + if (FirstRow < 0) FirstRow = r; + LastRow = r; + } + } + + // wait for a large enough window - careful that window size might increase by more than a line at once ! + if (FirstRow < 0 || ((LastCol - FirstCol + 1) * (r - FirstRow + 1) * 4 < PAGE_BLOCK && r != Device->Height - 1)) continue; + + FirstCol *= 2; + LastCol = LastCol * 2 + 1; + SetRowAddress( Device, FirstRow, LastRow ); + SetColumnAddress( Device, FirstCol, LastCol ); + + int ChunkSize = (LastCol - FirstCol + 1) * 2; + + // own use of IRAM has not proven to be much better than letting SPI do its copy + if (Private->iRAM) { + uint8_t *optr = Private->iRAM; + for (int i = FirstRow; i <= LastRow; i++) { + memcpy(optr, Private->Shadowbuffer + (i * Device->Width + FirstCol) * 2, ChunkSize); + optr += ChunkSize; + if (optr - Private->iRAM < PAGE_BLOCK && i < LastRow) continue; + Device->WriteCommand( Device, ENABLE_WRITE ); + Device->WriteData(Device, Private->iRAM, optr - Private->iRAM); + optr = Private->iRAM; + } + } else for (int i = FirstRow; i <= LastRow; i++) { + Device->WriteCommand( Device, ENABLE_WRITE ); + Device->WriteData( Device, Private->Shadowbuffer + (i * Device->Width + FirstCol) * 2, ChunkSize ); + } + + FirstCol = Device->Width / 2; LastCol = 0; + FirstRow = -1; + } +#else + // always update by full lines + SetColumnAddress( Device, 0, Device->Width - 1); + + for (int r = 0; r < Device->Height; r += min(Private->PageSize, Device->Height - r)) { + int Height = min(Private->PageSize, Device->Height - r); + + SetRowAddress( Device, r, r + Height - 1 ); + + if (Private->iRAM) { + memcpy(Private->iRAM, Device->Framebuffer + r * Device->Width * 2, Height * Device->Width * 2 ); + Device->WriteCommand(Device, ENABLE_WRITE); + Device->WriteData( Device, Private->iRAM, Height * Device->Width * 2 ); + } else { + Device->WriteCommand(Device, ENABLE_WRITE); + Device->WriteData( Device, Device->Framebuffer + r * Device->Width * 2, Height * Device->Width * 2 ); + } + } +#endif +} + +static void Update24( struct GDS_Device* Device ) { + struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private; + int FirstCol = (Device->Width * 3) / 2, LastCol = 0, FirstRow = -1, LastRow = 0; + +#ifdef SHADOW_BUFFER + uint16_t *optr = (uint16_t*) Private->Shadowbuffer, *iptr = (uint16_t*) Device->Framebuffer; + + for (int r = 0; r < Device->Height; r++) { + // look for change and update shadow (cheap optimization = width always / by 2) + for (int c = 0; c < (Device->Width * 3) / 2; c++, optr++, iptr++) { + if (*optr != *iptr) { + *optr = *iptr; + if (c < FirstCol) FirstCol = c; + if (c > LastCol) LastCol = c; + if (FirstRow < 0) FirstRow = r; + LastRow = r; + } + } + + // do we have enough to send (cols are divided by 3/2) + if (FirstRow < 0 || ((((LastCol - FirstCol + 1) * 2 + 3 - 1) / 3) * (r - FirstRow + 1) * 3 < PAGE_BLOCK && r != Device->Height - 1)) continue; + + FirstCol = (FirstCol * 2) / 3; + LastCol = (LastCol * 2 + 1) / 3; + SetRowAddress( Device, FirstRow, LastRow ); + SetColumnAddress( Device, FirstCol, LastCol ); + + int ChunkSize = (LastCol - FirstCol + 1) * 3; + + // own use of IRAM has not proven to be much better than letting SPI do its copy + if (Private->iRAM) { + uint8_t *optr = Private->iRAM; + for (int i = FirstRow; i <= LastRow; i++) { + memcpy(optr, Private->Shadowbuffer + (i * Device->Width + FirstCol) * 3, ChunkSize); + optr += ChunkSize; + if (optr - Private->iRAM < PAGE_BLOCK && i < LastRow) continue; + Device->WriteCommand( Device, ENABLE_WRITE ); + Device->WriteData(Device, Private->iRAM, optr - Private->iRAM); + optr = Private->iRAM; + } + } else for (int i = FirstRow; i <= LastRow; i++) { + Device->WriteCommand( Device, ENABLE_WRITE ); + Device->WriteData( Device, Private->Shadowbuffer + (i * Device->Width + FirstCol) * 3, ChunkSize ); + } + + FirstCol = (Device->Width * 3) / 2; LastCol = 0; + FirstRow = -1; + } +#else + // always update by full lines + SetColumnAddress( Device, 0, Device->Width - 1); + + for (int r = 0; r < Device->Height; r += Private->PageSize) { + SetRowAddress( Device, r, r + Private->PageSize - 1 ); + if (Private->iRAM) { + memcpy(Private->iRAM, Device->Framebuffer + r * Device->Width * 3, Private->PageSize * Device->Width * 3 ); + Device->WriteCommand(Device, ENABLE_WRITE); + Device->WriteData( Device, Private->iRAM, Private->PageSize * Device->Width * 3 ); + } else { + Device->WriteCommand(Device, ENABLE_WRITE); + Device->WriteData( Device, Device->Framebuffer + r * Device->Width * 3, Private->PageSize * Device->Width * 3 ); + } + } +#endif +} + +static void SetHFlip( struct GDS_Device* Device, bool On ) { + struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private; + Private->ReMap = On ? (Private->ReMap & ~(1 << 1)) : (Private->ReMap | (1 << 1)); + Device->WriteCommand( Device, 0xA0 ); + WriteByte( Device, Private->ReMap ); +} + +static void SetVFlip( struct GDS_Device *Device, bool On ) { + struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private; + Private->ReMap = On ? (Private->ReMap | (1 << 4)) : (Private->ReMap & ~(1 << 4)); + Device->WriteCommand( Device, 0xA0 ); + WriteByte( Device, Private->ReMap ); +} + +static void DisplayOn( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0xAF ); } +static void DisplayOff( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0xAE ); } + +static void SetContrast( struct GDS_Device* Device, uint8_t Contrast ) { + Device->WriteCommand( Device, 0xC7 ); + WriteByte( Device, Contrast >> 4); +} + +static bool Init( struct GDS_Device* Device ) { + struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private; + int Depth = (Device->Depth + 8 - 1) / 8; + + Private->PageSize = min(8, PAGE_BLOCK / (Device->Width * Depth)); + +#ifdef SHADOW_BUFFER + Private->Shadowbuffer = malloc( Device->FramebufferSize ); + memset(Private->Shadowbuffer, 0xFF, Device->FramebufferSize); +#endif +#ifdef USE_IRAM + Private->iRAM = heap_caps_malloc( (Private->PageSize + 1) * Device->Width * Depth, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA ); +#endif + + ESP_LOGI(TAG, "SSD1351 with bit depth %u, page %u, iRAM %p", Device->Depth, Private->PageSize, Private->iRAM); + + // unlock (specially 0xA2) + Device->WriteCommand( Device, 0xFD); + WriteByte(Device, 0xB1); + + // set clocks + /* + Device->WriteCommand( Device, 0xB3 ); + WriteByte( Device, ( 0x08 << 4 ) | 0x00 ); + */ + + // need to be off and disable display RAM + Device->DisplayOff( Device ); + + // need COM split (5) + Private->ReMap = (1 << 5); + + // Display Offset + Device->WriteCommand( Device, 0xA2 ); + WriteByte( Device, 0x00 ); + + // Display Start Line + Device->WriteCommand( Device, 0xA1 ); + WriteByte( Device, 0x00 ); + + // set flip modes & contrast + Device->SetContrast( Device, 0x7F ); + Device->SetVFlip( Device, false ); + Device->SetHFlip( Device, false ); + + // set Adressing Mode Horizontal + Private->ReMap |= (0 << 2); + // set screen depth (16/18) + if (Device->Depth == 24) Private->ReMap |= (0x02 << 6); + // write ReMap byte + Device->WriteCommand( Device, 0xA0 ); + WriteByte( Device, Private->ReMap ); + + // no Display Inversion + Device->WriteCommand( Device, 0xA6 ); + + // gone with the wind + Device->DisplayOn( Device ); + Device->Update( Device ); + + return true; +} + +static const struct GDS_Device SSD1351 = { + .DisplayOn = DisplayOn, .DisplayOff = DisplayOff, .SetContrast = SetContrast, + .SetVFlip = SetVFlip, .SetHFlip = SetHFlip, + .Update = Update16, .Init = Init, + .Mode = GDS_RGB565, .Depth = 16, +}; + +struct GDS_Device* SSD1351_Detect(char *Driver, struct GDS_Device* Device) { + int Depth; + + if (!strcasestr(Driver, "SSD1351")) return NULL; + + if (!Device) Device = calloc(1, sizeof(struct GDS_Device)); + + *Device = SSD1351; + sscanf(Driver, "%*[^:]:%u", &Depth); + + if (Depth == 18) { + Device->Mode = GDS_RGB666; + Device->Depth = 24; + Device->Update = Update24; + } + + return Device; +} \ No newline at end of file diff --git a/components/display/SSD1675.c b/components/display/SSD1675.c index 390c8986..75c56a8f 100644 --- a/components/display/SSD1675.c +++ b/components/display/SSD1675.c @@ -230,6 +230,8 @@ static const struct GDS_Device SSD1675 = { .DrawBitmapCBR = DrawBitmapCBR, .ClearWindow = ClearWindow, .DrawPixelFast = DrawPixelFast, .Update = Update, .Init = Init, + .Mode = GDS_MONO, .Depth = 1, + .Alloc = GDS_ALLOC_NONE, }; struct GDS_Device* SSD1675_Detect(char *Driver, struct GDS_Device* Device) { @@ -238,9 +240,6 @@ struct GDS_Device* SSD1675_Detect(char *Driver, struct GDS_Device* Device) { if (!Device) Device = calloc(1, sizeof(struct GDS_Device)); *Device = SSD1675; - Device->Depth = 1; - Device->Alloc = GDS_ALLOC_NONE; - char *p; struct PrivateSpace* Private = (struct PrivateSpace*) Device->Private; Private->ReadyPin = -1; diff --git a/components/display/core/gds.c b/components/display/core/gds.c index 947cf0ce..60de62db 100644 --- a/components/display/core/gds.c +++ b/components/display/core/gds.c @@ -53,12 +53,20 @@ void GDS_ClearExt(struct GDS_Device* Device, bool full, ...) { } void GDS_Clear( struct GDS_Device* Device, int Color ) { - if (Device->Depth == 1) Color = Color == GDS_COLOR_BLACK ? 0 : 0xff; - else if (Device->Depth == 4) Color = Color | (Color << 4); - memset( Device->Framebuffer, Color, Device->FramebufferSize ); + if (Color == GDS_COLOR_BLACK) memset( Device->Framebuffer, 0, Device->FramebufferSize ); + else if (Device->Depth == 1) memset( Device->Framebuffer, 0xff, Device->FramebufferSize ); + else if (Device->Depth == 4) memset( Device->Framebuffer, Color | (Color << 4), Device->FramebufferSize ); + else if (Device->Depth == 8) memset( Device->Framebuffer, Color, Device->FramebufferSize ); + else GDS_ClearWindow(Device, 0, 0, -1, -1, Color); Device->Dirty = true; } +#define CLEAR_WINDOW(x1,y1,x2,y2,F,W,C,T,N) \ + for (int y = y1; y <= y2; y++) { \ + T *Ptr = (T*) F + (y * W + x1)*N; \ + for (int c = (x2 - x1)*N; c-- >= 0; *Ptr++ = C); \ + } + void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color ) { // -1 means up to width/height if (x2 < 0) x2 = Device->Width - 1; @@ -110,6 +118,12 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2, if (c + chunk <= x2) GDS_DrawPixelFast( Device, x2, r, Color); } } + } else if (Device->Depth == 8) { + CLEAR_WINDOW(x1,y1,x2,y2,Device->Framebuffer,Device->Width,Color,uint8_t,1); + } else if (Device->Depth == 16) { + CLEAR_WINDOW(x1,y1,x2,y2,Device->Framebuffer,Device->Width,Color,uint16_t,1); + } else if (Device->Depth == 24) { + CLEAR_WINDOW(x1,y1,x2,y2,Device->Framebuffer,Device->Width,Color,uint8_t,3); } else { for (int y = y1; y <= y2; y++) { for (int x = x1; x <= x2; x++) { @@ -138,12 +152,13 @@ bool GDS_Reset( struct GDS_Device* Device ) { bool GDS_Init( struct GDS_Device* Device ) { - Device->FramebufferSize = (Device->Width * Device->Height) / (8 / Device->Depth); + if (Device->Depth > 8) Device->FramebufferSize = Device->Width * Device->Height * ((8 + Device->Depth - 1) / 8); + else Device->FramebufferSize = (Device->Width * Device->Height) / (8 / Device->Depth); // allocate FB unless explicitely asked not to if (!(Device->Alloc & GDS_ALLOC_NONE)) { if ((Device->Alloc & GDS_ALLOC_IRAM) || ((Device->Alloc & GDS_ALLOC_IRAM_SPI) && Device->IF == GDS_IF_SPI)) { - heap_caps_calloc( 1, Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA ); + Device->Framebuffer = heap_caps_calloc( 1, Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA ); } else { Device->Framebuffer = calloc( 1, Device->FramebufferSize ); } @@ -151,10 +166,36 @@ bool GDS_Init( struct GDS_Device* Device ) { } bool Res = Device->Init( Device ); - if (!Res) free(Device->Framebuffer); + if (!Res && Device->Framebuffer) free(Device->Framebuffer); return Res; } +int GDS_GrayMap( struct GDS_Device* Device, uint8_t Level) { + switch(Device->Mode) { + case GDS_MONO: return Level; + case GDS_GRAYSCALE: return Level >> (8 - Device->Depth); + case GDS_RGB332: + Level >>= 5; + return (Level << 6) | (Level << 3) | (Level >> 1); + case GDS_RGB444: + Level >>= 4; + return (Level << 8) | (Level << 4) | Level; + case GDS_RGB555: + Level >>= 3; + return (Level << 10) | (Level << 5) | Level; + case GDS_RGB565: + Level >>= 2; + return ((Level & ~0x01) << 10) | (Level << 5) | (Level >> 1); + case GDS_RGB666: + Level >>= 2; + return (Level << 12) | (Level << 6) | Level; + case GDS_RGB888: + return (Level << 16) | (Level << 8) | Level; + } + + return -1; +} + void GDS_SetContrast( struct GDS_Device* Device, uint8_t Contrast ) { if (Device->SetContrast) Device->SetContrast( Device, Contrast); } void GDS_SetHFlip( struct GDS_Device* Device, bool On ) { if (Device->SetHFlip) Device->SetHFlip( Device, On ); } void GDS_SetVFlip( struct GDS_Device* Device, bool On ) { if (Device->SetVFlip) Device->SetVFlip( Device, On ); } @@ -162,5 +203,6 @@ void GDS_SetDirty( struct GDS_Device* Device ) { Device->Dirty = true; } int GDS_GetWidth( struct GDS_Device* Device ) { return Device->Width; } int GDS_GetHeight( struct GDS_Device* Device ) { return Device->Height; } int GDS_GetDepth( struct GDS_Device* Device ) { return Device->Depth; } +int GDS_GetMode( struct GDS_Device* Device ) { return Device->Mode; } void GDS_DisplayOn( struct GDS_Device* Device ) { if (Device->DisplayOn) Device->DisplayOn( Device ); } void GDS_DisplayOff( struct GDS_Device* Device ) { if (Device->DisplayOff) Device->DisplayOff( Device ); } \ No newline at end of file diff --git a/components/display/core/gds.h b/components/display/core/gds.h index 6b4e58c2..bfbc29e4 100644 --- a/components/display/core/gds.h +++ b/components/display/core/gds.h @@ -13,14 +13,12 @@ monochrome mode is not such type of screen, SH1106 and SSD1306 are */ -enum { GDS_COLOR_L0 = 0, GDS_COLOR_L1, GDS_COLOR_L2, GDS_COLOR_L3, GDS_COLOR_L4, GDS_COLOR_L5, GDS_COLOR_L6, GDS_COLOR_L7, - GDS_COLOR_L8, GDS_COLOR_L9, GDS_COLOR_L10, GDS_COLOR_L11, GDS_COLOR_L12, GDS_COLOR_L13, GDS_COLOR_L14, GDS_COLOR_L15, - GDS_COLOR_MAX -}; +// this is an ordered enum, do not change! +enum { GDS_MONO = 0, GDS_GRAYSCALE, GDS_RGB332, GDS_RGB444, GDS_RGB555, GDS_RGB565, GDS_RGB666, GDS_RGB888 }; #define GDS_COLOR_BLACK (0) #define GDS_COLOR_WHITE (-1) -#define GDS_COLOR_XOR (GDS_COLOR_MAX + 1) +#define GDS_COLOR_XOR (256) struct GDS_Device; struct GDS_FontDef; @@ -39,6 +37,8 @@ void GDS_SetDirty( struct GDS_Device* Device ); int GDS_GetWidth( struct GDS_Device* Device ); int GDS_GetHeight( struct GDS_Device* Device ); int GDS_GetDepth( struct GDS_Device* Device ); +int GDS_GetMode( struct GDS_Device* Device ); +int GDS_GrayMap( struct GDS_Device* Device, uint8_t Level ); void GDS_ClearExt( struct GDS_Device* Device, bool full, ...); void GDS_Clear( struct GDS_Device* Device, int Color ); void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color ); diff --git a/components/display/core/gds_draw.c b/components/display/core/gds_draw.c index 1b7c0296..8e9ef765 100644 --- a/components/display/core/gds_draw.c +++ b/components/display/core/gds_draw.c @@ -45,7 +45,8 @@ __attribute__( ( always_inline ) ) static inline void SwapInt( int* a, int* b ) *a = Temp; } - +// un-comment if need to be instanciated for external callers +extern inline void IRAM_ATTR GDS_DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color ); extern inline void IRAM_ATTR GDS_DrawPixel( struct GDS_Device* Device, int X, int Y, int Color ); void GDS_DrawHLine( struct GDS_Device* Device, int x, int y, int Width, int Color ) { @@ -198,7 +199,9 @@ void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int if (Device->DrawBitmapCBR) { Device->DrawBitmapCBR( Device, Data, Width, Height, Color ); } else if (Device->Depth == 1) { + Height >>= 3; + // need to do row/col swap and bit-reverse for (int r = 0; r < Height; r++) { uint8_t *optr = Device->Framebuffer + r*Device->Width, *iptr = Data + r; @@ -210,8 +213,10 @@ void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int } else if (Device->Depth == 4) { uint8_t *optr = Device->Framebuffer; int LineLen = Device->Width >> 1; + Height >>= 3; Color &= 0x0f; + for (int i = Width * Height, r = 0, c = 0; --i >= 0;) { uint8_t Byte = BitReverseTable256[*Data++]; // we need to linearize code to let compiler better optimize @@ -237,8 +242,78 @@ void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int // end of a column, move to next one if (++r == Height) { c++; r = 0; optr = Device->Framebuffer + (c >> 1); } } + } else if (Device->Depth == 8) { + uint8_t *optr = Device->Framebuffer; + int LineLen = Device->Width; + + Height >>= 3; + + for (int i = Width * Height, r = 0, c = 0; --i >= 0;) { + uint8_t Byte = BitReverseTable256[*Data++]; + + // we need to linearize code to let compiler better optimize + *optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1; + *optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1; + *optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1; + *optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1; + *optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1; + *optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1; + *optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1; + *optr = ((Byte & 0x01) * Color); optr += LineLen; + + // end of a column, move to next one + if (++r == Height) { c++; r = 0; optr = Device->Framebuffer + c; } + } + } else if (Device->Depth == 16) { + uint16_t *optr = (uint16_t*) Device->Framebuffer; + int LineLen = Device->Width; + + Height >>= 3; + Color = __builtin_bswap16(Color); + + for (int i = Width * Height, r = 0, c = 0; --i >= 0;) { + uint8_t Byte = BitReverseTable256[*Data++]; + + // we need to linearize code to let compiler better optimize + *optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1; + *optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1; + *optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1; + *optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1; + *optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1; + *optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1; + *optr = ((Byte & 0x01) * Color); optr += LineLen; Byte >>= 1; + *optr = ((Byte & 0x01) * Color); optr += LineLen; + + // end of a column, move to next one + if (++r == Height) { c++; r = 0; optr = (uint16_t*) Device->Framebuffer + c; } + } + } else if (Device->Depth == 24) { + uint8_t *optr = Device->Framebuffer; + int LineLen = Device->Width * 3; + + Height >>= 3; + if (Device->Mode == GDS_RGB666) Color = ((Color << 4) & 0xff0000) | ((Color << 2) & 0xff00) | (Color & 0x00ff); + + for (int i = Width * Height, r = 0, c = 0; --i >= 0;) { + uint8_t Byte = BitReverseTable256[*Data++]; + + // we need to linearize code to let compiler better optimize + #define SET24(O,D) O[0]=(D)>>16; O[1]=(D)>>8; O[2]=(D); + SET24(optr,(Byte & 0x01) * Color); optr += LineLen; Byte >>= 1; + SET24(optr,(Byte & 0x01) * Color); optr += LineLen; Byte >>= 1; + SET24(optr,(Byte & 0x01) * Color); optr += LineLen; Byte >>= 1; + SET24(optr,(Byte & 0x01) * Color); optr += LineLen; Byte >>= 1; + SET24(optr,(Byte & 0x01) * Color); optr += LineLen; Byte >>= 1; + SET24(optr,(Byte & 0x01) * Color); optr += LineLen; Byte >>= 1; + SET24(optr,(Byte & 0x01) * Color); optr += LineLen; Byte >>= 1; + SET24(optr,(Byte & 0x01) * Color); optr += LineLen; + + // end of a column, move to next one + if (++r == Height) { c++; r = 0; optr = Device->Framebuffer + c * 3; } + } } else { Height >>= 3; + // don't know bitdepth, use brute-force solution for (int i = Width * Height, r = 0, c = 0; --i >= 0;) { uint8_t Byte = *Data++; @@ -268,6 +343,6 @@ void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int } */ } - + Device->Dirty = true; -} +} \ No newline at end of file diff --git a/components/display/core/gds_image.c b/components/display/core/gds_image.c index a1898bbb..467cca8c 100644 --- a/components/display/core/gds_image.c +++ b/components/display/core/gds_image.c @@ -24,10 +24,11 @@ typedef struct { const unsigned char *InData; // Pointer to jpeg data int InPos; // Current position in jpeg data int Width, Height; + uint8_t Mode; union { - uint16_t *OutData; // Decompress + void *OutData; struct { // DirectDraw - struct GDS_Device * Device; + struct GDS_Device *Device; int XOfs, YOfs; int XMin, YMin; int Depth; @@ -35,6 +36,40 @@ typedef struct { }; } JpegCtx; +/**************************************************************************************** + * RGB conversion (24 bits 888: RRRRRRRRGGGGGGGGBBBBBBBB and 16 bits 565: RRRRRGGGGGGBBBBB = B31..B0) + * so in other words for an array of 888 bytes: [0]=B, [1]=G, [2]=R, ... + * monochrome (0.2125 * color.r) + (0.7154 * color.g) + (0.0721 * color.b) + * grayscale (0.3 * R) + (0.59 * G) + (0.11 * B) ) + */ +inline int Scaler332(uint8_t *Pixels) { + return (Pixels[2] & ~0x1f) | ((Pixels[1] & ~0x1f) >> 3) | (Pixels[0] >> 6); +} + +inline int Scaler444(uint8_t *Pixels) { + return ((Pixels[2] & ~0x0f) << 4) | (Pixels[1] & ~0x0f) | (Pixels[0] >> 4); +} + +inline int Scaler555(uint8_t *Pixels) { + return ((Pixels[2] & ~0x07) << 7) | ((Pixels[1] & ~0x07) << 2) | (Pixels[0] >> 3); +} + +inline int Scaler565(uint8_t *Pixels) { + return ((Pixels[2] & ~0x07) << 8) | ((Pixels[1] & ~0x03) << 3) | (Pixels[0] >> 3); +} + +inline int Scaler666(uint8_t *Pixels) { + return ((Pixels[2] & ~0x03) << 10) | ((Pixels[1] & ~0x03) << 4) | (Pixels[0] >> 2); +} + +inline int Scaler888(uint8_t *Pixels) { + return (Pixels[2] << 16) | (Pixels[1] << 8) | Pixels[0]; +} + +inline int ScalerGray(uint8_t *Pixels) { + return (Pixels[2] * 14 + Pixels[1] * 76 + Pixels[0] * 38) >> 7; +} + static unsigned InHandler(JDEC *Decoder, uint8_t *Buf, unsigned Len) { JpegCtx *Context = (JpegCtx*) Decoder->device; if (Buf) memcpy(Buf, Context->InData + Context->InPos, Len); @@ -42,43 +77,94 @@ static unsigned InHandler(JDEC *Decoder, uint8_t *Buf, unsigned Len) { return Len; } +#define OUTHANDLER(F) \ + for (int y = Frame->top; y <= Frame->bottom; y++) { \ + for (int x = Frame->left; x <= Frame->right; x++) { \ + OutData[Context->Width * y + x] = F(Pixels); \ + Pixels += 3; \ + } \ + } + +#define OUTHANDLER24(F) \ + for (int y = Frame->top; y <= Frame->bottom; y++) { \ + uint8_t *p = OutData + (Context->Width * y + Frame->left) * 3; \ + for (int c = Frame->right - Frame->left; c-- >= 0;) { \ + uint32_t v = F(Pixels); \ + *p++ = v; *p++ = v >> 8; *p++ = v >> 16; \ + Pixels += 3; \ + } \ + } + static unsigned OutHandler(JDEC *Decoder, void *Bitmap, JRECT *Frame) { JpegCtx *Context = (JpegCtx*) Decoder->device; uint8_t *Pixels = (uint8_t*) Bitmap; - - for (int y = Frame->top; y <= Frame->bottom; y++) { - for (int x = Frame->left; x <= Frame->right; x++) { - // Convert the 888 to RGB565 - uint16_t Value = (*Pixels++ & ~0x07) << 8; - Value |= (*Pixels++ & ~0x03) << 3; - Value |= *Pixels++ >> 3; - Context->OutData[Context->Width * y + x] = Value; - } - } + + // decoded image is RGB888 + if (Context->Mode == GDS_RGB888) { + uint8_t *OutData = (uint8_t*) Context->OutData; + OUTHANDLER24(Scaler888); + } else if (Context->Mode == GDS_RGB666) { + uint8_t *OutData = (uint8_t*) Context->OutData; + OUTHANDLER24(Scaler666); + } else if (Context->Mode == GDS_RGB565) { + uint16_t *OutData = (uint16_t*) Context->OutData; + OUTHANDLER(Scaler565); + } else if (Context->Mode == GDS_RGB555) { + uint16_t *OutData = (uint16_t*) Context->OutData; + OUTHANDLER(Scaler555); + } else if (Context->Mode == GDS_RGB444) { + uint16_t *OutData = (uint16_t*) Context->OutData; + OUTHANDLER(Scaler444); + } else if (Context->Mode == GDS_RGB332) { + uint8_t *OutData = (uint8_t*) Context->OutData; + OUTHANDLER(Scaler332); + } else if (Context->Mode <= GDS_GRAYSCALE) { + uint8_t *OutData = (uint8_t*) Context->OutData; + OUTHANDLER(ScalerGray); + } + return 1; } +// Convert the RGB888 to destination color plane, use DrawPixel and not "fast" +// version as X,Y may be beyond screen +#define OUTHANDLERDIRECT(F,S) \ + for (int y = Frame->top; y <= Frame->bottom; y++) { \ + if (y < Context->YMin) continue; \ + for (int x = Frame->left; x <= Frame->right; x++) { \ + if (x < Context->XMin) continue; \ + GDS_DrawPixel( Context->Device, x + Context->XOfs, y + Context->YOfs, F(Pixels) >> S); \ + Pixels += 3; \ + } \ + } + static unsigned OutHandlerDirect(JDEC *Decoder, void *Bitmap, JRECT *Frame) { JpegCtx *Context = (JpegCtx*) Decoder->device; uint8_t *Pixels = (uint8_t*) Bitmap; int Shift = 8 - Context->Depth; - for (int y = Frame->top; y <= Frame->bottom; y++) { - if (y < Context->YMin) continue; - for (int x = Frame->left; x <= Frame->right; x++) { - if (x < Context->XMin) continue; - // Convert the 888 to RGB565 - int Value = ((Pixels[0]*11 + Pixels[1]*59 + Pixels[2]*30) / 100) >> Shift; - Pixels += 3; - // used DrawPixel and not "fast" version as X,Y may be beyond screen - GDS_DrawPixel( Context->Device, x + Context->XOfs, y + Context->YOfs, Value); - } - } + // decoded image is RGB888, shift only make sense for grayscale + if (Context->Mode == GDS_RGB888) { + OUTHANDLERDIRECT(Scaler888, 0); + } else if (Context->Mode == GDS_RGB666) { + OUTHANDLERDIRECT(Scaler666, 0); + } else if (Context->Mode == GDS_RGB565) { + OUTHANDLERDIRECT(Scaler565, 0); + } else if (Context->Mode == GDS_RGB555) { + OUTHANDLERDIRECT(Scaler555, 0); + } else if (Context->Mode == GDS_RGB444) { + OUTHANDLERDIRECT(Scaler444, 0); + } else if (Context->Mode == GDS_RGB332) { + OUTHANDLERDIRECT(Scaler332, 0); + } else if (Context->Mode <= GDS_GRAYSCALE) { + OUTHANDLERDIRECT(ScalerGray, Shift); + } + return 1; } //Decode the embedded image into pixel lines that can be used with the rest of the logic. -static uint16_t* DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scale, bool SizeOnly) { +static void* DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scale, bool SizeOnly, int RGB_Mode) { JDEC Decoder; JpegCtx Context; char *Scratch = calloc(SCRATCH_SIZE, 1); @@ -99,7 +185,9 @@ static uint16_t* DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scal Decoder.scale = Scale; if (Res == JDR_OK && !SizeOnly) { - Context.OutData = malloc(Decoder.width * Decoder.height * sizeof(uint16_t)); + if (RGB_Mode <= GDS_RGB332) Context.OutData = malloc(Decoder.width * Decoder.height); + else if (RGB_Mode < GDS_RGB666) Context.OutData = malloc(Decoder.width * Decoder.height * 2); + else if (RGB_Mode <= GDS_RGB888) Context.OutData = malloc(Decoder.width * Decoder.height * 3); // find the scaling factor uint8_t N = 0, ScaleInt = ceil(1.0 / Scale); @@ -114,6 +202,7 @@ static uint16_t* DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scal if (Context.OutData) { Context.Width = Decoder.width / (1 << N); Context.Height = Decoder.height / (1 << N); + Context.Mode = RGB_Mode; if (Width) *Width = Context.Width; if (Height) *Height = Context.Height; Res = jd_decomp(&Decoder, OutHandler, N); @@ -121,150 +210,165 @@ static uint16_t* DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scal ESP_LOGE(TAG, "Image decoder: jd_decode failed (%d)", Res); } } else { - ESP_LOGE(TAG, "Can't allocate bitmap %dx%d", Decoder.width, Decoder.height); + ESP_LOGE(TAG, "Can't allocate bitmap %dx%d or invalid mode %d", Decoder.width, Decoder.height, RGB_Mode); } } else if (!SizeOnly) { ESP_LOGE(TAG, "Image decoder: jd_prepare failed (%d)", Res); } - + // free scratch area if (Scratch) free(Scratch); return Context.OutData; } -uint16_t* GDS_DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scale) { - return DecodeJPEG(Source, Width, Height, Scale, false); +void* GDS_DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scale, int RGB_Mode) { + return DecodeJPEG(Source, Width, Height, Scale, false, RGB_Mode); } void GDS_GetJPEGSize(uint8_t *Source, int *Width, int *Height) { - DecodeJPEG(Source, Width, Height, 1, true); + DecodeJPEG(Source, Width, Height, 1, true, -1); } /**************************************************************************************** - * Simply draw a RGB 16bits image + * RGB conversion (24 bits: RRRRRRRRGGGGGGGGBBBBBBBB and 16 bits 565: RRRRRGGGGGGBBBBB = B31..B0) + * so in other words for an array of 888 bytes: [0]=B, [1]=G, [2]=R, ... * monochrome (0.2125 * color.r) + (0.7154 * color.g) + (0.0721 * color.b) * grayscale (0.3 * R) + (0.59 * G) + (0.11 * B) ) */ -void GDS_DrawRGB16( struct GDS_Device* Device, uint16_t *Image, int x, int y, int Width, int Height, int RGB_Mode ) { - if (Device->DrawRGB16) { - Device->DrawRGB16( Device, Image, x, y, Width, Height, RGB_Mode ); - } else { - switch(RGB_Mode) { - case GDS_RGB565: - // 6 bits pixels to be placed. Use a linearized structure for a bit of optimization - if (Device->Depth < 6) { - int Scale = 6 - Device->Depth; - for (int r = 0; r < Height; r++) { - for (int c = 0; c < Width; c++) { - int pixel = *Image++; - pixel = ((((pixel & 0x1f) * 11) << 1) + ((pixel >> 5) & 0x3f) * 59 + (((pixel >> 11) * 30) << 1) + 1) / 100; - GDS_DrawPixel( Device, c + x, r + y, pixel >> Scale); - } - } - } else { - int Scale = Device->Depth - 6; - for (int r = 0; r < Height; r++) { - for (int c = 0; c < Width; c++) { - int pixel = *Image++; - pixel = ((((pixel & 0x1f) * 11) << 1) + ((pixel >> 5) & 0x3f) * 59 + (((pixel >> 11) * 30) << 1) + 1) / 100; - GDS_DrawPixel( Device, c + x, r + y, pixel << Scale); - } - } - } - break; - case GDS_RGB555: - // 5 bits pixels to be placed Use a linearized structure for a bit of optimization - if (Device->Depth < 5) { - int Scale = 5 - Device->Depth; - for (int r = 0; r < Height; r++) { - for (int c = 0; c < Width; c++) { - int pixel = *Image++; - pixel = ((pixel & 0x1f) * 11 + ((pixel >> 5) & 0x1f) * 59 + (pixel >> 10) * 30) / 100; - GDS_DrawPixel( Device, c + x, r + y, pixel >> Scale); - } - } - } else { - int Scale = Device->Depth - 5; - for (int r = 0; r < Height; r++) { - for (int c = 0; c < Width; c++) { - int pixel = *Image++; - pixel = ((pixel & 0x1f) * 11 + ((pixel >> 5) & 0x1f) * 59 + (pixel >> 10) * 30) / 100; - GDS_DrawPixel( Device, c + x, r + y, pixel << Scale); - } - } - } - break; - case GDS_RGB444: - // 4 bits pixels to be placed - if (Device->Depth < 4) { - int Scale = 4 - Device->Depth; - for (int r = 0; r < Height; r++) { - for (int c = 0; c < Width; c++) { - int pixel = *Image++; - pixel = (pixel & 0x0f) * 11 + ((pixel >> 4) & 0x0f) * 59 + (pixel >> 8) * 30; - GDS_DrawPixel( Device, c + x, r + y, pixel >> Scale); - } - } - } else { - int Scale = Device->Depth - 4; - for (int r = 0; r < Height; r++) { - for (int c = 0; c < Width; c++) { - int pixel = *Image++; - pixel = (pixel & 0x0f) * 11 + ((pixel >> 4) & 0x0f) * 59 + (pixel >> 8) * 30; - GDS_DrawPixel( Device, c + x, r + y, pixel << Scale); - } - } - } - break; - } + +inline int ToGray888(uint8_t **Pixel) { + uint32_t v = *(*Pixel)++; v |= *(*Pixel)++ << 8; v |= *(*Pixel)++ << 16; + return (((v & 0xff) * 14) + ((v >> 8) & 0xff) * 76 + ((v >> 16) * 38) + 1) >> 7; +} + +inline int ToGray666(uint8_t **Pixel) { + uint32_t v = *(*Pixel)++; v |= *(*Pixel)++ << 8; v |= *(*Pixel)++ << 16; + return (((v & 0x3f) * 14) + ((v >> 6) & 0x3f) * 76 + ((v >> 12) * 38) + 1) >> 7; +} + +inline int ToGray565(uint16_t **Pixel) { + uint16_t v = *(*Pixel)++; + return ((((v & 0x1f) * 14) << 1) + ((v >> 5) & 0x3f) * 76 + (((v >> 11) * 38) << 1) + 1) >> 7; +} + +inline int ToGray555(uint16_t **Pixel) { + uint16_t v = *(*Pixel)++; + return ((v & 0x1f) * 14 + ((v >> 5) & 0x1f) * 76 + (v >> 10) * 38) >> 7; +} + +inline int ToGray444(uint16_t **Pixel) { + uint16_t v = *(*Pixel)++; + return ((v & 0x0f) * 14 + ((v >> 4) & 0x0f) * 76 + (v >> 8) * 38) >> 7; +} + +inline int ToGray332(uint8_t **Pixel) { + uint8_t v = *(*Pixel)++; + return ((((v & 0x3) * 14) << 1) + ((v >> 2) & 0x7) * 76 + (v >> 5) * 38 + 1) >> 7; +} + +inline int ToSelf(uint8_t **Pixel) { + return *(*Pixel)++; +} + +#define DRAW_GRAYRGB(S,F) \ + if (Scale > 0) { \ + for (int r = 0; r < Height; r++) { \ + for (int c = 0; c < Width; c++) { \ + GDS_DrawPixel( Device, c + x, r + y, F(S) >> Scale); \ + } \ + } \ + } else { \ + for (int r = 0; r < Height; r++) { \ + for (int c = 0; c < Width; c++) { \ + GDS_DrawPixel( Device, c + x, r + y, F(S) << -Scale); \ + } \ + } \ + } + +#define DRAW_RGB(T) \ + T *S = (T*) Image; \ + for (int r = 0; r < Height; r++) { \ + for (int c = 0; c < Width; c++) { \ + GDS_DrawPixel(Device, c + x, r + y, *S++); \ + } \ + } + +#define DRAW_RGB24 \ + uint8_t *S = (uint8_t*) Image; \ + for (int r = 0; r < Height; r++) { \ + for (int c = 0; c < Width; c++) { \ + uint32_t v = *S++; v |= *S++ << 8; v |= *S++ << 16; \ + GDS_DrawPixel(Device, c + x, r + y, v); \ + } \ } + +/**************************************************************************************** + * Decode the embedded image into pixel lines that can be used with the rest of the logic. + */ +void GDS_DrawRGB( struct GDS_Device* Device, uint8_t *Image, int x, int y, int Width, int Height, int RGB_Mode ) { + + // don't do anything if driver supplies a draw function + if (Device->DrawRGB) { + Device->DrawRGB( Device, Image, x, y, Width, Height, RGB_Mode ); + Device->Dirty = true; + return; + } + + // RGB type displays + if (Device->Mode > GDS_GRAYSCALE) { + // image must match the display mode! + if (Device->Mode != RGB_Mode) { + ESP_LOGE(TAG, "non-matching display & image mode %u %u", Device->Mode, RGB_Mode); + return; + } + + if (RGB_Mode == GDS_RGB332) { + DRAW_RGB(uint8_t); + } else if (RGB_Mode < GDS_RGB666) { + DRAW_RGB(uint16_t); + } else { + DRAW_RGB24; + } + + Device->Dirty = true; + return; + } + + // set the right scaler when displaying grayscale + if (RGB_Mode <= GDS_GRAYSCALE) { + int Scale = 8 - Device->Depth; + DRAW_GRAYRGB(&Image,ToSelf); + } else if (RGB_Mode == GDS_RGB332) { + int Scale = 3 - Device->Depth; + DRAW_GRAYRGB(&Image,ToGray332); + } else if (RGB_Mode < GDS_RGB666) { + if (RGB_Mode == GDS_RGB565) { + int Scale = 6 - Device->Depth; + DRAW_GRAYRGB((uint16_t**)&Image,ToGray565); + } else if (RGB_Mode == GDS_RGB555) { + int Scale = 5 - Device->Depth; + DRAW_GRAYRGB((uint16_t**)&Image,ToGray555); + } else if (RGB_Mode == GDS_RGB444) { + int Scale = 4 - Device->Depth; + DRAW_GRAYRGB((uint16_t**)&Image,ToGray444) + } + } else { + if (RGB_Mode == GDS_RGB666) { + int Scale = 6 - Device->Depth; + DRAW_GRAYRGB(&Image,ToGray666); + } else if (RGB_Mode == GDS_RGB888) { + int Scale = 8 - Device->Depth; + DRAW_GRAYRGB(&Image,ToGray888); + } + } Device->Dirty = true; } /**************************************************************************************** - * Simply draw a RGB 8 bits image (R:3,G:3,B:2) or plain grayscale - * monochrome (0.2125 * color.r) + (0.7154 * color.g) + (0.0721 * color.b) - * grayscale (0.3 * R) + (0.59 * G) + (0.11 * B) ) + * Decode the embedded image into pixel lines that can be used with the rest of the logic. */ -void GDS_DrawRGB8( struct GDS_Device* Device, uint8_t *Image, int x, int y, int Width, int Height, int RGB_Mode ) { - if (Device->DrawRGB8) { - Device->DrawRGB8( Device, Image, x, y, Width, Height, RGB_Mode ); - } else if (RGB_Mode == GDS_GRAYSCALE) { - // 8 bits pixels - int Scale = 8 - Device->Depth; - for (int r = 0; r < Height; r++) { - for (int c = 0; c < Width; c++) { - GDS_DrawPixel( Device, c + x, r + y, *Image++ >> Scale); - } - } - } else if (Device->Depth < 3) { - // 3 bits pixels to be placed - int Scale = 3 - Device->Depth; - for (int r = 0; r < Height; r++) { - for (int c = 0; c < Width; c++) { - int pixel = *Image++; - pixel = ((((pixel & 0x3) * 11) << 1) + ((pixel >> 2) & 0x7) * 59 + (pixel >> 5) * 30 + 1) / 100; - GDS_DrawPixel( Device, c + x, r + y, pixel >> Scale); - } - } - } else { - // 3 bits pixels to be placed - int Scale = Device->Depth - 3; - for (int r = 0; r < Height; r++) { - for (int c = 0; c < Width; c++) { - int pixel = *Image++; - pixel = ((((pixel & 0x3) * 11) << 1) + ((pixel >> 2) & 0x7) * 59 + (pixel >> 5) * 30 + 1) / 100; - GDS_DrawPixel( Device, c + x, r + y, pixel << Scale); - } - } - } - - Device->Dirty = true; -} - -//Decode the embedded image into pixel lines that can be used with the rest of the logic. -bool GDS_DrawJPEG( struct GDS_Device* Device, uint8_t *Source, int x, int y, int Fit) { +bool GDS_DrawJPEG(struct GDS_Device* Device, uint8_t *Source, int x, int y, int Fit) { JDEC Decoder; JpegCtx Context; bool Ret = false; @@ -313,6 +417,7 @@ bool GDS_DrawJPEG( struct GDS_Device* Device, uint8_t *Source, int x, int y, int Context.XMin = x - Context.XOfs; Context.YMin = y - Context.YOfs; + Context.Mode = Device->Mode; // do decompress & draw Res = jd_decomp(&Decoder, OutHandlerDirect, N); diff --git a/components/display/core/gds_image.h b/components/display/core/gds_image.h index 2017ba38..ff0c3e6d 100644 --- a/components/display/core/gds_image.h +++ b/components/display/core/gds_image.h @@ -15,8 +15,6 @@ struct GDS_Device; -enum { GDS_RGB565, GDS_RGB555, GDS_RGB444, GDS_RGB332, GDS_GRAYSCALE }; - // Fit options for GDS_DrawJPEG #define GDS_IMAGE_LEFT 0x00 #define GDS_IMAGE_CENTER_X 0x01 @@ -28,8 +26,7 @@ enum { GDS_RGB565, GDS_RGB555, GDS_RGB444, GDS_RGB332, GDS_GRAYSCALE }; #define GDS_IMAGE_FIT 0x10 // re-scale by a factor of 2^N (up to 3) // Width and Height can be NULL if you already know them (actual scaling is closest ^2) -uint16_t* GDS_DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scale); +void* GDS_DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scale, int RGB_Mode); // can be 8, 16 or 24 bits per pixel in return void GDS_GetJPEGSize(uint8_t *Source, int *Width, int *Height); -bool GDS_DrawJPEG( struct GDS_Device* Device, uint8_t *Source, int x, int y, int Fit); -void GDS_DrawRGB16( struct GDS_Device* Device, uint16_t *Image, int x, int y, int Width, int Height, int RGB_Mode ); -void GDS_DrawRGB8( struct GDS_Device* Device, uint8_t *Image, int x, int y, int Width, int Height, int RGB_Mode ); \ No newline at end of file +bool GDS_DrawJPEG( struct GDS_Device* Device, uint8_t *Source, int x, int y, int Fit); +void GDS_DrawRGB( struct GDS_Device* Device, uint8_t *Image, int x, int y, int Width, int Height, int RGB_Mode ); diff --git a/components/display/core/gds_private.h b/components/display/core/gds_private.h index 42466cd8..ca672b4c 100644 --- a/components/display/core/gds_private.h +++ b/components/display/core/gds_private.h @@ -93,7 +93,7 @@ struct GDS_Device { uint16_t Width; uint16_t Height; - uint8_t Depth; + uint8_t Depth, Mode; uint8_t Alloc; uint8_t* Framebuffer; @@ -119,8 +119,7 @@ struct GDS_Device { void (*DrawPixelFast)( struct GDS_Device* Device, int X, int Y, int Color ); void (*DrawBitmapCBR)(struct GDS_Device* Device, uint8_t *Data, int Width, int Height, int Color ); // may provide for optimization - void (*DrawRGB16)( struct GDS_Device* Device, uint16_t *Image,int x, int y, int Width, int Height, int RGB_Mode ); - void (*DrawRGB8)( struct GDS_Device* Device, uint8_t *Image, int x, int y, int Width, int Height, int RGB_Mode ); + void (*DrawRGB)( struct GDS_Device* Device, uint8_t *Image,int x, int y, int Width, int Height, int RGB_Mode ); void (*ClearWindow)( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color ); // interface-specific methods @@ -134,7 +133,7 @@ struct GDS_Device { bool GDS_Reset( struct GDS_Device* Device ); bool GDS_Init( struct GDS_Device* Device ); -static inline bool IsPixelVisible( struct GDS_Device* Device, int x, int y ) { +inline bool IsPixelVisible( struct GDS_Device* Device, int x, int y ) { bool Result = ( ( x >= 0 ) && ( x < Device->Width ) && @@ -151,9 +150,9 @@ static inline bool IsPixelVisible( struct GDS_Device* Device, int x, int y ) { return Result; } -static inline void IRAM_ATTR GDS_DrawPixel1Fast( struct GDS_Device* Device, int X, int Y, int Color ) { +inline void IRAM_ATTR GDS_DrawPixel1Fast( struct GDS_Device* Device, int X, int Y, int Color ) { uint32_t YBit = ( Y & 0x07 ); - uint8_t* FBOffset = NULL; + uint8_t* FBOffset; /* * We only need to modify the Y coordinate since the pitch @@ -172,23 +171,49 @@ static inline void IRAM_ATTR GDS_DrawPixel1Fast( struct GDS_Device* Device, int } } -static inline void IRAM_ATTR GDS_DrawPixel4Fast( struct GDS_Device* Device, int X, int Y, int Color ) { - uint8_t* FBOffset; - - FBOffset = Device->Framebuffer + ( (Y * Device->Width >> 1) + (X >> 1)); +inline void IRAM_ATTR GDS_DrawPixel4Fast( struct GDS_Device* Device, int X, int Y, int Color ) { + uint8_t* FBOffset = Device->Framebuffer + ( (Y * Device->Width >> 1) + (X >> 1)); *FBOffset = X & 0x01 ? (*FBOffset & 0x0f) | ((Color & 0x0f) << 4) : ((*FBOffset & 0xf0) | (Color & 0x0f)); } -static inline void IRAM_ATTR GDS_DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color ) { +inline void IRAM_ATTR GDS_DrawPixel8Fast( struct GDS_Device* Device, int X, int Y, int Color ) { + Device->Framebuffer[Y * Device->Width + X] = Color; +} + +// assumes that Color is 16 bits R..RG..GB..B from MSB to LSB and FB wants 1st serialized byte to start with R +inline void IRAM_ATTR GDS_DrawPixel16Fast( struct GDS_Device* Device, int X, int Y, int Color ) { + uint16_t* FBOffset = (uint16_t*) Device->Framebuffer + Y * Device->Width + X; + *FBOffset = __builtin_bswap16(Color); +} + +// assumes that Color is 18 bits RGB from MSB to LSB RRRRRRGGGGGGBBBBBB, so byte[0] is B +// FB is 3-bytes packets and starts with R for serialization so 0,1,2 ... = xxRRRRRR xxGGGGGG xxBBBBBB +inline void IRAM_ATTR GDS_DrawPixel18Fast( struct GDS_Device* Device, int X, int Y, int Color ) { + uint8_t* FBOffset = Device->Framebuffer + (Y * Device->Width + X) * 3; + *FBOffset++ = Color >> 12; *FBOffset++ = (Color >> 6) & 0x3f; *FBOffset = Color & 0x3f; +} + +// assumes that Color is 24 bits RGB from MSB to LSB RRRRRRRRGGGGGGGGBBBBBBBB, so byte[0] is B +// FB is 3-bytes packets and starts with R for serialization so 0,1,2 ... = RRRRRRRR GGGGGGGG BBBBBBBB +inline void IRAM_ATTR GDS_DrawPixel24Fast( struct GDS_Device* Device, int X, int Y, int Color ) { + uint8_t* FBOffset = Device->Framebuffer + (Y * Device->Width + X) * 3; + *FBOffset++ = Color >> 16; *FBOffset++ = Color >> 8; *FBOffset = Color; +} + +inline void IRAM_ATTR GDS_DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color ) { if (Device->DrawPixelFast) Device->DrawPixelFast( Device, X, Y, Color ); - else if (Device->Depth == 4) GDS_DrawPixel4Fast( Device, X, Y, Color); - else if (Device->Depth == 1) GDS_DrawPixel1Fast( Device, X, Y, Color); + else if (Device->Depth == 4) GDS_DrawPixel4Fast( Device, X, Y, Color ); + else if (Device->Depth == 1) GDS_DrawPixel1Fast( Device, X, Y, Color ); + else if (Device->Depth == 16) GDS_DrawPixel16Fast( Device, X, Y, Color ); + else if (Device->Depth == 24 && Device->Mode == GDS_RGB666) GDS_DrawPixel18Fast( Device, X, Y, Color ); + else if (Device->Depth == 24 && Device->Mode == GDS_RGB888) GDS_DrawPixel24Fast( Device, X, Y, Color ); + else if (Device->Depth == 8) GDS_DrawPixel8Fast( Device, X, Y, Color ); } -static inline void IRAM_ATTR GDS_DrawPixel( struct GDS_Device* Device, int x, int y, int Color ) { +inline void IRAM_ATTR GDS_DrawPixel( struct GDS_Device* Device, int x, int y, int Color ) { if ( IsPixelVisible( Device, x, y ) == true ) { GDS_DrawPixelFast( Device, x, y, Color ); } } -#endif +#endif \ No newline at end of file diff --git a/components/display/core/ifaces/default_if_spi.c b/components/display/core/ifaces/default_if_spi.c index 167f5760..14e2829c 100644 --- a/components/display/core/ifaces/default_if_spi.c +++ b/components/display/core/ifaces/default_if_spi.c @@ -12,7 +12,6 @@ #include #include #include -#include "freertos/FreeRTOS.h" #include #include "gds.h" #include "gds_err.h" @@ -51,6 +50,7 @@ bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int SPIDeviceConfig.clock_speed_hz = Speed > 0 ? Speed : SPI_MASTER_FREQ_8M; SPIDeviceConfig.spics_io_num = CSPin; SPIDeviceConfig.queue_size = 1; + SPIDeviceConfig.flags = SPI_DEVICE_NO_DUMMY; ESP_ERROR_CHECK_NONFATAL( spi_bus_add_device( SPIHost, &SPIDeviceConfig, &SPIDevice ), return false ); @@ -107,4 +107,4 @@ static bool SPIDefaultWriteData( struct GDS_Device* Device, const uint8_t* Data, NullCheck( Device->SPIHandle, return false ); return SPIDefaultWriteBytes( Device->SPIHandle, GDS_SPI_Data_Mode, Data, DataLength ); -} +} \ No newline at end of file diff --git a/components/display/display.c b/components/display/display.c index 18175b92..48c045d4 100644 --- a/components/display/display.c +++ b/components/display/display.c @@ -51,14 +51,15 @@ static const char *known_drivers[] = {"SH1106", "SSD1326", "SSD1327", "SSD1675", + "SSD1351", "ILI9341", NULL }; static void displayer_task(void *args); struct GDS_Device *display; -extern GDS_DetectFunc SSD1306_Detect, SSD132x_Detect, SH1106_Detect, SSD1675_Detect, SSD1322_Detect, ILI9341_Detect; -GDS_DetectFunc *drivers[] = { SH1106_Detect, SSD1306_Detect, SSD132x_Detect, SSD1675_Detect, SSD1322_Detect,ILI9341_Detect, NULL }; +extern GDS_DetectFunc SSD1306_Detect, SSD132x_Detect, SH1106_Detect, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, ILI9341_Detect; +GDS_DetectFunc *drivers[] = { SH1106_Detect, SSD1306_Detect, SSD132x_Detect, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, ILI9341_Detect, NULL }; /**************************************************************************************** * diff --git a/components/squeezelite/display.c b/components/squeezelite/display.c index 4af7afd8..e1bfaa48 100644 --- a/components/squeezelite/display.c +++ b/components/squeezelite/display.c @@ -123,6 +123,8 @@ static struct { bool owned; } displayer = { .dirty = true, .owned = true }; +static uint32_t *grayMap; + #define LONG_WAKE (10*1000) #define SB_HEIGHT 32 @@ -296,6 +298,12 @@ bool sb_display_init(void) { displayer.width = GDS_GetWidth(display); displayer.height = min(GDS_GetHeight(display), SB_HEIGHT); + // allocate gray-color mapping if needed; + if (GDS_GetMode(display) > GDS_GRAYSCALE) { + grayMap = malloc(256*sizeof(*grayMap)); + for (int i = 0; i < 256; i++) grayMap[i] = GDS_GrayMap(display, i); + } + // create visu configuration visu.bar_gap = 1; visu.speed = 100; @@ -571,22 +579,40 @@ void draw_VU(struct GDS_Device * display, const uint8_t *data, int level, int x, data += (VU_WIDTH - width) / 2 * VU_HEIGHT; } - // this is 8 bits grayscale - int scale = 8 - GDS_GetDepth(display); + if (GDS_GetMode(display) <= GDS_GRAYSCALE) { + // this is 8 bits grayscale + int scale = 8 - GDS_GetDepth(display); - // use "fast" version as we are not beyond screen boundaries - if (visu.rotate) { - for (int r = 0; r < width; r++) { - for (int c = VU_HEIGHT; --c >= 0;) { - GDS_DrawPixelFastExt(display, c + x, r + y, *data++ >> scale); + // use "fast" version as we are not beyond screen boundaries + if (visu.rotate) { + for (int r = 0; r < width; r++) { + for (int c = VU_HEIGHT; --c >= 0;) { + GDS_DrawPixelFast(display, c + x, r + y, *data++ >> scale); + } } + } else { + for (int r = 0; r < width; r++) { + for (int c = 0; c < VU_HEIGHT; c++) { + GDS_DrawPixelFast(display, r + x, c + y, *data++ >> scale); + } + } } } else { - for (int r = 0; r < width; r++) { - for (int c = 0; c < VU_HEIGHT; c++) { - GDS_DrawPixelFastExt(display, r + x, c + y, *data++ >> scale); + // use "fast" version as we are not beyond screen boundaries + if (visu.rotate) { + for (int r = 0; r < width; r++) { + for (int c = VU_HEIGHT; --c >= 0;) { + GDS_DrawPixelFast(display, c + x, r + y, grayMap[*data++]); + } } - } + } else { + for (int r = 0; r < width; r++) { + for (int c = 0; c < VU_HEIGHT; c++) { + GDS_DrawPixelFast(display, r + x, c + y, grayMap[*data++]); + } + } + } + } // need to manually set dirty flag as DrawPixel does not do it @@ -908,12 +934,13 @@ static void visu_update(void) { if (mode != VISU_VUMETER || !visu.style) { // there is much more optimization to be done here, like not redrawing bars unless needed + for (int i = visu.n; --i >= 0;) { // update maximum if (visu.bars[i].current > visu.bars[i].max) visu.bars[i].max = visu.bars[i].current; else if (visu.bars[i].max) visu.bars[i].max--; else if (!clear) continue; - + if (visu.rotate) { int x1 = visu.col; int y1 = visu.row + visu.border + visu.bar_border + i*(visu.bar_width + visu.bar_gap);