TinyGPU
Loading...
Searching...
No Matches
BMPParser.h
Go to the documentation of this file.
1#pragma once
2
3#include <stddef.h>
4#include <stdint.h>
5
6#include <algorithm>
7
9#include "ISurface.h"
10#include "TinyGPU/Vector.h"
11
12namespace tinygpu {
13
14/**
15 * @brief Incremental BMP decoder for TinyGPU framebuffers.
16 *
17 * The parser accepts BMP file bytes through write(), buffers them until enough
18 * data is available, then decodes supported uncompressed BMP formats directly
19 * into the provided ISurface target using RGB565 pixels.
20 */
21template <typename RGB_T = RGB565>
22class BMPParser {
23public:
24 /// Represents the parser status.
25 enum class Status {
28 Error,
29 };
30
31 /// Creates a parser that decodes into the provided framebuffer target.
32 BMPParser(ISurface<RGB_T>& target) : target_(target) {}
33
34 /// Resets the parser to decode a new BMP image.
35 void reset() {
36 buffer_.clear();
37 palette_.clear();
38 status_ = Status::Collecting;
39 errorMessage_ = nullptr;
40 imageWidth_ = 0;
41 imageHeight_ = 0;
42 topDown_ = false;
43 }
44
45 /// Writes BMP data incrementally to the parser.
47 if (data == nullptr || length == 0 || status_ != Status::Collecting) {
48 return 0;
49 }
50
51 buffer_.insert(buffer_.end(), data, data + length);
53 return length;
54 }
55
56 /// Writes BMP data incrementally to the parser.
58 return write(static_cast<const uint8_t*>(data), length);
59 }
60
61 /// Returns true when the full image has been decoded.
62 bool isComplete() const { return status_ == Status::Complete; }
63
64 /// Returns true when parsing failed.
65 bool hasError() const { return status_ == Status::Error; }
66
67 /// Returns the current parser status.
68 Status status() const { return status_; }
69
70 /// Returns the decoded image width in pixels.
71 size_t width() const { return imageWidth_; }
72
73 /// Returns the decoded image height in pixels.
74 size_t height() const { return imageHeight_; }
75
76 /// Returns the latest error message, if any.
77 const char* errorMessage() const { return errorMessage_; }
78
79 protected:
80 /// Stores parsed BMP metadata.
81 struct HeaderInfo {
82 uint32_t fileSize = 0;
83 uint32_t pixelOffset = 0;
84 uint32_t dibHeaderSize = 0;
85 int32_t width = 0;
86 int32_t height = 0;
87 uint16_t planes = 0;
88 uint16_t bitsPerPixel = 0;
89 uint32_t compression = 0;
90 uint32_t imageSize = 0;
91 uint32_t colorsUsed = 0;
92 uint32_t redMask = 0;
93 uint32_t greenMask = 0;
94 uint32_t blueMask = 0;
95 uint32_t alphaMask = 0;
96 };
97
98 ISurface<RGB_T>& target_;
101 Status status_ = Status::Collecting;
102 const char* errorMessage_ = nullptr;
105 bool topDown_ = false;
106
107 static constexpr uint32_t kCompressionRgb = 0;
108 static constexpr uint32_t kCompressionBitfields = 3;
109
110 void tryDecode() {
111 if (status_ != Status::Collecting) {
112 return;
113 }
114 if (buffer_.size() < 26) {
115 return;
116 }
117
118 HeaderInfo header;
119 if (!parseHeader(header)) {
120 return;
121 }
122
123 const uint32_t requiredSize = requiredDataSize(header);
124 if (requiredSize == 0 || buffer_.size() < requiredSize) {
125 return;
126 }
127
128 if (!decode(header)) {
129 return;
130 }
131
132 status_ = Status::Complete;
133 }
134
135 bool parseHeader(HeaderInfo& header) {
136 if (buffer_.size() < 54) {
137 return false;
138 }
139
140 if (readU16(0) != 0x4D42) {
141 setError("Invalid BMP signature");
142 return false;
143 }
144
145 header.fileSize = readU32(2);
146 header.pixelOffset = readU32(10);
147 header.dibHeaderSize = readU32(14);
148 if (header.dibHeaderSize < 40) {
149 setError("Unsupported BMP DIB header");
150 return false;
151 }
152
153 if (buffer_.size() < 14 + header.dibHeaderSize) {
154 return false;
155 }
156
157 header.width = readS32(18);
158 header.height = readS32(22);
159 header.planes = readU16(26);
160 header.bitsPerPixel = readU16(28);
161 header.compression = readU32(30);
162 header.imageSize = readU32(34);
163 header.colorsUsed = readU32(46);
164
165 if (header.planes != 1) {
166 setError("Unsupported BMP plane count");
167 return false;
168 }
169 if (header.width <= 0 || header.height == 0) {
170 setError("Invalid BMP dimensions");
171 return false;
172 }
173
174 if (header.bitsPerPixel != 8 && header.bitsPerPixel != 16 &&
175 header.bitsPerPixel != 24 && header.bitsPerPixel != 32) {
176 setError("Unsupported BMP bit depth");
177 return false;
178 }
179
180 if (header.compression != kCompressionRgb &&
181 header.compression != kCompressionBitfields) {
182 setError("Unsupported BMP compression");
183 return false;
184 }
185
186 topDown_ = header.height < 0;
187 imageWidth_ = static_cast<size_t>(header.width);
188 imageHeight_ =
189 static_cast<size_t>(topDown_ ? -header.height : header.height);
190
191 if (!parseBitMasks(header)) {
192 return false;
193 }
194 return parsePalette(header);
195 }
196
197 bool parseBitMasks(HeaderInfo& header) {
198 if (header.bitsPerPixel == 16 || header.bitsPerPixel == 32) {
199 if (header.compression == kCompressionBitfields) {
200 const size_t maskOffset =
201 14 + (header.dibHeaderSize >= 52 ? 40 : header.dibHeaderSize);
202 const size_t requiredMasks = header.dibHeaderSize >= 56 ? 16 : 12;
203 if (buffer_.size() < maskOffset + requiredMasks) {
204 return false;
205 }
206
207 header.redMask = readU32(maskOffset);
208 header.greenMask = readU32(maskOffset + 4);
209 header.blueMask = readU32(maskOffset + 8);
210 if (requiredMasks == 16) {
211 header.alphaMask = readU32(maskOffset + 12);
212 }
213 } else if (header.bitsPerPixel == 16) {
214 header.redMask = 0x7C00;
215 header.greenMask = 0x03E0;
216 header.blueMask = 0x001F;
217 } else {
218 header.redMask = 0x00FF0000;
219 header.greenMask = 0x0000FF00;
220 header.blueMask = 0x000000FF;
221 }
222 }
223
224 return true;
225 }
226
227 bool parsePalette(const HeaderInfo& header) {
228 palette_.clear();
229 if (header.bitsPerPixel != 8) {
230 return true;
231 }
232
233 const uint32_t paletteEntryCount =
234 header.colorsUsed != 0 ? header.colorsUsed : 256;
235 const size_t paletteOffset = 14 + header.dibHeaderSize;
236 const size_t paletteSize = static_cast<size_t>(paletteEntryCount) * 4;
237 if (buffer_.size() < paletteOffset + paletteSize) {
238 return false;
239 }
240
241 palette_.reserve(paletteEntryCount);
242 for (uint32_t index = 0; index < paletteEntryCount; ++index) {
243 const size_t entryOffset =
244 paletteOffset + (static_cast<size_t>(index) * 4);
245 palette_.push_back(RGB565(buffer_[entryOffset + 2],
246 buffer_[entryOffset + 1],
247 buffer_[entryOffset]));
248 }
249 return true;
250 }
251
252 uint32_t requiredDataSize(const HeaderInfo& header) const {
253 const uint32_t rowStride = bmpRowStride(header.bitsPerPixel, imageWidth_);
254 const uint32_t computedImageSize =
255 rowStride * static_cast<uint32_t>(imageHeight_);
256 const uint32_t pixelBytes =
257 header.imageSize != 0 ? header.imageSize : computedImageSize;
258 const uint32_t computedSize = header.pixelOffset + pixelBytes;
259
260 if (header.fileSize != 0) {
261 return std::max(header.fileSize, computedSize);
262 }
263 return computedSize;
264 }
265
266 bool decode(const HeaderInfo& header) {
267 if (header.pixelOffset >= buffer_.size()) {
268 setError("Invalid BMP pixel offset");
269 return false;
270 }
271
272 target_.resize(imageWidth_, imageHeight_);
273 const uint32_t rowStride = bmpRowStride(header.bitsPerPixel, imageWidth_);
274 for (size_t row = 0; row < imageHeight_; ++row) {
275 const size_t sourceRow = topDown_ ? row : (imageHeight_ - 1 - row);
276 const size_t rowOffset = header.pixelOffset + (sourceRow * rowStride);
277 if (rowOffset + rowStride > buffer_.size()) {
278 setError("Incomplete BMP pixel data");
279 return false;
280 }
281
282 if (!decodeRow(header, rowOffset, row)) {
283 return false;
284 }
285 }
286
287 return true;
288 }
289
290 bool decodeRow(const HeaderInfo& header, size_t rowOffset, size_t targetY) {
291 switch (header.bitsPerPixel) {
292 case 8:
293 return decode8BitRow(rowOffset, targetY);
294 case 16:
295 return decode16BitRow(header, rowOffset, targetY);
296 case 24:
297 return decode24BitRow(rowOffset, targetY);
298 case 32:
299 return decode32BitRow(header, rowOffset, targetY);
300 default:
301 setError("Unsupported BMP row format");
302 return false;
303 }
304 }
305
306 bool decode8BitRow(size_t rowOffset, size_t targetY) {
307 if (palette_.empty()) {
308 setError("Missing BMP palette");
309 return false;
310 }
311
312 for (size_t x = 0; x < imageWidth_; ++x) {
313 const uint8_t paletteIndex = buffer_[rowOffset + x];
314 if (paletteIndex >= palette_.size()) {
315 setError("BMP palette index out of range");
316 return false;
317 }
318 target_.setPixel(x, targetY, palette_[paletteIndex]);
319 }
320 return true;
321 }
322
323 bool decode16BitRow(const HeaderInfo& header, size_t rowOffset,
324 size_t targetY) {
325 for (size_t x = 0; x < imageWidth_; ++x) {
326 const uint16_t pixelValue =
327 static_cast<uint16_t>(buffer_[rowOffset + (x * 2)]) |
328 (static_cast<uint16_t>(buffer_[rowOffset + (x * 2) + 1]) << 8);
329 target_.setPixel(x, targetY, decodeMaskedPixel(pixelValue, header));
330 }
331 return true;
332 }
333
334 bool decode24BitRow(size_t rowOffset, size_t targetY) {
335 for (size_t x = 0; x < imageWidth_; ++x) {
336 const size_t pixelOffset = rowOffset + (x * 3);
337 target_.setPixel(x, targetY,
338 RGB565(buffer_[pixelOffset + 2],
339 buffer_[pixelOffset + 1], buffer_[pixelOffset]));
340 }
341 return true;
342 }
343
344 bool decode32BitRow(const HeaderInfo& header, size_t rowOffset,
345 size_t targetY) {
346 for (size_t x = 0; x < imageWidth_; ++x) {
347 const size_t pixelOffset = rowOffset + (x * 4);
348 const uint32_t pixelValue =
349 static_cast<uint32_t>(buffer_[pixelOffset]) |
350 (static_cast<uint32_t>(buffer_[pixelOffset + 1]) << 8) |
351 (static_cast<uint32_t>(buffer_[pixelOffset + 2]) << 16) |
352 (static_cast<uint32_t>(buffer_[pixelOffset + 3]) << 24);
353 target_.setPixel(x, targetY, decodeMaskedPixel(pixelValue, header));
354 }
355 return true;
356 }
357
358 RGB565 decodeMaskedPixel(uint32_t pixelValue,
359 const HeaderInfo& header) const {
360 const uint8_t red = extractChannel(pixelValue, header.redMask);
361 const uint8_t green = extractChannel(pixelValue, header.greenMask);
362 const uint8_t blue = extractChannel(pixelValue, header.blueMask);
363 return RGB565(red, green, blue);
364 }
365
366 static uint8_t extractChannel(uint32_t pixelValue, uint32_t mask) {
367 if (mask == 0) {
368 return 0;
369 }
370
371 uint8_t shift = 0;
372 uint8_t bits = 0;
373 while (((mask >> shift) & 0x1U) == 0U) {
374 ++shift;
375 }
376 while (((mask >> (shift + bits)) & 0x1U) != 0U) {
377 ++bits;
378 }
379
380 const uint32_t value = (pixelValue & mask) >> shift;
381 const uint32_t maxValue = (1UL << bits) - 1UL;
382 return maxValue == 0 ? 0 : static_cast<uint8_t>((value * 255UL) / maxValue);
383 }
384
385 static uint32_t bmpRowStride(uint16_t bitsPerPixel, size_t width) {
386 const uint32_t bitsPerRow = static_cast<uint32_t>(width) * bitsPerPixel;
387 return ((bitsPerRow + 31U) / 32U) * 4U;
388 }
389
390 uint16_t readU16(size_t offset) const {
391 return static_cast<uint16_t>(buffer_[offset]) |
392 (static_cast<uint16_t>(buffer_[offset + 1]) << 8);
393 }
394
395 uint32_t readU32(size_t offset) const {
396 return static_cast<uint32_t>(buffer_[offset]) |
397 (static_cast<uint32_t>(buffer_[offset + 1]) << 8) |
398 (static_cast<uint32_t>(buffer_[offset + 2]) << 16) |
399 (static_cast<uint32_t>(buffer_[offset + 3]) << 24);
400 }
401
402 int32_t readS32(size_t offset) const {
403 return static_cast<int32_t>(readU32(offset));
404 }
405
406 void setError(const char* message) {
407 status_ = Status::Error;
408 errorMessage_ = message;
409 }
410};
411
412} // namespace tinygpu
Incremental BMP decoder for TinyGPU framebuffers.
Definition: BMPParser.h:22
int32_t readS32(size_t offset) const
Definition: BMPParser.h:402
static uint8_t extractChannel(uint32_t pixelValue, uint32_t mask)
Definition: BMPParser.h:366
bool decode32BitRow(const HeaderInfo &header, size_t rowOffset, size_t targetY)
Definition: BMPParser.h:344
size_t write(const uint8_t *data, size_t length)
Writes BMP data incrementally to the parser.
Definition: BMPParser.h:46
bool topDown_
Definition: BMPParser.h:105
bool parseBitMasks(HeaderInfo &header)
Definition: BMPParser.h:197
bool decode8BitRow(size_t rowOffset, size_t targetY)
Definition: BMPParser.h:306
bool isComplete() const
Returns true when the full image has been decoded.
Definition: BMPParser.h:62
Vector< RGB_T > palette_
Definition: BMPParser.h:100
Status
Represents the parser status.
Definition: BMPParser.h:25
size_t write(uint8_t *data, size_t length)
Writes BMP data incrementally to the parser.
Definition: BMPParser.h:57
void tryDecode()
Definition: BMPParser.h:110
bool parseHeader(HeaderInfo &header)
Definition: BMPParser.h:135
size_t imageHeight_
Definition: BMPParser.h:104
bool hasError() const
Returns true when parsing failed.
Definition: BMPParser.h:65
uint32_t readU32(size_t offset) const
Definition: BMPParser.h:395
size_t imageWidth_
Definition: BMPParser.h:103
void setError(const char *message)
Definition: BMPParser.h:406
size_t width() const
Returns the decoded image width in pixels.
Definition: BMPParser.h:71
uint32_t requiredDataSize(const HeaderInfo &header) const
Definition: BMPParser.h:252
Vector< uint8_t > buffer_
Definition: BMPParser.h:99
bool decode16BitRow(const HeaderInfo &header, size_t rowOffset, size_t targetY)
Definition: BMPParser.h:323
static constexpr uint32_t kCompressionBitfields
Definition: BMPParser.h:108
Status status_
Definition: BMPParser.h:101
const char * errorMessage_
Definition: BMPParser.h:102
static uint32_t bmpRowStride(uint16_t bitsPerPixel, size_t width)
Definition: BMPParser.h:385
bool parsePalette(const HeaderInfo &header)
Definition: BMPParser.h:227
ISurface< RGB_T > & target_
Definition: BMPParser.h:98
bool decode24BitRow(size_t rowOffset, size_t targetY)
Definition: BMPParser.h:334
bool decode(const HeaderInfo &header)
Definition: BMPParser.h:266
void reset()
Resets the parser to decode a new BMP image.
Definition: BMPParser.h:35
RGB565 decodeMaskedPixel(uint32_t pixelValue, const HeaderInfo &header) const
Definition: BMPParser.h:358
Status status() const
Returns the current parser status.
Definition: BMPParser.h:68
size_t height() const
Returns the decoded image height in pixels.
Definition: BMPParser.h:74
const char * errorMessage() const
Returns the latest error message, if any.
Definition: BMPParser.h:77
static constexpr uint32_t kCompressionRgb
Definition: BMPParser.h:107
bool decodeRow(const HeaderInfo &header, size_t rowOffset, size_t targetY)
Definition: BMPParser.h:290
BMPParser(ISurface< RGB_T > &target)
Creates a parser that decodes into the provided framebuffer target.
Definition: BMPParser.h:32
uint16_t readU16(size_t offset) const
Definition: BMPParser.h:390
RGB color stored in 16-bit RGB565 format.
Definition: RGB565.h:13
RGB565(uint8_t r, uint8_t g, uint8_t b)
Creates a color from 8-bit red, green, and blue components.
Definition: RGB565.h:20
Definition: AVIWriter.h:9
Stores parsed BMP metadata.
Definition: BMPParser.h:81
uint32_t alphaMask
Definition: BMPParser.h:95
int32_t width
Definition: BMPParser.h:85
uint32_t pixelOffset
Definition: BMPParser.h:83
uint32_t fileSize
Definition: BMPParser.h:82
uint32_t redMask
Definition: BMPParser.h:92
uint32_t blueMask
Definition: BMPParser.h:94
int32_t height
Definition: BMPParser.h:86
uint16_t planes
Definition: BMPParser.h:87
uint16_t bitsPerPixel
Definition: BMPParser.h:88
uint32_t dibHeaderSize
Definition: BMPParser.h:84
uint32_t colorsUsed
Definition: BMPParser.h:91
uint32_t greenMask
Definition: BMPParser.h:93
uint32_t compression
Definition: BMPParser.h:89
uint32_t imageSize
Definition: BMPParser.h:90