ESPHome  2024.9.0
online_image.cpp
Go to the documentation of this file.
1 #include "online_image.h"
2 
3 #include "esphome/core/log.h"
4 
5 static const char *const TAG = "online_image";
6 
7 #include "image_decoder.h"
8 
9 #ifdef USE_ONLINE_IMAGE_PNG_SUPPORT
10 #include "png_image.h"
11 #endif
12 
13 namespace esphome {
14 namespace online_image {
15 
16 using image::ImageType;
17 
18 inline bool is_color_on(const Color &color) {
19  // This produces the most accurate monochrome conversion, but is slightly slower.
20  // return (0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b) > 127;
21 
22  // Approximation using fast integer computations; produces acceptable results
23  // Equivalent to 0.25 * R + 0.5 * G + 0.25 * B
24  return ((color.r >> 2) + (color.g >> 1) + (color.b >> 2)) & 0x80;
25 }
26 
27 OnlineImage::OnlineImage(const std::string &url, int width, int height, ImageFormat format, ImageType type,
28  uint32_t download_buffer_size)
29  : Image(nullptr, 0, 0, type),
30  buffer_(nullptr),
31  download_buffer_(download_buffer_size),
32  format_(format),
33  fixed_width_(width),
34  fixed_height_(height) {
35  this->set_url(url);
36 }
37 
38 void OnlineImage::draw(int x, int y, display::Display *display, Color color_on, Color color_off) {
39  if (this->data_start_) {
40  Image::draw(x, y, display, color_on, color_off);
41  } else if (this->placeholder_) {
42  this->placeholder_->draw(x, y, display, color_on, color_off);
43  }
44 }
45 
47  if (this->buffer_) {
48  ESP_LOGD(TAG, "Deallocating old buffer...");
49  this->allocator_.deallocate(this->buffer_, this->get_buffer_size_());
50  this->data_start_ = nullptr;
51  this->buffer_ = nullptr;
52  this->width_ = 0;
53  this->height_ = 0;
54  this->buffer_width_ = 0;
55  this->buffer_height_ = 0;
56  this->end_connection_();
57  }
58 }
59 
60 bool OnlineImage::resize_(int width_in, int height_in) {
61  int width = this->fixed_width_;
62  int height = this->fixed_height_;
63  if (this->auto_resize_()) {
64  width = width_in;
65  height = height_in;
66  if (this->width_ != width && this->height_ != height) {
67  this->release();
68  }
69  }
70  if (this->buffer_) {
71  return false;
72  }
73  auto new_size = this->get_buffer_size_(width, height);
74  ESP_LOGD(TAG, "Allocating new buffer of %d Bytes...", new_size);
76  this->buffer_ = this->allocator_.allocate(new_size);
77  if (this->buffer_) {
78  this->buffer_width_ = width;
79  this->buffer_height_ = height;
80  this->width_ = width;
81  ESP_LOGD(TAG, "New size: (%d, %d)", width, height);
82  } else {
83 #if defined(USE_ESP8266)
84  // NOLINTNEXTLINE(readability-static-accessed-through-instance)
85  int max_block = ESP.getMaxFreeBlockSize();
86 #elif defined(USE_ESP32)
87  int max_block = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL);
88 #else
89  int max_block = -1;
90 #endif
91  ESP_LOGE(TAG, "allocation failed. Biggest block in heap: %d Bytes", max_block);
92  this->end_connection_();
93  return false;
94  }
95  return true;
96 }
97 
99  if (this->decoder_) {
100  ESP_LOGW(TAG, "Image already being updated.");
101  return;
102  } else {
103  ESP_LOGI(TAG, "Updating image");
104  }
105 
106  this->downloader_ = this->parent_->get(this->url_);
107 
108  if (this->downloader_ == nullptr) {
109  ESP_LOGE(TAG, "Download failed.");
110  this->end_connection_();
111  this->download_error_callback_.call();
112  return;
113  }
114 
115  int http_code = this->downloader_->status_code;
116  if (http_code == HTTP_CODE_NOT_MODIFIED) {
117  // Image hasn't changed on server. Skip download.
118  this->end_connection_();
119  return;
120  }
121  if (http_code != HTTP_CODE_OK) {
122  ESP_LOGE(TAG, "HTTP result: %d", http_code);
123  this->end_connection_();
124  this->download_error_callback_.call();
125  return;
126  }
127 
128  ESP_LOGD(TAG, "Starting download");
129  size_t total_size = this->downloader_->content_length;
130 
131 #ifdef USE_ONLINE_IMAGE_PNG_SUPPORT
132  if (this->format_ == ImageFormat::PNG) {
133  this->decoder_ = esphome::make_unique<PngDecoder>(this);
134  }
135 #endif // ONLINE_IMAGE_PNG_SUPPORT
136 
137  if (!this->decoder_) {
138  ESP_LOGE(TAG, "Could not instantiate decoder. Image format unsupported.");
139  this->end_connection_();
140  this->download_error_callback_.call();
141  return;
142  }
143  this->decoder_->prepare(total_size);
144  ESP_LOGI(TAG, "Downloading image");
145 }
146 
148  if (!this->decoder_) {
149  // Not decoding at the moment => nothing to do.
150  return;
151  }
152  if (!this->downloader_ || this->decoder_->is_finished()) {
153  ESP_LOGD(TAG, "Image fully downloaded");
154  this->data_start_ = buffer_;
155  this->width_ = buffer_width_;
156  this->height_ = buffer_height_;
157  this->end_connection_();
158  this->download_finished_callback_.call();
159  return;
160  }
161  if (this->downloader_ == nullptr) {
162  ESP_LOGE(TAG, "Downloader not instantiated; cannot download");
163  return;
164  }
165  size_t available = this->download_buffer_.free_capacity();
166  if (available) {
167  auto len = this->downloader_->read(this->download_buffer_.append(), available);
168  if (len > 0) {
169  this->download_buffer_.write(len);
170  auto fed = this->decoder_->decode(this->download_buffer_.data(), this->download_buffer_.unread());
171  if (fed < 0) {
172  ESP_LOGE(TAG, "Error when decoding image.");
173  this->end_connection_();
174  this->download_error_callback_.call();
175  return;
176  }
177  this->download_buffer_.read(fed);
178  }
179  }
180 }
181 
182 void OnlineImage::draw_pixel_(int x, int y, Color color) {
183  if (!this->buffer_) {
184  ESP_LOGE(TAG, "Buffer not allocated!");
185  return;
186  }
187  if (x < 0 || y < 0 || x >= this->buffer_width_ || y >= this->buffer_height_) {
188  ESP_LOGE(TAG, "Tried to paint a pixel (%d,%d) outside the image!", x, y);
189  return;
190  }
191  uint32_t pos = this->get_position_(x, y);
192  switch (this->type_) {
194  const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u;
195  const uint32_t pos = x + y * width_8;
196  if ((this->has_transparency() && color.w > 127) || is_color_on(color)) {
197  this->buffer_[pos / 8u] |= (0x80 >> (pos % 8u));
198  } else {
199  this->buffer_[pos / 8u] &= ~(0x80 >> (pos % 8u));
200  }
201  break;
202  }
204  uint8_t gray = static_cast<uint8_t>(0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b);
205  if (this->has_transparency()) {
206  if (gray == 1) {
207  gray = 0;
208  }
209  if (color.w < 0x80) {
210  gray = 1;
211  }
212  }
213  this->buffer_[pos] = gray;
214  break;
215  }
217  uint16_t col565 = display::ColorUtil::color_to_565(color);
218  if (this->has_transparency()) {
219  if (col565 == 0x0020) {
220  col565 = 0;
221  }
222  if (color.w < 0x80) {
223  col565 = 0x0020;
224  }
225  }
226  this->buffer_[pos + 0] = static_cast<uint8_t>((col565 >> 8) & 0xFF);
227  this->buffer_[pos + 1] = static_cast<uint8_t>(col565 & 0xFF);
228  break;
229  }
231  this->buffer_[pos + 0] = color.r;
232  this->buffer_[pos + 1] = color.g;
233  this->buffer_[pos + 2] = color.b;
234  this->buffer_[pos + 3] = color.w;
235  break;
236  }
238  default: {
239  if (this->has_transparency()) {
240  if (color.b == 1 && color.r == 0 && color.g == 0) {
241  color.b = 0;
242  }
243  if (color.w < 0x80) {
244  color.r = 0;
245  color.g = 0;
246  color.b = 1;
247  }
248  }
249  this->buffer_[pos + 0] = color.r;
250  this->buffer_[pos + 1] = color.g;
251  this->buffer_[pos + 2] = color.b;
252  break;
253  }
254  }
255 }
256 
258  if (this->downloader_) {
259  this->downloader_->end();
260  this->downloader_ = nullptr;
261  }
262  this->decoder_.reset();
263  this->download_buffer_.reset();
264 }
265 
266 bool OnlineImage::validate_url_(const std::string &url) {
267  if ((url.length() < 8) || (url.find("http") != 0) || (url.find("://") == std::string::npos)) {
268  ESP_LOGE(TAG, "URL is invalid and/or must be prefixed with 'http://' or 'https://'");
269  return false;
270  }
271  return true;
272 }
273 
274 void OnlineImage::add_on_finished_callback(std::function<void()> &&callback) {
275  this->download_finished_callback_.add(std::move(callback));
276 }
277 
278 void OnlineImage::add_on_error_callback(std::function<void()> &&callback) {
279  this->download_error_callback_.add(std::move(callback));
280 }
281 
282 } // namespace online_image
283 } // namespace esphome
bool resize_(int width, int height)
bool has_transparency() const
Definition: image.h:46
bool validate_url_(const std::string &url)
void draw_pixel_(int x, int y, Color color)
Draw a pixel into the buffer.
std::shared_ptr< HttpContainer > get(std::string url)
Definition: http_request.h:65
int buffer_width_
Actual width of the current image.
Definition: online_image.h:142
void add_on_finished_callback(std::function< void()> &&callback)
static uint16_t color_to_565(Color color, ColorOrder color_order=ColorOrder::COLOR_ORDER_RGB)
const int fixed_height_
height requested on configuration, or 0 if non specified.
Definition: online_image.h:133
uint16_t x
Definition: tt21100.cpp:17
std::unique_ptr< ImageDecoder > decoder_
Definition: online_image.h:120
void deallocate(T *p, size_t n)
Definition: helpers.h:678
void set_url(const std::string &url)
Set the URL to download the image from.
Definition: online_image.h:59
int buffer_height_
Actual height of the current image.
Definition: online_image.h:151
void delay_microseconds_safe(uint32_t us)
Delay for the given amount of microseconds, possibly yielding to other processes during the wait...
Definition: helpers.cpp:702
const int fixed_width_
width requested on configuration, or 0 if non specified.
Definition: online_image.h:131
uint8_t g
Definition: color.h:18
uint16_t y
Definition: tt21100.cpp:18
ImageType type_
Definition: image.h:57
ESPHOME_ALWAYS_INLINE bool auto_resize_() const
Definition: online_image.h:97
std::shared_ptr< http_request::HttpContainer > downloader_
Definition: online_image.h:119
void add_on_error_callback(std::function< void()> &&callback)
esphome::http_request::HttpRequestComponent * parent_
Definition: helpers.h:532
bool is_color_on(const Color &color)
CallbackManager< void()> download_error_callback_
Definition: online_image.h:117
uint8_t type
OnlineImage(const std::string &url, int width, int height, ImageFormat format, image::ImageType type, uint32_t buffer_size)
Construct a new OnlineImage object.
const uint8_t * data_start_
Definition: image.h:58
uint8_t * data(size_t offset=0)
CallbackManager< void()> download_finished_callback_
Definition: online_image.h:116
void release()
Release the buffer storing the image.
uint8_t w
Definition: color.h:26
std::string size_t len
Definition: helpers.h:292
int get_position_(int x, int y) const
Definition: online_image.h:93
uint8_t b
Definition: color.h:22
void draw(int x, int y, display::Display *display, Color color_on, Color color_off) override
Definition: image.cpp:8
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
uint8_t r
Definition: color.h:14
void draw(int x, int y, display::Display *display, Color color_on, Color color_off) override
ImageFormat
Format that the image is encoded with.
Definition: online_image.h:23