mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 06:33:51 +00:00 
			
		
		
		
	Split display_buffer sub-components into own files (#4950)
* Split display_buffer sub-components into own files Move the Image, Animation and Font classes to their own h/cpp pairs, instead of having everything into the display_buffer h/cpp files. * Fixed COLOR_ON duplicate definition
This commit is contained in:
		
				
					committed by
					
						 Jesse Hills
						Jesse Hills
					
				
			
			
				
	
			
			
			
						parent
						
							abca47f36f
						
					
				
				
					commit
					6aa3092be0
				
			
							
								
								
									
										69
									
								
								esphome/components/display/animation.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								esphome/components/display/animation.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| #include "animation.h" | ||||
|  | ||||
| #include "esphome/core/hal.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace display { | ||||
|  | ||||
| Animation::Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type) | ||||
|     : Image(data_start, width, height, type), | ||||
|       animation_data_start_(data_start), | ||||
|       current_frame_(0), | ||||
|       animation_frame_count_(animation_frame_count), | ||||
|       loop_start_frame_(0), | ||||
|       loop_end_frame_(animation_frame_count_), | ||||
|       loop_count_(0), | ||||
|       loop_current_iteration_(1) {} | ||||
| void Animation::set_loop(uint32_t start_frame, uint32_t end_frame, int count) { | ||||
|   loop_start_frame_ = std::min(start_frame, animation_frame_count_); | ||||
|   loop_end_frame_ = std::min(end_frame, animation_frame_count_); | ||||
|   loop_count_ = count; | ||||
|   loop_current_iteration_ = 1; | ||||
| } | ||||
|  | ||||
| uint32_t Animation::get_animation_frame_count() const { return this->animation_frame_count_; } | ||||
| int Animation::get_current_frame() const { return this->current_frame_; } | ||||
| void Animation::next_frame() { | ||||
|   this->current_frame_++; | ||||
|   if (loop_count_ && this->current_frame_ == loop_end_frame_ && | ||||
|       (this->loop_current_iteration_ < loop_count_ || loop_count_ < 0)) { | ||||
|     this->current_frame_ = loop_start_frame_; | ||||
|     this->loop_current_iteration_++; | ||||
|   } | ||||
|   if (this->current_frame_ >= animation_frame_count_) { | ||||
|     this->loop_current_iteration_ = 1; | ||||
|     this->current_frame_ = 0; | ||||
|   } | ||||
|  | ||||
|   this->update_data_start_(); | ||||
| } | ||||
| void Animation::prev_frame() { | ||||
|   this->current_frame_--; | ||||
|   if (this->current_frame_ < 0) { | ||||
|     this->current_frame_ = this->animation_frame_count_ - 1; | ||||
|   } | ||||
|  | ||||
|   this->update_data_start_(); | ||||
| } | ||||
|  | ||||
| void Animation::set_frame(int frame) { | ||||
|   unsigned abs_frame = abs(frame); | ||||
|  | ||||
|   if (abs_frame < this->animation_frame_count_) { | ||||
|     if (frame >= 0) { | ||||
|       this->current_frame_ = frame; | ||||
|     } else { | ||||
|       this->current_frame_ = this->animation_frame_count_ - abs_frame; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   this->update_data_start_(); | ||||
| } | ||||
|  | ||||
| void Animation::update_data_start_() { | ||||
|   const uint32_t image_size = image_type_to_width_stride(this->width_, this->type_) * this->height_; | ||||
|   this->data_start_ = this->animation_data_start_ + image_size * this->current_frame_; | ||||
| } | ||||
|  | ||||
| }  // namespace display | ||||
| }  // namespace esphome | ||||
							
								
								
									
										37
									
								
								esphome/components/display/animation.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								esphome/components/display/animation.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| #pragma once | ||||
