mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 23:21:54 +00:00 
			
		
		
		
	Add Clipping to displaybuffer (#4271)
* adding Clipping support to the displaybuffer - add rect structure * removed unused define * add missing property for storing the clipped areas * include log header * Move Rect method's code to cpp file - removed obsolete remarks * fixed reported issues * make Rect class methods public * clang fix * Remove commented code * Renaming clipping methods * Multiple changes: - replaced 32766 with VALUE_NO_SET - fixed the way *_clipping(left, top, right, bottom) is stored - add `is_clipping();` - make sure that all clipped region are closed after `do_update_()` - rename de parameters for `Rect::expand();` * remove unneeded space * replace define with static const uint8_t * correcting my copy paste mistake --------- Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
		
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -128,3 +128,5 @@ tests/.esphome/ | |||||||
|  |  | ||||||
| sdkconfig.* | sdkconfig.* | ||||||
| !sdkconfig.defaults | !sdkconfig.defaults | ||||||
|  |  | ||||||
|  | .tests/ | ||||||
| @@ -15,6 +15,84 @@ static const char *const TAG = "display"; | |||||||
| const Color COLOR_OFF(0, 0, 0, 0); | const Color COLOR_OFF(0, 0, 0, 0); | ||||||
| const Color COLOR_ON(255, 255, 255, 255); | const Color COLOR_ON(255, 255, 255, 255); | ||||||
|  |  | ||||||
|  | void Rect::expand(int16_t horizontal, int16_t vertical) { | ||||||
|  |   if ((*this).is_set() && ((*this).w >= (-2 * horizontal)) && ((*this).h >= (-2 * vertical))) { | ||||||
|  |     (*this).x = (*this).x - horizontal; | ||||||
|  |     (*this).y = (*this).y - vertical; | ||||||
|  |     (*this).w = (*this).w + (2 * horizontal); | ||||||
|  |     (*this).h = (*this).h + (2 * vertical); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Rect::extend(Rect rect) { | ||||||
|  |   if (!this->is_set()) { | ||||||
|  |     this->x = rect.x; | ||||||
|  |     this->y = rect.y; | ||||||
|  |     this->w = rect.w; | ||||||
|  |     this->h = rect.h; | ||||||
|  |   } else { | ||||||
|  |     if (this->x > rect.x) { | ||||||
|  |       this->x = rect.x; | ||||||
|  |     } | ||||||
|  |     if (this->y > rect.y) { | ||||||
|  |       this->y = rect.y; | ||||||
|  |     } | ||||||
|  |     if (this->x2() < rect.x2()) { | ||||||
|  |       this->w = rect.x2() - this->x; | ||||||
|  |     } | ||||||
|  |     if (this->y2() < rect.y2()) { | ||||||
|  |       this->h = rect.y2() - this->y; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | void Rect::shrink(Rect rect) { | ||||||
|  |   if (!this->inside(rect)) { | ||||||
|  |     (*this) = Rect(); | ||||||
|  |   } else { | ||||||
|  |     if (this->x < rect.x) { | ||||||
|  |       this->x = rect.x; | ||||||
|  |     } | ||||||
|  |     if (this->y < rect.y) { | ||||||
|  |       this->y = rect.y; | ||||||
|  |     } | ||||||
|  |     if (this->x2() > rect.x2()) { | ||||||
|  |       this->w = rect.x2() - this->x; | ||||||
|  |     } | ||||||
|  |     if (this->y2() > rect.y2()) { | ||||||
|  |       this->h = rect.y2() - this->y; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool Rect::inside(int16_t x, int16_t y, bool absolute) {  // NOLINT | ||||||
|  |   if (!this->is_set()) { | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |   if (absolute) { | ||||||
|  |     return ((x >= 0) && (x <= this->w) && (y >= 0) && (y <= this->h)); | ||||||
|  |   } else { | ||||||
|  |     return ((x >= this->x) && (x <= this->x2()) && (y >= this->y) && (y <= this->y2())); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool Rect::inside(Rect rect, bool absolute) { | ||||||
|  |   if (!this->is_set() || !rect.is_set()) { | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |   if (absolute) { | ||||||
|  |     return ((rect.x <= this->w) && (rect.w >= 0) && (rect.y <= this->h) && (rect.h >= 0)); | ||||||
|  |   } else { | ||||||
|  |     return ((rect.x <= this->x2()) && (rect.x2() >= this->x) && (rect.y <= this->y2()) && (rect.y2() >= this->y)); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Rect::info(const std::string &prefix) { | ||||||
|  |   if (this->is_set()) { | ||||||
|  |     ESP_LOGI(TAG, "%s [%3d,%3d,%3d,%3d]", prefix.c_str(), this->x, this->y, this->w, this->h); | ||||||
|  |   } else | ||||||
|  |     ESP_LOGI(TAG, "%s ** IS NOT SET **", prefix.c_str()); | ||||||
|  | } | ||||||
|  |  | ||||||
| void DisplayBuffer::init_internal_(uint32_t buffer_length) { | void DisplayBuffer::init_internal_(uint32_t buffer_length) { | ||||||
|   ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE); |   ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE); | ||||||
|   this->buffer_ = allocator.allocate(buffer_length); |   this->buffer_ = allocator.allocate(buffer_length); | ||||||
| @@ -24,6 +102,7 @@ void DisplayBuffer::init_internal_(uint32_t buffer_length) { | |||||||
|   } |   } | ||||||
|   this->clear(); |   this->clear(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void DisplayBuffer::fill(Color color) { this->filled_rectangle(0, 0, this->get_width(), this->get_height(), color); } | void DisplayBuffer::fill(Color color) { this->filled_rectangle(0, 0, this->get_width(), this->get_height(), color); } | ||||||
| void DisplayBuffer::clear() { this->fill(COLOR_OFF); } | void DisplayBuffer::clear() { this->fill(COLOR_OFF); } | ||||||
| int DisplayBuffer::get_width() { | int DisplayBuffer::get_width() { | ||||||
| @@ -50,6 +129,9 @@ int DisplayBuffer::get_height() { | |||||||
| } | } | ||||||
| void DisplayBuffer::set_rotation(DisplayRotation rotation) { this->rotation_ = rotation; } | void DisplayBuffer::set_rotation(DisplayRotation rotation) { this->rotation_ = rotation; } | ||||||
| void HOT DisplayBuffer::draw_pixel_at(int x, int y, Color color) { | void HOT DisplayBuffer::draw_pixel_at(int x, int y, Color color) { | ||||||
|  |   if (!this->get_clipping().inside(x, y)) | ||||||
|  |     return;  // NOLINT | ||||||
|  |  | ||||||
|   switch (this->rotation_) { |   switch (this->rotation_) { | ||||||
|     case DISPLAY_ROTATION_0_DEGREES: |     case DISPLAY_ROTATION_0_DEGREES: | ||||||
|       break; |       break; | ||||||
| @@ -368,6 +450,10 @@ void DisplayBuffer::do_update_() { | |||||||
|   } else if (this->writer_.has_value()) { |   } else if (this->writer_.has_value()) { | ||||||
|     (*this->writer_)(*this); |     (*this->writer_)(*this); | ||||||
|   } |   } | ||||||
|  |   // remove all not ended clipping regions | ||||||
|  |   while (is_clipping()) { | ||||||
|  |     end_clipping(); | ||||||
|  |   } | ||||||
| } | } | ||||||
| void DisplayOnPageChangeTrigger::process(DisplayPage *from, DisplayPage *to) { | void DisplayOnPageChangeTrigger::process(DisplayPage *from, DisplayPage *to) { | ||||||
|   if ((this->from_ == nullptr || this->from_ == from) && (this->to_ == nullptr || this->to_ == to)) |   if ((this->from_ == nullptr || this->from_ == from) && (this->to_ == nullptr || this->to_ == to)) | ||||||
| @@ -392,6 +478,41 @@ void DisplayBuffer::strftime(int x, int y, Font *font, const char *format, time: | |||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | void DisplayBuffer::start_clipping(Rect rect) { | ||||||
|  |   if (!this->clipping_rectangle_.empty()) { | ||||||
|  |     Rect r = this->clipping_rectangle_.back(); | ||||||
|  |     rect.shrink(r); | ||||||
|  |   } | ||||||
|  |   this->clipping_rectangle_.push_back(rect); | ||||||
|  | } | ||||||
|  | void DisplayBuffer::end_clipping() { | ||||||
|  |   if (this->clipping_rectangle_.empty()) { | ||||||
|  |     ESP_LOGE(TAG, "clear: Clipping is not set."); | ||||||
|  |   } else { | ||||||
|  |     this->clipping_rectangle_.pop_back(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | void DisplayBuffer::extend_clipping(Rect add_rect) { | ||||||
|  |   if (this->clipping_rectangle_.empty()) { | ||||||
|  |     ESP_LOGE(TAG, "add: Clipping is not set."); | ||||||
|  |   } else { | ||||||
|  |     this->clipping_rectangle_.back().extend(add_rect); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | void DisplayBuffer::shrink_clipping(Rect add_rect) { | ||||||
|  |   if (this->clipping_rectangle_.empty()) { | ||||||
|  |     ESP_LOGE(TAG, "add: Clipping is not set."); | ||||||
|  |   } else { | ||||||
|  |     this->clipping_rectangle_.back().shrink(add_rect); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | Rect DisplayBuffer::get_clipping() { | ||||||
|  |   if (this->clipping_rectangle_.empty()) { | ||||||
|  |     return Rect(); | ||||||
|  |   } else { | ||||||
|  |     return this->clipping_rectangle_.back(); | ||||||
|  |   } | ||||||
|  | } | ||||||
| bool Glyph::get_pixel(int x, int y) const { | bool Glyph::get_pixel(int x, int y) const { | ||||||
|   const int x_data = x - this->glyph_data_->offset_x; |   const int x_data = x - this->glyph_data_->offset_x; | ||||||
|   const int y_data = y - this->glyph_data_->offset_y; |   const int y_data = y - this->glyph_data_->offset_y; | ||||||
|   | |||||||
| @@ -4,7 +4,6 @@ | |||||||
| #include "esphome/core/defines.h" | #include "esphome/core/defines.h" | ||||||
| #include "esphome/core/automation.h" | #include "esphome/core/automation.h" | ||||||
| #include "display_color_utils.h" | #include "display_color_utils.h" | ||||||
|  |  | ||||||
| #include <cstdarg> | #include <cstdarg> | ||||||
| #include <vector> | #include <vector> | ||||||
|  |  | ||||||
| @@ -100,6 +99,32 @@ enum DisplayRotation { | |||||||
|   DISPLAY_ROTATION_270_DEGREES = 270, |   DISPLAY_ROTATION_270_DEGREES = 270, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | static const int16_t VALUE_NO_SET = 32766; | ||||||
|  |  | ||||||
|  | class Rect { | ||||||
|  |  public: | ||||||
|  |   int16_t x;  ///< X coordinate of corner | ||||||
|  |   int16_t y;  ///< Y coordinate of corner | ||||||
|  |   int16_t w;  ///< Width of region | ||||||
|  |   int16_t h;  ///< Height of region | ||||||
|  |  | ||||||
|  |   Rect() : x(VALUE_NO_SET), y(VALUE_NO_SET), w(VALUE_NO_SET), h(VALUE_NO_SET) {}  // NOLINT | ||||||
|  |   inline Rect(int16_t x, int16_t y, int16_t w, int16_t h) ALWAYS_INLINE : x(x), y(y), w(w), h(h) {} | ||||||
|  |   inline int16_t x2() { return this->x + this->w; };  ///< X coordinate of corner | ||||||
|  |   inline int16_t y2() { return this->y + this->h; };  ///< Y coordinate of corner | ||||||
|  |  | ||||||
|  |   inline bool is_set() ALWAYS_INLINE { return (this->h != VALUE_NO_SET) && (this->w != VALUE_NO_SET); } | ||||||
|  |  | ||||||
|  |   void expand(int16_t horizontal, int16_t vertical); | ||||||
|  |  | ||||||
|  |   void extend(Rect rect); | ||||||
|  |   void shrink(Rect rect); | ||||||
|  |  | ||||||
|  |   bool inside(Rect rect, bool absolute = false); | ||||||
|  |   bool inside(int16_t x, int16_t y, bool absolute = false); | ||||||
|  |   void info(const std::string &prefix = "rect info:"); | ||||||
|  | }; | ||||||
|  |  | ||||||
| class Font; | class Font; | ||||||
| class Image; | class Image; | ||||||
| class DisplayBuffer; | class DisplayBuffer; | ||||||
| @@ -126,6 +151,7 @@ class DisplayBuffer { | |||||||
|   int get_width(); |   int get_width(); | ||||||
|   /// Get the height of the image in pixels with rotation applied. |   /// Get the height of the image in pixels with rotation applied. | ||||||
|   int get_height(); |   int get_height(); | ||||||
|  |  | ||||||
|   /// Set a single pixel at the specified coordinates to the given color. |   /// Set a single pixel at the specified coordinates to the given color. | ||||||
|   void draw_pixel_at(int x, int y, Color color = COLOR_ON); |   void draw_pixel_at(int x, int y, Color color = COLOR_ON); | ||||||
|  |  | ||||||
| @@ -374,6 +400,61 @@ class DisplayBuffer { | |||||||
|    */ |    */ | ||||||
|   virtual DisplayType get_display_type() = 0; |   virtual DisplayType get_display_type() = 0; | ||||||
|  |  | ||||||
|  |   /// | ||||||
|  |   /// Set the clipping rectangle for further drawing | ||||||
|  |   /// | ||||||
|  |   /// \param[in]  rect:       Pointer to Rect for clipping (or NULL for entire screen) | ||||||
|  |   /// | ||||||
|  |   /// \return true if success, false if error | ||||||
|  |   /// | ||||||
|  |   void start_clipping(Rect rect); | ||||||
|  |   void start_clipping(int16_t left, int16_t top, int16_t right, int16_t bottom) { | ||||||
|  |     start_clipping(Rect(left, top, right - left, bottom - top)); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   /// | ||||||
|  |   /// Add a rectangular region to the invalidation region | ||||||
|  |   /// - This is usually called when an element has been modified | ||||||
|  |   /// | ||||||
|  |   /// \param[in]  rect: Rectangle to add to the invalidation region | ||||||
|  |   /// | ||||||
|  |   /// \return none | ||||||
|  |   /// | ||||||
|  |   void extend_clipping(Rect rect); | ||||||
|  |   void extend_clipping(int16_t left, int16_t top, int16_t right, int16_t bottom) { | ||||||
|  |     this->extend_clipping(Rect(left, top, right - left, bottom - top)); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   /// | ||||||
|  |   /// substract a rectangular region to the invalidation region | ||||||
|  |   /// - This is usually called when an element has been modified | ||||||
|  |   /// | ||||||
|  |   /// \param[in]  rect: Rectangle to add to the invalidation region | ||||||
|  |   /// | ||||||
|  |   /// \return none | ||||||
|  |   /// | ||||||
|  |   void shrink_clipping(Rect rect); | ||||||
|  |   void shrink_clipping(uint16_t left, uint16_t top, uint16_t right, uint16_t bottom) { | ||||||
|  |     this->shrink_clipping(Rect(left, top, right - left, bottom - top)); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   /// | ||||||
|  |   /// Reset the invalidation region | ||||||
|  |   /// | ||||||
|  |   /// \return none | ||||||
|  |   /// | ||||||
|  |   void end_clipping(); | ||||||
|  |  | ||||||
|  |   /// | ||||||
|  |   /// Get the current the clipping rectangle | ||||||
|  |   /// | ||||||
|  |   /// | ||||||
|  |   /// \return rect for active clipping region | ||||||
|  |   /// | ||||||
|  |   Rect get_clipping(); | ||||||
|  |  | ||||||
|  |   bool is_clipping() const { return this->clipping_rectangle_.empty(); } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   void vprintf_(int x, int y, Font *font, Color color, TextAlign align, const char *format, va_list arg); |   void vprintf_(int x, int y, Font *font, Color color, TextAlign align, const char *format, va_list arg); | ||||||
|  |  | ||||||
| @@ -390,6 +471,7 @@ class DisplayBuffer { | |||||||
|   DisplayPage *previous_page_{nullptr}; |   DisplayPage *previous_page_{nullptr}; | ||||||
|   std::vector<DisplayOnPageChangeTrigger *> on_page_change_triggers_; |   std::vector<DisplayOnPageChangeTrigger *> on_page_change_triggers_; | ||||||
|   bool auto_clear_enabled_{true}; |   bool auto_clear_enabled_{true}; | ||||||
|  |   std::vector<Rect> clipping_rectangle_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class DisplayPage { | class DisplayPage { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user