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