fix: dynamic continue reading card sizing based on cover image

- Adapt card width to cover image aspect ratio when available
- Maintain fixed 400px height for consistent layout
- Use actual image dimensions instead of hardcoded 240px width for EPUBs
- Add fallback to half-screen size when no cover image exists
This commit is contained in:
pablohc 2026-02-03 14:40:07 +01:00
parent ebcd813ff6
commit fcec6a0b97
3 changed files with 86 additions and 8 deletions

View File

@ -468,10 +468,47 @@ bool Epub::generateThumbBmp() const {
coverJpg.close();
return false;
}
// Use smaller target size for Continue Reading card (half of screen: 240x400)
// Generate 1-bit BMP for fast home screen rendering (no gray passes needed)
constexpr int THUMB_TARGET_WIDTH = 240;
constexpr int THUMB_TARGET_HEIGHT = 400;
// Generate 1-bit BMP maintaining aspect ratio with fixed height of 400px
// This ensures the card width adapts to the actual cover image aspect ratio
const int THUMB_TARGET_HEIGHT = 400;
// Read a small portion of the JPG file to determine its dimensions
uint8_t headerBuffer[200];
size_t bytesRead = coverJpg.read(headerBuffer, sizeof(headerBuffer));
coverJpg.seek(0); // Reset file position
int imgWidth = 240, imgHeight = 400; // Default fallback
// Simple header parsing to get image dimensions (works for most JPGs)
if (bytesRead > 0) {
// Look for SOF (Start of Frame) markers
for (size_t i = 0; i < bytesRead - 10; i++) {
if (headerBuffer[i] == 0xFF && headerBuffer[i + 1] == 0xC0) {
// Found SOF0 marker
if (i + 7 < bytesRead) {
imgHeight = (headerBuffer[i + 5] << 8) | headerBuffer[i + 6];
imgWidth = (headerBuffer[i + 7] << 8) | headerBuffer[i + 8];
break;
}
} else if (headerBuffer[i] == 0xFF && headerBuffer[i + 1] == 0xC2) {
// Found SOF2 marker
if (i + 7 < bytesRead) {
imgHeight = (headerBuffer[i + 5] << 8) | headerBuffer[i + 6];
imgWidth = (headerBuffer[i + 7] << 8) | headerBuffer[i + 8];
break;
}
}
}
}
// Calculate target width maintaining aspect ratio
const float aspectRatio = static_cast<float>(imgWidth) / static_cast<float>(imgHeight);
const int THUMB_TARGET_WIDTH = static_cast<int>(THUMB_TARGET_HEIGHT * aspectRatio);
Serial.printf("[%lu] [EBP] Original JPG: %dx%d, Target: %dx%d\n", millis(), imgWidth, imgHeight, THUMB_TARGET_WIDTH,
THUMB_TARGET_HEIGHT);
const bool success = JpegToBmpConverter::jpegFileTo1BitBmpStreamWithSize(coverJpg, thumbBmp, THUMB_TARGET_WIDTH,
THUMB_TARGET_HEIGHT);
coverJpg.close();

View File

@ -567,5 +567,5 @@ bool JpegToBmpConverter::jpegFileToBmpStreamWithSize(FsFile& jpegFile, Print& bm
// Convert to 1-bit BMP (black and white only, no grays) for fast home screen rendering
bool JpegToBmpConverter::jpegFileTo1BitBmpStreamWithSize(FsFile& jpegFile, Print& bmpOut, int targetMaxWidth,
int targetMaxHeight) {
return jpegFileToBmpStreamInternal(jpegFile, bmpOut, targetMaxWidth, targetMaxHeight, true);
return jpegFileToBmpStreamInternal(jpegFile, bmpOut, targetMaxWidth, targetMaxHeight, true, false);
}

View File

@ -223,9 +223,50 @@ void HomeActivity::render() {
constexpr int bottomMargin = 60;
// --- Top "book" card for the current title (selectorIndex == 0) ---
const int bookWidth = pageWidth / 2;
const int bookHeight = pageHeight / 2;
const int bookX = (pageWidth - bookWidth) / 2;
// When there's no cover image, use fixed size (half screen)
// When there's cover image, adapt width to image aspect ratio, keep height fixed at 400px
const int baseHeight = 400; // Fixed height for both scenarios
int bookWidth, bookX;
if (hasCoverImage) {
// When there's cover, calculate width based on image aspect ratio
// Use default width ratio as fallback if we can't get image dimensions
const float defaultWidthRatio = 0.5f; // Same as half screen
bookWidth = static_cast<int>(pageWidth * defaultWidthRatio);
// If we have cover bitmap path, try to get actual image dimensions
if (!coverBmpPath.empty()) {
FsFile file;
if (SdMan.openFileForRead("HOME", coverBmpPath, file)) {
Bitmap bitmap(file);
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
const int imgWidth = bitmap.getWidth();
const int imgHeight = bitmap.getHeight();
// Calculate width based on aspect ratio, maintaining 400px height
if (imgHeight > 0) {
const float aspectRatio = static_cast<float>(imgWidth) / static_cast<float>(imgHeight);
bookWidth = static_cast<int>(baseHeight * aspectRatio);
// Ensure width doesn't exceed reasonable limits (max 90% of screen width)
const int maxWidth = static_cast<int>(pageWidth * 0.9f);
if (bookWidth > maxWidth) {
bookWidth = maxWidth;
}
}
}
file.close();
}
}
bookX = (pageWidth - bookWidth) / 2;
} else {
// No cover: use half screen size
bookWidth = pageWidth / 2;
bookX = (pageWidth - bookWidth) / 2;
}
const int bookHeight = baseHeight;
constexpr int bookY = 30;
const bool bookSelected = hasContinueReading && selectorIndex == 0;