mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-07 08:07:40 +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;
|
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
|
// Already generated, return true
|
||||||
if (SdMan.exists(getThumbBmpPath().c_str())) {
|
if (SdMan.exists(getThumbBmpPath(width, height).c_str())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -455,23 +457,21 @@ bool Epub::generateThumbBmp() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
FsFile thumbBmp;
|
FsFile thumbBmp;
|
||||||
if (!SdMan.openFileForWrite("EBP", getThumbBmpPath(), thumbBmp)) {
|
if (!SdMan.openFileForWrite("EBP", getThumbBmpPath(width, height), thumbBmp)) {
|
||||||
coverJpg.close();
|
coverJpg.close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Use smaller target size for Continue Reading card (half of screen: 240x400)
|
// 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)
|
// Generate 1-bit BMP for fast home screen rendering (no gray passes needed)
|
||||||
constexpr int THUMB_TARGET_WIDTH = 240;
|
const bool success = JpegToBmpConverter::jpegFileTo1BitBmpStreamWithSize(coverJpg, thumbBmp, width,
|
||||||
constexpr int THUMB_TARGET_HEIGHT = 400;
|
height);
|
||||||
const bool success = JpegToBmpConverter::jpegFileTo1BitBmpStreamWithSize(coverJpg, thumbBmp, THUMB_TARGET_WIDTH,
|
|
||||||
THUMB_TARGET_HEIGHT);
|
|
||||||
coverJpg.close();
|
coverJpg.close();
|
||||||
thumbBmp.close();
|
thumbBmp.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 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(),
|
Serial.printf("[%lu] [EBP] Generated thumb BMP from JPG cover image, success: %s\n", millis(),
|
||||||
success ? "yes" : "no");
|
success ? "yes" : "no");
|
||||||
|
|||||||
@ -47,8 +47,8 @@ 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;
|
std::string getThumbBmpPath(int width, int height) const;
|
||||||
bool generateThumbBmp() const;
|
bool generateThumbBmp(int width, int height) 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;
|
||||||
|
|||||||
@ -293,11 +293,13 @@ bool Xtc::generateCoverBmp() const {
|
|||||||
return true;
|
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
|
// Already generated
|
||||||
if (SdMan.exists(getThumbBmpPath().c_str())) {
|
if (SdMan.exists(getThumbBmpPath(width, height).c_str())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,40 +326,24 @@ 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)
|
// Calculate target dimensions for thumbnail
|
||||||
constexpr int THUMB_TARGET_WIDTH = 240;
|
uint16_t thumbWidth = width;
|
||||||
constexpr int THUMB_TARGET_HEIGHT = 400;
|
uint16_t thumbHeight = height;
|
||||||
|
|
||||||
// Calculate scale factor
|
float scaleX = static_cast<float>(thumbWidth) / pageInfo.width;
|
||||||
float scaleX = static_cast<float>(THUMB_TARGET_WIDTH) / pageInfo.width;
|
float scaleY = static_cast<float>(thumbHeight) / pageInfo.height;
|
||||||
float scaleY = static_cast<float>(THUMB_TARGET_HEIGHT) / pageInfo.height;
|
|
||||||
float scale = (scaleX < scaleY) ? scaleX : scaleY;
|
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) {
|
if (scale >= 1.0f) {
|
||||||
// Page is already small enough, just use cover.bmp
|
thumbWidth = pageInfo.width;
|
||||||
// Copy cover.bmp to thumb.bmp
|
thumbHeight = pageInfo.height;
|
||||||
if (generateCoverBmp()) {
|
scale = 1.0f;
|
||||||
FsFile src, dst;
|
} else {
|
||||||
if (SdMan.openFileForRead("XTC", getCoverBmpPath(), src)) {
|
thumbWidth = static_cast<uint16_t>(pageInfo.width * scale);
|
||||||
if (SdMan.openFileForWrite("XTC", getThumbBmpPath(), dst)) {
|
thumbHeight = static_cast<uint16_t>(pageInfo.height * scale);
|
||||||
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, 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)
|
// 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", getThumbBmpPath(width, height), 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;
|
||||||
@ -550,7 +536,7 @@ bool Xtc::generateThumbBmp() const {
|
|||||||
free(pageBuffer);
|
free(pageBuffer);
|
||||||
|
|
||||||
Serial.printf("[%lu] [XTC] Generated thumb BMP (%dx%d): %s\n", millis(), thumbWidth, thumbHeight,
|
Serial.printf("[%lu] [XTC] Generated thumb BMP (%dx%d): %s\n", millis(), thumbWidth, thumbHeight,
|
||||||
getThumbBmpPath().c_str());
|
getThumbBmpPath(width, height).c_str());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -63,8 +63,8 @@ class Xtc {
|
|||||||
std::string getCoverBmpPath() const;
|
std::string getCoverBmpPath() const;
|
||||||
bool generateCoverBmp() const;
|
bool generateCoverBmp() const;
|
||||||
// Thumbnail support (for Continue Reading card)
|
// Thumbnail support (for Continue Reading card)
|
||||||
std::string getThumbBmpPath() const;
|
std::string getThumbBmpPath(int width, int height) const;
|
||||||
bool generateThumbBmp() const;
|
bool generateThumbBmp(int width, int height) const;
|
||||||
|
|
||||||
// Page access
|
// Page access
|
||||||
uint32_t getPageCount() const;
|
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")) {
|
if (StringUtils::checkFileExtension(book.path, ".epub")) {
|
||||||
Epub epub(book.path, "/.crosspoint");
|
Epub epub(book.path, "/.crosspoint");
|
||||||
if (epub.load(false) && epub.generateThumbBmp()) {
|
if (epub.load(false) && epub.generateThumbBmp(coverWidth, itemHeight - 10)) {
|
||||||
coverBmpPath = epub.getThumbBmpPath();
|
coverBmpPath = epub.getThumbBmpPath(coverWidth, itemHeight - 10);
|
||||||
hasCoverImage = true;
|
hasCoverImage = true;
|
||||||
}
|
}
|
||||||
} else if (StringUtils::checkFileExtension(book.path, ".xtch") ||
|
} else if (StringUtils::checkFileExtension(book.path, ".xtch") ||
|
||||||
StringUtils::checkFileExtension(book.path, ".xtc")) {
|
StringUtils::checkFileExtension(book.path, ".xtc")) {
|
||||||
Xtc xtc(book.path, "/.crosspoint");
|
Xtc xtc(book.path, "/.crosspoint");
|
||||||
if (xtc.load() && xtc.generateThumbBmp()) {
|
if (xtc.load() && xtc.generateThumbBmp(coverWidth, itemHeight - 10)) {
|
||||||
coverBmpPath = xtc.getThumbBmpPath();
|
coverBmpPath = xtc.getThumbBmpPath(coverWidth, itemHeight - 10);
|
||||||
hasCoverImage = true;
|
hasCoverImage = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -554,15 +554,15 @@ void MyLibraryActivity::renderRecentAsBookCoverGrid() const {
|
|||||||
|
|
||||||
if (StringUtils::checkFileExtension(book.path, ".epub")) {
|
if (StringUtils::checkFileExtension(book.path, ".epub")) {
|
||||||
Epub epub(book.path, "/.crosspoint");
|
Epub epub(book.path, "/.crosspoint");
|
||||||
if (epub.load(false) && epub.generateThumbBmp()) {
|
if (epub.load(false) && epub.generateThumbBmp(itemWidth, itemHeight)) {
|
||||||
coverBmpPath = epub.getThumbBmpPath();
|
coverBmpPath = epub.getThumbBmpPath(itemWidth, itemHeight);
|
||||||
hasCoverImage = true;
|
hasCoverImage = true;
|
||||||
}
|
}
|
||||||
} else if (StringUtils::checkFileExtension(book.path, ".xtch") ||
|
} else if (StringUtils::checkFileExtension(book.path, ".xtch") ||
|
||||||
StringUtils::checkFileExtension(book.path, ".xtc")) {
|
StringUtils::checkFileExtension(book.path, ".xtc")) {
|
||||||
Xtc xtc(book.path, "/.crosspoint");
|
Xtc xtc(book.path, "/.crosspoint");
|
||||||
if (xtc.load() && xtc.generateThumbBmp()) {
|
if (xtc.load() && xtc.generateThumbBmp(itemWidth, itemHeight)) {
|
||||||
coverBmpPath = xtc.getThumbBmpPath();
|
coverBmpPath = xtc.getThumbBmpPath(itemWidth, itemHeight);
|
||||||
hasCoverImage = true;
|
hasCoverImage = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user