Book cover sleep screen (#89)

## Summary

* Fix issue with 2-bit bmp rendering
* Add support generate book cover BMP from JPG and use as sleep screen

## Additional Context

* It does not support other image formats beyond JPG at this point
* Something is cooked with my JpegToBmpConverter logic, it generates
weird interlaced looking images for some JPGs

| Book 1 | Book 2|
| --- | --- |
|
![IMG_5653](https://github.com/user-attachments/assets/49bbaeaa-b171-44c7-a68d-14cbe42aef03)
|
![IMG_5652](https://github.com/user-attachments/assets/7db88d70-e09a-49b0-a9a0-4cc729b4ca0c)
|
This commit is contained in:
Dave Allie
2025-12-21 18:42:06 +11:00
committed by GitHub
parent 958508eb6b
commit 955c78de64
10 changed files with 182 additions and 71 deletions

View File

@@ -1,6 +1,7 @@
#include "Epub.h"
#include <HardwareSerial.h>
#include <JpegToBmpConverter.h>
#include <SD.h>
#include <ZipFile.h>
@@ -218,7 +219,45 @@ const std::string& Epub::getPath() const { return filepath; }
const std::string& Epub::getTitle() const { return title; }
const std::string& Epub::getCoverImageItem() const { return coverImageItem; }
std::string Epub::getCoverBmpPath() const { return cachePath + "/cover.bmp"; }
bool Epub::generateCoverBmp() const {
// Already generated, return true
if (SD.exists(getCoverBmpPath().c_str())) {
return true;
}
if (coverImageItem.empty()) {
Serial.printf("[%lu] [EBP] No known cover image\n", millis());
return false;
}
if (coverImageItem.substr(coverImageItem.length() - 4) == ".jpg" ||
coverImageItem.substr(coverImageItem.length() - 5) == ".jpeg") {
Serial.printf("[%lu] [EBP] Generating BMP from JPG cover image\n", millis());
File coverJpg = SD.open((getCachePath() + "/.cover.jpg").c_str(), FILE_WRITE, true);
readItemContentsToStream(coverImageItem, coverJpg, 1024);
coverJpg.close();
coverJpg = SD.open((getCachePath() + "/.cover.jpg").c_str(), FILE_READ);
File coverBmp = SD.open(getCoverBmpPath().c_str(), FILE_WRITE, true);
const bool success = JpegToBmpConverter::jpegFileToBmpStream(coverJpg, coverBmp);
coverJpg.close();
coverBmp.close();
SD.remove((getCachePath() + "/.cover.jpg").c_str());
if (!success) {
Serial.printf("[%lu] [EBP] Failed to generate BMP from JPG cover image\n", millis());
SD.remove(getCoverBmpPath().c_str());
}
Serial.printf("[%lu] [EBP] Generated BMP from JPG cover image, success: %s\n", millis(), success ? "yes" : "no");
return success;
} else {
Serial.printf("[%lu] [EBP] Cover image is not a JPG, skipping\n", millis());
}
return false;
}
std::string normalisePath(const std::string& path) {
std::vector<std::string> components;

View File

@@ -48,7 +48,8 @@ class Epub {
const std::string& getCachePath() const;
const std::string& getPath() const;
const std::string& getTitle() const;
const std::string& getCoverImageItem() const;
std::string getCoverBmpPath() const;
bool generateCoverBmp() 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;

View File

@@ -128,7 +128,7 @@ BmpReaderError Bitmap::readRow(uint8_t* data, uint8_t* rowBuffer) const {
int bitShift = 6;
// Helper lambda to pack 2bpp color into the output stream
auto packPixel = [&](uint8_t lum) {
auto packPixel = [&](const uint8_t lum) {
uint8_t color = (lum >> 6); // Simple 2-bit reduction: 0-255 -> 0-3
currentOutByte |= (color << bitShift);
if (bitShift == 0) {
@@ -140,38 +140,49 @@ BmpReaderError Bitmap::readRow(uint8_t* data, uint8_t* rowBuffer) const {
}
};
uint8_t lum;
switch (bpp) {
case 8: {
case 32: {
const uint8_t* p = rowBuffer;
for (int x = 0; x < width; x++) {
packPixel(paletteLum[rowBuffer[x]]);
lum = (77u * p[2] + 150u * p[1] + 29u * p[0]) >> 8;
packPixel(lum);
p += 4;
}
break;
}
case 24: {
const uint8_t* p = rowBuffer;
for (int x = 0; x < width; x++) {
uint8_t lum = (77u * p[2] + 150u * p[1] + 29u * p[0]) >> 8;
lum = (77u * p[2] + 150u * p[1] + 29u * p[0]) >> 8;
packPixel(lum);
p += 3;
}
break;
}
case 8: {
for (int x = 0; x < width; x++) {
packPixel(paletteLum[rowBuffer[x]]);
}
break;
}
case 2: {
for (int x = 0; x < width; x++) {
lum = paletteLum[(rowBuffer[x >> 2] >> (6 - ((x & 3) * 2))) & 0x03];
packPixel(lum);
}
break;
}
case 1: {
for (int x = 0; x < width; x++) {
uint8_t lum = (rowBuffer[x >> 3] & (0x80 >> (x & 7))) ? 0xFF : 0x00;
lum = (rowBuffer[x >> 3] & (0x80 >> (x & 7))) ? 0xFF : 0x00;
packPixel(lum);
}
break;
}
case 32: {
const uint8_t* p = rowBuffer;
for (int x = 0; x < width; x++) {
uint8_t lum = (77u * p[2] + 150u * p[1] + 29u * p[0]) >> 8;
packPixel(lum);
p += 4;
}
break;
}
default:
return BmpReaderError::UnsupportedBpp;
}
// Flush remaining bits if width is not a multiple of 4