diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index 31dd431a..a0a7421a 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -1550,8 +1550,6 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con bool isScaled = false; int cropPixX = std::floor(bitmap.getWidth() * cropX / 2.0f); int cropPixY = std::floor(bitmap.getHeight() * cropY / 2.0f); - LOG_DBG("GFX", "Cropping %dx%d by %dx%d pix, is %s", bitmap.getWidth(), bitmap.getHeight(), cropPixX, cropPixY, - bitmap.isTopDown() ? "top-down" : "bottom-up"); const float croppedWidth = (1.0f - cropX) * static_cast(bitmap.getWidth()); const float croppedHeight = (1.0f - cropY) * static_cast(bitmap.getHeight()); @@ -1573,7 +1571,6 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con scale = fitScale; isScaled = true; } - LOG_DBG("GFX", "Scaling by %f - %s", scale, isScaled ? "scaled" : "not scaled"); // Calculate output row size (2 bits per pixel, packed into bytes) // IMPORTANT: Use int, not uint8_t, to avoid overflow for images > 1020 pixels wide @@ -1650,13 +1647,19 @@ void GfxRenderer::drawBitmap1Bit(const Bitmap& bitmap, const int x, const int y, const int maxHeight) const { float scale = 1.0f; bool isScaled = false; - if (maxWidth > 0 && bitmap.getWidth() > maxWidth) { - scale = static_cast(maxWidth) / static_cast(bitmap.getWidth()); - isScaled = true; + if (maxWidth > 0) { + const float s = static_cast(maxWidth) / static_cast(bitmap.getWidth()); + if (s != 1.0f) { + scale = s; + isScaled = true; + } } - if (maxHeight > 0 && bitmap.getHeight() > maxHeight) { - scale = std::min(scale, static_cast(maxHeight) / static_cast(bitmap.getHeight())); - isScaled = true; + if (maxHeight > 0) { + const float s = static_cast(maxHeight) / static_cast(bitmap.getHeight()); + if (s < scale) { + scale = s; + isScaled = (scale != 1.0f); + } } // For 1-bit BMP, output is still 2-bit packed (for consistency with readNextRow) diff --git a/lib/Xtc/Xtc.cpp b/lib/Xtc/Xtc.cpp index c01e68ed..d21fc1df 100644 --- a/lib/Xtc/Xtc.cpp +++ b/lib/Xtc/Xtc.cpp @@ -266,7 +266,15 @@ std::string Xtc::getThumbBmpPath(int width, int height) const { return cachePath + "/thumb_" + std::to_string(width) + "x" + std::to_string(height) + ".bmp"; } -bool Xtc::generateThumbBmp(int height) const { return generateThumbBmp(height * 0.6, height); } +bool Xtc::generateThumbBmp(int height) const { + const std::string destPath = getThumbBmpPath(height); + if (Storage.exists(destPath.c_str())) return true; + const int width = static_cast(height * 0.6f); + if (!generateThumbBmp(width, height)) return false; + const std::string srcPath = getThumbBmpPath(width, height); + Storage.rename(srcPath.c_str(), destPath.c_str()); + return Storage.exists(destPath.c_str()); +} bool Xtc::generateThumbBmp(int width, int height) const { if (Storage.exists(getThumbBmpPath(width, height).c_str())) return true; diff --git a/src/activities/home/HomeActivity.cpp b/src/activities/home/HomeActivity.cpp index 04830178..f8c97d97 100644 --- a/src/activities/home/HomeActivity.cpp +++ b/src/activities/home/HomeActivity.cpp @@ -217,6 +217,11 @@ void HomeActivity::loadRecentCovers(int coverHeight) { RECENT_BOOKS.updateBook(book.path, book.title, book.author, book.series, ""); book.coverBmpPath = ""; } else { + // Free the carousel frame cache before converting — PNG/JPEG decode needs ~42 KB + // contiguous heap, which won't be available while the 48 KB frame buffer is held. + // The cache will be rebuilt on the next render. + UITheme::getInstance().getMutableTheme().invalidateFrameCache(); + const std::string cacheBase = "/.crosspoint/sidecar_" + std::to_string(std::hash{}(book.path)); const std::string placeholder = cacheBase + "/[HEIGHT].bmp"; bool success = true; @@ -239,8 +244,8 @@ void HomeActivity::loadRecentCovers(int coverHeight) { book.coverBmpPath = placeholder; } else { LOG_ERR("HOME", "Failed to convert sidecar cover for %s", book.path.c_str()); - RECENT_BOOKS.updateBook(book.path, book.title, book.author, book.series, ""); - book.coverBmpPath = ""; + // Don't permanently clear the path on failure — keep the raw sidecar path + // so the next home visit can retry (e.g. after more memory becomes available). } coverRendered = false; nextRecentCoverIndex++; diff --git a/src/activities/reader/ReaderActivity.cpp b/src/activities/reader/ReaderActivity.cpp index a9fa44be..e115391b 100644 --- a/src/activities/reader/ReaderActivity.cpp +++ b/src/activities/reader/ReaderActivity.cpp @@ -62,13 +62,11 @@ std::string ReaderActivity::sidecarCoverPath(const std::string& bookPath) { const std::string base = bookPath.substr(0, dot); for (const char* ext : {".jpg", ".jpeg", ".png", ".bmp"}) { const std::string candidate = base + ext; - LOG_DBG("SIDECAR", "Checking: %s", candidate.c_str()); if (Storage.exists(candidate.c_str())) { LOG_DBG("SIDECAR", "Found sidecar cover: %s", candidate.c_str()); return candidate; } } - LOG_DBG("SIDECAR", "No sidecar found for: %s", bookPath.c_str()); return ""; } diff --git a/src/components/themes/lyra/Lyra3CoversTheme.cpp b/src/components/themes/lyra/Lyra3CoversTheme.cpp index 5e8671bf..06f5f230 100644 --- a/src/components/themes/lyra/Lyra3CoversTheme.cpp +++ b/src/components/themes/lyra/Lyra3CoversTheme.cpp @@ -52,7 +52,7 @@ void Lyra3CoversTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, con const float ratio = bitmapWidth / bitmapHeight; const float tileRatio = static_cast(tileWidth - 2 * hPaddingInSelection) / static_cast(coverHeight); - const float cropX = 1.0f - (tileRatio / ratio); + const float cropX = std::max(0.0f, 1.0f - (tileRatio / ratio)); renderer.drawBitmap(bitmap, tileX + hPaddingInSelection, tileY + hPaddingInSelection, tileWidth - 2 * hPaddingInSelection, coverHeight, cropX);