ESPHome 2025.5.2
Loading...
Searching...
No Matches
online_image.cpp
Go to the documentation of this file.
1#include "online_image.h"
2
3#include "esphome/core/log.h"
4
5static const char *const TAG = "online_image";
6
7#include "image_decoder.h"
8
9#ifdef USE_ONLINE_IMAGE_BMP_SUPPORT
10#include "bmp_image.h"
11#endif
12#ifdef USE_ONLINE_IMAGE_JPEG_SUPPORT
13#include "jpeg_image.h"
14#endif
15#ifdef USE_ONLINE_IMAGE_PNG_SUPPORT
16#include "png_image.h"
17#endif
18
19namespace esphome {
20namespace online_image {
21
23
24inline bool is_color_on(const Color &color) {
25 // This produces the most accurate monochrome conversion, but is slightly slower.
26 // return (0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b) > 127;
27
28 // Approximation using fast integer computations; produces acceptable results
29 // Equivalent to 0.25 * R + 0.5 * G + 0.25 * B
30 return ((color.r >> 2) + (color.g >> 1) + (color.b >> 2)) & 0x80;
31}
32
33OnlineImage::OnlineImage(const std::string &url, int width, int height, ImageFormat format, ImageType type,
34 image::Transparency transparency, uint32_t download_buffer_size)
35 : Image(nullptr, 0, 0, type, transparency),
36 buffer_(nullptr),
37 download_buffer_(download_buffer_size),
38 download_buffer_initial_size_(download_buffer_size),
39 format_(format),
40 fixed_width_(width),
41 fixed_height_(height) {
42 this->set_url(url);
43}
44
45void OnlineImage::draw(int x, int y, display::Display *display, Color color_on, Color color_off) {
46 if (this->data_start_) {
47 Image::draw(x, y, display, color_on, color_off);
48 } else if (this->placeholder_) {
49 this->placeholder_->draw(x, y, display, color_on, color_off);
50 }
51}
52
54 if (this->buffer_) {
55 ESP_LOGV(TAG, "Deallocating old buffer...");
56 this->allocator_.deallocate(this->buffer_, this->get_buffer_size_());
57 this->data_start_ = nullptr;
58 this->buffer_ = nullptr;
59 this->width_ = 0;
60 this->height_ = 0;
61 this->buffer_width_ = 0;
62 this->buffer_height_ = 0;
63 this->end_connection_();
64 }
65}
66
67size_t OnlineImage::resize_(int width_in, int height_in) {
68 int width = this->fixed_width_;
69 int height = this->fixed_height_;
70 if (this->is_auto_resize_()) {
71 width = width_in;
72 height = height_in;
73 if (this->width_ != width && this->height_ != height) {
74 this->release();
75 }
76 }
77 size_t new_size = this->get_buffer_size_(width, height);
78 if (this->buffer_) {
79 // Buffer already allocated => no need to resize
80 return new_size;
81 }
82 ESP_LOGD(TAG, "Allocating new buffer of %zu bytes", new_size);
83 this->buffer_ = this->allocator_.allocate(new_size);
84 if (this->buffer_ == nullptr) {
85 ESP_LOGE(TAG, "allocation of %zu bytes failed. Biggest block in heap: %zu Bytes", new_size,
87 this->end_connection_();
88 return 0;
89 }
90 this->buffer_width_ = width;
91 this->buffer_height_ = height;
92 this->width_ = width;
93 ESP_LOGV(TAG, "New size: (%d, %d)", width, height);
94 return new_size;
95}
96
98 if (this->decoder_) {
99 ESP_LOGW(TAG, "Image already being updated.");
100 return;
101 }
102 ESP_LOGI(TAG, "Updating image %s", this->url_.c_str());
103
104 std::list<http_request::Header> headers = {};
105
106 http_request::Header accept_header;
107 accept_header.name = "Accept";
108 std::string accept_mime_type;
109 switch (this->format_) {
110#ifdef USE_ONLINE_IMAGE_BMP_SUPPORT
111 case ImageFormat::BMP:
112 accept_mime_type = "image/bmp";
113 break;
114#endif // USE_ONLINE_IMAGE_BMP_SUPPORT
115#ifdef USE_ONLINE_IMAGE_JPEG_SUPPORT
117 accept_mime_type = "image/jpeg";
118 break;
119#endif // USE_ONLINE_IMAGE_JPEG_SUPPORT
120#ifdef USE_ONLINE_IMAGE_PNG_SUPPORT
121 case ImageFormat::PNG:
122 accept_mime_type = "image/png";
123 break;
124#endif // USE_ONLINE_IMAGE_PNG_SUPPORT
125 default:
126 accept_mime_type = "image/*";
127 }
128 accept_header.value = accept_mime_type + ",*/*;q=0.8";
129
130 headers.push_back(accept_header);
131
132 this->downloader_ = this->parent_->get(this->url_, headers);
133
134 if (this->downloader_ == nullptr) {
135 ESP_LOGE(TAG, "Download failed.");
136 this->end_connection_();
137 this->download_error_callback_.call();
138 return;
139 }
140
141 int http_code = this->downloader_->status_code;
142 if (http_code == HTTP_CODE_NOT_MODIFIED) {
143 // Image hasn't changed on server. Skip download.
144 this->end_connection_();
145 return;
146 }
147 if (http_code != HTTP_CODE_OK) {
148 ESP_LOGE(TAG, "HTTP result: %d", http_code);
149 this->end_connection_();
150 this->download_error_callback_.call();
151 return;
152 }
153
154 ESP_LOGD(TAG, "Starting download");
155 size_t total_size = this->downloader_->content_length;
156
157#ifdef USE_ONLINE_IMAGE_BMP_SUPPORT
158 if (this->format_ == ImageFormat::BMP) {
159 ESP_LOGD(TAG, "Allocating BMP decoder");
160 this->decoder_ = make_unique<BmpDecoder>(this);
161 }
162#endif // USE_ONLINE_IMAGE_BMP_SUPPORT
163#ifdef USE_ONLINE_IMAGE_JPEG_SUPPORT
164 if (this->format_ == ImageFormat::JPEG) {
165 ESP_LOGD(TAG, "Allocating JPEG decoder");
167 }
168#endif // USE_ONLINE_IMAGE_JPEG_SUPPORT
169#ifdef USE_ONLINE_IMAGE_PNG_SUPPORT
170 if (this->format_ == ImageFormat::PNG) {
171 ESP_LOGD(TAG, "Allocating PNG decoder");
172 this->decoder_ = make_unique<PngDecoder>(this);
173 }
174#endif // USE_ONLINE_IMAGE_PNG_SUPPORT
175
176 if (!this->decoder_) {
177 ESP_LOGE(TAG, "Could not instantiate decoder. Image format unsupported: %d", this->format_);
178 this->end_connection_();
179 this->download_error_callback_.call();
180 return;
181 }
182 auto prepare_result = this->decoder_->prepare(total_size);
183 if (prepare_result < 0) {
184 this->end_connection_();
185 this->download_error_callback_.call();
186 return;
187 }
188 ESP_LOGI(TAG, "Downloading image (Size: %zu)", total_size);
189 this->start_time_ = ::time(nullptr);
190}
191
193 if (!this->decoder_) {
194 // Not decoding at the moment => nothing to do.
195 return;
196 }
197 if (!this->downloader_ || this->decoder_->is_finished()) {
198 this->data_start_ = buffer_;
199 this->width_ = buffer_width_;
200 this->height_ = buffer_height_;
201 ESP_LOGD(TAG, "Image fully downloaded, read %zu bytes, width/height = %d/%d", this->downloader_->get_bytes_read(),
202 this->width_, this->height_);
203 ESP_LOGD(TAG, "Total time: %lds", ::time(nullptr) - this->start_time_);
204 this->end_connection_();
205 this->download_finished_callback_.call();
206 return;
207 }
208 if (this->downloader_ == nullptr) {
209 ESP_LOGE(TAG, "Downloader not instantiated; cannot download");
210 return;
211 }
212 size_t available = this->download_buffer_.free_capacity();
213 if (available) {
214 // Some decoders need to fully download the image before downloading.
215 // In case of huge images, don't wait blocking until the whole image has been downloaded,
216 // use smaller chunks
217 available = std::min(available, this->download_buffer_initial_size_);
218 auto len = this->downloader_->read(this->download_buffer_.append(), available);
219 if (len > 0) {
221 auto fed = this->decoder_->decode(this->download_buffer_.data(), this->download_buffer_.unread());
222 if (fed < 0) {
223 ESP_LOGE(TAG, "Error when decoding image.");
224 this->end_connection_();
225 this->download_error_callback_.call();
226 return;
227 }
228 this->download_buffer_.read(fed);
229 }
230 }
231}
232
235 if (color.g == 1 && color.r == 0 && color.b == 0) {
236 color.g = 0;
237 }
238 if (color.w < 0x80) {
239 color.r = 0;
240 color.g = this->type_ == ImageType::IMAGE_TYPE_RGB565 ? 4 : 1;
241 color.b = 0;
242 }
243 }
244}
245
246void OnlineImage::draw_pixel_(int x, int y, Color color) {
247 if (!this->buffer_) {
248 ESP_LOGE(TAG, "Buffer not allocated!");
249 return;
250 }
251 if (x < 0 || y < 0 || x >= this->buffer_width_ || y >= this->buffer_height_) {
252 ESP_LOGE(TAG, "Tried to paint a pixel (%d,%d) outside the image!", x, y);
253 return;
254 }
255 uint32_t pos = this->get_position_(x, y);
256 switch (this->type_) {
258 const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u;
259 pos = x + y * width_8;
260 auto bitno = 0x80 >> (pos % 8u);
261 pos /= 8u;
262 auto on = is_color_on(color);
263 if (this->has_transparency() && color.w < 0x80)
264 on = false;
265 if (on) {
266 this->buffer_[pos] |= bitno;
267 } else {
268 this->buffer_[pos] &= ~bitno;
269 }
270 break;
271 }
273 uint8_t gray = static_cast<uint8_t>(0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b);
275 if (gray == 1) {
276 gray = 0;
277 }
278 if (color.w < 0x80) {
279 gray = 1;
280 }
282 if (color.w != 0xFF)
283 gray = color.w;
284 }
285 this->buffer_[pos] = gray;
286 break;
287 }
289 this->map_chroma_key(color);
290 uint16_t col565 = display::ColorUtil::color_to_565(color);
291 this->buffer_[pos + 0] = static_cast<uint8_t>((col565 >> 8) & 0xFF);
292 this->buffer_[pos + 1] = static_cast<uint8_t>(col565 & 0xFF);
294 this->buffer_[pos + 2] = color.w;
295 }
296 break;
297 }
299 this->map_chroma_key(color);
300 this->buffer_[pos + 0] = color.r;
301 this->buffer_[pos + 1] = color.g;
302 this->buffer_[pos + 2] = color.b;
304 this->buffer_[pos + 3] = color.w;
305 }
306 break;
307 }
308 }
309}
310
312 if (this->downloader_) {
313 this->downloader_->end();
314 this->downloader_ = nullptr;
315 }
316 this->decoder_.reset();
317 this->download_buffer_.reset();
318}
319
320bool OnlineImage::validate_url_(const std::string &url) {
321 if ((url.length() < 8) || (url.find("http") != 0) || (url.find("://") == std::string::npos)) {
322 ESP_LOGE(TAG, "URL is invalid and/or must be prefixed with 'http://' or 'https://'");
323 return false;
324 }
325 return true;
326}
327
328void OnlineImage::add_on_finished_callback(std::function<void()> &&callback) {
329 this->download_finished_callback_.add(std::move(callback));
330}
331
332void OnlineImage::add_on_error_callback(std::function<void()> &&callback) {
333 this->download_error_callback_.add(std::move(callback));
334}
335
336} // namespace online_image
337} // namespace esphome
esphome::http_request::HttpRequestComponent * parent_
Definition helpers.h:550
void deallocate(T *p, size_t n)
Definition helpers.h:742
size_t get_max_free_block_size() const
Return the maximum size block this allocator could allocate.
Definition helpers.h:770
T * allocate(size_t n)
Definition helpers.h:704
static uint16_t color_to_565(Color color, ColorOrder color_order=ColorOrder::COLOR_ORDER_RGB)
std::shared_ptr< HttpContainer > get(const std::string &url)
const uint8_t * data_start_
Definition image.h:55
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:8
uint8_t * data(size_t offset=0)
CallbackManager< void()> download_finished_callback_
size_t resize_(int width, int height)
Resize the image buffer to the requested dimensions.
bool validate_url_(const std::string &url)
int get_position_(int x, int y) const
void draw_pixel_(int x, int y, Color color)
Draw a pixel into the buffer.
friend void ImageDecoder::draw(int x, int y, int w, int h, const Color &color)
void add_on_error_callback(std::function< void()> &&callback)
std::unique_ptr< ImageDecoder > decoder_
void add_on_finished_callback(std::function< void()> &&callback)
RAMAllocator< uint8_t > allocator_
int buffer_width_
Actual width of the current image.
ESPHOME_ALWAYS_INLINE bool is_auto_resize_() const
void set_url(const std::string &url)
Set the URL to download the image from.
std::shared_ptr< http_request::HttpContainer > downloader_
const int fixed_width_
width requested on configuration, or 0 if non specified.
CallbackManager< void()> download_error_callback_
const int fixed_height_
height requested on configuration, or 0 if non specified.
int buffer_height_
Actual height of the current image.
size_t download_buffer_initial_size_
This is the initial size of the download buffer, not the current size.
OnlineImage(const std::string &url, int width, int height, ImageFormat format, image::ImageType type, image::Transparency transparency, uint32_t buffer_size)
Construct a new OnlineImage object.
void release()
Release the buffer storing the image.
uint8_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
Format that the image is encoded with.
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
std::string size_t len
Definition helpers.h:302
std::unique_ptr< T > make_unique(Args &&...args)
Definition helpers.h:86
uint8_t w
Definition color.h:26
uint8_t g
Definition color.h:18
uint8_t b
Definition color.h:22
uint8_t r
Definition color.h:14
uint16_t x
Definition tt21100.cpp:5
uint16_t y
Definition tt21100.cpp:6