diff --git a/libs/display/EInkDisplay/include/EInkDisplay.h b/libs/display/EInkDisplay/include/EInkDisplay.h index 16592af..10d832f 100644 --- a/libs/display/EInkDisplay/include/EInkDisplay.h +++ b/libs/display/EInkDisplay/include/EInkDisplay.h @@ -43,6 +43,8 @@ class EInkDisplay { #endif void displayBuffer(RefreshMode mode = FAST_REFRESH); + // EXPERIMENTAL: Windowed update - display only a rectangular region + void displayWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h); void displayGrayBuffer(bool turnOffScreen = false); void refreshDisplay(RefreshMode mode = FAST_REFRESH, bool turnOffScreen = false); diff --git a/libs/display/EInkDisplay/src/EInkDisplay.cpp b/libs/display/EInkDisplay/src/EInkDisplay.cpp index cc3a0b8..1f7e0ac 100644 --- a/libs/display/EInkDisplay/src/EInkDisplay.cpp +++ b/libs/display/EInkDisplay/src/EInkDisplay.cpp @@ -452,6 +452,82 @@ void EInkDisplay::displayBuffer(RefreshMode mode) { #endif } +// EXPERIMENTAL: Windowed update support +// Displays only a rectangular region of the frame buffer, preserving the rest of the screen. +// Requirements: x and w must be byte-aligned (multiples of 8 pixels) +void EInkDisplay::displayWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h) { + Serial.printf("[%lu] Displaying window at (%d,%d) size (%dx%d)\n", millis(), x, y, w, h); + + // Validate bounds + if (x + w > DISPLAY_WIDTH || y + h > DISPLAY_HEIGHT) { + Serial.printf("[%lu] ERROR: Window bounds exceed display dimensions!\n", millis()); + return; + } + + // Validate byte alignment + if (x % 8 != 0 || w % 8 != 0) { + Serial.printf("[%lu] ERROR: Window x and width must be byte-aligned (multiples of 8)!\n", millis()); + return; + } + + if (!frameBuffer) { + Serial.printf("[%lu] ERROR: Frame buffer not allocated!\n", millis()); + return; + } + + // displayWindow is not supported while the rest of the screen has grayscale content, revert it + if (inGrayscaleMode) { + inGrayscaleMode = false; + grayscaleRevert(); + } + + // Calculate window buffer size + const uint16_t windowWidthBytes = w / 8; + const uint32_t windowBufferSize = windowWidthBytes * h; + + Serial.printf("[%lu] Window buffer size: %lu bytes (%d x %d pixels)\n", millis(), windowBufferSize, w, h); + + // Allocate temporary buffer on stack + std::vector windowBuffer(windowBufferSize); + + // Extract window region from frame buffer + for (uint16_t row = 0; row < h; row++) { + const uint16_t srcY = y + row; + const uint16_t srcOffset = srcY * DISPLAY_WIDTH_BYTES + (x / 8); + const uint16_t dstOffset = row * windowWidthBytes; + memcpy(&windowBuffer[dstOffset], &frameBuffer[srcOffset], windowWidthBytes); + } + + // Configure RAM area for window + setRamArea(x, y, w, h); + + // Write to BW RAM (current frame) + writeRamBuffer(CMD_WRITE_RAM_BW, windowBuffer.data(), windowBufferSize); + +#ifndef EINK_DISPLAY_SINGLE_BUFFER_MODE + // Dual buffer: Extract window from frameBufferActive (previous frame) + std::vector previousWindowBuffer(windowBufferSize); + for (uint16_t row = 0; row < h; row++) { + const uint16_t srcY = y + row; + const uint16_t srcOffset = srcY * DISPLAY_WIDTH_BYTES + (x / 8); + const uint16_t dstOffset = row * windowWidthBytes; + memcpy(&previousWindowBuffer[dstOffset], &frameBufferActive[srcOffset], windowWidthBytes); + } + writeRamBuffer(CMD_WRITE_RAM_RED, previousWindowBuffer.data(), windowBufferSize); +#endif + + // Perform fast refresh + refreshDisplay(FAST_REFRESH); + +#ifdef EINK_DISPLAY_SINGLE_BUFFER_MODE + // Post-refresh: Sync RED RAM with current window (for next fast refresh) + setRamArea(x, y, w, h); + writeRamBuffer(CMD_WRITE_RAM_RED, windowBuffer.data(), windowBufferSize); +#endif + + Serial.printf("[%lu] Window display complete\n", millis()); +} + void EInkDisplay::displayGrayBuffer(const bool turnOffScreen) { drawGrayscale = false; inGrayscaleMode = true;