From a35fcc12c368f21368c0e6667bf6a18b4593ccf8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 17 Sep 2026 18:01:44 -0500 Subject: [PATCH] [display] Share the packed pixel read through ColorUtil ColorUtil::read_packed() lives next to to_color() so drivers with their own blit loop (it8951 carries a copy of the same switch) can pick it up; the comments say where the folding happens. --- esphome/components/display/display.cpp | 28 +++---------------- .../components/display/display_color_utils.h | 22 +++++++++++++++ 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/esphome/components/display/display.cpp b/esphome/components/display/display.cpp index a1e95c1d296..d1d461b3c1d 100644 --- a/esphome/components/display/display.cpp +++ b/esphome/components/display/display.cpp @@ -53,27 +53,6 @@ void Display::line_at_angle(int x, int y, int angle, int start_radius, int stop_ this->line(x1, y1, x2, y2, color); } -// Reads one source pixel of draw_pixels_at(). BITNESS is a template argument so -// the byte layout folds and to_color()'s channel scaling divides by a constant, -// which the compiler turns into a multiply and shift; at runtime it was three -// divisions per pixel. -template -static inline ESPHOME_ALWAYS_INLINE uint32_t read_packed_pixel(const uint8_t *ptr, size_t source_idx, bool big_endian) { - if constexpr (BITNESS == COLOR_BITNESS_565) { - const size_t idx = source_idx * 2; - if (big_endian) - return (ptr[idx] << 8) + ptr[idx + 1]; - return ptr[idx] + (ptr[idx + 1] << 8); - } else if constexpr (BITNESS == COLOR_BITNESS_888) { - const size_t idx = source_idx * 3; - if (big_endian) - return (ptr[idx + 0] << 16) + (ptr[idx + 1] << 8) + ptr[idx + 2]; - return ptr[idx + 0] + (ptr[idx + 1] << 8) + (ptr[idx + 2] << 16); - } else { - return ptr[source_idx]; - } -} - // flatten keeps to_color() inlined in the pixel loop; in PSRAM builds GCC at // -Os stops inlining it once there are two call sites, which costs a call and // spills per pixel. Without PSRAM there is one call site and it changes @@ -83,10 +62,12 @@ static void __attribute__((flatten)) draw_packed_pixels(Display *display, int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order, bool big_endian, int x_offset, int y_offset, int x_pad) { const size_t line_stride = x_offset + w + x_pad; // length of each source line in pixels + // BITNESS is a constant here, so to_color()'s three per pixel divisions + // become a multiply and shift and its bitness switch folds. const auto draw_source_pixel = [&](int x, int y, size_t source_idx) ESPHOME_ALWAYS_INLINE { display->draw_pixel_at( x + x_start, y + y_start, - ColorUtil::to_color(read_packed_pixel(ptr, source_idx, big_endian), order, BITNESS)); + ColorUtil::to_color(ColorUtil::read_packed(ptr, source_idx, big_endian), order, BITNESS)); }; // The source and frame buffers are row-major, so walking rows keeps the read // and the write sequential. When the display swaps the axes before writing, @@ -104,8 +85,7 @@ draw_packed_pixels(Display *display, int x_start, int y_start, int w, int h, con return; } #endif - // Without PSRAM the frame buffer is in uncached RAM, where the strided write - // is free, so one copy of the loop is enough. + // Row walk: builds without PSRAM, and PSRAM builds whose axes are not swapped. for (int y = 0; y != h; y++) { size_t source_idx = (y_offset + y) * line_stride + x_offset; for (int x = 0; x != w; x++, source_idx++) { diff --git a/esphome/components/display/display_color_utils.h b/esphome/components/display/display_color_utils.h index 608642caa44..4ad954ba0bb 100644 --- a/esphome/components/display/display_color_utils.h +++ b/esphome/components/display/display_color_utils.h @@ -1,5 +1,6 @@ #pragma once #include "esphome/core/color.h" +#include "esphome/core/helpers.h" namespace esphome::display { enum ColorOrder : uint8_t { COLOR_ORDER_RGB = 0, COLOR_ORDER_BGR = 1, COLOR_ORDER_GRB = 2 }; @@ -8,6 +9,27 @@ inline static uint8_t esp_scale(uint8_t i, uint8_t scale, uint8_t max_value = 25 class ColorUtil { public: + /// Reads one packed pixel of the given bitness from a source buffer into the + /// value to_color() takes. BITNESS is a template argument so the byte layout + /// folds; pass it on to to_color() as a constant too, and its channel scaling + /// divides by a constant, which the compiler turns into a multiply and shift. + template + static ESPHOME_ALWAYS_INLINE uint32_t read_packed(const uint8_t *ptr, size_t index, bool big_endian) { + if constexpr (BITNESS == COLOR_BITNESS_565) { + const size_t idx = index * 2; + if (big_endian) + return (ptr[idx] << 8) + ptr[idx + 1]; + return ptr[idx] + (ptr[idx + 1] << 8); + } else if constexpr (BITNESS == COLOR_BITNESS_888) { + const size_t idx = index * 3; + if (big_endian) + return (ptr[idx + 0] << 16) + (ptr[idx + 1] << 8) + ptr[idx + 2]; + return ptr[idx + 0] + (ptr[idx + 1] << 8) + (ptr[idx + 2] << 16); + } else { + return ptr[index]; + } + } + static Color to_color(uint32_t colorcode, ColorOrder color_order, ColorBitness color_bitness = ColorBitness::COLOR_BITNESS_888, bool right_bit_aligned = true) { uint8_t first_color, second_color, third_color;