mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +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:
		
							
								
								
									
										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/helpers.h" | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | #include "animation.h" | ||||||
|  | #include "image.h" | ||||||
|  | #include "font.h" | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace display { | namespace display { | ||||||
|  |  | ||||||
| @@ -15,25 +19,6 @@ static const char *const TAG = "display"; | |||||||
| const Color COLOR_OFF(0, 0, 0, 255); | const Color COLOR_OFF(0, 0, 0, 255); | ||||||
| const Color COLOR_ON(255, 255, 255, 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) { | void Rect::expand(int16_t horizontal, int16_t vertical) { | ||||||
|   if (this->is_set() && (this->w >= (-2 * horizontal)) && (this->h >= (-2 * vertical))) { |   if (this->is_set() && (this->w >= (-2 * horizontal)) && (this->h >= (-2 * vertical))) { | ||||||
|     this->x = this->x - horizontal; |     this->x = this->x - horizontal; | ||||||
| @@ -505,286 +490,6 @@ Rect DisplayBuffer::get_clipping() { | |||||||
|     return this->clipping_rectangle_.back(); |     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)) {} | DisplayPage::DisplayPage(display_writer_t writer) : writer_(std::move(writer)) {} | ||||||
| void DisplayPage::show() { this->parent_->show_page(this); } | void DisplayPage::show() { this->parent_->show_page(this); } | ||||||
|   | |||||||
| @@ -16,6 +16,10 @@ | |||||||
| #include "esphome/components/qr_code/qr_code.h" | #include "esphome/components/qr_code/qr_code.h" | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | #include "animation.h" | ||||||
|  | #include "font.h" | ||||||
|  | #include "image.h" | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace display { | namespace display { | ||||||
|  |  | ||||||
| @@ -70,19 +74,6 @@ enum class TextAlign { | |||||||
|   BOTTOM_RIGHT = BOTTOM | RIGHT, |   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 { | enum DisplayType { | ||||||
|   DISPLAY_TYPE_BINARY = 1, |   DISPLAY_TYPE_BINARY = 1, | ||||||
|   DISPLAY_TYPE_GRAYSCALE = 2, |   DISPLAY_TYPE_GRAYSCALE = 2, | ||||||
| @@ -123,8 +114,6 @@ class Rect { | |||||||
|   void info(const std::string &prefix = "rect info:"); |   void info(const std::string &prefix = "rect info:"); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class BaseImage; |  | ||||||
| class Font; |  | ||||||
| class DisplayBuffer; | class DisplayBuffer; | ||||||
| class DisplayPage; | class DisplayPage; | ||||||
| class DisplayOnPageChangeTrigger; | class DisplayOnPageChangeTrigger; | ||||||
| @@ -475,123 +464,6 @@ class DisplayPage { | |||||||
|   DisplayPage *next_{nullptr}; |   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...> { | template<typename... Ts> class DisplayPageShowAction : public Action<Ts...> { | ||||||
|  public: |  public: | ||||||
|   TEMPLATABLE_VALUE(DisplayPage *, page) |   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 |     file: mdi:alert-outline | ||||||
|     type: BINARY |     type: BINARY | ||||||
|  |  | ||||||
|  | graph: | ||||||
|  |   - id: my_graph | ||||||
|  |     sensor: ha_hello_world_temperature | ||||||
|  |     duration: 1h | ||||||
|  |     width: 100 | ||||||
|  |     height: 100 | ||||||
|  |  | ||||||
| cap1188: | cap1188: | ||||||
|   id: cap1188_component |   id: cap1188_component | ||||||
|   address: 0x29 |   address: 0x29 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user