Fix image display inside epubs
This commit is contained in:
+20
-4
@@ -30,8 +30,9 @@ void PageImage::render(GfxRenderer& renderer, const int fontId, const int xOffse
|
|||||||
imageBlock->render(renderer, xPos + xOffset, yPos + yOffset);
|
imageBlock->render(renderer, xPos + xOffset, yPos + yOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PageImage::renderWithForceLoad(GfxRenderer& renderer, const int xOffset, const int yOffset, const bool forceLoad) {
|
void PageImage::renderWithForceLoad(GfxRenderer& renderer, const int xOffset, const int yOffset, const bool forceLoad,
|
||||||
imageBlock->render(renderer, xPos + xOffset, yPos + yOffset, forceLoad);
|
const bool monochromeOutput) {
|
||||||
|
imageBlock->render(renderer, xPos + xOffset, yPos + yOffset, forceLoad, monochromeOutput);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PageImage::serialize(FsFile& file) {
|
bool PageImage::serialize(FsFile& file) {
|
||||||
@@ -205,10 +206,12 @@ std::unique_ptr<PageTableFragment> PageTableFragment::deserialize(FsFile& file)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Page::render(GfxRenderer& renderer, const int fontId, const int xOffset, const int yOffset,
|
void Page::render(GfxRenderer& renderer, const int fontId, const int xOffset, const int yOffset,
|
||||||
const bool forceLoadLargeImages) const {
|
const bool forceLoadLargeImages, const bool skipDecodedImages, const bool monochromeImages) const {
|
||||||
for (auto& element : elements) {
|
for (auto& element : elements) {
|
||||||
if (element->getTag() == TAG_PageImage) {
|
if (element->getTag() == TAG_PageImage) {
|
||||||
static_cast<PageImage&>(*element).renderWithForceLoad(renderer, xOffset, yOffset, forceLoadLargeImages);
|
auto& pi = static_cast<PageImage&>(*element);
|
||||||
|
if (skipDecodedImages && !pi.getImageBlock().wouldShowPlaceholder(forceLoadLargeImages)) continue;
|
||||||
|
pi.renderWithForceLoad(renderer, xOffset, yOffset, forceLoadLargeImages, monochromeImages);
|
||||||
} else {
|
} else {
|
||||||
element->render(renderer, fontId, xOffset, yOffset);
|
element->render(renderer, fontId, xOffset, yOffset);
|
||||||
}
|
}
|
||||||
@@ -262,6 +265,19 @@ void Page::renderTextOnly(GfxRenderer& renderer, const int fontId, const int xOf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Page::renderImagesOnly(GfxRenderer& renderer, const int xOffset, const int yOffset,
|
||||||
|
const bool forceLoadLargeImages) const {
|
||||||
|
for (auto& element : elements) {
|
||||||
|
if (element->getTag() == TAG_PageImage) {
|
||||||
|
auto& pi = static_cast<PageImage&>(*element);
|
||||||
|
// Placeholders already drew in the BW pass; the grayscale path is only
|
||||||
|
// for images with actual decoded pixel data.
|
||||||
|
if (pi.getImageBlock().wouldShowPlaceholder(forceLoadLargeImages)) continue;
|
||||||
|
pi.renderWithForceLoad(renderer, xOffset, yOffset, forceLoadLargeImages, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool Page::serialize(FsFile& file) const {
|
bool Page::serialize(FsFile& file) const {
|
||||||
const uint16_t count = elements.size();
|
const uint16_t count = elements.size();
|
||||||
serialization::writePod(file, count);
|
serialization::writePod(file, count);
|
||||||
|
|||||||
+14
-2
@@ -58,7 +58,8 @@ class PageImage final : public PageElement {
|
|||||||
PageImage(std::shared_ptr<ImageBlock> block, const int16_t xPos, const int16_t yPos)
|
PageImage(std::shared_ptr<ImageBlock> block, const int16_t xPos, const int16_t yPos)
|
||||||
: PageElement(xPos, yPos), imageBlock(std::move(block)) {}
|
: PageElement(xPos, yPos), imageBlock(std::move(block)) {}
|
||||||
void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) override;
|
void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) override;
|
||||||
void renderWithForceLoad(GfxRenderer& renderer, int xOffset, int yOffset, bool forceLoad);
|
void renderWithForceLoad(GfxRenderer& renderer, int xOffset, int yOffset, bool forceLoad,
|
||||||
|
bool monochromeOutput = false);
|
||||||
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);
|
||||||
@@ -129,8 +130,19 @@ class Page {
|
|||||||
footnotes.push_back(entry);
|
footnotes.push_back(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset, bool forceLoadLargeImages = true) const;
|
// skipDecodedImages=true: PageImages that would decode (not show as a placeholder)
|
||||||
|
// are skipped. Used by the BW pass when an AA grayscale-image pass will redraw
|
||||||
|
// them at full 4-level tonality. Placeholders still render in BW (they're just
|
||||||
|
// a box+text, not dithered pixels).
|
||||||
|
// monochromeImages=true: render images via the 1-bit Atkinson path. Used when
|
||||||
|
// no grayscale image pass will follow (AA off or low-mem) so the BW
|
||||||
|
// DirectPixelWriter `<3` rule yields clean black/white instead of muddy dark.
|
||||||
|
void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset, bool forceLoadLargeImages = true,
|
||||||
|
bool skipDecodedImages = false, bool monochromeImages = false) const;
|
||||||
void renderTextOnly(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) const;
|
void renderTextOnly(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) const;
|
||||||
|
// Renders only PageImages that have actual decoded pixels (skips placeholders,
|
||||||
|
// which already rendered in the BW pass as a box+text).
|
||||||
|
void renderImagesOnly(GfxRenderer& renderer, int xOffset, int yOffset, bool forceLoadLargeImages) const;
|
||||||
// Decode any missing .pxc pixel caches for images on this page. Called before the
|
// Decode any missing .pxc pixel caches for images on this page. Called before the
|
||||||
// BW render so the large (~60 KB contiguous) PNG decoder allocation runs while heap
|
// BW render so the large (~60 KB contiguous) PNG decoder allocation runs while heap
|
||||||
// contig is at its peak — before font prewarm and BW backup chunks fragment it.
|
// contig is at its peak — before font prewarm and BW backup chunks fragment it.
|
||||||
|
|||||||
@@ -25,13 +25,16 @@ bool ImageBlock::imageExists() const { return Storage.exists(imagePath.c_str());
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
std::string getCachePath(const std::string& imagePath, ImageDitherMode ditherMode) {
|
std::string getCachePath(const std::string& imagePath, ImageDitherMode ditherMode, bool monochromeOutput) {
|
||||||
|
// The monochrome (1-bit Atkinson) cache only stores values 0/3, so it can't be
|
||||||
|
// reused for the 4-level grayscale render path — keep it in a separate file.
|
||||||
|
const char* monoSuffix = monochromeOutput ? "_mono" : "";
|
||||||
// Replace extension with .pxc (pixel cache)
|
// Replace extension with .pxc (pixel cache)
|
||||||
size_t dotPos = imagePath.rfind('.');
|
size_t dotPos = imagePath.rfind('.');
|
||||||
if (dotPos != std::string::npos) {
|
if (dotPos != std::string::npos) {
|
||||||
return imagePath.substr(0, dotPos) + getImageDitherCacheSuffix(ditherMode) + ".pxc";
|
return imagePath.substr(0, dotPos) + getImageDitherCacheSuffix(ditherMode) + monoSuffix + ".pxc";
|
||||||
}
|
}
|
||||||
return imagePath + getImageDitherCacheSuffix(ditherMode) + ".pxc";
|
return imagePath + getImageDitherCacheSuffix(ditherMode) + monoSuffix + ".pxc";
|
||||||
}
|
}
|
||||||
|
|
||||||
bool renderFromCache(GfxRenderer& renderer, const std::string& cachePath, int x, int y, int expectedWidth,
|
bool renderFromCache(GfxRenderer& renderer, const std::string& cachePath, int x, int y, int expectedWidth,
|
||||||
@@ -118,7 +121,7 @@ bool ImageBlock::isLargeImage() const {
|
|||||||
|
|
||||||
bool ImageBlock::hasPixelCache() const {
|
bool ImageBlock::hasPixelCache() const {
|
||||||
const ImageDitherMode ditherMode = imageDitherModeFromSetting(SETTINGS.imageDithering);
|
const ImageDitherMode ditherMode = imageDitherModeFromSetting(SETTINGS.imageDithering);
|
||||||
return Storage.exists(getCachePath(imagePath, ditherMode).c_str());
|
return Storage.exists(getCachePath(imagePath, ditherMode, false).c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ImageBlock::wouldShowPlaceholder(bool forceLoad) const {
|
bool ImageBlock::wouldShowPlaceholder(bool forceLoad) const {
|
||||||
@@ -126,12 +129,7 @@ bool ImageBlock::wouldShowPlaceholder(bool forceLoad) const {
|
|||||||
if (!isLargeImage()) return false;
|
if (!isLargeImage()) return false;
|
||||||
// If the pixel cache already exists the render is instant — no placeholder needed
|
// If the pixel cache already exists the render is instant — no placeholder needed
|
||||||
const ImageDitherMode ditherMode = imageDitherModeFromSetting(SETTINGS.imageDithering);
|
const ImageDitherMode ditherMode = imageDitherModeFromSetting(SETTINGS.imageDithering);
|
||||||
const std::string pxcPath = [&] {
|
return !Storage.exists(getCachePath(imagePath, ditherMode, false).c_str());
|
||||||
size_t dot = imagePath.rfind('.');
|
|
||||||
return (dot != std::string::npos ? imagePath.substr(0, dot) : imagePath) + getImageDitherCacheSuffix(ditherMode) +
|
|
||||||
".pxc";
|
|
||||||
}();
|
|
||||||
return !Storage.exists(pxcPath.c_str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImageBlock::renderPlaceholder(GfxRenderer& renderer, const int x, const int y) const {
|
void ImageBlock::renderPlaceholder(GfxRenderer& renderer, const int x, const int y) const {
|
||||||
@@ -156,7 +154,8 @@ void ImageBlock::renderPlaceholder(GfxRenderer& renderer, const int x, const int
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImageBlock::render(GfxRenderer& renderer, const int x, const int y, const bool forceLoad) {
|
void ImageBlock::render(GfxRenderer& renderer, const int x, const int y, const bool forceLoad,
|
||||||
|
const bool monochromeOutput) {
|
||||||
LOG_DBG("IMG", "Rendering image at %d,%d: %s (%dx%d)", x, y, imagePath.c_str(), width, height);
|
LOG_DBG("IMG", "Rendering image at %d,%d: %s (%dx%d)", x, y, imagePath.c_str(), width, height);
|
||||||
|
|
||||||
const int screenWidth = renderer.getScreenWidth();
|
const int screenWidth = renderer.getScreenWidth();
|
||||||
@@ -169,9 +168,11 @@ void ImageBlock::render(GfxRenderer& renderer, const int x, const int y, const b
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to render from pixel cache first (always, regardless of forceLoad)
|
// Try to render from pixel cache first (always, regardless of forceLoad).
|
||||||
|
// Mono and 4-level caches are kept in separate files so each render path gets
|
||||||
|
// the dither output its DirectPixelWriter branch expects.
|
||||||
const ImageDitherMode ditherMode = imageDitherModeFromSetting(SETTINGS.imageDithering);
|
const ImageDitherMode ditherMode = imageDitherModeFromSetting(SETTINGS.imageDithering);
|
||||||
std::string cachePath = getCachePath(imagePath, ditherMode);
|
std::string cachePath = getCachePath(imagePath, ditherMode, monochromeOutput);
|
||||||
if (renderFromCache(renderer, cachePath, x, y, width, height)) {
|
if (renderFromCache(renderer, cachePath, x, y, width, height)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -210,6 +211,7 @@ void ImageBlock::render(GfxRenderer& renderer, const int x, const int y, const b
|
|||||||
config.performanceMode = false;
|
config.performanceMode = false;
|
||||||
config.useExactDimensions = true;
|
config.useExactDimensions = true;
|
||||||
config.cachePath = cachePath;
|
config.cachePath = cachePath;
|
||||||
|
config.monochromeOutput = monochromeOutput;
|
||||||
|
|
||||||
ImageToFramebufferDecoder* decoder = ImageDecoderFactory::getDecoder(imagePath);
|
ImageToFramebufferDecoder* decoder = ImageDecoderFactory::getDecoder(imagePath);
|
||||||
if (!decoder) {
|
if (!decoder) {
|
||||||
|
|||||||
@@ -38,7 +38,11 @@ class ImageBlock final : public Block {
|
|||||||
BlockType getType() override { return IMAGE_BLOCK; }
|
BlockType getType() override { return IMAGE_BLOCK; }
|
||||||
bool isEmpty() override { return false; }
|
bool isEmpty() override { return false; }
|
||||||
|
|
||||||
void render(GfxRenderer& renderer, int x, int y, bool forceLoad = true);
|
// monochromeOutput=true switches the decode pipeline to a 1-bit Atkinson dither
|
||||||
|
// that emits only levels 0 and 3, so the BW DirectPixelWriter `<3` rule yields
|
||||||
|
// clean black/white instead of collapsing mid-greys to black. Used by the BW
|
||||||
|
// pass when the AA grayscale image pass isn't available (AA off or low-mem).
|
||||||
|
void render(GfxRenderer& renderer, int x, int y, bool forceLoad = true, bool monochromeOutput = false);
|
||||||
bool serialize(FsFile& file);
|
bool serialize(FsFile& file);
|
||||||
static std::unique_ptr<ImageBlock> deserialize(FsFile& file);
|
static std::unique_ptr<ImageBlock> deserialize(FsFile& file);
|
||||||
|
|
||||||
|
|||||||
@@ -135,10 +135,12 @@ inline void logReaderMemSnapshot(const char*) {}
|
|||||||
//
|
//
|
||||||
// The page is re-rendered ceil(panelHeight/stripRows) times per plane, but
|
// The page is re-rendered ceil(panelHeight/stripRows) times per plane, but
|
||||||
// renderCharImpl culls out-of-band glyphs before bitmap decode so the cost
|
// renderCharImpl culls out-of-band glyphs before bitmap decode so the cost
|
||||||
// stays close to one render. Only renderTextOnly() is called here, matching the
|
// stays close to one render. Text always participates; images participate when
|
||||||
// legacy AA pass — images and HRs do not participate in grayscale.
|
// `includeImages` is set (caller decides — typically true when the page has any
|
||||||
|
// decoded images, false otherwise to skip the per-strip image cost on text-only
|
||||||
|
// pages).
|
||||||
bool runTiledGrayscalePass(GfxRenderer& renderer, const Page& page, int fontId, int marginLeft, int contentTop,
|
bool runTiledGrayscalePass(GfxRenderer& renderer, const Page& page, int fontId, int marginLeft, int contentTop,
|
||||||
bool fastAA) {
|
bool fastAA, bool includeImages, bool forceLoadLargeImages) {
|
||||||
if (!renderer.supportsStripGrayscale()) return false;
|
if (!renderer.supportsStripGrayscale()) return false;
|
||||||
|
|
||||||
// Push the SETTINGS toggle into the SDK before the AA refresh. No-op on X4;
|
// Push the SETTINGS toggle into the SDK before the AA refresh. No-op on X4;
|
||||||
@@ -166,6 +168,11 @@ bool runTiledGrayscalePass(GfxRenderer& renderer, const Page& page, int fontId,
|
|||||||
renderer.beginStripTarget(scratch, y, rows);
|
renderer.beginStripTarget(scratch, y, rows);
|
||||||
renderer.clearScreen(0x00);
|
renderer.clearScreen(0x00);
|
||||||
page.renderTextOnly(renderer, fontId, marginLeft, contentTop);
|
page.renderTextOnly(renderer, fontId, marginLeft, contentTop);
|
||||||
|
if (includeImages) {
|
||||||
|
// DirectPixelWriter clips against the active strip via originY/clipRows,
|
||||||
|
// so off-band image pixels are dropped automatically.
|
||||||
|
page.renderImagesOnly(renderer, marginLeft, contentTop, forceLoadLargeImages);
|
||||||
|
}
|
||||||
renderer.endStripTarget();
|
renderer.endStripTarget();
|
||||||
renderer.writeGrayscalePlaneStrip(lsbPlane, scratch, y, rows);
|
renderer.writeGrayscalePlaneStrip(lsbPlane, scratch, y, rows);
|
||||||
}
|
}
|
||||||
@@ -2207,7 +2214,14 @@ void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int or
|
|||||||
lastRenderStats.forcedHalfRefresh = forceHalfRefreshThisPage;
|
lastRenderStats.forcedHalfRefresh = forceHalfRefreshThisPage;
|
||||||
|
|
||||||
logReaderMemSnapshot("before_bw_render");
|
logReaderMemSnapshot("before_bw_render");
|
||||||
page->render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, contentTop, effectiveForceLoad);
|
// When AA + grayscale image pass will run, skip decoded images in BW so they
|
||||||
|
// don't get squashed by DirectPixelWriter's `<3` rule (3/4 dither levels → black).
|
||||||
|
// When AA isn't available, render images via the 1-bit Atkinson dither so the
|
||||||
|
// BW pass produces clean monochrome instead of muddy dark grays.
|
||||||
|
const bool grayscaleImagePassWillRun = imagePageWithAA;
|
||||||
|
page->render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, contentTop, effectiveForceLoad,
|
||||||
|
/*skipDecodedImages=*/grayscaleImagePassWillRun,
|
||||||
|
/*monochromeImages=*/!grayscaleImagePassWillRun);
|
||||||
renderStatusBar();
|
renderStatusBar();
|
||||||
if (showTruncatedSectionHintThisRender) {
|
if (showTruncatedSectionHintThisRender) {
|
||||||
const int hintX = orientedMarginLeft + 4;
|
const int hintX = orientedMarginLeft + 4;
|
||||||
@@ -2231,27 +2245,11 @@ void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int or
|
|||||||
logReaderMemSnapshot("after_bw_render");
|
logReaderMemSnapshot("after_bw_render");
|
||||||
|
|
||||||
if (imagePageWithAA) {
|
if (imagePageWithAA) {
|
||||||
// Double FAST_REFRESH with selective image blanking (pablohc's technique):
|
// The BW pass skipped decoded images, so the image area is currently blank
|
||||||
// HALF_REFRESH sets particles too firmly for the grayscale LUT to adjust.
|
// in the framebuffer — no need for the legacy double-FAST_REFRESH blanking
|
||||||
// Instead, blank only the image area and do two fast refreshes.
|
// dance (pablohc's technique). The image will be painted by the subsequent
|
||||||
// Step 1: Display page with image area blanked (text appears, image area white)
|
// grayscale image pass, which produces the cleaner 4-level result anyway.
|
||||||
// Step 2: Re-render with images and display again (images appear clean)
|
ReaderUtils::displayWithRefreshCycle(renderer, pagesUntilFullRefresh);
|
||||||
int16_t imgX, imgY, imgW, imgH;
|
|
||||||
if (page->getImageBoundingBox(imgX, imgY, imgW, imgH)) {
|
|
||||||
renderer.fillRect(imgX + orientedMarginLeft, imgY + contentTop, imgW, imgH, false);
|
|
||||||
renderer.displayBuffer(HalDisplay::FAST_REFRESH);
|
|
||||||
|
|
||||||
// Re-render page content to restore images into the blanked area
|
|
||||||
// Status bar is not re-rendered here to avoid reading stale dynamic values (e.g. battery %)
|
|
||||||
page->render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, contentTop, effectiveForceLoad);
|
|
||||||
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
|
|
||||||
if (forceHalfRefreshThisPage) {
|
|
||||||
pagesUntilFullRefresh = SETTINGS.getRefreshFrequency();
|
|
||||||
}
|
|
||||||
} else if (forceHalfRefreshThisPage) {
|
} else if (forceHalfRefreshThisPage) {
|
||||||
renderer.displayBuffer(HalDisplay::HALF_REFRESH);
|
renderer.displayBuffer(HalDisplay::HALF_REFRESH);
|
||||||
pagesUntilFullRefresh = SETTINGS.getRefreshFrequency();
|
pagesUntilFullRefresh = SETTINGS.getRefreshFrequency();
|
||||||
@@ -2278,7 +2276,8 @@ void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int or
|
|||||||
logReaderMemSnapshot("tiled_gray_begin");
|
logReaderMemSnapshot("tiled_gray_begin");
|
||||||
const auto tTiledBegin = millis();
|
const auto tTiledBegin = millis();
|
||||||
grayscaleDone = runTiledGrayscalePass(renderer, *page, getEffectiveReaderFontId(), orientedMarginLeft, contentTop,
|
grayscaleDone = runTiledGrayscalePass(renderer, *page, getEffectiveReaderFontId(), orientedMarginLeft, contentTop,
|
||||||
SETTINGS.fastAntiAliasing);
|
SETTINGS.fastAntiAliasing,
|
||||||
|
/*includeImages=*/imagePageWithAA, effectiveForceLoad);
|
||||||
if (grayscaleDone) {
|
if (grayscaleDone) {
|
||||||
tiledGrayMs = millis() - tTiledBegin;
|
tiledGrayMs = millis() - tTiledBegin;
|
||||||
fcm->logStats("tiled_gray");
|
fcm->logStats("tiled_gray");
|
||||||
@@ -2342,6 +2341,9 @@ void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int or
|
|||||||
renderer.clearScreen(0x00);
|
renderer.clearScreen(0x00);
|
||||||
renderer.setRenderMode(GfxRenderer::GRAYSCALE_LSB);
|
renderer.setRenderMode(GfxRenderer::GRAYSCALE_LSB);
|
||||||
page->renderTextOnly(renderer, getEffectiveReaderFontId(), orientedMarginLeft, contentTop);
|
page->renderTextOnly(renderer, getEffectiveReaderFontId(), orientedMarginLeft, contentTop);
|
||||||
|
if (imagePageWithAA) {
|
||||||
|
page->renderImagesOnly(renderer, orientedMarginLeft, contentTop, effectiveForceLoad);
|
||||||
|
}
|
||||||
renderer.copyGrayscaleLsbBuffers();
|
renderer.copyGrayscaleLsbBuffers();
|
||||||
const auto tGrayLsb = millis();
|
const auto tGrayLsb = millis();
|
||||||
logReaderMemSnapshot("gray_lsb_end");
|
logReaderMemSnapshot("gray_lsb_end");
|
||||||
@@ -2351,6 +2353,9 @@ void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int or
|
|||||||
renderer.clearScreen(0x00);
|
renderer.clearScreen(0x00);
|
||||||
renderer.setRenderMode(GfxRenderer::GRAYSCALE_MSB);
|
renderer.setRenderMode(GfxRenderer::GRAYSCALE_MSB);
|
||||||
page->renderTextOnly(renderer, getEffectiveReaderFontId(), orientedMarginLeft, contentTop);
|
page->renderTextOnly(renderer, getEffectiveReaderFontId(), orientedMarginLeft, contentTop);
|
||||||
|
if (imagePageWithAA) {
|
||||||
|
page->renderImagesOnly(renderer, orientedMarginLeft, contentTop, effectiveForceLoad);
|
||||||
|
}
|
||||||
renderer.copyGrayscaleMsbBuffers();
|
renderer.copyGrayscaleMsbBuffers();
|
||||||
const auto tGrayMsb = millis();
|
const auto tGrayMsb = millis();
|
||||||
logReaderMemSnapshot("gray_msb_end");
|
logReaderMemSnapshot("gray_msb_end");
|
||||||
@@ -2390,6 +2395,16 @@ void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int or
|
|||||||
bwRestoreMs = tBwRestore - tBwStore;
|
bwRestoreMs = tBwRestore - tBwStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Recovery: the BW pass deliberately skipped decoded images expecting the
|
||||||
|
// grayscale image pass to paint them, but no grayscale pass ran. Re-render
|
||||||
|
// the images in BW using the 1-bit Atkinson dither (monochromeImages=true)
|
||||||
|
// so the cover at least shows up as clean black/white instead of nothing.
|
||||||
|
if (imagePageWithAA) {
|
||||||
|
page->render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, contentTop, effectiveForceLoad,
|
||||||
|
/*skipDecodedImages=*/false, /*monochromeImages=*/true);
|
||||||
|
renderer.displayBuffer(HalDisplay::FAST_REFRESH);
|
||||||
|
}
|
||||||
|
|
||||||
const auto tEnd = millis();
|
const auto tEnd = millis();
|
||||||
lastRenderStats.usedGrayscale = false;
|
lastRenderStats.usedGrayscale = false;
|
||||||
lastRenderStats.phases = {
|
lastRenderStats.phases = {
|
||||||
@@ -2459,8 +2474,9 @@ void EpubReaderActivity::displayPreRenderedPage(const Page& page, const int orie
|
|||||||
// support strip grayscale or when the strip scratch can't be allocated.
|
// support strip grayscale or when the strip scratch can't be allocated.
|
||||||
const bool aaConfigured = getEffectiveTextAntiAliasing() && !antiAliasingSuspendedLowMemory;
|
const bool aaConfigured = getEffectiveTextAntiAliasing() && !antiAliasingSuspendedLowMemory;
|
||||||
if (aaConfigured) {
|
if (aaConfigured) {
|
||||||
|
// Pre-rendered pages are text-only — no image pass needed.
|
||||||
if (runTiledGrayscalePass(renderer, page, getEffectiveReaderFontId(), orientedMarginLeft, contentTop,
|
if (runTiledGrayscalePass(renderer, page, getEffectiveReaderFontId(), orientedMarginLeft, contentTop,
|
||||||
SETTINGS.fastAntiAliasing)) {
|
SETTINGS.fastAntiAliasing, /*includeImages=*/false, /*forceLoadLargeImages=*/false)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user