FastArduino v1.10
C++ library to build fast but small Arduino/AVR projects
Loading...
Searching...
No Matches
display.h
Go to the documentation of this file.
1// Copyright 2016-2023 Jean-Francois Poilpret
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
16
21#ifndef DISPLAY_HH
22#define DISPLAY_HH
23
24#include "../flash.h"
25#include "../initializer_list.h"
26#include "../types_traits.h"
27#include "../utilities.h"
28#include "font.h"
29
30namespace devices
31{
70 namespace display
71 {
72 }
73}
74
75//TODO Add new 2D primitives e.g. color pixmaps
76//TODO Add pattern to DrawMode (to allow dotted lines or pseudo-grey)?
77//TODO Add thickness to DrawMode (to allow thick lines)?
78//TODO Use device traits to also determine what modes a device can handle!
79
80namespace devices::display
81{
83 template<typename XCOORD, typename YCOORD>
84 struct Point
85 {
86 Point(XCOORD x, YCOORD y) : x{x}, y{y} {}
87 XCOORD x;
88 YCOORD y;
89 };
91
101 enum class Mode : uint8_t
102 {
104 COPY = 0,
106 XOR,
108 AND,
110 OR,
112 NO_CHANGE = 0xFF
113 };
114
123 template<typename COLOR> class DrawMode
124 {
125 public:
136 : mode_{mode}, color_{color} {}
137
144 operator bool() const
145 {
146 return mode_ != Mode::NO_CHANGE;
147 }
148
154 Mode mode() const
155 {
156 return mode_;
157 }
158
164 COLOR color() const
165 {
166 return color_;
167 }
168
178 uint8_t bw_pixels_op(uint8_t source, uint8_t dest) const
179 {
180 // Invert source if colow is black
181 if (!color_)
182 source = ~source;
183 switch (mode_)
184 {
185 case Mode::COPY:
186 default:
187 return source;
188
189 case Mode::XOR:
190 return source ^ dest;
191
192 case Mode::AND:
193 return source & dest;
194
195 case Mode::OR:
196 return source | dest;
197 }
198 }
199
208 COLOR pixel_op(COLOR dest) const
209 {
210 switch (mode_)
211 {
212 case Mode::COPY:
213 default:
214 return color_;
215
216 case Mode::XOR:
217 return color_ ^ dest;
218
219 case Mode::AND:
220 return color_ & dest;
221
222 case Mode::OR:
223 return color_ | dest;
224 }
225 }
226
227 private:
228 Mode mode_;
229 COLOR color_;
230 };
231
247 template<typename COLOR, bool VERTICAL_FONT> class DrawContext
248 {
249 public:
251 DrawContext() = default;
253
262 {
263 return (is_fill_ ? fill_ : draw_);
264 }
265
272 {
273 return *font_;
274 }
275
276 private:
277 // These variables are set by `Display` (which is a friend) before calling
278 // a display device low-level primitive
279 bool is_fill_ = false;
280 DrawMode<COLOR> draw_{};
281 DrawMode<COLOR> fill_{};
282 const Font<VERTICAL_FONT>* font_ = nullptr;
283
284 template<typename> friend class Display;
285 };
286
291 enum class Error : uint8_t
292 {
294 NO_ERROR = 0,
321 };
322
341 template<typename DEVICE> struct DisplayDeviceTrait
342 {
344 static constexpr bool IS_DISPLAY = false;
350 using COLOR = void;
352 using XCOORD = uint8_t;
354 using YCOORD = uint8_t;
356 static constexpr XCOORD MAX_X = 0;
358 static constexpr YCOORD MAX_Y = 0;
360 static constexpr uint8_t WIDTH = 0;
362 static constexpr uint8_t HEIGHT = 0;
367 using SCALAR = uint8_t;
373 using SIGNED_SCALAR = int8_t;
375 static constexpr bool VERTICAL_FONT = false;
377 static constexpr bool HAS_RASTER = false;
378 };
379
391 template<typename COLOR_, uint16_t WIDTH_, uint16_t HEIGHT_,
392 bool HAS_RASTER_ = false, bool VERTICAL_FONT_ = false>
394 {
396 static constexpr bool IS_DISPLAY = true;
397 using COLOR = COLOR_;
398
399 using XCOORD = typename types_traits::SmallestInt<WIDTH_ - 1>::UNSIGNED_TYPE;
400 using YCOORD = typename types_traits::SmallestInt<HEIGHT_ - 1>::UNSIGNED_TYPE;
401
402 static constexpr XCOORD MAX_X = WIDTH_ - 1;
403 static constexpr YCOORD MAX_Y = HEIGHT_ - 1;
404
405 static constexpr uint16_t WIDTH = WIDTH_;
406 static constexpr uint16_t HEIGHT = HEIGHT_;
407
408 // SCALAR is the biggest of XCOORD and YCOORD types
409 using SCALAR = typename types_traits::SmallestInt<utils::max(WIDTH, HEIGHT)>::UNSIGNED_TYPE;
410 // SIGNED_SCALAR must be large enough to store -4 * min(WIDTH,HEIGHT)
411 using SIGNED_SCALAR = typename types_traits::SmallestInt<utils::min(WIDTH, HEIGHT) * 4 + 1>::SIGNED_TYPE;
412
413 static constexpr bool VERTICAL_FONT = VERTICAL_FONT_;
414 static constexpr bool HAS_RASTER = HAS_RASTER_;
416 };
417
434 template<typename DISPLAY_DEVICE> class Display : public DISPLAY_DEVICE
435 {
437 // Check at compile-time that DISPLAY_DEVICE is really a Display Device
438 static_assert(DISPLAY_TRAITS::IS_DISPLAY,
439 "DISPLAY_DEVICE must be a Display Device (a DisplayDeviceTrait must exist for it)!");
441
442 public:
444 using COLOR = typename DISPLAY_TRAITS::COLOR;
453
455 using POINT = Point<XCOORD, YCOORD>;
456
462
467
468 protected:
475
476 public:
478 Display() = default;
479
485 void set_draw_mode(const DRAW_MODE& mode)
486 {
487 context_.draw_ = mode;
488 }
489
496 void set_fill_mode(const DRAW_MODE& mode)
497 {
498 context_.fill_ = mode;
499 }
500
507 void set_font(const FONT& font)
508 {
509 context_.font_ = &font;
510 }
511
522 {
523 return last_error_;
524 }
525
529 void erase()
530 {
531 DISPLAY_DEVICE::erase();
532 invalidate();
533 }
534
544 void draw_char(POINT point, char value)
545 {
546 // Check one font is currently selected
547 if (!check_font()) return;
548 // Check coordinates are suitable for character display
549 const uint8_t width = context_.font_->width();
550 XCOORD x = point.x;
551 YCOORD y = point.y;
552 if (!is_valid_char_xy(x, y, width)) return;
553 // Check glyph exists for current character
554 uint16_t glyph_ref = get_glyph(value);
555 if (glyph_ref == 0) return;
556 // Delegate glyph display to actual device
557 uint8_t displayed_width = DISPLAY_DEVICE::write_char(x, y, glyph_ref, context_);
558 invalidate(x, y, XCOORD(x + displayed_width), YCOORD(y + context_.font_->height() - 1));
559 }
560
570 void draw_string(POINT point, const char* content)
571 {
572 // Check one font is currently selected
573 if (!check_font()) return;
574 const uint8_t width = context_.font_->width();
575
576 XCOORD x = point.x;
577 YCOORD y = point.y;
578 XCOORD xcurrent = x;
579 while (*content)
580 {
581 // Check coordinates are suitable for character display
582 if (!is_valid_char_xy(xcurrent, y, width)) break;
583 // Check glyph exists for current character
584 uint16_t glyph_ref = get_glyph(*content);
585 if (glyph_ref == 0) break;
586 // Delegate glyph display to actual device
587 const uint8_t displayed_width = DISPLAY_DEVICE::write_char(xcurrent, y, glyph_ref, context_);
588 xcurrent += displayed_width;
589 ++content;
590 }
591 // Invalidate if needed
592 if (xcurrent > x)
593 {
594 // Clear error only if all content was displayed without issue
595 bool clear_error = (*content == 0);
596 invalidate(x, y, XCOORD(xcurrent - 1), YCOORD(y + context_.font_->height() - 1), clear_error);
597 }
598 }
599
609 void draw_string(POINT point, const flash::FlashStorage* content)
610 {
611 //TODO factor out common code with 1st draw_string() [only 2 lines different!]
612 // Check one font is currently selected
613 if (!check_font()) return;
614 const uint8_t width = context_.font_->width();
615
616 XCOORD x = point.x;
617 YCOORD y = point.y;
618 XCOORD xcurrent = x;
619 uint16_t address = (uint16_t) content;
620 char value;
621 while ((value = pgm_read_byte(address)) != 0)
622 {
623 // Check coordinates are suitable for character display
624 if (!is_valid_char_xy(xcurrent, y, width)) break;
625 // Check glyph exists for current character
626 uint16_t glyph_ref = get_glyph(value);
627 if (glyph_ref == 0) break;
628 // Delegate glyph display to actual device
629 const uint8_t displayed_width = DISPLAY_DEVICE::write_char(xcurrent, y, glyph_ref, context_);
630 xcurrent += displayed_width;
631 ++address;
632 }
633 // Invalidate if needed
634 if (xcurrent > x)
635 {
636 // Clear error only if all content was displayed without issue
637 bool clear_error = (value == 0);
638 invalidate(x, y, XCOORD(xcurrent -1), YCOORD(y + context_.font_->height() - 1), clear_error);
639 }
640 }
641
647 void draw_point(POINT point)
648 {
649 XCOORD x = point.x;
650 YCOORD y = point.y;
651 if (!is_valid_xy(x, y)) return;
652 if (DISPLAY_DEVICE::set_pixel(x, y, context_))
653 invalidate(x, y, x, y);
654 else
655 // Even when set_pixel() returns false, this is not an error!
656 last_error_ = Error::NO_ERROR;
657 }
658
665 void draw_line(POINT point1, POINT point2)
666 {
667 XCOORD x1 = point1.x;
668 YCOORD y1 = point1.y;
669 XCOORD x2 = point2.x;
670 YCOORD y2 = point2.y;
671 if (!is_valid_xy(x1, y1)) return;
672 if (!is_valid_xy(x2, y2)) return;
673
674 // Check if specifc case (vertical or horizontal line)
675 if (x1 == x2)
676 {
677 // if 2 points are the same: nothing to do
678 if (y1 == y2)
679 {
680 last_error_ = Error::INVALID_GEOMETRY;
681 return;
682 }
683 // Ensure y1 < y2
684 swap_to_sort(y1, y2);
685 draw_vline(x1, y1, y2);
686 }
687 else if (y1 == y2)
688 {
689 // Ensure x1 < x2
690 swap_to_sort(x1, x2);
691 draw_hline(x1, y1, x2);
692 }
693 else
694 {
695 // Possibly swap x1-x2 and y1-y2 to ensure x1 < x2
696 if (swap_to_sort(x1, x2)) utils::swap(y1, y2);
697 // Usual case, apply Bresenham's line algorithm
698 draw_line_bresenham(x1, y1, x2, y2);
699 // Ensure y1 < y2 for invalid region instantiation
700 swap_to_sort(y1, y2);
701 }
702 invalidate(x1, y1, x2, y2);
703 }
704
711 void draw_rectangle(POINT point1, POINT point2)
712 {
713 draw_rounded_rectangle(point1, point2, 0);
714 }
715
724 void draw_rounded_rectangle(POINT point1, POINT point2, SCALAR radius)
725 {
726 XCOORD x1 = point1.x;
727 YCOORD y1 = point1.y;
728 XCOORD x2 = point2.x;
729 YCOORD y2 = point2.y;
730 if (!is_valid_xy(x1, y1)) return;
731 if (!is_valid_xy(x2, y2)) return;
732 if ((x1 == x2) || (y1 == y2))
733 {
734 last_error_ = Error::INVALID_GEOMETRY;
735 return;
736 }
737 // Possibly swap x1-x2 and y1-y2
738 swap_to_sort(x1, x2);
739 swap_to_sort(y1, y2);
740 if ((radius * 2 > x2 - x1) || (radius * 2 > y2 - y1))
741 {
742 last_error_ = Error::INVALID_GEOMETRY;
743 return;
744 }
745
746 // Draw edges
747 if (context_.draw_)
748 {
749 // For rounded rectangles we need to draw one less pixel on the right (in XOR mode)
750 SCALAR delta = (radius ? radius + 1 : 0);
751 // Simply draw 2 horizontal and 2 vertical lines
752 draw_hline(x1 + radius, y1, x2 - delta);
753 draw_hline(x1 + radius, y2, x2 - delta);
754 // Note that we avoid drawing the same pixels (corners) twice
755 // (due to a drawing mode that might potentially be XOR)
756 draw_vline(x1, y1 + radius + 1, y2 - radius - 1);
757 draw_vline(x2, y1 + radius + 1, y2 - radius - 1);
758 }
759
760 if ((radius != 0) && (context_.draw_ || context_.fill_))
761 {
762 // Draw 4 quarter-circles & fill with horizontal lines
763 draw_circle_bresenham(x1 + radius, y1 + radius, x2 - radius, y2 - radius, radius);
764 }
765
766 // Fill rectangle inside
767 if (context_.fill_)
768 {
769 context_.is_fill_ = true;
770 // Simply draw enough horizontal lines
771 // For rounded rectangles we need to draw one more line on the top
772 SCALAR delta = (radius ? radius : 1);
773 for (YCOORD y = y1 + delta; y < y2 - radius; ++y)
774 draw_hline(x1 + 1, y, x2 - 1);
775 context_.is_fill_ = false;
776 }
777
778 invalidate(x1, y1, x2, y2);
779 }
780
787 void draw_circle(POINT center, SCALAR radius)
788 {
789 XCOORD xc = center.x;
790 YCOORD yc = center.y;
791 if (!is_valid_xy(xc, yc)) return;
792 if (radius == 0)
793 {
794 last_error_ = Error::INVALID_GEOMETRY;
795 return;
796 }
797 if ( (xc < radius) || (xc + radius >= WIDTH)
798 || (yc < radius) || (yc + radius >= HEIGHT))
799 {
800 last_error_ = Error::OUT_OF_DISPLAY;
801 return;
802 }
803 // Apply Bresenham's circle algorithm
804 draw_circle_bresenham(xc, yc, xc, yc, radius);
805 invalidate(XCOORD(xc - radius), YCOORD(yc - radius), XCOORD(xc + radius), YCOORD(yc + radius));
806 }
807
814 {
815 draw_lines(points, false);
816 }
817
826 {
827 draw_lines(points, true);
828 }
829
853 template<typename F>
854 void draw_bitmap(POINT origin, POINT size, F input_streamer)
855 {
856 // Check origin and size are compatible with display device resolution
857 const XCOORD xorg = origin.x;
858 const YCOORD yorg = origin.y;
859 if (!is_valid_xy(xorg, yorg)) return;
860 const XCOORD w = size.x;
861 const YCOORD h = size.y;
862 if (!is_valid_xy(w, h)) return;
863 if (!is_valid_xy(xorg + w, yorg + h)) return;
864
865 if (context_.draw_ || context_.fill_)
866 {
867 const XCOORD cols = (w / 8) + (w % 8 ? 1 : 0);
868 XCOORD xcurrent = 0;
869 YCOORD ycurrent;
870 for (ycurrent = yorg; ycurrent < yorg + h; ++ycurrent)
871 {
872 xcurrent = xorg;
873 for (XCOORD col = 0; col < cols; ++col)
874 {
875 uint8_t value = input_streamer();
876 // Draw each pixel with draw or fill mode
877 for (uint8_t i = 0; i < 8; ++i)
878 {
879 if (value & 0x80)
880 {
881 DISPLAY_DEVICE::set_pixel(xcurrent, ycurrent, context_);
882 }
883 else
884 {
885 context_.is_fill_ = true;
886 DISPLAY_DEVICE::set_pixel(xcurrent, ycurrent, context_);
887 context_.is_fill_ = false;
888 }
889 ++xcurrent;
890 value <<= 1;
891 }
892 }
893 }
894 // Invalidate
895 invalidate(xorg, yorg, XCOORD(xcurrent - 1), YCOORD(ycurrent - 1));
896 }
897 }
898
906 void update()
907 {
908 if (DISPLAY_TRAITS::HAS_RASTER && !invalid_area_.empty)
909 {
910 DISPLAY_DEVICE::update(invalid_area_.x1, invalid_area_.y1, invalid_area_.x2, invalid_area_.y2);
911 invalid_area_.empty = true;
912 }
913 }
914
922 {
923 invalidate();
924 update();
925 }
926
927 protected:
929 struct InvalidArea
930 {
931 InvalidArea() = default;
932 InvalidArea(XCOORD x1, YCOORD y1, XCOORD x2, YCOORD y2)
933 : x1{x1}, y1{y1}, x2{x2}, y2{y2}, empty{false} {}
934
935 InvalidArea& operator+=(const InvalidArea& a)
936 {
937 if (!a.empty)
938 {
939 if (empty)
940 {
941 x1 = a.x1;
942 y1 = a.y1;
943 x2 = a.x2;
944 y2 = a.y2;
945 empty = false;
946 }
947 else
948 {
949 if (a.x1 < x1) x1 = a.x1;
950 if (a.y1 < y1) y1 = a.y1;
951 if (a.x2 > x2) x2 = a.x2;
952 if (a.y2 > y2) y2 = a.y2;
953 }
954 }
955 return *this;
956 }
957
958 static InvalidArea EMPTY;
959
960 XCOORD x1 = 0;
961 YCOORD y1 = 0;
962 XCOORD x2 = 0;
963 YCOORD y2 = 0;
964 bool empty = true;
965 };
966
967 using INVALID_AREA = InvalidArea;
968
969 void invalidate(XCOORD x1, XCOORD y1,XCOORD x2, YCOORD y2, bool clear_error = true)
970 {
972 invalid_area_ += INVALID_AREA{x1, y1, x2, y2};
973 if (clear_error)
974 last_error_ = Error::NO_ERROR;
975 }
976
977 void invalidate()
978 {
980 invalid_area_ = INVALID_AREA{0, 0, WIDTH - 1, HEIGHT - 1};
981 last_error_ = Error::NO_ERROR;
982 }
983
984 bool check_font()
985 {
986 if (context_.font_ != nullptr) return true;
987 last_error_ = Error::NO_FONT_SET;
988 return false;
989 }
990
991 bool is_valid_xy(XCOORD x, YCOORD y)
992 {
993 if ((x < WIDTH) && (y < HEIGHT)) return true;
994 last_error_ = Error::OUT_OF_DISPLAY;
995 return false;
996 }
997
998 bool is_valid_char_xy(XCOORD x, YCOORD y, uint8_t width)
999 {
1000 if ((x + width > WIDTH) || (y >= HEIGHT))
1001 {
1002 last_error_ = Error::OUT_OF_DISPLAY;
1003 return false;
1004 }
1005 if (DISPLAY_DEVICE::is_valid_char_xy(x, y)) return true;
1006 last_error_ = Error::COORDS_INVALID;
1007 return false;
1008 }
1009
1010 uint16_t get_glyph(char value)
1011 {
1012 // Load pixmap for current character
1013 uint16_t glyph_ref = context_.font_->get_char_glyph_ref(value);
1014 if (glyph_ref != 0) return glyph_ref;
1015 last_error_ = Error::NO_GLYPH_FOUND;
1016 return 0;
1017 }
1018
1019 void draw_lines(std::initializer_list<POINT> points, bool polygon)
1020 {
1021 if (points.size() < 2)
1022 {
1023 last_error_ = Error::INVALID_GEOMETRY;
1024 return;
1025 }
1026 const POINT* next = points.begin();
1027 POINT current = *next;
1028 POINT first = current;
1029 while (++next != points.end())
1030 {
1031 draw_line(current, *next);
1032 current = *next;
1033 // Revert double end point drawing (in case of XOR mode)!
1034 if (!polygon || (next + 1) != points.end())
1035 draw_point(current);
1036 }
1037 if (polygon)
1038 {
1039 draw_line(current, first);
1040 // Revert double end point drawing (in case of XOR mode)!
1041 draw_point(first);
1042 }
1043 }
1044
1045 void draw_vline(XCOORD x1, YCOORD y1, YCOORD y2)
1046 {
1047 swap_to_sort(y1, y2);
1048 for (YCOORD y = y1; y <= y2; ++y)
1049 DISPLAY_DEVICE::set_pixel(x1, y, context_);
1050 }
1051
1052 void draw_hline(XCOORD x1, YCOORD y1, XCOORD x2)
1053 {
1054 swap_to_sort(x1, x2);
1055 for (XCOORD x = x1; x <= x2; ++x)
1056 DISPLAY_DEVICE::set_pixel(x, y1, context_);
1057 }
1058
1059 // Draw a segment according to Bresenham algorithm
1060 // https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
1061 // https://fr.wikipedia.org/wiki/Algorithme_de_trac%C3%A9_de_segment_de_Bresenham
1062 void draw_line_bresenham(XCOORD x1, YCOORD y1, XCOORD x2, YCOORD y2)
1063 {
1064 // We are sure that x1 < x2 when calling this method
1067
1068 if (dy > 0)
1069 {
1070 // 1st quadrant
1071 draw_line_bresenham_1st_quadrant(x1, y1, x2, y2, dx, dy);
1072 }
1073 else
1074 {
1075 // 4th quadrant
1076 draw_line_bresenham_4th_quadrant(x1, y1, x2, y2, dx, dy);
1077 }
1078 }
1079
1080 void draw_line_bresenham_1st_quadrant(XCOORD x1, YCOORD y1, XCOORD x2, YCOORD y2,
1082 {
1083 if (dx >= dy)
1084 {
1085 // 1st octant
1086 SIGNED_SCALAR e = dx;
1087 dx *= 2;
1088 dy *= 2;
1089 while (true)
1090 {
1091 DISPLAY_DEVICE::set_pixel(x1, y1, context_);
1092 if (x1 == x2) break;
1093 ++x1;
1094 e -= dy;
1095 if (e < 0)
1096 {
1097 ++y1;
1098 e += dx;
1099 }
1100 }
1101 }
1102 else
1103 {
1104 // 2nd octant
1105 SIGNED_SCALAR e = dy;
1106 dx *= 2;
1107 dy *= 2;
1108 while (true)
1109 {
1110 DISPLAY_DEVICE::set_pixel(x1, y1, context_);
1111 if (y1 == y2) break;
1112 ++y1;
1113 e -= dx;
1114 if (e < 0)
1115 {
1116 ++x1;
1117 e += dy;
1118 }
1119 }
1120 }
1121 }
1122
1123 void draw_line_bresenham_4th_quadrant(XCOORD x1, YCOORD y1, XCOORD x2, YCOORD y2,
1125 {
1126 if (dx >= -dy)
1127 {
1128 // 8th octant
1129 SIGNED_SCALAR e = dx;
1130 dx *= 2;
1131 dy *= 2;
1132 while (true)
1133 {
1134 DISPLAY_DEVICE::set_pixel(x1, y1, context_);
1135 if (x1 == x2) break;
1136 ++x1;
1137 e += dy;
1138 if (e < 0)
1139 {
1140 --y1;
1141 e += dx;
1142 }
1143 }
1144 }
1145 else
1146 {
1147 // 7th octant
1148 SIGNED_SCALAR e = dy;
1149 dx *= 2;
1150 dy *= 2;
1151 while (true)
1152 {
1153 DISPLAY_DEVICE::set_pixel(x1, y1, context_);
1154 if (y1 == y2) break;
1155 --y1;
1156 e += dx;
1157 if (e > 0)
1158 {
1159 ++x1;
1160 e += dy;
1161 }
1162 }
1163 }
1164 }
1165
1166 void draw_pixels(XCOORD x, YCOORD y1, YCOORD y2)
1167 {
1168 DISPLAY_DEVICE::set_pixel(x, y1, context_);
1169 if (y1 != y2)
1170 DISPLAY_DEVICE::set_pixel(x, y2, context_);
1171 }
1172
1173 // https://fr.wikipedia.org/wiki/Algorithme_de_trac%C3%A9_d%27arc_de_cercle_de_Bresenham
1174 void draw_circle_bresenham(XCOORD xc1, YCOORD yc1, XCOORD xc2, YCOORD yc2, SCALAR radius)
1175 {
1176 XCOORD x = 0;
1177 YCOORD y = radius;
1178 SIGNED_SCALAR m = 5 - 4 * radius;
1179 // Ensure filler lines in octants 3 & 4 do net get drawn twice (partly)
1180 XCOORD delta_x = 0;
1181 while (x <= y)
1182 {
1183 if (context_.draw_)
1184 {
1185 // All these conditions are necessary to avoid drawing the same point twice
1186 // which would fail in XOR Mode
1187 draw_pixels(x + xc2, y + yc2, -y + yc1); // octants 2 & 7
1188 if (x != 0)
1189 draw_pixels(-x + xc1, y + yc2, -y + yc1); // octants 3 & 6
1190 if (x != y)
1191 {
1192 draw_pixels(y + xc2, x + yc2, -x + yc1); // octants 1 & 8
1193 if (y != 0)
1194 draw_pixels(-y + xc1, x + yc2, -x + yc1); // octants 4 & 5
1195 }
1196 }
1197 // Draw filler lines for octants 1&4, 5&8 if needed
1198 // If y == x, fill lines are already drawn afterwards
1199 if (context_.fill_ && x != y)
1200 {
1201 context_.is_fill_ = true;
1202 draw_hline(y + xc2 - 1, x + yc2, -y + xc1 + 1); // octants 1 & 8
1203 // If x==0, fill line has just been drawn above
1204 if (x != 0)
1205 draw_hline(y + xc2 - 1, -x + yc1, -y + xc1 + 1); // octants 4 & 5
1206 context_.is_fill_ = false;
1207 }
1208 if (m > 0)
1209 {
1210 // Draw filler lines for octants 2&3, 6&7 if needed
1211 if (context_.fill_ && y != radius)
1212 {
1213 context_.is_fill_ = true;
1214 draw_hline(xc2 + x - delta_x, yc1 - y, xc1 - x + delta_x); // octants 2 & 3
1215 draw_hline(xc2 + x - delta_x, yc2 + y, xc1 - x + delta_x); // octants 6 & 7
1216 context_.is_fill_ = false;
1217 }
1218 delta_x = 0;
1219 --y;
1220 m -= 8 * y;
1221 }
1222 ++x;
1223 ++delta_x;
1224 m += 8 * x + 4;
1225 }
1226 }
1227
1228 template<typename COORD>
1229 static bool swap_to_sort(COORD& a1, COORD& a2)
1230 {
1231 if (a1 > a2)
1232 {
1233 utils::swap(a1, a2);
1234 return true;
1235 }
1236 else
1237 return false;
1238 }
1240
1241 private:
1242 // Draw Context that will be passed to actual device low-level drawing primitives
1243 DRAW_CONTEXT context_{};
1244
1245 // The result status of the last drawing primitive called
1246 Error last_error_ = Error::NO_ERROR;
1247
1248 // Minimal rectangle to update
1249 INVALID_AREA invalid_area_ = INVALID_AREA::EMPTY;
1250 };
1251
1253 template<typename DISPLAY_DEVICE>
1254 typename Display<DISPLAY_DEVICE>::InvalidArea Display<DISPLAY_DEVICE>::InvalidArea::EMPTY = InvalidArea{};
1256}
1257
1258#endif /* DISPLAY_HH */
Class handling drawing primitives on any display device.
Definition: display.h:435
typename DISPLAY_TRAITS::SCALAR SCALAR
Integral type used in various calculations based on coordinates.
Definition: display.h:461
void draw_bitmap(POINT origin, POINT size, F input_streamer)
Draw a bitmap onto the display device.
Definition: display.h:854
Error last_error() const
Error code of latest called drawing primitive.
Definition: display.h:521
void draw_rectangle(POINT point1, POINT point2)
Draw a rectangle defined by 2 corner points, at given coordinates.
Definition: display.h:711
void erase()
Erase complete display.
Definition: display.h:529
Point< XCOORD, YCOORD > POINT
Coordinates of a point in the display.
Definition: display.h:455
Display()=default
Comstruct a display instance.
void draw_polyline(std::initializer_list< POINT > points)
Draw lines between consecutive points in the provided list.
Definition: display.h:813
typename DISPLAY_TRAITS::XCOORD XCOORD
Integral type of X coordinate.
Definition: display.h:450
void draw_string(POINT point, const char *content)
Draw a string of characters at the given display location.
Definition: display.h:570
void draw_char(POINT point, char value)
Draw one character at the given display location.
Definition: display.h:544
void draw_rounded_rectangle(POINT point1, POINT point2, SCALAR radius)
Draw a rounded rectangle defined by 2 corner points, at given coordinates, and the radius of circle a...
Definition: display.h:724
void draw_line(POINT point1, POINT point2)
Draw a line between 2 points, at given coordinates.
Definition: display.h:665
static constexpr XCOORD WIDTH
Display width.
Definition: display.h:464
void draw_point(POINT point)
Draw a pixel at given coordinate.
Definition: display.h:647
void set_draw_mode(const DRAW_MODE &mode)
Set draw mode (color, pixel op) to use for next calls to drawing primitives.
Definition: display.h:485
typename DISPLAY_TRAITS::COLOR COLOR
The type of one pixel color.
Definition: display.h:444
void draw_polygon(std::initializer_list< POINT > points)
Draw a polygon (closed surface) with lines between consecutive points in the provided list.
Definition: display.h:825
void force_update()
For display devices having a raster buffer, this method copies the whole raster buffer to the device.
Definition: display.h:921
void set_font(const FONT &font)
Set the new font to use for next calls to text drawing perimitives.
Definition: display.h:507
void update()
For display devices having a raster buffer, this method copies invalid (modified) parts of the raster...
Definition: display.h:906
typename DISPLAY_TRAITS::YCOORD YCOORD
Integral type of Y coordinate.
Definition: display.h:452
typename DISPLAY_TRAITS::SIGNED_SCALAR SIGNED_SCALAR
Integral signed type used in calculations of some drawing primitives.
Definition: display.h:474
static constexpr YCOORD HEIGHT
Display height.
Definition: display.h:466
void draw_circle(POINT center, SCALAR radius)
Draw a circle defined by its center, at given coordinates, and its radius.
Definition: display.h:787
void draw_string(POINT point, const flash::FlashStorage *content)
Draw a string of characters at the given display location.
Definition: display.h:609
void set_fill_mode(const DRAW_MODE &mode)
Set fill mode (color, pixel op) to use for next calls to drawing primitives (for closed surfaces only...
Definition: display.h:496
Drawing Context passed to display devices low-level primitives set_pixel() and write_char().
Definition: display.h:248
const Font< VERTICAL_FONT > & font() const
Return the current Font to use in the called primitive draw_char()
Definition: display.h:271
DrawMode< COLOR > draw_mode() const
Return the current DrawMode to use in the called primitive.
Definition: display.h:261
Drawing Mode to use for Display drawing primitives.
Definition: display.h:124
COLOR pixel_op(COLOR dest) const
Combine the predefined color (defined at construction time) with one destination pixel,...
Definition: display.h:208
DrawMode(Mode mode=Mode::NO_CHANGE, COLOR color=COLOR{})
Construct a new DrawMode object, to be sued with Display.
Definition: display.h:135
Mode mode() const
Return the current Mode for this DrawMode.
Definition: display.h:154
uint8_t bw_pixels_op(uint8_t source, uint8_t dest) const
Combine 8 source B&W pixels and 8 destination B&W pixels, all gathered in a byte, according to the Mo...
Definition: display.h:178
COLOR color() const
Return the current color for this DrawMode.
Definition: display.h:164
Generic font support class.
Definition: font.h:39
uint16_t get_char_glyph_ref(char value) const
Get a glyph reference for the requested character value.
Definition: font.h:139
uint8_t width() const
Width of font glyphs in pixels.
Definition: font.h:64
uint8_t height() const
Height of font glyphs in pixels.
Definition: font.h:70
C++ standard support for "initializer_list".
constexpr const T * begin() const
The first element of this initializer_list.
constexpr size_t size() const
The size of this initializer_list.
constexpr const T * end() const
One past the last element of this initializer_list.
#define F(ptr)
Force string constant to be stored as flash storage.
Definition: flash.h:150
Generic API to handle Character Fonts for any display device.
Defines generic API for all display devices.
Definition: display.h:71
Error
Types of errors that can occur on Display instances.
Definition: display.h:292
@ INVALID_GEOMETRY
A drawing primitive would lead to incorrect geometry due to invalid arguments.
@ NO_FONT_SET
A text drawing primitive has been called but no font has been set yet.
@ OUT_OF_DISPLAY
A drawing primitive would draw its shape outside the display estate, which is forbidden.
@ NO_GLYPH_FOUND
A text drawing primitive has been called with a character value which has no glyph in current font.
@ COORDS_INVALID
A drawing primitive has been called with invalid (x,y) coordinates; this is different to COORDS_OUT_O...
@ NO_ERROR
No error occurred.
Mode
Mode used when drawing pixels.
Definition: display.h:102
@ NO_CHANGE
In this mode, the destination pixel never changes, whatever the source color.
@ OR
Destination pixel is or'ed with source color (set mode).
@ AND
Destination pixel is and'ed with source color (clear mode).
@ XOR
Destination pixel is xor'ed with source color (inversion mode).
@ COPY
Source color simply replaces destination pixel.
Defines all API for all external devices supported by FastArduino.
constexpr T min(T a, T b)
Compute the min of 2 integral values.
Definition: utilities.h:127
constexpr T max(T a, T b)
Compute the max of 2 integral values.
Definition: utilities.h:143
void swap(T &a, T &b)
Swap the values of 2 variables passed by reference.
Definition: utilities.h:475
Default base class for all DisplayDeviceTrait.
Definition: display.h:394
Traits for display devices.
Definition: display.h:342
uint8_t YCOORD
The shortest integral type that can hold Y coordinates for DEVICE.
Definition: display.h:354
static constexpr bool IS_DISPLAY
Marker of display devices.
Definition: display.h:344
uint8_t XCOORD
The shortest integral type that can hold X coordinates for DEVICE.
Definition: display.h:352
static constexpr uint8_t WIDTH
The width in pixels of DEVICE.
Definition: display.h:360
void COLOR
The type of a pixel for DEVICE.
Definition: display.h:350
static constexpr bool HAS_RASTER
Tells if DEVICE implements a bitmap raster in SRAM (e.g.
Definition: display.h:377
static constexpr uint8_t HEIGHT
The height in pixels of DEVICE.
Definition: display.h:362
int8_t SIGNED_SCALAR
The signed integral type used by Display algorithms in some drawing primitives, like Display::draw_li...
Definition: display.h:373
static constexpr YCOORD MAX_Y
The maximum Y coordinate for DEVICE.
Definition: display.h:358
uint8_t SCALAR
The longest type of XCOORD and YCOORD, used to hold scalar on some drawing primitives,...
Definition: display.h:367
static constexpr bool VERTICAL_FONT
Tells if DEVICE uses vertical fonts (e.g.
Definition: display.h:375
static constexpr XCOORD MAX_X
The maximum X coordinate for DEVICE.
Definition: display.h:356
Find the smallest integral types, signed and unsigned, tha can hold a given value.
Definition: types_traits.h:168