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));