From 1ae8f80e536d8cacefb93df26b7d8be3a84cc0ea Mon Sep 17 00:00:00 2001 From: philippe44 Date: Wed, 26 Feb 2020 23:27:28 -0800 Subject: [PATCH] Complete display + add JPEG --- components/display/SSD132x.c | 20 ++ components/display/core/gds.c | 14 +- components/display/core/gds_default_if.h | 2 +- components/display/core/gds_image.c | 225 ++++++++++++++++++ components/display/core/gds_image.h | 23 ++ .../display/core/ifaces/default_if_i2c.c | 9 +- components/display/display.c | 3 +- components/services/accessors.c | 4 +- components/services/globdefs.h | 1 + components/services/services.c | 1 + 10 files changed, 290 insertions(+), 12 deletions(-) create mode 100644 components/display/core/gds_image.c create mode 100644 components/display/core/gds_image.h diff --git a/components/display/SSD132x.c b/components/display/SSD132x.c index 0508554c..cdb5779f 100644 --- a/components/display/SSD132x.c +++ b/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; } diff --git a/components/display/core/gds.c b/components/display/core/gds.c index 353dc414..7f59f79e 100644 --- a/components/display/core/gds.c +++ b/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); } } diff --git a/components/display/core/gds_default_if.h b/components/display/core/gds_default_if.h index 698cb5e1..aac616e0 100644 --- a/components/display/core/gds_default_if.h +++ b/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 ); diff --git a/components/display/core/gds_image.c b/components/display/core/gds_image.c new file mode 100644 index 00000000..0500ac1a --- /dev/null +++ b/components/display/core/gds_image.c @@ -0,0 +1,225 @@ +#include +#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; +} + diff --git a/components/display/core/gds_image.h b/components/display/core/gds_image.h new file mode 100644 index 00000000..fa87001a --- /dev/null +++ b/components/display/core/gds_image.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#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); \ No newline at end of file diff --git a/components/display/core/ifaces/default_if_i2c.c b/components/display/core/ifaces/default_if_i2c.c index a2b80c8b..c4b91b16 100644 --- a/components/display/core/ifaces/default_if_i2c.c +++ b/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 ); } diff --git a/components/display/display.c b/components/display/display.c index e5e28a70..689aa8e1 100644 --- a/components/display/display.c +++ b/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); diff --git a/components/services/accessors.c b/components/services/accessors.c index 72543de8..cd274476 100644 --- a/components/services/accessors.c +++ b/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); diff --git a/components/services/globdefs.h b/components/services/globdefs.h index 5fdfec5b..8faf2d48 100644 --- a/components/services/globdefs.h +++ b/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; diff --git a/components/services/services.c b/components/services/services.c index 36e53b60..6538f128 100644 --- a/components/services/services.c +++ b/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;