diff --git a/components/display/SH1106.c b/components/display/SH1106.c index f6c1dc2f..f4b93dfe 100644 --- a/components/display/SH1106.c +++ b/components/display/SH1106.c @@ -21,6 +21,10 @@ static char TAG[] = "SH1106"; +struct SH1106_Private { + uint8_t *Shadowbuffer; +}; + // Functions are not declared to minimize # of lines static void SetColumnAddress( struct GDS_Device* Device, uint8_t Start, uint8_t End ) { @@ -36,9 +40,10 @@ static void SetPageAddress( struct GDS_Device* Device, uint8_t Start, uint8_t En static void Update( struct GDS_Device* Device ) { #ifdef SHADOW_BUFFER + struct SH1106_Private *Private = (struct SH1106_Private*) Device->Private; // not sure the compiler does not have to redo all calculation in for loops, so local it is int width = Device->Width, rows = Device->Height / 8; - uint8_t *optr = Device->Shadowbuffer, *iptr = Device->Framebuffer; + uint8_t *optr = Private->Shadowbuffer, *iptr = Device->Framebuffer; // by row, find first and last columns that have been updated for (int r = 0; r < rows; r++) { @@ -55,7 +60,7 @@ static void Update( struct GDS_Device* Device ) { if (first--) { SetColumnAddress( Device, first, last ); SetPageAddress( Device, r, r); - Device->WriteData( Device, Device->Shadowbuffer + r*width + first, last - first + 1); + Device->WriteData( Device, Private->Shadowbuffer + r*width + first, last - first + 1); } } #else @@ -83,15 +88,16 @@ static bool Init( struct GDS_Device* Device ) { // benchmarks showed little gain to have SPI memory already in IRAL vs letting driver copy #ifdef SHADOW_BUFFER + struct SH1106_Private *Private = (struct SH1106_Private*) Device->Private; Device->Framebuffer = calloc( 1, Device->FramebufferSize ); NullCheck( Device->Framebuffer, return false ); #ifdef USE_IRAM - if (Device->IF == IF_SPI) Device->Shadowbuffer = heap_caps_malloc( Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA ); + if (Device->IF == IF_SPI) Private->Shadowbuffer = heap_caps_malloc( Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA ); else #endif - Device->Shadowbuffer = malloc( Device->FramebufferSize ); - NullCheck( Device->Shadowbuffer, return false ); - memset(Device->Shadowbuffer, 0xFF, Device->FramebufferSize); + Private->Shadowbuffer = malloc( Device->FramebufferSize ); + NullCheck( Private->Shadowbuffer, return false ); + memset(Private->Shadowbuffer, 0xFF, Device->FramebufferSize); #else // not SHADOW_BUFFER #ifdef USE_IRAM // benchmarks showed little gain to have SPI memory already in IRAL vs letting driver copy diff --git a/components/display/SSD1306.c b/components/display/SSD1306.c index 1e27fcb7..7cea4505 100644 --- a/components/display/SSD1306.c +++ b/components/display/SSD1306.c @@ -21,6 +21,10 @@ static char TAG[] = "SSD1306"; +struct SSD1306_Private { + uint8_t *Shadowbuffer; +}; + // Functions are not deckared to minimize # of lines static void SetColumnAddress( struct GDS_Device* Device, uint8_t Start, uint8_t End ) { @@ -36,9 +40,10 @@ static void SetPageAddress( struct GDS_Device* Device, uint8_t Start, uint8_t En static void Update( struct GDS_Device* Device ) { #ifdef SHADOW_BUFFER + struct SSD1306_Private *Private = (struct SSD1306_Private*) Device->Private; // not sure the compiler does not have to redo all calculation in for loops, so local it is int width = Device->Width, rows = Device->Height / 8; - uint8_t *optr = Device->Shadowbuffer, *iptr = Device->Framebuffer; + uint8_t *optr = Private->Shadowbuffer, *iptr = Device->Framebuffer; int CurrentRow = -1, FirstCol = -1, LastCol = -1; // by row, find first and last columns that have been updated @@ -54,7 +59,6 @@ static void Update( struct GDS_Device* Device ) { // now update the display by "byte rows" if (first--) { - // only set column when useful, saves a fair bit of CPU if (first > FirstCol && first <= FirstCol + 4 && last < LastCol && last >= LastCol - 4) { first = FirstCol; @@ -70,7 +74,7 @@ static void Update( struct GDS_Device* Device ) { CurrentRow = r + 1; // actual write - Device->WriteData( Device, Device->Shadowbuffer + r*width + first, last - first + 1); + Device->WriteData( Device, Private->Shadowbuffer + r*width + first, last - first + 1); } } #else @@ -94,17 +98,18 @@ static void SetContrast( struct GDS_Device* Device, uint8_t Contrast ) { static bool Init( struct GDS_Device* Device ) { Device->FramebufferSize = ( Device->Width * Device->Height ) / 8; -// benchmarks showed little gain to have SPI memory already in IRAL vs letting driver copy + // benchmarks showed little gain to have SPI memory already in IRAL vs letting driver copy #ifdef SHADOW_BUFFER + struct SSD1306_Private *Private = (struct SSD1306_Private*) Device->Private; Device->Framebuffer = calloc( 1, Device->FramebufferSize ); NullCheck( Device->Framebuffer, return false ); #ifdef USE_IRAM - if (Device->IF == IF_SPI) Device->Shadowbuffer = heap_caps_malloc( Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA ); + if (Device->IF == IF_SPI) Private->Shadowbuffer = heap_caps_malloc( Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA ); else #endif - Device->Shadowbuffer = malloc( Device->FramebufferSize ); - NullCheck( Device->Shadowbuffer, return false ); - memset(Device->Shadowbuffer, 0xFF, Device->FramebufferSize); + Private->Shadowbuffer = malloc( Device->FramebufferSize ); + NullCheck( Private->Shadowbuffer, return false ); + memset(Private->Shadowbuffer, 0xFF, Device->FramebufferSize); #else // not SHADOW_BUFFER #ifdef USE_IRAM // benchmarks showed little gain to have SPI memory already in IRAL vs letting driver copy diff --git a/components/display/SSD132x.c b/components/display/SSD132x.c index cdb5779f..cdbadc2b 100644 --- a/components/display/SSD132x.c +++ b/components/display/SSD132x.c @@ -27,7 +27,7 @@ static char TAG[] = "SSD132x"; enum { SSD1326, SSD1327 }; struct SSD132x_Private { - uint8_t *iRAM; + uint8_t *iRAM, *Shadowbuffer; uint8_t ReMap, PageSize; uint8_t Model; }; @@ -73,7 +73,7 @@ static void Update4( struct GDS_Device* Device ) { SetColumnAddress( Device, 0, Device->Width / 2 - 1); #ifdef SHADOW_BUFFER - uint16_t *optr = (uint16_t*) Device->Shadowbuffer, *iptr = (uint16_t*) Device->Framebuffer; + uint16_t *optr = (uint16_t*) Private->Shadowbuffer, *iptr = (uint16_t*) Device->Framebuffer; bool dirty = false; for (int r = 0, page = 0; r < Device->Height; r++) { @@ -92,10 +92,10 @@ static void Update4( struct GDS_Device* Device ) { SetRowAddress( Device, r - page + 1, r ); // own use of IRAM has not proven to be much better than letting SPI do its copy if (Private->iRAM) { - memcpy(Private->iRAM, Device->Shadowbuffer + (r - page + 1) * Device->Width / 2, page * Device->Width / 2 ); + memcpy(Private->iRAM, Private->Shadowbuffer + (r - page + 1) * Device->Width / 2, page * Device->Width / 2 ); Device->WriteData( Device, Private->iRAM, Device->Width * page / 2 ); } else { - Device->WriteData( Device, Device->Shadowbuffer + (r - page + 1) * Device->Width / 2, page * Device->Width / 2 ); + Device->WriteData( Device, Private->Shadowbuffer + (r - page + 1) * Device->Width / 2, page * Device->Width / 2 ); } dirty = false; } @@ -122,9 +122,10 @@ static void Update4( struct GDS_Device* Device ) { */ static void Update1( struct GDS_Device* Device ) { #ifdef SHADOW_BUFFER + struct SSD132x_Private *Private = (struct SSD132x_Private*) Device->Private; // not sure the compiler does not have to redo all calculation in for loops, so local it is int width = Device->Width / 8, rows = Device->Height; - uint8_t *optr = Device->Shadowbuffer, *iptr = Device->Framebuffer; + uint8_t *optr = Private->Shadowbuffer, *iptr = Device->Framebuffer; // by row, find first and last columns that have been updated for (int r = 0; r < rows; r++) { @@ -141,7 +142,7 @@ static void Update1( struct GDS_Device* Device ) { if (first--) { SetColumnAddress( Device, first, last ); SetRowAddress( Device, r, r); - Device->WriteData( Device, Device->Shadowbuffer + r*width + first, last - first + 1); + Device->WriteData( Device, Private->Shadowbuffer + r*width + first, last - first + 1); } } #else @@ -242,15 +243,15 @@ static bool Init( struct GDS_Device* Device ) { #ifdef USE_IRAM if (Device->IF == IF_SPI) { if (Device->Depth == 1) { - Device->Shadowbuffer = heap_caps_malloc( Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA ); + Private->Shadowbuffer = heap_caps_malloc( Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA ); } else { - Device->Shadowbuffer = malloc( Device->FramebufferSize ); + Private->Shadowbuffer = malloc( Device->FramebufferSize ); Private->iRAM = heap_caps_malloc( Private->PageSize * Device->Width / 2, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA ); } } else #endif - Device->Shadowbuffer = malloc( Device->FramebufferSize ); - memset(Device->Shadowbuffer, 0xFF, Device->FramebufferSize); + Private->Shadowbuffer = malloc( Device->FramebufferSize ); + memset(Private->Shadowbuffer, 0xFF, Device->FramebufferSize); #else // not SHADOW_BUFFER #ifdef USE_IRAM if (Device->IF == IF_SPI) { diff --git a/components/display/core/gds.h b/components/display/core/gds.h index 2c3a9e26..f1b5475c 100644 --- a/components/display/core/gds.h +++ b/components/display/core/gds.h @@ -13,13 +13,14 @@ 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, +enum { GDS_COLOR_L0 = 0, GDS_COLOR_L1 = 1, 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 }; #define GDS_COLOR_BLACK GDS_COLOR_L0 -#define GDS_COLOR_WHITE GDS_COLOR_L15 -#define GDS_COLOR_XOR 2 +#define GDS_COLOR_WHITE GDS_COLOR_L1 +#define GDS_COLOR_XOR (GDS_COLOR_MAX + 1) struct GDS_Device; struct GDS_FontDef; diff --git a/components/display/core/gds_image.c b/components/display/core/gds_image.c index e7d4dac7..a5919f74 100644 --- a/components/display/core/gds_image.c +++ b/components/display/core/gds_image.c @@ -21,6 +21,7 @@ typedef struct { struct { // DirectDraw struct GDS_Device * Device; int XOfs, YOfs; + int XMin, YMin; int Depth; }; }; @@ -38,9 +39,7 @@ static unsigned OutHandler(JDEC *Decoder, void *Bitmap, JRECT *Frame) { 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; @@ -57,12 +56,14 @@ static unsigned OutHandlerDirect(JDEC *Decoder, void *Bitmap, JRECT *Frame) { 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, Context->XOfs + x, Context->YOfs + y, Value); + GDS_DrawPixel( Context->Device, x + Context->XOfs, y + Context->YOfs, Value); } } return 1; @@ -90,11 +91,16 @@ static uint16_t* DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scal Decoder.scale = Scale; if (Res == JDR_OK && !SizeOnly) { - // find the scaling factor Context.OutData = malloc(Decoder.width * Decoder.height * sizeof(uint16_t)); + + // find the scaling factor uint8_t N = 0, ScaleInt = ceil(1.0 / Scale); ScaleInt--; ScaleInt |= ScaleInt >> 1; ScaleInt |= ScaleInt >> 2; ScaleInt++; while (ScaleInt >>= 1) N++; + if (N > 3) { + ESP_LOGW(TAG, "Image will not fit %dx%d", Decoder.width, Decoder.height); + N = 3; + } // ready to decode if (Context.OutData) { @@ -102,7 +108,7 @@ static uint16_t* DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scal 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); + Res = jd_decomp(&Decoder, OutHandler, N); if (Res != JDR_OK) { ESP_LOGE(TAG, "Image decoder: jd_decode failed (%d)", Res); } @@ -202,16 +208,23 @@ bool GDS_DrawJPEG( struct GDS_Device* Device, uint8_t *Source, int x, int y, int uint8_t Ratio = XRatio < YRatio ? ceil(1/XRatio) : ceil(1/YRatio); Ratio--; Ratio |= Ratio >> 1; Ratio |= Ratio >> 2; Ratio++; while (Ratio >>= 1) N++; + if (N > 3) { + ESP_LOGW(TAG, "Image will not fit %dx%d", Decoder.width, Decoder.height); + N = 3; + } 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; + if (Fit & GDS_IMAGE_CENTER_X) Context.XOfs = (Device->Width + x - Context.Width) / 2; + if (Fit & GDS_IMAGE_CENTER_Y) Context.YOfs = (Device->Height + y - Context.Height) / 2; + Context.XMin = x - Context.XOfs; + Context.YMin = y - Context.YOfs; + // do decompress & draw - Res = jd_decomp(&Decoder, OutHandlerDirect, N > 3 ? 3 : N); + Res = jd_decomp(&Decoder, OutHandlerDirect, N); if (Res == JDR_OK) { Ret = true; } else { diff --git a/components/display/core/gds_private.h b/components/display/core/gds_private.h index 60893be5..76fcb167 100644 --- a/components/display/core/gds_private.h +++ b/components/display/core/gds_private.h @@ -83,7 +83,7 @@ struct GDS_Device { uint16_t Height; uint8_t Depth; - uint8_t* Framebuffer, *Shadowbuffer; + uint8_t* Framebuffer; uint16_t FramebufferSize; bool Dirty; diff --git a/components/squeezelite/display.c b/components/squeezelite/display.c index f7c9b385..91030330 100644 --- a/components/squeezelite/display.c +++ b/components/squeezelite/display.c @@ -115,7 +115,7 @@ static struct scroller_s { u16_t mode; s16_t by; // scroller management & sharing between grfg and scrolling task - bool active, first; + bool active, first, overflow; int scrolled; struct { u8_t *frame; @@ -234,7 +234,7 @@ bool sb_display_init(void) { displayer.task = xTaskCreateStatic( (TaskFunction_t) displayer_task, "displayer_thread", SCROLL_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, &xTaskBuffer); // size scroller (width + current screen) - scroller.scroll.max = (displayer.width * displayer.height / 8) * (10 + 1); + scroller.scroll.max = (displayer.width * displayer.height / 8) * (15 + 1); scroller.scroll.frame = malloc(scroller.scroll.max); scroller.back.frame = malloc(displayer.width * displayer.height / 8); scroller.frame = malloc(displayer.width * displayer.height / 8); @@ -559,6 +559,7 @@ static void grfs_handler(u8_t *data, int len) { scroller.mode = htons(pkt->mode); scroller.scroll.width = htons(pkt->width); scroller.first = true; + scroller.overflow = false; // background excludes space taken by visu (if any) scroller.back.width = displayer.width - ((visu.mode && visu.row < SB_HEIGHT) ? visu.width : 0); @@ -576,13 +577,14 @@ static void grfs_handler(u8_t *data, int len) { } // copy scroll frame data (no semaphore needed) - if (scroller.scroll.size + size < scroller.scroll.max) { + if (scroller.scroll.size + size < scroller.scroll.max && !scroller.overflow) { memcpy(scroller.scroll.frame + offset, data + sizeof(struct grfs_packet), size); scroller.scroll.size = offset + size; - LOG_INFO("scroller current size %u", scroller.scroll.size); + LOG_INFO("scroller current size %u (w:%u)", scroller.scroll.size, scroller.scroll.width); } else { - LOG_INFO("scroller too larger %u/%u/%u", scroller.scroll.size + size, scroller.scroll.max, scroller.scroll.width); - scroller.scroll.width = scroller.scroll.size / (displayer.height / 8); + LOG_INFO("scroller too large %u/%u (w:%u)", scroller.scroll.size + size, scroller.scroll.max, scroller.scroll.width); + scroller.scroll.width = scroller.scroll.size / (displayer.height / 8) - scroller.back.width; + scroller.overflow = true; } } @@ -860,7 +862,7 @@ static void displayer_task(void *args) { // by default go for the long sleep, will change below if required scroller.wake = LONG_WAKE; - // do we have more to scroll (scroll.width is the last column from which we havea full zone) + // do we have more to scroll (scroll.width is the last column from which we have a full zone) if (scroller.by > 0 ? (scroller.scrolled <= scroller.scroll.width) : (scroller.scrolled >= 0)) { memcpy(scroller.frame, scroller.back.frame, scroller.back.width * displayer.height / 8); for (int i = 0; i < scroller.width * displayer.height / 8; i++) scroller.frame[i] |= scroller.scroll.frame[scroller.scrolled * displayer.height / 8 + i];