| #include "image.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace display { | ||||
|  | ||||
| class Animation : public Image { | ||||
|  public: | ||||
|   Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type); | ||||
|  | ||||
|   uint32_t get_animation_frame_count() const; | ||||
|   int get_current_frame() const; | ||||
|   void next_frame(); | ||||
|   void prev_frame(); | ||||
|  | ||||
|   /** Selects a specific frame within the animation. | ||||
|    * | ||||
|    * @param frame If possitive, advance to the frame. If negative, recede to that frame from the end frame. | ||||
|    */ | ||||
|   void set_frame(int frame); | ||||
|  | ||||
|   void set_loop(uint32_t start_frame, uint32_t end_frame, int count); | ||||
|  | ||||
|  protected: | ||||
|   void update_data_start_(); | ||||
|  | ||||
|   const uint8_t *animation_data_start_; | ||||
|   int current_frame_; | ||||
|   uint32_t animation_frame_count_; | ||||
|   uint32_t loop_start_frame_; | ||||
|   uint32_t loop_end_frame_; | ||||
|   int loop_count_; | ||||
|   int loop_current_iteration_; | ||||
| }; | ||||
|  | ||||
| }  // namespace display | ||||
| }  // namespace esphome | ||||
| @@ -7,6 +7,10 @@ | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| #include "animation.h" | ||||
| #include "image.h" | ||||
| #include "font.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace display { | ||||
|  | ||||
| @@ -15,25 +19,6 @@ static const char *const TAG = "display"; | ||||
| const Color COLOR_OFF(0, 0, 0, 255); | ||||
| const Color COLOR_ON(255, 255, 255, 255); | ||||
|  | ||||
| static int image_type_to_bpp(ImageType type) { | ||||
|   switch (type) { | ||||
|     case IMAGE_TYPE_BINARY: | ||||
|       return 1; | ||||
|     case IMAGE_TYPE_GRAYSCALE: | ||||
|       return 8; | ||||
|     case IMAGE_TYPE_RGB565: | ||||
|       return 16; | ||||
|     case IMAGE_TYPE_RGB24: | ||||
|       return 24; | ||||
|     case IMAGE_TYPE_RGBA: | ||||
|       return 32; | ||||
|     default: | ||||
|       return 0; | ||||
|   } | ||||
| } | ||||
|  | ||||
| static int image_type_to_width_stride(int width, ImageType type) { return (width * image_type_to_bpp(type) + 7u) / 8u; } | ||||
|  | ||||
| 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; | ||||
| @@ -505,286 +490,6 @@ Rect DisplayBuffer::get_clipping() { | ||||
|     return this->clipping_rectangle_.back(); | ||||
|   } | ||||
| } | ||||
| bool Glyph::get_pixel(int x, int y) const { | ||||
|   const int x_data = x - this->glyph_data_->offset_x; | ||||
|   const int y_data = y - this->glyph_data_->offset_y; | ||||
|   if (x_data < 0 || x_data >= this->glyph_data_->width || y_data < 0 || y_data >= this->glyph_data_->height) | ||||
|     return false; | ||||
|   const uint32_t width_8 = ((this->glyph_data_->width + 7u) / 8u) * 8u; | ||||
|   const uint32_t pos = x_data + y_data * width_8; | ||||
|   return progmem_read_byte(this->glyph_data_->data + (pos / 8u)) & (0x80 >> (pos % 8u)); | ||||
| } | ||||
| const char *Glyph::get_char() const { return this->glyph_data_->a_char; } | ||||
| bool Glyph::compare_to(const char *str) const { | ||||
|   // 1 -> this->char_ | ||||
|   // 2 -> str | ||||
|   for (uint32_t i = 0;; i++) { | ||||
|     if (this->glyph_data_->a_char[i] == '\0') | ||||
|       return true; | ||||
|     if (str[i] == '\0') | ||||
|       return false; | ||||
|     if (this->glyph_data_->a_char[i] > str[i]) | ||||
|       return false; | ||||
|     if (this->glyph_data_->a_char[i] < str[i]) | ||||
|       return true; | ||||
|   } | ||||
|   // this should not happen | ||||
|   return false; | ||||
| } | ||||
| int Glyph::match_length(const char *str) const { | ||||
|   for (uint32_t i = 0;; i++) { | ||||
|     if (this->glyph_data_->a_char[i] == '\0') | ||||
|       return i; | ||||
|     if (str[i] != this->glyph_data_->a_char[i]) | ||||
|       return 0; | ||||
|   } | ||||
|   // this should not happen | ||||
|   return 0; | ||||
| } | ||||
| void Glyph::scan_area(int *x1, int *y1, int *width, int *height) const { | ||||
|   *x1 = this->glyph_data_->offset_x; | ||||
|   *y1 = this->glyph_data_->offset_y; | ||||
|   *width = this->glyph_data_->width; | ||||
|   *height = this->glyph_data_->height; | ||||
| } | ||||
| int Font::match_next_glyph(const char *str, int *match_length) { | ||||
|   int lo = 0; | ||||
|   int hi = this->glyphs_.size() - 1; | ||||
|   while (lo != hi) { | ||||
|     int mid = (lo + hi + 1) / 2; | ||||
|     if (this->glyphs_[mid].compare_to(str)) { | ||||
|       lo = mid; | ||||
|     } else { | ||||
|       hi = mid - 1; | ||||
|     } | ||||
|   } | ||||
|   *match_length = this->glyphs_[lo].match_length(str); | ||||
|   if (*match_length <= 0) | ||||
|     return -1; | ||||
|   return lo; | ||||
| } | ||||
| void Font::measure(const char *str, int *width, int *x_offset, int *baseline, int *height) { | ||||
|   *baseline = this->baseline_; | ||||
|   *height = this->height_; | ||||
|   int i = 0; | ||||
|   int min_x = 0; | ||||
|   bool has_char = false; | ||||
|   int x = 0; | ||||
|   while (str[i] != '\0') { | ||||
|     int match_length; | ||||
|     int glyph_n = this->match_next_glyph(str + i, &match_length); | ||||
|     if (glyph_n < 0) { | ||||
|       // Unknown char, skip | ||||
|       if (!this->get_glyphs().empty()) | ||||
|         x += this->get_glyphs()[0].glyph_data_->width; | ||||
|       i++; | ||||
|       continue; | ||||
|     } | ||||
|  | ||||
|     const Glyph &glyph = this->glyphs_[glyph_n]; | ||||
|     if (!has_char) { | ||||
|       min_x = glyph.glyph_data_->offset_x; | ||||
|     } else { | ||||
|       min_x = std::min(min_x, x + glyph.glyph_data_->offset_x); | ||||
|     } | ||||
|     x += glyph.glyph_data_->width + glyph.glyph_data_->offset_x; | ||||
|  | ||||
|     i += match_length; | ||||
|     has_char = true; | ||||
|   } | ||||
|   *x_offset = min_x; | ||||
|   *width = x - min_x; | ||||
| } | ||||
| Font::Font(const GlyphData *data, int data_nr, int baseline, int height) : baseline_(baseline), height_(height) { | ||||
|   glyphs_.reserve(data_nr); | ||||
|   for (int i = 0; i < data_nr; ++i) | ||||
|     glyphs_.emplace_back(&data[i]); | ||||
| } | ||||
|  | ||||
| void Image::draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) { | ||||
|   switch (type_) { | ||||
|     case IMAGE_TYPE_BINARY: { | ||||
|       for (int img_x = 0; img_x < width_; img_x++) { | ||||
|         for (int img_y = 0; img_y < height_; img_y++) { | ||||
|           if (this->get_binary_pixel_(img_x, img_y)) { | ||||
|             display->draw_pixel_at(x + img_x, y + img_y, color_on); | ||||
|           } else if (!this->transparent_) { | ||||
|             display->draw_pixel_at(x + img_x, y + img_y, color_off); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|       break; | ||||
|     } | ||||
|     case IMAGE_TYPE_GRAYSCALE: | ||||
|       for (int img_x = 0; img_x < width_; img_x++) { | ||||
|         for (int img_y = 0; img_y < height_; img_y++) { | ||||
|           auto color = this->get_grayscale_pixel_(img_x, img_y); | ||||
|           if (color.w >= 0x80) { | ||||
|             display->draw_pixel_at(x + img_x, y + img_y, color); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|       break; | ||||
|     case IMAGE_TYPE_RGB565: | ||||
|       for (int img_x = 0; img_x < width_; img_x++) { | ||||
|         for (int img_y = 0; img_y < height_; img_y++) { | ||||
|           auto color = this->get_rgb565_pixel_(img_x, img_y); | ||||
|           if (color.w >= 0x80) { | ||||
|             display->draw_pixel_at(x + img_x, y + img_y, color); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|       break; | ||||
|     case IMAGE_TYPE_RGB24: | ||||
|       for (int img_x = 0; img_x < width_; img_x++) { | ||||
|         for (int img_y = 0; img_y < height_; img_y++) { | ||||
|           auto color = this->get_rgb24_pixel_(img_x, img_y); | ||||
|           if (color.w >= 0x80) { | ||||
|             display->draw_pixel_at(x + img_x, y + img_y, color); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|       break; | ||||
|     case IMAGE_TYPE_RGBA: | ||||
|       for (int img_x = 0; img_x < width_; img_x++) { | ||||
|         for (int img_y = 0; img_y < height_; img_y++) { | ||||
|           auto color = this->get_rgba_pixel_(img_x, img_y); | ||||
|           if (color.w >= 0x80) { | ||||
|             display->draw_pixel_at(x + img_x, y + img_y, color); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|       break; | ||||
|   } | ||||
| } | ||||
| Color Image::get_pixel(int x, int y, Color color_on, Color color_off) const { | ||||
|   if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) | ||||
|     return color_off; | ||||
|   switch (this->type_) { | ||||
|     case IMAGE_TYPE_BINARY: | ||||
|       return this->get_binary_pixel_(x, y) ? color_on : color_off; | ||||
|     case IMAGE_TYPE_GRAYSCALE: | ||||
|       return this->get_grayscale_pixel_(x, y); | ||||
|     case IMAGE_TYPE_RGB565: | ||||
|       return this->get_rgb565_pixel_(x, y); | ||||
|     case IMAGE_TYPE_RGB24: | ||||
|       return this->get_rgb24_pixel_(x, y); | ||||
|     case IMAGE_TYPE_RGBA: | ||||
|       return this->get_rgba_pixel_(x, y); | ||||
|     default: | ||||
|       return color_off; | ||||
|   } | ||||
| } | ||||
| bool Image::get_binary_pixel_(int x, int y) const { | ||||
|   const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u; | ||||
|   const uint32_t pos = x + y * width_8; | ||||
|   return progmem_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u)); | ||||
| } | ||||
| Color Image::get_rgba_pixel_(int x, int y) const { | ||||
|   const uint32_t pos = (x + y * this->width_) * 4; | ||||
|   return Color(progmem_read_byte(this->data_start_ + pos + 0), progmem_read_byte(this->data_start_ + pos + 1), | ||||
|                progmem_read_byte(this->data_start_ + pos + 2), progmem_read_byte(this->data_start_ + pos + 3)); | ||||
| } | ||||
| Color Image::get_rgb24_pixel_(int x, int y) const { | ||||
|   const uint32_t pos = (x + y * this->width_) * 3; | ||||
|   Color color = Color(progmem_read_byte(this->data_start_ + pos + 0), progmem_read_byte(this->data_start_ + pos + 1), | ||||
|                       progmem_read_byte(this->data_start_ + pos + 2)); | ||||
|   if (color.b == 1 && color.r == 0 && color.g == 0 && transparent_) { | ||||
|     // (0, 0, 1) has been defined as transparent color for non-alpha images. | ||||
|     // putting blue == 1 as a first condition for performance reasons (least likely value to short-cut the if) | ||||
|     color.w = 0; | ||||
|   } else { | ||||
|     color.w = 0xFF; | ||||
|   } | ||||
|   return color; | ||||
| } | ||||
| Color Image::get_rgb565_pixel_(int x, int y) const { | ||||
|   const uint32_t pos = (x + y * this->width_) * 2; | ||||
|   uint16_t rgb565 = | ||||
|       progmem_read_byte(this->data_start_ + pos + 0) << 8 | progmem_read_byte(this->data_start_ + pos + 1); | ||||
|   auto r = (rgb565 & 0xF800) >> 11; | ||||
|   auto g = (rgb565 & 0x07E0) >> 5; | ||||
|   auto b = rgb565 & 0x001F; | ||||
|   Color color = Color((r << 3) | (r >> 2), (g << 2) | (g >> 4), (b << 3) | (b >> 2)); | ||||
|   if (rgb565 == 0x0020 && transparent_) { | ||||
|     // darkest green has been defined as transparent color for transparent RGB565 images. | ||||
|     color.w = 0; | ||||
|   } else { | ||||
|     color.w = 0xFF; | ||||
|   } | ||||
|   return color; | ||||
| } | ||||
| Color Image::get_grayscale_pixel_(int x, int y) const { | ||||
|   const uint32_t pos = (x + y * this->width_); | ||||
|   const uint8_t gray = progmem_read_byte(this->data_start_ + pos); | ||||
|   uint8_t alpha = (gray == 1 && transparent_) ? 0 : 0xFF; | ||||
|   return Color(gray, gray, gray, alpha); | ||||
| } | ||||
| int Image::get_width() const { return this->width_; } | ||||
| int Image::get_height() const { return this->height_; } | ||||
| ImageType Image::get_type() const { return this->type_; } | ||||
| Image::Image(const uint8_t *data_start, int width, int height, ImageType type) | ||||
|     : width_(width), height_(height), type_(type), data_start_(data_start) {} | ||||
|  | ||||
| Animation::Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type) | ||||
|     : Image(data_start, width, height, type), | ||||
|       animation_data_start_(data_start), | ||||
|       current_frame_(0), | ||||
|       animation_frame_count_(animation_frame_count), | ||||
|       loop_start_frame_(0), | ||||
|       loop_end_frame_(animation_frame_count_), | ||||
|       loop_count_(0), | ||||
|       loop_current_iteration_(1) {} | ||||
| void Animation::set_loop(uint32_t start_frame, uint32_t end_frame, int count) { | ||||
|   loop_start_frame_ = std::min(start_frame, animation_frame_count_); | ||||
|   loop_end_frame_ = std::min(end_frame, animation_frame_count_); | ||||
|   loop_count_ = count; | ||||
|   loop_current_iteration_ = 1; | ||||
| } | ||||
|  | ||||
| uint32_t Animation::get_animation_frame_count() const { return this->animation_frame_count_; } | ||||
| int Animation::get_current_frame() const { return this->current_frame_; } | ||||
| void Animation::next_frame() { | ||||
|   this->current_frame_++; | ||||
|   if (loop_count_ && this->current_frame_ == loop_end_frame_ && | ||||
|       (this->loop_current_iteration_ < loop_count_ || loop_count_ < 0)) { | ||||
|     this->current_frame_ = loop_start_frame_; | ||||
|     this->loop_current_iteration_++; | ||||
|   } | ||||
|   if (this->current_frame_ >= animation_frame_count_) { | ||||
|     this->loop_current_iteration_ = 1; | ||||
|     this->current_frame_ = 0; | ||||
|   } | ||||
|  | ||||
|   this->update_data_start_(); | ||||
| } | ||||
| void Animation::prev_frame() { | ||||
|   this->current_frame_--; | ||||
|   if (this->current_frame_ < 0) { | ||||
|     this->current_frame_ = this->animation_frame_count_ - 1; | ||||
|   } | ||||
|  | ||||
|   this->update_data_start_(); | ||||
| } | ||||
|  | ||||
| void Animation::set_frame(int frame) { | ||||
|   unsigned abs_frame = abs(frame); | ||||
|  | ||||
|   if (abs_frame < this->animation_frame_count_) { | ||||
|     if (frame >= 0) { | ||||
|       this->current_frame_ = frame; | ||||
|     } else { | ||||
|       this->current_frame_ = this->animation_frame_count_ - abs_frame; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   this->update_data_start_(); | ||||
| } | ||||
|  | ||||
| void Animation::update_data_start_() { | ||||
|   const uint32_t image_size = image_type_to_width_stride(this->width_, this->type_) * this->height_; | ||||
|   this->data_start_ = this->animation_data_start_ + image_size * this->current_frame_; | ||||
| } | ||||
|  | ||||
| DisplayPage::DisplayPage(display_writer_t writer) : writer_(std::move(writer)) {} | ||||
| void DisplayPage::show() { this->parent_->show_page(this); } | ||||
|   | ||||
| @@ -16,6 +16,10 @@ | ||||
| #include "esphome/components/qr_code/qr_code.h" | ||||
| #endif | ||||
|  | ||||
| #include "animation.h" | ||||
| #include "font.h" | ||||
| #include "image.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace display { | ||||
|  | ||||
| @@ -70,19 +74,6 @@ enum class TextAlign { | ||||
|   BOTTOM_RIGHT = BOTTOM | RIGHT, | ||||
| }; | ||||
|  | ||||
| /// Turn the pixel OFF. | ||||
| extern const Color COLOR_OFF; | ||||
| /// Turn the pixel ON. | ||||
| extern const Color COLOR_ON; | ||||
|  | ||||
| enum ImageType { | ||||
|   IMAGE_TYPE_BINARY = 0, | ||||
|   IMAGE_TYPE_GRAYSCALE = 1, | ||||
|   IMAGE_TYPE_RGB24 = 2, | ||||
|   IMAGE_TYPE_RGB565 = 3, | ||||
|   IMAGE_TYPE_RGBA = 4, | ||||
| }; | ||||
|  | ||||
| enum DisplayType { | ||||
|   DISPLAY_TYPE_BINARY = 1, | ||||
|   DISPLAY_TYPE_GRAYSCALE = 2, | ||||
| @@ -123,8 +114,6 @@ class Rect { | ||||
|   void info(const std::string &prefix = "rect info:"); | ||||
| }; | ||||
|  | ||||
| class BaseImage; | ||||
| class Font; | ||||
| class DisplayBuffer; | ||||
| class DisplayPage; | ||||
| class DisplayOnPageChangeTrigger; | ||||
| @@ -475,123 +464,6 @@ class DisplayPage { | ||||
|   DisplayPage *next_{nullptr}; | ||||
| }; | ||||
|  | ||||
| struct GlyphData { | ||||
|   const char *a_char; | ||||
|   const uint8_t *data; | ||||
|   int offset_x; | ||||
|   int offset_y; | ||||
|   int width; | ||||
|   int height; | ||||
| }; | ||||
|  | ||||
| class Glyph { | ||||
|  public: | ||||
|   Glyph(const GlyphData *data) : glyph_data_(data) {} | ||||
|  | ||||
|   bool get_pixel(int x, int y) const; | ||||
|  | ||||
|   const char *get_char() const; | ||||
|  | ||||
|   bool compare_to(const char *str) const; | ||||
|  | ||||
|   int match_length(const char *str) const; | ||||
|  | ||||
|   void scan_area(int *x1, int *y1, int *width, int *height) const; | ||||
|  | ||||
|  protected: | ||||
|   friend Font; | ||||
|   friend DisplayBuffer; | ||||
|  | ||||
|   const GlyphData *glyph_data_; | ||||
| }; | ||||
|  | ||||
| class Font { | ||||
|  public: | ||||
|   /** Construct the font with the given glyphs. | ||||
|    * | ||||
|    * @param glyphs A vector of glyphs, must be sorted lexicographically. | ||||
|    * @param baseline The y-offset from the top of the text to the baseline. | ||||
|    * @param bottom The y-offset from the top of the text to the bottom (i.e. height). | ||||
|    */ | ||||
|   Font(const GlyphData *data, int data_nr, int baseline, int height); | ||||
|  | ||||
|   int match_next_glyph(const char *str, int *match_length); | ||||
|  | ||||
|   void measure(const char *str, int *width, int *x_offset, int *baseline, int *height); | ||||
|   inline int get_baseline() { return this->baseline_; } | ||||
|   inline int get_height() { return this->height_; } | ||||
|  | ||||
|   const std::vector<Glyph, ExternalRAMAllocator<Glyph>> &get_glyphs() const { return glyphs_; } | ||||
|  | ||||
|  protected: | ||||
|   std::vector<Glyph, ExternalRAMAllocator<Glyph>> glyphs_; | ||||
|   int baseline_; | ||||
|   int height_; | ||||
| }; | ||||
|  | ||||
| class BaseImage { | ||||
|  public: | ||||
|   virtual void draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) = 0; | ||||
|   virtual int get_width() const = 0; | ||||
|   virtual int get_height() const = 0; | ||||
| }; | ||||
|  | ||||
| class Image : public BaseImage { | ||||
|  public: | ||||
|   Image(const uint8_t *data_start, int width, int height, ImageType type); | ||||
|   Color get_pixel(int x, int y, Color color_on = COLOR_ON, Color color_off = COLOR_OFF) const; | ||||
|   int get_width() const override; | ||||
|   int get_height() const override; | ||||
|   ImageType get_type() const; | ||||
|  | ||||
|   void draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) override; | ||||
|  | ||||
|   void set_transparency(bool transparent) { transparent_ = transparent; } | ||||
|   bool has_transparency() const { return transparent_; } | ||||
|  | ||||
|  protected: | ||||
|   bool get_binary_pixel_(int x, int y) const; | ||||
|   Color get_rgb24_pixel_(int x, int y) const; | ||||
|   Color get_rgba_pixel_(int x, int y) const; | ||||
|   Color get_rgb565_pixel_(int x, int y) const; | ||||
|   Color get_grayscale_pixel_(int x, int y) const; | ||||
|  | ||||
|   int width_; | ||||
|   int height_; | ||||
|   ImageType type_; | ||||
|   const uint8_t *data_start_; | ||||
|   bool transparent_; | ||||
| }; | ||||
|  | ||||
| class Animation : public Image { | ||||
|  public: | ||||
|   Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type); | ||||
|  | ||||
|   uint32_t get_animation_frame_count() const; | ||||
|   int get_current_frame() const; | ||||
|   void next_frame(); | ||||
|   void prev_frame(); | ||||
|  | ||||
|   /** Selects a specific frame within the animation. | ||||
|    * | ||||
|    * @param frame If possitive, advance to the frame. If negative, recede to that frame from the end frame. | ||||
|    */ | ||||
|   void set_frame(int frame); | ||||
|  | ||||
|   void set_loop(uint32_t start_frame, uint32_t end_frame, int count); | ||||
|  | ||||
|  protected: | ||||
|   void update_data_start_(); | ||||
|  | ||||
|   const uint8_t *animation_data_start_; | ||||
|   int current_frame_; | ||||
|   uint32_t animation_frame_count_; | ||||
|   uint32_t loop_start_frame_; | ||||
|   uint32_t loop_end_frame_; | ||||
|   int loop_count_; | ||||
|   int loop_current_iteration_; | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class DisplayPageShowAction : public Action<Ts...> { | ||||
|  public: | ||||
|   TEMPLATABLE_VALUE(DisplayPage *, page) | ||||
|   | ||||
							
								
								
									
										105
									
								
								esphome/components/display/font.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								esphome/components/display/font.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,105 @@ | ||||
| #include "font.h" | ||||
|  | ||||
| #include "esphome/core/hal.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace display { | ||||
|  | ||||
| bool Glyph::get_pixel(int x, int y) const { | ||||
|   const int x_data = x - this->glyph_data_->offset_x; | ||||
|   const int y_data = y - this->glyph_data_->offset_y; | ||||
|   if (x_data < 0 || x_data >= this->glyph_data_->width || y_data < 0 || y_data >= this->glyph_data_->height) | ||||
|     return false; | ||||
|   const uint32_t width_8 = ((this->glyph_data_->width + 7u) / 8u) * 8u; | ||||
|   const uint32_t pos = x_data + y_data * width_8; | ||||
|   return progmem_read_byte(this->glyph_data_->data + (pos / 8u)) & (0x80 >> (pos % 8u)); | ||||
| } | ||||
| const char *Glyph::get_char() const { return this->glyph_data_->a_char; } | ||||
| bool Glyph::compare_to(const char *str) const { | ||||
|   // 1 -> this->char_ | ||||
|   // 2 -> str | ||||
|   for (uint32_t i = 0;; i++) { | ||||
|     if (this->glyph_data_->a_char[i] == '\0') | ||||
|       return true; | ||||
|     if (str[i] == '\0') | ||||
|       return false; | ||||
|     if (this->glyph_data_->a_char[i] > str[i]) | ||||
|       return false; | ||||
|     if (this->glyph_data_->a_char[i] < str[i]) | ||||
|       return true; | ||||
|   } | ||||
|   // this should not happen | ||||
|   return false; | ||||
| } | ||||
| int Glyph::match_length(const char *str) const { | ||||
|   for (uint32_t i = 0;; i++) { | ||||
|     if (this->glyph_data_->a_char[i] == '\0') | ||||
|       return i; | ||||
|     if (str[i] != this->glyph_data_->a_char[i]) | ||||
|       return 0; | ||||
|   } | ||||
|   // this should not happen | ||||
|   return 0; | ||||
| } | ||||
| void Glyph::scan_area(int *x1, int *y1, int *width, int *height) const { | ||||
|   *x1 = this->glyph_data_->offset_x; | ||||
|   *y1 = this->glyph_data_->offset_y; | ||||
|   *width = this->glyph_data_->width; | ||||
|   *height = this->glyph_data_->height; | ||||
| } | ||||
| int Font::match_next_glyph(const char *str, int *match_length) { | ||||
|   int lo = 0; | ||||
|   int hi = this->glyphs_.size() - 1; | ||||
|   while (lo != hi) { | ||||
|     int mid = (lo + hi + 1) / 2; | ||||
|     if (this->glyphs_[mid].compare_to(str)) { | ||||
|       lo = mid; | ||||
|     } else { | ||||
|       hi = mid - 1; | ||||
|     } | ||||
|   } | ||||
|   *match_length = this->glyphs_[lo].match_length(str); | ||||
|   if (*match_length <= 0) | ||||
|     return -1; | ||||
|   return lo; | ||||
| } | ||||
| void Font::measure(const char *str, int *width, int *x_offset, int *baseline, int *height) { | ||||
|   *baseline = this->baseline_; | ||||
|   *height = this->height_; | ||||
|   int i = 0; | ||||
|   int min_x = 0; | ||||
|   bool has_char = false; | ||||
|   int x = 0; | ||||
|   while (str[i] != '\0') { | ||||
|     int match_length; | ||||
|     int glyph_n = this->match_next_glyph(str + i, &match_length); | ||||
|     if (glyph_n < 0) { | ||||
|       // Unknown char, skip | ||||
|       if (!this->get_glyphs().empty()) | ||||
|         x += this->get_glyphs()[0].glyph_data_->width; | ||||
|       i++; | ||||
|       continue; | ||||
|     } | ||||
|  | ||||
|     const Glyph &glyph = this->glyphs_[glyph_n]; | ||||
|     if (!has_char) { | ||||
|       min_x = glyph.glyph_data_->offset_x; | ||||
|     } else { | ||||
|       min_x = std::min(min_x, x + glyph.glyph_data_->offset_x); | ||||
|     } | ||||
|     x += glyph.glyph_data_->width + glyph.glyph_data_->offset_x; | ||||
|  | ||||
|     i += match_length; | ||||
|     has_char = true; | ||||
|   } | ||||
|   *x_offset = min_x; | ||||
|   *width = x - min_x; | ||||
| } | ||||
| Font::Font(const GlyphData *data, int data_nr, int baseline, int height) : baseline_(baseline), height_(height) { | ||||
|   glyphs_.reserve(data_nr); | ||||
|   for (int i = 0; i < data_nr; ++i) | ||||
|     glyphs_.emplace_back(&data[i]); | ||||
| } | ||||
|  | ||||
| }  // namespace display | ||||
| }  // namespace esphome | ||||
							
								
								
									
										66
									
								
								esphome/components/display/font.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								esphome/components/display/font.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/datatypes.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace display { | ||||
|  | ||||
| class DisplayBuffer; | ||||
| class Font; | ||||
|  | ||||
| struct GlyphData { | ||||
|   const char *a_char; | ||||
|   const uint8_t *data; | ||||
|   int offset_x; | ||||
|   int offset_y; | ||||
|   int width; | ||||
|   int height; | ||||
| }; | ||||
|  | ||||
| class Glyph { | ||||
|  public: | ||||
|   Glyph(const GlyphData *data) : glyph_data_(data) {} | ||||
|  | ||||
|   bool get_pixel(int x, int y) const; | ||||
|  | ||||
|   const char *get_char() const; | ||||
|  | ||||
|   bool compare_to(const char *str) const; | ||||
|  | ||||
|   int match_length(const char *str) const; | ||||
|  | ||||
|   void scan_area(int *x1, int *y1, int *width, int *height) const; | ||||
|  | ||||
|  protected: | ||||
|   friend Font; | ||||
|   friend DisplayBuffer; | ||||
|  | ||||
|   const GlyphData *glyph_data_; | ||||
| }; | ||||
|  | ||||
| class Font { | ||||
|  public: | ||||
|   /** Construct the font with the given glyphs. | ||||
|    * | ||||
|    * @param glyphs A vector of glyphs, must be sorted lexicographically. | ||||
|    * @param baseline The y-offset from the top of the text to the baseline. | ||||
|    * @param bottom The y-offset from the top of the text to the bottom (i.e. height). | ||||
|    */ | ||||
|   Font(const GlyphData *data, int data_nr, int baseline, int height); | ||||
|  | ||||
|   int match_next_glyph(const char *str, int *match_length); | ||||
|  | ||||
|   void measure(const char *str, int *width, int *x_offset, int *baseline, int *height); | ||||
|   inline int get_baseline() { return this->baseline_; } | ||||
|   inline int get_height() { return this->height_; } | ||||
|  | ||||
|   const std::vector<Glyph, ExternalRAMAllocator<Glyph>> &get_glyphs() const { return glyphs_; } | ||||
|  | ||||
|  protected: | ||||
|   std::vector<Glyph, ExternalRAMAllocator<Glyph>> glyphs_; | ||||
|   int baseline_; | ||||
|   int height_; | ||||
| }; | ||||
|  | ||||
| }  // namespace display | ||||
| }  // namespace esphome | ||||
							
								
								
									
										135
									
								
								esphome/components/display/image.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								esphome/components/display/image.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,135 @@ | ||||
| #include "image.h" | ||||
|  | ||||
| #include "esphome/core/hal.h" | ||||
| #include "display_buffer.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace display { | ||||
|  | ||||
| void Image::draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) { | ||||
|   switch (type_) { | ||||
|     case IMAGE_TYPE_BINARY: { | ||||
|       for (int img_x = 0; img_x < width_; img_x++) { | ||||
|         for (int img_y = 0; img_y < height_; img_y++) { | ||||
|           if (this->get_binary_pixel_(img_x, img_y)) { | ||||
|             display->draw_pixel_at(x + img_x, y + img_y, color_on); | ||||
|           } else if (!this->transparent_) { | ||||
|             display->draw_pixel_at(x + img_x, y + img_y, color_off); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|       break; | ||||
|     } | ||||
|     case IMAGE_TYPE_GRAYSCALE: | ||||
|       for (int img_x = 0; img_x < width_; img_x++) { | ||||
|         for (int img_y = 0; img_y < height_; img_y++) { | ||||
|           auto color = this->get_grayscale_pixel_(img_x, img_y); | ||||
|           if (color.w >= 0x80) { | ||||
|             display->draw_pixel_at(x + img_x, y + img_y, color); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|       break; | ||||
|     case IMAGE_TYPE_RGB565: | ||||
|       for (int img_x = 0; img_x < width_; img_x++) { | ||||
|         for (int img_y = 0; img_y < height_; img_y++) { | ||||
|           auto color = this->get_rgb565_pixel_(img_x, img_y); | ||||
|           if (color.w >= 0x80) { | ||||
|             display->draw_pixel_at(x + img_x, y + img_y, color); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|       break; | ||||
|     case IMAGE_TYPE_RGB24: | ||||
|       for (int img_x = 0; img_x < width_; img_x++) { | ||||
|         for (int img_y = 0; img_y < height_; img_y++) { | ||||
|           auto color = this->get_rgb24_pixel_(img_x, img_y); | ||||
|           if (color.w >= 0x80) { | ||||
|             display->draw_pixel_at(x + img_x, y + img_y, color); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|       break; | ||||
|     case IMAGE_TYPE_RGBA: | ||||
|       for (int img_x = 0; img_x < width_; img_x++) { | ||||
|         for (int img_y = 0; img_y < height_; img_y++) { | ||||
|           auto color = this->get_rgba_pixel_(img_x, img_y); | ||||
|           if (color.w >= 0x80) { | ||||
|             display->draw_pixel_at(x + img_x, y + img_y, color); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|       break; | ||||
|   } | ||||
| } | ||||
| Color Image::get_pixel(int x, int y, Color color_on, Color color_off) const { | ||||
|   if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) | ||||
|     return color_off; | ||||
|   switch (this->type_) { | ||||
|     case IMAGE_TYPE_BINARY: | ||||
|       return this->get_binary_pixel_(x, y) ? color_on : color_off; | ||||
|     case IMAGE_TYPE_GRAYSCALE: | ||||
|       return this->get_grayscale_pixel_(x, y); | ||||
|     case IMAGE_TYPE_RGB565: | ||||
|       return this->get_rgb565_pixel_(x, y); | ||||
|     case IMAGE_TYPE_RGB24: | ||||
|       return this->get_rgb24_pixel_(x, y); | ||||
|     case IMAGE_TYPE_RGBA: | ||||
|       return this->get_rgba_pixel_(x, y); | ||||
|     default: | ||||
|       return color_off; | ||||
|   } | ||||
| } | ||||
| bool Image::get_binary_pixel_(int x, int y) const { | ||||
|   const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u; | ||||
|   const uint32_t pos = x + y * width_8; | ||||
|   return progmem_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u)); | ||||
| } | ||||
| Color Image::get_rgba_pixel_(int x, int y) const { | ||||
|   const uint32_t pos = (x + y * this->width_) * 4; | ||||
|   return Color(progmem_read_byte(this->data_start_ + pos + 0), progmem_read_byte(this->data_start_ + pos + 1), | ||||
|                progmem_read_byte(this->data_start_ + pos + 2), progmem_read_byte(this->data_start_ + pos + 3)); | ||||
| } | ||||
| Color Image::get_rgb24_pixel_(int x, int y) const { | ||||
|   const uint32_t pos = (x + y * this->width_) * 3; | ||||
|   Color color = Color(progmem_read_byte(this->data_start_ + pos + 0), progmem_read_byte(this->data_start_ + pos + 1), | ||||
|                       progmem_read_byte(this->data_start_ + pos + 2)); | ||||
|   if (color.b == 1 && color.r == 0 && color.g == 0 && transparent_) { | ||||
|     // (0, 0, 1) has been defined as transparent color for non-alpha images. | ||||
|     // putting blue == 1 as a first condition for performance reasons (least likely value to short-cut the if) | ||||
|     color.w = 0; | ||||
|   } else { | ||||
|     color.w = 0xFF; | ||||
|   } | ||||
|   return color; | ||||
| } | ||||
| Color Image::get_rgb565_pixel_(int x, int y) const { | ||||
|   const uint32_t pos = (x + y * this->width_) * 2; | ||||
|   uint16_t rgb565 = | ||||
|       progmem_read_byte(this->data_start_ + pos + 0) << 8 | progmem_read_byte(this->data_start_ + pos + 1); | ||||
|   auto r = (rgb565 & 0xF800) >> 11; | ||||
|   auto g = (rgb565 & 0x07E0) >> 5; | ||||
|   auto b = rgb565 & 0x001F; | ||||
|   Color color = Color((r << 3) | (r >> 2), (g << 2) | (g >> 4), (b << 3) | (b >> 2)); | ||||
|   if (rgb565 == 0x0020 && transparent_) { | ||||
|     // darkest green has been defined as transparent color for transparent RGB565 images. | ||||
|     color.w = 0; | ||||
|   } else { | ||||
|     color.w = 0xFF; | ||||
|   } | ||||
|   return color; | ||||
| } | ||||
| Color Image::get_grayscale_pixel_(int x, int y) const { | ||||
|   const uint32_t pos = (x + y * this->width_); | ||||
|   const uint8_t gray = progmem_read_byte(this->data_start_ + pos); | ||||
|   uint8_t alpha = (gray == 1 && transparent_) ? 0 : 0xFF; | ||||
|   return Color(gray, gray, gray, alpha); | ||||
| } | ||||
| int Image::get_width() const { return this->width_; } | ||||
| int Image::get_height() const { return this->height_; } | ||||
| ImageType Image::get_type() const { return this->type_; } | ||||
| Image::Image(const uint8_t *data_start, int width, int height, ImageType type) | ||||
|     : width_(width), height_(height), type_(type), data_start_(data_start) {} | ||||
|  | ||||
| }  // namespace display | ||||
| }  // namespace esphome | ||||
							
								
								
									
										75
									
								
								esphome/components/display/image.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								esphome/components/display/image.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,75 @@ | ||||
| #pragma once | ||||
| #include "esphome/core/color.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace display { | ||||
|  | ||||
| enum ImageType { | ||||
|   IMAGE_TYPE_BINARY = 0, | ||||
|   IMAGE_TYPE_GRAYSCALE = 1, | ||||
|   IMAGE_TYPE_RGB24 = 2, | ||||
|   IMAGE_TYPE_RGB565 = 3, | ||||
|   IMAGE_TYPE_RGBA = 4, | ||||
| }; | ||||
|  | ||||
| inline int image_type_to_bpp(ImageType type) { | ||||
|   switch (type) { | ||||
|     case IMAGE_TYPE_BINARY: | ||||
|       return 1; | ||||
|     case IMAGE_TYPE_GRAYSCALE: | ||||
|       return 8; | ||||
|     case IMAGE_TYPE_RGB565: | ||||
|       return 16; | ||||
|     case IMAGE_TYPE_RGB24: | ||||
|       return 24; | ||||
|     case IMAGE_TYPE_RGBA: | ||||
|       return 32; | ||||
|   } | ||||
|   return 0; | ||||
| } | ||||
|  | ||||
| inline int image_type_to_width_stride(int width, ImageType type) { return (width * image_type_to_bpp(type) + 7u) / 8u; } | ||||
|  | ||||
| /// Turn the pixel OFF. | ||||
| extern const Color COLOR_OFF; | ||||
| /// Turn the pixel ON. | ||||
| extern const Color COLOR_ON; | ||||
|  | ||||
| class DisplayBuffer; | ||||
|  | ||||
| class BaseImage { | ||||
|  public: | ||||
|   virtual void draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) = 0; | ||||
|   virtual int get_width() const = 0; | ||||
|   virtual int get_height() const = 0; | ||||
| }; | ||||
|  | ||||
| class Image : public BaseImage { | ||||
|  public: | ||||
|   Image(const uint8_t *data_start, int width, int height, ImageType type); | ||||
|   Color get_pixel(int x, int y, Color color_on = COLOR_ON, Color color_off = COLOR_OFF) const; | ||||
|   int get_width() const override; | ||||
|   int get_height() const override; | ||||
|   ImageType get_type() const; | ||||
|  | ||||
|   void draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) override; | ||||
|  | ||||
|   void set_transparency(bool transparent) { transparent_ = transparent; } | ||||
|   bool has_transparency() const { return transparent_; } | ||||
|  | ||||
|  protected: | ||||
|   bool get_binary_pixel_(int x, int y) const; | ||||
|   Color get_rgb24_pixel_(int x, int y) const; | ||||
|   Color get_rgba_pixel_(int x, int y) const; | ||||
|   Color get_rgb565_pixel_(int x, int y) const; | ||||
|   Color get_grayscale_pixel_(int x, int y) const; | ||||
|  | ||||
|   int width_; | ||||
|   int height_; | ||||
|   ImageType type_; | ||||
|   const uint8_t *data_start_; | ||||
|   bool transparent_; | ||||
| }; | ||||
|  | ||||
| }  // namespace display | ||||
| }  // namespace esphome | ||||
| @@ -695,6 +695,13 @@ image: | ||||
|     file: mdi:alert-outline | ||||
|     type: BINARY | ||||
|  | ||||
| graph: | ||||
|   - id: my_graph | ||||
|     sensor: ha_hello_world_temperature | ||||
|     duration: 1h | ||||
|     width: 100 | ||||
|     height: 100 | ||||
|  | ||||
| cap1188: | ||||
|   id: cap1188_component | ||||
|   address: 0x29 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user