mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-04 22:57:50 +03:00
fix: correct home cover dimensions
This commit is contained in:
parent
3ce11f14ce
commit
5671b05d04
@ -419,28 +419,29 @@ bool Epub::generateCoverBmp(bool cropped) const {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Epub::getThumbBmpPath() const { return cachePath + "/thumb.bmp"; }
|
std::string Epub::getCoverHomeBmpPath() const { return cachePath + "/cover_home.bmp"; }
|
||||||
|
|
||||||
bool Epub::generateThumbBmp() const {
|
|
||||||
|
bool Epub::generateCoverHomeBmp() const {
|
||||||
// Already generated, return true
|
// Already generated, return true
|
||||||
if (SdMan.exists(getThumbBmpPath().c_str())) {
|
if (SdMan.exists(getCoverHomeBmpPath().c_str())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
||||||
Serial.printf("[%lu] [EBP] Cannot generate thumb BMP, cache not loaded\n", millis());
|
Serial.printf("[%lu] [EBP] Cannot generate home BMP, cache not loaded\n", millis());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto coverImageHref = bookMetadataCache->coreMetadata.coverItemHref;
|
const auto coverImageHref = bookMetadataCache->coreMetadata.coverItemHref;
|
||||||
if (coverImageHref.empty()) {
|
if (coverImageHref.empty()) {
|
||||||
Serial.printf("[%lu] [EBP] No known cover image for thumbnail\n", millis());
|
Serial.printf("[%lu] [EBP] No known cover image for home screen\n", millis());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (coverImageHref.substr(coverImageHref.length() - 4) == ".jpg" ||
|
if (coverImageHref.substr(coverImageHref.length() - 4) == ".jpg" ||
|
||||||
coverImageHref.substr(coverImageHref.length() - 5) == ".jpeg") {
|
coverImageHref.substr(coverImageHref.length() - 5) == ".jpeg") {
|
||||||
Serial.printf("[%lu] [EBP] Generating thumb BMP from JPG cover image\n", millis());
|
Serial.printf("[%lu] [EBP] Generating home BMP from JPG cover image\n", millis());
|
||||||
const auto coverJpgTempPath = getCachePath() + "/.cover.jpg";
|
const auto coverJpgTempPath = getCachePath() + "/.cover.jpg";
|
||||||
|
|
||||||
FsFile coverJpg;
|
FsFile coverJpg;
|
||||||
@ -454,30 +455,49 @@ bool Epub::generateThumbBmp() const {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
FsFile thumbBmp;
|
FsFile homeBmp;
|
||||||
if (!SdMan.openFileForWrite("EBP", getThumbBmpPath(), thumbBmp)) {
|
if (!SdMan.openFileForWrite("EBP", getCoverHomeBmpPath(), homeBmp)) {
|
||||||
coverJpg.close();
|
coverJpg.close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Use smaller target size for Continue Reading card (half of screen: 240x400)
|
|
||||||
|
// For home screen, use 400px height with proportional width for optimal performance
|
||||||
// Generate 1-bit BMP for fast home screen rendering (no gray passes needed)
|
// Generate 1-bit BMP for fast home screen rendering (no gray passes needed)
|
||||||
constexpr int THUMB_TARGET_WIDTH = 240;
|
constexpr int HOME_TARGET_HEIGHT = 400;
|
||||||
constexpr int THUMB_TARGET_HEIGHT = 400;
|
|
||||||
const bool success = JpegToBmpConverter::jpegFileTo1BitBmpStreamWithSize(coverJpg, thumbBmp, THUMB_TARGET_WIDTH,
|
FsFile tempJpg;
|
||||||
THUMB_TARGET_HEIGHT);
|
if (!SdMan.openFileForRead("EBP", coverJpgTempPath, tempJpg)) {
|
||||||
|
coverJpg.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// First get JPEG dimensions to calculate proper width
|
||||||
|
int jpegWidth, jpegHeight;
|
||||||
|
if (!JpegToBmpConverter::getJpegDimensions(tempJpg, &jpegWidth, &jpegHeight)) {
|
||||||
|
Serial.printf("[%lu] [EBP] Failed to get JPEG dimensions for home cover\n", millis());
|
||||||
|
coverJpg.close();
|
||||||
|
tempJpg.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
tempJpg.close();
|
||||||
|
|
||||||
|
// Calculate proportional width for 400px height
|
||||||
|
const int targetWidth = (400 * jpegWidth) / jpegHeight;
|
||||||
|
|
||||||
|
const bool success = JpegToBmpConverter::jpegFileTo1BitBmpStreamWithSize(coverJpg, homeBmp, targetWidth, HOME_TARGET_HEIGHT);
|
||||||
coverJpg.close();
|
coverJpg.close();
|
||||||
thumbBmp.close();
|
homeBmp.close();
|
||||||
SdMan.remove(coverJpgTempPath.c_str());
|
SdMan.remove(coverJpgTempPath.c_str());
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
Serial.printf("[%lu] [EBP] Failed to generate thumb BMP from JPG cover image\n", millis());
|
Serial.printf("[%lu] [EBP] Failed to generate home BMP from JPG cover image\n", millis());
|
||||||
SdMan.remove(getThumbBmpPath().c_str());
|
SdMan.remove(getCoverHomeBmpPath().c_str());
|
||||||
}
|
}
|
||||||
Serial.printf("[%lu] [EBP] Generated thumb BMP from JPG cover image, success: %s\n", millis(),
|
Serial.printf("[%lu] [EBP] Generated home BMP from JPG cover image, success: %s\n", millis(),
|
||||||
success ? "yes" : "no");
|
success ? "yes" : "no");
|
||||||
return success;
|
return success;
|
||||||
} else {
|
} else {
|
||||||
Serial.printf("[%lu] [EBP] Cover image is not a JPG, skipping thumbnail\n", millis());
|
Serial.printf("[%lu] [EBP] Cover image is not a JPG, skipping home screen\n", millis());
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -47,8 +47,9 @@ class Epub {
|
|||||||
const std::string& getLanguage() const;
|
const std::string& getLanguage() const;
|
||||||
std::string getCoverBmpPath(bool cropped = false) const;
|
std::string getCoverBmpPath(bool cropped = false) const;
|
||||||
bool generateCoverBmp(bool cropped = false) const;
|
bool generateCoverBmp(bool cropped = false) const;
|
||||||
std::string getThumbBmpPath() const;
|
// Home screen support (optimized 400px height covers for Continue Reading card)
|
||||||
bool generateThumbBmp() const;
|
std::string getCoverHomeBmpPath() const;
|
||||||
|
bool generateCoverHomeBmp() const;
|
||||||
uint8_t* readItemContentsToBytes(const std::string& itemHref, size_t* size = nullptr,
|
uint8_t* readItemContentsToBytes(const std::string& itemHref, size_t* size = nullptr,
|
||||||
bool trailingNullByte = false) const;
|
bool trailingNullByte = false) const;
|
||||||
bool readItemContentsToStream(const std::string& itemHref, Print& out, size_t chunkSize) const;
|
bool readItemContentsToStream(const std::string& itemHref, Print& out, size_t chunkSize) const;
|
||||||
|
|||||||
@ -565,3 +565,32 @@ bool JpegToBmpConverter::jpegFileTo1BitBmpStreamWithSize(FsFile& jpegFile, Print
|
|||||||
int targetMaxHeight) {
|
int targetMaxHeight) {
|
||||||
return jpegFileToBmpStreamInternal(jpegFile, bmpOut, targetMaxWidth, targetMaxHeight, true);
|
return jpegFileToBmpStreamInternal(jpegFile, bmpOut, targetMaxWidth, targetMaxHeight, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get JPEG dimensions without full conversion
|
||||||
|
bool JpegToBmpConverter::getJpegDimensions(FsFile& jpegFile, int* width, int* height) {
|
||||||
|
// Reset file position to beginning
|
||||||
|
if (!jpegFile.seek(0)) {
|
||||||
|
Serial.printf("[%lu] [JPG] Failed to seek to beginning of JPEG file\n", millis());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize JPEG decoder
|
||||||
|
pjpeg_image_info_t imageInfo = {};
|
||||||
|
JpegReadContext context{jpegFile, {}, 0, 0};
|
||||||
|
|
||||||
|
const int decodeStatus = pjpeg_decode_init(&imageInfo, jpegReadCallback, &context, false);
|
||||||
|
if (decodeStatus != 0) {
|
||||||
|
Serial.printf("[%lu] [JPG] pjpeg_decode_init failed with status %d\n", millis(), decodeStatus);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get dimensions from image info
|
||||||
|
*width = imageInfo.m_width;
|
||||||
|
*height = imageInfo.m_height;
|
||||||
|
|
||||||
|
Serial.printf("[%lu] [JPG] Read JPEG dimensions: %dx%d\n", millis(), *width, *height);
|
||||||
|
|
||||||
|
// Reset file position after reading
|
||||||
|
jpegFile.seek(0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|||||||
@ -16,4 +16,6 @@ class JpegToBmpConverter {
|
|||||||
static bool jpegFileToBmpStreamWithSize(FsFile& jpegFile, Print& bmpOut, int targetMaxWidth, int targetMaxHeight);
|
static bool jpegFileToBmpStreamWithSize(FsFile& jpegFile, Print& bmpOut, int targetMaxWidth, int targetMaxHeight);
|
||||||
// Convert to 1-bit BMP (black and white only, no grays) for fast home screen rendering
|
// Convert to 1-bit BMP (black and white only, no grays) for fast home screen rendering
|
||||||
static bool jpegFileTo1BitBmpStreamWithSize(FsFile& jpegFile, Print& bmpOut, int targetMaxWidth, int targetMaxHeight);
|
static bool jpegFileTo1BitBmpStreamWithSize(FsFile& jpegFile, Print& bmpOut, int targetMaxWidth, int targetMaxHeight);
|
||||||
|
// Extract JPEG dimensions without loading full image
|
||||||
|
static bool getJpegDimensions(FsFile& jpegFile, int* width, int* height);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -293,11 +293,11 @@ bool Xtc::generateCoverBmp() const {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Xtc::getThumbBmpPath() const { return cachePath + "/thumb.bmp"; }
|
std::string Xtc::getCoverHomeBmpPath() const { return cachePath + "/cover_home.bmp"; }
|
||||||
|
|
||||||
bool Xtc::generateThumbBmp() const {
|
bool Xtc::generateCoverHomeBmp() const {
|
||||||
// Already generated
|
// Already generated
|
||||||
if (SdMan.exists(getThumbBmpPath().c_str())) {
|
if (SdMan.exists(getCoverHomeBmpPath().c_str())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,43 +324,18 @@ bool Xtc::generateThumbBmp() const {
|
|||||||
// Get bit depth
|
// Get bit depth
|
||||||
const uint8_t bitDepth = parser->getBitDepth();
|
const uint8_t bitDepth = parser->getBitDepth();
|
||||||
|
|
||||||
// Calculate target dimensions for thumbnail (fit within 240x400 Continue Reading card)
|
// For home screen, use 400px height with proportional width for optimal performance
|
||||||
constexpr int THUMB_TARGET_WIDTH = 240;
|
// Generate 1-bit BMP for fast home screen rendering (no gray passes needed)
|
||||||
constexpr int THUMB_TARGET_HEIGHT = 400;
|
constexpr int HOME_TARGET_HEIGHT = 400;
|
||||||
|
|
||||||
// Calculate scale factor
|
// Calculate proportional width for 400px height
|
||||||
float scaleX = static_cast<float>(THUMB_TARGET_WIDTH) / pageInfo.width;
|
const uint16_t targetWidth = static_cast<uint16_t>((HOME_TARGET_HEIGHT * pageInfo.width) / pageInfo.height);
|
||||||
float scaleY = static_cast<float>(THUMB_TARGET_HEIGHT) / pageInfo.height;
|
|
||||||
float scale = (scaleX < scaleY) ? scaleX : scaleY;
|
|
||||||
|
|
||||||
// Only scale down, never up
|
Serial.printf("[%lu] [XTC] Generating home BMP: %dx%d -> %dx%d\n", millis(), pageInfo.width,
|
||||||
if (scale >= 1.0f) {
|
pageInfo.height, targetWidth, HOME_TARGET_HEIGHT);
|
||||||
// Page is already small enough, just use cover.bmp
|
|
||||||
// Copy cover.bmp to thumb.bmp
|
|
||||||
if (generateCoverBmp()) {
|
|
||||||
FsFile src, dst;
|
|
||||||
if (SdMan.openFileForRead("XTC", getCoverBmpPath(), src)) {
|
|
||||||
if (SdMan.openFileForWrite("XTC", getThumbBmpPath(), dst)) {
|
|
||||||
uint8_t buffer[512];
|
|
||||||
while (src.available()) {
|
|
||||||
size_t bytesRead = src.read(buffer, sizeof(buffer));
|
|
||||||
dst.write(buffer, bytesRead);
|
|
||||||
}
|
|
||||||
dst.close();
|
|
||||||
}
|
|
||||||
src.close();
|
|
||||||
}
|
|
||||||
Serial.printf("[%lu] [XTC] Copied cover to thumb (no scaling needed)\n", millis());
|
|
||||||
return SdMan.exists(getThumbBmpPath().c_str());
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t thumbWidth = static_cast<uint16_t>(pageInfo.width * scale);
|
|
||||||
uint16_t thumbHeight = static_cast<uint16_t>(pageInfo.height * scale);
|
|
||||||
|
|
||||||
Serial.printf("[%lu] [XTC] Generating thumb BMP: %dx%d -> %dx%d (scale: %.3f)\n", millis(), pageInfo.width,
|
Serial.printf("[%lu] [XTC] Generating thumb BMP: %dx%d -> %dx%d (scale: %.3f)\n", millis(), pageInfo.width,
|
||||||
pageInfo.height, thumbWidth, thumbHeight, scale);
|
pageInfo.height, targetWidth, HOME_TARGET_HEIGHT);
|
||||||
|
|
||||||
// Allocate buffer for page data
|
// Allocate buffer for page data
|
||||||
size_t bitmapSize;
|
size_t bitmapSize;
|
||||||
@ -385,15 +360,15 @@ bool Xtc::generateThumbBmp() const {
|
|||||||
|
|
||||||
// Create thumbnail BMP file - use 1-bit format for fast home screen rendering (no gray passes)
|
// Create thumbnail BMP file - use 1-bit format for fast home screen rendering (no gray passes)
|
||||||
FsFile thumbBmp;
|
FsFile thumbBmp;
|
||||||
if (!SdMan.openFileForWrite("XTC", getThumbBmpPath(), thumbBmp)) {
|
if (!SdMan.openFileForWrite("XTC", getCoverHomeBmpPath(), thumbBmp)) {
|
||||||
Serial.printf("[%lu] [XTC] Failed to create thumb BMP file\n", millis());
|
Serial.printf("[%lu] [XTC] Failed to create thumb BMP file\n", millis());
|
||||||
free(pageBuffer);
|
free(pageBuffer);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write 1-bit BMP header for fast home screen rendering
|
// Write 1-bit BMP header for fast home screen rendering
|
||||||
const uint32_t rowSize = (thumbWidth + 31) / 32 * 4; // 1 bit per pixel, aligned to 4 bytes
|
const uint32_t rowSize = (targetWidth + 31) / 32 * 4; // 1 bit per pixel, aligned to 4 bytes
|
||||||
const uint32_t imageSize = rowSize * thumbHeight;
|
const uint32_t imageSize = rowSize * HOME_TARGET_HEIGHT;
|
||||||
const uint32_t fileSize = 14 + 40 + 8 + imageSize; // 8 bytes for 2-color palette
|
const uint32_t fileSize = 14 + 40 + 8 + imageSize; // 8 bytes for 2-color palette
|
||||||
|
|
||||||
// File header
|
// File header
|
||||||
@ -408,9 +383,9 @@ bool Xtc::generateThumbBmp() const {
|
|||||||
// DIB header
|
// DIB header
|
||||||
uint32_t dibHeaderSize = 40;
|
uint32_t dibHeaderSize = 40;
|
||||||
thumbBmp.write(reinterpret_cast<const uint8_t*>(&dibHeaderSize), 4);
|
thumbBmp.write(reinterpret_cast<const uint8_t*>(&dibHeaderSize), 4);
|
||||||
int32_t widthVal = thumbWidth;
|
int32_t widthVal = targetWidth;
|
||||||
thumbBmp.write(reinterpret_cast<const uint8_t*>(&widthVal), 4);
|
thumbBmp.write(reinterpret_cast<const uint8_t*>(&widthVal), 4);
|
||||||
int32_t heightVal = -static_cast<int32_t>(thumbHeight); // Negative for top-down
|
int32_t heightVal = -static_cast<int32_t>(HOME_TARGET_HEIGHT); // Negative for top-down
|
||||||
thumbBmp.write(reinterpret_cast<const uint8_t*>(&heightVal), 4);
|
thumbBmp.write(reinterpret_cast<const uint8_t*>(&heightVal), 4);
|
||||||
uint16_t planes = 1;
|
uint16_t planes = 1;
|
||||||
thumbBmp.write(reinterpret_cast<const uint8_t*>(&planes), 2);
|
thumbBmp.write(reinterpret_cast<const uint8_t*>(&planes), 2);
|
||||||
@ -443,8 +418,8 @@ bool Xtc::generateThumbBmp() const {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fixed-point scale factor (16.16)
|
// Fixed-point scale factor (16.16) - scale to fit 400px height
|
||||||
uint32_t scaleInv_fp = static_cast<uint32_t>(65536.0f / scale);
|
uint32_t scaleInv_fp = static_cast<uint32_t>(65536.0f * static_cast<float>(pageInfo.height) / HOME_TARGET_HEIGHT);
|
||||||
|
|
||||||
// Pre-calculate plane info for 2-bit mode
|
// Pre-calculate plane info for 2-bit mode
|
||||||
const size_t planeSize = (bitDepth == 2) ? ((static_cast<size_t>(pageInfo.width) * pageInfo.height + 7) / 8) : 0;
|
const size_t planeSize = (bitDepth == 2) ? ((static_cast<size_t>(pageInfo.width) * pageInfo.height + 7) / 8) : 0;
|
||||||
@ -453,7 +428,7 @@ bool Xtc::generateThumbBmp() const {
|
|||||||
const size_t colBytes = (bitDepth == 2) ? ((pageInfo.height + 7) / 8) : 0;
|
const size_t colBytes = (bitDepth == 2) ? ((pageInfo.height + 7) / 8) : 0;
|
||||||
const size_t srcRowBytes = (bitDepth == 1) ? ((pageInfo.width + 7) / 8) : 0;
|
const size_t srcRowBytes = (bitDepth == 1) ? ((pageInfo.width + 7) / 8) : 0;
|
||||||
|
|
||||||
for (uint16_t dstY = 0; dstY < thumbHeight; dstY++) {
|
for (uint16_t dstY = 0; dstY < HOME_TARGET_HEIGHT; dstY++) {
|
||||||
memset(rowBuffer, 0xFF, rowSize); // Start with all white (bit 1)
|
memset(rowBuffer, 0xFF, rowSize); // Start with all white (bit 1)
|
||||||
|
|
||||||
// Calculate source Y range with bounds checking
|
// Calculate source Y range with bounds checking
|
||||||
@ -464,7 +439,7 @@ bool Xtc::generateThumbBmp() const {
|
|||||||
if (srcYEnd <= srcYStart) srcYEnd = srcYStart + 1;
|
if (srcYEnd <= srcYStart) srcYEnd = srcYStart + 1;
|
||||||
if (srcYEnd > pageInfo.height) srcYEnd = pageInfo.height;
|
if (srcYEnd > pageInfo.height) srcYEnd = pageInfo.height;
|
||||||
|
|
||||||
for (uint16_t dstX = 0; dstX < thumbWidth; dstX++) {
|
for (uint16_t dstX = 0; dstX < targetWidth; dstX++) {
|
||||||
// Calculate source X range with bounds checking
|
// Calculate source X range with bounds checking
|
||||||
uint32_t srcXStart = (static_cast<uint32_t>(dstX) * scaleInv_fp) >> 16;
|
uint32_t srcXStart = (static_cast<uint32_t>(dstX) * scaleInv_fp) >> 16;
|
||||||
uint32_t srcXEnd = (static_cast<uint32_t>(dstX + 1) * scaleInv_fp) >> 16;
|
uint32_t srcXEnd = (static_cast<uint32_t>(dstX + 1) * scaleInv_fp) >> 16;
|
||||||
@ -549,8 +524,8 @@ bool Xtc::generateThumbBmp() const {
|
|||||||
thumbBmp.close();
|
thumbBmp.close();
|
||||||
free(pageBuffer);
|
free(pageBuffer);
|
||||||
|
|
||||||
Serial.printf("[%lu] [XTC] Generated thumb BMP (%dx%d): %s\n", millis(), thumbWidth, thumbHeight,
|
Serial.printf("[%lu] [XTC] Generated home BMP (%dx%d): %s\n", millis(), targetWidth, HOME_TARGET_HEIGHT,
|
||||||
getThumbBmpPath().c_str());
|
getCoverHomeBmpPath().c_str());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -62,9 +62,9 @@ class Xtc {
|
|||||||
// Cover image support (for sleep screen)
|
// Cover image support (for sleep screen)
|
||||||
std::string getCoverBmpPath() const;
|
std::string getCoverBmpPath() const;
|
||||||
bool generateCoverBmp() const;
|
bool generateCoverBmp() const;
|
||||||
// Thumbnail support (for Continue Reading card)
|
// Home screen support (optimized 400px height covers for Continue Reading card)
|
||||||
std::string getThumbBmpPath() const;
|
std::string getCoverHomeBmpPath() const;
|
||||||
bool generateThumbBmp() const;
|
bool generateCoverHomeBmp() const;
|
||||||
|
|
||||||
// Page access
|
// Page access
|
||||||
uint32_t getPageCount() const;
|
uint32_t getPageCount() const;
|
||||||
|
|||||||
@ -59,8 +59,8 @@ void HomeActivity::onEnter() {
|
|||||||
lastBookAuthor = std::string(epub.getAuthor());
|
lastBookAuthor = std::string(epub.getAuthor());
|
||||||
}
|
}
|
||||||
// Try to generate thumbnail image for Continue Reading card
|
// Try to generate thumbnail image for Continue Reading card
|
||||||
if (epub.generateThumbBmp()) {
|
if (epub.generateCoverHomeBmp()) {
|
||||||
coverBmpPath = epub.getThumbBmpPath();
|
coverBmpPath = epub.getCoverHomeBmpPath();
|
||||||
hasCoverImage = true;
|
hasCoverImage = true;
|
||||||
}
|
}
|
||||||
} else if (StringUtils::checkFileExtension(lastBookTitle, ".xtch") ||
|
} else if (StringUtils::checkFileExtension(lastBookTitle, ".xtch") ||
|
||||||
@ -72,8 +72,8 @@ void HomeActivity::onEnter() {
|
|||||||
lastBookTitle = std::string(xtc.getTitle());
|
lastBookTitle = std::string(xtc.getTitle());
|
||||||
}
|
}
|
||||||
// Try to generate thumbnail image for Continue Reading card
|
// Try to generate thumbnail image for Continue Reading card
|
||||||
if (xtc.generateThumbBmp()) {
|
if (xtc.generateCoverHomeBmp()) {
|
||||||
coverBmpPath = xtc.getThumbBmpPath();
|
coverBmpPath = xtc.getCoverHomeBmpPath();
|
||||||
hasCoverImage = true;
|
hasCoverImage = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -220,10 +220,32 @@ void HomeActivity::render() {
|
|||||||
constexpr int bottomMargin = 60;
|
constexpr int bottomMargin = 60;
|
||||||
|
|
||||||
// --- Top "book" card for the current title (selectorIndex == 0) ---
|
// --- Top "book" card for the current title (selectorIndex == 0) ---
|
||||||
const int bookWidth = pageWidth / 2;
|
// Load cover image to get its dimensions
|
||||||
const int bookHeight = pageHeight / 2;
|
int coverWidth = 0;
|
||||||
const int bookX = (pageWidth - bookWidth) / 2;
|
int coverHeight = 0;
|
||||||
|
if (hasContinueReading && hasCoverImage && !coverBmpPath.empty()) {
|
||||||
|
FsFile coverFile;
|
||||||
|
if (SdMan.openFileForRead("HOME", coverBmpPath, coverFile)) {
|
||||||
|
Bitmap testBitmap(coverFile);
|
||||||
|
if (testBitmap.parseHeaders() == BmpReaderError::Ok) {
|
||||||
|
coverWidth = testBitmap.getWidth();
|
||||||
|
coverHeight = testBitmap.getHeight();
|
||||||
|
}
|
||||||
|
coverFile.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate card dimensions based on cover image
|
||||||
|
// Use 400px height as specified, with proportional width
|
||||||
|
constexpr int CARD_HEIGHT = 400;
|
||||||
|
const int cardWidth = (coverWidth > 0 && coverHeight > 0)
|
||||||
|
? (CARD_HEIGHT * coverWidth) / coverHeight
|
||||||
|
: 240; // Fallback to 240px width if no image (maintain aspect ratio)
|
||||||
|
|
||||||
|
const int bookX = (pageWidth - cardWidth) / 2;
|
||||||
constexpr int bookY = 30;
|
constexpr int bookY = 30;
|
||||||
|
const int bookWidth = cardWidth;
|
||||||
|
const int bookHeight = CARD_HEIGHT;
|
||||||
const bool bookSelected = hasContinueReading && selectorIndex == 0;
|
const bool bookSelected = hasContinueReading && selectorIndex == 0;
|
||||||
|
|
||||||
// Bookmark dimensions (used in multiple places)
|
// Bookmark dimensions (used in multiple places)
|
||||||
@ -242,27 +264,9 @@ void HomeActivity::render() {
|
|||||||
if (SdMan.openFileForRead("HOME", coverBmpPath, file)) {
|
if (SdMan.openFileForRead("HOME", coverBmpPath, file)) {
|
||||||
Bitmap bitmap(file);
|
Bitmap bitmap(file);
|
||||||
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
|
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
|
||||||
// Calculate position to center image within the book card
|
// Since the book card already has the exact same size as the image,
|
||||||
int coverX, coverY;
|
// we can draw it at the same position with the same dimensions
|
||||||
|
renderer.drawBitmap(bitmap, bookX, bookY, bookWidth, bookHeight);
|
||||||
if (bitmap.getWidth() > bookWidth || bitmap.getHeight() > bookHeight) {
|
|
||||||
const float imgRatio = static_cast<float>(bitmap.getWidth()) / static_cast<float>(bitmap.getHeight());
|
|
||||||
const float boxRatio = static_cast<float>(bookWidth) / static_cast<float>(bookHeight);
|
|
||||||
|
|
||||||
if (imgRatio > boxRatio) {
|
|
||||||
coverX = bookX;
|
|
||||||
coverY = bookY + (bookHeight - static_cast<int>(bookWidth / imgRatio)) / 2;
|
|
||||||
} else {
|
|
||||||
coverX = bookX + (bookWidth - static_cast<int>(bookHeight * imgRatio)) / 2;
|
|
||||||
coverY = bookY;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
coverX = bookX + (bookWidth - bitmap.getWidth()) / 2;
|
|
||||||
coverY = bookY + (bookHeight - bitmap.getHeight()) / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw the cover image centered within the book card
|
|
||||||
renderer.drawBitmap(bitmap, coverX, coverY, bookWidth, bookHeight);
|
|
||||||
|
|
||||||
// Draw border around the card
|
// Draw border around the card
|
||||||
renderer.drawRect(bookX, bookY, bookWidth, bookHeight);
|
renderer.drawRect(bookX, bookY, bookWidth, bookHeight);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user