| 83 | } |
| 84 | |
| 85 | BmpReaderError Bitmap::parseHeaders() { |
| 86 | if (!file) return BmpReaderError::FileInvalid; |
| 87 | if (!file.seek(0)) return BmpReaderError::SeekStartFailed; |
| 88 | |
| 89 | // --- BMP FILE HEADER --- |
| 90 | const uint16_t bfType = readLE16(file); |
| 91 | if (bfType != 0x4D42) return BmpReaderError::NotBMP; |
| 92 | |
| 93 | file.seekCur(8); |
| 94 | bfOffBits = readLE32(file); |
| 95 | |
| 96 | // --- DIB HEADER --- |
| 97 | const uint32_t biSize = readLE32(file); |
| 98 | if (biSize < 40) return BmpReaderError::DIBTooSmall; |
| 99 | |
| 100 | width = static_cast<int32_t>(readLE32(file)); |
| 101 | const auto rawHeight = static_cast<int32_t>(readLE32(file)); |
| 102 | topDown = rawHeight < 0; |
| 103 | height = topDown ? -rawHeight : rawHeight; |
| 104 | |
| 105 | const uint16_t planes = readLE16(file); |
| 106 | bpp = readLE16(file); |
| 107 | const uint32_t comp = readLE32(file); |
| 108 | const bool validBpp = bpp == 1 || bpp == 2 || bpp == 4 || bpp == 8 || bpp == 24 || bpp == 32; |
| 109 | |
| 110 | if (planes != 1) return BmpReaderError::BadPlanes; |
| 111 | if (!validBpp) return BmpReaderError::UnsupportedBpp; |
| 112 | // Allow BI_RGB (0) for all, and BI_BITFIELDS (3) for 32bpp which is common for BGRA masks. |
| 113 | if (!(comp == 0 || (bpp == 32 && comp == 3))) return BmpReaderError::UnsupportedCompression; |
| 114 | |
| 115 | file.seekCur(12); // biSizeImage, biXPelsPerMeter, biYPelsPerMeter |
| 116 | colorsUsed = readLE32(file); |
| 117 | // BMP spec: colorsUsed==0 means default (2^bpp for paletted formats) |
| 118 | if (colorsUsed == 0 && bpp <= 8) colorsUsed = 1u << bpp; |
| 119 | if (colorsUsed > 256u) return BmpReaderError::PaletteTooLarge; |
| 120 | file.seekCur(4); // biClrImportant |
| 121 | |
| 122 | if (width <= 0 || height <= 0) return BmpReaderError::BadDimensions; |
| 123 | |
| 124 | // Safety limits to prevent memory issues on ESP32 |
| 125 | constexpr int MAX_IMAGE_WIDTH = 2048; |
| 126 | constexpr int MAX_IMAGE_HEIGHT = 3072; |
| 127 | if (width > MAX_IMAGE_WIDTH || height > MAX_IMAGE_HEIGHT) { |
| 128 | return BmpReaderError::ImageTooLarge; |
| 129 | } |
| 130 | |
| 131 | // Pre-calculate Row Bytes to avoid doing this every row |
| 132 | rowBytes = (width * bpp + 31) / 32 * 4; |
| 133 | |
| 134 | for (int i = 0; i < 256; i++) paletteLum[i] = static_cast<uint8_t>(i); |
| 135 | if (colorsUsed > 0) { |
| 136 | for (uint32_t i = 0; i < colorsUsed; i++) { |
| 137 | uint8_t rgb[4]; |
| 138 | file.read(rgb, 4); // Read B, G, R, Reserved in one go |
| 139 | paletteLum[i] = (77u * rgb[2] + 150u * rgb[1] + 29u * rgb[0]) >> 8; |
| 140 | } |
| 141 | } |
| 142 |
no test coverage detected