fix: use double FAST_REFRESH to prevent washout on large grey images (#957)
## Summary Fixes https://github.com/crosspoint-reader/crosspoint-reader/issues/1011 Use double FAST_REFRESH for image pages to prevent grayscale washout, HALF_REFRESH sets e-ink particles too firmly for the grayscale LUT to adjust, causing washed-out images (especially large, light-gray ones). Replace HALF_REFRESH with @pablohc's double FAST_REFRESH technique: blank only the image bounding box area, then re-render with images. This clears ghosting while keeping particles loosely set for grayscale. ## Additional Context --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**< PARTIALLY >**_
This commit is contained in:
@@ -49,6 +49,7 @@ class PageImage final : public PageElement {
|
|||||||
bool serialize(FsFile& file) override;
|
bool serialize(FsFile& file) override;
|
||||||
PageElementTag getTag() const override { return TAG_PageImage; }
|
PageElementTag getTag() const override { return TAG_PageImage; }
|
||||||
static std::unique_ptr<PageImage> deserialize(FsFile& file);
|
static std::unique_ptr<PageImage> deserialize(FsFile& file);
|
||||||
|
const ImageBlock& getImageBlock() const { return *imageBlock; }
|
||||||
};
|
};
|
||||||
|
|
||||||
class Page {
|
class Page {
|
||||||
@@ -64,4 +65,32 @@ class Page {
|
|||||||
return std::any_of(elements.begin(), elements.end(),
|
return std::any_of(elements.begin(), elements.end(),
|
||||||
[](const std::shared_ptr<PageElement>& el) { return el->getTag() == TAG_PageImage; });
|
[](const std::shared_ptr<PageElement>& 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<const PageImage&>(*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;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -638,13 +638,31 @@ void EpubReaderActivity::saveProgress(int spineIndex, int currentPage, int pageC
|
|||||||
void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int orientedMarginTop,
|
void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int orientedMarginTop,
|
||||||
const int orientedMarginRight, const int orientedMarginBottom,
|
const int orientedMarginRight, const int orientedMarginBottom,
|
||||||
const int orientedMarginLeft) {
|
const int orientedMarginLeft) {
|
||||||
// Force full refresh for pages with images when anti-aliasing is on,
|
// Force special handling for pages with images when anti-aliasing is on
|
||||||
// as grayscale tones require half refresh to display correctly
|
bool imagePageWithAA = page->hasImages() && SETTINGS.textAntiAliasing;
|
||||||
bool forceFullRefresh = page->hasImages() && SETTINGS.textAntiAliasing;
|
|
||||||
|
|
||||||
page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop);
|
page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop);
|
||||||
renderStatusBar(orientedMarginRight, orientedMarginBottom, orientedMarginLeft);
|
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);
|
renderer.displayBuffer(HalDisplay::HALF_REFRESH);
|
||||||
pagesUntilFullRefresh = SETTINGS.getRefreshFrequency();
|
pagesUntilFullRefresh = SETTINGS.getRefreshFrequency();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user