[display] Drop the color order from draw_pixels_at() and ColorUtil (#20884)

This commit is contained in:
Clyde Stubbs
2026-10-17 06:45:03 +11:00
committed by GitHub
parent 9440012413
commit 04aa59563d
37 changed files with 598 additions and 332 deletions
+50 -11
View File
@@ -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<ColorBitness BITNESS>
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<ColorBitness BITNESS, bool HIGH_BYTE_FIRST = false>
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<BITNESS>(ptr, source_idx, big_endian), order, BITNESS));
ColorUtil::to_color(ColorUtil::read_packed<BITNESS>(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<COLOR_BITNESS_565>(ptr, source_idx, big_endian);
} else if (bitness == COLOR_BITNESS_888) {
code = ColorUtil::read_packed<COLOR_BITNESS_888>(ptr, source_idx, big_endian);
} else {
code = ColorUtil::read_packed<COLOR_BITNESS_332>(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<COLOR_BITNESS_565>(this, x_start, y_start, w, h, ptr, order, big_endian, x_offset, y_offset,
x_pad);
if (big_endian) {
draw_packed_pixels<COLOR_BITNESS_565, true>(this, x_start, y_start, w, h, ptr, x_offset, y_offset, x_pad);
} else {
draw_packed_pixels<COLOR_BITNESS_565, false>(this, x_start, y_start, w, h, ptr, x_offset, y_offset, x_pad);
}
break;
case COLOR_BITNESS_888:
draw_packed_pixels<COLOR_BITNESS_888>(this, x_start, y_start, w, h, ptr, order, big_endian, x_offset, y_offset,
x_pad);
draw_packed_pixels<COLOR_BITNESS_888>(this, x_start, y_start, w, h, ptr, x_offset, y_offset, x_pad);
break;
default:
draw_packed_pixels<COLOR_BITNESS_332>(this, x_start, y_start, w, h, ptr, order, big_endian, x_offset, y_offset,
x_pad);
draw_packed_pixels<COLOR_BITNESS_332>(this, x_start, y_start, w, h, ptr, x_offset, y_offset, x_pad);
break;
}
}
+40 -10
View File
@@ -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,
+101 -77
View File
@@ -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<uint16_t, 32> high_{};
std::array<uint16_t, 64> middle_{};
+7 -6
View File
@@ -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() {
+5 -4
View File
@@ -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; }
+8 -9
View File
@@ -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<int>(bitness));
+3 -2
View File
@@ -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; }
+8 -10
View File
@@ -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.
+3 -2
View File
@@ -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())
+2 -2
View File
@@ -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
+5 -4
View File
@@ -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<uint16_t>(nx), static_cast<uint16_t>(ny),
ColorUtil::to_color(color_value, order, bitness));
ColorUtil::to_color(color_value, bitness));
}
}
+6 -5
View File
@@ -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_; }
+1 -2
View File
@@ -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_);
}
}
+5 -6
View File
@@ -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;
+3 -3
View File
@@ -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_();
+5 -4
View File
@@ -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) {
+4 -3
View File
@@ -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_();
+59 -61
View File
@@ -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<const BUFFERTYPE *>(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<const BUFFERTYPE *>(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<int>(0, this->start_line_ - y_start);
const int end_row = std::min<int>(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<int>(this->x_low_, x_start + first_col);
this->y_low_ = std::min<int>(this->y_low_, y_start + first_row);
this->x_high_ = std::max<int>(this->x_high_, x_start + end_col - 1);
this->y_high_ = std::max<int>(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<int>(0, this->start_line_ - y_start);
const int end_row = std::min<int>(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<int>(this->x_low_, x_start + first_col);
this->y_low_ = std::min<int>(this->y_low_, y_start + first_row);
this->x_high_ = std::max<int>(this->x_high_, x_start + end_col - 1);
this->y_high_ = std::max<int>(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) {
+12 -16
View File
@@ -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<uint32_t>(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<int>(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<uint32_t>(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<display::COLOR_BITNESS_565>(ptr, source_idx, big_endian),
display::COLOR_BITNESS_565));
}
}
}
+3 -2
View File
@@ -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<int>(this->model_); }
int get_height_internal() override { return static_cast<int>(this->model_); }
+11 -11
View File
@@ -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<uint8_t>(new_color >> 8)) {
this->buffer_[pos] = static_cast<uint8_t>(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<int>(this->width_) - x_start);
+4 -4
View File
@@ -112,12 +112,12 @@ class QspiDbi final : public display::DisplayBuffer,
}
void write_sequence_(const std::vector<uint8_t> &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.)
@@ -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);
+3 -2
View File
@@ -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_; }
+4 -4
View File
@@ -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;
+3 -2
View File
@@ -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);
@@ -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<size_t>(x_offset) + w + x_pad;
@@ -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_; }
+6 -6
View File
@@ -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);
+4 -3
View File
@@ -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);
+2 -2
View File
@@ -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);
+1 -2
View File
@@ -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) {
@@ -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);
}
@@ -42,10 +42,10 @@ class BenchDisplay : public display::DisplayBuffer {
class BulkBenchDisplay : public BenchDisplay {
public:
BulkBenchDisplay() : frame565_(std::make_unique<uint16_t[]>(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;
@@ -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<size_t>(W * H));
EXPECT_EQ(columns.count(), static_cast<size_t>(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<uint8_t> bytes, bool big_endian, Color expected) {
const std::vector<uint8_t> 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<std::vector<int>> 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<int>{10, 20, W, H, COLOR_BITNESS_565, true, X_OFFSET, Y_OFFSET, X_PAD}));
EXPECT_EQ(display.calls[1], (std::vector<int>{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<std::vector<int>> 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<int>{10, 20, W, H, X_OFFSET, Y_OFFSET, X_PAD}));
EXPECT_EQ(legacy.calls_[1], (std::vector<int>{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<uint8_t> bytes, uint32_t code, ColorOrder order,
Color expected) {
const std::vector<uint8_t> 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
@@ -42,11 +42,11 @@ template<typename Driver> class TestableDisplay : public Driver {
std::vector<uint16_t> 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<EPaperSSD1683> fast(21, 13);
TestableDisplay<EPaperSSD1683> 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<EPaperSSD1683> slow(24, 16);
slow.draw_one_by_one(0, 0, 24, 16, data.data(), display::COLOR_BITNESS_565, false, 0, 0, 0);
TestableDisplay<EPaperSSD1683> full(24, 16);
TestableDisplay<EPaperSSD1683> 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<EPaperSSD1683> old_full(24, 16);
TestableDisplay<EPaperSSD1683> 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<uint16_t>{4, 4, 12, 10}));
}
@@ -145,13 +157,28 @@ TEST(EPaperDrawPixels, OtherBufferLayoutsDrawPixelByPixel) {
TestableDisplay<EPaperSSD1677Gray4> fast(16, 4);
TestableDisplay<EPaperSSD1677Gray4> 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<uint16_t>(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<EPaperSSD1683> fast(24, 16);
TestableDisplay<EPaperSSD1683> 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);
}
+3 -3
View File
@@ -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<display::COLOR_BITNESS_565>(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;
}
}