diff --git a/esphome/components/display/display.cpp b/esphome/components/display/display.cpp index ba8f85463f4..a5dd398e81b 100644 --- a/esphome/components/display/display.cpp +++ b/esphome/components/display/display.cpp @@ -57,17 +57,17 @@ void Display::line_at_angle(int x, int y, int angle, int start_radius, int stop_ // -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 // nothing, and draw_pixel_at() stays a virtual call either way. -template -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) { +// HIGH_BYTE_FIRST (big endian) only matters for 565; as a template argument its test leaves the pixel loop. +template +static void __attribute__((flatten)) draw_packed_pixels(Display *display, int x_start, int y_start, int w, int h, + const uint8_t *ptr, 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(ColorUtil::read_packed(ptr, source_idx, big_endian), order, BITNESS)); + ColorUtil::to_color(ColorUtil::read_packed(ptr, source_idx, HIGH_BYTE_FIRST), 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, @@ -94,20 +94,59 @@ draw_packed_pixels(Display *display, int x_start, int y_start, int w, int h, con } } +// Remove before 2027.5.0. Draws a block in the deprecated BGR or GRB order, pixel by pixel. Not inlined, so +// the RGB loops above compile as tightly as before. +static void __attribute__((noinline)) +draw_ordered_pixels(Display *display, int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order, + ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) { + const size_t line_stride = x_offset + w + x_pad; + 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++) { + uint32_t code; + if (bitness == COLOR_BITNESS_565) { + code = ColorUtil::read_packed(ptr, source_idx, big_endian); + } else if (bitness == COLOR_BITNESS_888) { + code = ColorUtil::read_packed(ptr, source_idx, big_endian); + } else { + code = ColorUtil::read_packed(ptr, source_idx, big_endian); + } + display->draw_pixel_at(x + x_start, y + y_start, apply_color_order(ColorUtil::to_color(code, bitness), order)); + } + } +} + +void Display::draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorBitness bitness, + bool big_endian, int x_offset, int y_offset, int x_pad) { + // Through the deprecated form, so a driver that still overrides only that one keeps its fast path. + // Remove before 2027.5.0, when the per pixel drawing below moves here. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + this->draw_pixels_at(x_start, y_start, w, h, ptr, COLOR_ORDER_RGB, bitness, big_endian, x_offset, y_offset, x_pad); +#pragma GCC diagnostic pop +} + +// Must not call draw_pixels_at() without a color order or draw_pixels_internal(): those call this, and a legacy +// override's fallback ends up here. void Display::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order, ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) { + if (order != COLOR_ORDER_RGB) { + draw_ordered_pixels(this, x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, x_pad); + return; + } switch (bitness) { case COLOR_BITNESS_565: - draw_packed_pixels(this, x_start, y_start, w, h, ptr, order, big_endian, x_offset, y_offset, - x_pad); + if (big_endian) { + draw_packed_pixels(this, x_start, y_start, w, h, ptr, x_offset, y_offset, x_pad); + } else { + draw_packed_pixels(this, x_start, y_start, w, h, ptr, x_offset, y_offset, x_pad); + } break; case COLOR_BITNESS_888: - draw_packed_pixels(this, x_start, y_start, w, h, ptr, order, big_endian, x_offset, y_offset, - x_pad); + draw_packed_pixels(this, x_start, y_start, w, h, ptr, x_offset, y_offset, x_pad); break; default: - draw_packed_pixels(this, x_start, y_start, w, h, ptr, order, big_endian, x_offset, y_offset, - x_pad); + draw_packed_pixels(this, x_start, y_start, w, h, ptr, x_offset, y_offset, x_pad); break; } } diff --git a/esphome/components/display/display.h b/esphome/components/display/display.h index b43a3221518..a6ac319939c 100644 --- a/esphome/components/display/display.h +++ b/esphome/components/display/display.h @@ -346,8 +346,6 @@ class Display : public PollingComponent { virtual void draw_pixel_at(int x, int y, Color color) = 0; /** Given an array of pixels encoded in the nominated format, draw these into the display's buffer. - * The naive implementation here will work in all cases, but can be overridden by sub-classes - * in order to optimise the procedure. * The parameters describe a rectangular block of pixels, potentially within a larger buffer. * * \param x_start The starting destination x position @@ -355,27 +353,49 @@ class Display : public PollingComponent { * \param w the width of the pixel block * \param h the height of the pixel block * \param ptr A pointer to the start of the data to be copied - * \param order The ordering of the colors - * \param bitness Defines the number of bits and their format for each pixel + * \param bitness Defines the number of bits and their format for each pixel. Red is always in the + * most significant bits. * \param big_endian True if 16 bit values are stored big-endian. Ignored for 888, which is always b,g,r - * in memory for RGB order, as LVGL and ESPHome images store it. + * in memory, as LVGL and ESPHome images store it. * \param x_offset The initial x-offset into the source buffer. * \param y_offset The initial y-offset into the source buffer. * \param x_pad How many pixels are in each line after the end of the pixels to be copied. * * The length of each source buffer line (stride) will be x_offset + w + x_pad. - * - * An override that copies the block as is must send any other format, byte order, a software - * rotation or an active clipping rectangle through this base version, which draws per pixel. - * The block must lie on the screen. + * The block must lie on the screen. A driver that can copy blocks itself overrides draw_pixels_internal(). */ + void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorBitness bitness, bool big_endian, + int x_offset, int y_offset, int x_pad) { + this->draw_pixels_internal(x_start, y_start, w, h, ptr, bitness, big_endian, x_offset, y_offset, x_pad); + } + + /// Convenience overload for base case where the pixels are packed into the buffer with no gaps (e.g. suits LVGL.) + void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorBitness bitness, + bool big_endian) { + this->draw_pixels_internal(x_start, y_start, w, h, ptr, bitness, big_endian, 0, 0, 0); + } + + // Remove before 2027.5.0 + /// The block form above with a color order. The base version draws per pixel and honors the order; + /// a driver that still overrides this one keeps receiving every block, since draw_pixels_internal() calls it. + /// Such a driver needs `using display::Display::draw_pixels_at;` so the forms above stay visible through it, + /// and its fallback must call Display::draw_pixels_at() with the order: Display::draw_pixels_internal() would + /// call the override again. + ESPDEPRECATED( + "Pixels are always RGB: call draw_pixels_at() without the ColorOrder, and override draw_pixels_internal() " + "to copy blocks. Removed in 2027.5.0", + "2026.11.0") virtual void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order, ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad); - /// Convenience overload for base case where the pixels are packed into the buffer with no gaps (e.g. suits LVGL.) + // Remove before 2027.5.0 + ESPDEPRECATED("Pixels are always RGB, drop the ColorOrder argument. Removed in 2027.5.0", "2026.11.0") void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order, ColorBitness bitness, bool big_endian) { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" this->draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, 0, 0, 0); +#pragma GCC diagnostic pop } /// Draw a straight line from the point [x1,y1] to [x2,y2] with the given color. @@ -796,6 +816,16 @@ class Display : public PollingComponent { void show_test_card() { this->show_test_card_ = true; } protected: + /** Draws a block for draw_pixels_at(); the parameters are the same. The base version draws it per pixel + * through draw_pixel_at(), so it works on any display. + * + * A driver can override it to copy blocks that are already laid out the way its own draw_pixel_at() + * writes, and must send any other format, byte order, a software rotation or an active clipping + * rectangle through Display::draw_pixels_internal(), which draws per pixel. Do not also override the + * deprecated draw_pixels_at() with a color order: Display::draw_pixels_internal() calls it. + */ + virtual void draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorBitness bitness, + bool big_endian, int x_offset, int y_offset, int x_pad); bool clamp_x_(int x, int w, int &min_x, int &max_x); bool clamp_y_(int y, int h, int &min_y, int &max_y); void vprintf_(int x, int y, BaseFont *font, Color color, Color background, TextAlign align, const char *format, diff --git a/esphome/components/display/display_color_utils.h b/esphome/components/display/display_color_utils.h index bd658986519..1b4f47c1250 100644 --- a/esphome/components/display/display_color_utils.h +++ b/esphome/components/display/display_color_utils.h @@ -9,6 +9,38 @@ enum ColorOrder : uint8_t { COLOR_ORDER_RGB = 0, COLOR_ORDER_BGR = 1, COLOR_ORDE enum ColorBitness : uint8_t { COLOR_BITNESS_888 = 0, COLOR_BITNESS_565 = 1, COLOR_BITNESS_332 = 2 }; inline static uint8_t esp_scale(uint8_t i, uint8_t scale, uint8_t max_value = 255) { return (max_value * i / scale); } +/// esp_scale(value, (1 << bits) - 1) for a field of 2, 3, 5, 6 or 8 bits, as a multiply and shift that gives the +/// same result for every field value. With bits a constant this avoids a division. +constexpr uint8_t expand_field(uint8_t value, uint8_t bits) { + switch (bits) { + case 2: + return (value * 170) >> 1; + case 3: + return (value * 583) >> 4; + case 5: + return (value * 1053) >> 7; + case 6: + return (value * 4145) >> 10; + case 8: + return value; + default: + return esp_scale(value, (1 << bits) - 1); + } +} + +// Remove before 2027.5.0, with the forms that take a color order. Maps a color read in RGB order to the +// given order, by moving its channels. +inline Color apply_color_order(Color color, ColorOrder order) { + switch (order) { + case COLOR_ORDER_BGR: + return Color(color.b, color.g, color.r); + case COLOR_ORDER_GRB: + return Color(color.g, color.r, color.b); + default: + return color; + } +} + class ColorUtil { public: /// Reads one packed pixel of the given bitness from a source buffer into the @@ -23,7 +55,7 @@ class ColorUtil { return (ptr[idx] << 8) + ptr[idx + 1]; return ptr[idx] + (ptr[idx + 1] << 8); } else if constexpr (BITNESS == COLOR_BITNESS_888) { - // Always the LVGL RGB888 layout, b,g,r in memory for RGB order; byte order applies to 565 only + // Always the LVGL RGB888 layout, b,g,r in memory; byte order applies to 565 only const size_t idx = index * 3; return ptr[idx + 0] + (ptr[idx + 1] << 8) + (ptr[idx + 2] << 16); } else { @@ -31,9 +63,9 @@ class ColorUtil { } } - 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; + /// Unpacks a color of the given bitness, red in the most significant bits. + static Color to_color(uint32_t colorcode, ColorBitness color_bitness = ColorBitness::COLOR_BITNESS_888, + bool right_bit_aligned = true) { uint8_t first_bits = 0; uint8_t second_bits = 0; uint8_t third_bits = 0; @@ -56,74 +88,66 @@ class ColorUtil { break; } - first_color = right_bit_aligned ? esp_scale(((colorcode >> (second_bits + third_bits)) & ((1 << first_bits) - 1)), - ((1 << first_bits) - 1)) - : esp_scale(((colorcode >> 16) & 0xFF), (1 << first_bits) - 1); + const uint8_t red = + right_bit_aligned + ? expand_field((colorcode >> (second_bits + third_bits)) & ((1 << first_bits) - 1), first_bits) + : esp_scale(((colorcode >> 16) & 0xFF), (1 << first_bits) - 1); - second_color = right_bit_aligned - ? esp_scale(((colorcode >> third_bits) & ((1 << second_bits) - 1)), ((1 << second_bits) - 1)) - : esp_scale(((colorcode >> 8) & 0xFF), ((1 << second_bits) - 1)); + const uint8_t green = right_bit_aligned + ? expand_field((colorcode >> third_bits) & ((1 << second_bits) - 1), second_bits) + : esp_scale(((colorcode >> 8) & 0xFF), ((1 << second_bits) - 1)); - third_color = (right_bit_aligned ? esp_scale(((colorcode >> 0) & ((1 << third_bits) - 1)), ((1 << third_bits) - 1)) - : esp_scale(((colorcode >> 0) & 0xFF), (1 << third_bits) - 1)); + const uint8_t blue = right_bit_aligned ? expand_field(colorcode & ((1 << third_bits) - 1), third_bits) + : esp_scale(((colorcode >> 0) & 0xFF), (1 << third_bits) - 1); - Color color_return; - - switch (color_order) { - case COLOR_ORDER_RGB: - color_return.r = first_color; - color_return.g = second_color; - color_return.b = third_color; - break; - case COLOR_ORDER_BGR: - color_return.b = first_color; - color_return.g = second_color; - color_return.r = third_color; - break; - case COLOR_ORDER_GRB: - color_return.g = first_color; - color_return.r = second_color; - color_return.b = third_color; - break; - } - return color_return; + return Color(red, green, blue); + } + // Remove before 2027.5.0 + ESPDEPRECATED("Packed colors are always RGB, drop the ColorOrder argument. Removed in 2027.5.0", "2026.11.0") + static Color to_color(uint32_t colorcode, ColorOrder color_order, + ColorBitness color_bitness = ColorBitness::COLOR_BITNESS_888, bool right_bit_aligned = true) { + return apply_color_order(to_color(colorcode, color_bitness, right_bit_aligned), color_order); } static inline Color rgb332_to_color(uint8_t rgb332_color) { - return to_color((uint32_t) rgb332_color, COLOR_ORDER_RGB, COLOR_BITNESS_332); + return to_color((uint32_t) rgb332_color, COLOR_BITNESS_332); } - static uint8_t color_to_332(Color color, ColorOrder color_order = ColorOrder::COLOR_ORDER_RGB) { - uint16_t red_color, green_color, blue_color; - - red_color = esp_scale8(color.red, ((1 << 3) - 1)); - green_color = esp_scale8(color.green, ((1 << 3) - 1)); - blue_color = esp_scale8(color.blue, (1 << 2) - 1); - - switch (color_order) { - case COLOR_ORDER_RGB: - return red_color << 5 | green_color << 2 | blue_color; - case COLOR_ORDER_BGR: - return blue_color << 6 | green_color << 3 | red_color; - case COLOR_ORDER_GRB: - return green_color << 5 | red_color << 2 | blue_color; - } - return 0; + static uint8_t color_to_332(Color color) { + const uint16_t red_color = esp_scale8(color.red, ((1 << 3) - 1)); + const uint16_t green_color = esp_scale8(color.green, ((1 << 3) - 1)); + const uint16_t blue_color = esp_scale8(color.blue, (1 << 2) - 1); + return red_color << 5 | green_color << 2 | blue_color; } - static uint16_t color_to_565(Color color, ColorOrder color_order = ColorOrder::COLOR_ORDER_RGB) { - uint16_t red_color, green_color, blue_color; - - red_color = esp_scale8(color.red, ((1 << 5) - 1)); - green_color = esp_scale8(color.green, ((1 << 6) - 1)); - blue_color = esp_scale8(color.blue, (1 << 5) - 1); - + // Remove before 2027.5.0 + ESPDEPRECATED("Packed colors are always RGB, drop the ColorOrder argument. Removed in 2027.5.0", "2026.11.0") + static uint8_t color_to_332(Color color, ColorOrder color_order) { + if (color_order == COLOR_ORDER_RGB) + return color_to_332(color); + // The other orders move the 2 bit field, so they cannot reuse the RGB packing + const uint16_t red_color = esp_scale8(color.red, ((1 << 3) - 1)); + const uint16_t green_color = esp_scale8(color.green, ((1 << 3) - 1)); + if (color_order == COLOR_ORDER_BGR) + return esp_scale8(color.blue, (1 << 2) - 1) << 6 | green_color << 3 | red_color; + return green_color << 5 | red_color << 2 | esp_scale8(color.blue, (1 << 2) - 1); + } + static uint16_t color_to_565(Color color) { + const uint16_t red_color = esp_scale8(color.red, ((1 << 5) - 1)); + const uint16_t green_color = esp_scale8(color.green, ((1 << 6) - 1)); + const uint16_t blue_color = esp_scale8(color.blue, (1 << 5) - 1); + return red_color << 11 | green_color << 5 | blue_color; + } + // Remove before 2027.5.0 + ESPDEPRECATED("Packed colors are always RGB, drop the ColorOrder argument. Removed in 2027.5.0", "2026.11.0") + static uint16_t color_to_565(Color color, ColorOrder color_order) { switch (color_order) { - case COLOR_ORDER_RGB: - return red_color << 11 | green_color << 5 | blue_color; case COLOR_ORDER_BGR: - return blue_color << 11 | green_color << 5 | red_color; + return color_to_565(Color(color.b, color.g, color.r)); case COLOR_ORDER_GRB: - return green_color << 10 | red_color << 5 | blue_color; + // Green moves to the 5 bit field at the top, so it cannot reuse the RGB packing + return esp_scale8(color.green, (1 << 6) - 1) << 10 | esp_scale8(color.red, (1 << 5) - 1) << 5 | + esp_scale8(color.blue, (1 << 5) - 1); + default: + return color_to_565(color); } - return 0; } static uint32_t color_to_grayscale4(Color color) { uint32_t gs4 = esp_scale8(color.white, 15); @@ -196,23 +220,13 @@ constexpr uint8_t color_to_mono(Color color) { return rec601_luma(color) >= 128 */ class Rgb565ToMono { public: + Rgb565ToMono() { this->fill_tables_(REC601_LUMA_R, REC601_LUMA_G, REC601_LUMA_B); } + // Remove before 2027.5.0 + ESPDEPRECATED("Packed colors are always RGB, drop the ColorOrder argument. Removed in 2027.5.0", "2026.11.0") explicit Rgb565ToMono(ColorOrder order) { - // Rec.601 weight of the 5-bit, 6-bit and 5-bit fields, by the channel each one holds in this order - uint16_t high = REC601_LUMA_R, middle = REC601_LUMA_G, low = REC601_LUMA_B; - if (order == COLOR_ORDER_BGR) { - high = REC601_LUMA_B; - low = REC601_LUMA_R; - } else if (order == COLOR_ORDER_GRB) { - high = REC601_LUMA_G; - middle = REC601_LUMA_R; - } - // Each field value scaled to 0..255, then weighted - for (unsigned i = 0; i != 32; i++) { - this->high_[i] = high * esp_scale(i, 31); - this->low_[i] = low * esp_scale(i, 31); - } - for (unsigned i = 0; i != 64; i++) - this->middle_[i] = middle * esp_scale(i, 63); + // The weight of the channel each 5, 6 and 5 bit field holds in this order + const Color weights = apply_color_order(Color(REC601_LUMA_R, REC601_LUMA_G, REC601_LUMA_B), order); + this->fill_tables_(weights.r, weights.g, weights.b); } uint8_t operator()(uint16_t value) const { @@ -221,6 +235,16 @@ class Rgb565ToMono { } protected: + // Each field value scaled to 0..255, then weighted + void fill_tables_(uint16_t high, uint16_t middle, uint16_t low) { + for (unsigned i = 0; i != 32; i++) { + this->high_[i] = high * esp_scale(i, 31); + this->low_[i] = low * esp_scale(i, 31); + } + for (unsigned i = 0; i != 64; i++) + this->middle_[i] = middle * esp_scale(i, 63); + } + // Weighted contribution of each value of each field std::array high_{}; std::array middle_{}; diff --git a/esphome/components/epaper_spi/epaper_spi.cpp b/esphome/components/epaper_spi/epaper_spi.cpp index d8ead388183..a0c90a0b686 100644 --- a/esphome/components/epaper_spi/epaper_spi.cpp +++ b/esphome/components/epaper_spi/epaper_spi.cpp @@ -399,7 +399,7 @@ void HOT write_rgb565_row(uint8_t *byte, uint8_t mask, int step, size_t row_widt // Each source column maps to one buffer coordinate and each source row to the other, each moving by 1 per pixel, so // the visible part of a block and its dirty area are worked out once for the whole block. void HOT EPaperBase::draw_rgb565_block_(uint8_t *buffer, 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) { + bool big_endian, int x_offset, int y_offset, int x_pad) { const bool swap = this->effective_transform_ & SWAP_XY; const bool mirror_x = this->effective_transform_ & MIRROR_X; const bool mirror_y = this->effective_transform_ & MIRROR_Y; @@ -432,7 +432,7 @@ void HOT EPaperBase::draw_rgb565_block_(uint8_t *buffer, int x_start, int y_star const int x_high = x_low + (swap ? rows : columns); const int y_high = y_low + (swap ? columns : rows); - const Rgb565ToMono to_mono(order); + const Rgb565ToMono to_mono; const size_t line_stride = x_offset + w + x_pad; const size_t row_width = this->row_width_; const int step = column_mirrored ? -1 : 1; @@ -464,16 +464,17 @@ void HOT EPaperBase::draw_rgb565_block_(uint8_t *buffer, int x_start, int y_star // Writes an RGB565 block of pixels, as LVGL sends them, straight into a 1 bit per pixel buffer that is one block (the // usual case): the same result as draw_pixel_at() for each pixel. Anything else goes through draw_pixel_at(). -void HOT EPaperBase::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order, - ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) { +void HOT EPaperBase::draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, + ColorBitness bitness, bool big_endian, int x_offset, int y_offset, + int x_pad) { size_t contiguous_length; uint8_t *contiguous = this->buffer_.get_span(0, contiguous_length); if (!this->buffer_is_mono_() || this->get_clipping().is_set() || bitness != COLOR_BITNESS_565 || contiguous_length != this->buffer_length_) { - Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, x_pad); + Display::draw_pixels_internal(x_start, y_start, w, h, ptr, bitness, big_endian, x_offset, y_offset, x_pad); return; } - this->draw_rgb565_block_(contiguous, x_start, y_start, w, h, ptr, order, big_endian, x_offset, y_offset, x_pad); + this->draw_rgb565_block_(contiguous, x_start, y_start, w, h, ptr, big_endian, x_offset, y_offset, x_pad); } void EPaperBase::dump_config() { diff --git a/esphome/components/epaper_spi/epaper_spi.h b/esphome/components/epaper_spi/epaper_spi.h index 37f9a1449a2..dccff3cbef1 100644 --- a/esphome/components/epaper_spi/epaper_spi.h +++ b/esphome/components/epaper_spi/epaper_spi.h @@ -111,14 +111,15 @@ class EPaperBase : public Display, int get_height() override { return this->effective_transform_ & SWAP_XY ? this->width_ : this->height_; } bool pixel_axes_swapped() const override { return this->effective_transform_ & SWAP_XY; } void draw_pixel_at(int x, int y, Color color) override; - void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order, - ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override; // Make the next update a full one. Applied when that update starts, so one in progress is not affected. void request_full_update() { this->full_update_requested_ = true; } // True from the start of an update until the display has refreshed and gone back to sleep. bool is_updating() const { return this->state_ != EPaperState::IDLE; } protected: + void draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorBitness bitness, + bool big_endian, int x_offset, int y_offset, int x_pad) override; + int get_height_internal() override { return this->height_; }; int get_width_internal() override { return this->width_; }; bool is_using_partial_update_() const { return this->full_update_every_ > 1; } @@ -135,8 +136,8 @@ class EPaperBase : public Display, bool init_buffer_(size_t buffer_length); void update_effective_transform_(); bool rotate_coordinates_(int &x, int &y); - void draw_rgb565_block_(uint8_t *buffer, 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); + void draw_rgb565_block_(uint8_t *buffer, int x_start, int y_start, int w, int h, const uint8_t *ptr, bool big_endian, + int x_offset, int y_offset, int x_pad); // Whether the buffer is a single plane of 1 bit per pixel, as draw_pixel_at() writes it bool buffer_is_mono_() const { return this->bits_per_pixel_ == 1 && this->planes_ == 1; } diff --git a/esphome/components/hub75/hub75.cpp b/esphome/components/hub75/hub75.cpp index 5f2149747e1..4edc1c548f6 100644 --- a/esphome/components/hub75/hub75.cpp +++ b/esphome/components/hub75/hub75.cpp @@ -130,8 +130,9 @@ void HOT HUB75Display::draw_pixel_at(int x, int y, Color color) { this->feed_wdt_per_pixel_(); } -void HOT HUB75Display::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order, - ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) { +void HOT HUB75Display::draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, + ColorBitness bitness, bool big_endian, int x_offset, int y_offset, + int x_pad) { if (!driver_) [[unlikely]] return; if (!this->enabled_) [[unlikely]] @@ -153,15 +154,13 @@ void HOT HUB75Display::draw_pixels_at(int x_start, int y_start, int w, int h, co format = Hub75PixelFormat::RGB888_32; bytes_per_pixel = 4; - // Map ESPHome ColorOrder to Hub75ColorOrder - // ESPHome ColorOrder is typically BGR for little-endian 32-bit - color_order = (order == ColorOrder::COLOR_ORDER_RGB) ? Hub75ColorOrder::RGB : Hub75ColorOrder::BGR; + color_order = Hub75ColorOrder::RGB; #elif LV_COLOR_DEPTH == 24 // 24-bit: 3 bytes per pixel, tightly packed format = Hub75PixelFormat::RGB888; bytes_per_pixel = 3; - // ESPHome RGB order 888 is b,g,r in memory, which hub75 calls BGR - color_order = (order == ColorOrder::COLOR_ORDER_RGB) ? Hub75ColorOrder::BGR : Hub75ColorOrder::RGB; + // 888 is b,g,r in memory, which hub75 calls BGR + color_order = Hub75ColorOrder::BGR; #else ESP_LOGE(TAG, "Unsupported LV_COLOR_DEPTH: %d", LV_COLOR_DEPTH); return; @@ -170,8 +169,8 @@ void HOT HUB75Display::draw_pixels_at(int x_start, int y_start, int w, int h, co // Non-LVGL mode: standard 24-bit RGB888 format = Hub75PixelFormat::RGB888; bytes_per_pixel = 3; - // ESPHome RGB order 888 is b,g,r in memory, which hub75 calls BGR - color_order = (order == ColorOrder::COLOR_ORDER_RGB) ? Hub75ColorOrder::BGR : Hub75ColorOrder::RGB; + // 888 is b,g,r in memory, which hub75 calls BGR + color_order = Hub75ColorOrder::BGR; #endif } else { ESP_LOGE(TAG, "Unsupported bitness: %d", static_cast(bitness)); diff --git a/esphome/components/hub75/hub75_component.h b/esphome/components/hub75/hub75_component.h index 122e8960fb0..ae31cadf83a 100644 --- a/esphome/components/hub75/hub75_component.h +++ b/esphome/components/hub75/hub75_component.h @@ -30,8 +30,6 @@ class HUB75Display final : public display::Display { display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; } void fill(Color color) override; void draw_pixel_at(int x, int y, Color color) override; - void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, - display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override; // Brightness control (runtime mutable) void set_brightness(uint8_t brightness); @@ -40,6 +38,9 @@ class HUB75Display final : public display::Display { display::DisplayRotation get_rotation() const override; protected: + void draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorBitness bitness, + bool big_endian, int x_offset, int y_offset, int x_pad) override; + // Display internal methods int get_width_internal() override { return this->driver_ != nullptr ? this->driver_->get_width() : 0; } int get_height_internal() override { return this->driver_ != nullptr ? this->driver_->get_height() : 0; } diff --git a/esphome/components/ili9xxx/ili9xxx_display.cpp b/esphome/components/ili9xxx/ili9xxx_display.cpp index d2e1bc127e5..379c45f6737 100644 --- a/esphome/components/ili9xxx/ili9xxx_display.cpp +++ b/esphome/components/ili9xxx/ili9xxx_display.cpp @@ -162,7 +162,7 @@ void ILI9XXXDisplay::fill(Color color) { } return; default: - new_color = display::ColorUtil::color_to_332(color, display::ColorOrder::COLOR_ORDER_RGB); + new_color = display::ColorUtil::color_to_332(color); break; } memset(this->buffer_, (uint8_t) new_color, this->get_buffer_length_()); @@ -183,7 +183,7 @@ void HOT ILI9XXXDisplay::draw_absolute_pixel_internal(int x, int y, Color color) break; case BITS_16: pos = pos * 2; - new_color = display::ColorUtil::color_to_565(color, display::ColorOrder::COLOR_ORDER_RGB); + new_color = display::ColorUtil::color_to_565(color); if (this->buffer_[pos] != (uint8_t) (new_color >> 8)) { this->buffer_[pos] = (uint8_t) (new_color >> 8); updated = true; @@ -192,7 +192,7 @@ void HOT ILI9XXXDisplay::draw_absolute_pixel_internal(int x, int y, Color color) new_color = new_color & 0xFF; break; default: - new_color = display::ColorUtil::color_to_332(color, display::ColorOrder::COLOR_ORDER_RGB); + new_color = display::ColorUtil::color_to_332(color); break; } @@ -315,19 +315,17 @@ void ILI9XXXDisplay::display_() { // Outside update() this bypasses the buffer and writes directly to the display. Inside it the block goes into the // buffer, or the flush at the end of update() would paint the old buffer contents over it. -void ILI9XXXDisplay::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, - display::ColorOrder order, display::ColorBitness bitness, bool big_endian, - int x_offset, int y_offset, int x_pad) { +void ILI9XXXDisplay::draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, + display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, + int x_pad) { if (w <= 0 || h <= 0) return; // if color mapping or software rotation is required, hand this off to the parent implementation. This will // do color conversion pixel-by-pixel into the buffer and draw it later. If this is happening the user has not // configured the renderer well. if (this->rotation_ != display::DISPLAY_ROTATION_0_DEGREES || bitness != display::COLOR_BITNESS_565 || !big_endian || - order != display::COLOR_ORDER_RGB || this->is_clipping() || - (this->prossing_update_ && this->buffer_color_mode_ != BITS_16)) { - display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, - x_pad); + this->is_clipping() || (this->prossing_update_ && this->buffer_color_mode_ != BITS_16)) { + display::Display::draw_pixels_internal(x_start, y_start, w, h, ptr, bitness, big_endian, x_offset, y_offset, x_pad); return; } // x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display. diff --git a/esphome/components/ili9xxx/ili9xxx_display.h b/esphome/components/ili9xxx/ili9xxx_display.h index 77cee44ae68..4eabd02f3d5 100644 --- a/esphome/components/ili9xxx/ili9xxx_display.h +++ b/esphome/components/ili9xxx/ili9xxx_display.h @@ -92,10 +92,11 @@ class ILI9XXXDisplay : public display::DisplayBuffer, void on_powerdown() override { this->command(ILI9XXX_SLPIN); } display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; } - void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, - display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override; protected: + void draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorBitness bitness, + bool big_endian, int x_offset, int y_offset, int x_pad) override; + inline bool check_buffer_() { if (this->buffer_ == nullptr) { if (!this->is_failed()) diff --git a/esphome/components/image/image.cpp b/esphome/components/image/image.cpp index 60d81ad62f1..a38e84caaf9 100644 --- a/esphome/components/image/image.cpp +++ b/esphome/components/image/image.cpp @@ -59,8 +59,8 @@ void Image::draw(int x, int y, display::Display *display, Color color_on, Color return; 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, - this->big_endian_, x_start, y_start, this->width_ - x_end); + rgb565 ? display::COLOR_BITNESS_565 : display::COLOR_BITNESS_888, this->big_endian_, + x_start, y_start, this->width_ - x_end); return; } #endif diff --git a/esphome/components/it8951/it8951.cpp b/esphome/components/it8951/it8951.cpp index 3612b795b98..6b0a4b56094 100644 --- a/esphome/components/it8951/it8951.cpp +++ b/esphome/components/it8951/it8951.cpp @@ -959,8 +959,9 @@ void HOT IT8951Display::write_pixel_native_(uint16_t x, uint16_t y, const Color } } -void HOT IT8951Display::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order, - ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) { +void HOT IT8951Display::draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, + ColorBitness bitness, bool big_endian, int x_offset, int y_offset, + int x_pad) { // A writer (e.g. LVGL) may push pixels before the framebuffer is ready or // after an allocation failure; ignore those rather than dereferencing null. if (this->buffer_ == nullptr) @@ -968,7 +969,7 @@ void HOT IT8951Display::draw_pixels_at(int x_start, int y_start, int w, int h, c // A clipping rectangle would need a per-pixel test; that's rare for the bulk // blit callers (LVGL, images), so fall back to the base per-pixel path then. if (this->get_clipping().is_set()) { - Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, x_pad); + Display::draw_pixels_internal(x_start, y_start, w, h, ptr, bitness, big_endian, x_offset, y_offset, x_pad); return; } @@ -1000,7 +1001,7 @@ void HOT IT8951Display::draw_pixels_at(int x_start, int y_start, int w, int h, c if (nx < 0 || ny < 0 || nx >= this->width_ || ny >= this->height_) continue; this->write_pixel_native_(static_cast(nx), static_cast(ny), - ColorUtil::to_color(color_value, order, bitness)); + ColorUtil::to_color(color_value, bitness)); } } diff --git a/esphome/components/it8951/it8951.h b/esphome/components/it8951/it8951.h index e99e905ebf5..2b5f8d299fc 100644 --- a/esphome/components/it8951/it8951.h +++ b/esphome/components/it8951/it8951.h @@ -197,16 +197,17 @@ class IT8951Display : public Display, void fill(Color color) override; void clear() override { this->fill(Color::WHITE); } void draw_pixel_at(int x, int y, Color color) override; - // Bulk pixel blit (used by LVGL and image rendering). Overridden to write - // straight into the framebuffer, avoiding the base class's per-pixel - // draw_pixel_at overhead (watchdog feed, clipping test, dirty-box clamps). - void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order, - ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override; int get_width() override { return (this->effective_transform_ & TRANSFORM_SWAP_XY) ? this->height_ : this->width_; } int get_height() override { return (this->effective_transform_ & TRANSFORM_SWAP_XY) ? this->width_ : this->height_; } bool pixel_axes_swapped() const override { return this->effective_transform_ & TRANSFORM_SWAP_XY; } protected: + // Bulk pixel blit (used by LVGL and image rendering). Overridden to write + // straight into the framebuffer, avoiding the base class's per-pixel + // draw_pixel_at overhead (watchdog feed, clipping test, dirty-box clamps). + void draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorBitness bitness, + bool big_endian, int x_offset, int y_offset, int x_pad) override; + int get_height_internal() override { return this->height_; } int get_width_internal() override { return this->width_; } diff --git a/esphome/components/lvgl/lvgl_esphome.cpp b/esphome/components/lvgl/lvgl_esphome.cpp index 9bc83800051..e968e329dfb 100644 --- a/esphome/components/lvgl/lvgl_esphome.cpp +++ b/esphome/components/lvgl/lvgl_esphome.cpp @@ -439,8 +439,7 @@ void LvglComponent::draw_buffer_(const lv_area_t *area, lv_color_data *ptr) { ptr = dst; } for (auto *display : this->displays_) { - display->draw_pixels_at(x1, y1, width, height, (const uint8_t *) ptr, display::COLOR_ORDER_RGB, LV_BITNESS, - this->big_endian_); + display->draw_pixels_at(x1, y1, width, height, (const uint8_t *) ptr, LV_BITNESS, this->big_endian_); } } diff --git a/esphome/components/mipi_dsi/mipi_dsi.cpp b/esphome/components/mipi_dsi/mipi_dsi.cpp index 31bf69b178c..005087b719b 100644 --- a/esphome/components/mipi_dsi/mipi_dsi.cpp +++ b/esphome/components/mipi_dsi/mipi_dsi.cpp @@ -213,15 +213,14 @@ void MipiDsi::flush_dirty_() { this->y_high_ = 0; } -void MipiDsi::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, - display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) { +void MipiDsi::draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, + display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, + int x_pad) { if (w <= 0 || h <= 0) return; - if (bitness != this->color_depth_ || order != display::COLOR_ORDER_RGB || - (bitness == display::COLOR_BITNESS_565 && big_endian != this->big_endian_) || + if (bitness != this->color_depth_ || (bitness == display::COLOR_BITNESS_565 && big_endian != this->big_endian_) || this->rotation_ != display::DISPLAY_ROTATION_0_DEGREES || this->is_clipping()) { - display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, - x_pad); + display::Display::draw_pixels_internal(x_start, y_start, w, h, ptr, bitness, big_endian, x_offset, y_offset, x_pad); this->mark_dirty_(x_start, y_start, w, h); this->flush_dirty_(); return; diff --git a/esphome/components/mipi_dsi/mipi_dsi.h b/esphome/components/mipi_dsi/mipi_dsi.h index dd1b0dc5f90..9e7c562b54d 100644 --- a/esphome/components/mipi_dsi/mipi_dsi.h +++ b/esphome/components/mipi_dsi/mipi_dsi.h @@ -72,9 +72,6 @@ class MipiDsi final : public display::Display { void setup() override; - void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, - display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override; - void draw_pixel_at(int x, int y, Color color) override; void fill(Color color) override; int get_width() override; @@ -83,6 +80,9 @@ class MipiDsi final : public display::Display { void dump_config() override; protected: + void draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorBitness bitness, + bool big_endian, int x_offset, int y_offset, int x_pad) override; + void write_to_display_(int x_start, int y_start, int w, int h, const uint8_t *ptr, int x_offset, int y_offset, int x_pad); bool check_buffer_(); diff --git a/esphome/components/mipi_rgb/mipi_rgb.cpp b/esphome/components/mipi_rgb/mipi_rgb.cpp index 2bc300e5b82..a38404030ad 100644 --- a/esphome/components/mipi_rgb/mipi_rgb.cpp +++ b/esphome/components/mipi_rgb/mipi_rgb.cpp @@ -218,13 +218,14 @@ void MipiRgb::flush_dirty_() { this->y_high_ = 0; } -void MipiRgb::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, - display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) { +void MipiRgb::draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, + display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, + int x_pad) { if (w <= 0 || h <= 0 || this->is_failed()) return; - if (bitness != display::COLOR_BITNESS_565 || order != display::COLOR_ORDER_RGB || big_endian != this->big_endian_ || + if (bitness != display::COLOR_BITNESS_565 || big_endian != this->big_endian_ || this->rotation_ != display::DISPLAY_ROTATION_0_DEGREES || this->is_clipping()) { - Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, x_pad); + Display::draw_pixels_internal(x_start, y_start, w, h, ptr, bitness, big_endian, x_offset, y_offset, x_pad); this->mark_dirty_(x_start, y_start, w, h); this->flush_dirty_(); } else if (this->buffer_ != nullptr) { diff --git a/esphome/components/mipi_rgb/mipi_rgb.h b/esphome/components/mipi_rgb/mipi_rgb.h index 393e052b26b..f6aec76976b 100644 --- a/esphome/components/mipi_rgb/mipi_rgb.h +++ b/esphome/components/mipi_rgb/mipi_rgb.h @@ -36,8 +36,6 @@ class MipiRgb : public display::Display { #endif void update() override; void fill(Color color) override; - void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, - display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override; display::ColorOrder get_color_mode() { return this->color_mode_; } void set_color_mode(display::ColorOrder color_mode) { this->color_mode_ = color_mode; } @@ -69,8 +67,11 @@ class MipiRgb : public display::Display { void dump_config() override; void draw_pixel_at(int x, int y, Color color) override; - // this will be horribly slow. protected: + void draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorBitness bitness, + bool big_endian, int x_offset, int y_offset, int x_pad) override; + + // this will be horribly slow. void write_to_display_(int x_start, int y_start, int w, int h, const uint8_t *ptr, int x_offset, int y_offset, int x_pad); bool check_buffer_(); diff --git a/esphome/components/mipi_spi/mipi_spi.h b/esphome/components/mipi_spi/mipi_spi.h index cbf025e8c31..f8fecf2e4ea 100644 --- a/esphome/components/mipi_spi/mipi_spi.h +++ b/esphome/components/mipi_spi/mipi_spi.h @@ -199,22 +199,6 @@ class MipiSpi : public display::Display, this->init_sequence_len_ = 0; } - // Drawing operations - - void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, - display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override { - if (this->is_failed()) - return; - if (w <= 0 || h <= 0) - return; - if (!this->is_native_block_(order, bitness, big_endian)) { - this->draw_pixels_slow(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, - x_pad); - } - void dump_config() override { internal_dump_config(this->model_, this->get_width(), this->get_height(), this->get_offset_width_(), this->get_offset_height_(), (uint8_t) MADCTL, this->invert_colors_, DISPLAYPIXEL * 8, @@ -223,6 +207,22 @@ class MipiSpi : public display::Display, } protected: + // Drawing operations + + void draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorBitness bitness, + bool big_endian, int x_offset, int y_offset, int x_pad) override { + if (this->is_failed()) + return; + if (w <= 0 || h <= 0) + return; + if (!this->is_native_block_(bitness, big_endian)) { + this->draw_pixels_slow(x_start, y_start, w, h, ptr, 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, + x_pad); + } + /* METHODS */ // If hardware rotation is in use, the actual display width/height changes with rotation int get_width_internal() override { @@ -421,15 +421,15 @@ class MipiSpi : public display::Display, } // True when the block is already in the buffer pixel format and can be copied without per pixel work - bool is_native_block_(display::ColorOrder order, display::ColorBitness bitness, bool big_endian) const { - return get_pixel_mode(bitness) == BUFFERPIXEL && big_endian == IS_BIG_ENDIAN && order == display::COLOR_ORDER_RGB && + bool is_native_block_(display::ColorBitness bitness, bool big_endian) const { + return get_pixel_mode(bitness) == BUFFERPIXEL && big_endian == IS_BIG_ENDIAN && (HAS_HARDWARE_ROTATION || this->rotation_ == display::DISPLAY_ROTATION_0_DEGREES) && !this->is_clipping(); } // Without a buffer there is nothing to convert a foreign block into, so it is dropped - virtual void draw_pixels_slow(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, + virtual void draw_pixels_slow(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) { - esph_log_e(TAG, "Unsupported color depth, order, bit order or rotation"); + esph_log_e(TAG, "Unsupported color depth, bit order or rotation"); } /** @@ -625,42 +625,6 @@ class MipiSpiBuffer #endif } - // While update() runs the writer, a block goes into the buffer slice, or the slice flush would paint over it. - // Outside update() (e.g. LVGL) it is written straight to the display. - void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, - display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override { - if (!this->updating_) { - MipiSpiBuffer::MipiSpi::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, - y_offset, x_pad); - return; - } - if (this->is_failed() || w <= 0 || h <= 0) - return; - if (!this->is_native_block_(order, bitness, big_endian)) { - display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, - x_pad); - return; - } - // Only the rows inside the current slice are kept - const int first_col = std::max(0, -x_start); - const int end_col = std::min(w, this->get_width_internal() - x_start); - const int first_row = std::max(0, this->start_line_ - y_start); - const int end_row = std::min(h, this->end_line_ - y_start); - if (first_col >= end_col || first_row >= end_row) - return; - const size_t stride = x_offset + w + x_pad; - const size_t buffer_stride = round_buffer(this->get_width_internal()); - for (int y = first_row; y != end_row; y++) { - memcpy(this->buffer_ + (y_start + y - this->start_line_) * buffer_stride + x_start + first_col, - ptr + ((y_offset + y) * stride + x_offset + first_col) * sizeof(BUFFERTYPE), - (end_col - first_col) * sizeof(BUFFERTYPE)); - } - this->x_low_ = std::min(this->x_low_, x_start + first_col); - this->y_low_ = std::min(this->y_low_, y_start + first_row); - this->x_high_ = std::max(this->x_high_, x_start + end_col - 1); - this->y_high_ = std::max(this->y_high_, y_start + end_row - 1); - } - // With hardware rotation the buffer is written in drawn coordinates. bool pixel_axes_swapped() const override { return !HAS_HARDWARE_ROTATION && display::Display::pixel_axes_swapped(); } @@ -716,11 +680,45 @@ class MipiSpiBuffer } protected: - void draw_pixels_slow(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, - display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, - int x_pad) override { - display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, - x_pad); + // While update() runs the writer, a block goes into the buffer slice, or the slice flush would paint over it. + // Outside update() (e.g. LVGL) it is written straight to the display. + void draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorBitness bitness, + bool big_endian, int x_offset, int y_offset, int x_pad) override { + if (!this->updating_) { + MipiSpiBuffer::MipiSpi::draw_pixels_internal(x_start, y_start, w, h, ptr, bitness, big_endian, x_offset, y_offset, + x_pad); + return; + } + if (this->is_failed() || w <= 0 || h <= 0) + return; + if (!this->is_native_block_(bitness, big_endian)) { + display::Display::draw_pixels_internal(x_start, y_start, w, h, ptr, bitness, big_endian, x_offset, y_offset, + x_pad); + return; + } + // Only the rows inside the current slice are kept + const int first_col = std::max(0, -x_start); + const int end_col = std::min(w, this->get_width_internal() - x_start); + const int first_row = std::max(0, this->start_line_ - y_start); + const int end_row = std::min(h, this->end_line_ - y_start); + if (first_col >= end_col || first_row >= end_row) + return; + const size_t stride = x_offset + w + x_pad; + const size_t buffer_stride = round_buffer(this->get_width_internal()); + for (int y = first_row; y != end_row; y++) { + memcpy(this->buffer_ + (y_start + y - this->start_line_) * buffer_stride + x_start + first_col, + ptr + ((y_offset + y) * stride + x_offset + first_col) * sizeof(BUFFERTYPE), + (end_col - first_col) * sizeof(BUFFERTYPE)); + } + this->x_low_ = std::min(this->x_low_, x_start + first_col); + this->y_low_ = std::min(this->y_low_, y_start + first_row); + this->x_high_ = std::max(this->x_high_, x_start + end_col - 1); + this->y_high_ = std::max(this->y_high_, y_start + end_row - 1); + } + + void draw_pixels_slow(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorBitness bitness, + bool big_endian, int x_offset, int y_offset, int x_pad) override { + display::Display::draw_pixels_internal(x_start, y_start, w, h, ptr, bitness, big_endian, x_offset, y_offset, x_pad); } // Convert a color to the buffer pixel format. static BUFFERTYPE convert_color(const Color &color) { diff --git a/esphome/components/pixoo/pixoo.cpp b/esphome/components/pixoo/pixoo.cpp index 7e9b0bd89bd..f6ccdef27a5 100644 --- a/esphome/components/pixoo/pixoo.cpp +++ b/esphome/components/pixoo/pixoo.cpp @@ -1,4 +1,5 @@ #include "pixoo.h" +#include "esphome/components/display/display_color_utils.h" #include "esphome/core/log.h" @@ -144,17 +145,16 @@ void HOT Pixoo::draw_pixel_at(int x, int y, Color color) { this->set_pixel_(static_cast(y) * side + x, color); } -void Pixoo::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, - display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) { - // Fast path for the common LVGL/image blit: RGB565, RGB order, no rotation, no active clipping. +// flatten keeps the RGB565 decode inlined in the pixel loop, as in Display's own per pixel loop. +void __attribute__((flatten)) +Pixoo::draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorBitness bitness, + bool big_endian, int x_offset, int y_offset, int x_pad) { + // Fast path for the common LVGL/image blit: RGB565, no rotation, no active clipping. // Anything else defers to the base implementation, which decodes per pixel and routes through // draw_pixel_at() so rotation, clipping and other color formats stay correct. - // NOTE: the stride/index math and 565->888 expansion below mirror Display::draw_pixels_at (the - // source of truth) -- keep them in sync if the base ever changes its source layout or decoding. - if (bitness != display::COLOR_BITNESS_565 || order != display::COLOR_ORDER_RGB || - this->rotation_ != display::DISPLAY_ROTATION_0_DEGREES || this->is_clipping()) { - display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, - x_pad); + if (bitness != display::COLOR_BITNESS_565 || this->rotation_ != display::DISPLAY_ROTATION_0_DEGREES || + this->is_clipping()) { + display::Display::draw_pixels_internal(x_start, y_start, w, h, ptr, bitness, big_endian, x_offset, y_offset, x_pad); return; } const int side = static_cast(this->model_); @@ -168,14 +168,10 @@ void Pixoo::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t const int dst_x = x_start + x; if (dst_x < 0 || dst_x >= side) continue; - const size_t byte_idx = source_idx * 2; - const uint16_t rgb565 = - big_endian ? (ptr[byte_idx] << 8) | ptr[byte_idx + 1] : ptr[byte_idx] | (ptr[byte_idx + 1] << 8); - const uint8_t r5 = (rgb565 >> 11) & 0x1F; - const uint8_t g6 = (rgb565 >> 5) & 0x3F; - const uint8_t b5 = rgb565 & 0x1F; this->set_pixel_(static_cast(dst_y) * side + dst_x, - Color((r5 << 3) | (r5 >> 2), (g6 << 2) | (g6 >> 4), (b5 << 3) | (b5 >> 2))); + display::ColorUtil::to_color( + display::ColorUtil::read_packed(ptr, source_idx, big_endian), + display::COLOR_BITNESS_565)); } } } diff --git a/esphome/components/pixoo/pixoo.h b/esphome/components/pixoo/pixoo.h index 4913ef85db9..e8c8efbae73 100644 --- a/esphome/components/pixoo/pixoo.h +++ b/esphome/components/pixoo/pixoo.h @@ -38,10 +38,11 @@ class Pixoo : public display::Display, void fill(Color color) override; void draw_pixel_at(int x, int y, Color color) override; - void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, - display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override; protected: + void draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorBitness bitness, + bool big_endian, int x_offset, int y_offset, int x_pad) override; + int get_width_internal() override { return static_cast(this->model_); } int get_height_internal() override { return static_cast(this->model_); } diff --git a/esphome/components/qspi_dbi/qspi_dbi.cpp b/esphome/components/qspi_dbi/qspi_dbi.cpp index ce0df1beb50..8387652d31e 100644 --- a/esphome/components/qspi_dbi/qspi_dbi.cpp +++ b/esphome/components/qspi_dbi/qspi_dbi.cpp @@ -72,7 +72,7 @@ void QspiDbi::draw_absolute_pixel_internal(int x, int y, Color color) { uint32_t pos = (y * this->width_) + x; bool updated = false; pos = pos * 2; - uint16_t new_color = display::ColorUtil::color_to_565(color, display::ColorOrder::COLOR_ORDER_RGB); + uint16_t new_color = display::ColorUtil::color_to_565(color); if (this->buffer_[pos] != static_cast(new_color >> 8)) { this->buffer_[pos] = static_cast(new_color >> 8); updated = true; @@ -137,20 +137,20 @@ void QspiDbi::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y this->write_command_(CASET, buf, sizeof buf); } -void QspiDbi::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, - display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) { +void QspiDbi::draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, + display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, + int x_pad) { if (!this->setup_complete_ || this->is_failed()) return; if (w <= 0 || h <= 0) return; - if (bitness != display::COLOR_BITNESS_565 || order != this->color_mode_ || - big_endian != (this->bit_order_ == spi::BIT_ORDER_MSB_FIRST) || + if (bitness != display::COLOR_BITNESS_565 || big_endian != (this->bit_order_ == spi::BIT_ORDER_MSB_FIRST) || this->rotation_ != display::DISPLAY_ROTATION_0_DEGREES || this->is_clipping()) { - Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, x_pad); + Display::draw_pixels_internal(x_start, y_start, w, h, ptr, bitness, big_endian, x_offset, y_offset, x_pad); return; } else if (this->updating_) { // Inside update() the block goes into the buffer, or the flush at its end would paint over it - this->copy_to_buffer_(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, x_pad); + this->copy_to_buffer_(x_start, y_start, w, h, ptr, bitness, big_endian, x_offset, y_offset, x_pad); return; } else if (this->draw_from_origin_) { auto stride = x_offset + w + x_pad; @@ -169,11 +169,11 @@ void QspiDbi::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8 this->write_to_display_(x_start, y_start, w, h, ptr, x_offset, y_offset, x_pad); } -void QspiDbi::copy_to_buffer_(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, - display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) { +void QspiDbi::copy_to_buffer_(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorBitness bitness, + bool big_endian, int x_offset, int y_offset, int x_pad) { // The buffer holds big endian RGB565 - if (!big_endian || order != display::COLOR_ORDER_RGB) { - Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, x_pad); + if (!big_endian) { + Display::draw_pixels_internal(x_start, y_start, w, h, ptr, bitness, big_endian, x_offset, y_offset, x_pad); return; } const int first_col = std::max(0, -x_start), end_col = std::min(w, static_cast(this->width_) - x_start); diff --git a/esphome/components/qspi_dbi/qspi_dbi.h b/esphome/components/qspi_dbi/qspi_dbi.h index 1c2565625b2..2d7c4572ca9 100644 --- a/esphome/components/qspi_dbi/qspi_dbi.h +++ b/esphome/components/qspi_dbi/qspi_dbi.h @@ -112,12 +112,12 @@ class QspiDbi final : public display::DisplayBuffer, } void write_sequence_(const std::vector &vec); void draw_absolute_pixel_internal(int x, int y, Color color) override; - void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, - display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override; + void draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorBitness bitness, + bool big_endian, int x_offset, int y_offset, int x_pad) override; void write_to_display_(int x_start, int y_start, int w, int h, const uint8_t *ptr, int x_offset, int y_offset, int x_pad); - void copy_to_buffer_(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, - display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad); + void copy_to_buffer_(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorBitness bitness, + bool big_endian, int x_offset, int y_offset, int x_pad); /** * the RM67162 in quad SPI mode seems to work like this (not in the datasheet, this is deduced from the * sample code.) diff --git a/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp index 9f92a850f55..1e248780414 100644 --- a/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp +++ b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp @@ -47,14 +47,14 @@ void RpiDpiRgb::loop() { esp_lcd_rgb_panel_restart(this->handle_); } -void RpiDpiRgb::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, - display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) { +void RpiDpiRgb::draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, + display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, + int x_pad) { if (w <= 0 || h <= 0) return; - if (bitness != display::COLOR_BITNESS_565 || order != display::COLOR_ORDER_RGB || !big_endian || - this->rotation_ != display::DISPLAY_ROTATION_0_DEGREES || this->is_clipping()) { - display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, - x_pad); + if (bitness != display::COLOR_BITNESS_565 || !big_endian || this->rotation_ != display::DISPLAY_ROTATION_0_DEGREES || + this->is_clipping()) { + display::Display::draw_pixels_internal(x_start, y_start, w, h, ptr, bitness, big_endian, x_offset, y_offset, x_pad); return; } this->write_to_display_(x_start, y_start, w, h, ptr, x_offset, y_offset, x_pad); diff --git a/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.h b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.h index 5f1f3be0191..3c4d7f14e9b 100644 --- a/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.h +++ b/esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.h @@ -24,8 +24,6 @@ class RpiDpiRgb final : public display::Display { void setup() override; void loop() override; float get_setup_priority() const override { return setup_priority::HARDWARE; } - void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, - display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override; void draw_pixel_at(int x, int y, Color color) override; display::ColorOrder get_color_mode() { return this->color_mode_; } @@ -62,6 +60,9 @@ class RpiDpiRgb final : public display::Display { void dump_config() override; protected: + void draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorBitness bitness, + bool big_endian, int x_offset, int y_offset, int x_pad) override; + void write_to_display_(int x_start, int y_start, int w, int h, const uint8_t *ptr, int x_offset, int y_offset, int x_pad); int get_width_internal() override { return this->width_; } diff --git a/esphome/components/sdl/sdl_esphome.cpp b/esphome/components/sdl/sdl_esphome.cpp index 5dd25ec2bb8..e98cf3da482 100644 --- a/esphome/components/sdl/sdl_esphome.cpp +++ b/esphome/components/sdl/sdl_esphome.cpp @@ -148,13 +148,13 @@ void Sdl::redraw_(SDL_Rect &rect) { SDL_RenderPresent(this->renderer_); } -void Sdl::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, - display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) { +void Sdl::draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, + display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) { if (this->texture_ == nullptr) return; SDL_Rect rect{x_start, y_start, w, h}; if (this->rotation_ != display::DISPLAY_ROTATION_0_DEGREES || bitness != display::COLOR_BITNESS_565 || big_endian) { - Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, x_pad); + Display::draw_pixels_internal(x_start, y_start, w, h, ptr, bitness, big_endian, x_offset, y_offset, x_pad); } else { auto stride = x_offset + w + x_pad; const auto *data = ptr + (stride * y_offset + x_offset) * 2; @@ -181,7 +181,7 @@ void Sdl::draw_pixel_at(int x, int y, Color color) { } SDL_Rect rect{x, y, 1, 1}; - auto data = (display::ColorUtil::color_to_565(color, display::COLOR_ORDER_RGB)); + auto data = (display::ColorUtil::color_to_565(color)); SDL_UpdateTexture(this->texture_, &rect, &data, 2); if (x < this->x_low_) this->x_low_ = x; diff --git a/esphome/components/sdl/sdl_esphome.h b/esphome/components/sdl/sdl_esphome.h index c927004e1f6..e46a64acdc2 100644 --- a/esphome/components/sdl/sdl_esphome.h +++ b/esphome/components/sdl/sdl_esphome.h @@ -21,8 +21,6 @@ class Sdl final : public display::Display, public snapshot::Snapshot { void update() override; void loop() override; void setup() override; - void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, - display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override; void draw_pixel_at(int x, int y, Color color) override; void process_key(uint32_t keycode, bool down); void set_dimensions(uint16_t width, uint16_t height) { @@ -53,6 +51,9 @@ class Sdl final : public display::Display, public snapshot::Snapshot { bool mouse_down{}; protected: + void draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorBitness bitness, + bool big_endian, int x_offset, int y_offset, int x_pad) override; + int get_width_internal() override { return this->width_; } int get_height_internal() override { return this->height_; } void redraw_(SDL_Rect &rect); diff --git a/esphome/components/snapshot/display/snapshot_display.cpp b/esphome/components/snapshot/display/snapshot_display.cpp index 6618d9ae74a..301e2bd47ba 100644 --- a/esphome/components/snapshot/display/snapshot_display.cpp +++ b/esphome/components/snapshot/display/snapshot_display.cpp @@ -34,12 +34,12 @@ void SnapshotDisplay::dump_config() { LOG_DISPLAY("", "Snapshot", this); } void SnapshotDisplay::draw_absolute_pixel_internal(int x, int y, Color color) { if (this->buffer_ == nullptr || x < 0 || x >= this->width_ || y < 0 || y >= this->height_) return; - this->pixels_()[y * this->width_ + x] = display::ColorUtil::color_to_565(color, display::COLOR_ORDER_RGB); + this->pixels_()[y * this->width_ + x] = display::ColorUtil::color_to_565(color); } -void SnapshotDisplay::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, - display::ColorOrder order, display::ColorBitness bitness, bool big_endian, - int x_offset, int y_offset, int x_pad) { +void SnapshotDisplay::draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, + display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, + int x_pad) { if (this->buffer_ == nullptr) return; // Anything that is not already laid out the way the buffer is, or that would reach outside it, @@ -48,7 +48,7 @@ void SnapshotDisplay::draw_pixels_at(int x_start, int y_start, int w, int h, con bitness == display::COLOR_BITNESS_565 && !big_endian && x_start >= 0 && y_start >= 0 && x_start + w <= this->width_ && y_start + h <= this->height_; if (!copyable) { - DisplayBuffer::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, x_pad); + DisplayBuffer::draw_pixels_internal(x_start, y_start, w, h, ptr, bitness, big_endian, x_offset, y_offset, x_pad); return; } const size_t stride = static_cast(x_offset) + w + x_pad; diff --git a/esphome/components/snapshot/display/snapshot_display.h b/esphome/components/snapshot/display/snapshot_display.h index 5317bc6058b..31e711c3bf5 100644 --- a/esphome/components/snapshot/display/snapshot_display.h +++ b/esphome/components/snapshot/display/snapshot_display.h @@ -23,10 +23,10 @@ class SnapshotDisplay final : public display::DisplayBuffer, public Snapshot { this->height_ = height; } - void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, - display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override; - protected: + void draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorBitness bitness, + bool big_endian, int x_offset, int y_offset, int x_pad) override; + void draw_absolute_pixel_internal(int x, int y, Color color) override; int get_width_internal() override { return this->width_; } int get_height_internal() override { return this->height_; } diff --git a/esphome/components/st7701s/st7701s.cpp b/esphome/components/st7701s/st7701s.cpp index 756473dae71..6bffafe7389 100644 --- a/esphome/components/st7701s/st7701s.cpp +++ b/esphome/components/st7701s/st7701s.cpp @@ -50,14 +50,14 @@ void ST7701S::loop() { esp_lcd_rgb_panel_restart(this->handle_); } -void ST7701S::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, - display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) { +void ST7701S::draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, + display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, + int x_pad) { if (w <= 0 || h <= 0) return; - if (bitness != display::COLOR_BITNESS_565 || order != display::COLOR_ORDER_RGB || !big_endian || - this->rotation_ != display::DISPLAY_ROTATION_0_DEGREES || this->is_clipping()) { - display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, - x_pad); + if (bitness != display::COLOR_BITNESS_565 || !big_endian || this->rotation_ != display::DISPLAY_ROTATION_0_DEGREES || + this->is_clipping()) { + display::Display::draw_pixels_internal(x_start, y_start, w, h, ptr, bitness, big_endian, x_offset, y_offset, x_pad); return; } this->write_to_display_(x_start, y_start, w, h, ptr, x_offset, y_offset, x_pad); diff --git a/esphome/components/st7701s/st7701s.h b/esphome/components/st7701s/st7701s.h index e34b980e73b..181a580ada7 100644 --- a/esphome/components/st7701s/st7701s.h +++ b/esphome/components/st7701s/st7701s.h @@ -34,8 +34,6 @@ class ST7701S final : public display::Display, void update() override { this->do_update_(); } void setup() override; void loop() override; - void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, - display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override; display::ColorOrder get_color_mode() { return this->color_mode_; } void set_color_mode(display::ColorOrder color_mode) { this->color_mode_ = color_mode; } @@ -76,8 +74,11 @@ class ST7701S final : public display::Display, void dump_config() override; void draw_pixel_at(int x, int y, Color color) override; - // this will be horribly slow. protected: + void draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorBitness bitness, + bool big_endian, int x_offset, int y_offset, int x_pad) override; + + // this will be horribly slow. void write_to_display_(int x_start, int y_start, int w, int h, const uint8_t *ptr, int x_offset, int y_offset, int x_pad); void write_command_(uint8_t value); diff --git a/esphome/components/st7735/st7735.cpp b/esphome/components/st7735/st7735.cpp index 94b94d17a98..c8b10fd8318 100644 --- a/esphome/components/st7735/st7735.cpp +++ b/esphome/components/st7735/st7735.cpp @@ -449,8 +449,8 @@ void HOT ST7735::write_display_data_() { if (this->eightbitcolor_) { for (size_t line = 0; line < this->get_buffer_length(); line = line + this->get_width_internal()) { for (int index = 0; index < this->get_width_internal(); ++index) { - auto color332 = display::ColorUtil::to_color(this->buffer_[index + line], display::ColorOrder::COLOR_ORDER_RGB, - display::ColorBitness::COLOR_BITNESS_332, true); + auto color332 = + display::ColorUtil::to_color(this->buffer_[index + line], display::ColorBitness::COLOR_BITNESS_332, true); auto color = display::ColorUtil::color_to_565(color332); diff --git a/esphome/components/st7789v/st7789v.cpp b/esphome/components/st7789v/st7789v.cpp index 343d196b507..84b27251053 100644 --- a/esphome/components/st7789v/st7789v.cpp +++ b/esphome/components/st7789v/st7789v.cpp @@ -182,8 +182,7 @@ void ST7789V::write_display_data() { for (size_t line = 0; line < this->get_buffer_length_(); line += width) { for (size_t index = 0; index < width; ++index) { auto color = display::ColorUtil::color_to_565( - display::ColorUtil::to_color(this->buffer_[index + line], display::ColorOrder::COLOR_ORDER_RGB, - display::ColorBitness::COLOR_BITNESS_332, true)); + display::ColorUtil::to_color(this->buffer_[index + line], display::ColorBitness::COLOR_BITNESS_332, true)); temp_buffer[temp_index++] = (uint8_t) (color >> 8); temp_buffer[temp_index++] = (uint8_t) color; if (temp_index == TEMP_BUFFER_SIZE) { diff --git a/tests/benchmarks/components/display/bench_draw_pixels_at.cpp b/tests/benchmarks/components/display/bench_draw_pixels_at.cpp index da96dd83b9a..9469011cf68 100644 --- a/tests/benchmarks/components/display/bench_draw_pixels_at.cpp +++ b/tests/benchmarks/components/display/bench_draw_pixels_at.cpp @@ -47,7 +47,7 @@ static void draw_block(benchmark::State &state, ColorBitness bitness, bool big_e BenchDisplay display; display.set_rotation(rotation); for (auto _ : state) { - display.draw_pixels_at(0, 0, kWidth, kHeight, data.get(), COLOR_ORDER_RGB, bitness, big_endian, 0, 0, 0); + display.draw_pixels_at(0, 0, kWidth, kHeight, data.get(), bitness, big_endian, 0, 0, 0); } state.SetItemsProcessed(state.iterations() * kWidth * kHeight); } diff --git a/tests/benchmarks/components/image/bench_image_draw.cpp b/tests/benchmarks/components/image/bench_image_draw.cpp index 6bdd0acffbc..63e2d8bac6f 100644 --- a/tests/benchmarks/components/image/bench_image_draw.cpp +++ b/tests/benchmarks/components/image/bench_image_draw.cpp @@ -42,10 +42,10 @@ class BenchDisplay : public display::DisplayBuffer { class BulkBenchDisplay : public BenchDisplay { public: BulkBenchDisplay() : frame565_(std::make_unique(kStride * kStride)) {} - void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, - display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override { + void draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorBitness bitness, + bool big_endian, int x_offset, int y_offset, int x_pad) override { if (bitness != display::COLOR_BITNESS_565 || big_endian) { - BenchDisplay::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, x_pad); + BenchDisplay::draw_pixels_internal(x_start, y_start, w, h, ptr, bitness, big_endian, x_offset, y_offset, x_pad); return; } const size_t stride = x_offset + w + x_pad; diff --git a/tests/components/display/test_draw_pixels_at.cpp b/tests/components/display/test_draw_pixels_at.cpp index 1278e2462df..3fbf76dd713 100644 --- a/tests/components/display/test_draw_pixels_at.cpp +++ b/tests/components/display/test_draw_pixels_at.cpp @@ -52,8 +52,8 @@ static void expect_walks_match(ColorBitness bitness, int bytes_per_pixel, bool b RecordingDisplay rows; RecordingDisplay columns; columns.set_axes_swapped(true); - rows.draw_pixels_at(10, 20, W, H, source.data(), COLOR_ORDER_RGB, bitness, big_endian, X_OFFSET, Y_OFFSET, X_PAD); - columns.draw_pixels_at(10, 20, W, H, source.data(), COLOR_ORDER_RGB, bitness, big_endian, X_OFFSET, Y_OFFSET, X_PAD); + rows.draw_pixels_at(10, 20, W, H, source.data(), bitness, big_endian, X_OFFSET, Y_OFFSET, X_PAD); + columns.draw_pixels_at(10, 20, W, H, source.data(), bitness, big_endian, X_OFFSET, Y_OFFSET, X_PAD); EXPECT_EQ(rows.count(), static_cast(W * H)); EXPECT_EQ(columns.count(), static_cast(W * H)); EXPECT_EQ(rows.sorted_pixels(), columns.sorted_pixels()); @@ -69,7 +69,7 @@ TEST(DrawPixelsAt, ColumnWalkMatchesRowWalk888) { expect_walks_match(COLOR_BITNE static void expect_golden(ColorBitness bitness, std::initializer_list bytes, bool big_endian, Color expected) { const std::vector source(bytes); RecordingDisplay display; - display.draw_pixels_at(3, 4, 1, 1, source.data(), COLOR_ORDER_RGB, bitness, big_endian, 0, 0, 0); + display.draw_pixels_at(3, 4, 1, 1, source.data(), bitness, big_endian, 0, 0, 0); ASSERT_EQ(display.count(), 1u); EXPECT_EQ(display.pixels()[0], std::make_tuple(3, 4, expected.raw_32)); } @@ -97,13 +97,160 @@ TEST(DrawPixelsAt, ColumnWalkChangesOrder) { RecordingDisplay rows; RecordingDisplay columns; columns.set_axes_swapped(true); - rows.draw_pixels_at(0, 0, W, H, source.data(), COLOR_ORDER_RGB, COLOR_BITNESS_332, true, X_OFFSET, Y_OFFSET, X_PAD); - columns.draw_pixels_at(0, 0, W, H, source.data(), COLOR_ORDER_RGB, COLOR_BITNESS_332, true, X_OFFSET, Y_OFFSET, - X_PAD); + rows.draw_pixels_at(0, 0, W, H, source.data(), COLOR_BITNESS_332, true, X_OFFSET, Y_OFFSET, X_PAD); + columns.draw_pixels_at(0, 0, W, H, source.data(), COLOR_BITNESS_332, true, X_OFFSET, Y_OFFSET, X_PAD); ASSERT_GT(rows.count(), 1u); ASSERT_GT(columns.count(), 1u); EXPECT_EQ(std::get<0>(rows.pixels()[1]), 1); // the row walk steps x first EXPECT_EQ(std::get<1>(columns.pixels()[1]), 1); // the column walk steps y first } +// The multiply and shift field expansion gives exactly what dividing with esp_scale() does. +TEST(ColorUtil, ExpandFieldMatchesEspScale) { + // 2, 3, 5, 6 and 8 have a multiply and shift; the others fall back to esp_scale() + for (const uint8_t bits : {1, 2, 3, 4, 5, 6, 7, 8}) { + const uint8_t max = (1 << bits) - 1; + for (uint32_t value = 0; value <= max; value++) + EXPECT_EQ(expand_field(value, bits), esp_scale(value, max)) << "bits " << int(bits) << " value " << value; + } +} + +// to_color() decodes every 565 and 332 value as the per channel esp_scale() arithmetic does. +TEST(ColorUtil, ToColorMatchesEspScale) { + for (uint32_t value = 0; value != 0x10000; value++) { + const Color expected(esp_scale((value >> 11) & 0x1F, 31), esp_scale((value >> 5) & 0x3F, 63), + esp_scale(value & 0x1F, 31)); + ASSERT_EQ(ColorUtil::to_color(value, COLOR_BITNESS_565).raw_32, expected.raw_32) << "565 value " << value; + } + for (uint32_t value = 0; value != 0x100; value++) { + const Color expected(esp_scale((value >> 5) & 0x07, 7), esp_scale((value >> 2) & 0x07, 7), + esp_scale(value & 0x03, 3)); + ASSERT_EQ(ColorUtil::to_color(value, COLOR_BITNESS_332).raw_32, expected.raw_32) << "332 value " << value; + } +} + +// Without right_bit_aligned each channel is read from its own byte; at 8 bits it comes back unchanged. +TEST(ColorUtil, ToColorByteAligned) { + EXPECT_EQ(ColorUtil::to_color(0x563412, COLOR_BITNESS_888, false).raw_32, Color(0x56, 0x34, 0x12).raw_32); +} + +// A driver that copies blocks itself overrides the protected draw_pixels_internal() hook. +class HookDisplay : public RecordingDisplay { + public: + std::vector> calls; + const uint8_t *ptr{}; + + protected: + void draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorBitness bitness, + bool big_endian, int x_offset, int y_offset, int x_pad) override { + this->calls.push_back({x_start, y_start, w, h, bitness, big_endian, x_offset, y_offset, x_pad}); + this->ptr = ptr; + } +}; + +// Both public forms reach the hook, and nothing is hidden when called through the driver's own type. +TEST(DrawPixelsAt, BothFormsReachTheHook) { + const auto source = make_source(2); + HookDisplay display; + display.draw_pixels_at(10, 20, W, H, source.data(), COLOR_BITNESS_565, true, X_OFFSET, Y_OFFSET, X_PAD); + display.draw_pixels_at(1, 2, 3, 4, source.data(), COLOR_BITNESS_888, false); + ASSERT_EQ(display.calls.size(), 2u); + EXPECT_EQ(display.calls[0], (std::vector{10, 20, W, H, COLOR_BITNESS_565, true, X_OFFSET, Y_OFFSET, X_PAD})); + EXPECT_EQ(display.calls[1], (std::vector{1, 2, 3, 4, COLOR_BITNESS_888, false, 0, 0, 0})); + EXPECT_EQ(display.ptr, source.data()); + EXPECT_EQ(display.count(), 0u); +} + +// Remove before 2027.5.0, with the draw_pixels_at() overloads that take a color order. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + +// An out of tree driver that still overrides only the form with a color order. +class LegacyDisplay : public RecordingDisplay { + public: + void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order, + ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override { + this->calls_.push_back({x_start, y_start, w, h, x_offset, y_offset, x_pad}); + this->order_ = order; + this->ptr_ = ptr; + this->bitness_ = bitness; + this->big_endian_ = big_endian; + } + + std::vector> calls_; + ColorOrder order_{COLOR_ORDER_GRB}; + const uint8_t *ptr_{}; + ColorBitness bitness_{COLOR_BITNESS_888}; + bool big_endian_{}; +}; + +// Callers use the form without a color order; a legacy override still receives every block, as RGB. +TEST(DrawPixelsAt, LegacyOverrideStillReceivesBlocks) { + const auto source = make_source(2); + LegacyDisplay legacy; + Display &display = legacy; + display.draw_pixels_at(10, 20, W, H, source.data(), COLOR_BITNESS_565, true, X_OFFSET, Y_OFFSET, X_PAD); + display.draw_pixels_at(1, 2, 3, 4, source.data(), COLOR_BITNESS_565, true); + ASSERT_EQ(legacy.calls_.size(), 2u); + EXPECT_EQ(legacy.calls_[0], (std::vector{10, 20, W, H, X_OFFSET, Y_OFFSET, X_PAD})); + EXPECT_EQ(legacy.calls_[1], (std::vector{1, 2, 3, 4, 0, 0, 0})); + EXPECT_EQ(legacy.order_, COLOR_ORDER_RGB); + EXPECT_EQ(legacy.ptr_, source.data()); + EXPECT_EQ(legacy.bitness_, COLOR_BITNESS_565); + EXPECT_TRUE(legacy.big_endian_); + EXPECT_EQ(legacy.count(), 0u); +} + +// The deprecated form still honors a color order other than RGB when it draws per pixel. +static void expect_ordered(ColorBitness bitness, std::initializer_list bytes, uint32_t code, ColorOrder order, + Color expected) { + const std::vector source(bytes); + RecordingDisplay display; + display.draw_pixels_at(3, 4, 1, 1, source.data(), order, bitness, true); + ASSERT_EQ(display.count(), 1u); + EXPECT_EQ(display.pixels()[0], std::make_tuple(3, 4, expected.raw_32)); + EXPECT_EQ(expected.raw_32, ColorUtil::to_color(code, order, bitness).raw_32); +} + +// 888 is b, g, r in memory: 0x563412 +TEST(DrawPixelsAt, DeprecatedOrderRgb) { + expect_ordered(COLOR_BITNESS_888, {0x12, 0x34, 0x56}, 0x563412, COLOR_ORDER_RGB, Color(0x56, 0x34, 0x12)); +} +TEST(DrawPixelsAt, DeprecatedOrderBgr) { + expect_ordered(COLOR_BITNESS_888, {0x12, 0x34, 0x56}, 0x563412, COLOR_ORDER_BGR, Color(0x12, 0x34, 0x56)); +} +TEST(DrawPixelsAt, DeprecatedOrderGrb) { + expect_ordered(COLOR_BITNESS_888, {0x12, 0x34, 0x56}, 0x563412, COLOR_ORDER_GRB, Color(0x34, 0x56, 0x12)); +} +// 565 big endian 0xE007 is r 230, g 0, b 57 in RGB order +TEST(DrawPixelsAt, DeprecatedOrderBgr565) { + expect_ordered(COLOR_BITNESS_565, {0xE0, 0x07}, 0xE007, COLOR_ORDER_BGR, Color(57, 0, 230)); +} +TEST(DrawPixelsAt, DeprecatedOrderGrb565) { + expect_ordered(COLOR_BITNESS_565, {0xE0, 0x07}, 0xE007, COLOR_ORDER_GRB, Color(0, 230, 57)); +} +// 332 0xE0 is r 255, g 0, b 0 in RGB order +TEST(DrawPixelsAt, DeprecatedOrderBgr332) { + expect_ordered(COLOR_BITNESS_332, {0xE0}, 0xE0, COLOR_ORDER_BGR, Color(0, 0, 255)); +} +TEST(DrawPixelsAt, DeprecatedOrderGrb332) { + expect_ordered(COLOR_BITNESS_332, {0xE0}, 0xE0, COLOR_ORDER_GRB, Color(0, 255, 0)); +} + +// The deprecated packing functions still honor every order; with RGB they match the new ones. +// 0xC0, 0x60, 0x30 is red 24, green 24, blue 6 in 565 and red 6, green 3, blue 0 in 332. +TEST(DrawPixelsAt, DeprecatedPackingHonorsTheOrder) { + const Color color(0xC0, 0x60, 0x30); + EXPECT_EQ(ColorUtil::color_to_565(color), 0xC306); + EXPECT_EQ(ColorUtil::color_to_565(color, COLOR_ORDER_RGB), 0xC306); + EXPECT_EQ(ColorUtil::color_to_565(color, COLOR_ORDER_BGR), 0x3318); + EXPECT_EQ(ColorUtil::color_to_565(color, COLOR_ORDER_GRB), 0x6306); + EXPECT_EQ(ColorUtil::color_to_332(color), 0xCC); + EXPECT_EQ(ColorUtil::color_to_332(color, COLOR_ORDER_RGB), 0xCC); + EXPECT_EQ(ColorUtil::color_to_332(color, COLOR_ORDER_BGR), 0x1E); + EXPECT_EQ(ColorUtil::color_to_332(color, COLOR_ORDER_GRB), 0x78); +} + +#pragma GCC diagnostic pop + } // namespace esphome::display::testing diff --git a/tests/components/epaper_spi/display/test_draw_pixels.cpp b/tests/components/epaper_spi/display/test_draw_pixels.cpp index a3b47ea8fc1..1933637efdb 100644 --- a/tests/components/epaper_spi/display/test_draw_pixels.cpp +++ b/tests/components/epaper_spi/display/test_draw_pixels.cpp @@ -42,11 +42,11 @@ template class TestableDisplay : public Driver { std::vector bounds() const { return {this->x_low_, this->y_low_, this->x_high_, this->y_high_}; } - void draw_one_by_one(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, - display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) { + void draw_one_by_one(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorBitness bitness, + bool big_endian, int x_offset, int y_offset, int x_pad) { // The per-pixel path the fast one must match - display::Display::draw_pixels_at( // NOLINT(bugprone-parent-virtual-call) - x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, x_pad); + display::Display::draw_pixels_internal( // NOLINT(bugprone-parent-virtual-call) + x_start, y_start, w, h, ptr, bitness, big_endian, x_offset, y_offset, x_pad); } }; @@ -100,10 +100,10 @@ TEST(EPaperDrawPixels, SameResultAsDrawingPixelByPixel) { } // a 3 pixel left offset, 2 rows skipped and 4 pixels of padding const auto data = random_bytes((3 + block.w + 4) * (2 + block.h), seed++); - fast.draw_pixels_at(block.x, block.y, block.w, block.h, data.data(), display::COLOR_ORDER_RGB, - display::COLOR_BITNESS_565, big_endian, 3, 2, 4); - slow.draw_one_by_one(block.x, block.y, block.w, block.h, data.data(), display::COLOR_ORDER_RGB, - display::COLOR_BITNESS_565, big_endian, 3, 2, 4); + fast.draw_pixels_at(block.x, block.y, block.w, block.h, data.data(), display::COLOR_BITNESS_565, big_endian, + 3, 2, 4); + slow.draw_one_by_one(block.x, block.y, block.w, block.h, data.data(), display::COLOR_BITNESS_565, big_endian, + 3, 2, 4); const auto where = ::testing::Message() << "rotation " << rotation << " mirroring " << int(mirroring) << " big endian " << big_endian << " block " << block.x << "," << block.y << " " << block.w << "x" << block.h; @@ -115,16 +115,28 @@ TEST(EPaperDrawPixels, SameResultAsDrawingPixelByPixel) { } } -/// The colour order reaches the RGB565 block writer. -TEST(EPaperDrawPixels, ColourOrderIsHonoured) { - for (const auto order : {display::COLOR_ORDER_RGB, display::COLOR_ORDER_BGR, display::COLOR_ORDER_GRB}) { - TestableDisplay fast(21, 13); - TestableDisplay slow(21, 13); - const auto data = random_bytes(21 * 13, 99); - fast.draw_pixels_at(0, 0, 21, 13, data.data(), order, display::COLOR_BITNESS_565, false, 0, 0, 0); - slow.draw_one_by_one(0, 0, 21, 13, data.data(), order, display::COLOR_BITNESS_565, false, 0, 0, 0); - EXPECT_EQ(fast.buffer(), slow.buffer()) << "order " << int(order); - } +/// Every form of draw_pixels_at() can be called through the driver's own type and draws the same result. +TEST(EPaperDrawPixels, AllFormsAreVisibleThroughTheDriver) { + const auto data = rgb565(24 * 16, 11); + TestableDisplay slow(24, 16); + slow.draw_one_by_one(0, 0, 24, 16, data.data(), display::COLOR_BITNESS_565, false, 0, 0, 0); + TestableDisplay full(24, 16); + TestableDisplay packed(24, 16); + full.draw_pixels_at(0, 0, 24, 16, data.data(), display::COLOR_BITNESS_565, false, 0, 0, 0); + packed.draw_pixels_at(0, 0, 24, 16, data.data(), display::COLOR_BITNESS_565, false); + EXPECT_EQ(full.buffer(), slow.buffer()); + EXPECT_EQ(packed.buffer(), slow.buffer()); + // Remove before 2027.5.0, with the forms that take a color order +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + TestableDisplay old_full(24, 16); + TestableDisplay old_packed(24, 16); + old_full.draw_pixels_at(0, 0, 24, 16, data.data(), display::COLOR_ORDER_RGB, display::COLOR_BITNESS_565, false, 0, 0, + 0); + old_packed.draw_pixels_at(0, 0, 24, 16, data.data(), display::COLOR_ORDER_RGB, display::COLOR_BITNESS_565, false); +#pragma GCC diagnostic pop + EXPECT_EQ(old_full.buffer(), slow.buffer()); + EXPECT_EQ(old_packed.buffer(), slow.buffer()); } /// With a clipping region set the block goes through draw_pixel_at(), which honours it. @@ -134,8 +146,8 @@ TEST(EPaperDrawPixels, ClippingFallsBackToDrawingPixelByPixel) { const auto data = rgb565(24 * 16, 7); for (auto *display : {&fast, &slow}) display->start_clipping(4, 4, 12, 10); - fast.draw_pixels_at(0, 0, 24, 16, data.data(), display::COLOR_ORDER_RGB, display::COLOR_BITNESS_565, false, 0, 0, 0); - slow.draw_one_by_one(0, 0, 24, 16, data.data(), display::COLOR_ORDER_RGB, display::COLOR_BITNESS_565, false, 0, 0, 0); + fast.draw_pixels_at(0, 0, 24, 16, data.data(), display::COLOR_BITNESS_565, false, 0, 0, 0); + slow.draw_one_by_one(0, 0, 24, 16, data.data(), display::COLOR_BITNESS_565, false, 0, 0, 0); EXPECT_EQ(fast.buffer(), slow.buffer()); EXPECT_EQ(fast.bounds(), (std::vector{4, 4, 12, 10})); } @@ -145,13 +157,28 @@ TEST(EPaperDrawPixels, OtherBufferLayoutsDrawPixelByPixel) { TestableDisplay fast(16, 4); TestableDisplay slow(16, 4); const auto data = rgb565(16 * 4, 11); - fast.draw_pixels_at(0, 0, 16, 4, data.data(), display::COLOR_ORDER_RGB, display::COLOR_BITNESS_565, false, 0, 0, 0); - slow.draw_one_by_one(0, 0, 16, 4, data.data(), display::COLOR_ORDER_RGB, display::COLOR_BITNESS_565, false, 0, 0, 0); + fast.draw_pixels_at(0, 0, 16, 4, data.data(), display::COLOR_BITNESS_565, false, 0, 0, 0); + slow.draw_one_by_one(0, 0, 16, 4, data.data(), display::COLOR_BITNESS_565, false, 0, 0, 0); EXPECT_EQ(fast.buffer(), slow.buffer()); } -/// The RGB565 lookup gives exactly what converting the colour and thresholding it does, for every value and order. -TEST(EPaperDrawPixels, Rgb565LookupMatchesTheColourConversion) { +/// The RGB565 lookup gives exactly what converting the color and thresholding it does, for every value. +TEST(EPaperDrawPixels, Rgb565LookupMatchesTheColorConversion) { + const Rgb565ToMono to_mono; + size_t mismatches = 0; + for (uint32_t value = 0; value != 0x10000; value++) { + const uint8_t expected = color_to_mono(ColorUtil::to_color(value, display::COLOR_BITNESS_565)); + if (to_mono(static_cast(value)) != expected) + mismatches++; + } + EXPECT_EQ(mismatches, 0u); +} + +// Remove before 2027.5.0 +/// The deprecated lookup that takes a color order still matches the deprecated conversion with that order. +TEST(EPaperDrawPixels, DeprecatedOrderedLookupMatchesTheColorConversion) { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" for (const auto order : {display::COLOR_ORDER_RGB, display::COLOR_ORDER_BGR, display::COLOR_ORDER_GRB}) { const Rgb565ToMono to_mono(order); size_t mismatches = 0; @@ -162,6 +189,7 @@ TEST(EPaperDrawPixels, Rgb565LookupMatchesTheColourConversion) { } EXPECT_EQ(mismatches, 0u) << "order " << int(order); } +#pragma GCC diagnostic pop } /// Other bit depths, and a buffer split into several blocks, go through draw_pixel_at() and give the same result. @@ -172,10 +200,8 @@ TEST(EPaperDrawPixels, OtherCasesDrawPixelByPixel) { fast.split_buffer(7); slow.split_buffer(7); const auto data = rgb565(24 * 16, 5); - fast.draw_pixels_at(-2, 3, 24, 16, data.data(), display::COLOR_ORDER_RGB, display::COLOR_BITNESS_565, false, 0, 0, - 0); - slow.draw_one_by_one(-2, 3, 24, 16, data.data(), display::COLOR_ORDER_RGB, display::COLOR_BITNESS_565, false, 0, 0, - 0); + fast.draw_pixels_at(-2, 3, 24, 16, data.data(), display::COLOR_BITNESS_565, false, 0, 0, 0); + slow.draw_one_by_one(-2, 3, 24, 16, data.data(), display::COLOR_BITNESS_565, false, 0, 0, 0); EXPECT_EQ(fast.buffer(), slow.buffer()) << "split buffer"; EXPECT_EQ(fast.bounds(), slow.bounds()) << "split buffer"; } @@ -183,8 +209,8 @@ TEST(EPaperDrawPixels, OtherCasesDrawPixelByPixel) { TestableDisplay fast(24, 16); TestableDisplay slow(24, 16); const auto data = random_bytes(24 * 16, 9); - fast.draw_pixels_at(0, 0, 24, 16, data.data(), display::COLOR_ORDER_RGB, bitness, false, 0, 0, 0); - slow.draw_one_by_one(0, 0, 24, 16, data.data(), display::COLOR_ORDER_RGB, bitness, false, 0, 0, 0); + fast.draw_pixels_at(0, 0, 24, 16, data.data(), bitness, false, 0, 0, 0); + slow.draw_one_by_one(0, 0, 24, 16, data.data(), bitness, false, 0, 0, 0); EXPECT_EQ(fast.buffer(), slow.buffer()) << "bitness " << int(bitness); EXPECT_EQ(fast.bounds(), slow.bounds()) << "bitness " << int(bitness); } diff --git a/tests/components/image/test_image_draw.cpp b/tests/components/image/test_image_draw.cpp index 80db8a4ad2a..516f07fea52 100644 --- a/tests/components/image/test_image_draw.cpp +++ b/tests/components/image/test_image_draw.cpp @@ -26,8 +26,8 @@ struct Blit { // Records the bulk calls and counts the per pixel calls Image::draw() makes. class RecordingDisplay : public TestDisplay { public: - void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, - display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override { + void draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorBitness bitness, + bool big_endian, int x_offset, int y_offset, int x_pad) override { this->blits.push_back({x_start, y_start, w, h, ptr, bitness, big_endian, x_offset, y_offset, x_pad}); } void draw_pixel_at(int x, int y, Color color) override { this->pixels++; } @@ -154,7 +154,7 @@ static void expect_fallback_decodes(bool big_endian) { for (const auto &pixel : display.pixels) { const uint32_t rgb565 = display::ColorUtil::read_packed(data.data(), pixel.x + pixel.y * W, big_endian); - const Color expected = display::ColorUtil::to_color(rgb565, display::COLOR_ORDER_RGB, display::COLOR_BITNESS_565); + const Color expected = display::ColorUtil::to_color(rgb565, display::COLOR_BITNESS_565); EXPECT_EQ(pixel.raw, expected.raw_32) << "at " << pixel.x << "," << pixel.y; } }