diff --git a/lib/Epub/Epub/Page.h b/lib/Epub/Epub/Page.h index 88f98dc7..af792f6f 100644 --- a/lib/Epub/Epub/Page.h +++ b/lib/Epub/Epub/Page.h @@ -49,6 +49,7 @@ class PageImage final : public PageElement { bool serialize(FsFile& file) override; PageElementTag getTag() const override { return TAG_PageImage; } static std::unique_ptr deserialize(FsFile& file); + const ImageBlock& getImageBlock() const { return *imageBlock; } }; class Page { @@ -64,4 +65,32 @@ class Page { return std::any_of(elements.begin(), elements.end(), [](const std::shared_ptr& el) { return el->getTag() == TAG_PageImage; }); } + + // Get bounding box of all images on the page (union of image rects) + // Returns false if no images. Coordinates are relative to page origin. + bool getImageBoundingBox(int16_t& outX, int16_t& outY, int16_t& outW, int16_t& outH) const { + bool found = false; + int16_t minX = INT16_MAX, minY = INT16_MAX, maxX = INT16_MIN, maxY = INT16_MIN; + for (const auto& el : elements) { + if (el->getTag() == TAG_PageImage) { + const auto& img = static_cast(*el); + int16_t x = img.xPos; + int16_t y = img.yPos; + int16_t right = x + img.getImageBlock().getWidth(); + int16_t bottom = y + img.getImageBlock().getHeight(); + minX = std::min(minX, x); + minY = std::min(minY, y); + maxX = std::max(maxX, right); + maxY = std::max(maxY, bottom); + found = true; + } + } + if (found) { + outX = minX; + outY = minY; + outW = maxX - minX; + outH = maxY - minY; + } + return found; + } }; diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 029847c1..7f66d3bf 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -638,13 +638,31 @@ void EpubReaderActivity::saveProgress(int spineIndex, int currentPage, int pageC void EpubReaderActivity::renderContents(std::unique_ptr page, const int orientedMarginTop, const int orientedMarginRight, const int orientedMarginBottom, const int orientedMarginLeft) { - // Force full refresh for pages with images when anti-aliasing is on, - // as grayscale tones require half refresh to display correctly - bool forceFullRefresh = page->hasImages() && SETTINGS.textAntiAliasing; + // Force special handling for pages with images when anti-aliasing is on + bool imagePageWithAA = page->hasImages() && SETTINGS.textAntiAliasing; page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop); renderStatusBar(orientedMarginRight, orientedMarginBottom, orientedMarginLeft); - if (forceFullRefresh || pagesUntilFullRefresh <= 1) { + if (imagePageWithAA) { + // Double FAST_REFRESH with selective image blanking (pablohc's technique): + // HALF_REFRESH sets particles too firmly for the grayscale LUT to adjust. + // Instead, blank only the image area and do two fast refreshes. + // Step 1: Display page with image area blanked (text appears, image area white) + // Step 2: Re-render with images and display again (images appear clean) + int16_t imgX, imgY, imgW, imgH; + if (page->getImageBoundingBox(imgX, imgY, imgW, imgH)) { + renderer.fillRect(imgX + orientedMarginLeft, imgY + orientedMarginTop, imgW, imgH, false); + renderer.displayBuffer(HalDisplay::FAST_REFRESH); + + // Re-render page content to restore images into the blanked area + page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop); + renderStatusBar(orientedMarginRight, orientedMarginBottom, orientedMarginLeft); + renderer.displayBuffer(HalDisplay::FAST_REFRESH); + } else { + renderer.displayBuffer(HalDisplay::HALF_REFRESH); + } + // Double FAST_REFRESH handles ghosting for image pages; don't count toward full refresh cadence + } else if (pagesUntilFullRefresh <= 1) { renderer.displayBuffer(HalDisplay::HALF_REFRESH); pagesUntilFullRefresh = SETTINGS.getRefreshFrequency(); } else {