mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-06 15:47:39 +03:00
new thumb cache
This commit is contained in:
parent
3ec8c1f23a
commit
fc44b8f9ed
@ -419,11 +419,13 @@ bool Epub::generateCoverBmp(bool cropped) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string Epub::getThumbBmpPath() const { return cachePath + "/thumb.bmp"; }
|
||||
std::string Epub::getThumbBmpPath(int width, int height) const {
|
||||
return cachePath + "/thumb_" + std::to_string(width) + "x" + std::to_string(height) + ".bmp";
|
||||
}
|
||||
|
||||
bool Epub::generateThumbBmp() const {
|
||||
bool Epub::generateThumbBmp(int width, int height) const {
|
||||
// Already generated, return true
|
||||
if (SdMan.exists(getThumbBmpPath().c_str())) {
|
||||
if (SdMan.exists(getThumbBmpPath(width, height).c_str())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -455,23 +457,21 @@ bool Epub::generateThumbBmp() const {
|
||||
}
|
||||
|
||||
FsFile thumbBmp;
|
||||
if (!SdMan.openFileForWrite("EBP", getThumbBmpPath(), thumbBmp)) {
|
||||
if (!SdMan.openFileForWrite("EBP", getThumbBmpPath(width, height), thumbBmp)) {
|
||||
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;
|
||||
const bool success = JpegToBmpConverter::jpegFileTo1BitBmpStreamWithSize(coverJpg, thumbBmp, THUMB_TARGET_WIDTH,
|
||||
THUMB_TARGET_HEIGHT);
|
||||
const bool success = JpegToBmpConverter::jpegFileTo1BitBmpStreamWithSize(coverJpg, thumbBmp, width,
|
||||
height);
|
||||
coverJpg.close();
|
||||
thumbBmp.close();
|
||||
SdMan.remove(coverJpgTempPath.c_str());
|
||||
|
||||
if (!success) {
|
||||
Serial.printf("[%lu] [EBP] Failed to generate thumb BMP from JPG cover image\n", millis());
|
||||
SdMan.remove(getThumbBmpPath().c_str());
|
||||
SdMan.remove(getThumbBmpPath(width, height).c_str());
|
||||
}
|
||||
Serial.printf("[%lu] [EBP] Generated thumb BMP from JPG cover image, success: %s\n", millis(),
|
||||
success ? "yes" : "no");
|
||||
|
||||
@ -47,8 +47,8 @@ class Epub {
|
||||
const std::string& getLanguage() const;
|
||||
std::string getCoverBmpPath(bool cropped = false) const;
|
||||
bool generateCoverBmp(bool cropped = false) const;
|
||||
std::string getThumbBmpPath() const;
|
||||
bool generateThumbBmp() const;
|
||||
std::string getThumbBmpPath(int width, int height) const;
|
||||
bool generateThumbBmp(int width, int height) const;
|
||||
uint8_t* readItemContentsToBytes(const std::string& itemHref, size_t* size = nullptr,
|
||||
bool trailingNullByte = false) const;
|
||||
bool readItemContentsToStream(const std::string& itemHref, Print& out, size_t chunkSize) const;
|
||||
|
||||
@ -293,11 +293,13 @@ bool Xtc::generateCoverBmp() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string Xtc::getThumbBmpPath() const { return cachePath + "/thumb.bmp"; }
|
||||
std::string Xtc::getThumbBmpPath(int width, int height) const {
|
||||
return cachePath + "/thumb_" + std::to_string(width) + "x" + std::to_string(height) + ".bmp";
|
||||
}
|
||||
|
||||
bool Xtc::generateThumbBmp() const {
|
||||
bool Xtc::generateThumbBmp(int width, int height) const {
|
||||
// Already generated
|
||||
if (SdMan.exists(getThumbBmpPath().c_str())) {
|
||||
if (SdMan.exists(getThumbBmpPath(width, height).c_str())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -324,41 +326,25 @@ bool Xtc::generateThumbBmp() const {
|
||||
// Get bit depth
|
||||
const uint8_t bitDepth = parser->getBitDepth();
|
||||
|
||||
// Calculate target dimensions for thumbnail (fit within 240x400 Continue Reading card)
|
||||
constexpr int THUMB_TARGET_WIDTH = 240;
|
||||
constexpr int THUMB_TARGET_HEIGHT = 400;
|
||||
// Calculate target dimensions for thumbnail
|
||||
uint16_t thumbWidth = width;
|
||||
uint16_t thumbHeight = height;
|
||||
|
||||
// Calculate scale factor
|
||||
float scaleX = static_cast<float>(THUMB_TARGET_WIDTH) / pageInfo.width;
|
||||
float scaleY = static_cast<float>(THUMB_TARGET_HEIGHT) / pageInfo.height;
|
||||
float scaleX = static_cast<float>(thumbWidth) / pageInfo.width;
|
||||
float scaleY = static_cast<float>(thumbHeight) / pageInfo.height;
|
||||
float scale = (scaleX < scaleY) ? scaleX : scaleY;
|
||||
|
||||
// Only scale down, never up
|
||||
// If we are scaling up, we should adjust thumbWidth and thumbHeight
|
||||
// to maintain aspect ratio, but not exceed the given width and height.
|
||||
if (scale >= 1.0f) {
|
||||
// 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;
|
||||
thumbWidth = pageInfo.width;
|
||||
thumbHeight = pageInfo.height;
|
||||
scale = 1.0f;
|
||||
} else {
|
||||
thumbWidth = static_cast<uint16_t>(pageInfo.width * scale);
|
||||
thumbHeight = static_cast<uint16_t>(pageInfo.height * scale);
|
||||
}
|
||||
|
||||
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,
|
||||
pageInfo.height, thumbWidth, thumbHeight, scale);
|
||||
|
||||
@ -385,7 +371,7 @@ bool Xtc::generateThumbBmp() const {
|
||||
|
||||
// Create thumbnail BMP file - use 1-bit format for fast home screen rendering (no gray passes)
|
||||
FsFile thumbBmp;
|
||||
if (!SdMan.openFileForWrite("XTC", getThumbBmpPath(), thumbBmp)) {
|
||||
if (!SdMan.openFileForWrite("XTC", getThumbBmpPath(width, height), thumbBmp)) {
|
||||
Serial.printf("[%lu] [XTC] Failed to create thumb BMP file\n", millis());
|
||||
free(pageBuffer);
|
||||
return false;
|
||||
@ -550,7 +536,7 @@ bool Xtc::generateThumbBmp() const {
|
||||
free(pageBuffer);
|
||||
|
||||
Serial.printf("[%lu] [XTC] Generated thumb BMP (%dx%d): %s\n", millis(), thumbWidth, thumbHeight,
|
||||
getThumbBmpPath().c_str());
|
||||
getThumbBmpPath(width, height).c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -63,8 +63,8 @@ class Xtc {
|
||||
std::string getCoverBmpPath() const;
|
||||
bool generateCoverBmp() const;
|
||||
// Thumbnail support (for Continue Reading card)
|
||||
std::string getThumbBmpPath() const;
|
||||
bool generateThumbBmp() const;
|
||||
std::string getThumbBmpPath(int width, int height) const;
|
||||
bool generateThumbBmp(int width, int height) const;
|
||||
|
||||
// Page access
|
||||
uint32_t getPageCount() const;
|
||||
|
||||
85
src/activities/home/BookCoverCache.cpp
Normal file
85
src/activities/home/BookCoverCache.cpp
Normal file
@ -0,0 +1,85 @@
|
||||
#include "BookCoverCache.h"
|
||||
#include <Epub.h>
|
||||
#include <Xtc.h>
|
||||
#include "util/StringUtils.h"
|
||||
#include <sys/stat.h>
|
||||
|
||||
BookCoverCache::BookCoverCache(const std::string& cache_dir) : cache_dir(cache_dir) {
|
||||
// Ensure the cache directory exists
|
||||
if (!SdMan.exists(cache_dir.c_str())) {
|
||||
SdMan.mkdir(cache_dir.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
BookCoverCache::~BookCoverCache() {
|
||||
}
|
||||
|
||||
std::shared_ptr<Bitmap> BookCoverCache::getCover(const std::string& book_path) {
|
||||
std::string cache_path = getCachePath(book_path);
|
||||
|
||||
if (isCacheValid(book_path)) {
|
||||
FsFile file;
|
||||
if (SdMan.openFileForRead("CACHE", cache_path, file)) {
|
||||
auto bitmap = std::make_shared<Bitmap>(file);
|
||||
if (bitmap->parseHeaders() == BmpReaderError::Ok) {
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return generateThumbnail(book_path);
|
||||
}
|
||||
|
||||
void BookCoverCache::clearCache() {
|
||||
// TODO: Implement this
|
||||
}
|
||||
|
||||
std::string BookCoverCache::getCachePath(const std::string& book_path) const {
|
||||
// Simple cache path: replace '/' with '_' and append .bmp
|
||||
std::string safe_path = book_path;
|
||||
std::replace(safe_path.begin(), safe_path.end(), '/', '_');
|
||||
return cache_dir + "/" + safe_path + ".bmp";
|
||||
}
|
||||
|
||||
bool BookCoverCache::isCacheValid(const std::string& book_path) const {
|
||||
std::string cache_path = getCachePath(book_path);
|
||||
return SdMan.exists(cache_path.c_str());
|
||||
}
|
||||
|
||||
std::shared_ptr<Bitmap> BookCoverCache::generateThumbnail(const std::string& book_path) {
|
||||
std::string coverBmpPath;
|
||||
bool hasCoverImage = false;
|
||||
|
||||
if (StringUtils::checkFileExtension(book_path, ".epub")) {
|
||||
Epub epub(book_path, "/.crosspoint/epub_cache");
|
||||
if (epub.load(false) && epub.generateThumbBmp()) {
|
||||
coverBmpPath = epub.getThumbBmpPath();
|
||||
hasCoverImage = true;
|
||||
}
|
||||
} else if (StringUtils::checkFileExtension(book_path, ".xtch") ||
|
||||
StringUtils::checkFileExtension(book_path, ".xtc")) {
|
||||
Xtc xtc(book_path, "/.crosspoint/xtc_cache");
|
||||
if (xtc.load() && xtc.generateThumbBmp()) {
|
||||
coverBmpPath = xtc.getThumbBmpPath();
|
||||
hasCoverImage = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasCoverImage && !coverBmpPath.empty()) {
|
||||
std::string cache_path = getCachePath(book_path);
|
||||
// This is not very efficient, we are copying the file.
|
||||
// We should ideally generate the thumbnail directly to the cache path.
|
||||
// For now, this will do.
|
||||
if(SdMan.copy(coverBmpPath.c_str(), cache_path.c_str())) {
|
||||
FsFile file;
|
||||
if (SdMan.openFileForRead("CACHE", cache_path, file)) {
|
||||
auto bitmap = std::make_shared<Bitmap>(file);
|
||||
if (bitmap->parseHeaders() == BmpReaderError::Ok) {
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
27
src/activities/home/BookCoverCache.h
Normal file
27
src/activities/home/BookCoverCache.h
Normal file
@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <Bitmap.h>
|
||||
#include <SDCardManager.h>
|
||||
#include <GfxRenderer.h>
|
||||
|
||||
class BookCoverCache {
|
||||
public:
|
||||
BookCoverCache(const std::string& cache_dir, GfxRenderer* renderer);
|
||||
~BookCoverCache();
|
||||
|
||||
bool render(const std::string& book_path, int x, int y, int width, int height);
|
||||
void clearCache();
|
||||
void setTargetSize(int width, int height);
|
||||
|
||||
private:
|
||||
std::string getCachePath(const std::string& book_path) const;
|
||||
bool generateThumbnail(const std::string& book_path);
|
||||
|
||||
std::string cache_dir;
|
||||
GfxRenderer* renderer;
|
||||
int target_width;
|
||||
int target_height;
|
||||
};
|
||||
@ -466,15 +466,15 @@ void MyLibraryActivity::renderRecentAsBookCoverList() const {
|
||||
|
||||
if (StringUtils::checkFileExtension(book.path, ".epub")) {
|
||||
Epub epub(book.path, "/.crosspoint");
|
||||
if (epub.load(false) && epub.generateThumbBmp()) {
|
||||
coverBmpPath = epub.getThumbBmpPath();
|
||||
if (epub.load(false) && epub.generateThumbBmp(coverWidth, itemHeight - 10)) {
|
||||
coverBmpPath = epub.getThumbBmpPath(coverWidth, itemHeight - 10);
|
||||
hasCoverImage = true;
|
||||
}
|
||||
} else if (StringUtils::checkFileExtension(book.path, ".xtch") ||
|
||||
StringUtils::checkFileExtension(book.path, ".xtc")) {
|
||||
Xtc xtc(book.path, "/.crosspoint");
|
||||
if (xtc.load() && xtc.generateThumbBmp()) {
|
||||
coverBmpPath = xtc.getThumbBmpPath();
|
||||
if (xtc.load() && xtc.generateThumbBmp(coverWidth, itemHeight - 10)) {
|
||||
coverBmpPath = xtc.getThumbBmpPath(coverWidth, itemHeight - 10);
|
||||
hasCoverImage = true;
|
||||
}
|
||||
}
|
||||
@ -554,15 +554,15 @@ void MyLibraryActivity::renderRecentAsBookCoverGrid() const {
|
||||
|
||||
if (StringUtils::checkFileExtension(book.path, ".epub")) {
|
||||
Epub epub(book.path, "/.crosspoint");
|
||||
if (epub.load(false) && epub.generateThumbBmp()) {
|
||||
coverBmpPath = epub.getThumbBmpPath();
|
||||
if (epub.load(false) && epub.generateThumbBmp(itemWidth, itemHeight)) {
|
||||
coverBmpPath = epub.getThumbBmpPath(itemWidth, itemHeight);
|
||||
hasCoverImage = true;
|
||||
}
|
||||
} else if (StringUtils::checkFileExtension(book.path, ".xtch") ||
|
||||
StringUtils::checkFileExtension(book.path, ".xtc")) {
|
||||
Xtc xtc(book.path, "/.crosspoint");
|
||||
if (xtc.load() && xtc.generateThumbBmp()) {
|
||||
coverBmpPath = xtc.getThumbBmpPath();
|
||||
if (xtc.load() && xtc.generateThumbBmp(itemWidth, itemHeight)) {
|
||||
coverBmpPath = xtc.getThumbBmpPath(itemWidth, itemHeight);
|
||||
hasCoverImage = true;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user