[EInkDisplay] Add basic implementation for displayWindow (#9)

Adds a basic implementation of `displayWindow` which displays a subset
of the current frame buffer to the screen. It is significantly less
performant than a full screen reload, so it shouldn't be used unless the
underlying content is hard to get/rerender.

I've also spotted a little bit of ghosting, but it's not fully clear as
to why that is. My only thought it the LUT and grayscale mode isn't
fully disengaged or the RED RAM buffer isn't correctly setup (but afaict
it should be populated correctly).

Going to merge this to continue testing this feature out in the wild,
but consider it experimental.
This commit is contained in:
Dave Allie 2025-12-17 00:01:47 +11:00 committed by GitHub
parent fba62200e2
commit 98a5aa1f89
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 78 additions and 0 deletions

View File

@ -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);

View File

@ -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<uint8_t> 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<uint8_t> 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;