From 1ae8f80e536d8cacefb93df26b7d8be3a84cc0ea Mon Sep 17 00:00:00 2001 From: philippe44 Date: Wed, 26 Feb 2020 23:27:28 -0800 Subject: [PATCH 1/4] 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; From c00f513be7f87db33dfe7cf8f1a5ca61062390c7 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Wed, 26 Feb 2020 23:48:18 -0800 Subject: [PATCH 2/4] forgot a few backports - release --- components/display/core/gds_draw.c | 44 +-------------------------- components/display/core/gds_draw.h | 5 --- components/display/core/gds_image.c | 9 ++++-- components/display/core/gds_private.h | 2 +- 4 files changed, 8 insertions(+), 52 deletions(-) diff --git a/components/display/core/gds_draw.c b/components/display/core/gds_draw.c index f22d1d00..60467a33 100644 --- a/components/display/core/gds_draw.c +++ b/components/display/core/gds_draw.c @@ -268,46 +268,4 @@ void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int } Device->Dirty = 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, int x, int y, int Width, int Height, int RGB_Mode, uint16_t **Image ) { - 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[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[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[r][c]; - pixel = (pixel & 0x0f) * 11 + ((pixel >> 4) & 0x0f) * 59 + (pixel >> 8) * 30; - GDS_DrawPixel( Device, c + x, r + y, pixel >> (Scale - 1)); - } - } - break; - } - } -} +} \ No newline at end of file diff --git a/components/display/core/gds_draw.h b/components/display/core/gds_draw.h index 9daf654a..e98f958c 100644 --- a/components/display/core/gds_draw.h +++ b/components/display/core/gds_draw.h @@ -9,10 +9,6 @@ extern "C" { #endif -struct GDS_Device; - -enum { GDS_RGB565, GDS_RGB555, GDS_RGB444 }; - #ifndef _GDS_PRIVATE_H_ void IRAM_ATTR GDS_DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color ); void IRAM_ATTR GDS_DrawPixel( struct GDS_Device* Device, int X, int Y, int Color ); @@ -21,7 +17,6 @@ void GDS_DrawHLine( struct GDS_Device* Device, int x, int y, int Width, int Colo void GDS_DrawVLine( struct GDS_Device* Device, int x, int y, int Height, int Color ); void GDS_DrawLine( struct GDS_Device* Device, int x0, int y0, int x1, int y1, int Color ); void GDS_DrawBox( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color, bool Fill ); -void GDS_DrawRGB16( struct GDS_Device* Device, int x, int y, int Width, int Height, int RGB_Mode, uint16_t **Image ); // draw a bitmap with source 1-bit depth organized in column and col0 = bit7 of byte 0 void GDS_DrawBitmapCBR( struct GDS_Device* Device, uint8_t *Data, int Width, int Height, int Color); diff --git a/components/display/core/gds_image.c b/components/display/core/gds_image.c index 0500ac1a..e7d4dac7 100644 --- a/components/display/core/gds_image.c +++ b/components/display/core/gds_image.c @@ -90,10 +90,13 @@ static uint16_t* DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scal Decoder.scale = Scale; if (Res == JDR_OK && !SizeOnly) { - // ready to decode + // find the scaling factor Context.OutData = malloc(Decoder.width * Decoder.height * sizeof(uint16_t)); - uint8_t N = 0, iScale = 1.0 / Scale; - while (iScale >>= 1) N++; + uint8_t N = 0, ScaleInt = ceil(1.0 / Scale); + ScaleInt--; ScaleInt |= ScaleInt >> 1; ScaleInt |= ScaleInt >> 2; ScaleInt++; + while (ScaleInt >>= 1) N++; + + // ready to decode if (Context.OutData) { Context.Width = Decoder.width / (1 << N); Context.Height = Decoder.height / (1 << N); diff --git a/components/display/core/gds_private.h b/components/display/core/gds_private.h index bc883009..60893be5 100644 --- a/components/display/core/gds_private.h +++ b/components/display/core/gds_private.h @@ -105,7 +105,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, int x, int y, int Width, int Height, int RGB_Mode, uint16_t **Image ); + void (*DrawRGB16)( struct GDS_Device* Device, int x, int y, int Width, int Height, int RGB_Mode, uint16_t *Image ); void (*ClearWindow)( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color ); // interface-specific methods From bbe602284414df211d250079cd8fef9890e5bef9 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Wed, 26 Feb 2020 23:53:42 -0800 Subject: [PATCH 3/4] add AirPlay/BT pause length option - release --- components/display/display.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/display/display.c b/components/display/display.c index 689aa8e1..a1cb0e4d 100644 --- a/components/display/display.c +++ b/components/display/display.c @@ -89,7 +89,6 @@ 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); @@ -275,8 +274,9 @@ void displayer_metadata(char *artist, char *album, char *title) { strncpy(string, title ? title : "", SCROLLABLE_SIZE); } - // get optional scroll speed + // get optional scroll speed & pause if ((p = strcasestr(displayer.metadata_config, "speed")) != NULL) sscanf(p, "%*[^=]=%d", &displayer.speed); + if ((p = strcasestr(displayer.metadata_config, "pause")) != NULL) sscanf(p, "%*[^=]=%d", &displayer.pause); displayer.offset = 0; utf8_decode(displayer.string); From 5fb4b14e87562219a30dbd40ac6082417cd2dae0 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Wed, 26 Feb 2020 23:56:20 -0800 Subject: [PATCH 4/4] Update README.md --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index eed79d09..bb701d3b 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Use the `squeezelite-esp32-I2S-4MFlash-sdkconfig.defaults` configuration file. To access NVS, in the webUI, go to credits and select "shows nvs editor". Go into the NVS editor tab to change NFS parameters. In syntax description below \<\> means a value while \[\] describe optional parameters. ### I2C -The NVS parameter "i2c_config" set the i2c's gpio used for generic purpose (e.g. display). Leave it blank to disable I2C usage. Note that on SqueezeAMP, port must be 1. Default speed is 400000. Syntax is +The NVS parameter "i2c_config" set the i2c's gpio used for generic purpose (e.g. display). Leave it blank to disable I2C usage. Note that on SqueezeAMP, port must be 1. Default speed is 400000 but some display can do up to 800000 or more. Syntax is ``` sda=,scl=[,port=0|1][,speed=] ``` @@ -62,16 +62,18 @@ I2C,width=,height=[address=][,HFlip][,VFlip][driver SPI,width=,height=,cs=[,speed=][,HFlip][,VFlip][driver=SSD1306|SSD1326|SH1106] ``` - VFlip and HFlip are optional can be used to change display orientation -- Default speed is 8000000 (8MHz) +- Default speed is 8000000 (8MHz) but SPI can work up to 26MHz or even 40MHz Currently 128x32/64 I2C display like [this](https://www.buydisplay.com/i2c-blue-0-91-inch-oled-display-module-128x32-arduino-raspberry-pi) and [this](https://www.waveshare.com/wiki/1.3inch_OLED_HAT) are supported The NVS parameter "metadata_config" sets how metadata is displayed for AirPlay and Bluetooth. Syntax is ``` -[format=][,speed=] +[format=][,speed=][,pause=] ``` - 'speed' is the scrolling speed in ms (default is 33ms) +- 'pause' is the pause time between scrolls in ms (default is 3600ms) + - 'format' can contain free text and any of the 3 keywords %artist%, %album%, %title%. Using that format string, the keywords are replaced by their value to build the string to be displayed. Note that the plain text following a keyword that happens to be empty during playback of a track will be removed. For example, if you have set format=%artist% - %title% and there is no artist in the metadata then only will be displayed not " - <title>". ### Set GPIO