Browse Source

add color display support + SSD1351 driver

Philippe G 4 years ago
parent
commit
62824da779

+ 5 - 4
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;

+ 5 - 4
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;

+ 3 - 3
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;
 }

+ 8 - 9
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;
 }

+ 294 - 0
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 <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <esp_heap_caps.h>
+#include <esp_log.h>
+
+#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;
+}

+ 2 - 3
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;

+ 48 - 6
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 ); }

+ 5 - 5
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 );

+ 78 - 3
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;
-}
+}

+ 247 - 142
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;				
-		}
-	}	
-	
-	Device->Dirty = true;	
+ 
+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);					\
+		}															\
+	}	
+
 /****************************************************************************************
- * 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);
-			}	
+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;
 		}	
-	} 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);
-			}	
+		
+		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;
 	}
 	
-	Device->Dirty = true;		
-}	
+	// 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;	
+}
 
-//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) {
+/****************************************************************************************
+ *  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) {
     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);

+ 3 - 6
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 );
+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 );

+ 40 - 15
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

+ 2 - 2
components/display/core/ifaces/default_if_spi.c

@@ -12,7 +12,6 @@
 #include <string.h>
 #include <driver/spi_master.h>
 #include <driver/gpio.h>
-#include "freertos/FreeRTOS.h"
 #include <freertos/task.h>
 #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 );
-}
+}

+ 3 - 2
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 };
 
 /****************************************************************************************
  * 

+ 39 - 12
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);