feat: Add placeholder cover generator for books without covers
Generate styled placeholder covers (title, author, book icon) when a book has no embedded cover image, instead of showing a blank rectangle. - Add PlaceholderCoverGenerator lib with 1-bit BMP rendering, scaled fonts, word-wrap, and a book icon bitmap - Integrate as fallback in Epub/Xtc/Txt reader activities and SleepActivity after format-specific cover generation fails - Add fallback in HomeActivity::loadRecentCovers() so the home screen also shows placeholder thumbnails when cache is cleared - Add Txt::getThumbBmpPath() for TXT thumbnail support - Add helper scripts for icon and layout preview generation Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -6,6 +6,8 @@
|
||||
#include <HalStorage.h>
|
||||
#include <Logging.h>
|
||||
|
||||
#include <PlaceholderCoverGenerator.h>
|
||||
|
||||
#include "CrossPointSettings.h"
|
||||
#include "CrossPointState.h"
|
||||
#include "EpubReaderBookmarkSelectionActivity.h"
|
||||
@@ -126,15 +128,31 @@ void EpubReaderActivity::onEnter() {
|
||||
|
||||
if (!Storage.exists(epub->getCoverBmpPath(false).c_str())) {
|
||||
epub->generateCoverBmp(false);
|
||||
// Fallback: generate placeholder if real cover extraction failed
|
||||
if (!Storage.exists(epub->getCoverBmpPath(false).c_str())) {
|
||||
PlaceholderCoverGenerator::generate(epub->getCoverBmpPath(false), epub->getTitle(), epub->getAuthor(), 480,
|
||||
800);
|
||||
}
|
||||
updateProgress();
|
||||
}
|
||||
if (!Storage.exists(epub->getCoverBmpPath(true).c_str())) {
|
||||
epub->generateCoverBmp(true);
|
||||
if (!Storage.exists(epub->getCoverBmpPath(true).c_str())) {
|
||||
PlaceholderCoverGenerator::generate(epub->getCoverBmpPath(true), epub->getTitle(), epub->getAuthor(), 480,
|
||||
800);
|
||||
}
|
||||
updateProgress();
|
||||
}
|
||||
for (int i = 0; i < PRERENDER_THUMB_HEIGHTS_COUNT; i++) {
|
||||
if (!Storage.exists(epub->getThumbBmpPath(PRERENDER_THUMB_HEIGHTS[i]).c_str())) {
|
||||
epub->generateThumbBmp(PRERENDER_THUMB_HEIGHTS[i]);
|
||||
// Fallback: generate placeholder thumbnail
|
||||
if (!Storage.exists(epub->getThumbBmpPath(PRERENDER_THUMB_HEIGHTS[i]).c_str())) {
|
||||
const int thumbHeight = PRERENDER_THUMB_HEIGHTS[i];
|
||||
const int thumbWidth = static_cast<int>(thumbHeight * 0.6);
|
||||
PlaceholderCoverGenerator::generate(epub->getThumbBmpPath(thumbHeight), epub->getTitle(),
|
||||
epub->getAuthor(), thumbWidth, thumbHeight);
|
||||
}
|
||||
updateProgress();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <Serialization.h>
|
||||
#include <Utf8.h>
|
||||
|
||||
#include <PlaceholderCoverGenerator.h>
|
||||
|
||||
#include "CrossPointSettings.h"
|
||||
#include "CrossPointState.h"
|
||||
#include "MappedInputManager.h"
|
||||
@@ -57,13 +59,43 @@ void TxtReaderActivity::onEnter() {
|
||||
|
||||
txt->setupCacheDir();
|
||||
|
||||
// Prerender cover on first open so the Sleep screen is instant.
|
||||
// generateCoverBmp() is a no-op if the file already exists, so this only does work once.
|
||||
// TXT has no thumbnail support, so only the sleep screen cover is generated.
|
||||
if (!Storage.exists(txt->getCoverBmpPath().c_str())) {
|
||||
Rect popupRect = GUI.drawPopup(renderer, "Preparing book...");
|
||||
txt->generateCoverBmp();
|
||||
GUI.fillPopupProgress(renderer, popupRect, 100);
|
||||
// Prerender covers and thumbnails on first open so Home and Sleep screens are instant.
|
||||
// Each generate* call is a no-op if the file already exists, so this only does work once.
|
||||
{
|
||||
int totalSteps = 0;
|
||||
if (!Storage.exists(txt->getCoverBmpPath().c_str())) totalSteps++;
|
||||
for (int i = 0; i < PRERENDER_THUMB_HEIGHTS_COUNT; i++) {
|
||||
if (!Storage.exists(txt->getThumbBmpPath(PRERENDER_THUMB_HEIGHTS[i]).c_str())) totalSteps++;
|
||||
}
|
||||
|
||||
if (totalSteps > 0) {
|
||||
Rect popupRect = GUI.drawPopup(renderer, "Preparing book...");
|
||||
int completedSteps = 0;
|
||||
|
||||
auto updateProgress = [&]() {
|
||||
completedSteps++;
|
||||
GUI.fillPopupProgress(renderer, popupRect, completedSteps * 100 / totalSteps);
|
||||
};
|
||||
|
||||
if (!Storage.exists(txt->getCoverBmpPath().c_str())) {
|
||||
const bool coverGenerated = txt->generateCoverBmp();
|
||||
// Fallback: generate placeholder if no cover image was found
|
||||
if (!coverGenerated) {
|
||||
PlaceholderCoverGenerator::generate(txt->getCoverBmpPath(), txt->getTitle(), "", 480, 800);
|
||||
}
|
||||
updateProgress();
|
||||
}
|
||||
for (int i = 0; i < PRERENDER_THUMB_HEIGHTS_COUNT; i++) {
|
||||
if (!Storage.exists(txt->getThumbBmpPath(PRERENDER_THUMB_HEIGHTS[i]).c_str())) {
|
||||
// TXT has no native thumbnail generation, always use placeholder
|
||||
const int thumbHeight = PRERENDER_THUMB_HEIGHTS[i];
|
||||
const int thumbWidth = static_cast<int>(thumbHeight * 0.6);
|
||||
PlaceholderCoverGenerator::generate(txt->getThumbBmpPath(thumbHeight), txt->getTitle(), "", thumbWidth,
|
||||
thumbHeight);
|
||||
updateProgress();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save current txt as last opened file and add to recent books
|
||||
@@ -71,7 +103,7 @@ void TxtReaderActivity::onEnter() {
|
||||
auto fileName = filePath.substr(filePath.rfind('/') + 1);
|
||||
APP_STATE.openEpubPath = filePath;
|
||||
APP_STATE.saveToFile();
|
||||
RECENT_BOOKS.addBook(filePath, fileName, "", "");
|
||||
RECENT_BOOKS.addBook(filePath, fileName, "", txt->getThumbBmpPath());
|
||||
|
||||
// Trigger first update
|
||||
updateRequired = true;
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#include <GfxRenderer.h>
|
||||
#include <HalStorage.h>
|
||||
|
||||
#include <PlaceholderCoverGenerator.h>
|
||||
|
||||
#include "CrossPointSettings.h"
|
||||
#include "CrossPointState.h"
|
||||
#include "MappedInputManager.h"
|
||||
@@ -63,11 +65,22 @@ void XtcReaderActivity::onEnter() {
|
||||
|
||||
if (!Storage.exists(xtc->getCoverBmpPath().c_str())) {
|
||||
xtc->generateCoverBmp();
|
||||
// Fallback: generate placeholder if first-page cover extraction failed
|
||||
if (!Storage.exists(xtc->getCoverBmpPath().c_str())) {
|
||||
PlaceholderCoverGenerator::generate(xtc->getCoverBmpPath(), xtc->getTitle(), xtc->getAuthor(), 480, 800);
|
||||
}
|
||||
updateProgress();
|
||||
}
|
||||
for (int i = 0; i < PRERENDER_THUMB_HEIGHTS_COUNT; i++) {
|
||||
if (!Storage.exists(xtc->getThumbBmpPath(PRERENDER_THUMB_HEIGHTS[i]).c_str())) {
|
||||
xtc->generateThumbBmp(PRERENDER_THUMB_HEIGHTS[i]);
|
||||
// Fallback: generate placeholder thumbnail
|
||||
if (!Storage.exists(xtc->getThumbBmpPath(PRERENDER_THUMB_HEIGHTS[i]).c_str())) {
|
||||
const int thumbHeight = PRERENDER_THUMB_HEIGHTS[i];
|
||||
const int thumbWidth = static_cast<int>(thumbHeight * 0.6);
|
||||
PlaceholderCoverGenerator::generate(xtc->getThumbBmpPath(thumbHeight), xtc->getTitle(), xtc->getAuthor(),
|
||||
thumbWidth, thumbHeight);
|
||||
}
|
||||
updateProgress();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user