From 130242fde85dfa1b0cd4362a4b6482a6217948c3 Mon Sep 17 00:00:00 2001 From: Sam Davis Date: Sun, 14 Dec 2025 15:01:37 +1100 Subject: [PATCH 1/6] ignore .vscode directory --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 29bccdd..3690b08 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .pio .idea .DS_Store +.vscode \ No newline at end of file From e2951c85e2e1dc2adc805461a338c3b4c27a88b0 Mon Sep 17 00:00:00 2001 From: Sam Davis Date: Sun, 14 Dec 2025 15:06:05 +1100 Subject: [PATCH 2/6] add separate render path when sleep.bmp is found in root of sd card --- src/screens/SleepScreen.cpp | 26 ++++++++++++++++++++++++++ src/screens/SleepScreen.h | 3 +++ 2 files changed, 29 insertions(+) diff --git a/src/screens/SleepScreen.cpp b/src/screens/SleepScreen.cpp index cbcb411..0b83387 100644 --- a/src/screens/SleepScreen.cpp +++ b/src/screens/SleepScreen.cpp @@ -2,10 +2,23 @@ #include +#include "SD.h" #include "config.h" #include "images/CrossLarge.h" void SleepScreen::onEnter() { + // Look for sleep.bmp on the root of the sd card to determine if we should + // render a custom sleep screen instead of the default. + auto file = SD.open("/sleep.bmp"); + if (file) { + renderCustomSleepScreen(file); + return; + } + + renderDefaultSleepScreen(); +} + +void SleepScreen::renderDefaultSleepScreen() { const auto pageWidth = GfxRenderer::getScreenWidth(); const auto pageHeight = GfxRenderer::getScreenHeight(); @@ -16,3 +29,16 @@ void SleepScreen::onEnter() { renderer.invertScreen(); renderer.displayBuffer(EInkDisplay::HALF_REFRESH); } + +void SleepScreen::renderCustomSleepScreen(File file) { + Serial.println("Rendering custom sleep screen from sleep.bmp"); + + const auto pageWidth = GfxRenderer::getScreenWidth(); + const auto pageHeight = GfxRenderer::getScreenHeight(); + + renderer.clearScreen(); + + renderer.drawCenteredText(UI_FONT_ID, pageHeight / 2 + 70, "CUSTOM SLEEP SCREEN", true, BOLD); + + renderer.displayBuffer(EInkDisplay::HALF_REFRESH); +} diff --git a/src/screens/SleepScreen.h b/src/screens/SleepScreen.h index b280087..29857a8 100644 --- a/src/screens/SleepScreen.h +++ b/src/screens/SleepScreen.h @@ -1,8 +1,11 @@ #pragma once +#include "SD.h" #include "Screen.h" class SleepScreen final : public Screen { public: explicit SleepScreen(GfxRenderer& renderer, InputManager& inputManager) : Screen(renderer, inputManager) {} void onEnter() override; + void renderDefaultSleepScreen(); + void renderCustomSleepScreen(File file); }; From 602d3da3a269cb6a40af7e5bb53e1d92be9c5e59 Mon Sep 17 00:00:00 2001 From: Sam Davis Date: Sun, 14 Dec 2025 17:07:14 +1100 Subject: [PATCH 3/6] Render sleep.bmp when sleeping if it exists --- lib/BmpToMono/BmpToMono.cpp | 179 ++++++++++++++++++++++++++++++++ lib/BmpToMono/BmpToMono.h | 62 +++++++++++ lib/GfxRenderer/GfxRenderer.cpp | 33 ++++++ lib/GfxRenderer/GfxRenderer.h | 2 + src/main.cpp | 6 ++ src/screens/SleepScreen.cpp | 13 ++- src/screens/SleepScreen.h | 2 +- 7 files changed, 289 insertions(+), 8 deletions(-) create mode 100644 lib/BmpToMono/BmpToMono.cpp create mode 100644 lib/BmpToMono/BmpToMono.h diff --git a/lib/BmpToMono/BmpToMono.cpp b/lib/BmpToMono/BmpToMono.cpp new file mode 100644 index 0000000..10e9303 --- /dev/null +++ b/lib/BmpToMono/BmpToMono.cpp @@ -0,0 +1,179 @@ +#include "BmpToMono.h" + +#include +#include + +uint16_t BmpToMono::readLE16(fs::File& f) { + const int c0 = f.read(); + const int c1 = f.read(); + const uint8_t b0 = (uint8_t)(c0 < 0 ? 0 : c0); + const uint8_t b1 = (uint8_t)(c1 < 0 ? 0 : c1); + return (uint16_t)b0 | ((uint16_t)b1 << 8); +} + +uint32_t BmpToMono::readLE32(fs::File& f) { + const int c0 = f.read(); + const int c1 = f.read(); + const int c2 = f.read(); + const int c3 = f.read(); + + const uint8_t b0 = (uint8_t)(c0 < 0 ? 0 : c0); + const uint8_t b1 = (uint8_t)(c1 < 0 ? 0 : c1); + const uint8_t b2 = (uint8_t)(c2 < 0 ? 0 : c2); + const uint8_t b3 = (uint8_t)(c3 < 0 ? 0 : c3); + + return (uint32_t)b0 | ((uint32_t)b1 << 8) | ((uint32_t)b2 << 16) | ((uint32_t)b3 << 24); +} + +void BmpToMono::freeMonoBitmap(MonoBitmap& bmp) { + if (bmp.data) { + free(bmp.data); + bmp.data = nullptr; + } + bmp.width = 0; + bmp.height = 0; + bmp.len = 0; +} + +const char* BmpToMono::errorToString(BmpToMonoError err) { + switch (err) { + case BmpToMonoError::Ok: + return "Ok"; + case BmpToMonoError::FileInvalid: + return "FileInvalid"; + case BmpToMonoError::SeekStartFailed: + return "SeekStartFailed"; + case BmpToMonoError::NotBMP: + return "NotBMP (missing 'BM')"; + case BmpToMonoError::DIBTooSmall: + return "DIBTooSmall (<40 bytes)"; + case BmpToMonoError::BadPlanes: + return "BadPlanes (!= 1)"; + case BmpToMonoError::UnsupportedBpp: + return "UnsupportedBpp (expected 24)"; + case BmpToMonoError::UnsupportedCompression: + return "UnsupportedCompression (expected BI_RGB)"; + case BmpToMonoError::BadDimensions: + return "BadDimensions"; + case BmpToMonoError::SeekPixelDataFailed: + return "SeekPixelDataFailed"; + case BmpToMonoError::OomOutput: + return "OomOutput"; + case BmpToMonoError::OomRowBuffer: + return "OomRowBuffer"; + case BmpToMonoError::ShortReadRow: + return "ShortReadRow"; + } + return "Unknown"; +} + +BmpToMonoError BmpToMono::convert24(fs::File& file, MonoBitmap& out, uint8_t threshold, bool invert) { + return convert24Impl(file, out, threshold, invert, false); +} + +BmpToMonoError BmpToMono::convert24Rotate90CW(fs::File& file, MonoBitmap& out, uint8_t threshold, bool invert) { + return convert24Impl(file, out, threshold, invert, true); +} + +BmpToMonoError BmpToMono::convert24Impl(fs::File& f, MonoBitmap& out, uint8_t threshold, bool invert, bool rotate90CW) { + freeMonoBitmap(out); + + if (!f) return BmpToMonoError::FileInvalid; + if (!f.seek(0)) return BmpToMonoError::SeekStartFailed; + + // --- BMP FILE HEADER --- + const uint16_t bfType = readLE16(f); + if (bfType != 0x4D42) return BmpToMonoError::NotBMP; + + (void)readLE32(f); + (void)readLE16(f); + (void)readLE16(f); + const uint32_t bfOffBits = readLE32(f); + + // --- DIB HEADER --- + const uint32_t biSize = readLE32(f); + if (biSize < 40) return BmpToMonoError::DIBTooSmall; + + const int32_t srcW = (int32_t)readLE32(f); + int32_t srcHRaw = (int32_t)readLE32(f); + const uint16_t planes = readLE16(f); + const uint16_t bpp = readLE16(f); + const uint32_t comp = readLE32(f); + + if (planes != 1) return BmpToMonoError::BadPlanes; + if (bpp != 24) return BmpToMonoError::UnsupportedBpp; + if (comp != 0) return BmpToMonoError::UnsupportedCompression; + + (void)readLE32(f); + (void)readLE32(f); + (void)readLE32(f); + (void)readLE32(f); + (void)readLE32(f); + + if (srcW <= 0) return BmpToMonoError::BadDimensions; + + const bool topDown = (srcHRaw < 0); + const int32_t srcH = topDown ? -srcHRaw : srcHRaw; + if (srcH <= 0) return BmpToMonoError::BadDimensions; + + // Output dimensions + out.width = rotate90CW ? (int)srcH : (int)srcW; + out.height = rotate90CW ? (int)srcW : (int)srcH; + + const size_t outBytesPerRow = (size_t)(out.width + 7) / 8; + out.len = outBytesPerRow * (size_t)out.height; + + out.data = (uint8_t*)malloc(out.len); + if (!out.data) return BmpToMonoError::OomOutput; + memset(out.data, 0xFF, out.len); + + // Source row stride (padded to 4 bytes) + const uint32_t srcBytesPerRow24 = (uint32_t)srcW * 3u; + const uint32_t srcRowStride = (srcBytesPerRow24 + 3u) & ~3u; + + if (!f.seek(bfOffBits)) { + freeMonoBitmap(out); + return BmpToMonoError::SeekPixelDataFailed; + } + + uint8_t* rowBuf = (uint8_t*)malloc(srcRowStride); + if (!rowBuf) { + freeMonoBitmap(out); + return BmpToMonoError::OomRowBuffer; + } + + for (int fileRow = 0; fileRow < (int)srcH; fileRow++) { + if (f.read(rowBuf, srcRowStride) != (int)srcRowStride) { + free(rowBuf); + freeMonoBitmap(out); + return BmpToMonoError::ShortReadRow; + } + + const int srcY = topDown ? fileRow : ((int)srcH - 1 - fileRow); + + for (int srcX = 0; srcX < (int)srcW; srcX++) { + const uint8_t b = rowBuf[srcX * 3 + 0]; + const uint8_t g = rowBuf[srcX * 3 + 1]; + const uint8_t r = rowBuf[srcX * 3 + 2]; + + const uint8_t lum = (uint8_t)((77u * r + 150u * g + 29u * b) >> 8); + bool isBlack = (lum < threshold); + if (invert) isBlack = !isBlack; + + int outX, outY; + if (!rotate90CW) { + outX = srcX; + outY = srcY; + } else { + // 90° clockwise: (x,y) -> (h-1-y, x) + outX = (int)srcH - 1 - srcY; + outY = srcX; + } + + setMonoPixel(out.data, out.width, outX, outY, isBlack); + } + } + + free(rowBuf); + return BmpToMonoError::Ok; +} \ No newline at end of file diff --git a/lib/BmpToMono/BmpToMono.h b/lib/BmpToMono/BmpToMono.h new file mode 100644 index 0000000..b50937d --- /dev/null +++ b/lib/BmpToMono/BmpToMono.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include + +struct MonoBitmap { + int width = 0; + int height = 0; + size_t len = 0; // bytesPerRow * height + uint8_t* data = nullptr; // row-aligned, MSB-first, 1=white 0=black +}; + +enum class BmpToMonoError : uint8_t { + Ok = 0, + FileInvalid, + SeekStartFailed, + + NotBMP, + DIBTooSmall, + + BadPlanes, + UnsupportedBpp, + UnsupportedCompression, + + BadDimensions, + + SeekPixelDataFailed, + OomOutput, + OomRowBuffer, + ShortReadRow, +}; + +class BmpToMono { + public: + // No rotation: output size == BMP size + static BmpToMonoError convert24(fs::File& file, MonoBitmap& out, uint8_t threshold = 160, bool invert = false); + + // Rotate 90° clockwise: (w,h) -> (h,w) + // Useful for converting portrait BMP (480x800) into landscape framebuffer (800x480). + static BmpToMonoError convert24Rotate90CW(fs::File& file, MonoBitmap& out, uint8_t threshold = 160, + bool invert = false); + + static void freeMonoBitmap(MonoBitmap& bmp); + static const char* errorToString(BmpToMonoError err); + + private: + static uint16_t readLE16(fs::File& f); + static uint32_t readLE32(fs::File& f); + + // Writes a single pixel into a row-aligned 1bpp buffer (MSB-first), 0=black, 1=white + static inline void setMonoPixel(uint8_t* buf, int w, int x, int y, bool isBlack) { + const size_t bytesPerRow = (size_t)(w + 7) / 8; + const size_t idx = (size_t)y * bytesPerRow + (size_t)(x >> 3); + const uint8_t mask = (uint8_t)(0x80u >> (x & 7)); + if (isBlack) + buf[idx] &= (uint8_t)~mask; + else + buf[idx] |= mask; + } + + static BmpToMonoError convert24Impl(fs::File& file, MonoBitmap& out, uint8_t threshold, bool invert, bool rotate90CW); +}; \ No newline at end of file diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index dd33264..cdd519f 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -2,6 +2,8 @@ #include +#include "BmpToMono.h" + void GfxRenderer::insertFont(const int fontId, EpdFontFamily font) { fontMap.insert({fontId, font}); } void GfxRenderer::drawPixel(const int x, const int y, const bool state) const { @@ -119,6 +121,37 @@ void GfxRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, co einkDisplay.drawImage(bitmap, y, x, height, width); } +bool GfxRenderer::drawFullScreenBmp(File& file) { + if (!file) { + Serial.printf("[%lu] [GFX] drawFullScreenBmp: invalid file\n", millis()); + return false; + } + + file.seek(0); // Ensure we're at the start of the file + + MonoBitmap mono; + auto err = BmpToMono::convert24Rotate90CW(file, mono, 160, false); + + if (err != BmpToMonoError::Ok) { + Serial.printf("[%lu] [GFX] BMP convert failed: %s\n", millis(), BmpToMono::errorToString(err)); + return false; + } + + // Hard requirement: must match panel exactly + if (mono.width != EInkDisplay::DISPLAY_WIDTH || mono.height != EInkDisplay::DISPLAY_HEIGHT) { + Serial.printf("[%lu] [GFX] drawFullScreenBmp: rotated BMP size %dx%d does not match panel %dx%d\n", millis(), + mono.width, mono.height, EInkDisplay::DISPLAY_WIDTH, EInkDisplay::DISPLAY_HEIGHT); + BmpToMono::freeMonoBitmap(mono); + return false; + } + + // Raw full-screen blit + einkDisplay.drawImage(mono.data, 0, 0, mono.width, mono.height); + + BmpToMono::freeMonoBitmap(mono); + return true; +} + void GfxRenderer::clearScreen(const uint8_t color) const { einkDisplay.clearScreen(color); } void GfxRenderer::invertScreen() const { diff --git a/lib/GfxRenderer/GfxRenderer.h b/lib/GfxRenderer/GfxRenderer.h index d7b5925..0e716f4 100644 --- a/lib/GfxRenderer/GfxRenderer.h +++ b/lib/GfxRenderer/GfxRenderer.h @@ -2,6 +2,7 @@ #include #include +#include #include @@ -36,6 +37,7 @@ class GfxRenderer { void drawRect(int x, int y, int width, int height, bool state = true) const; void fillRect(int x, int y, int width, int height, bool state = true) const; void drawImage(const uint8_t bitmap[], int x, int y, int width, int height) const; + bool drawFullScreenBmp(File& file); // Text int getTextWidth(int fontId, const char* text, EpdFontStyle style = REGULAR) const; diff --git a/src/main.cpp b/src/main.cpp index f13e21c..9020c69 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -199,6 +199,12 @@ void setup() { // SD Card Initialization SD.begin(SD_SPI_CS, SPI, SPI_FQ); + // TODO: Remove testing code before merging + // exitScreen(); + // enterNewScreen(new SleepScreen(renderer, inputManager)); + // waitForPowerRelease(); + // return; + appState.loadFromFile(); if (!appState.openEpubPath.empty()) { auto epub = loadEpub(appState.openEpubPath); diff --git a/src/screens/SleepScreen.cpp b/src/screens/SleepScreen.cpp index 0b83387..b4972d1 100644 --- a/src/screens/SleepScreen.cpp +++ b/src/screens/SleepScreen.cpp @@ -30,15 +30,14 @@ void SleepScreen::renderDefaultSleepScreen() { renderer.displayBuffer(EInkDisplay::HALF_REFRESH); } -void SleepScreen::renderCustomSleepScreen(File file) { - Serial.println("Rendering custom sleep screen from sleep.bmp"); - - const auto pageWidth = GfxRenderer::getScreenWidth(); - const auto pageHeight = GfxRenderer::getScreenHeight(); - +void SleepScreen::renderCustomSleepScreen(File& file) { renderer.clearScreen(); + bool didImageDrawSuccessfully = renderer.drawFullScreenBmp(file); - renderer.drawCenteredText(UI_FONT_ID, pageHeight / 2 + 70, "CUSTOM SLEEP SCREEN", true, BOLD); + if (!didImageDrawSuccessfully) { + renderer.clearScreen(); + renderer.drawCenteredText(UI_FONT_ID, GfxRenderer::getScreenHeight() / 2, "BAD CUSTOM SLEEP SCREEN", true, BOLD); + } renderer.displayBuffer(EInkDisplay::HALF_REFRESH); } diff --git a/src/screens/SleepScreen.h b/src/screens/SleepScreen.h index 29857a8..49caa84 100644 --- a/src/screens/SleepScreen.h +++ b/src/screens/SleepScreen.h @@ -7,5 +7,5 @@ class SleepScreen final : public Screen { explicit SleepScreen(GfxRenderer& renderer, InputManager& inputManager) : Screen(renderer, inputManager) {} void onEnter() override; void renderDefaultSleepScreen(); - void renderCustomSleepScreen(File file); + void renderCustomSleepScreen(File& file); }; From d60378719cd9f4cb67b12cbb1e68f36de1bd502b Mon Sep 17 00:00:00 2001 From: Sam Davis Date: Sun, 14 Dec 2025 19:50:05 +1100 Subject: [PATCH 4/6] rotate fullscreen bmp CCW instead of CW --- .gitignore | 2 +- lib/BmpToMono/BmpToMono.cpp | 27 +++++++++++---------------- lib/BmpToMono/BmpToMono.h | 16 ++++++---------- lib/GfxRenderer/GfxRenderer.cpp | 16 ++++++++-------- 4 files changed, 26 insertions(+), 35 deletions(-) diff --git a/.gitignore b/.gitignore index 3690b08..c989c30 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ .pio .idea .DS_Store -.vscode \ No newline at end of file +.vscode diff --git a/lib/BmpToMono/BmpToMono.cpp b/lib/BmpToMono/BmpToMono.cpp index 10e9303..b2eb982 100644 --- a/lib/BmpToMono/BmpToMono.cpp +++ b/lib/BmpToMono/BmpToMono.cpp @@ -3,7 +3,7 @@ #include #include -uint16_t BmpToMono::readLE16(fs::File& f) { +uint16_t BmpToMono::readLE16(File& f) { const int c0 = f.read(); const int c1 = f.read(); const uint8_t b0 = (uint8_t)(c0 < 0 ? 0 : c0); @@ -11,7 +11,7 @@ uint16_t BmpToMono::readLE16(fs::File& f) { return (uint16_t)b0 | ((uint16_t)b1 << 8); } -uint32_t BmpToMono::readLE32(fs::File& f) { +uint32_t BmpToMono::readLE32(File& f) { const int c0 = f.read(); const int c1 = f.read(); const int c2 = f.read(); @@ -67,15 +67,11 @@ const char* BmpToMono::errorToString(BmpToMonoError err) { return "Unknown"; } -BmpToMonoError BmpToMono::convert24(fs::File& file, MonoBitmap& out, uint8_t threshold, bool invert) { - return convert24Impl(file, out, threshold, invert, false); +BmpToMonoError BmpToMono::convert24BitRotate90CCW(File& file, MonoBitmap& out, uint8_t threshold) { + return convert24BitImpl(file, out, threshold, true); } -BmpToMonoError BmpToMono::convert24Rotate90CW(fs::File& file, MonoBitmap& out, uint8_t threshold, bool invert) { - return convert24Impl(file, out, threshold, invert, true); -} - -BmpToMonoError BmpToMono::convert24Impl(fs::File& f, MonoBitmap& out, uint8_t threshold, bool invert, bool rotate90CW) { +BmpToMonoError BmpToMono::convert24BitImpl(File& f, MonoBitmap& out, uint8_t threshold, bool rotate90CCW) { freeMonoBitmap(out); if (!f) return BmpToMonoError::FileInvalid; @@ -117,8 +113,8 @@ BmpToMonoError BmpToMono::convert24Impl(fs::File& f, MonoBitmap& out, uint8_t th if (srcH <= 0) return BmpToMonoError::BadDimensions; // Output dimensions - out.width = rotate90CW ? (int)srcH : (int)srcW; - out.height = rotate90CW ? (int)srcW : (int)srcH; + out.width = rotate90CCW ? (int)srcH : (int)srcW; + out.height = rotate90CCW ? (int)srcW : (int)srcH; const size_t outBytesPerRow = (size_t)(out.width + 7) / 8; out.len = outBytesPerRow * (size_t)out.height; @@ -158,16 +154,15 @@ BmpToMonoError BmpToMono::convert24Impl(fs::File& f, MonoBitmap& out, uint8_t th const uint8_t lum = (uint8_t)((77u * r + 150u * g + 29u * b) >> 8); bool isBlack = (lum < threshold); - if (invert) isBlack = !isBlack; int outX, outY; - if (!rotate90CW) { + if (!rotate90CCW) { outX = srcX; outY = srcY; } else { - // 90° clockwise: (x,y) -> (h-1-y, x) - outX = (int)srcH - 1 - srcY; - outY = srcX; + // 90° counter-clockwise: (x,y) -> (y, w-1-x) + outX = srcY; + outY = (int)srcW - 1 - srcX; } setMonoPixel(out.data, out.width, outX, outY, isBlack); diff --git a/lib/BmpToMono/BmpToMono.h b/lib/BmpToMono/BmpToMono.h index b50937d..ef2d3b6 100644 --- a/lib/BmpToMono/BmpToMono.h +++ b/lib/BmpToMono/BmpToMono.h @@ -32,20 +32,16 @@ enum class BmpToMonoError : uint8_t { class BmpToMono { public: - // No rotation: output size == BMP size - static BmpToMonoError convert24(fs::File& file, MonoBitmap& out, uint8_t threshold = 160, bool invert = false); - - // Rotate 90° clockwise: (w,h) -> (h,w) - // Useful for converting portrait BMP (480x800) into landscape framebuffer (800x480). - static BmpToMonoError convert24Rotate90CW(fs::File& file, MonoBitmap& out, uint8_t threshold = 160, - bool invert = false); + // Rotate 90° counter-clockwise: (w,h) -> (h,w) + // Used for converting portrait BMP (480x800) into landscape framebuffer (800x480) + static BmpToMonoError convert24BitRotate90CCW(File& file, MonoBitmap& out, uint8_t threshold = 160); static void freeMonoBitmap(MonoBitmap& bmp); static const char* errorToString(BmpToMonoError err); private: - static uint16_t readLE16(fs::File& f); - static uint32_t readLE32(fs::File& f); + static uint16_t readLE16(File& f); + static uint32_t readLE32(File& f); // Writes a single pixel into a row-aligned 1bpp buffer (MSB-first), 0=black, 1=white static inline void setMonoPixel(uint8_t* buf, int w, int x, int y, bool isBlack) { @@ -58,5 +54,5 @@ class BmpToMono { buf[idx] |= mask; } - static BmpToMonoError convert24Impl(fs::File& file, MonoBitmap& out, uint8_t threshold, bool invert, bool rotate90CW); + static BmpToMonoError convert24BitImpl(File& file, MonoBitmap& out, uint8_t threshold, bool rotate90CW); }; \ No newline at end of file diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index cdd519f..3509188 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -129,8 +129,8 @@ bool GfxRenderer::drawFullScreenBmp(File& file) { file.seek(0); // Ensure we're at the start of the file - MonoBitmap mono; - auto err = BmpToMono::convert24Rotate90CW(file, mono, 160, false); + MonoBitmap bmp; + auto err = BmpToMono::convert24BitRotate90CCW(file, bmp); if (err != BmpToMonoError::Ok) { Serial.printf("[%lu] [GFX] BMP convert failed: %s\n", millis(), BmpToMono::errorToString(err)); @@ -138,17 +138,17 @@ bool GfxRenderer::drawFullScreenBmp(File& file) { } // Hard requirement: must match panel exactly - if (mono.width != EInkDisplay::DISPLAY_WIDTH || mono.height != EInkDisplay::DISPLAY_HEIGHT) { + if (bmp.width != EInkDisplay::DISPLAY_WIDTH || bmp.height != EInkDisplay::DISPLAY_HEIGHT) { Serial.printf("[%lu] [GFX] drawFullScreenBmp: rotated BMP size %dx%d does not match panel %dx%d\n", millis(), - mono.width, mono.height, EInkDisplay::DISPLAY_WIDTH, EInkDisplay::DISPLAY_HEIGHT); - BmpToMono::freeMonoBitmap(mono); + bmp.width, bmp.height, EInkDisplay::DISPLAY_WIDTH, EInkDisplay::DISPLAY_HEIGHT); + BmpToMono::freeMonoBitmap(bmp); return false; } - // Raw full-screen blit - einkDisplay.drawImage(mono.data, 0, 0, mono.width, mono.height); + // Full-screen blit + einkDisplay.drawImage(bmp.data, 0, 0, bmp.width, bmp.height); - BmpToMono::freeMonoBitmap(mono); + BmpToMono::freeMonoBitmap(bmp); return true; } From 9f51a31677186f660278a2f330ed08739ef027fd Mon Sep 17 00:00:00 2001 From: Sam Davis Date: Mon, 15 Dec 2025 16:25:12 +1100 Subject: [PATCH 5/6] Rename BmpToMono -> BmpReader --- .../BmpToMono.cpp => BmpReader/BmpReader.cpp} | 70 +++++++++---------- .../BmpToMono.h => BmpReader/BmpReader.h} | 12 ++-- lib/GfxRenderer/GfxRenderer.cpp | 12 ++-- 3 files changed, 47 insertions(+), 47 deletions(-) rename lib/{BmpToMono/BmpToMono.cpp => BmpReader/BmpReader.cpp} (69%) rename lib/{BmpToMono/BmpToMono.h => BmpReader/BmpReader.h} (80%) diff --git a/lib/BmpToMono/BmpToMono.cpp b/lib/BmpReader/BmpReader.cpp similarity index 69% rename from lib/BmpToMono/BmpToMono.cpp rename to lib/BmpReader/BmpReader.cpp index b2eb982..dcf5b7b 100644 --- a/lib/BmpToMono/BmpToMono.cpp +++ b/lib/BmpReader/BmpReader.cpp @@ -1,9 +1,9 @@ -#include "BmpToMono.h" +#include "BmpReader.h" #include #include -uint16_t BmpToMono::readLE16(File& f) { +uint16_t BmpReader::readLE16(File& f) { const int c0 = f.read(); const int c1 = f.read(); const uint8_t b0 = (uint8_t)(c0 < 0 ? 0 : c0); @@ -11,7 +11,7 @@ uint16_t BmpToMono::readLE16(File& f) { return (uint16_t)b0 | ((uint16_t)b1 << 8); } -uint32_t BmpToMono::readLE32(File& f) { +uint32_t BmpReader::readLE32(File& f) { const int c0 = f.read(); const int c1 = f.read(); const int c2 = f.read(); @@ -25,7 +25,7 @@ uint32_t BmpToMono::readLE32(File& f) { return (uint32_t)b0 | ((uint32_t)b1 << 8) | ((uint32_t)b2 << 16) | ((uint32_t)b3 << 24); } -void BmpToMono::freeMonoBitmap(MonoBitmap& bmp) { +void BmpReader::freeMonoBitmap(MonoBitmap& bmp) { if (bmp.data) { free(bmp.data); bmp.data = nullptr; @@ -35,51 +35,51 @@ void BmpToMono::freeMonoBitmap(MonoBitmap& bmp) { bmp.len = 0; } -const char* BmpToMono::errorToString(BmpToMonoError err) { +const char* BmpReader::errorToString(BmpReaderError err) { switch (err) { - case BmpToMonoError::Ok: + case BmpReaderError::Ok: return "Ok"; - case BmpToMonoError::FileInvalid: + case BmpReaderError::FileInvalid: return "FileInvalid"; - case BmpToMonoError::SeekStartFailed: + case BmpReaderError::SeekStartFailed: return "SeekStartFailed"; - case BmpToMonoError::NotBMP: + case BmpReaderError::NotBMP: return "NotBMP (missing 'BM')"; - case BmpToMonoError::DIBTooSmall: + case BmpReaderError::DIBTooSmall: return "DIBTooSmall (<40 bytes)"; - case BmpToMonoError::BadPlanes: + case BmpReaderError::BadPlanes: return "BadPlanes (!= 1)"; - case BmpToMonoError::UnsupportedBpp: + case BmpReaderError::UnsupportedBpp: return "UnsupportedBpp (expected 24)"; - case BmpToMonoError::UnsupportedCompression: + case BmpReaderError::UnsupportedCompression: return "UnsupportedCompression (expected BI_RGB)"; - case BmpToMonoError::BadDimensions: + case BmpReaderError::BadDimensions: return "BadDimensions"; - case BmpToMonoError::SeekPixelDataFailed: + case BmpReaderError::SeekPixelDataFailed: return "SeekPixelDataFailed"; - case BmpToMonoError::OomOutput: + case BmpReaderError::OomOutput: return "OomOutput"; - case BmpToMonoError::OomRowBuffer: + case BmpReaderError::OomRowBuffer: return "OomRowBuffer"; - case BmpToMonoError::ShortReadRow: + case BmpReaderError::ShortReadRow: return "ShortReadRow"; } return "Unknown"; } -BmpToMonoError BmpToMono::convert24BitRotate90CCW(File& file, MonoBitmap& out, uint8_t threshold) { +BmpReaderError BmpReader::convert24BitRotate90CCW(File& file, MonoBitmap& out, uint8_t threshold) { return convert24BitImpl(file, out, threshold, true); } -BmpToMonoError BmpToMono::convert24BitImpl(File& f, MonoBitmap& out, uint8_t threshold, bool rotate90CCW) { +BmpReaderError BmpReader::convert24BitImpl(File& f, MonoBitmap& out, uint8_t threshold, bool rotate90CCW) { freeMonoBitmap(out); - if (!f) return BmpToMonoError::FileInvalid; - if (!f.seek(0)) return BmpToMonoError::SeekStartFailed; + if (!f) return BmpReaderError::FileInvalid; + if (!f.seek(0)) return BmpReaderError::SeekStartFailed; // --- BMP FILE HEADER --- const uint16_t bfType = readLE16(f); - if (bfType != 0x4D42) return BmpToMonoError::NotBMP; + if (bfType != 0x4D42) return BmpReaderError::NotBMP; (void)readLE32(f); (void)readLE16(f); @@ -88,7 +88,7 @@ BmpToMonoError BmpToMono::convert24BitImpl(File& f, MonoBitmap& out, uint8_t thr // --- DIB HEADER --- const uint32_t biSize = readLE32(f); - if (biSize < 40) return BmpToMonoError::DIBTooSmall; + if (biSize < 40) return BmpReaderError::DIBTooSmall; const int32_t srcW = (int32_t)readLE32(f); int32_t srcHRaw = (int32_t)readLE32(f); @@ -96,9 +96,9 @@ BmpToMonoError BmpToMono::convert24BitImpl(File& f, MonoBitmap& out, uint8_t thr const uint16_t bpp = readLE16(f); const uint32_t comp = readLE32(f); - if (planes != 1) return BmpToMonoError::BadPlanes; - if (bpp != 24) return BmpToMonoError::UnsupportedBpp; - if (comp != 0) return BmpToMonoError::UnsupportedCompression; + if (planes != 1) return BmpReaderError::BadPlanes; + if (bpp != 24) return BmpReaderError::UnsupportedBpp; + if (comp != 0) return BmpReaderError::UnsupportedCompression; (void)readLE32(f); (void)readLE32(f); @@ -106,11 +106,11 @@ BmpToMonoError BmpToMono::convert24BitImpl(File& f, MonoBitmap& out, uint8_t thr (void)readLE32(f); (void)readLE32(f); - if (srcW <= 0) return BmpToMonoError::BadDimensions; + if (srcW <= 0) return BmpReaderError::BadDimensions; const bool topDown = (srcHRaw < 0); const int32_t srcH = topDown ? -srcHRaw : srcHRaw; - if (srcH <= 0) return BmpToMonoError::BadDimensions; + if (srcH <= 0) return BmpReaderError::BadDimensions; // Output dimensions out.width = rotate90CCW ? (int)srcH : (int)srcW; @@ -120,7 +120,7 @@ BmpToMonoError BmpToMono::convert24BitImpl(File& f, MonoBitmap& out, uint8_t thr out.len = outBytesPerRow * (size_t)out.height; out.data = (uint8_t*)malloc(out.len); - if (!out.data) return BmpToMonoError::OomOutput; + if (!out.data) return BmpReaderError::OomOutput; memset(out.data, 0xFF, out.len); // Source row stride (padded to 4 bytes) @@ -129,20 +129,20 @@ BmpToMonoError BmpToMono::convert24BitImpl(File& f, MonoBitmap& out, uint8_t thr if (!f.seek(bfOffBits)) { freeMonoBitmap(out); - return BmpToMonoError::SeekPixelDataFailed; + return BmpReaderError::SeekPixelDataFailed; } uint8_t* rowBuf = (uint8_t*)malloc(srcRowStride); if (!rowBuf) { freeMonoBitmap(out); - return BmpToMonoError::OomRowBuffer; + return BmpReaderError::OomRowBuffer; } for (int fileRow = 0; fileRow < (int)srcH; fileRow++) { if (f.read(rowBuf, srcRowStride) != (int)srcRowStride) { free(rowBuf); freeMonoBitmap(out); - return BmpToMonoError::ShortReadRow; + return BmpReaderError::ShortReadRow; } const int srcY = topDown ? fileRow : ((int)srcH - 1 - fileRow); @@ -170,5 +170,5 @@ BmpToMonoError BmpToMono::convert24BitImpl(File& f, MonoBitmap& out, uint8_t thr } free(rowBuf); - return BmpToMonoError::Ok; -} \ No newline at end of file + return BmpReaderError::Ok; +} diff --git a/lib/BmpToMono/BmpToMono.h b/lib/BmpReader/BmpReader.h similarity index 80% rename from lib/BmpToMono/BmpToMono.h rename to lib/BmpReader/BmpReader.h index ef2d3b6..83dc427 100644 --- a/lib/BmpToMono/BmpToMono.h +++ b/lib/BmpReader/BmpReader.h @@ -10,7 +10,7 @@ struct MonoBitmap { uint8_t* data = nullptr; // row-aligned, MSB-first, 1=white 0=black }; -enum class BmpToMonoError : uint8_t { +enum class BmpReaderError : uint8_t { Ok = 0, FileInvalid, SeekStartFailed, @@ -30,14 +30,14 @@ enum class BmpToMonoError : uint8_t { ShortReadRow, }; -class BmpToMono { +class BmpReader { public: // Rotate 90° counter-clockwise: (w,h) -> (h,w) // Used for converting portrait BMP (480x800) into landscape framebuffer (800x480) - static BmpToMonoError convert24BitRotate90CCW(File& file, MonoBitmap& out, uint8_t threshold = 160); + static BmpReaderError convert24BitRotate90CCW(File& file, MonoBitmap& out, uint8_t threshold = 160); static void freeMonoBitmap(MonoBitmap& bmp); - static const char* errorToString(BmpToMonoError err); + static const char* errorToString(BmpReaderError err); private: static uint16_t readLE16(File& f); @@ -54,5 +54,5 @@ class BmpToMono { buf[idx] |= mask; } - static BmpToMonoError convert24BitImpl(File& file, MonoBitmap& out, uint8_t threshold, bool rotate90CW); -}; \ No newline at end of file + static BmpReaderError convert24BitImpl(File& file, MonoBitmap& out, uint8_t threshold, bool rotate90CCW); +}; diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index 3509188..34b1b54 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -2,7 +2,7 @@ #include -#include "BmpToMono.h" +#include "BmpReader.h" void GfxRenderer::insertFont(const int fontId, EpdFontFamily font) { fontMap.insert({fontId, font}); } @@ -130,10 +130,10 @@ bool GfxRenderer::drawFullScreenBmp(File& file) { file.seek(0); // Ensure we're at the start of the file MonoBitmap bmp; - auto err = BmpToMono::convert24BitRotate90CCW(file, bmp); + auto err = BmpReader::convert24BitRotate90CCW(file, bmp); - if (err != BmpToMonoError::Ok) { - Serial.printf("[%lu] [GFX] BMP convert failed: %s\n", millis(), BmpToMono::errorToString(err)); + if (err != BmpReaderError::Ok) { + Serial.printf("[%lu] [GFX] BMP convert failed: %s\n", millis(), BmpReader::errorToString(err)); return false; } @@ -141,14 +141,14 @@ bool GfxRenderer::drawFullScreenBmp(File& file) { if (bmp.width != EInkDisplay::DISPLAY_WIDTH || bmp.height != EInkDisplay::DISPLAY_HEIGHT) { Serial.printf("[%lu] [GFX] drawFullScreenBmp: rotated BMP size %dx%d does not match panel %dx%d\n", millis(), bmp.width, bmp.height, EInkDisplay::DISPLAY_WIDTH, EInkDisplay::DISPLAY_HEIGHT); - BmpToMono::freeMonoBitmap(bmp); + BmpReader::freeMonoBitmap(bmp); return false; } // Full-screen blit einkDisplay.drawImage(bmp.data, 0, 0, bmp.width, bmp.height); - BmpToMono::freeMonoBitmap(bmp); + BmpReader::freeMonoBitmap(bmp); return true; } From dc2a6ca2f84e90a5a07993f4d0fcad4e3f638e1a Mon Sep 17 00:00:00 2001 From: Sam Davis Date: Mon, 15 Dec 2025 17:48:09 +1100 Subject: [PATCH 6/6] add support for 1, 8 and 32 bit bmps --- lib/BmpReader/BmpReader.cpp | 134 ++++++++++++++++++++------------ lib/BmpReader/BmpReader.h | 5 +- lib/GfxRenderer/GfxRenderer.cpp | 2 +- 3 files changed, 88 insertions(+), 53 deletions(-) diff --git a/lib/BmpReader/BmpReader.cpp b/lib/BmpReader/BmpReader.cpp index dcf5b7b..f71dbda 100644 --- a/lib/BmpReader/BmpReader.cpp +++ b/lib/BmpReader/BmpReader.cpp @@ -50,9 +50,9 @@ const char* BmpReader::errorToString(BmpReaderError err) { case BmpReaderError::BadPlanes: return "BadPlanes (!= 1)"; case BmpReaderError::UnsupportedBpp: - return "UnsupportedBpp (expected 24)"; + return "UnsupportedBpp (expected 24, 32 or 1)"; case BmpReaderError::UnsupportedCompression: - return "UnsupportedCompression (expected BI_RGB)"; + return "UnsupportedCompression (expected BI_RGB or BI_BITFIELDS for 32bpp)"; case BmpReaderError::BadDimensions: return "BadDimensions"; case BmpReaderError::SeekPixelDataFailed: @@ -67,44 +67,45 @@ const char* BmpReader::errorToString(BmpReaderError err) { return "Unknown"; } -BmpReaderError BmpReader::convert24BitRotate90CCW(File& file, MonoBitmap& out, uint8_t threshold) { - return convert24BitImpl(file, out, threshold, true); -} - -BmpReaderError BmpReader::convert24BitImpl(File& f, MonoBitmap& out, uint8_t threshold, bool rotate90CCW) { +BmpReaderError BmpReader::read(File& file, MonoBitmap& out, uint8_t threshold) { freeMonoBitmap(out); - if (!f) return BmpReaderError::FileInvalid; - if (!f.seek(0)) return BmpReaderError::SeekStartFailed; + if (!file) return BmpReaderError::FileInvalid; + if (!file.seek(0)) return BmpReaderError::SeekStartFailed; // --- BMP FILE HEADER --- - const uint16_t bfType = readLE16(f); + const uint16_t bfType = readLE16(file); if (bfType != 0x4D42) return BmpReaderError::NotBMP; - (void)readLE32(f); - (void)readLE16(f); - (void)readLE16(f); - const uint32_t bfOffBits = readLE32(f); + (void)readLE32(file); + (void)readLE16(file); + (void)readLE16(file); + const uint32_t bfOffBits = readLE32(file); // --- DIB HEADER --- - const uint32_t biSize = readLE32(f); + const uint32_t biSize = readLE32(file); if (biSize < 40) return BmpReaderError::DIBTooSmall; - const int32_t srcW = (int32_t)readLE32(f); - int32_t srcHRaw = (int32_t)readLE32(f); - const uint16_t planes = readLE16(f); - const uint16_t bpp = readLE16(f); - const uint32_t comp = readLE32(f); + const int32_t srcW = (int32_t)readLE32(file); + int32_t srcHRaw = (int32_t)readLE32(file); + const uint16_t planes = readLE16(file); + const uint16_t bpp = readLE16(file); + const uint32_t comp = readLE32(file); + const bool is24Bit = (bpp == 24); + const bool is32Bit = (bpp == 32); + const bool is8Bit = (bpp == 8); + const bool is1Bit = (bpp == 1); if (planes != 1) return BmpReaderError::BadPlanes; - if (bpp != 24) return BmpReaderError::UnsupportedBpp; - if (comp != 0) return BmpReaderError::UnsupportedCompression; + if (!is24Bit && !is32Bit && !is8Bit && !is1Bit) return BmpReaderError::UnsupportedBpp; + // Allow BI_RGB (0) for all, and BI_BITFIELDS (3) for 32bpp which is common for BGRA masks. + if (!(comp == 0 || (is32Bit && comp == 3))) return BmpReaderError::UnsupportedCompression; - (void)readLE32(f); - (void)readLE32(f); - (void)readLE32(f); - (void)readLE32(f); - (void)readLE32(f); + (void)readLE32(file); // biSizeImage + (void)readLE32(file); // biXPelsPerMeter + (void)readLE32(file); // biYPelsPerMeter + const uint32_t clrUsed = readLE32(file); + (void)readLE32(file); // biClrImportant if (srcW <= 0) return BmpReaderError::BadDimensions; @@ -112,9 +113,9 @@ BmpReaderError BmpReader::convert24BitImpl(File& f, MonoBitmap& out, uint8_t thr const int32_t srcH = topDown ? -srcHRaw : srcHRaw; if (srcH <= 0) return BmpReaderError::BadDimensions; - // Output dimensions - out.width = rotate90CCW ? (int)srcH : (int)srcW; - out.height = rotate90CCW ? (int)srcW : (int)srcH; + // Output dimensions after 90° CCW rotation + out.width = (int)srcH; + out.height = (int)srcW; const size_t outBytesPerRow = (size_t)(out.width + 7) / 8; out.len = outBytesPerRow * (size_t)out.height; @@ -123,11 +124,38 @@ BmpReaderError BmpReader::convert24BitImpl(File& f, MonoBitmap& out, uint8_t thr if (!out.data) return BmpReaderError::OomOutput; memset(out.data, 0xFF, out.len); - // Source row stride (padded to 4 bytes) - const uint32_t srcBytesPerRow24 = (uint32_t)srcW * 3u; - const uint32_t srcRowStride = (srcBytesPerRow24 + 3u) & ~3u; + // Palette for 8-bit indexed images + uint8_t paletteLum[256]; + if (is8Bit) { + for (int i = 0; i < 256; i++) paletteLum[i] = (uint8_t)i; // default grayscale ramp + uint32_t paletteCount = (clrUsed == 0) ? 256u : clrUsed; + if (paletteCount > 256u) paletteCount = 256u; + for (uint32_t i = 0; i < paletteCount; i++) { + const int b = file.read(); + const int g = file.read(); + const int r = file.read(); + (void)file.read(); // reserved + const uint8_t bb = (uint8_t)(b < 0 ? 0 : b); + const uint8_t gg = (uint8_t)(g < 0 ? 0 : g); + const uint8_t rr = (uint8_t)(r < 0 ? 0 : r); + paletteLum[i] = (uint8_t)((77u * rr + 150u * gg + 29u * bb) >> 8); + } + } - if (!f.seek(bfOffBits)) { + // Source row stride (padded to 4 bytes) + uint32_t bytesPerPixel = 0u; + if (is8Bit) { + bytesPerPixel = 1u; + } else if (is32Bit) { + bytesPerPixel = 4u; + } else if (is24Bit) { + bytesPerPixel = 3u; + } + const uint32_t srcBytesPerRow = + is1Bit ? ((uint32_t)srcW + 7u) / 8u : (uint32_t)srcW * bytesPerPixel; // bpp==1 ignores bytesPerPixel + const uint32_t srcRowStride = (srcBytesPerRow + 3u) & ~3u; + + if (!file.seek(bfOffBits)) { freeMonoBitmap(out); return BmpReaderError::SeekPixelDataFailed; } @@ -139,7 +167,7 @@ BmpReaderError BmpReader::convert24BitImpl(File& f, MonoBitmap& out, uint8_t thr } for (int fileRow = 0; fileRow < (int)srcH; fileRow++) { - if (f.read(rowBuf, srcRowStride) != (int)srcRowStride) { + if (file.read(rowBuf, srcRowStride) != (int)srcRowStride) { free(rowBuf); freeMonoBitmap(out); return BmpReaderError::ShortReadRow; @@ -148,23 +176,31 @@ BmpReaderError BmpReader::convert24BitImpl(File& f, MonoBitmap& out, uint8_t thr const int srcY = topDown ? fileRow : ((int)srcH - 1 - fileRow); for (int srcX = 0; srcX < (int)srcW; srcX++) { - const uint8_t b = rowBuf[srcX * 3 + 0]; - const uint8_t g = rowBuf[srcX * 3 + 1]; - const uint8_t r = rowBuf[srcX * 3 + 2]; - - const uint8_t lum = (uint8_t)((77u * r + 150u * g + 29u * b) >> 8); - bool isBlack = (lum < threshold); - - int outX, outY; - if (!rotate90CCW) { - outX = srcX; - outY = srcY; + bool isBlack; + if (is1Bit) { + const uint8_t byte = rowBuf[srcX >> 3]; + const uint8_t mask = (uint8_t)(0x80u >> (srcX & 7)); + const bool bitSet = (byte & mask) != 0; + // In 1bpp BMPs, palette index 0 is conventionally black and index 1 is white. + isBlack = !bitSet; + } else if (is8Bit) { + const uint8_t idx = rowBuf[srcX]; + const uint8_t lum = paletteLum[idx]; + isBlack = (lum < threshold); } else { - // 90° counter-clockwise: (x,y) -> (y, w-1-x) - outX = srcY; - outY = (int)srcW - 1 - srcX; + const uint8_t* px = &rowBuf[srcX * bytesPerPixel]; + const uint8_t b = px[0]; + const uint8_t g = px[1]; + const uint8_t r = px[2]; + + const uint8_t lum = (uint8_t)((77u * r + 150u * g + 29u * b) >> 8); + isBlack = (lum < threshold); } + // 90° counter-clockwise: (x,y) -> (y, w-1-x) + const int outX = srcY; + const int outY = (int)srcW - 1 - srcX; + setMonoPixel(out.data, out.width, outX, outY, isBlack); } } diff --git a/lib/BmpReader/BmpReader.h b/lib/BmpReader/BmpReader.h index 83dc427..24ec1d3 100644 --- a/lib/BmpReader/BmpReader.h +++ b/lib/BmpReader/BmpReader.h @@ -34,7 +34,8 @@ class BmpReader { public: // Rotate 90° counter-clockwise: (w,h) -> (h,w) // Used for converting portrait BMP (480x800) into landscape framebuffer (800x480) - static BmpReaderError convert24BitRotate90CCW(File& file, MonoBitmap& out, uint8_t threshold = 160); + // Supports 8-bit, 24-bit, 32-bit color and 1-bit monochrome BMPs. + static BmpReaderError read(File& file, MonoBitmap& out, uint8_t threshold = 160); static void freeMonoBitmap(MonoBitmap& bmp); static const char* errorToString(BmpReaderError err); @@ -53,6 +54,4 @@ class BmpReader { else buf[idx] |= mask; } - - static BmpReaderError convert24BitImpl(File& file, MonoBitmap& out, uint8_t threshold, bool rotate90CCW); }; diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index 34b1b54..d9b4614 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -130,7 +130,7 @@ bool GfxRenderer::drawFullScreenBmp(File& file) { file.seek(0); // Ensure we're at the start of the file MonoBitmap bmp; - auto err = BmpReader::convert24BitRotate90CCW(file, bmp); + auto err = BmpReader::read(file, bmp); if (err != BmpReaderError::Ok) { Serial.printf("[%lu] [GFX] BMP convert failed: %s\n", millis(), BmpReader::errorToString(err));