Files
crosspoint-reader-mod/lib/Epub/Epub/Page.cpp

97 lines
2.6 KiB
C++
Raw Normal View History

2025-12-03 22:00:29 +11:00
#include "Page.h"
#include <Logging.h>
2025-12-03 22:00:29 +11:00
#include <Serialization.h>
Rotation Support (#77) • What is the goal of this PR? Implement a horizontal EPUB reading mode so books can be read in landscape orientation (both 90° and 270°), while keeping the rest of the UI in portrait. • What changes are included? ◦ Rendering / Display ▪ Added an orientation model to GfxRenderer (Portrait, LandscapeNormal, LandscapeFlipped) and made: ▪ drawPixel, drawImage, displayWindow map logical coordinates differently depending on orientation. ▪ getScreenWidth() / getScreenHeight() return orientation‑aware logical dimensions (480×800 in portrait, 800×480 in landscape). ◦ Settings / Configuration ▪ Extended CrossPointSettings with: ▪ landscapeReading (toggle for portrait vs. landscape EPUB reading). ▪ landscapeFlipped (toggle to flip landscape 180° so both horizontal holding directions are supported). ▪ Updated settings serialization/deserialization to persist these fields while remaining backward‑compatible with existing settings files. ▪ Updated SettingsActivity to expose two new toggles: ▪ “Landscape Reading” ▪ “Flip Landscape (swap top/bottom)” ◦ EPUB Reader ▪ In EpubReaderActivity: ▪ On onEnter, set GfxRenderer orientation based on the new settings (Portrait, LandscapeNormal, or LandscapeFlipped). ▪ On onExit, reset orientation back to Portrait so Home, WiFi, Settings, etc. continue to render as before. ▪ Adjusted renderStatusBar to position the status bar and battery indicator relative to GfxRenderer::getScreenHeight() instead of hard‑coded Y coordinates, so it stays correctly at the bottom in both portrait and landscape. ◦ EPUB Caching / Layout ▪ Extended Section cache metadata (section.bin) to include the logical screenWidth and screenHeight used when pages were generated; bumped SECTION_FILE_VERSION. ▪ Updated loadCacheMetadata to compare: ▪ font/margins/line compression/extraParagraphSpacing and screen dimensions; mismatches now invalidate and clear the cache. ▪ Updated persistPageDataToSD and all call sites in EpubReaderActivity to pass the current GfxRenderer::getScreenWidth() / getScreenHeight() so portrait and landscape caches are kept separate and correctly sized. Additional Context • Cache behavior / migration ◦ Existing section.bin files (old SECTION_FILE_VERSION) will be detected as incompatible and their caches cleared and rebuilt once per chapter when first opened after this change. ◦ Within a given orientation, caches will be reused as before. Switching orientation (portrait ↔ landscape) will cause a one‑time re‑index of each chapter in the new orientation. • Scope and risks ◦ Orientation changes are scoped to the EPUB reader; the Home screen, Settings, WiFi selection, sleep screens, and web server UI continue to assume portrait orientation. ◦ The renderer’s orientation is a static/global setting; if future code uses GfxRenderer outside the reader while a reader instance is active, it should be aware that orientation is no longer implicitly fixed. ◦ All drawing primitives now go through orientation‑aware coordinate transforms; any code that previously relied on edge‑case behavior or out‑of‑bounds writes might surface as logged “Outside range” warnings instead. • Testing suggestions / areas to focus on ◦ Verify in hardware: ▪ Portrait mode still renders correctly (boot, home, settings, WiFi, reader). ▪ Landscape reading in both directions: ▪ Landscape Reading = ON, Flip Landscape = OFF. ▪ Landscape Reading = ON, Flip Landscape = ON. ▪ Status bar (page X/Y, % progress, battery icon) is fully visible and aligned at the bottom in all three combinations. ◦ Open the same book: ▪ In portrait first, then switch to landscape and reopen it. ▪ Confirm that: ▪ Old portrait caches are rebuilt once for landscape (you should see the “Indexing…” page). ▪ Progress save/restore still works (resume opens to the correct page in the current orientation). ◦ Ensure grayscale rendering (the secondary pass in EpubReaderActivity::renderContents) still looks correct in both orientations. --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2025-12-28 05:33:20 -05:00
void PageLine::render(GfxRenderer& renderer, const int fontId, const int xOffset, const int yOffset) {
block->render(renderer, fontId, xPos + xOffset, yPos + yOffset);
}
2025-12-03 22:00:29 +11:00
bool PageLine::serialize(FsFile& file) {
serialization::writePod(file, xPos);
serialization::writePod(file, yPos);
2025-12-03 22:00:29 +11:00
// serialize TextBlock pointed to by PageLine
return block->serialize(file);
2025-12-03 22:00:29 +11:00
}
std::unique_ptr<PageLine> PageLine::deserialize(FsFile& file) {
2025-12-13 00:42:17 +11:00
int16_t xPos;
int16_t yPos;
serialization::readPod(file, xPos);
serialization::readPod(file, yPos);
2025-12-03 22:00:29 +11:00
auto tb = TextBlock::deserialize(file);
return std::unique_ptr<PageLine>(new PageLine(std::move(tb), xPos, yPos));
2025-12-03 22:00:29 +11:00
}
feat: add png jpeg support (#556) ## Summary - Add embedded image support to EPUB rendering with JPEG and PNG decoders - Implement pixel caching system to cache decoded/dithered images to SD card for faster re-rendering - Add 4-level grayscale support for display ## Changes ### New Image Rendering System - Add `ImageBlock` class to represent an image with its cached path and display dimensions - Add `PageImage` class as a new `PageElement` type for images on pages - Add `ImageToFramebufferDecoder` interface for format-specific image decoders - Add `JpegToFramebufferConverter` - JPEG decoder with Bayer dithering and scaling - Add `PngToFramebufferConverter` - PNG decoder with Bayer dithering and scaling - Add `ImageDecoderFactory` to select appropriate decoder based on file extension - Add `getRenderMode()` to GfxRenderer for grayscale render mode queries ### Dithering and Grayscale - Implement 4x4 Bayer ordered dithering for 4-level grayscale output - Stateless algorithm works correctly with MCU block decoding - Handles scaling without artifacts - Add grayscale render mode support (BW, GRAYSCALE_LSB, GRAYSCALE_MSB) - Image decoders and cache renderer respect current render mode - Enables proper 4-level e-ink grayscale when anti-aliasing is enabled ### Pixel Caching - Cache decoded/dithered images to `.pxc` files on SD card - Cache format: 2-bit packed pixels (4 pixels per byte) with width/height header - On subsequent renders, load directly from cache instead of re-decoding - Cache renderer supports grayscale render modes for multi-pass rendering - Significantly improves page navigation speed for image-heavy EPUBs ### HTML Parser Integration - Update `ChapterHtmlSlimParser` to process `<img>` tags and extract images from EPUB - Resolve relative image paths within EPUB ZIP structure - Extract images to cache directory before decoding - Create `PageImage` elements with proper scaling to fit viewport - Fall back to alt text display if image processing fails ### Build Configuration - Add `PNG_MAX_BUFFERED_PIXELS=6402` to support up to 800px wide images ### Test Script - Generate test EPUBs with annotated JPEG and PNG images - Test cases cover: grayscale (4 levels), centering, scaling, cache performance ## Test plan - [x] Open EPUB with JPEG images - verify images display with proper grayscale - [x] Open EPUB with PNG images - verify images display correctly and no crash - [x] Navigate away from image page and back - verify faster load from cache - [x] Verify grayscale tones render correctly (not just black/white dithering) - [x] Verify large images are scaled down to fit screen - [x] Verify images are centered horizontally - [x] Verify page serialization/deserialization works with images - [x] Verify images rendered in landscape mode ## Test Results [png](https://photos.app.goo.gl/5zFUb8xA8db3dPd19) [jpeg](https://photos.app.goo.gl/SwtwaL2DSQwKybhw7) ![20260128_231123790](https://github.com/user-attachments/assets/78855971-4bb8-441a-b207-0a292b9739f5) ![20260128_231012253](https://github.com/user-attachments/assets/f08fb63f-1b73-41d9-a25e-78232ec0c495) ![20260128_231004209](https://github.com/user-attachments/assets/06c94acc-8a06-4955-978e-6e583399478d) ![20260128_230954997](https://github.com/user-attachments/assets/49bc44d5-0f2c-416b-9199-4d680fb0f4c3) ![20260128_230945717](https://github.com/user-attachments/assets/93446da5-2e07-410c-89c9-6a21d14e5acb) ![20260128_230938313](https://github.com/user-attachments/assets/4c74c72a-3d40-4a25-b0f3-acc703f42c00) ![20260128_230925546](https://github.com/user-attachments/assets/8d8f62ee-c8fc-4f19-a12c-da29083bb766) ![20260128_230918374](https://github.com/user-attachments/assets/f007d5db-41cc-4fa6-bb22-9e767ee7b00d) --- ### AI Usage Did you use AI tools to help write this code? _**< YES >**_ --------- Co-authored-by: Matthías Páll Gissurarson <mpg@mpg.is> Co-authored-by: Dave Allie <dave@daveallie.com>
2026-02-16 08:56:59 +00:00
void PageImage::render(GfxRenderer& renderer, const int fontId, const int xOffset, const int yOffset) {
// Images don't use fontId or text rendering
imageBlock->render(renderer, xPos + xOffset, yPos + yOffset);
}
bool PageImage::serialize(FsFile& file) {
serialization::writePod(file, xPos);
serialization::writePod(file, yPos);
// serialize ImageBlock
return imageBlock->serialize(file);
}
std::unique_ptr<PageImage> PageImage::deserialize(FsFile& file) {
int16_t xPos;
int16_t yPos;
serialization::readPod(file, xPos);
serialization::readPod(file, yPos);
auto ib = ImageBlock::deserialize(file);
return std::unique_ptr<PageImage>(new PageImage(std::move(ib), xPos, yPos));
}
Rotation Support (#77) • What is the goal of this PR? Implement a horizontal EPUB reading mode so books can be read in landscape orientation (both 90° and 270°), while keeping the rest of the UI in portrait. • What changes are included? ◦ Rendering / Display ▪ Added an orientation model to GfxRenderer (Portrait, LandscapeNormal, LandscapeFlipped) and made: ▪ drawPixel, drawImage, displayWindow map logical coordinates differently depending on orientation. ▪ getScreenWidth() / getScreenHeight() return orientation‑aware logical dimensions (480×800 in portrait, 800×480 in landscape). ◦ Settings / Configuration ▪ Extended CrossPointSettings with: ▪ landscapeReading (toggle for portrait vs. landscape EPUB reading). ▪ landscapeFlipped (toggle to flip landscape 180° so both horizontal holding directions are supported). ▪ Updated settings serialization/deserialization to persist these fields while remaining backward‑compatible with existing settings files. ▪ Updated SettingsActivity to expose two new toggles: ▪ “Landscape Reading” ▪ “Flip Landscape (swap top/bottom)” ◦ EPUB Reader ▪ In EpubReaderActivity: ▪ On onEnter, set GfxRenderer orientation based on the new settings (Portrait, LandscapeNormal, or LandscapeFlipped). ▪ On onExit, reset orientation back to Portrait so Home, WiFi, Settings, etc. continue to render as before. ▪ Adjusted renderStatusBar to position the status bar and battery indicator relative to GfxRenderer::getScreenHeight() instead of hard‑coded Y coordinates, so it stays correctly at the bottom in both portrait and landscape. ◦ EPUB Caching / Layout ▪ Extended Section cache metadata (section.bin) to include the logical screenWidth and screenHeight used when pages were generated; bumped SECTION_FILE_VERSION. ▪ Updated loadCacheMetadata to compare: ▪ font/margins/line compression/extraParagraphSpacing and screen dimensions; mismatches now invalidate and clear the cache. ▪ Updated persistPageDataToSD and all call sites in EpubReaderActivity to pass the current GfxRenderer::getScreenWidth() / getScreenHeight() so portrait and landscape caches are kept separate and correctly sized. Additional Context • Cache behavior / migration ◦ Existing section.bin files (old SECTION_FILE_VERSION) will be detected as incompatible and their caches cleared and rebuilt once per chapter when first opened after this change. ◦ Within a given orientation, caches will be reused as before. Switching orientation (portrait ↔ landscape) will cause a one‑time re‑index of each chapter in the new orientation. • Scope and risks ◦ Orientation changes are scoped to the EPUB reader; the Home screen, Settings, WiFi selection, sleep screens, and web server UI continue to assume portrait orientation. ◦ The renderer’s orientation is a static/global setting; if future code uses GfxRenderer outside the reader while a reader instance is active, it should be aware that orientation is no longer implicitly fixed. ◦ All drawing primitives now go through orientation‑aware coordinate transforms; any code that previously relied on edge‑case behavior or out‑of‑bounds writes might surface as logged “Outside range” warnings instead. • Testing suggestions / areas to focus on ◦ Verify in hardware: ▪ Portrait mode still renders correctly (boot, home, settings, WiFi, reader). ▪ Landscape reading in both directions: ▪ Landscape Reading = ON, Flip Landscape = OFF. ▪ Landscape Reading = ON, Flip Landscape = ON. ▪ Status bar (page X/Y, % progress, battery icon) is fully visible and aligned at the bottom in all three combinations. ◦ Open the same book: ▪ In portrait first, then switch to landscape and reopen it. ▪ Confirm that: ▪ Old portrait caches are rebuilt once for landscape (you should see the “Indexing…” page). ▪ Progress save/restore still works (resume opens to the correct page in the current orientation). ◦ Ensure grayscale rendering (the secondary pass in EpubReaderActivity::renderContents) still looks correct in both orientations. --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2025-12-28 05:33:20 -05:00
void Page::render(GfxRenderer& renderer, const int fontId, const int xOffset, const int yOffset) const {
2025-12-13 00:42:17 +11:00
for (auto& element : elements) {
Rotation Support (#77) • What is the goal of this PR? Implement a horizontal EPUB reading mode so books can be read in landscape orientation (both 90° and 270°), while keeping the rest of the UI in portrait. • What changes are included? ◦ Rendering / Display ▪ Added an orientation model to GfxRenderer (Portrait, LandscapeNormal, LandscapeFlipped) and made: ▪ drawPixel, drawImage, displayWindow map logical coordinates differently depending on orientation. ▪ getScreenWidth() / getScreenHeight() return orientation‑aware logical dimensions (480×800 in portrait, 800×480 in landscape). ◦ Settings / Configuration ▪ Extended CrossPointSettings with: ▪ landscapeReading (toggle for portrait vs. landscape EPUB reading). ▪ landscapeFlipped (toggle to flip landscape 180° so both horizontal holding directions are supported). ▪ Updated settings serialization/deserialization to persist these fields while remaining backward‑compatible with existing settings files. ▪ Updated SettingsActivity to expose two new toggles: ▪ “Landscape Reading” ▪ “Flip Landscape (swap top/bottom)” ◦ EPUB Reader ▪ In EpubReaderActivity: ▪ On onEnter, set GfxRenderer orientation based on the new settings (Portrait, LandscapeNormal, or LandscapeFlipped). ▪ On onExit, reset orientation back to Portrait so Home, WiFi, Settings, etc. continue to render as before. ▪ Adjusted renderStatusBar to position the status bar and battery indicator relative to GfxRenderer::getScreenHeight() instead of hard‑coded Y coordinates, so it stays correctly at the bottom in both portrait and landscape. ◦ EPUB Caching / Layout ▪ Extended Section cache metadata (section.bin) to include the logical screenWidth and screenHeight used when pages were generated; bumped SECTION_FILE_VERSION. ▪ Updated loadCacheMetadata to compare: ▪ font/margins/line compression/extraParagraphSpacing and screen dimensions; mismatches now invalidate and clear the cache. ▪ Updated persistPageDataToSD and all call sites in EpubReaderActivity to pass the current GfxRenderer::getScreenWidth() / getScreenHeight() so portrait and landscape caches are kept separate and correctly sized. Additional Context • Cache behavior / migration ◦ Existing section.bin files (old SECTION_FILE_VERSION) will be detected as incompatible and their caches cleared and rebuilt once per chapter when first opened after this change. ◦ Within a given orientation, caches will be reused as before. Switching orientation (portrait ↔ landscape) will cause a one‑time re‑index of each chapter in the new orientation. • Scope and risks ◦ Orientation changes are scoped to the EPUB reader; the Home screen, Settings, WiFi selection, sleep screens, and web server UI continue to assume portrait orientation. ◦ The renderer’s orientation is a static/global setting; if future code uses GfxRenderer outside the reader while a reader instance is active, it should be aware that orientation is no longer implicitly fixed. ◦ All drawing primitives now go through orientation‑aware coordinate transforms; any code that previously relied on edge‑case behavior or out‑of‑bounds writes might surface as logged “Outside range” warnings instead. • Testing suggestions / areas to focus on ◦ Verify in hardware: ▪ Portrait mode still renders correctly (boot, home, settings, WiFi, reader). ▪ Landscape reading in both directions: ▪ Landscape Reading = ON, Flip Landscape = OFF. ▪ Landscape Reading = ON, Flip Landscape = ON. ▪ Status bar (page X/Y, % progress, battery icon) is fully visible and aligned at the bottom in all three combinations. ◦ Open the same book: ▪ In portrait first, then switch to landscape and reopen it. ▪ Confirm that: ▪ Old portrait caches are rebuilt once for landscape (you should see the “Indexing…” page). ▪ Progress save/restore still works (resume opens to the correct page in the current orientation). ◦ Ensure grayscale rendering (the secondary pass in EpubReaderActivity::renderContents) still looks correct in both orientations. --------- Co-authored-by: Dave Allie <dave@daveallie.com>
2025-12-28 05:33:20 -05:00
element->render(renderer, fontId, xOffset, yOffset);
2025-12-03 22:00:29 +11:00
}
}
bool Page::serialize(FsFile& file) const {
const uint16_t count = elements.size();
serialization::writePod(file, count);
2025-12-03 22:00:29 +11:00
for (const auto& el : elements) {
feat: add png jpeg support (#556) ## Summary - Add embedded image support to EPUB rendering with JPEG and PNG decoders - Implement pixel caching system to cache decoded/dithered images to SD card for faster re-rendering - Add 4-level grayscale support for display ## Changes ### New Image Rendering System - Add `ImageBlock` class to represent an image with its cached path and display dimensions - Add `PageImage` class as a new `PageElement` type for images on pages - Add `ImageToFramebufferDecoder` interface for format-specific image decoders - Add `JpegToFramebufferConverter` - JPEG decoder with Bayer dithering and scaling - Add `PngToFramebufferConverter` - PNG decoder with Bayer dithering and scaling - Add `ImageDecoderFactory` to select appropriate decoder based on file extension - Add `getRenderMode()` to GfxRenderer for grayscale render mode queries ### Dithering and Grayscale - Implement 4x4 Bayer ordered dithering for 4-level grayscale output - Stateless algorithm works correctly with MCU block decoding - Handles scaling without artifacts - Add grayscale render mode support (BW, GRAYSCALE_LSB, GRAYSCALE_MSB) - Image decoders and cache renderer respect current render mode - Enables proper 4-level e-ink grayscale when anti-aliasing is enabled ### Pixel Caching - Cache decoded/dithered images to `.pxc` files on SD card - Cache format: 2-bit packed pixels (4 pixels per byte) with width/height header - On subsequent renders, load directly from cache instead of re-decoding - Cache renderer supports grayscale render modes for multi-pass rendering - Significantly improves page navigation speed for image-heavy EPUBs ### HTML Parser Integration - Update `ChapterHtmlSlimParser` to process `<img>` tags and extract images from EPUB - Resolve relative image paths within EPUB ZIP structure - Extract images to cache directory before decoding - Create `PageImage` elements with proper scaling to fit viewport - Fall back to alt text display if image processing fails ### Build Configuration - Add `PNG_MAX_BUFFERED_PIXELS=6402` to support up to 800px wide images ### Test Script - Generate test EPUBs with annotated JPEG and PNG images - Test cases cover: grayscale (4 levels), centering, scaling, cache performance ## Test plan - [x] Open EPUB with JPEG images - verify images display with proper grayscale - [x] Open EPUB with PNG images - verify images display correctly and no crash - [x] Navigate away from image page and back - verify faster load from cache - [x] Verify grayscale tones render correctly (not just black/white dithering) - [x] Verify large images are scaled down to fit screen - [x] Verify images are centered horizontally - [x] Verify page serialization/deserialization works with images - [x] Verify images rendered in landscape mode ## Test Results [png](https://photos.app.goo.gl/5zFUb8xA8db3dPd19) [jpeg](https://photos.app.goo.gl/SwtwaL2DSQwKybhw7) ![20260128_231123790](https://github.com/user-attachments/assets/78855971-4bb8-441a-b207-0a292b9739f5) ![20260128_231012253](https://github.com/user-attachments/assets/f08fb63f-1b73-41d9-a25e-78232ec0c495) ![20260128_231004209](https://github.com/user-attachments/assets/06c94acc-8a06-4955-978e-6e583399478d) ![20260128_230954997](https://github.com/user-attachments/assets/49bc44d5-0f2c-416b-9199-4d680fb0f4c3) ![20260128_230945717](https://github.com/user-attachments/assets/93446da5-2e07-410c-89c9-6a21d14e5acb) ![20260128_230938313](https://github.com/user-attachments/assets/4c74c72a-3d40-4a25-b0f3-acc703f42c00) ![20260128_230925546](https://github.com/user-attachments/assets/8d8f62ee-c8fc-4f19-a12c-da29083bb766) ![20260128_230918374](https://github.com/user-attachments/assets/f007d5db-41cc-4fa6-bb22-9e767ee7b00d) --- ### AI Usage Did you use AI tools to help write this code? _**< YES >**_ --------- Co-authored-by: Matthías Páll Gissurarson <mpg@mpg.is> Co-authored-by: Dave Allie <dave@daveallie.com>
2026-02-16 08:56:59 +00:00
// Use getTag() method to determine type
serialization::writePod(file, static_cast<uint8_t>(el->getTag()));
if (!el->serialize(file)) {
return false;
}
2025-12-03 22:00:29 +11:00
}
return true;
2025-12-03 22:00:29 +11:00
}
std::unique_ptr<Page> Page::deserialize(FsFile& file) {
auto page = std::unique_ptr<Page>(new Page());
2025-12-03 22:00:29 +11:00
uint16_t count;
serialization::readPod(file, count);
2025-12-03 22:00:29 +11:00
for (uint16_t i = 0; i < count; i++) {
2025-12-03 22:00:29 +11:00
uint8_t tag;
serialization::readPod(file, tag);
2025-12-03 22:00:29 +11:00
if (tag == TAG_PageLine) {
auto pl = PageLine::deserialize(file);
page->elements.push_back(std::move(pl));
feat: add png jpeg support (#556) ## Summary - Add embedded image support to EPUB rendering with JPEG and PNG decoders - Implement pixel caching system to cache decoded/dithered images to SD card for faster re-rendering - Add 4-level grayscale support for display ## Changes ### New Image Rendering System - Add `ImageBlock` class to represent an image with its cached path and display dimensions - Add `PageImage` class as a new `PageElement` type for images on pages - Add `ImageToFramebufferDecoder` interface for format-specific image decoders - Add `JpegToFramebufferConverter` - JPEG decoder with Bayer dithering and scaling - Add `PngToFramebufferConverter` - PNG decoder with Bayer dithering and scaling - Add `ImageDecoderFactory` to select appropriate decoder based on file extension - Add `getRenderMode()` to GfxRenderer for grayscale render mode queries ### Dithering and Grayscale - Implement 4x4 Bayer ordered dithering for 4-level grayscale output - Stateless algorithm works correctly with MCU block decoding - Handles scaling without artifacts - Add grayscale render mode support (BW, GRAYSCALE_LSB, GRAYSCALE_MSB) - Image decoders and cache renderer respect current render mode - Enables proper 4-level e-ink grayscale when anti-aliasing is enabled ### Pixel Caching - Cache decoded/dithered images to `.pxc` files on SD card - Cache format: 2-bit packed pixels (4 pixels per byte) with width/height header - On subsequent renders, load directly from cache instead of re-decoding - Cache renderer supports grayscale render modes for multi-pass rendering - Significantly improves page navigation speed for image-heavy EPUBs ### HTML Parser Integration - Update `ChapterHtmlSlimParser` to process `<img>` tags and extract images from EPUB - Resolve relative image paths within EPUB ZIP structure - Extract images to cache directory before decoding - Create `PageImage` elements with proper scaling to fit viewport - Fall back to alt text display if image processing fails ### Build Configuration - Add `PNG_MAX_BUFFERED_PIXELS=6402` to support up to 800px wide images ### Test Script - Generate test EPUBs with annotated JPEG and PNG images - Test cases cover: grayscale (4 levels), centering, scaling, cache performance ## Test plan - [x] Open EPUB with JPEG images - verify images display with proper grayscale - [x] Open EPUB with PNG images - verify images display correctly and no crash - [x] Navigate away from image page and back - verify faster load from cache - [x] Verify grayscale tones render correctly (not just black/white dithering) - [x] Verify large images are scaled down to fit screen - [x] Verify images are centered horizontally - [x] Verify page serialization/deserialization works with images - [x] Verify images rendered in landscape mode ## Test Results [png](https://photos.app.goo.gl/5zFUb8xA8db3dPd19) [jpeg](https://photos.app.goo.gl/SwtwaL2DSQwKybhw7) ![20260128_231123790](https://github.com/user-attachments/assets/78855971-4bb8-441a-b207-0a292b9739f5) ![20260128_231012253](https://github.com/user-attachments/assets/f08fb63f-1b73-41d9-a25e-78232ec0c495) ![20260128_231004209](https://github.com/user-attachments/assets/06c94acc-8a06-4955-978e-6e583399478d) ![20260128_230954997](https://github.com/user-attachments/assets/49bc44d5-0f2c-416b-9199-4d680fb0f4c3) ![20260128_230945717](https://github.com/user-attachments/assets/93446da5-2e07-410c-89c9-6a21d14e5acb) ![20260128_230938313](https://github.com/user-attachments/assets/4c74c72a-3d40-4a25-b0f3-acc703f42c00) ![20260128_230925546](https://github.com/user-attachments/assets/8d8f62ee-c8fc-4f19-a12c-da29083bb766) ![20260128_230918374](https://github.com/user-attachments/assets/f007d5db-41cc-4fa6-bb22-9e767ee7b00d) --- ### AI Usage Did you use AI tools to help write this code? _**< YES >**_ --------- Co-authored-by: Matthías Páll Gissurarson <mpg@mpg.is> Co-authored-by: Dave Allie <dave@daveallie.com>
2026-02-16 08:56:59 +00:00
} else if (tag == TAG_PageImage) {
auto pi = PageImage::deserialize(file);
page->elements.push_back(std::move(pi));
2025-12-03 22:00:29 +11:00
} else {
LOG_ERR("PGE", "Deserialization failed: Unknown tag %u", tag);
return nullptr;
2025-12-03 22:00:29 +11:00
}
}
return page;
}