WIP: route opaque RGB565 and RGB images through draw_pixels_at

This commit is contained in:
J. Nick Koston
2026-09-17 18:13:22 -05:00
parent a35fcc12c3
commit 1203e6009a
6 changed files with 41 additions and 5 deletions
+3 -1
View File
@@ -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)
+2
View File
@@ -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]
)
+27 -1
View File
@@ -1,5 +1,7 @@
#include "image.h"
#include <algorithm>
#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;
+3
View File
@@ -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_{};
+3 -2
View File
@@ -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<const BUFFERTYPE *>(ptr), x_offset, y_offset,
@@ -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(); }