mirror of
https://github.com/esphome/esphome.git
synced 2026-10-17 11:42:51 +00:00
[display] Drop the color order from draw_pixels_at() and ColorUtil (#20884)
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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_{};
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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_; }
|
||||
|
||||
|
||||
@@ -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_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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_();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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_();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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_); }
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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_; }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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_; }
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user