From 49598aec3eecfcba030248525acbb531ff585577 Mon Sep 17 00:00:00 2001 From: Brackyt <60280126+Brackyt@users.noreply.github.com> Date: Sun, 25 Jan 2026 20:35:31 +0100 Subject: [PATCH] feat: draw icons with transparent background --- lib/GfxRenderer/GfxRenderer.cpp | 61 ++++++++++++++++++++++++++ lib/GfxRenderer/GfxRenderer.h | 1 + lib/ThemeEngine/src/LayoutElements.cpp | 2 +- 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index 32642757..62f790ea 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -821,6 +821,67 @@ void GfxRenderer::drawBitmap1Bit(const Bitmap &bitmap, const int x, const int y, free(rowBytes); } +void GfxRenderer::drawTransparentBitmap(const Bitmap& bitmap, const int x, const int y, const int w, const int h) const { + // Similar to drawBitmap1Bit but strictly skips 1s (white) in the source 1-bit data + // The Bitmap reader returns 2-bit packed data where 0-2=Black and 3=White for 1-bit sources + + float scale = 1.0f; + bool isScaled = false; + if (w > 0 && bitmap.getWidth() > w) { + scale = static_cast(w) / static_cast(bitmap.getWidth()); + isScaled = true; + } + if (h > 0 && bitmap.getHeight() > h) { + scale = std::min(scale, static_cast(h) / static_cast(bitmap.getHeight())); + isScaled = true; + } + + const int outputRowSize = (bitmap.getWidth() + 3) / 4; + auto* outputRow = static_cast(malloc(outputRowSize)); + auto* rowBytes = static_cast(malloc(bitmap.getRowBytes())); + + if (!outputRow || !rowBytes) { + Serial.printf("[%lu] [GFX] !! Failed to allocate BMP row buffers\n", millis()); + free(outputRow); + free(rowBytes); + return; + } + + for (int bmpY = 0; bmpY < bitmap.getHeight(); bmpY++) { + if (bitmap.readNextRow(outputRow, rowBytes) != BmpReaderError::Ok) { + free(outputRow); + free(rowBytes); + return; + } + + const int bmpYOffset = bitmap.isTopDown() ? bmpY : bitmap.getHeight() - 1 - bmpY; + int screenY = y + (isScaled ? static_cast(std::floor(bmpYOffset * scale)) : bmpYOffset); + + if (screenY >= getScreenHeight()) continue; + if (screenY < 0) continue; + + for (int bmpX = 0; bmpX < bitmap.getWidth(); bmpX++) { + int screenX = x + (isScaled ? static_cast(std::floor(bmpX * scale)) : bmpX); + + if (screenX >= getScreenWidth()) break; + if (screenX < 0) continue; + + // Get 2-bit value. For 1-bit BMPs from BmpReader: + // Black in BMP -> 0 (Black) + // White in BMP -> 3 (White) + const uint8_t val = outputRow[bmpX / 4] >> (6 - ((bmpX * 2) % 8)) & 0x3; + + // Only draw if NOT white (val < 3) + if (val < 3) { + drawPixel(screenX, screenY, true); // True = Black + } + } + } + + free(outputRow); + free(rowBytes); +} + void GfxRenderer::fillPolygon(const int *xPoints, const int *yPoints, int numPoints, bool state) const { if (numPoints < 3) diff --git a/lib/GfxRenderer/GfxRenderer.h b/lib/GfxRenderer/GfxRenderer.h index bfc9b6a8..20a6c8e0 100644 --- a/lib/GfxRenderer/GfxRenderer.h +++ b/lib/GfxRenderer/GfxRenderer.h @@ -80,6 +80,7 @@ public: int maxHeight, float cropX = 0, float cropY = 0) const; void drawBitmap1Bit(const Bitmap &bitmap, int x, int y, int maxWidth, int maxHeight) const; + void drawTransparentBitmap(const Bitmap& bitmap, int x, int y, int w, int h) const; void draw2BitImage(const uint8_t data[], int x, int y, int w, int h) const; void fillPolygon(const int *xPoints, const int *yPoints, int numPoints, bool state = true) const; diff --git a/lib/ThemeEngine/src/LayoutElements.cpp b/lib/ThemeEngine/src/LayoutElements.cpp index e1c8dca8..b8bc2d4e 100644 --- a/lib/ThemeEngine/src/LayoutElements.cpp +++ b/lib/ThemeEngine/src/LayoutElements.cpp @@ -45,7 +45,7 @@ void Icon::draw(const GfxRenderer& renderer, const ThemeContext& context) { if (data && !data->empty()) { Bitmap bmp(data->data(), data->size()); if (bmp.parseHeaders() == BmpReaderError::Ok) { - renderer.drawBitmap(bmp, absX, absY, w, h); + renderer.drawTransparentBitmap(bmp, absX, absY, w, h); markClean(); return; }