ESPHome 2026.9.0-dev
Loading...
Searching...
No Matches
bmp_decoder.cpp
Go to the documentation of this file.
1#include "bmp_decoder.h"
2
3#ifdef USE_RUNTIME_IMAGE_BMP
4
7#include "esphome/core/log.h"
8
9#include <cinttypes>
10
12
13static const char *const TAG = "image_decoder.bmp";
14
17 this->bits_per_pixel_ = 0;
18 this->compression_method_ = 0;
19 this->image_data_size_ = 0;
20 this->width_ = 0;
21 this->height_ = 0;
22 this->current_index_ = 0;
23 this->paint_index_ = 0;
24 // color_table_ is deliberately kept allocated so the next decode can reuse it
25 this->color_table_entries_ = 0;
26 this->data_offset_ = 0;
27 this->padding_bytes_ = 0;
28 this->width_bytes_ = 0;
29}
30
31int HOT BmpDecoder::decode(uint8_t *buffer, size_t size) {
32 size_t index = 0;
33 if (this->current_index_ == 0) {
34 if (size <= 14) {
35 return 0; // Need more data for file header
36 }
47 // Check if the file is a BMP image
48 if (buffer[0] != 'B' || buffer[1] != 'M') {
49 ESP_LOGE(TAG, "Not a BMP file");
51 }
52
53 // BMP file contains its own size in the header
54 size_t file_size = encode_uint32(buffer[5], buffer[4], buffer[3], buffer[2]);
55 if (this->expected_size_ == 0) {
56 this->expected_size_ = file_size; // Use file header size if not provided
57 }
58 this->data_offset_ = encode_uint32(buffer[13], buffer[12], buffer[11], buffer[10]);
59
60 this->current_index_ = 14;
61 index = 14;
62 }
63 if (this->current_index_ == 14) {
65 return 0; // Need more data for DIB header and color table
66 }
81 this->width_ = encode_uint32(buffer[21], buffer[20], buffer[19], buffer[18]);
82 this->height_ = encode_uint32(buffer[25], buffer[24], buffer[23], buffer[22]);
83 if (this->width_ <= 0 || this->height_ <= 0) {
84 ESP_LOGE(TAG, "Invalid image dimensions: (%zdx%zd)", this->width_, this->height_);
86 }
87 this->bits_per_pixel_ = encode_uint16(buffer[29], buffer[28]);
88 this->compression_method_ = encode_uint32(buffer[33], buffer[32], buffer[31], buffer[30]);
89 this->image_data_size_ = encode_uint32(buffer[37], buffer[36], buffer[35], buffer[34]);
90 this->color_table_entries_ = encode_uint32(buffer[49], buffer[48], buffer[47], buffer[46]);
91
92 switch (this->bits_per_pixel_) {
93 case 1:
94 this->width_bytes_ = (this->width_ + 7) / 8;
95 this->padding_bytes_ = (4 - (this->width_bytes_ % 4)) % 4;
96 break;
97 case 8: {
98 this->width_bytes_ = this->width_;
99 if (this->color_table_entries_ == 0) {
100 this->color_table_entries_ = 256;
101 } else if (this->color_table_entries_ > 256) {
102 ESP_LOGE(TAG, "Too many color table entries: %" PRIu32, this->color_table_entries_);
104 }
105 size_t header_size = encode_uint32(buffer[17], buffer[16], buffer[15], buffer[14]);
106 size_t offset = 14 + header_size;
107
109 this->color_table_ = std::make_unique<uint32_t[]>(this->color_table_entries_);
111 }
112
113 for (size_t i = 0; i < this->color_table_entries_; i++) {
114 this->color_table_[i] = encode_uint32(buffer[offset + i * 4 + 3], buffer[offset + i * 4 + 2],
115 buffer[offset + i * 4 + 1], buffer[offset + i * 4]);
116 }
117
118 this->padding_bytes_ = (4 - (this->width_bytes_ % 4)) % 4;
119
120 break;
121 }
122 case 24:
123 this->width_bytes_ = this->width_ * 3;
124 if (this->width_bytes_ % 4 != 0) {
125 this->padding_bytes_ = 4 - (this->width_bytes_ % 4);
126 this->width_bytes_ += this->padding_bytes_;
127 }
128 break;
129 default:
130 ESP_LOGE(TAG, "Unsupported bits per pixel: %d", this->bits_per_pixel_);
132 }
133
134 if (this->compression_method_ != 0) {
135 ESP_LOGE(TAG, "Unsupported compression method: %" PRIu32, this->compression_method_);
137 }
138
139 if (!this->set_size(this->width_, this->height_)) {
141 }
142 this->current_index_ = this->data_offset_;
143 index = this->data_offset_;
144 }
145 switch (this->bits_per_pixel_) {
146 case 1: {
147 size_t width = static_cast<size_t>(this->width_);
148 while (index < size) {
149 size_t x = this->paint_index_ % width;
150 size_t y = static_cast<size_t>(this->height_ - 1) - (this->paint_index_ / width);
151 size_t remaining_in_row = width - x;
152 uint8_t pixels_in_byte = std::min<size_t>(remaining_in_row, 8);
153 bool end_of_row = remaining_in_row <= 8;
154 size_t needed = 1 + (end_of_row ? this->padding_bytes_ : 0);
155 if (index + needed > size) {
156 this->decoded_bytes_ += index;
157 return index;
158 }
159 uint8_t current_byte = buffer[index];
160 for (uint8_t i = 0; i < pixels_in_byte; i++) {
161 Color c = (current_byte & (1 << (7 - i))) ? display::COLOR_ON : display::COLOR_OFF;
162 this->draw(x + i, y, 1, 1, c);
163 }
164 this->paint_index_ += pixels_in_byte;
165 this->current_index_++;
166 index++;
167 // End of pixel row: skip row padding bytes (4-byte alignment)
168 if (end_of_row && this->padding_bytes_ > 0) {
169 index += this->padding_bytes_;
170 this->current_index_ += this->padding_bytes_;
171 }
172 }
173 break;
174 }
175 case 8: {
176 size_t width = static_cast<size_t>(this->width_);
177 size_t last_col = width - 1;
178 while (index < size) {
179 size_t x = this->paint_index_ % width;
180 size_t y = static_cast<size_t>(this->height_ - 1) - (this->paint_index_ / width);
181 size_t needed = 1 + ((x == last_col) ? this->padding_bytes_ : 0);
182 if (index + needed > size) {
183 this->decoded_bytes_ += index;
184 return index;
185 }
186
187 uint8_t color_index = buffer[index];
188 if (color_index >= this->color_table_entries_) {
189 ESP_LOGE(TAG, "Invalid color index: %u", color_index);
191 }
192
193 uint32_t rgb = this->color_table_[color_index];
194 uint8_t b = rgb & 0xff;
195 uint8_t g = (rgb >> 8) & 0xff;
196 uint8_t r = (rgb >> 16) & 0xff;
197 this->draw(x, y, 1, 1, Color(r, g, b));
198 this->paint_index_++;
199 this->current_index_++;
200 index++;
201 if (x == last_col && this->padding_bytes_ > 0) {
202 index += this->padding_bytes_;
203 this->current_index_ += this->padding_bytes_;
204 }
205 }
206 break;
207 }
208 case 24: {
209 size_t width = static_cast<size_t>(this->width_);
210 size_t last_col = width - 1;
211 while (index < size) {
212 size_t x = this->paint_index_ % width;
213 size_t y = static_cast<size_t>(this->height_ - 1) - (this->paint_index_ / width);
214 size_t needed = 3 + ((x == last_col) ? this->padding_bytes_ : 0);
215 if (index + needed > size) {
216 this->decoded_bytes_ += index;
217 return index;
218 }
219 uint8_t b = buffer[index];
220 uint8_t g = buffer[index + 1];
221 uint8_t r = buffer[index + 2];
222 this->draw(x, y, 1, 1, Color(r, g, b));
223 this->paint_index_++;
224 this->current_index_ += 3;
225 index += 3;
226 if (x == last_col && this->padding_bytes_ > 0) {
227 index += this->padding_bytes_;
228 this->current_index_ += this->padding_bytes_;
229 }
230 }
231 break;
232 }
233 default:
234 ESP_LOGE(TAG, "Unsupported bits per pixel: %d", this->bits_per_pixel_);
236 }
237 this->decoded_bytes_ += size;
238 return size;
239};
240
241} // namespace esphome::runtime_image
242
243#endif // USE_RUNTIME_IMAGE_BMP
std::unique_ptr< uint32_t[]> color_table_
Definition bmp_decoder.h:39
int HOT decode(uint8_t *buffer, size_t size) override
virtual void reset()
Reset the decoder state, ending any decoding session.
void draw(int x, int y, int w, int h, const Color &color)
Fill a rectangle on the display_buffer using the defined color.
bool set_size(int width, int height)
Request the image to be resized once the actual dimensions are known.
constexpr Color COLOR_ON(255, 255, 255, 255)
Turn the pixel ON.
constexpr Color COLOR_OFF(0, 0, 0, 0)
Turn the pixel OFF.
uint16_t size
Definition helpers.cpp:25
constexpr uint32_t encode_uint32(uint8_t byte1, uint8_t byte2, uint8_t byte3, uint8_t byte4)
Encode a 32-bit value given four bytes in most to least significant byte order.
Definition helpers.h:892
constexpr uint16_t encode_uint16(uint8_t msb, uint8_t lsb)
Encode a 16-bit value given the most and least significant byte.
Definition helpers.h:884
static void uint32_t
uint16_t x
Definition tt21100.cpp:5
uint16_t y
Definition tt21100.cpp:6