ESPHome 2026.3.0-dev
Loading...
Searching...
No Matches
runtime_image.cpp
Go to the documentation of this file.
1#include "runtime_image.h"
2#include "image_decoder.h"
3#include "esphome/core/log.h"
5#include <cstring>
6
7#ifdef USE_RUNTIME_IMAGE_BMP
8#include "bmp_decoder.h"
9#endif
10#ifdef USE_RUNTIME_IMAGE_JPEG
11#include "jpeg_decoder.h"
12#endif
13#ifdef USE_RUNTIME_IMAGE_PNG
14#include "png_decoder.h"
15#endif
16
17namespace esphome::runtime_image {
18
19static const char *const TAG = "runtime_image";
20
21inline bool is_color_on(const Color &color) {
22 // This produces the most accurate monochrome conversion, but is slightly slower.
23 // return (0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b) > 127;
24
25 // Approximation using fast integer computations; produces acceptable results
26 // Equivalent to 0.25 * R + 0.5 * G + 0.25 * B
27 return ((color.r >> 2) + (color.g >> 1) + (color.b >> 2)) & 0x80;
28}
29
31 image::Image *placeholder, bool is_big_endian, int fixed_width, int fixed_height)
32 : Image(nullptr, 0, 0, type, transparency),
33 format_(format),
34 fixed_width_(fixed_width),
35 fixed_height_(fixed_height),
36 placeholder_(placeholder),
37 is_big_endian_(is_big_endian) {}
38
40
41int RuntimeImage::resize(int width, int height) {
42 // Use fixed dimensions if specified (0 means auto-resize)
43 int target_width = this->fixed_width_ ? this->fixed_width_ : width;
44 int target_height = this->fixed_height_ ? this->fixed_height_ : height;
45
46 size_t result = this->resize_buffer_(target_width, target_height);
47 if (result > 0 && this->progressive_display_) {
48 // Update display dimensions for progressive display
49 this->width_ = this->buffer_width_;
50 this->height_ = this->buffer_height_;
51 this->data_start_ = this->buffer_;
52 }
53 return result;
54}
55
56void RuntimeImage::draw_pixel(int x, int y, const Color &color) {
57 if (!this->buffer_) {
58 ESP_LOGE(TAG, "Buffer not allocated!");
59 return;
60 }
61 if (x < 0 || y < 0 || x >= this->buffer_width_ || y >= this->buffer_height_) {
62 ESP_LOGE(TAG, "Tried to paint a pixel (%d,%d) outside the image!", x, y);
63 return;
64 }
65
66 switch (this->type_) {
68 const uint32_t width_8 = ((this->buffer_width_ + 7u) / 8u) * 8u;
69 uint32_t pos = x + y * width_8;
70 auto bitno = 0x80 >> (pos % 8u);
71 pos /= 8u;
72 auto on = is_color_on(color);
73 if (this->has_transparency() && color.w < 0x80)
74 on = false;
75 if (on) {
76 this->buffer_[pos] |= bitno;
77 } else {
78 this->buffer_[pos] &= ~bitno;
79 }
80 break;
81 }
83 uint32_t pos = this->get_position_(x, y);
84 auto gray = static_cast<uint8_t>(0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b);
86 if (gray == 1) {
87 gray = 0;
88 }
89 if (color.w < 0x80) {
90 gray = 1;
91 }
93 if (color.w != 0xFF)
94 gray = color.w;
95 }
96 this->buffer_[pos] = gray;
97 break;
98 }
100 uint32_t pos = this->get_position_(x, y);
101 Color mapped_color = color;
102 this->map_chroma_key(mapped_color);
103 uint16_t rgb565 = display::ColorUtil::color_to_565(mapped_color);
104 if (this->is_big_endian_) {
105 this->buffer_[pos + 0] = static_cast<uint8_t>((rgb565 >> 8) & 0xFF);
106 this->buffer_[pos + 1] = static_cast<uint8_t>(rgb565 & 0xFF);
107 } else {
108 this->buffer_[pos + 0] = static_cast<uint8_t>(rgb565 & 0xFF);
109 this->buffer_[pos + 1] = static_cast<uint8_t>((rgb565 >> 8) & 0xFF);
110 }
112 this->buffer_[pos + 2] = color.w;
113 }
114 break;
115 }
117 uint32_t pos = this->get_position_(x, y);
118 Color mapped_color = color;
119 this->map_chroma_key(mapped_color);
120 this->buffer_[pos + 0] = mapped_color.r;
121 this->buffer_[pos + 1] = mapped_color.g;
122 this->buffer_[pos + 2] = mapped_color.b;
124 this->buffer_[pos + 3] = color.w;
125 }
126 break;
127 }
128 }
129}
130
133 if (color.g == 1 && color.r == 0 && color.b == 0) {
134 color.g = 0;
135 }
136 if (color.w < 0x80) {
137 color.r = 0;
138 color.g = this->type_ == image::IMAGE_TYPE_RGB565 ? 4 : 1;
139 color.b = 0;
140 }
141 }
142}
143
144void RuntimeImage::draw(int x, int y, display::Display *display, Color color_on, Color color_off) {
145 if (this->data_start_) {
146 // If we have a complete image, use the base class draw method
147 Image::draw(x, y, display, color_on, color_off);
148 } else if (this->placeholder_) {
149 // Show placeholder while the runtime image is not available
150 this->placeholder_->draw(x, y, display, color_on, color_off);
151 }
152 // If no image is loaded and no placeholder, nothing to draw
153}
154
155bool RuntimeImage::begin_decode(size_t expected_size) {
156 if (this->decoder_) {
157 ESP_LOGW(TAG, "Decoding already in progress");
158 return false;
159 }
160
161 this->decoder_ = this->create_decoder_();
162 if (!this->decoder_) {
163 ESP_LOGE(TAG, "Failed to create decoder for format %d", this->format_);
164 return false;
165 }
166
167 this->total_size_ = expected_size;
168 this->decoded_bytes_ = 0;
169
170 // Initialize decoder
171 int result = this->decoder_->prepare(expected_size);
172 if (result < 0) {
173 ESP_LOGE(TAG, "Failed to prepare decoder: %d", result);
174 this->decoder_ = nullptr;
175 return false;
176 }
177
178 return true;
179}
180
181int RuntimeImage::feed_data(uint8_t *data, size_t len) {
182 if (!this->decoder_) {
183 ESP_LOGE(TAG, "No decoder initialized");
184 return -1;
185 }
186
187 int consumed = this->decoder_->decode(data, len);
188 if (consumed > 0) {
189 this->decoded_bytes_ += consumed;
190 }
191
192 return consumed;
193}
194
196 if (!this->decoder_) {
197 return false;
198 }
199
200 // Finalize the image for display
201 if (!this->progressive_display_) {
202 // Only now make the image visible
203 this->width_ = this->buffer_width_;
204 this->height_ = this->buffer_height_;
205 this->data_start_ = this->buffer_;
206 }
207
208 // Clean up decoder
209 this->decoder_ = nullptr;
210
211 ESP_LOGD(TAG, "Decoding complete: %dx%d, %zu bytes", this->width_, this->height_, this->decoded_bytes_);
212 return true;
213}
214
216 if (!this->decoder_) {
217 return false;
218 }
219 return this->decoder_->is_finished();
220}
221
223 this->release_buffer_();
224 // Reset decoder separately — release() can be called from within the decoder
225 // (via set_size -> resize -> resize_buffer_), so we must not destroy the decoder here.
226 // The decoder lifecycle is managed by begin_decode()/end_decode().
227 this->decoder_ = nullptr;
228}
229
231 if (this->buffer_) {
232 ESP_LOGV(TAG, "Releasing buffer of size %zu", this->get_buffer_size_(this->buffer_width_, this->buffer_height_));
234 this->buffer_ = nullptr;
235 this->data_start_ = nullptr;
236 this->width_ = 0;
237 this->height_ = 0;
238 this->buffer_width_ = 0;
239 this->buffer_height_ = 0;
240 }
241}
242
243size_t RuntimeImage::resize_buffer_(int width, int height) {
244 size_t new_size = this->get_buffer_size_(width, height);
245
246 if (this->buffer_ && this->buffer_width_ == width && this->buffer_height_ == height) {
247 // Buffer already allocated with correct size
248 return new_size;
249 }
250
251 // Release old buffer if dimensions changed
252 if (this->buffer_) {
253 this->release_buffer_();
254 }
255
256 ESP_LOGD(TAG, "Allocating buffer: %dx%d, %zu bytes", width, height, new_size);
257 this->buffer_ = this->allocator_.allocate(new_size);
258
259 if (!this->buffer_) {
260 ESP_LOGE(TAG, "Failed to allocate %zu bytes. Largest free block: %zu", new_size,
262 return 0;
263 }
264
265 // Clear buffer
266 memset(this->buffer_, 0, new_size);
267
268 this->buffer_width_ = width;
269 this->buffer_height_ = height;
270
271 return new_size;
272}
273
274size_t RuntimeImage::get_buffer_size_(int width, int height) const {
275 return (this->get_bpp() * width + 7u) / 8u * height;
276}
277
278int RuntimeImage::get_position_(int x, int y) const { return (x + y * this->buffer_width_) * this->get_bpp() / 8; }
279
280std::unique_ptr<ImageDecoder> RuntimeImage::create_decoder_() {
281 switch (this->format_) {
282#ifdef USE_RUNTIME_IMAGE_BMP
283 case BMP:
284 return make_unique<BmpDecoder>(this);
285#endif
286#ifdef USE_RUNTIME_IMAGE_JPEG
287 case JPEG:
288 return make_unique<JpegDecoder>(this);
289#endif
290#ifdef USE_RUNTIME_IMAGE_PNG
291 case PNG:
292 return make_unique<PngDecoder>(this);
293#endif
294 default:
295 ESP_LOGE(TAG, "Unsupported image format: %d", this->format_);
296 return nullptr;
297 }
298}
299
300} // namespace esphome::runtime_image
void deallocate(T *p, size_t n)
Definition helpers.h:1723
size_t get_max_free_block_size() const
Return the maximum size block this allocator could allocate.
Definition helpers.h:1751
T * allocate(size_t n)
Definition helpers.h:1685
static uint16_t color_to_565(Color color, ColorOrder color_order=ColorOrder::COLOR_ORDER_RGB)
const uint8_t * data_start_
Definition image.h:55
int get_bpp() const
Definition image.h:34
bool has_transparency() const
Definition image.h:41
ImageType type_
Definition image.h:54
Transparency transparency_
Definition image.h:56
void draw(int x, int y, display::Display *display, Color color_on, Color color_off) override
Definition image.cpp:9
size_t get_buffer_size_(int width, int height) const
Get the buffer size in bytes for given dimensions.
RAMAllocator< uint8_t > allocator_
bool is_big_endian_
Whether the image is stored in big-endian format.
void draw(int x, int y, display::Display *display, Color color_on, Color color_off) override
int buffer_width_
Actual width of the current image.
bool end_decode()
Complete the decoding process.
int get_position_(int x, int y) const
Get the position in the buffer for a pixel.
const int fixed_height_
Fixed height requested on configuration, or 0 if not specified.
void release_buffer_()
Release only the image buffer without resetting the decoder.
int resize(int width, int height)
Resize the image buffer to the requested dimensions.
int feed_data(uint8_t *data, size_t len)
Feed data to the decoder.
const int fixed_width_
Fixed width requested on configuration, or 0 if not specified.
bool is_decode_finished() const
Check if the decoder has finished processing all data.
void release()
Release the image buffer and free memory.
image::Image * placeholder_
Placeholder image to show when the runtime image is not available.
RuntimeImage(ImageFormat format, image::ImageType type, image::Transparency transparency, image::Image *placeholder=nullptr, bool is_big_endian=false, int fixed_width=0, int fixed_height=0)
Construct a new RuntimeImage object.
const ImageFormat format_
The image format this RuntimeImage is configured to decode.
size_t resize_buffer_(int width, int height)
Resize the image buffer to the requested dimensions.
std::unique_ptr< ImageDecoder > decoder_
std::unique_ptr< ImageDecoder > create_decoder_()
Create decoder instance for the image's format.
void draw_pixel(int x, int y, const Color &color)
int buffer_height_
Actual height of the current image.
bool begin_decode(size_t expected_size=0)
Begin decoding an image.
uint16_t type
@ TRANSPARENCY_ALPHA_CHANNEL
Definition image.h:22
@ TRANSPARENCY_CHROMA_KEY
Definition image.h:21
@ IMAGE_TYPE_GRAYSCALE
Definition image.h:14
@ IMAGE_TYPE_BINARY
Definition image.h:13
@ IMAGE_TYPE_RGB565
Definition image.h:16
@ IMAGE_TYPE_RGB
Definition image.h:15
bool is_color_on(const Color &color)
ImageFormat
Image format types that can be decoded dynamically.
std::string size_t len
Definition helpers.h:692
size_t size_t pos
Definition helpers.h:729
uint8_t w
Definition color.h:42
uint8_t g
Definition color.h:34
uint8_t b
Definition color.h:38
uint8_t r
Definition color.h:30
uint16_t x
Definition tt21100.cpp:5
uint16_t y
Definition tt21100.cpp:6