Display Pixel Blocks Drop the Color Order and Get a Driver Hook
ESPHome 2026.11.0 removes the ColorOrder argument from Display::draw_pixels_at() and from the ColorUtil color conversions. Pixel blocks and packed colors are always RGB. draw_pixels_at() is no longer virtual: a display driver that copies blocks itself now overrides the protected draw_pixels_internal(). The forms that take a ColorOrder still work, with a deprecation warning, until 2027.5.0.
This is a developer breaking change for external display components in ESPHome 2026.11.0 and later, with a deprecation period until 2027.5.0.
Background
Section titled “Background”draw_pixels_at() hands a display a whole block of pixels at once. LVGL uses it to flush what it has rendered, and image uses it for opaque RGB565 and RGB images. It took a ColorOrder, but every caller passed COLOR_ORDER_RGB, and the BGR and GRB conversions in ColorUtil were never used and did not agree with each other. The panel color order (color_order: bgr in YAML) is a separate setting that panel drivers apply with their MADCTL register, and it is unchanged.
draw_pixels_at() was also the virtual function drivers overrode. A driver that overrode one form hid the other forms for calls made through the driver’s own type, so the public call and the driver hook are now separate functions.
Changes
Section titled “Changes”- PR #20884: drops the color order from
draw_pixels_at()andColorUtil, and adds thedraw_pixels_internal()hook.
What’s Changing
Section titled “What’s Changing”Display
Section titled “Display”// OLD - public and virtual; drivers overrode itvirtual 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);
// NEW - public and not virtual; callers use thesevoid 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);void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorBitness bitness, bool big_endian);
// NEW - protected and virtual; drivers override this onevirtual 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);The base draw_pixels_internal() draws the block per pixel through draw_pixel_at(), as the old base draw_pixels_at() did. See Drawing blocks of pixels for the rules an override must follow.
ColorUtil
Section titled “ColorUtil”// OLDColor c = display::ColorUtil::to_color(value, display::COLOR_ORDER_RGB, display::COLOR_BITNESS_565);uint16_t v = display::ColorUtil::color_to_565(color, display::COLOR_ORDER_RGB);uint8_t v = display::ColorUtil::color_to_332(color, display::COLOR_ORDER_RGB);display::Rgb565ToMono to_mono(display::COLOR_ORDER_RGB);
// NEWColor c = display::ColorUtil::to_color(value, display::COLOR_BITNESS_565);uint16_t v = display::ColorUtil::color_to_565(color);uint8_t v = display::ColorUtil::color_to_332(color);display::Rgb565ToMono to_mono;color_to_565(color) and color_to_332(color) were already valid calls, since the order defaulted to RGB; they now go to the new functions.
Who This Affects
Section titled “Who This Affects”Standard YAML configurations need no changes.
Code that calls draw_pixels_at() with a ColorOrder, for example from a lambda, keeps working until 2027.5.0 and gets a deprecation warning. Drop the order:
// OLDit.draw_pixels_at(0, 0, w, h, buffer, display::COLOR_ORDER_RGB, display::COLOR_BITNESS_565, true);
// NEWit.draw_pixels_at(0, 0, w, h, buffer, display::COLOR_BITNESS_565, true);Until you migrate, such a call draws correctly but per pixel, even on a display that can copy blocks.
External display drivers that override draw_pixels_at() keep working until 2027.5.0: their override still receives every block, as RGB, and its call to display::Display::draw_pixels_at(...) for anything it cannot copy still draws per pixel. Until you migrate, add using display::Display::draw_pixels_at; to the driver’s class, or your override hides the new forms when they are called through your driver’s type, and do not also override draw_pixels_internal(). Overriding a deprecated function does not produce a warning, so check your driver by hand. To migrate, rename the override and drop the order:
// OLDvoid 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 (bitness != display::COLOR_BITNESS_565 || order != display::COLOR_ORDER_RGB || ...) { display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, x_pad); return; } ...}
// NEWvoid 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 || ...) { display::Display::draw_pixels_internal(x_start, y_start, w, h, ptr, bitness, big_endian, x_offset, y_offset, x_pad); return; } ...}The fallback must call display::Display::draw_pixels_internal(). Calling draw_pixels_at() there would call your own override again.
Code that uses ColorUtil with a ColorOrder gets a deprecation warning; drop the order as shown above. If you relied on BGR or GRB packing, swap the channels of the Color yourself before converting.
Finding Code That Needs Updates
Section titled “Finding Code That Needs Updates”grep -rnE 'draw_pixels_at|COLOR_ORDER_(RGB|BGR|GRB)|Rgb565ToMono\(' --include='*.cpp' --include='*.h' --include='*.yaml'COLOR_ORDER_RGB and COLOR_ORDER_BGR also appear in panel drivers that set the MADCTL color order; those uses are unchanged.
Timeline
Section titled “Timeline”- ESPHome 2026.11.0:
draw_pixels_at()andColorUtiltake no color order; drivers overridedraw_pixels_internal(). The forms with aColorOrderare deprecated. - ESPHome 2027.5.0: the forms with a
ColorOrderare removed. An override of the olddraw_pixels_at()no longer compiles.
Questions?
Section titled “Questions?”If you have questions about these changes or need help migrating your external component, please ask in the ESPHome Discord or open a discussion on GitHub.
Copyright © 2026 ESPHome - A project from the Open Home Foundation
Comments
Feel free to leave a comment here to discuss this post with others. You can ask questions, share your experience, or suggest improvements. If you have a question about a specific feature or issue, please consider using the ESPHome Discord. Stick to English and follow ESPHome's code of conduct. These comments exist on a discussion on GitHub, so you can also comment there directly if you prefer.