diff --git a/esphome/components/animation/__init__.py b/esphome/components/animation/__init__.py index 7c9ff07f97..4cf0b0ed7d 100644 --- a/esphome/components/animation/__init__.py +++ b/esphome/components/animation/__init__.py @@ -92,6 +92,29 @@ async def to_code(config): data[pos] = pix[2] pos += 1 + elif config[CONF_TYPE] == "RGB565": + data = [0 for _ in range(height * width * 2 * frames)] + pos = 0 + for frameIndex in range(frames): + image.seek(frameIndex) + frame = image.convert("RGB") + if CONF_RESIZE in config: + frame = frame.resize([width, height]) + pixels = list(frame.getdata()) + if len(pixels) != height * width: + raise core.EsphomeError( + f"Unexpected number of pixels in {path} frame {frameIndex}: ({len(pixels)} != {height*width})" + ) + for pix in pixels: + R = pix[0] >> 3 + G = pix[1] >> 2 + B = pix[2] >> 3 + rgb = (R << 11) | (G << 5) | B + data[pos] = rgb >> 8 + pos += 1 + data[pos] = rgb & 255 + pos += 1 + elif config[CONF_TYPE] == "BINARY": width8 = ((width + 7) // 8) * 8 data = [0 for _ in range((height * width8 // 8) * frames)] diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index 4ad353a254..d00fdd5240 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -242,6 +242,13 @@ void DisplayBuffer::image(int x, int y, Image *image, Color color_on, Color colo } } break; + case IMAGE_TYPE_RGB565: + for (int img_x = 0; img_x < image->get_width(); img_x++) { + for (int img_y = 0; img_y < image->get_height(); img_y++) { + this->draw_pixel_at(x + img_x, y + img_y, image->get_rgb565_pixel(img_x, img_y)); + } + } + break; } } @@ -497,6 +504,17 @@ Color Image::get_color_pixel(int x, int y) const { (progmem_read_byte(this->data_start_ + pos + 0) << 16); return Color(color32); } +Color Image::get_rgb565_pixel(int x, int y) const { + if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) + return Color::BLACK; + 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; + return Color((r << 3) | (r >> 2), (g << 2) | (g >> 4), (b << 3) | (b >> 2)); +} Color Image::get_grayscale_pixel(int x, int y) const { if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) return Color::BLACK; @@ -532,6 +550,20 @@ Color Animation::get_color_pixel(int x, int y) const { (progmem_read_byte(this->data_start_ + pos + 0) << 16); return Color(color32); } +Color Animation::get_rgb565_pixel(int x, int y) const { + if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) + return Color::BLACK; + const uint32_t frame_index = this->width_ * this->height_ * this->current_frame_; + if (frame_index >= (uint32_t)(this->width_ * this->height_ * this->animation_frame_count_)) + return Color::BLACK; + const uint32_t pos = (x + y * this->width_ + frame_index) * 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; + return Color((r << 3) | (r >> 2), (g << 2) | (g >> 4), (b << 3) | (b >> 2)); +} Color Animation::get_grayscale_pixel(int x, int y) const { if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) return Color::BLACK; diff --git a/esphome/components/display/display_buffer.h b/esphome/components/display/display_buffer.h index 8ee1cd8779..86221c5f96 100644 --- a/esphome/components/display/display_buffer.h +++ b/esphome/components/display/display_buffer.h @@ -82,6 +82,7 @@ enum ImageType { IMAGE_TYPE_GRAYSCALE = 1, IMAGE_TYPE_RGB24 = 2, IMAGE_TYPE_TRANSPARENT_BINARY = 3, + IMAGE_TYPE_RGB565 = 4, }; enum DisplayRotation { @@ -453,6 +454,7 @@ class Image { Image(const uint8_t *data_start, int width, int height, ImageType type); virtual bool get_pixel(int x, int y) const; virtual Color get_color_pixel(int x, int y) const; + virtual Color get_rgb565_pixel(int x, int y) const; virtual Color get_grayscale_pixel(int x, int y) const; int get_width() const; int get_height() const; @@ -470,6 +472,7 @@ class Animation : public Image { Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type); bool get_pixel(int x, int y) const override; Color get_color_pixel(int x, int y) const override; + Color get_rgb565_pixel(int x, int y) const override; Color get_grayscale_pixel(int x, int y) const override; int get_animation_frame_count() const; diff --git a/esphome/components/image/__init__.py b/esphome/components/image/__init__.py index 70d77dfd14..0004391f20 100644 --- a/esphome/components/image/__init__.py +++ b/esphome/components/image/__init__.py @@ -25,6 +25,7 @@ IMAGE_TYPE = { "GRAYSCALE": ImageType.IMAGE_TYPE_GRAYSCALE, "RGB24": ImageType.IMAGE_TYPE_RGB24, "TRANSPARENT_BINARY": ImageType.IMAGE_TYPE_TRANSPARENT_BINARY, + "RGB565": ImageType.IMAGE_TYPE_RGB565, } Image_ = display.display_ns.class_("Image") @@ -89,6 +90,21 @@ async def to_code(config): data[pos] = pix[2] pos += 1 + elif config[CONF_TYPE] == "RGB565": + image = image.convert("RGB") + pixels = list(image.getdata()) + data = [0 for _ in range(height * width * 3)] + pos = 0 + for pix in pixels: + R = pix[0] >> 3 + G = pix[1] >> 2 + B = pix[2] >> 3 + rgb = (R << 11) | (G << 5) | B + data[pos] = rgb >> 8 + pos += 1 + data[pos] = rgb & 255 + pos += 1 + elif config[CONF_TYPE] == "BINARY": image = image.convert("1", dither=dither) width8 = ((width + 7) // 8) * 8