diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index 993b2cb7..57a8a937 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -1094,6 +1094,89 @@ int GfxRenderer::getScreenHeight() const { return panelWidth; } +// Translate a logical rect through rotateCoordinates and take the bounding +// box of its four corners on the physical panel. Output coords are inclusive +// and clamped. Returns false if the rect ends up fully off-panel. +static bool logicalRectToPhysicalBounds(GfxRenderer::Orientation orientation, int lx, int ly, int lw, int lh, + uint16_t panelWidth, uint16_t panelHeight, int* outX0, int* outY0, int* outX1, + int* outY1) { + if (lw <= 0 || lh <= 0) return false; + int minX = INT32_MAX; + int minY = INT32_MAX; + int maxX = INT32_MIN; + int maxY = INT32_MIN; + const int corners[4][2] = {{lx, ly}, {lx + lw - 1, ly}, {lx, ly + lh - 1}, {lx + lw - 1, ly + lh - 1}}; + for (auto& c : corners) { + int phyX; + int phyY; + rotateCoordinates(orientation, c[0], c[1], &phyX, &phyY, panelWidth, panelHeight); + if (phyX < minX) minX = phyX; + if (phyY < minY) minY = phyY; + if (phyX > maxX) maxX = phyX; + if (phyY > maxY) maxY = phyY; + } + if (minX < 0) minX = 0; + if (minY < 0) minY = 0; + if (maxX >= panelWidth) maxX = panelWidth - 1; + if (maxY >= panelHeight) maxY = panelHeight - 1; + if (minX > maxX || minY > maxY) return false; + *outX0 = minX; + *outY0 = minY; + *outX1 = maxX; + *outY1 = maxY; + return true; +} + +size_t GfxRenderer::getRegionByteSize(int lx, int ly, int lw, int lh) const { + int x0, y0, x1, y1; + if (!logicalRectToPhysicalBounds(orientation, lx, ly, lw, lh, panelWidth, panelHeight, &x0, &y0, &x1, &y1)) { + return 0; + } + // x bounds are in pixels; widen to byte boundaries on either side so per-row + // memcpy stays byte-aligned even when the logical rect doesn't. + const int byteX0 = x0 / 8; + const int byteX1 = x1 / 8; + const int bytesPerRow = byteX1 - byteX0 + 1; + const int rowCount = y1 - y0 + 1; + return static_cast(bytesPerRow) * static_cast(rowCount); +} + +bool GfxRenderer::copyRegionToBuffer(int lx, int ly, int lw, int lh, uint8_t* buf, size_t bufSize) const { + int x0, y0, x1, y1; + if (!logicalRectToPhysicalBounds(orientation, lx, ly, lw, lh, panelWidth, panelHeight, &x0, &y0, &x1, &y1)) { + return false; + } + const int byteX0 = x0 / 8; + const int byteX1 = x1 / 8; + const int bytesPerRow = byteX1 - byteX0 + 1; + const int rowCount = y1 - y0 + 1; + const size_t needed = static_cast(bytesPerRow) * static_cast(rowCount); + if (bufSize < needed || !frameBuffer || !buf) return false; + for (int row = 0; row < rowCount; row++) { + const uint8_t* src = frameBuffer + (y0 + row) * panelWidthBytes + byteX0; + memcpy(buf + row * bytesPerRow, src, bytesPerRow); + } + return true; +} + +bool GfxRenderer::copyBufferToRegion(int lx, int ly, int lw, int lh, const uint8_t* buf, size_t bufSize) const { + int x0, y0, x1, y1; + if (!logicalRectToPhysicalBounds(orientation, lx, ly, lw, lh, panelWidth, panelHeight, &x0, &y0, &x1, &y1)) { + return false; + } + const int byteX0 = x0 / 8; + const int byteX1 = x1 / 8; + const int bytesPerRow = byteX1 - byteX0 + 1; + const int rowCount = y1 - y0 + 1; + const size_t needed = static_cast(bytesPerRow) * static_cast(rowCount); + if (bufSize < needed || !frameBuffer || !buf) return false; + for (int row = 0; row < rowCount; row++) { + uint8_t* dst = frameBuffer + (y0 + row) * panelWidthBytes + byteX0; + memcpy(dst, buf + row * bytesPerRow, bytesPerRow); + } + return true; +} + int GfxRenderer::getSpaceWidth(const int fontId, const EpdFontFamily::Style style) const { // Advance table fast-path for SD card fonts during layout auto sdIt = sdCardFonts_.find(fontId); diff --git a/lib/GfxRenderer/GfxRenderer.h b/lib/GfxRenderer/GfxRenderer.h index 21a9cad9..a97b2b9f 100644 --- a/lib/GfxRenderer/GfxRenderer.h +++ b/lib/GfxRenderer/GfxRenderer.h @@ -185,4 +185,16 @@ class GfxRenderer { uint16_t getDisplayWidth() const { return panelWidth; } uint16_t getDisplayHeight() const { return panelHeight; } uint16_t getDisplayWidthBytes() const { return panelWidthBytes; } + + // Region cache: take a logical (orientation-aware) rect, hit the framebuffer + // bytes that the rect can have touched, and pump them in or out of a caller- + // supplied buffer. Used by HomeActivity to snapshot just the cover tile + // (~16 KB in Portrait) instead of cloning the entire 48 KB framebuffer. + // + // getRegionByteSize: required buffer length for the rect at current orientation. + // copyRegionToBuffer / copyBufferToRegion: false if `bufSize` is smaller than that. + size_t getRegionByteSize(int logicalX, int logicalY, int logicalW, int logicalH) const; + bool copyRegionToBuffer(int logicalX, int logicalY, int logicalW, int logicalH, uint8_t* buf, size_t bufSize) const; + bool copyBufferToRegion(int logicalX, int logicalY, int logicalW, int logicalH, const uint8_t* buf, + size_t bufSize) const; }; diff --git a/src/activities/home/HomeActivity.cpp b/src/activities/home/HomeActivity.cpp index 76457491..7f643994 100644 --- a/src/activities/home/HomeActivity.cpp +++ b/src/activities/home/HomeActivity.cpp @@ -130,37 +130,30 @@ void HomeActivity::onExit() { } bool HomeActivity::storeCoverBuffer() { - uint8_t* frameBuffer = renderer.getFrameBuffer(); - if (!frameBuffer) { - return false; - } - - // Free any existing buffer first + // render() must have already set the cover rect; without it we'd be back to + // cloning the whole framebuffer. + if (coverRectW <= 0 || coverRectH <= 0) return false; freeCoverBuffer(); - - const size_t bufferSize = renderer.getBufferSize(); - coverBuffer = static_cast(malloc(bufferSize)); + const size_t needed = renderer.getRegionByteSize(coverRectX, coverRectY, coverRectW, coverRectH); + if (needed == 0) return false; + coverBuffer = static_cast(malloc(needed)); if (!coverBuffer) { + LOG_ERR("HOME", "OOM: cover buffer (%u bytes)", (unsigned)needed); + return false; + } + coverBufferSize = needed; + if (!renderer.copyRegionToBuffer(coverRectX, coverRectY, coverRectW, coverRectH, coverBuffer, coverBufferSize)) { + free(coverBuffer); + coverBuffer = nullptr; + coverBufferSize = 0; return false; } - - memcpy(coverBuffer, frameBuffer, bufferSize); return true; } bool HomeActivity::restoreCoverBuffer() { - if (!coverBuffer) { - return false; - } - - uint8_t* frameBuffer = renderer.getFrameBuffer(); - if (!frameBuffer) { - return false; - } - - const size_t bufferSize = renderer.getBufferSize(); - memcpy(frameBuffer, coverBuffer, bufferSize); - return true; + if (!coverBuffer || coverRectW <= 0 || coverRectH <= 0) return false; + return renderer.copyBufferToRegion(coverRectX, coverRectY, coverRectW, coverRectH, coverBuffer, coverBufferSize); } void HomeActivity::freeCoverBuffer() { @@ -168,6 +161,7 @@ void HomeActivity::freeCoverBuffer() { free(coverBuffer); coverBuffer = nullptr; } + coverBufferSize = 0; coverBufferStored = false; } @@ -221,6 +215,14 @@ void HomeActivity::render(RenderLock&&) { GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.homeTopPadding}, metrics.homeContinueReadingInMenu && !recentBooks.empty() ? recentBooks[0].title.c_str() : nullptr); + // Record the tile rect so storeCoverBuffer (called from the theme) knows + // which sub-region of the framebuffer to snapshot. ~16 KB in Portrait + // instead of the 48 KB full framebuffer the previous bind captured. + coverRectX = 0; + coverRectY = metrics.homeTopPadding; + coverRectW = pageWidth; + coverRectH = metrics.homeCoverTileHeight; + GUI.drawRecentBookCover(renderer, Rect{0, metrics.homeTopPadding, pageWidth, metrics.homeCoverTileHeight}, recentBooks, selectorIndex, coverRendered, coverBufferStored, bufferRestored, std::bind(&HomeActivity::storeCoverBuffer, this)); diff --git a/src/activities/home/HomeActivity.h b/src/activities/home/HomeActivity.h index e3bfd4db..b1bd494d 100644 --- a/src/activities/home/HomeActivity.h +++ b/src/activities/home/HomeActivity.h @@ -19,6 +19,14 @@ class HomeActivity final : public Activity { bool coverRendered = false; // Track if cover has been rendered once bool coverBufferStored = false; // Track if cover buffer is stored uint8_t* coverBuffer = nullptr; // HomeActivity's own buffer for cover image + size_t coverBufferSize = 0; // Bytes allocated to coverBuffer + // Logical rect last passed to drawRecentBookCover. The cover snapshot only + // needs to cover this region, not the entire framebuffer, so we cache the + // tile instead of all 48 KB. Set in render() before the call. + int coverRectX = 0; + int coverRectY = 0; + int coverRectW = 0; + int coverRectH = 0; std::vector recentBooks; void onSelectBook(const std::string& path); void onFileBrowserOpen();