ESPHome  2024.11.3
display.cpp
Go to the documentation of this file.
1 #include "display.h"
2 #include "display_color_utils.h"
3 #include <utility>
4 #include "esphome/core/hal.h"
5 #include "esphome/core/log.h"
6 
7 namespace esphome {
8 namespace display {
9 
10 static const char *const TAG = "display";
11 
12 const Color COLOR_OFF(0, 0, 0, 0);
13 const Color COLOR_ON(255, 255, 255, 255);
14 
15 void Display::fill(Color color) { this->filled_rectangle(0, 0, this->get_width(), this->get_height(), color); }
16 void Display::clear() { this->fill(COLOR_OFF); }
17 void Display::set_rotation(DisplayRotation rotation) { this->rotation_ = rotation; }
18 void HOT Display::line(int x1, int y1, int x2, int y2, Color color) {
19  const int32_t dx = abs(x2 - x1), sx = x1 < x2 ? 1 : -1;
20  const int32_t dy = -abs(y2 - y1), sy = y1 < y2 ? 1 : -1;
21  int32_t err = dx + dy;
22 
23  while (true) {
24  this->draw_pixel_at(x1, y1, color);
25  if (x1 == x2 && y1 == y2)
26  break;
27  int32_t e2 = 2 * err;
28  if (e2 >= dy) {
29  err += dy;
30  x1 += sx;
31  }
32  if (e2 <= dx) {
33  err += dx;
34  y1 += sy;
35  }
36  }
37 }
38 
39 void Display::line_at_angle(int x, int y, int angle, int length, Color color) {
40  this->line_at_angle(x, y, angle, 0, length, color);
41 }
42 
43 void Display::line_at_angle(int x, int y, int angle, int start_radius, int stop_radius, Color color) {
44  // Calculate start and end points
45  int x1 = (start_radius * cos(angle * M_PI / 180)) + x;
46  int y1 = (start_radius * sin(angle * M_PI / 180)) + y;
47  int x2 = (stop_radius * cos(angle * M_PI / 180)) + x;
48  int y2 = (stop_radius * sin(angle * M_PI / 180)) + y;
49 
50  // Draw line
51  this->line(x1, y1, x2, y2, color);
52 }
53 
54 void Display::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order,
55  ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) {
56  size_t line_stride = x_offset + w + x_pad; // length of each source line in pixels
57  uint32_t color_value;
58  for (int y = 0; y != h; y++) {
59  size_t source_idx = (y_offset + y) * line_stride + x_offset;
60  size_t source_idx_mod;
61  for (int x = 0; x != w; x++, source_idx++) {
62  switch (bitness) {
63  default:
64  color_value = ptr[source_idx];
65  break;
66  case COLOR_BITNESS_565:
67  source_idx_mod = source_idx * 2;
68  if (big_endian) {
69  color_value = (ptr[source_idx_mod] << 8) + ptr[source_idx_mod + 1];
70  } else {
71  color_value = ptr[source_idx_mod] + (ptr[source_idx_mod + 1] << 8);
72  }
73  break;
74  case COLOR_BITNESS_888:
75  source_idx_mod = source_idx * 3;
76  if (big_endian) {
77  color_value = (ptr[source_idx_mod + 0] << 16) + (ptr[source_idx_mod + 1] << 8) + ptr[source_idx_mod + 2];
78  } else {
79  color_value = ptr[source_idx_mod + 0] + (ptr[source_idx_mod + 1] << 8) + (ptr[source_idx_mod + 2] << 16);
80  }
81  break;
82  }
83  this->draw_pixel_at(x + x_start, y + y_start, ColorUtil::to_color(color_value, order, bitness));
84  }
85  }
86 }
87 
88 void HOT Display::horizontal_line(int x, int y, int width, Color color) {
89  // Future: Could be made more efficient by manipulating buffer directly in certain rotations.
90  for (int i = x; i < x + width; i++)
91  this->draw_pixel_at(i, y, color);
92 }
93 void HOT Display::vertical_line(int x, int y, int height, Color color) {
94  // Future: Could be made more efficient by manipulating buffer directly in certain rotations.
95  for (int i = y; i < y + height; i++)
96  this->draw_pixel_at(x, i, color);
97 }
98 void Display::rectangle(int x1, int y1, int width, int height, Color color) {
99  this->horizontal_line(x1, y1, width, color);
100  this->horizontal_line(x1, y1 + height - 1, width, color);
101  this->vertical_line(x1, y1, height, color);
102  this->vertical_line(x1 + width - 1, y1, height, color);
103 }
104 void Display::filled_rectangle(int x1, int y1, int width, int height, Color color) {
105  // Future: Use vertical_line and horizontal_line methods depending on rotation to reduce memory accesses.
106  for (int i = y1; i < y1 + height; i++) {
107  this->horizontal_line(x1, i, width, color);
108  }
109 }
110 void HOT Display::circle(int center_x, int center_xy, int radius, Color color) {
111  int dx = -radius;
112  int dy = 0;
113  int err = 2 - 2 * radius;
114  int e2;
115 
116  do {
117  this->draw_pixel_at(center_x - dx, center_xy + dy, color);
118  this->draw_pixel_at(center_x + dx, center_xy + dy, color);
119  this->draw_pixel_at(center_x + dx, center_xy - dy, color);
120  this->draw_pixel_at(center_x - dx, center_xy - dy, color);
121  e2 = err;
122  if (e2 < dy) {
123  err += ++dy * 2 + 1;
124  if (-dx == dy && e2 <= dx) {
125  e2 = 0;
126  }
127  }
128  if (e2 > dx) {
129  err += ++dx * 2 + 1;
130  }
131  } while (dx <= 0);
132 }
133 void Display::filled_circle(int center_x, int center_y, int radius, Color color) {
134  int dx = -int32_t(radius);
135  int dy = 0;
136  int err = 2 - 2 * radius;
137  int e2;
138 
139  do {
140  this->draw_pixel_at(center_x - dx, center_y + dy, color);
141  this->draw_pixel_at(center_x + dx, center_y + dy, color);
142  this->draw_pixel_at(center_x + dx, center_y - dy, color);
143  this->draw_pixel_at(center_x - dx, center_y - dy, color);
144  int hline_width = 2 * (-dx) + 1;
145  this->horizontal_line(center_x + dx, center_y + dy, hline_width, color);
146  this->horizontal_line(center_x + dx, center_y - dy, hline_width, color);
147  e2 = err;
148  if (e2 < dy) {
149  err += ++dy * 2 + 1;
150  if (-dx == dy && e2 <= dx) {
151  e2 = 0;
152  }
153  }
154  if (e2 > dx) {
155  err += ++dx * 2 + 1;
156  }
157  } while (dx <= 0);
158 }
159 void Display::filled_ring(int center_x, int center_y, int radius1, int radius2, Color color) {
160  int rmax = radius1 > radius2 ? radius1 : radius2;
161  int rmin = radius1 < radius2 ? radius1 : radius2;
162  int dxmax = -int32_t(rmax), dxmin = -int32_t(rmin);
163  int dymax = 0, dymin = 0;
164  int errmax = 2 - 2 * rmax, errmin = 2 - 2 * rmin;
165  int e2max, e2min;
166  do {
167  // 8 dots for borders
168  this->draw_pixel_at(center_x - dxmax, center_y + dymax, color);
169  this->draw_pixel_at(center_x + dxmax, center_y + dymax, color);
170  this->draw_pixel_at(center_x - dxmin, center_y + dymin, color);
171  this->draw_pixel_at(center_x + dxmin, center_y + dymin, color);
172  this->draw_pixel_at(center_x + dxmax, center_y - dymax, color);
173  this->draw_pixel_at(center_x - dxmax, center_y - dymax, color);
174  this->draw_pixel_at(center_x + dxmin, center_y - dymin, color);
175  this->draw_pixel_at(center_x - dxmin, center_y - dymin, color);
176  if (dymin < rmin) {
177  // two parts - four lines
178  int hline_width = -(dxmax - dxmin) + 1;
179  this->horizontal_line(center_x + dxmax, center_y + dymax, hline_width, color);
180  this->horizontal_line(center_x - dxmin, center_y + dymax, hline_width, color);
181  this->horizontal_line(center_x + dxmax, center_y - dymax, hline_width, color);
182  this->horizontal_line(center_x - dxmin, center_y - dymax, hline_width, color);
183  } else {
184  // one part - top and bottom
185  int hline_width = 2 * (-dxmax) + 1;
186  this->horizontal_line(center_x + dxmax, center_y + dymax, hline_width, color);
187  this->horizontal_line(center_x + dxmax, center_y - dymax, hline_width, color);
188  }
189  e2max = errmax;
190  // tune external
191  if (e2max < dymax) {
192  errmax += ++dymax * 2 + 1;
193  if (-dxmax == dymax && e2max <= dxmax) {
194  e2max = 0;
195  }
196  }
197  if (e2max > dxmax) {
198  errmax += ++dxmax * 2 + 1;
199  }
200  // tune internal
201  while (dymin < dymax && dymin < rmin) {
202  e2min = errmin;
203  if (e2min < dymin) {
204  errmin += ++dymin * 2 + 1;
205  if (-dxmin == dymin && e2min <= dxmin) {
206  e2min = 0;
207  }
208  }
209  if (e2min > dxmin) {
210  errmin += ++dxmin * 2 + 1;
211  }
212  }
213  } while (dxmax <= 0);
214 }
215 void Display::filled_gauge(int center_x, int center_y, int radius1, int radius2, int progress, Color color) {
216  int rmax = radius1 > radius2 ? radius1 : radius2;
217  int rmin = radius1 < radius2 ? radius1 : radius2;
218  int dxmax = -int32_t(rmax), dxmin = -int32_t(rmin), upd_dxmax, upd_dxmin;
219  int dymax = 0, dymin = 0;
220  int errmax = 2 - 2 * rmax, errmin = 2 - 2 * rmin;
221  int e2max, e2min;
222  progress = std::max(0, std::min(progress, 100)); // 0..100
223  int draw_progress = progress > 50 ? (100 - progress) : progress;
224  float tan_a = (progress == 50) ? 65535 : tan(float(draw_progress) * M_PI / 100); // slope
225 
226  do {
227  // outer dots
228  this->draw_pixel_at(center_x + dxmax, center_y - dymax, color);
229  this->draw_pixel_at(center_x - dxmax, center_y - dymax, color);
230  if (dymin < rmin) { // side parts
231  int lhline_width = -(dxmax - dxmin) + 1;
232  if (progress >= 50) {
233  if (float(dymax) < float(-dxmax) * tan_a) {
234  upd_dxmax = ceil(float(dymax) / tan_a);
235  } else {
236  upd_dxmax = -dxmax;
237  }
238  this->horizontal_line(center_x + dxmax, center_y - dymax, lhline_width, color); // left
239  if (!dymax)
240  this->horizontal_line(center_x - dxmin, center_y, lhline_width, color); // right horizontal border
241  if (upd_dxmax > -dxmin) { // right
242  int rhline_width = (upd_dxmax + dxmin) + 1;
243  this->horizontal_line(center_x - dxmin, center_y - dymax,
244  rhline_width > lhline_width ? lhline_width : rhline_width, color);
245  }
246  } else {
247  if (float(dymin) > float(-dxmin) * tan_a) {
248  upd_dxmin = ceil(float(dymin) / tan_a);
249  } else {
250  upd_dxmin = -dxmin;
251  }
252  lhline_width = -(dxmax + upd_dxmin) + 1;
253  if (!dymax)
254  this->horizontal_line(center_x - dxmin, center_y, lhline_width, color); // right horizontal border
255  if (lhline_width > 0)
256  this->horizontal_line(center_x + dxmax, center_y - dymax, lhline_width, color);
257  }
258  } else { // top part
259  int hline_width = 2 * (-dxmax) + 1;
260  if (progress >= 50) {
261  if (dymax < float(-dxmax) * tan_a) {
262  upd_dxmax = ceil(float(dymax) / tan_a);
263  hline_width = -dxmax + upd_dxmax + 1;
264  }
265  } else {
266  if (dymax < float(-dxmax) * tan_a) {
267  upd_dxmax = ceil(float(dymax) / tan_a);
268  hline_width = -dxmax - upd_dxmax + 1;
269  } else
270  hline_width = 0;
271  }
272  if (hline_width > 0)
273  this->horizontal_line(center_x + dxmax, center_y - dymax, hline_width, color);
274  }
275  e2max = errmax;
276  if (e2max < dymax) {
277  errmax += ++dymax * 2 + 1;
278  if (-dxmax == dymax && e2max <= dxmax) {
279  e2max = 0;
280  }
281  }
282  if (e2max > dxmax) {
283  errmax += ++dxmax * 2 + 1;
284  }
285  while (dymin <= dymax && dymin <= rmin && dxmin <= 0) {
286  this->draw_pixel_at(center_x + dxmin, center_y - dymin, color);
287  this->draw_pixel_at(center_x - dxmin, center_y - dymin, color);
288  e2min = errmin;
289  if (e2min < dymin) {
290  errmin += ++dymin * 2 + 1;
291  if (-dxmin == dymin && e2min <= dxmin) {
292  e2min = 0;
293  }
294  }
295  if (e2min > dxmin) {
296  errmin += ++dxmin * 2 + 1;
297  }
298  }
299  } while (dxmax <= 0);
300 }
301 void HOT Display::triangle(int x1, int y1, int x2, int y2, int x3, int y3, Color color) {
302  this->line(x1, y1, x2, y2, color);
303  this->line(x1, y1, x3, y3, color);
304  this->line(x2, y2, x3, y3, color);
305 }
306 void Display::sort_triangle_points_by_y_(int *x1, int *y1, int *x2, int *y2, int *x3, int *y3) {
307  if (*y1 > *y2) {
308  int x_temp = *x1, y_temp = *y1;
309  *x1 = *x2, *y1 = *y2;
310  *x2 = x_temp, *y2 = y_temp;
311  }
312  if (*y1 > *y3) {
313  int x_temp = *x1, y_temp = *y1;
314  *x1 = *x3, *y1 = *y3;
315  *x3 = x_temp, *y3 = y_temp;
316  }
317  if (*y2 > *y3) {
318  int x_temp = *x2, y_temp = *y2;
319  *x2 = *x3, *y2 = *y3;
320  *x3 = x_temp, *y3 = y_temp;
321  }
322 }
323 void Display::filled_flat_side_triangle_(int x1, int y1, int x2, int y2, int x3, int y3, Color color) {
324  // y2 must be equal to y3 (same horizontal line)
325 
326  // Initialize Bresenham's algorithm for side 1
327  int s1_current_x = x1;
328  int s1_current_y = y1;
329  bool s1_axis_swap = false;
330  int s1_dx = abs(x2 - x1);
331  int s1_dy = abs(y2 - y1);
332  int s1_sign_x = ((x2 - x1) >= 0) ? 1 : -1;
333  int s1_sign_y = ((y2 - y1) >= 0) ? 1 : -1;
334  if (s1_dy > s1_dx) { // swap values
335  int tmp = s1_dx;
336  s1_dx = s1_dy;
337  s1_dy = tmp;
338  s1_axis_swap = true;
339  }
340  int s1_error = 2 * s1_dy - s1_dx;
341 
342  // Initialize Bresenham's algorithm for side 2
343  int s2_current_x = x1;
344  int s2_current_y = y1;
345  bool s2_axis_swap = false;
346  int s2_dx = abs(x3 - x1);
347  int s2_dy = abs(y3 - y1);
348  int s2_sign_x = ((x3 - x1) >= 0) ? 1 : -1;
349  int s2_sign_y = ((y3 - y1) >= 0) ? 1 : -1;
350  if (s2_dy > s2_dx) { // swap values
351  int tmp = s2_dx;
352  s2_dx = s2_dy;
353  s2_dy = tmp;
354  s2_axis_swap = true;
355  }
356  int s2_error = 2 * s2_dy - s2_dx;
357 
358  // Iterate on side 1 and allow side 2 to be processed to match the advance of the y-axis.
359  for (int i = 0; i <= s1_dx; i++) {
360  if (s1_current_x <= s2_current_x) {
361  this->horizontal_line(s1_current_x, s1_current_y, s2_current_x - s1_current_x + 1, color);
362  } else {
363  this->horizontal_line(s2_current_x, s2_current_y, s1_current_x - s2_current_x + 1, color);
364  }
365 
366  // Bresenham's #1
367  // Side 1 s1_current_x and s1_current_y calculation
368  while (s1_error >= 0) {
369  if (s1_axis_swap) {
370  s1_current_x += s1_sign_x;
371  } else {
372  s1_current_y += s1_sign_y;
373  }
374  s1_error = s1_error - 2 * s1_dx;
375  }
376  if (s1_axis_swap) {
377  s1_current_y += s1_sign_y;
378  } else {
379  s1_current_x += s1_sign_x;
380  }
381  s1_error = s1_error + 2 * s1_dy;
382 
383  // Bresenham's #2
384  // Side 2 s2_current_x and s2_current_y calculation
385  while (s2_current_y != s1_current_y) {
386  while (s2_error >= 0) {
387  if (s2_axis_swap) {
388  s2_current_x += s2_sign_x;
389  } else {
390  s2_current_y += s2_sign_y;
391  }
392  s2_error = s2_error - 2 * s2_dx;
393  }
394  if (s2_axis_swap) {
395  s2_current_y += s2_sign_y;
396  } else {
397  s2_current_x += s2_sign_x;
398  }
399  s2_error = s2_error + 2 * s2_dy;
400  }
401  }
402 }
403 void Display::filled_triangle(int x1, int y1, int x2, int y2, int x3, int y3, Color color) {
404  // Sort the three points by y-coordinate ascending, so [x1,y1] is the topmost point
405  this->sort_triangle_points_by_y_(&x1, &y1, &x2, &y2, &x3, &y3);
406 
407  if (y2 == y3) { // Check for special case of a bottom-flat triangle
408  this->filled_flat_side_triangle_(x1, y1, x2, y2, x3, y3, color);
409  } else if (y1 == y2) { // Check for special case of a top-flat triangle
410  this->filled_flat_side_triangle_(x3, y3, x1, y1, x2, y2, color);
411  } else { // General case: split the no-flat-side triangle in a top-flat triangle and bottom-flat triangle
412  int x_temp = (int) (x1 + ((float) (y2 - y1) / (float) (y3 - y1)) * (x3 - x1)), y_temp = y2;
413  this->filled_flat_side_triangle_(x1, y1, x2, y2, x_temp, y_temp, color);
414  this->filled_flat_side_triangle_(x3, y3, x2, y2, x_temp, y_temp, color);
415  }
416 }
417 void HOT Display::get_regular_polygon_vertex(int vertex_id, int *vertex_x, int *vertex_y, int center_x, int center_y,
418  int radius, int edges, RegularPolygonVariation variation,
419  float rotation_degrees) {
420  if (edges >= 2) {
421  // Given the orientation of the display component, an angle is measured clockwise from the x axis.
422  // For a regular polygon, the human reference would be the top of the polygon,
423  // hence we rotate the shape by 270° to orient the polygon up.
424  rotation_degrees += ROTATION_270_DEGREES;
425  // Convert the rotation to radians, easier to use in trigonometrical calculations
426  float rotation_radians = rotation_degrees * PI / 180;
427  // A pointy top variation means the first vertex of the polygon is at the top center of the shape, this requires no
428  // additional rotation of the shape.
429  // A flat top variation means the first point of the polygon has to be rotated so that the first edge is horizontal,
430  // this requires to rotate the shape by Ï€/edges radians counter-clockwise so that the first point is located on the
431  // left side of the first horizontal edge.
432  rotation_radians -= (variation == VARIATION_FLAT_TOP) ? PI / edges : 0.0;
433 
434  float vertex_angle = ((float) vertex_id) / edges * 2 * PI + rotation_radians;
435  *vertex_x = (int) round(cos(vertex_angle) * radius) + center_x;
436  *vertex_y = (int) round(sin(vertex_angle) * radius) + center_y;
437  }
438 }
439 
440 void HOT Display::regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation,
441  float rotation_degrees, Color color, RegularPolygonDrawing drawing) {
442  if (edges >= 2) {
443  int previous_vertex_x, previous_vertex_y;
444  for (int current_vertex_id = 0; current_vertex_id <= edges; current_vertex_id++) {
445  int current_vertex_x, current_vertex_y;
446  get_regular_polygon_vertex(current_vertex_id, &current_vertex_x, &current_vertex_y, x, y, radius, edges,
447  variation, rotation_degrees);
448  if (current_vertex_id > 0) { // Start drawing after the 2nd vertex coordinates has been calculated
449  if (drawing == DRAWING_FILLED) {
450  this->filled_triangle(x, y, previous_vertex_x, previous_vertex_y, current_vertex_x, current_vertex_y, color);
451  } else if (drawing == DRAWING_OUTLINE) {
452  this->line(previous_vertex_x, previous_vertex_y, current_vertex_x, current_vertex_y, color);
453  }
454  }
455  previous_vertex_x = current_vertex_x;
456  previous_vertex_y = current_vertex_y;
457  }
458  }
459 }
460 void HOT Display::regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation, Color color,
461  RegularPolygonDrawing drawing) {
462  regular_polygon(x, y, radius, edges, variation, ROTATION_0_DEGREES, color, drawing);
463 }
464 void HOT Display::regular_polygon(int x, int y, int radius, int edges, Color color, RegularPolygonDrawing drawing) {
465  regular_polygon(x, y, radius, edges, VARIATION_POINTY_TOP, ROTATION_0_DEGREES, color, drawing);
466 }
467 void Display::filled_regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation,
468  float rotation_degrees, Color color) {
469  regular_polygon(x, y, radius, edges, variation, rotation_degrees, color, DRAWING_FILLED);
470 }
471 void Display::filled_regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation,
472  Color color) {
473  regular_polygon(x, y, radius, edges, variation, ROTATION_0_DEGREES, color, DRAWING_FILLED);
474 }
475 void Display::filled_regular_polygon(int x, int y, int radius, int edges, Color color) {
477 }
478 
479 void Display::print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text, Color background) {
480  int x_start, y_start;
481  int width, height;
482  this->get_text_bounds(x, y, text, font, align, &x_start, &y_start, &width, &height);
483  font->print(x_start, y_start, this, color, text, background);
484 }
485 
486 void Display::vprintf_(int x, int y, BaseFont *font, Color color, Color background, TextAlign align, const char *format,
487  va_list arg) {
488  char buffer[256];
489  int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
490  if (ret > 0)
491  this->print(x, y, font, color, align, buffer, background);
492 }
493 
494 void Display::image(int x, int y, BaseImage *image, Color color_on, Color color_off) {
495  this->image(x, y, image, ImageAlign::TOP_LEFT, color_on, color_off);
496 }
497 
498 void Display::image(int x, int y, BaseImage *image, ImageAlign align, Color color_on, Color color_off) {
499  auto x_align = ImageAlign(int(align) & (int(ImageAlign::HORIZONTAL_ALIGNMENT)));
500  auto y_align = ImageAlign(int(align) & (int(ImageAlign::VERTICAL_ALIGNMENT)));
501 
502  switch (x_align) {
503  case ImageAlign::RIGHT:
504  x -= image->get_width();
505  break;
507  x -= image->get_width() / 2;
508  break;
509  case ImageAlign::LEFT:
510  default:
511  break;
512  }
513 
514  switch (y_align) {
515  case ImageAlign::BOTTOM:
516  y -= image->get_height();
517  break;
519  y -= image->get_height() / 2;
520  break;
521  case ImageAlign::TOP:
522  default:
523  break;
524  }
525 
526  image->draw(x, y, this, color_on, color_off);
527 }
528 
529 #ifdef USE_GRAPH
530 void Display::graph(int x, int y, graph::Graph *graph, Color color_on) { graph->draw(this, x, y, color_on); }
531 void Display::legend(int x, int y, graph::Graph *graph, Color color_on) { graph->draw_legend(this, x, y, color_on); }
532 #endif // USE_GRAPH
533 
534 #ifdef USE_QR_CODE
535 void Display::qr_code(int x, int y, qr_code::QrCode *qr_code, Color color_on, int scale) {
536  qr_code->draw(this, x, y, color_on, scale);
537 }
538 #endif // USE_QR_CODE
539 
540 #ifdef USE_GRAPHICAL_DISPLAY_MENU
541 void Display::menu(int x, int y, graphical_display_menu::GraphicalDisplayMenu *menu, int width, int height) {
542  Rect rect(x, y, width, height);
543  menu->draw(this, &rect);
544 }
545 #endif // USE_GRAPHICAL_DISPLAY_MENU
546 
547 void Display::get_text_bounds(int x, int y, const char *text, BaseFont *font, TextAlign align, int *x1, int *y1,
548  int *width, int *height) {
549  int x_offset, baseline;
550  font->measure(text, width, &x_offset, &baseline, height);
551 
552  auto x_align = TextAlign(int(align) & 0x18);
553  auto y_align = TextAlign(int(align) & 0x07);
554 
555  switch (x_align) {
556  case TextAlign::RIGHT:
557  *x1 = x - *width;
558  break;
560  *x1 = x - (*width) / 2;
561  break;
562  case TextAlign::LEFT:
563  default:
564  // LEFT
565  *x1 = x;
566  break;
567  }
568 
569  switch (y_align) {
570  case TextAlign::BOTTOM:
571  *y1 = y - *height;
572  break;
573  case TextAlign::BASELINE:
574  *y1 = y - baseline;
575  break;
577  *y1 = y - (*height) / 2;
578  break;
579  case TextAlign::TOP:
580  default:
581  *y1 = y;
582  break;
583  }
584 }
585 void Display::print(int x, int y, BaseFont *font, Color color, const char *text, Color background) {
586  this->print(x, y, font, color, TextAlign::TOP_LEFT, text, background);
587 }
588 void Display::print(int x, int y, BaseFont *font, TextAlign align, const char *text) {
589  this->print(x, y, font, COLOR_ON, align, text);
590 }
591 void Display::print(int x, int y, BaseFont *font, const char *text) {
592  this->print(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, text);
593 }
594 void Display::printf(int x, int y, BaseFont *font, Color color, Color background, TextAlign align, const char *format,
595  ...) {
596  va_list arg;
597  va_start(arg, format);
598  this->vprintf_(x, y, font, color, background, align, format, arg);
599  va_end(arg);
600 }
601 void Display::printf(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ...) {
602  va_list arg;
603  va_start(arg, format);
604  this->vprintf_(x, y, font, color, COLOR_OFF, align, format, arg);
605  va_end(arg);
606 }
607 void Display::printf(int x, int y, BaseFont *font, Color color, const char *format, ...) {
608  va_list arg;
609  va_start(arg, format);
610  this->vprintf_(x, y, font, color, COLOR_OFF, TextAlign::TOP_LEFT, format, arg);
611  va_end(arg);
612 }
613 void Display::printf(int x, int y, BaseFont *font, TextAlign align, const char *format, ...) {
614  va_list arg;
615  va_start(arg, format);
616  this->vprintf_(x, y, font, COLOR_ON, COLOR_OFF, align, format, arg);
617  va_end(arg);
618 }
619 void Display::printf(int x, int y, BaseFont *font, const char *format, ...) {
620  va_list arg;
621  va_start(arg, format);
622  this->vprintf_(x, y, font, COLOR_ON, COLOR_OFF, TextAlign::TOP_LEFT, format, arg);
623  va_end(arg);
624 }
625 void Display::set_writer(display_writer_t &&writer) { this->writer_ = writer; }
626 void Display::set_pages(std::vector<DisplayPage *> pages) {
627  for (auto *page : pages)
628  page->set_parent(this);
629 
630  for (uint32_t i = 0; i < pages.size() - 1; i++) {
631  pages[i]->set_next(pages[i + 1]);
632  pages[i + 1]->set_prev(pages[i]);
633  }
634  pages[0]->set_prev(pages[pages.size() - 1]);
635  pages[pages.size() - 1]->set_next(pages[0]);
636  this->show_page(pages[0]);
637 }
639  this->previous_page_ = this->page_;
640  this->page_ = page;
641  if (this->previous_page_ != this->page_) {
642  for (auto *t : on_page_change_triggers_)
643  t->process(this->previous_page_, this->page_);
644  }
645 }
649  if (this->auto_clear_enabled_) {
650  this->clear();
651  }
652  if (this->show_test_card_) {
653  this->test_card();
654  } else if (this->page_ != nullptr) {
655  this->page_->get_writer()(*this);
656  } else if (this->writer_.has_value()) {
657  (*this->writer_)(*this);
658  }
659  this->clear_clipping_();
660 }
662  if ((this->from_ == nullptr || this->from_ == from) && (this->to_ == nullptr || this->to_ == to))
663  this->trigger(from, to);
664 }
665 void Display::strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ESPTime time) {
666  char buffer[64];
667  size_t ret = time.strftime(buffer, sizeof(buffer), format);
668  if (ret > 0)
669  this->print(x, y, font, color, align, buffer);
670 }
671 void Display::strftime(int x, int y, BaseFont *font, Color color, const char *format, ESPTime time) {
672  this->strftime(x, y, font, color, TextAlign::TOP_LEFT, format, time);
673 }
674 void Display::strftime(int x, int y, BaseFont *font, TextAlign align, const char *format, ESPTime time) {
675  this->strftime(x, y, font, COLOR_ON, align, format, time);
676 }
677 void Display::strftime(int x, int y, BaseFont *font, const char *format, ESPTime time) {
678  this->strftime(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, time);
679 }
680 
682  if (!this->clipping_rectangle_.empty()) {
683  Rect r = this->clipping_rectangle_.back();
684  rect.shrink(r);
685  }
686  this->clipping_rectangle_.push_back(rect);
687 }
689  if (this->clipping_rectangle_.empty()) {
690  ESP_LOGE(TAG, "clear: Clipping is not set.");
691  } else {
692  this->clipping_rectangle_.pop_back();
693  }
694 }
696  if (this->clipping_rectangle_.empty()) {
697  ESP_LOGE(TAG, "add: Clipping is not set.");
698  } else {
699  this->clipping_rectangle_.back().extend(add_rect);
700  }
701 }
703  if (this->clipping_rectangle_.empty()) {
704  ESP_LOGE(TAG, "add: Clipping is not set.");
705  } else {
706  this->clipping_rectangle_.back().shrink(add_rect);
707  }
708 }
710  if (this->clipping_rectangle_.empty()) {
711  return Rect();
712  } else {
713  return this->clipping_rectangle_.back();
714  }
715 }
717 bool Display::clip(int x, int y) {
718  if (x < 0 || x >= this->get_width() || y < 0 || y >= this->get_height())
719  return false;
720  if (!this->get_clipping().inside(x, y))
721  return false;
722  return true;
723 }
724 bool Display::clamp_x_(int x, int w, int &min_x, int &max_x) {
725  min_x = std::max(x, 0);
726  max_x = std::min(x + w, this->get_width());
727 
728  if (!this->clipping_rectangle_.empty()) {
729  const auto &rect = this->clipping_rectangle_.back();
730  if (!rect.is_set())
731  return false;
732 
733  min_x = std::max(min_x, (int) rect.x);
734  max_x = std::min(max_x, (int) rect.x2());
735  }
736 
737  return min_x < max_x;
738 }
739 bool Display::clamp_y_(int y, int h, int &min_y, int &max_y) {
740  min_y = std::max(y, 0);
741  max_y = std::min(y + h, this->get_height());
742 
743  if (!this->clipping_rectangle_.empty()) {
744  const auto &rect = this->clipping_rectangle_.back();
745  if (!rect.is_set())
746  return false;
747 
748  min_y = std::max(min_y, (int) rect.y);
749  max_y = std::min(max_y, (int) rect.y2());
750  }
751 
752  return min_y < max_y;
753 }
754 
755 const uint8_t TESTCARD_FONT[3][8] PROGMEM = {{0x41, 0x7F, 0x7F, 0x09, 0x19, 0x7F, 0x66, 0x00}, // 'R'
756  {0x1C, 0x3E, 0x63, 0x41, 0x51, 0x73, 0x72, 0x00}, // 'G'
757  {0x41, 0x7F, 0x7F, 0x49, 0x49, 0x7F, 0x36, 0x00}}; // 'B'
758 
760  int w = get_width(), h = get_height(), image_w, image_h;
761  this->clear();
762  this->show_test_card_ = false;
763  if (this->get_display_type() == DISPLAY_TYPE_COLOR) {
764  Color r(255, 0, 0), g(0, 255, 0), b(0, 0, 255);
765  image_w = std::min(w - 20, 310);
766  image_h = std::min(h - 20, 255);
767 
768  int shift_x = (w - image_w) / 2;
769  int shift_y = (h - image_h) / 2;
770  int line_w = (image_w - 6) / 6;
771  int image_c = image_w / 2;
772  for (auto i = 0; i <= image_h; i++) {
773  int c = esp_scale(i, image_h);
774  this->horizontal_line(shift_x + 0, shift_y + i, line_w, r.fade_to_white(c));
775  this->horizontal_line(shift_x + line_w, shift_y + i, line_w, r.fade_to_black(c)); //
776 
777  this->horizontal_line(shift_x + image_c - line_w, shift_y + i, line_w, g.fade_to_white(c));
778  this->horizontal_line(shift_x + image_c, shift_y + i, line_w, g.fade_to_black(c));
779 
780  this->horizontal_line(shift_x + image_w - (line_w * 2), shift_y + i, line_w, b.fade_to_white(c));
781  this->horizontal_line(shift_x + image_w - line_w, shift_y + i, line_w, b.fade_to_black(c));
782  }
783  this->rectangle(shift_x, shift_y, image_w, image_h, Color(127, 127, 0));
784 
785  uint16_t shift_r = shift_x + line_w - (8 * 3);
786  uint16_t shift_g = shift_x + image_c - (8 * 3);
787  uint16_t shift_b = shift_x + image_w - line_w - (8 * 3);
788  shift_y = h / 2 - (8 * 3);
789  for (auto i = 0; i < 8; i++) {
790  uint8_t ftr = progmem_read_byte(&TESTCARD_FONT[0][i]);
791  uint8_t ftg = progmem_read_byte(&TESTCARD_FONT[1][i]);
792  uint8_t ftb = progmem_read_byte(&TESTCARD_FONT[2][i]);
793  for (auto k = 0; k < 8; k++) {
794  if ((ftr & (1 << k)) != 0) {
795  this->filled_rectangle(shift_r + (i * 6), shift_y + (k * 6), 6, 6, COLOR_OFF);
796  }
797  if ((ftg & (1 << k)) != 0) {
798  this->filled_rectangle(shift_g + (i * 6), shift_y + (k * 6), 6, 6, COLOR_OFF);
799  }
800  if ((ftb & (1 << k)) != 0) {
801  this->filled_rectangle(shift_b + (i * 6), shift_y + (k * 6), 6, 6, COLOR_OFF);
802  }
803  }
804  }
805  }
806  this->rectangle(0, 0, w, h, Color(127, 0, 127));
807  this->filled_rectangle(0, 0, 10, 10, Color(255, 0, 255));
808  this->stop_poller();
809 }
810 
812 void DisplayPage::show() { this->parent_->show_page(this); }
813 void DisplayPage::show_next() { this->next_->show(); }
814 void DisplayPage::show_prev() { this->prev_->show(); }
815 void DisplayPage::set_parent(Display *parent) { this->parent_ = parent; }
816 void DisplayPage::set_prev(DisplayPage *prev) { this->prev_ = prev; }
817 void DisplayPage::set_next(DisplayPage *next) { this->next_ = next; }
818 const display_writer_t &DisplayPage::get_writer() const { return this->writer_; }
819 
820 const LogString *text_align_to_string(TextAlign textalign) {
821  switch (textalign) {
822  case TextAlign::TOP_LEFT:
823  return LOG_STR("TOP_LEFT");
825  return LOG_STR("TOP_CENTER");
827  return LOG_STR("TOP_RIGHT");
829  return LOG_STR("CENTER_LEFT");
830  case TextAlign::CENTER:
831  return LOG_STR("CENTER");
833  return LOG_STR("CENTER_RIGHT");
835  return LOG_STR("BASELINE_LEFT");
837  return LOG_STR("BASELINE_CENTER");
839  return LOG_STR("BASELINE_RIGHT");
841  return LOG_STR("BOTTOM_LEFT");
843  return LOG_STR("BOTTOM_CENTER");
845  return LOG_STR("BOTTOM_RIGHT");
846  default:
847  return LOG_STR("UNKNOWN");
848  }
849 }
850 
851 } // namespace display
852 } // namespace esphome
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...
Definition: display.cpp:110
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.
Definition: display.cpp:88
optional< display_writer_t > writer_
Definition: display.h:667
std::vector< DisplayOnPageChangeTrigger * > on_page_change_triggers_
Definition: display.h:670
void set_pages(std::vector< DisplayPage *> pages)
Definition: display.cpp:626
bool clamp_x_(int x, int w, int &min_x, int &max_x)
Definition: display.cpp:724
void sort_triangle_points_by_y_(int *x1, int *y1, int *x2, int *y2, int *x3, int *y3)
Definition: display.cpp:306
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...
Definition: time.cpp:15
void get_text_bounds(int x, int y, const char *text, BaseFont *font, TextAlign align, int *x1, int *y1, int *width, int *height)
Get the text bounds of the given string.
Definition: display.cpp:547
void set_next(DisplayPage *next)
Definition: display.cpp:817
void set_parent(Display *parent)
Definition: display.cpp:815
uint16_t x
Definition: tt21100.cpp:17
Color fade_to_black(uint8_t amnt)
Definition: color.h:163
void filled_flat_side_triangle_(int x1, int y1, int x2, int y2, int x3, int y3, Color color)
This method fills a triangle using only integer variables by using a modified bresenham algorithm...
Definition: display.cpp:323
A more user-friendly version of struct tm from time.h.
Definition: time.h:15
void set_rotation(DisplayRotation rotation)
Internal method to set the display rotation with.
Definition: display.cpp:17
const uint8_t TESTCARD_FONT [3][8] PROGMEM
Definition: display.cpp:755
Color fade_to_white(uint8_t amnt)
Definition: color.h:162
const Color COLOR_OFF(0, 0, 0, 0)
Turn the pixel OFF.
Definition: display.h:191
void filled_ring(int center_x, int center_y, int radius1, int radius2, Color color=COLOR_ON)
Fill a ring centered around [center_x,center_y] between two circles with the radius1 and radius2 with...
Definition: display.cpp:159
void extend_clipping(Rect rect)
Add a rectangular region to the invalidation region.
Definition: display.cpp:695
STL namespace.
void shrink_clipping(Rect rect)
substract a rectangular region to the invalidation region
Definition: display.cpp:702
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...
Definition: display.cpp:133
void triangle(int x1, int y1, int x2, int y2, int x3, int y3, Color color=COLOR_ON)
Draw the outline of a triangle contained between the points [x1,y1], [x2,y2] and [x3,y3] with the given color.
Definition: display.cpp:301
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...
Definition: display.cpp:104
bool has_value() const
Definition: optional.h:87
virtual void fill(Color color)
Fill the entire screen with the given color.
Definition: display.cpp:15
uint8_t h
Definition: bl0906.h:209
DisplayPage(display_writer_t writer)
Definition: display.cpp:811
Rect get_clipping() const
Get the current the clipping rectangle.
Definition: display.cpp:709
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.
Definition: display.cpp:535
virtual int get_width()
Get the calculated width of the display in pixels with rotation applied.
Definition: display.h:216
const float ROTATION_270_DEGREES
Definition: display.h:165
bool clamp_y_(int y, int h, int &min_y, int &max_y)
Definition: display.cpp:739
virtual void draw(int x, int y, Display *display, Color color_on, Color color_off)=0
uint16_t y
Definition: tt21100.cpp:18
const display_writer_t & get_writer() const
Definition: display.cpp:818
void filled_triangle(int x1, int y1, int x2, int y2, int x3, int y3, Color color=COLOR_ON)
Fill a triangle contained between the points [x1,y1], [x2,y2] and [x3,y3] with the given color...
Definition: display.cpp:403
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+...
Definition: display.cpp:98
virtual void print(int x, int y, Display *display, Color color, const char *text, Color background)=0
TextAlign
TextAlign is used to tell the display class how to position a piece of text.
Definition: display.h:53
DisplayPage * page_
Definition: display.h:668
std::function< void(Display &)> display_writer_t
Definition: display.h:181
void process(DisplayPage *from, DisplayPage *to)
Definition: display.cpp:661
void start_clipping(Rect rect)
Set the clipping rectangle for further drawing.
Definition: display.cpp:681
ImageAlign
ImageAlign is used to tell the display class how to position a image.
Definition: display.h:103
void clear()
Clear the entire screen by filling it with OFF pixels.
Definition: display.cpp:16
void line_at_angle(int x, int y, int angle, int length, Color color=COLOR_ON)
Draw a straight line at the given angle based on the origin [x, y] for a specified length with the gi...
Definition: display.cpp:39
void print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text, Color background=COLOR_OFF)
Print text with the anchor point at [x,y] with font.
Definition: display.cpp:479
void draw_legend(display::Display *buff, uint16_t x_offset, uint16_t y_offset, Color color)
Definition: graph.cpp:335
const float ROTATION_0_DEGREES
Definition: display.h:161
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.
Definition: display.cpp:18
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.
Definition: display.cpp:531
void menu(int x, int y, graphical_display_menu::GraphicalDisplayMenu *menu, int width, int height)
Definition: display.cpp:541
void draw(display::Display *buff, uint16_t x_offset, uint16_t y_offset, Color color, int scale)
Definition: qr_code.cpp:36
void filled_regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation=VARIATION_POINTY_TOP, float rotation_degrees=ROTATION_0_DEGREES, Color color=COLOR_ON)
Fill a regular polygon inscribed in the circle centered on [x,y] with the given radius and color...
Definition: display.cpp:467
void vprintf_(int x, int y, BaseFont *font, Color color, Color background, TextAlign align, const char *format, va_list arg)
Definition: display.cpp:486
display_writer_t writer_
Definition: display.h:689
DisplayRotation rotation_
Definition: display.h:666
void end_clipping()
Reset the invalidation region.
Definition: display.cpp:688
virtual int get_height()
Get the calculated height of the display in pixels with rotation applied.
Definition: display.h:218
DisplayPage * previous_page_
Definition: display.h:669
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.
Definition: display.cpp:93
uint8_t progmem_read_byte(const uint8_t *addr)
Definition: core.cpp:55
const Color COLOR_ON(255, 255, 255, 255)
Turn the pixel ON.
Definition: display.h:193
const LogString * text_align_to_string(TextAlign textalign)
Definition: display.cpp:820
virtual DisplayType get_display_type()=0
Get the type of display that the buffer corresponds to.
virtual int get_width() const =0
bool clip(int x, int y)
Check if pixel is within region of display.
Definition: display.cpp:717
void void void void void void strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ESPTime time) __attribute__((format(strftime
Evaluate the strftime-format format and print the result with the anchor point at [x...
Definition: display.cpp:665
uint16_t length
Definition: tt21100.cpp:12
void draw_pixel_at(int x, int y)
Set a single pixel at the specified coordinates to default color.
Definition: display.h:226
void printf(int x, int y, BaseFont *font, Color color, Color background, 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.
Definition: display.cpp:594
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
static Color to_color(uint32_t colorcode, ColorOrder color_order, ColorBitness color_bitness=ColorBitness::COLOR_BITNESS_888, bool right_bit_aligned=true)
void draw(display::Display *display, const display::Rect *bounds)
void draw(display::Display *buff, uint16_t x_offset, uint16_t y_offset, Color color)
Definition: graph.cpp:59
void get_regular_polygon_vertex(int vertex_id, int *vertex_x, int *vertex_y, int center_x, int center_y, int radius, int edges, RegularPolygonVariation variation=VARIATION_POINTY_TOP, float rotation_degrees=ROTATION_0_DEGREES)
Get the specified vertex (x,y) coordinates for the regular polygon inscribed in the circle centered o...
Definition: display.cpp:417
virtual void measure(const char *str, int *width, int *x_offset, int *baseline, int *height)=0
std::vector< Rect > clipping_rectangle_
Definition: display.h:672
void set_writer(display_writer_t &&writer)
Internal method to set the display writer lambda.
Definition: display.cpp:625
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.
Definition: display.cpp:530
void shrink(Rect rect)
Definition: rect.cpp:42
void void void void void void void void void void image(int x, int y, BaseImage *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.
Definition: display.cpp:494
void show_page(DisplayPage *page)
Definition: display.cpp:638
void regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation=VARIATION_POINTY_TOP, float rotation_degrees=ROTATION_0_DEGREES, Color color=COLOR_ON, RegularPolygonDrawing drawing=DRAWING_OUTLINE)
Draw the outline of a regular polygon inscribed in the circle centered on [x,y] with the given radius...
Definition: display.cpp:440
virtual void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order, ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad)
Given an array of pixels encoded in the nominated format, draw these into the display&#39;s buffer...
Definition: display.cpp:54
void set_prev(DisplayPage *prev)
Definition: display.cpp:816
virtual int get_height() const =0
void filled_gauge(int center_x, int center_y, int radius1, int radius2, int progress, Color color=COLOR_ON)
Fill a half-ring "gauge" centered around [center_x,center_y] between two circles with the radius1 and...
Definition: display.cpp:215