## Summary - EPUB books with PNG cover images now display covers on the home screen instead of blank rectangles - Adds `PngToBmpConverter` library mirroring the existing `JpegToBmpConverter` pattern - Uses miniz (already in the project) for streaming zlib decompression of PNG IDAT data - Supports all PNG color types (Grayscale, RGB, RGBA, Palette, Gray+Alpha) - Optimized for ESP32-C3: batch grayscale conversion, 2KB read buffer, same area-averaging scaling and Atkinson dithering as the JPEG path ## Changes - **New:** `lib/PngToBmpConverter/PngToBmpConverter.h` — Public API matching JpegToBmpConverter's interface - **New:** `lib/PngToBmpConverter/PngToBmpConverter.cpp` — Streaming PNG decoder + BMP converter - **Modified:** `lib/Epub/Epub.cpp` — Added `.png` handling in `generateCoverBmp()` and `generateThumbBmp()` ## Test plan - [x] Tested with EPUB files using PNG covers — covers appear correctly on home screen - [ ] Verify with various PNG color types (most stock EPUBs use 8-bit RGB) - [ ] Confirm no regressions with JPEG cover EPUBs <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit **New Features** - Added PNG format support for EPUB cover and thumbnail images. PNG files are automatically processed and cached alongside existing supported formats. This enhancement enables users to leverage PNG cover artwork when generating EPUB files, improving workflow flexibility and compatibility with common image sources. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Nik Outchcunis <outchy@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Dave Allie <dave@daveallie.com>
83 lines
2.4 KiB
C++
83 lines
2.4 KiB
C++
#pragma once
|
|
|
|
#include <HalStorage.h>
|
|
#include <Logging.h>
|
|
#include <stdint.h>
|
|
|
|
#include <cstring>
|
|
#include <string>
|
|
|
|
// Cache buffer for storing 2-bit pixels (4 levels) during decode.
|
|
// Packs 4 pixels per byte, MSB first.
|
|
struct PixelCache {
|
|
uint8_t* buffer;
|
|
int width;
|
|
int height;
|
|
int bytesPerRow;
|
|
int originX; // config.x - to convert screen coords to cache coords
|
|
int originY; // config.y
|
|
|
|
PixelCache() : buffer(nullptr), width(0), height(0), bytesPerRow(0), originX(0), originY(0) {}
|
|
PixelCache(const PixelCache&) = delete;
|
|
PixelCache& operator=(const PixelCache&) = delete;
|
|
|
|
static constexpr size_t MAX_CACHE_BYTES = 256 * 1024; // 256KB limit for embedded targets
|
|
|
|
bool allocate(int w, int h, int ox, int oy) {
|
|
width = w;
|
|
height = h;
|
|
originX = ox;
|
|
originY = oy;
|
|
bytesPerRow = (w + 3) / 4; // 2 bits per pixel, 4 pixels per byte
|
|
size_t bufferSize = (size_t)bytesPerRow * h;
|
|
if (bufferSize > MAX_CACHE_BYTES) {
|
|
LOG_ERR("IMG", "Cache buffer too large: %d bytes for %dx%d (limit %d)", bufferSize, w, h, MAX_CACHE_BYTES);
|
|
return false;
|
|
}
|
|
buffer = (uint8_t*)malloc(bufferSize);
|
|
if (buffer) {
|
|
memset(buffer, 0, bufferSize);
|
|
LOG_DBG("IMG", "Allocated cache buffer: %d bytes for %dx%d", bufferSize, w, h);
|
|
}
|
|
return buffer != nullptr;
|
|
}
|
|
|
|
void setPixel(int screenX, int screenY, uint8_t value) {
|
|
if (!buffer) return;
|
|
int localX = screenX - originX;
|
|
int localY = screenY - originY;
|
|
if (localX < 0 || localX >= width || localY < 0 || localY >= height) return;
|
|
|
|
int byteIdx = localY * bytesPerRow + localX / 4;
|
|
int bitShift = 6 - (localX % 4) * 2; // MSB first: pixel 0 at bits 6-7
|
|
buffer[byteIdx] = (buffer[byteIdx] & ~(0x03 << bitShift)) | ((value & 0x03) << bitShift);
|
|
}
|
|
|
|
bool writeToFile(const std::string& cachePath) {
|
|
if (!buffer) return false;
|
|
|
|
FsFile cacheFile;
|
|
if (!Storage.openFileForWrite("IMG", cachePath, cacheFile)) {
|
|
LOG_ERR("IMG", "Failed to open cache file for writing: %s", cachePath.c_str());
|
|
return false;
|
|
}
|
|
|
|
uint16_t w = width;
|
|
uint16_t h = height;
|
|
cacheFile.write(&w, 2);
|
|
cacheFile.write(&h, 2);
|
|
cacheFile.write(buffer, bytesPerRow * height);
|
|
cacheFile.close();
|
|
|
|
LOG_DBG("IMG", "Cache written: %s (%dx%d, %d bytes)", cachePath.c_str(), width, height, 4 + bytesPerRow * height);
|
|
return true;
|
|
}
|
|
|
|
~PixelCache() {
|
|
if (buffer) {
|
|
free(buffer);
|
|
buffer = nullptr;
|
|
}
|
|
}
|
|
};
|