From 1203e6009a740ddd0ff87e93a2257079c584f7ca Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 17 Sep 2026 18:13:22 -0500 Subject: [PATCH] WIP: route opaque RGB565 and RGB images through draw_pixels_at --- esphome/components/animation/image.py | 4 ++- esphome/components/file/image.py | 2 ++ esphome/components/image/image.cpp | 28 ++++++++++++++++++- esphome/components/image/image.h | 3 ++ esphome/components/mipi_spi/mipi_spi.h | 5 ++-- .../runtime_image/runtime_image.cpp | 4 ++- 6 files changed, 41 insertions(+), 5 deletions(-) diff --git a/esphome/components/animation/image.py b/esphome/components/animation/image.py index 0265a350f71..f2027b500f8 100644 --- a/esphome/components/animation/image.py +++ b/esphome/components/animation/image.py @@ -1,6 +1,6 @@ from esphome import automation import esphome.codegen as cg -from esphome.components.const import CONF_LOOP +from esphome.components.const import CONF_BYTE_ORDER, CONF_LOOP from esphome.components.file import image as file_image from esphome.components.file.image import image_schema, write_image from esphome.components.image import Image_, validate_settings @@ -115,6 +115,8 @@ async def setup_animation(config: ConfigType) -> None: image_type, trans_value, ) + if config.get(CONF_BYTE_ORDER) == "BIG_ENDIAN": + cg.add(var.set_big_endian(True)) if loop_config := config.get(CONF_LOOP): start = loop_config[CONF_START_FRAME] end = loop_config.get(CONF_END_FRAME, frame_count) diff --git a/esphome/components/file/image.py b/esphome/components/file/image.py index ab769954124..2edf8dfcc47 100644 --- a/esphome/components/file/image.py +++ b/esphome/components/file/image.py @@ -256,6 +256,8 @@ async def new_image(config: ConfigType) -> MockObj: var = cg.new_Pvariable( config[CONF_ID], prog_arr, width, height, image_type, trans_value ) + if config.get(CONF_BYTE_ORDER) == "BIG_ENDIAN": + cg.add(var.set_big_endian(True)) add_metadata( config[CONF_ID], width, height, config[CONF_TYPE], config[CONF_TRANSPARENCY] ) diff --git a/esphome/components/image/image.cpp b/esphome/components/image/image.cpp index 2ecbe836b79..1713e103ec6 100644 --- a/esphome/components/image/image.cpp +++ b/esphome/components/image/image.cpp @@ -1,5 +1,7 @@ #include "image.h" +#include + #include "esphome/core/hal.h" #include "esphome/core/helpers.h" @@ -40,6 +42,29 @@ void Image::draw(int x, int y, display::Display *display, Color color_on, Color y_end = clipping.y2() - y; } + // Clamp to the display so the bulk path below never writes off screen; the + // per pixel path bounds checks itself, this just saves it the work. + if (x < 0) + x_start = std::max(x_start, -x); + if (y < 0) + y_start = std::max(y_start, -y); + x_end = std::min(x_end, display->get_width() - x); + y_end = std::min(y_end, display->get_height() - y); + if (x_end <= x_start || y_end <= y_start) + return; + + // Opaque RGB565 and RGB pixels are already in the layout draw_pixels_at() + // takes, so hand the window to the display: drivers with a bulk path blit + // it, the others walk it per pixel as before. + if (this->transparency_ == TRANSPARENCY_OPAQUE && + (this->type_ == IMAGE_TYPE_RGB565 || this->type_ == IMAGE_TYPE_RGB)) { + const bool rgb565 = this->type_ == IMAGE_TYPE_RGB565; + display->draw_pixels_at(x + x_start, y + y_start, x_end - x_start, y_end - y_start, this->data_start_, + display::COLOR_ORDER_RGB, rgb565 ? display::COLOR_BITNESS_565 : display::COLOR_BITNESS_888, + rgb565 && this->big_endian_, x_start, y_start, this->width_ - x_end); + return; + } + // Pixel data and frame buffers are row-major, so walking rows keeps the // image read and the frame buffer write sequential. When the display swaps // the axes before writing, walking columns keeps the write sequential @@ -200,7 +225,8 @@ Color Image::get_rgb_pixel_(int x, int y) const { } Color Image::get_rgb565_pixel_(int x, int y) const { const uint8_t *pos = this->data_start_ + (x + y * this->width_) * this->bpp_ / 8; - uint16_t rgb565 = encode_uint16(progmem_read_byte(pos + 1), progmem_read_byte(pos)); + const uint16_t rgb565 = this->big_endian_ ? encode_uint16(progmem_read_byte(pos), progmem_read_byte(pos + 1)) + : encode_uint16(progmem_read_byte(pos + 1), progmem_read_byte(pos)); auto r = (rgb565 & 0xF800) >> 11; auto g = (rgb565 & 0x07E0) >> 5; auto b = rgb565 & 0x001F; diff --git a/esphome/components/image/image.h b/esphome/components/image/image.h index 736e7c61a2d..53972a323f6 100644 --- a/esphome/components/image/image.h +++ b/esphome/components/image/image.h @@ -38,6 +38,8 @@ class Image : public display::BaseImage { void draw(int x, int y, display::Display *display, Color color_on, Color color_off) override; bool has_transparency() const { return this->transparency_ != TRANSPARENCY_OPAQUE; } + /// RGB565 pixel byte order; static images default to little endian. + void set_big_endian(bool big_endian) { this->big_endian_ = big_endian; } #ifdef USE_LVGL lv_image_dsc_t *get_lv_image_dsc(); @@ -57,6 +59,7 @@ class Image : public display::BaseImage { ImageType type_; const uint8_t *data_start_; Transparency transparency_; + bool big_endian_{false}; size_t bpp_{}; #ifdef USE_LVGL lv_img_dsc_t dsc_{}; diff --git a/esphome/components/mipi_spi/mipi_spi.h b/esphome/components/mipi_spi/mipi_spi.h index 2131bf8a3f1..aac5ff627b0 100644 --- a/esphome/components/mipi_spi/mipi_spi.h +++ b/esphome/components/mipi_spi/mipi_spi.h @@ -189,8 +189,9 @@ class MipiSpi : public display::Display, if (w <= 0 || h <= 0) return; if (get_pixel_mode(bitness) != BUFFERPIXEL || big_endian != IS_BIG_ENDIAN) { - // note that the usual logging macros are banned in header files, so use their replacement - esph_log_e(TAG, "Unsupported color depth or bit order"); + // Not the buffer's own format: convert per pixel through draw_pixel_at() + display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, + x_pad); return; } this->write_to_display_(x_start, y_start, w, h, reinterpret_cast(ptr), x_offset, y_offset, diff --git a/esphome/components/runtime_image/runtime_image.cpp b/esphome/components/runtime_image/runtime_image.cpp index ef92d0d7073..d737218ed3e 100644 --- a/esphome/components/runtime_image/runtime_image.cpp +++ b/esphome/components/runtime_image/runtime_image.cpp @@ -46,7 +46,9 @@ RuntimeImage::RuntimeImage(ImageFormat format, image::ImageType type, image::Tra fixed_width_(fixed_width), fixed_height_(fixed_height), placeholder_(placeholder), - is_big_endian_(is_big_endian) {} + is_big_endian_(is_big_endian) { + this->big_endian_ = is_big_endian; +} RuntimeImage::~RuntimeImage() { this->release(); }