ESPHome  2023.3.1
display_buffer.cpp
Go to the documentation of this file.
1 #include "display_buffer.h"
2 
3 #include <utility>
5 #include "esphome/core/color.h"
6 #include "esphome/core/log.h"
7 #include "esphome/core/hal.h"
8 #include "esphome/core/helpers.h"
9 
10 namespace esphome {
11 namespace display {
12 
13 static const char *const TAG = "display";
14 
15 const Color COLOR_OFF(0, 0, 0, 0);
16 const Color COLOR_ON(255, 255, 255, 255);
17 
18 void Rect::expand(int16_t horizontal, int16_t vertical) {
19  if (this->is_set() && (this->w >= (-2 * horizontal)) && (this->h >= (-2 * vertical))) {
20  this->x = this->x - horizontal;
21  this->y = this->y - vertical;
22  this->w = this->w + (2 * horizontal);
23  this->h = this->h + (2 * vertical);
24  }
25 }
26 
27 void Rect::extend(Rect rect) {
28  if (!this->is_set()) {
29  this->x = rect.x;
30  this->y = rect.y;
31  this->w = rect.w;
32  this->h = rect.h;
33  } else {
34  if (this->x > rect.x) {
35  this->w = this->w + (this->x - rect.x);
36  this->x = rect.x;
37  }
38  if (this->y > rect.y) {
39  this->h = this->h + (this->y - rect.y);
40  this->y = rect.y;
41  }
42  if (this->x2() < rect.x2()) {
43  this->w = rect.x2() - this->x;
44  }
45  if (this->y2() < rect.y2()) {
46  this->h = rect.y2() - this->y;
47  }
48  }
49 }
50 void Rect::shrink(Rect rect) {
51  if (!this->inside(rect)) {
52  (*this) = Rect();
53  } else {
54  if (this->x2() > rect.x2()) {
55  this->w = rect.x2() - this->x;
56  }
57  if (this->x < rect.x) {
58  this->w = this->w + (this->x - rect.x);
59  this->x = rect.x;
60  }
61  if (this->y2() > rect.y2()) {
62  this->h = rect.y2() - this->y;
63  }
64  if (this->y < rect.y) {
65  this->h = this->h + (this->y - rect.y);
66  this->y = rect.y;
67  }
68  }
69 }
70 
71 bool Rect::equal(Rect rect) {
72  return (rect.x == this->x) && (rect.w == this->w) && (rect.y == this->y) && (rect.h == this->h);
73 }
74 
75 bool Rect::inside(int16_t test_x, int16_t test_y, bool absolute) { // NOLINT
76  if (!this->is_set()) {
77  return true;
78  }
79  if (absolute) {
80  return ((test_x >= this->x) && (test_x <= this->x2()) && (test_y >= this->y) && (test_y <= this->y2()));
81  } else {
82  return ((test_x >= 0) && (test_x <= this->w) && (test_y >= 0) && (test_y <= this->h));
83  }
84 }
85 
86 bool Rect::inside(Rect rect, bool absolute) {
87  if (!this->is_set() || !rect.is_set()) {
88  return true;
89  }
90  if (absolute) {
91  return ((rect.x <= this->x2()) && (rect.x2() >= this->x) && (rect.y <= this->y2()) && (rect.y2() >= this->y));
92  } else {
93  return ((rect.x <= this->w) && (rect.w >= 0) && (rect.y <= this->h) && (rect.h >= 0));
94  }
95 }
96 
97 void Rect::info(const std::string &prefix) {
98  if (this->is_set()) {
99  ESP_LOGI(TAG, "%s [%3d,%3d,%3d,%3d] (%3d,%3d)", prefix.c_str(), this->x, this->y, this->w, this->h, this->x2(),
100  this->y2());
101  } else
102  ESP_LOGI(TAG, "%s ** IS NOT SET **", prefix.c_str());
103 }
104 
105 void DisplayBuffer::init_internal_(uint32_t buffer_length) {
107  this->buffer_ = allocator.allocate(buffer_length);
108  if (this->buffer_ == nullptr) {
109  ESP_LOGE(TAG, "Could not allocate buffer for display!");
110  return;
111  }
112  this->clear();
113 }
114 
115 void DisplayBuffer::fill(Color color) { this->filled_rectangle(0, 0, this->get_width(), this->get_height(), color); }
116 void DisplayBuffer::clear() { this->fill(COLOR_OFF); }
118  switch (this->rotation_) {
121  return this->get_height_internal();
124  default:
125  return this->get_width_internal();
126  }
127 }
129  switch (this->rotation_) {
132  return this->get_height_internal();
135  default:
136  return this->get_width_internal();
137  }
138 }
139 void DisplayBuffer::set_rotation(DisplayRotation rotation) { this->rotation_ = rotation; }
140 void HOT DisplayBuffer::draw_pixel_at(int x, int y, Color color) {
141  if (!this->get_clipping().inside(x, y))
142  return; // NOLINT
143 
144  switch (this->rotation_) {
146  break;
148  std::swap(x, y);
149  x = this->get_width_internal() - x - 1;
150  break;
152  x = this->get_width_internal() - x - 1;
153  y = this->get_height_internal() - y - 1;
154  break;
156  std::swap(x, y);
157  y = this->get_height_internal() - y - 1;
158  break;
159  }
160  this->draw_absolute_pixel_internal(x, y, color);
161  App.feed_wdt();
162 }
163 void HOT DisplayBuffer::line(int x1, int y1, int x2, int y2, Color color) {
164  const int32_t dx = abs(x2 - x1), sx = x1 < x2 ? 1 : -1;
165  const int32_t dy = -abs(y2 - y1), sy = y1 < y2 ? 1 : -1;
166  int32_t err = dx + dy;
167 
168  while (true) {
169  this->draw_pixel_at(x1, y1, color);
170  if (x1 == x2 && y1 == y2)
171  break;
172  int32_t e2 = 2 * err;
173  if (e2 >= dy) {
174  err += dy;
175  x1 += sx;
176  }
177  if (e2 <= dx) {
178  err += dx;
179  y1 += sy;
180  }
181  }
182 }
183 void HOT DisplayBuffer::horizontal_line(int x, int y, int width, Color color) {
184  // Future: Could be made more efficient by manipulating buffer directly in certain rotations.
185  for (int i = x; i < x + width; i++)
186  this->draw_pixel_at(i, y, color);
187 }
188 void HOT DisplayBuffer::vertical_line(int x, int y, int height, Color color) {
189  // Future: Could be made more efficient by manipulating buffer directly in certain rotations.
190  for (int i = y; i < y + height; i++)
191  this->draw_pixel_at(x, i, color);
192 }
193 void DisplayBuffer::rectangle(int x1, int y1, int width, int height, Color color) {
194  this->horizontal_line(x1, y1, width, color);
195  this->horizontal_line(x1, y1 + height - 1, width, color);
196  this->vertical_line(x1, y1, height, color);
197  this->vertical_line(x1 + width - 1, y1, height, color);
198 }
199 void DisplayBuffer::filled_rectangle(int x1, int y1, int width, int height, Color color) {
200  // Future: Use vertical_line and horizontal_line methods depending on rotation to reduce memory accesses.
201  for (int i = y1; i < y1 + height; i++) {
202  this->horizontal_line(x1, i, width, color);
203  }
204 }
205 void HOT DisplayBuffer::circle(int center_x, int center_xy, int radius, Color color) {
206  int dx = -radius;
207  int dy = 0;
208  int err = 2 - 2 * radius;
209  int e2;
210 
211  do {
212  this->draw_pixel_at(center_x - dx, center_xy + dy, color);
213  this->draw_pixel_at(center_x + dx, center_xy + dy, color);
214  this->draw_pixel_at(center_x + dx, center_xy - dy, color);
215  this->draw_pixel_at(center_x - dx, center_xy - dy, color);
216  e2 = err;
217  if (e2 < dy) {
218  err += ++dy * 2 + 1;
219  if (-dx == dy && e2 <= dx) {
220  e2 = 0;
221  }
222  }
223  if (e2 > dx) {
224  err += ++dx * 2 + 1;
225  }
226  } while (dx <= 0);
227 }
228 void DisplayBuffer::filled_circle(int center_x, int center_y, int radius, Color color) {
229  int dx = -int32_t(radius);
230  int dy = 0;
231  int err = 2 - 2 * radius;
232  int e2;
233 
234  do {
235  this->draw_pixel_at(center_x - dx, center_y + dy, color);
236  this->draw_pixel_at(center_x + dx, center_y + dy, color);
237  this->draw_pixel_at(center_x + dx, center_y - dy, color);
238  this->draw_pixel_at(center_x - dx, center_y - dy, color);
239  int hline_width = 2 * (-dx) + 1;
240  this->horizontal_line(center_x + dx, center_y + dy, hline_width, color);
241  this->horizontal_line(center_x + dx, center_y - dy, hline_width, color);
242  e2 = err;
243  if (e2 < dy) {
244  err += ++dy * 2 + 1;
245  if (-dx == dy && e2 <= dx) {
246  e2 = 0;
247  }
248  }
249  if (e2 > dx) {
250  err += ++dx * 2 + 1;
251  }
252  } while (dx <= 0);
253 }
254 
255 void DisplayBuffer::print(int x, int y, Font *font, Color color, TextAlign align, const char *text) {
256  int x_start, y_start;
257  int width, height;
258  this->get_text_bounds(x, y, text, font, align, &x_start, &y_start, &width, &height);
259 
260  int i = 0;
261  int x_at = x_start;
262  while (text[i] != '\0') {
263  int match_length;
264  int glyph_n = font->match_next_glyph(text + i, &match_length);
265  if (glyph_n < 0) {
266  // Unknown char, skip
267  ESP_LOGW(TAG, "Encountered character without representation in font: '%c'", text[i]);
268  if (!font->get_glyphs().empty()) {
269  uint8_t glyph_width = font->get_glyphs()[0].glyph_data_->width;
270  for (int glyph_x = 0; glyph_x < glyph_width; glyph_x++) {
271  for (int glyph_y = 0; glyph_y < height; glyph_y++)
272  this->draw_pixel_at(glyph_x + x_at, glyph_y + y_start, color);
273  }
274  x_at += glyph_width;
275  }
276 
277  i++;
278  continue;
279  }
280 
281  const Glyph &glyph = font->get_glyphs()[glyph_n];
282  int scan_x1, scan_y1, scan_width, scan_height;
283  glyph.scan_area(&scan_x1, &scan_y1, &scan_width, &scan_height);
284 
285  for (int glyph_x = scan_x1; glyph_x < scan_x1 + scan_width; glyph_x++) {
286  for (int glyph_y = scan_y1; glyph_y < scan_y1 + scan_height; glyph_y++) {
287  if (glyph.get_pixel(glyph_x, glyph_y)) {
288  this->draw_pixel_at(glyph_x + x_at, glyph_y + y_start, color);
289  }
290  }
291  }
292 
293  x_at += glyph.glyph_data_->width + glyph.glyph_data_->offset_x;
294 
295  i += match_length;
296  }
297 }
298 void DisplayBuffer::vprintf_(int x, int y, Font *font, Color color, TextAlign align, const char *format, va_list arg) {
299  char buffer[256];
300  int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
301  if (ret > 0)
302  this->print(x, y, font, color, align, buffer);
303 }
304 
305 void DisplayBuffer::image(int x, int y, Image *image, Color color_on, Color color_off) {
306  switch (image->get_type()) {
307  case IMAGE_TYPE_BINARY:
308  for (int img_x = 0; img_x < image->get_width(); img_x++) {
309  for (int img_y = 0; img_y < image->get_height(); img_y++) {
310  this->draw_pixel_at(x + img_x, y + img_y, image->get_pixel(img_x, img_y) ? color_on : color_off);
311  }
312  }
313  break;
315  for (int img_x = 0; img_x < image->get_width(); img_x++) {
316  for (int img_y = 0; img_y < image->get_height(); img_y++) {
317  this->draw_pixel_at(x + img_x, y + img_y, image->get_grayscale_pixel(img_x, img_y));
318  }
319  }
320  break;
321  case IMAGE_TYPE_RGB24:
322  for (int img_x = 0; img_x < image->get_width(); img_x++) {
323  for (int img_y = 0; img_y < image->get_height(); img_y++) {
324  this->draw_pixel_at(x + img_x, y + img_y, image->get_color_pixel(img_x, img_y));
325  }
326  }
327  break;
329  for (int img_x = 0; img_x < image->get_width(); img_x++) {
330  for (int img_y = 0; img_y < image->get_height(); img_y++) {
331  if (image->get_pixel(img_x, img_y))
332  this->draw_pixel_at(x + img_x, y + img_y, color_on);
333  }
334  }
335  break;
336  case IMAGE_TYPE_RGB565:
337  for (int img_x = 0; img_x < image->get_width(); img_x++) {
338  for (int img_y = 0; img_y < image->get_height(); img_y++) {
339  this->draw_pixel_at(x + img_x, y + img_y, image->get_rgb565_pixel(img_x, img_y));
340  }
341  }
342  break;
343  }
344 }
345 
346 #ifdef USE_GRAPH
347 void DisplayBuffer::graph(int x, int y, graph::Graph *graph, Color color_on) { graph->draw(this, x, y, color_on); }
348 void DisplayBuffer::legend(int x, int y, graph::Graph *graph, Color color_on) {
349  graph->draw_legend(this, x, y, color_on);
350 }
351 #endif // USE_GRAPH
352 
353 #ifdef USE_QR_CODE
354 void DisplayBuffer::qr_code(int x, int y, qr_code::QrCode *qr_code, Color color_on, int scale) {
355  qr_code->draw(this, x, y, color_on, scale);
356 }
357 #endif // USE_QR_CODE
358 
359 void DisplayBuffer::get_text_bounds(int x, int y, const char *text, Font *font, TextAlign align, int *x1, int *y1,
360  int *width, int *height) {
361  int x_offset, baseline;
362  font->measure(text, width, &x_offset, &baseline, height);
363 
364  auto x_align = TextAlign(int(align) & 0x18);
365  auto y_align = TextAlign(int(align) & 0x07);
366 
367  switch (x_align) {
368  case TextAlign::RIGHT:
369  *x1 = x - *width;
370  break;
372  *x1 = x - (*width) / 2;
373  break;
374  case TextAlign::LEFT:
375  default:
376  // LEFT
377  *x1 = x;
378  break;
379  }
380 
381  switch (y_align) {
382  case TextAlign::BOTTOM:
383  *y1 = y - *height;
384  break;
385  case TextAlign::BASELINE:
386  *y1 = y - baseline;
387  break;
389  *y1 = y - (*height) / 2;
390  break;
391  case TextAlign::TOP:
392  default:
393  *y1 = y;
394  break;
395  }
396 }
397 void DisplayBuffer::print(int x, int y, Font *font, Color color, const char *text) {
398  this->print(x, y, font, color, TextAlign::TOP_LEFT, text);
399 }
400 void DisplayBuffer::print(int x, int y, Font *font, TextAlign align, const char *text) {
401  this->print(x, y, font, COLOR_ON, align, text);
402 }
403 void DisplayBuffer::print(int x, int y, Font *font, const char *text) {
404  this->print(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, text);
405 }
406 void DisplayBuffer::printf(int x, int y, Font *font, Color color, TextAlign align, const char *format, ...) {
407  va_list arg;
408  va_start(arg, format);
409  this->vprintf_(x, y, font, color, align, format, arg);
410  va_end(arg);
411 }
412 void DisplayBuffer::printf(int x, int y, Font *font, Color color, const char *format, ...) {
413  va_list arg;
414  va_start(arg, format);
415  this->vprintf_(x, y, font, color, TextAlign::TOP_LEFT, format, arg);
416  va_end(arg);
417 }
418 void DisplayBuffer::printf(int x, int y, Font *font, TextAlign align, const char *format, ...) {
419  va_list arg;
420  va_start(arg, format);
421  this->vprintf_(x, y, font, COLOR_ON, align, format, arg);
422  va_end(arg);
423 }
424 void DisplayBuffer::printf(int x, int y, Font *font, const char *format, ...) {
425  va_list arg;
426  va_start(arg, format);
427  this->vprintf_(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, arg);
428  va_end(arg);
429 }
430 void DisplayBuffer::set_writer(display_writer_t &&writer) { this->writer_ = writer; }
431 void DisplayBuffer::set_pages(std::vector<DisplayPage *> pages) {
432  for (auto *page : pages)
433  page->set_parent(this);
434 
435  for (uint32_t i = 0; i < pages.size() - 1; i++) {
436  pages[i]->set_next(pages[i + 1]);
437  pages[i + 1]->set_prev(pages[i]);
438  }
439  pages[0]->set_prev(pages[pages.size() - 1]);
440  pages[pages.size() - 1]->set_next(pages[0]);
441  this->show_page(pages[0]);
442 }
444  this->previous_page_ = this->page_;
445  this->page_ = page;
446  if (this->previous_page_ != this->page_) {
447  for (auto *t : on_page_change_triggers_)
448  t->process(this->previous_page_, this->page_);
449  }
450 }
451 void DisplayBuffer::show_next_page() { this->page_->show_next(); }
452 void DisplayBuffer::show_prev_page() { this->page_->show_prev(); }
454  if (this->auto_clear_enabled_) {
455  this->clear();
456  }
457  if (this->page_ != nullptr) {
458  this->page_->get_writer()(*this);
459  } else if (this->writer_.has_value()) {
460  (*this->writer_)(*this);
461  }
462  // remove all not ended clipping regions
463  while (is_clipping()) {
464  end_clipping();
465  }
466 }
468  if ((this->from_ == nullptr || this->from_ == from) && (this->to_ == nullptr || this->to_ == to))
469  this->trigger(from, to);
470 }
471 #ifdef USE_TIME
472 void DisplayBuffer::strftime(int x, int y, Font *font, Color color, TextAlign align, const char *format,
473  time::ESPTime time) {
474  char buffer[64];
475  size_t ret = time.strftime(buffer, sizeof(buffer), format);
476  if (ret > 0)
477  this->print(x, y, font, color, align, buffer);
478 }
479 void DisplayBuffer::strftime(int x, int y, Font *font, Color color, const char *format, time::ESPTime time) {
480  this->strftime(x, y, font, color, TextAlign::TOP_LEFT, format, time);
481 }
482 void DisplayBuffer::strftime(int x, int y, Font *font, TextAlign align, const char *format, time::ESPTime time) {
483  this->strftime(x, y, font, COLOR_ON, align, format, time);
484 }
485 void DisplayBuffer::strftime(int x, int y, Font *font, const char *format, time::ESPTime time) {
486  this->strftime(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, time);
487 }
488 #endif
489 
491  if (!this->clipping_rectangle_.empty()) {
492  Rect r = this->clipping_rectangle_.back();
493  rect.shrink(r);
494  }
495  this->clipping_rectangle_.push_back(rect);
496 }
498  if (this->clipping_rectangle_.empty()) {
499  ESP_LOGE(TAG, "clear: Clipping is not set.");
500  } else {
501  this->clipping_rectangle_.pop_back();
502  }
503 }
505  if (this->clipping_rectangle_.empty()) {
506  ESP_LOGE(TAG, "add: Clipping is not set.");
507  } else {
508  this->clipping_rectangle_.back().extend(add_rect);
509  }
510 }
512  if (this->clipping_rectangle_.empty()) {
513  ESP_LOGE(TAG, "add: Clipping is not set.");
514  } else {
515  this->clipping_rectangle_.back().shrink(add_rect);
516  }
517 }
519  if (this->clipping_rectangle_.empty()) {
520  return Rect();
521  } else {
522  return this->clipping_rectangle_.back();
523  }
524 }
525 bool Glyph::get_pixel(int x, int y) const {
526  const int x_data = x - this->glyph_data_->offset_x;
527  const int y_data = y - this->glyph_data_->offset_y;
528  if (x_data < 0 || x_data >= this->glyph_data_->width || y_data < 0 || y_data >= this->glyph_data_->height)
529  return false;
530  const uint32_t width_8 = ((this->glyph_data_->width + 7u) / 8u) * 8u;
531  const uint32_t pos = x_data + y_data * width_8;
532  return progmem_read_byte(this->glyph_data_->data + (pos / 8u)) & (0x80 >> (pos % 8u));
533 }
534 const char *Glyph::get_char() const { return this->glyph_data_->a_char; }
535 bool Glyph::compare_to(const char *str) const {
536  // 1 -> this->char_
537  // 2 -> str
538  for (uint32_t i = 0;; i++) {
539  if (this->glyph_data_->a_char[i] == '\0')
540  return true;
541  if (str[i] == '\0')
542  return false;
543  if (this->glyph_data_->a_char[i] > str[i])
544  return false;
545  if (this->glyph_data_->a_char[i] < str[i])
546  return true;
547  }
548  // this should not happen
549  return false;
550 }
551 int Glyph::match_length(const char *str) const {
552  for (uint32_t i = 0;; i++) {
553  if (this->glyph_data_->a_char[i] == '\0')
554  return i;
555  if (str[i] != this->glyph_data_->a_char[i])
556  return 0;
557  }
558  // this should not happen
559  return 0;
560 }
561 void Glyph::scan_area(int *x1, int *y1, int *width, int *height) const {
562  *x1 = this->glyph_data_->offset_x;
563  *y1 = this->glyph_data_->offset_y;
564  *width = this->glyph_data_->width;
565  *height = this->glyph_data_->height;
566 }
567 int Font::match_next_glyph(const char *str, int *match_length) {
568  int lo = 0;
569  int hi = this->glyphs_.size() - 1;
570  while (lo != hi) {
571  int mid = (lo + hi + 1) / 2;
572  if (this->glyphs_[mid].compare_to(str)) {
573  lo = mid;
574  } else {
575  hi = mid - 1;
576  }
577  }
578  *match_length = this->glyphs_[lo].match_length(str);
579  if (*match_length <= 0)
580  return -1;
581  return lo;
582 }
583 void Font::measure(const char *str, int *width, int *x_offset, int *baseline, int *height) {
584  *baseline = this->baseline_;
585  *height = this->height_;
586  int i = 0;
587  int min_x = 0;
588  bool has_char = false;
589  int x = 0;
590  while (str[i] != '\0') {
591  int match_length;
592  int glyph_n = this->match_next_glyph(str + i, &match_length);
593  if (glyph_n < 0) {
594  // Unknown char, skip
595  if (!this->get_glyphs().empty())
596  x += this->get_glyphs()[0].glyph_data_->width;
597  i++;
598  continue;
599  }
600 
601  const Glyph &glyph = this->glyphs_[glyph_n];
602  if (!has_char) {
603  min_x = glyph.glyph_data_->offset_x;
604  } else {
605  min_x = std::min(min_x, x + glyph.glyph_data_->offset_x);
606  }
607  x += glyph.glyph_data_->width + glyph.glyph_data_->offset_x;
608 
609  i += match_length;
610  has_char = true;
611  }
612  *x_offset = min_x;
613  *width = x - min_x;
614 }
615 Font::Font(const GlyphData *data, int data_nr, int baseline, int height) : baseline_(baseline), height_(height) {
616  glyphs_.reserve(data_nr);
617  for (int i = 0; i < data_nr; ++i)
618  glyphs_.emplace_back(&data[i]);
619 }
620 
621 bool Image::get_pixel(int x, int y) const {
622  if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
623  return false;
624  const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u;
625  const uint32_t pos = x + y * width_8;
626  return progmem_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u));
627 }
628 Color Image::get_color_pixel(int x, int y) const {
629  if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
630  return Color::BLACK;
631  const uint32_t pos = (x + y * this->width_) * 3;
632  const uint32_t color32 = (progmem_read_byte(this->data_start_ + pos + 2) << 0) |
633  (progmem_read_byte(this->data_start_ + pos + 1) << 8) |
634  (progmem_read_byte(this->data_start_ + pos + 0) << 16);
635  return Color(color32);
636 }
637 Color Image::get_rgb565_pixel(int x, int y) const {
638  if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
639  return Color::BLACK;
640  const uint32_t pos = (x + y * this->width_) * 2;
641  uint16_t rgb565 =
642  progmem_read_byte(this->data_start_ + pos + 0) << 8 | progmem_read_byte(this->data_start_ + pos + 1);
643  auto r = (rgb565 & 0xF800) >> 11;
644  auto g = (rgb565 & 0x07E0) >> 5;
645  auto b = rgb565 & 0x001F;
646  return Color((r << 3) | (r >> 2), (g << 2) | (g >> 4), (b << 3) | (b >> 2));
647 }
648 Color Image::get_grayscale_pixel(int x, int y) const {
649  if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
650  return Color::BLACK;
651  const uint32_t pos = (x + y * this->width_);
652  const uint8_t gray = progmem_read_byte(this->data_start_ + pos);
653  return Color(gray | gray << 8 | gray << 16 | gray << 24);
654 }
655 int Image::get_width() const { return this->width_; }
656 int Image::get_height() const { return this->height_; }
657 ImageType Image::get_type() const { return this->type_; }
658 Image::Image(const uint8_t *data_start, int width, int height, ImageType type)
659  : width_(width), height_(height), type_(type), data_start_(data_start) {}
660 int Image::get_current_frame() const { return 0; }
661 
662 bool Animation::get_pixel(int x, int y) const {
663  if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
664  return false;
665  const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u;
666  const uint32_t frame_index = this->height_ * width_8 * this->current_frame_;
667  if (frame_index >= (uint32_t)(this->width_ * this->height_ * this->animation_frame_count_))
668  return false;
669  const uint32_t pos = x + y * width_8 + frame_index;
670  return progmem_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u));
671 }
672 Color Animation::get_color_pixel(int x, int y) const {
673  if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
674  return Color::BLACK;
675  const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_;
676  if (frame_index >= (uint32_t)(this->width_ * this->height_ * this->animation_frame_count_))
677  return Color::BLACK;
678  const uint32_t pos = (x + y * this->width_ + frame_index) * 3;
679  const uint32_t color32 = (progmem_read_byte(this->data_start_ + pos + 2) << 0) |
680  (progmem_read_byte(this->data_start_ + pos + 1) << 8) |
681  (progmem_read_byte(this->data_start_ + pos + 0) << 16);
682  return Color(color32);
683 }
684 Color Animation::get_rgb565_pixel(int x, int y) const {
685  if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
686  return Color::BLACK;
687  const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_;
688  if (frame_index >= (uint32_t)(this->width_ * this->height_ * this->animation_frame_count_))
689  return Color::BLACK;
690  const uint32_t pos = (x + y * this->width_ + frame_index) * 2;
691  uint16_t rgb565 =
692  progmem_read_byte(this->data_start_ + pos + 0) << 8 | progmem_read_byte(this->data_start_ + pos + 1);
693  auto r = (rgb565 & 0xF800) >> 11;
694  auto g = (rgb565 & 0x07E0) >> 5;
695  auto b = rgb565 & 0x001F;
696  return Color((r << 3) | (r >> 2), (g << 2) | (g >> 4), (b << 3) | (b >> 2));
697 }
699  if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
700  return Color::BLACK;
701  const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_;
702  if (frame_index >= (uint32_t)(this->width_ * this->height_ * this->animation_frame_count_))
703  return Color::BLACK;
704  const uint32_t pos = (x + y * this->width_ + frame_index);
705  const uint8_t gray = progmem_read_byte(this->data_start_ + pos);
706  return Color(gray | gray << 8 | gray << 16 | gray << 24);
707 }
708 Animation::Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type)
709  : Image(data_start, width, height, type), current_frame_(0), animation_frame_count_(animation_frame_count) {}
711 int Animation::get_current_frame() const { return this->current_frame_; }
713  this->current_frame_++;
714  if (this->current_frame_ >= animation_frame_count_) {
715  this->current_frame_ = 0;
716  }
717 }
719  this->current_frame_--;
720  if (this->current_frame_ < 0) {
721  this->current_frame_ = this->animation_frame_count_ - 1;
722  }
723 }
724 
725 void Animation::set_frame(int frame) {
726  unsigned abs_frame = abs(frame);
727 
728  if (abs_frame < this->animation_frame_count_) {
729  if (frame >= 0) {
730  this->current_frame_ = frame;
731  } else {
732  this->current_frame_ = this->animation_frame_count_ - abs_frame;
733  }
734  }
735 }
736 
737 DisplayPage::DisplayPage(display_writer_t writer) : writer_(std::move(writer)) {}
738 void DisplayPage::show() { this->parent_->show_page(this); }
739 void DisplayPage::show_next() { this->next_->show(); }
740 void DisplayPage::show_prev() { this->prev_->show(); }
741 void DisplayPage::set_parent(DisplayBuffer *parent) { this->parent_ = parent; }
742 void DisplayPage::set_prev(DisplayPage *prev) { this->prev_ = prev; }
743 void DisplayPage::set_next(DisplayPage *next) { this->next_ = next; }
744 const display_writer_t &DisplayPage::get_writer() const { return this->writer_; }
745 
746 } // namespace display
747 } // namespace esphome
void printf(int x, int y, Font *font, Color color, TextAlign align, const char *format,...) __attribute__((format(printf
Evaluate the printf-format format and print the result with the anchor point at [x,y] with font.
void legend(int x, int y, graph::Graph *graph, Color color_on=COLOR_ON)
Draw the legend for graph with the top-left corner at [x,y] to the screen.
void void void void void strftime(int x, int y, Font *font, Color color, TextAlign align, const char *format, time::ESPTime time) __attribute__((format(strftime
Evaluate the strftime-format format and print the result with the anchor point at [x...
virtual Color get_grayscale_pixel(int x, int y) const
void show_page(DisplayPage *page)
void end_clipping()
Reset the invalidation region.
void draw(display::DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Color color, int scale)
Definition: qr_code.cpp:36
Font(const GlyphData *data, int data_nr, int baseline, int height)
Construct the font with the given glyphs.
void set_pages(std::vector< DisplayPage *> pages)
void qr_code(int x, int y, qr_code::QrCode *qr_code, Color color_on=COLOR_ON, int scale=1)
Draw the qr_code with the top-left corner at [x,y] to the screen.
Image(const uint8_t *data_start, int width, int height, ImageType type)
void scan_area(int *x1, int *y1, int *width, int *height) const
void filled_circle(int center_x, int center_y, int radius, Color color=COLOR_ON)
Fill a circle centered around [center_x,center_y] with the radius radius with the given color...
void filled_rectangle(int x1, int y1, int width, int height, Color color=COLOR_ON)
Fill a rectangle with the top left point at [x1,y1] and the bottom right point at [x1+width...
void set_next(DisplayPage *next)
void set_rotation(DisplayRotation rotation)
Internal method to set the display rotation with.
Color get_rgb565_pixel(int x, int y) const override
std::vector< Glyph, ExternalRAMAllocator< Glyph > > glyphs_
void clear()
Clear the entire screen by filling it with OFF pixels.
int16_t x
X coordinate of corner.
void shrink_clipping(Rect rect)
substract a rectangular region to the invalidation region
const Color COLOR_OFF(0, 0, 0, 0)
Turn the pixel OFF.
An STL allocator that uses SPI RAM.
Definition: helpers.h:635
STL namespace.
size_t strftime(char *buffer, size_t buffer_len, const char *format)
Convert this ESPTime struct to a null-terminated c string buffer as specified by the format argument...
void graph(int x, int y, graph::Graph *graph, Color color_on=COLOR_ON)
Draw the graph with the top-left corner at [x,y] to the screen.
virtual Color get_color_pixel(int x, int y) const
void start_clipping(Rect rect)
Set the clipping rectangle for further drawing.
int16_t h
Height of region.
bool get_pixel(int x, int y) const
DisplayPage(display_writer_t writer)
void vprintf_(int x, int y, Font *font, Color color, TextAlign align, const char *format, va_list arg)
const uint8_t * data_start_
int16_t y
Y coordinate of corner.
void print(int x, int y, Font *font, Color color, TextAlign align, const char *text)
Print text with the anchor point at [x,y] with font.
void set_frame(int frame)
Selects a specific frame within the animation.
const display_writer_t & get_writer() const
void init_internal_(uint32_t buffer_length)
bool compare_to(const char *str) const
void vertical_line(int x, int y, int height, Color color=COLOR_ON)
Draw a vertical line from the point [x,y] to [x,y+width] with the given color.
ImageType get_type() const
void extend(Rect rect)
virtual void fill(Color color)
Fill the entire screen with the given color.
Color get_grayscale_pixel(int x, int y) const override
TextAlign
TextAlign is used to tell the display class how to position a piece of text.
void measure(const char *str, int *width, int *x_offset, int *baseline, int *height)
void line(int x1, int y1, int x2, int y2, Color color=COLOR_ON)
Draw a straight line from the point [x1,y1] to [x2,y2] with the given color.
void horizontal_line(int x, int y, int width, Color color=COLOR_ON)
Draw a horizontal line from the point [x,y] to [x+width,y] with the given color.
A more user-friendly version of struct tm from time.h.
void process(DisplayPage *from, DisplayPage *to)
int match_length(const char *str) const
Color get_color_pixel(int x, int y) const override
int match_next_glyph(const char *str, int *match_length)
void circle(int center_x, int center_xy, int radius, Color color=COLOR_ON)
Draw the outline of a circle centered around [center_x,center_y] with the radius radius with the give...
bool inside(Rect rect, bool absolute=true)
uint8_t type
Application App
Global storage of Application pointer - only one Application can exist.
bool is_set() ALWAYS_INLINE
Y coordinate of corner.
void set_writer(display_writer_t &&writer)
Internal method to set the display writer lambda.
void void void void void void void void void image(int x, int y, Image *image, Color color_on=COLOR_ON, Color color_off=COLOR_OFF)
Draw the image with the top-left corner at [x,y] to the screen.
Rect get_clipping()
Get the current the clipping rectangle.
void info(const std::string &prefix="rect info:")
void swap(optional< T > &x, optional< T > &y)
Definition: optional.h:210
virtual int get_current_frame() const
const std::vector< Glyph, ExternalRAMAllocator< Glyph > > & get_glyphs() const
uint8_t progmem_read_byte(const uint8_t *addr)
Definition: core.cpp:56
const Color COLOR_ON(255, 255, 255, 255)
Turn the pixel ON.
int16_t y2()
X coordinate of corner.
void draw(display::DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Color color)
Definition: graph.cpp:59
static const Color BLACK
Definition: color.h:172
void get_text_bounds(int x, int y, const char *text, Font *font, TextAlign align, int *x1, int *y1, int *width, int *height)
Get the text bounds of the given string.
Definition: a4988.cpp:4
virtual Color get_rgb565_pixel(int x, int y) const
const GlyphData * glyph_data_
const char * get_char() const
int get_current_frame() const override
void expand(int16_t horizontal, int16_t vertical)
std::function< void(DisplayBuffer &)> display_writer_t
void shrink(Rect rect)
void extend_clipping(Rect rect)
Add a rectangular region to the invalidation region.
void draw_legend(display::DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Color color)
Definition: graph.cpp:299
void set_parent(DisplayBuffer *parent)
void draw_pixel_at(int x, int y, Color color=COLOR_ON)
Set a single pixel at the specified coordinates to the given color.
Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type)
int get_height()
Get the height of the image in pixels with rotation applied.
void rectangle(int x1, int y1, int width, int height, Color color=COLOR_ON)
Draw the outline of a rectangle with the top left point at [x1,y1] and the bottom right point at [x1+...
virtual bool get_pixel(int x, int y) const
int16_t w
Width of region.
void set_prev(DisplayPage *prev)
int get_width()
Get the width of the image in pixels with rotation applied.
bool get_pixel(int x, int y) const override