diff --git a/lib/Epub/Epub/converters/JpegToFramebufferConverter.cpp b/lib/Epub/Epub/converters/JpegToFramebufferConverter.cpp index 4c23323e..fbb3e96b 100644 --- a/lib/Epub/Epub/converters/JpegToFramebufferConverter.cpp +++ b/lib/Epub/Epub/converters/JpegToFramebufferConverter.cpp @@ -9,6 +9,9 @@ #include #include +#include "DitherUtils.h" +#include "PixelCache.h" + struct JpegContext { FsFile& file; uint8_t buffer[512]; @@ -17,108 +20,6 @@ struct JpegContext { JpegContext(FsFile& f) : file(f), bufferPos(0), bufferFilled(0) {} }; -// Cache buffer for storing 2-bit pixels during decode -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) {} - - 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 = bytesPerRow * h; - buffer = (uint8_t*)malloc(bufferSize); - if (buffer) { - memset(buffer, 0, bufferSize); - Serial.printf("[%lu] [JPG] Allocated cache buffer: %d bytes for %dx%d\n", millis(), 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 (!SdMan.openFileForWrite("IMG", cachePath, cacheFile)) { - Serial.printf("[%lu] [JPG] Failed to open cache file for writing: %s\n", millis(), 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(); - - Serial.printf("[%lu] [JPG] Cache written: %s (%dx%d, %d bytes)\n", millis(), cachePath.c_str(), width, height, - 4 + bytesPerRow * height); - return true; - } - - ~PixelCache() { - if (buffer) { - free(buffer); - buffer = nullptr; - } - } -}; - -// 4x4 Bayer matrix for ordered dithering -static const uint8_t bayer4x4[4][4] = { - {0, 8, 2, 10}, - {12, 4, 14, 6}, - {3, 11, 1, 9}, - {15, 7, 13, 5}, -}; - -// Apply Bayer dithering and quantize to 4 levels (0-3) -// Stateless - works correctly with any pixel processing order (ideal for MCU-based decoding) -static uint8_t applyBayerDither4Level(uint8_t gray, int x, int y) { - int bayer = bayer4x4[y & 3][x & 3]; - int dither = (bayer - 8) * 5; // Scale to ±40 (half of quantization step 85) - - int adjusted = gray + dither; - if (adjusted < 0) adjusted = 0; - if (adjusted > 255) adjusted = 255; - - if (adjusted < 64) return 0; - if (adjusted < 128) return 1; - if (adjusted < 192) return 2; - return 3; -} - -// Draw a pixel respecting the current render mode for grayscale support -static void drawPixelWithRenderMode(GfxRenderer& renderer, int x, int y, uint8_t pixelValue) { - GfxRenderer::RenderMode renderMode = renderer.getRenderMode(); - if (renderMode == GfxRenderer::BW && pixelValue < 3) { - renderer.drawPixel(x, y, true); - } else if (renderMode == GfxRenderer::GRAYSCALE_MSB && (pixelValue == 1 || pixelValue == 2)) { - renderer.drawPixel(x, y, false); - } else if (renderMode == GfxRenderer::GRAYSCALE_LSB && pixelValue == 1) { - renderer.drawPixel(x, y, false); - } -} - bool JpegToFramebufferConverter::getDimensionsStatic(const std::string& imagePath, ImageDimensions& out) { FsFile file; if (!SdMan.openFileForRead("JPG", imagePath, file)) {