浏览代码

Complete display + add JPEG

philippe44 5 年之前
父节点
当前提交
1ae8f80e53

+ 20 - 0
components/display/SSD132x.c

@@ -165,6 +165,25 @@ static void IRAM_ATTR DrawPixel1Fast( struct GDS_Device* Device, int X, int Y, i
     }
 }
 
+static void ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color ) {
+	uint8_t _Color = Color == GDS_COLOR_BLACK ? 0: 0xff;
+	uint8_t Width = Device->Width >> 3;
+	uint8_t *optr = Device->Framebuffer;
+
+	for (int r = y1; r <= y2; r++) {
+		int c = x1;
+		// for a row that is not on a boundary, not column opt can be done, so handle all columns on that line
+		while (c & 0x07 && c <= x2) DrawPixel1Fast( Device, c++, r, Color );
+		// at this point we are aligned on column boundary
+		int chunk = (x2 - c + 1) >> 3;
+		memset(optr + Width * r + (c >> 3), _Color, chunk );
+		c += chunk * 8;
+		while (c <= x2) DrawPixel1Fast( Device, c++, r, Color );
+	}
+	
+	Device->Dirty = true;
+}
+
 static void DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int Height, int Color ) {
 	uint8_t *optr = Device->Framebuffer;
 	
@@ -312,6 +331,7 @@ struct GDS_Device* SSD132x_Detect(char *Driver, struct GDS_Device* Device) {
 		Device->Update = Update1;
 		Device->DrawPixelFast = DrawPixel1Fast;
 		Device->DrawBitmapCBR = DrawBitmapCBR;
+		Device->ClearWindow = ClearWindow;
 	} else {
 		Device->Depth = 4;
 	}	

+ 8 - 6
components/display/core/gds.c

@@ -69,21 +69,22 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
 			memset( Device->Framebuffer, Color == GDS_COLOR_BLACK ? 0 : 0xff, Device->FramebufferSize );
 		} else {
 			uint8_t _Color = Color == GDS_COLOR_BLACK ? 0: 0xff;
-			uint8_t Width = Device->Width;
+			uint8_t Width = Device->Width >> 3;
+			uint8_t *optr = Device->Framebuffer;
 			// try to do byte processing as much as possible
 			for (int r = y1; r <= y2;) {
 				int c = x1;
 				// for a row that is not on a boundary, no optimization possible
 				while (r & 0x07 && r <= y2) {
-					for (c = x1; c <= x2; c++) GDS_DrawPixelFast( Device, c, r, Color);
+					for (c = x1; c <= x2; c++) GDS_DrawPixelFast( Device, c, r, Color );
 					r++;
 				}
 				// go fast if we have more than 8 lines to write
-				if (r + 8 < y2) {
-					memset(Device->Framebuffer + Width * r / 8 + x1, _Color, x2 - x1 + 1);
+				if (r + 8 <= y2) {
+					memset(optr + Width * r + x1, _Color, x2 - x1 + 1);
 					r += 8;
 				} else while (r <= y2) {
-					for (c = x1; c <= x2; c++) GDS_DrawPixelFast( Device, c, r, Color);
+					for (c = x1; c <= x2; c++) GDS_DrawPixelFast( Device, c, r, Color );
 					r++;
 				}
 			}
@@ -95,12 +96,13 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
 		} else {
 			uint8_t _Color = Color | (Color << 4);
 			uint8_t Width = Device->Width;
+			uint8_t *optr = Device->Framebuffer;
 			// try to do byte processing as much as possible
 			for (int r = y1; r <= y2; r++) {
 				int c = x1;
 				if (c & 0x01) GDS_DrawPixelFast( Device, c++, r, Color);
 				int chunk = (x2 - c + 1) >> 1;
-				memset(Device->Framebuffer + ((r * Width + c)  >> 1), _Color, chunk);
+				memset(optr + ((r * Width + c)  >> 1), _Color, chunk);
 				if (c + chunk <= x2) GDS_DrawPixelFast( Device, x2, r, Color);
 			}
 		}	

+ 1 - 1
components/display/core/gds_default_if.h

@@ -7,7 +7,7 @@ extern "C" {
 
 struct GDS_Device;
 
-bool GDS_I2CInit( int PortNumber, int SDA, int SCL );
+bool GDS_I2CInit( int PortNumber, int SDA, int SCL, int speed );
 bool GDS_I2CAttachDevice( struct GDS_Device* Device, int Width, int Height, int I2CAddress, int RSTPin );
 
 bool GDS_SPIInit( int SPI, int DC );

+ 225 - 0
components/display/core/gds_image.c

@@ -0,0 +1,225 @@
+#include <string.h>
+#include "math.h"
+#include "esp32/rom/tjpgd.h"
+#include "esp_log.h"
+
+#include "gds.h"
+#include "gds_private.h"
+#include "gds_image.h"
+
+const char TAG[] = "ImageDec";
+
+#define SCRATCH_SIZE	3100
+
+//Data that is passed from the decoder function to the infunc/outfunc functions.
+typedef struct {
+    const unsigned char *InData;	// Pointer to jpeg data
+    int InPos;						// Current position in jpeg data
+	int Width, Height;	
+	union {
+		uint16_t *OutData;				// Decompress
+		struct {						// DirectDraw
+			struct GDS_Device * Device;
+			int XOfs, YOfs;
+			int Depth;
+		};	
+	};	
+} JpegCtx;
+
+static unsigned InHandler(JDEC *Decoder, uint8_t *Buf, unsigned Len) {
+    JpegCtx *Context = (JpegCtx*) Decoder->device;
+    if (Buf) memcpy(Buf, Context->InData +  Context->InPos, Len);
+    Context->InPos += Len;
+    return Len;
+}
+
+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++) {
+		if (y < Context->YOfs) continue;
+        for (int x = Frame->left; x <= Frame->right; x++) {
+			if (x < Context->XOfs) continue;
+            // 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;
+        }
+    }
+    return 1;
+}
+
+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++) {
+        for (int x = Frame->left; x <= Frame->right; x++) {
+            // 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, Context->XOfs + x, Context->YOfs + y, Value);
+        }
+    }
+    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) {
+    JDEC Decoder;
+    JpegCtx Context;
+	char *Scratch = calloc(SCRATCH_SIZE, 1);
+	
+    if (!Scratch) {
+        ESP_LOGE(TAG, "Cannot allocate workspace");
+        return NULL;
+    }
+
+	Context.OutData = NULL;
+    Context.InData = Source;
+    Context.InPos = 0;
+	        
+    //Prepare and decode the jpeg.
+    int Res = jd_prepare(&Decoder, InHandler, Scratch, SCRATCH_SIZE, (void*) &Context);
+	if (Width) *Width = Decoder.width;
+	if (Height) *Height = Decoder.height;
+	Decoder.scale = Scale;
+
+    if (Res == JDR_OK && !SizeOnly) {
+		// ready to decode		
+		Context.OutData = malloc(Decoder.width * Decoder.height * sizeof(uint16_t));
+		uint8_t N = 0, iScale =  1.0 / Scale;
+		while (iScale >>= 1) N++;
+		if (Context.OutData) {
+			Context.Width = Decoder.width / (1 << N);
+			Context.Height = Decoder.height / (1 << N);
+			if (Width) *Width = Context.Width;
+			if (Height) *Height = Context.Height;
+			Res = jd_decomp(&Decoder, OutHandler, N > 3 ? 3 : N);
+			if (Res != JDR_OK) {
+				ESP_LOGE(TAG, "Image decoder: jd_decode failed (%d)", Res);
+			}	
+		} else {
+			ESP_LOGE(TAG, "Can't allocate bitmap %dx%d", Decoder.width, Decoder.height);			
+		}	
+	} 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_GetJPEGSize(uint8_t *Source, int *Width, int *Height) {
+	DecodeJPEG(Source, Width, Height, 1, true);
+}	
+
+/****************************************************************************************
+ * Simply draw a RGB565 image
+ * monoschrome (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, x, y, Width, Height, RGB_Mode, Image );
+	} else {
+		int Scale = Device->Depth < 5 ? 5 - Device->Depth : 0;
+		switch(RGB_Mode) {
+		case GDS_RGB565:
+			for (int c = 0; c < Width; c++) {
+				for (int r = 0; r < Height; r++) {
+					int pixel = Image[Width*r + c];
+					pixel = ((pixel & 0x1f) * 11 + ((((pixel >> 5) & 0x3f)  * 59) >> 1) + (pixel >> 11) * 30) / 100;
+					GDS_DrawPixel( Device, c + x, r + y, pixel >> Scale);
+				}	
+			}	
+			break;
+		case GDS_RGB555:
+			for (int c = 0; c < Width; c++) {
+				for (int r = 0; r < Height; r++) {
+					int pixel = Image[Width*r + c];
+					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:
+			for (int c = 0; c < Width; c++) {
+				for (int r = 0; r < Height; r++) {
+					int pixel = Image[Width*r + c];
+					pixel = (pixel & 0x0f) * 11 + ((pixel >> 4) & 0x0f)  * 59 + (pixel >> 8) * 30;
+					GDS_DrawPixel( Device, c + x, r + y, pixel >> (Scale - 1));
+				}	
+			}	
+			break;				
+		}
+	}	 
+}	
+
+//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;
+	char *Scratch = calloc(SCRATCH_SIZE, 1);
+	
+    if (!Scratch) {
+        ESP_LOGE(TAG, "Cannot allocate workspace");
+        return NULL;
+    }
+
+    // Populate fields of the JpegCtx struct.
+    Context.InData = Source;
+    Context.InPos = 0;
+	Context.XOfs = x;
+	Context.YOfs = y;
+	Context.Device = Device;
+	Context.Depth = Device->Depth;
+        
+    //Prepare and decode the jpeg.
+    int Res = jd_prepare(&Decoder, InHandler, Scratch, SCRATCH_SIZE, (void*) &Context);
+	Context.Width = Decoder.width;
+	Context.Height = Decoder.height;
+	
+    if (Res == JDR_OK) {
+		uint8_t N = 0;
+		
+		// do we need to fit the image
+		if (Fit & GDS_IMAGE_FIT) {
+			float XRatio = (Device->Width - x) / (float) Decoder.width, YRatio = (Device->Height - y) / (float) Decoder.height;
+			uint8_t Ratio = XRatio < YRatio ? ceil(1/XRatio) : ceil(1/YRatio);
+			Ratio--; Ratio |= Ratio >> 1; Ratio |= Ratio >> 2; Ratio++;
+			while (Ratio >>= 1) N++;
+			Context.Width /= 1 << N;
+			Context.Height /= 1 << N;
+		} 
+		
+		// then place it
+		if (Fit & GDS_IMAGE_CENTER_X) Context.XOfs = x + ((Device->Width - x) - Context.Width) / 2;
+		if (Fit & GDS_IMAGE_CENTER_Y) Context.YOfs = y + ((Device->Height - y) - Context.Height) / 2;
+		
+		// do decompress & draw
+		Res = jd_decomp(&Decoder, OutHandlerDirect, N > 3 ? 3 : N);
+		if (Res == JDR_OK) {
+			Ret = true;
+		} else {	
+			ESP_LOGE(TAG, "Image decoder: jd_decode failed (%d)", Res);
+		}	
+	} else {	
+        ESP_LOGE(TAG, "Image decoder: jd_prepare failed (%d)", Res);
+    }    
+      
+	// free scratch area
+    if (Scratch) free(Scratch);
+	return Ret;
+}
+

+ 23 - 0
components/display/core/gds_image.h

@@ -0,0 +1,23 @@
+#pragma once
+
+#include <stdint.h>
+#include "esp_err.h"
+
+// no progressive JPEG handling
+
+struct GDS_Device;
+
+enum { GDS_RGB565, GDS_RGB555, GDS_RGB444 };
+
+#define GDS_IMAGE_TOP		0x00
+#define GDS_IMAGE_CENTER_X	0x01
+#define GDS_IMAGE_CENTER_Y	0x02
+#define GDS_IMAGE_CENTER	(GDS_IMAGE_CENTER_X | GDS_IMAGE_CENTER_Y)
+#define GDS_IMAGE_FIT		0x10
+
+// 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_GetJPEGSize(uint8_t *Source, int *Width, int *Height);
+void 		GDS_DrawRGB16( struct GDS_Device* Device, uint16_t *Image, int x, int y, int Width, int Height, int RGB_Mode );
+// set DisplayWidth and DisplayHeight to non-zero if you want autoscale to closest factor ^2 from 0..3
+bool 		GDS_DrawJPEG( struct GDS_Device* Device, uint8_t *Source, int x, int y, int Fit);

+ 6 - 3
components/display/core/ifaces/default_if_i2c.c

@@ -17,6 +17,7 @@
 #include "gds_default_if.h"
 
 static int I2CPortNumber;
+static int I2CWait;
 
 static const int GDS_I2C_COMMAND_MODE = 0x80;
 static const int GDS_I2C_DATA_MODE = 0x40;
@@ -31,9 +32,11 @@ static bool I2CDefaultWriteData( struct GDS_Device* Device, const uint8_t* Data,
  * 
  * Returns true on successful init of the i2c bus.
  */
-bool GDS_I2CInit( int PortNumber, int SDA, int SCL ) {
+bool GDS_I2CInit( int PortNumber, int SDA, int SCL, int Speed ) {
 	I2CPortNumber = PortNumber;
 	
+	I2CWait = pdMS_TO_TICKS( Speed ? Speed / 4000 : 100 );
+	
 	if (SDA != -1 && SCL != -1) {
 		i2c_config_t Config = { 0 };
 
@@ -42,7 +45,7 @@ bool GDS_I2CInit( int PortNumber, int SDA, int SCL ) {
 		Config.sda_pullup_en = GPIO_PULLUP_ENABLE;
 		Config.scl_io_num = SCL;
 		Config.scl_pullup_en = GPIO_PULLUP_ENABLE;
-		Config.master.clk_speed = 250000;
+		Config.master.clk_speed = Speed ? Speed : 400000;
 
 		ESP_ERROR_CHECK_NONFATAL( i2c_param_config( I2CPortNumber, &Config ), return false );
 		ESP_ERROR_CHECK_NONFATAL( i2c_driver_install( I2CPortNumber, Config.mode, 0, 0, 0 ), return false );
@@ -98,7 +101,7 @@ static bool I2CDefaultWriteBytes( int Address, bool IsCommand, const uint8_t* Da
         ESP_ERROR_CHECK_NONFATAL( i2c_master_write( CommandHandle, ( uint8_t* ) Data, DataLength, true ), goto error );
         ESP_ERROR_CHECK_NONFATAL( i2c_master_stop( CommandHandle ), goto error );
 
-        ESP_ERROR_CHECK_NONFATAL( i2c_master_cmd_begin( I2CPortNumber, CommandHandle, pdMS_TO_TICKS( 1000 ) ), goto error );
+        ESP_ERROR_CHECK_NONFATAL( i2c_master_cmd_begin( I2CPortNumber, CommandHandle, I2CWait ), goto error );
         i2c_cmd_link_delete( CommandHandle );
     }
 

+ 2 - 1
components/display/display.c

@@ -89,11 +89,12 @@ void display_init(char *welcome) {
 		// Detect driver interface
 		if (strstr(config, "I2C") && i2c_system_port != -1) {
 			int address = 0x3C;
+			int speed = 0;
 				
 			if ((p = strcasestr(config, "address")) != NULL) address = atoi(strchr(p, '=') + 1);
 		
 			init = true;
-			GDS_I2CInit( i2c_system_port, -1, -1 ) ;
+			GDS_I2CInit( i2c_system_port, -1, -1, i2c_system_speed ) ;
 			GDS_I2CAttachDevice( display, width, height, address, -1 );
 		
 			ESP_LOGI(TAG, "Display is I2C on port %u", address);

+ 3 - 1
components/services/accessors.c

@@ -45,9 +45,11 @@ const i2c_config_t * config_i2c_get(int * i2c_port) {
 		.sda_pullup_en = GPIO_PULLUP_ENABLE,
 		.scl_io_num = -1,
 		.scl_pullup_en = GPIO_PULLUP_ENABLE,
-		.master.clk_speed = 400000,
+		.master.clk_speed = 0,
 	};
 
+	i2c.master.clk_speed = i2c_system_speed;
+	
 	nvs_item = config_alloc_get(NVS_TYPE_STR, "i2c_config");
 	if (nvs_item) {
 		if ((p = strcasestr(nvs_item, "scl")) != NULL) i2c.scl_io_num = atoi(strchr(p, '=') + 1);

+ 1 - 0
components/services/globdefs.h

@@ -24,6 +24,7 @@
 #define SPI_SYSTEM_HOST	SPI2_HOST
 
 extern int i2c_system_port;
+extern int i2c_system_speed;
 extern int spi_system_host;
 extern int spi_system_dc_gpio;
 extern bool gpio36_39_used;

+ 1 - 0
components/services/services.c

@@ -22,6 +22,7 @@ extern void monitor_svc_init(void);
 extern void led_svc_init(void);
 
 int i2c_system_port = I2C_SYSTEM_PORT;
+int i2c_system_speed = 400000;
 int spi_system_host = SPI_SYSTEM_HOST;
 int spi_system_dc_gpio = -1;