mirror of
https://github.com/esphome/esphome.git
synced 2026-10-14 10:12:52 +00:00
Merge remote-tracking branch 'origin/dev' into tx20-tables-progmem
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
#pragma once
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include "esphome/core/color.h"
|
||||
|
||||
namespace esphome::display {
|
||||
@@ -154,4 +156,54 @@ class ColorUtil {
|
||||
return color;
|
||||
}
|
||||
};
|
||||
|
||||
/** Rec.601 luma weights (0.299/0.587/0.114), scaled by 256 */
|
||||
static constexpr uint16_t REC601_LUMA_R = 77;
|
||||
static constexpr uint16_t REC601_LUMA_G = 150;
|
||||
static constexpr uint16_t REC601_LUMA_B = 29;
|
||||
|
||||
/** Rec.601 luma of an RGB color, for optimum perceptual brightness */
|
||||
constexpr uint8_t rec601_luma(Color color) {
|
||||
return (uint8_t) ((REC601_LUMA_R * color.r + REC601_LUMA_G * color.g + REC601_LUMA_B * color.b + 128u) >> 8);
|
||||
}
|
||||
|
||||
/** Map an RGB color to a single monochrome bit: 1 = white, 0 = black */
|
||||
constexpr uint8_t color_to_mono(Color color) { return rec601_luma(color) >= 128 ? 1 : 0; }
|
||||
|
||||
/** color_to_mono() of an RGB565 value as ColorUtil::to_color() reads it, from table lookups instead of a division
|
||||
* per channel.
|
||||
*/
|
||||
class Rgb565ToMono {
|
||||
public:
|
||||
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);
|
||||
}
|
||||
|
||||
uint8_t operator()(uint16_t value) const {
|
||||
const uint32_t weighted = this->high_[value >> 11] + this->middle_[(value >> 5) & 0x3F] + this->low_[value & 0x1F];
|
||||
return ((weighted + 128u) >> 8) >= 128 ? 1 : 0;
|
||||
}
|
||||
|
||||
protected:
|
||||
// Weighted contribution of each value of each field
|
||||
std::array<uint16_t, 32> high_{};
|
||||
std::array<uint16_t, 64> middle_{};
|
||||
std::array<uint16_t, 32> low_{};
|
||||
};
|
||||
|
||||
} // namespace esphome::display
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <algorithm>
|
||||
#include "esphome/components/display/display_color_utils.h"
|
||||
#include "esphome/core/color.h"
|
||||
|
||||
/* Utility for converting internal \a Color RGB representation to supported IC hardware color keys
|
||||
@@ -13,21 +14,15 @@
|
||||
|
||||
namespace esphome::epaper_spi {
|
||||
|
||||
// The Rec.601 luma helpers and the RGB565 mono lookup live in the shared display utilities, so other
|
||||
// drivers (e.g. it8951 in its 1 bit per pixel mode) can use them too.
|
||||
using display::color_to_mono;
|
||||
using display::rec601_luma;
|
||||
using display::Rgb565ToMono;
|
||||
|
||||
/** Delta for when to regard as gray */
|
||||
static constexpr uint8_t COLORCONV_GRAY_THRESHOLD = 50;
|
||||
|
||||
/** Rec.601 luma (0.299/0.587/0.114 weights, scaled by 256) for optimum perceptual brightness */
|
||||
constexpr uint8_t rec601_luma(Color color) {
|
||||
return (uint8_t) ((77u * color.r + 150u * color.g + 29u * color.b + 128u) >> 8);
|
||||
}
|
||||
|
||||
/** Map RGB color to a single monochrome bit
|
||||
*
|
||||
* @param color RGB color to convert from
|
||||
* @return 1 = white, 0 = black
|
||||
*/
|
||||
constexpr uint8_t color_to_mono(Color color) { return rec601_luma(color) >= 128 ? 1 : 0; }
|
||||
|
||||
/** Map RGB color to one of 4 discrete gray levels (2 bits per pixel)
|
||||
*
|
||||
* @param color RGB color to convert from
|
||||
|
||||
@@ -345,6 +345,120 @@ void HOT EPaperBase::draw_pixel_at(int x, int y, Color color) {
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
// One source row of an RGB565 block, written into a contiguous 1 bit per pixel buffer. Without SwapAxes the row lies
|
||||
// along a buffer row and the bit moves by step; with it the row lies along a bit column and the byte moves by step
|
||||
// rows.
|
||||
template<bool SwapAxes, bool BigEndianSource>
|
||||
void HOT write_rgb565_row(uint8_t *byte, uint8_t mask, int step, size_t row_width, const uint8_t *source, int count,
|
||||
const Rgb565ToMono &to_mono) {
|
||||
const ptrdiff_t byte_step = SwapAxes ? step * static_cast<ptrdiff_t>(row_width) : 0;
|
||||
for (; count != 0; count--, source += 2) {
|
||||
const uint16_t value = BigEndianSource ? (source[0] << 8) | source[1] : source[0] | (source[1] << 8);
|
||||
if (to_mono(value)) {
|
||||
*byte |= mask;
|
||||
} else {
|
||||
*byte &= ~mask;
|
||||
}
|
||||
if constexpr (SwapAxes) {
|
||||
byte += byte_step;
|
||||
} else if (step > 0) {
|
||||
mask >>= 1;
|
||||
if (mask == 0) {
|
||||
mask = 0x80;
|
||||
byte++;
|
||||
}
|
||||
} else {
|
||||
mask <<= 1;
|
||||
if (mask == 0) {
|
||||
mask = 0x01;
|
||||
byte--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// 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) {
|
||||
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;
|
||||
// The buffer axis a source column moves along, and the one a source row moves along
|
||||
const int column_limit = swap ? this->height_ : this->width_;
|
||||
const int row_limit = swap ? this->width_ : this->height_;
|
||||
const bool column_mirrored = swap ? mirror_y : mirror_x;
|
||||
const bool row_mirrored = swap ? mirror_x : mirror_y;
|
||||
const int first_column = std::max(0, -x_start);
|
||||
const int end_column = std::min(w, column_limit - x_start);
|
||||
const int first_row = std::max(0, -y_start);
|
||||
const int end_row = std::min(h, row_limit - y_start);
|
||||
if (first_column >= end_column || first_row >= end_row)
|
||||
return;
|
||||
auto column_to_buffer = [&](int column) {
|
||||
const int u = x_start + column;
|
||||
return column_mirrored ? column_limit - 1 - u : u;
|
||||
};
|
||||
auto row_to_buffer = [&](int row) {
|
||||
const int v = y_start + row;
|
||||
return row_mirrored ? row_limit - 1 - v : v;
|
||||
};
|
||||
|
||||
const int column_low = std::min(column_to_buffer(first_column), column_to_buffer(end_column - 1));
|
||||
const int row_low = std::min(row_to_buffer(first_row), row_to_buffer(end_row - 1));
|
||||
const int columns = end_column - first_column;
|
||||
const int rows = end_row - first_row;
|
||||
const int x_low = swap ? row_low : column_low;
|
||||
const int y_low = swap ? column_low : row_low;
|
||||
const int x_high = x_low + (swap ? rows : columns);
|
||||
const int y_high = y_low + (swap ? columns : rows);
|
||||
|
||||
const Rgb565ToMono to_mono(order);
|
||||
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;
|
||||
for (int row = first_row; row != end_row; row++) {
|
||||
const uint8_t *source = ptr + ((y_offset + row) * line_stride + x_offset + first_column) * 2;
|
||||
const int along = column_to_buffer(first_column);
|
||||
const int across = row_to_buffer(row);
|
||||
const int x = swap ? across : along;
|
||||
const int y = swap ? along : across;
|
||||
uint8_t *byte = buffer + y * row_width + x / 8;
|
||||
const uint8_t mask = 0x80u >> (x % 8);
|
||||
if (swap) {
|
||||
if (big_endian) {
|
||||
write_rgb565_row<true, true>(byte, mask, step, row_width, source, columns, to_mono);
|
||||
} else {
|
||||
write_rgb565_row<true, false>(byte, mask, step, row_width, source, columns, to_mono);
|
||||
}
|
||||
} else if (big_endian) {
|
||||
write_rgb565_row<false, true>(byte, mask, step, row_width, source, columns, to_mono);
|
||||
} else {
|
||||
write_rgb565_row<false, false>(byte, mask, step, row_width, source, columns, to_mono);
|
||||
}
|
||||
}
|
||||
this->x_low_ = clamp_at_most(this->x_low_, x_low);
|
||||
this->x_high_ = clamp_at_least(this->x_high_, x_high);
|
||||
this->y_low_ = clamp_at_most(this->y_low_, y_low);
|
||||
this->y_high_ = clamp_at_least(this->y_high_, y_high);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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);
|
||||
return;
|
||||
}
|
||||
this->draw_rgb565_block_(contiguous, x_start, y_start, w, h, ptr, order, big_endian, x_offset, y_offset, x_pad);
|
||||
}
|
||||
|
||||
void EPaperBase::dump_config() {
|
||||
LOG_DISPLAY("", "E-Paper SPI", this);
|
||||
ESP_LOGCONFIG(TAG,
|
||||
|
||||
@@ -107,6 +107,8 @@ class EPaperBase : public Display,
|
||||
int get_width() override { return this->effective_transform_ & SWAP_XY ? this->height_ : this->width_; }
|
||||
int get_height() override { return this->effective_transform_ & SWAP_XY ? this->width_ : this->height_; }
|
||||
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.
|
||||
@@ -128,6 +130,10 @@ 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);
|
||||
// Whether the buffer holds 1 bit per pixel as draw_pixel_at() writes it, so draw_pixels_at() can write it directly
|
||||
virtual bool buffer_is_mono() const { return false; }
|
||||
|
||||
/**
|
||||
* Methods that must be implemented by concrete classes to control the display
|
||||
|
||||
@@ -15,6 +15,7 @@ class EPaperMono : public EPaperBase {
|
||||
}
|
||||
|
||||
protected:
|
||||
bool buffer_is_mono() const override { return true; }
|
||||
void refresh_screen(bool partial) override;
|
||||
void power_on() override {}
|
||||
void power_off() override{};
|
||||
|
||||
@@ -58,7 +58,9 @@ bool HOT EPaperSSD1677::transfer_data() {
|
||||
// A full update ignores 0x26, and the copy may not hold a real frame yet (first update after
|
||||
// boot): send the new frame to both planes.
|
||||
const bool send_copy = this->plane_ == 0 && this->update_count_ != 0;
|
||||
const bool direct = send_copy || this->buffer_is_plane();
|
||||
// A mono buffer already holds the frame as a RAM plane does, so it can be sent as it is; a deeper
|
||||
// buffer (the four-level model) returns false and packs the plane in plane_row().
|
||||
const bool direct = send_copy || this->buffer_is_mono();
|
||||
const auto &source = send_copy ? this->sent_ : this->buffer_;
|
||||
const size_t row_length = this->plane_row_length_();
|
||||
const size_t plane_length = row_length * this->height_;
|
||||
|
||||
@@ -34,9 +34,6 @@ class EPaperSSD1677 : public EPaperMono {
|
||||
void init_comparison_frame_();
|
||||
// Bytes in one row of a RAM plane: 8 pixels per byte, whatever the buffer holds.
|
||||
size_t plane_row_length_() const { return (this->width_ + 7) / 8; }
|
||||
// Whether the buffer already holds the frame as a RAM plane does, so it can be sent as it is.
|
||||
// A subclass with a deeper buffer returns false and overrides plane_row().
|
||||
virtual bool buffer_is_plane() const { return true; }
|
||||
// Row y of the frame as a RAM plane holds it, 1 bit per pixel with 1 = white.
|
||||
virtual void plane_row(size_t y, uint8_t *out);
|
||||
bool reset() override;
|
||||
|
||||
@@ -38,9 +38,9 @@ class EPaperSSD1677Gray4 : public EPaperSSD1677 {
|
||||
void draw_pixel_at(int x, int y, Color color) override;
|
||||
|
||||
protected:
|
||||
bool buffer_is_mono() const override { return false; }
|
||||
// A partial update if partial updates are enabled and this is not a full one.
|
||||
bool is_partial_push_() const { return this->update_count_ != 0 && this->sent_.is_valid(); }
|
||||
bool buffer_is_plane() const override { return false; }
|
||||
void plane_row(size_t y, uint8_t *out) override;
|
||||
void refresh_screen(bool partial) override;
|
||||
bool transfer_data() override;
|
||||
|
||||
@@ -37,6 +37,7 @@ class EPaperUC8179 final : public EPaperBase {
|
||||
}
|
||||
|
||||
protected:
|
||||
bool buffer_is_mono() const override { return true; }
|
||||
bool initialise(bool partial) override;
|
||||
bool transfer_data() override;
|
||||
void refresh_screen(bool partial) override;
|
||||
|
||||
@@ -891,12 +891,10 @@ static uint8_t color_to_nibble(const Color &color) {
|
||||
if (color.raw_32 == 0xFFFFFFFF)
|
||||
return 0x0F; // white
|
||||
|
||||
// Derive luma from RGB using Rec.601 weights (0.299/0.587/0.114, scaled by
|
||||
// 256). Rec.601 is the standard for converting SDR images to grayscale and
|
||||
// spreads saturated colours across the mid-range; Rec.709 instead crams them
|
||||
// against white/black where the 16 panel levels are hard to tell apart.
|
||||
auto luma = static_cast<uint8_t>((77u * color.r + 150u * color.g + 29u * color.b + 128u) >> 8);
|
||||
return quantize_8bit_to_nibble(luma);
|
||||
// Rec.601 luma is the standard for converting SDR images to grayscale and spreads saturated colours
|
||||
// across the mid-range; Rec.709 instead crams them against white/black where the 16 panel levels are
|
||||
// hard to tell apart.
|
||||
return quantize_8bit_to_nibble(display::rec601_luma(color));
|
||||
}
|
||||
|
||||
// 4x4 ordered (Bayer) dither threshold over the weighted-luma range (0..65535).
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "../common.h"
|
||||
#include "esphome/components/epaper_spi/colorconv.h"
|
||||
#include "esphome/components/epaper_spi/epaper_spi_ssd1677_gray4.h"
|
||||
#include "esphome/components/epaper_spi/epaper_spi_ssd1683.h"
|
||||
|
||||
namespace esphome::epaper_spi::testing {
|
||||
|
||||
/// Exposes the buffer and the dirty bounds so the fast path can be compared with drawing pixel by pixel.
|
||||
template<typename Driver> class TestableDisplay : public Driver {
|
||||
public:
|
||||
TestableDisplay(uint16_t width, uint16_t height) : Driver("test", width, height, nullptr, 0) {
|
||||
EXPECT_TRUE(this->init_buffer_(this->buffer_length_));
|
||||
this->reset_bounds();
|
||||
}
|
||||
|
||||
void reset_bounds() {
|
||||
this->x_low_ = this->width_;
|
||||
this->y_low_ = this->height_;
|
||||
this->x_high_ = 0;
|
||||
this->y_high_ = 0;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> buffer() const {
|
||||
std::vector<uint8_t> out;
|
||||
for (size_t i = 0; i != this->buffer_length_; i++)
|
||||
out.push_back(this->buffer_[i]);
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Reallocates the buffer in blocks of at most max_block bytes, as SplitBuffer does when memory is fragmented.
|
||||
void split_buffer(size_t max_block) {
|
||||
ASSERT_TRUE(this->buffer_.init(this->buffer_length_, max_block));
|
||||
ASSERT_GT(this->buffer_.get_buffer_count(), 1u);
|
||||
this->clear();
|
||||
this->reset_bounds();
|
||||
}
|
||||
|
||||
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) {
|
||||
// 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);
|
||||
}
|
||||
};
|
||||
|
||||
/// RGB565 data with a spread of light and dark pixels, including values either side of the threshold.
|
||||
static std::vector<uint8_t> rgb565(size_t pixels, uint32_t seed) {
|
||||
std::vector<uint8_t> out;
|
||||
uint32_t state = seed;
|
||||
for (size_t i = 0; i != pixels; i++) {
|
||||
state = state * 1103515245u + 12345u;
|
||||
const uint16_t pixel = static_cast<uint16_t>(state >> 8);
|
||||
out.push_back(pixel & 0xFF);
|
||||
out.push_back(pixel >> 8);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Random bytes for a block of pixels, enough for 3 bytes a pixel.
|
||||
static std::vector<uint8_t> random_bytes(size_t pixels, uint32_t seed) {
|
||||
std::vector<uint8_t> out;
|
||||
uint32_t state = seed;
|
||||
for (size_t i = 0; i != pixels * 3; i++) {
|
||||
state = state * 1103515245u + 12345u;
|
||||
out.push_back(static_cast<uint8_t>(state >> 16));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/// For every rotation, mirroring and byte order, with source offsets and padding, and with blocks inside the panel,
|
||||
/// running off each edge and larger than the panel, drawing an RGB565 block directly gives the same buffer and the same
|
||||
/// dirty area as drawing it pixel by pixel. The panel width is not a multiple of 8.
|
||||
TEST(EPaperDrawPixels, SameResultAsDrawingPixelByPixel) {
|
||||
struct Block {
|
||||
int x, y, w, h;
|
||||
};
|
||||
const Block blocks[] = {{6, 5, 12, 6}, {-3, -2, 10, 8}, {14, 8, 12, 9}, {-5, 4, 30, 3}, {-2, -3, 26, 20}};
|
||||
const display::DisplayRotation rotations[] = {
|
||||
display::DISPLAY_ROTATION_0_DEGREES, display::DISPLAY_ROTATION_90_DEGREES, display::DISPLAY_ROTATION_180_DEGREES,
|
||||
display::DISPLAY_ROTATION_270_DEGREES};
|
||||
// Swapping the axes comes from the 90 and 270 degree rotations
|
||||
const uint8_t mirrorings[] = {NONE, MIRROR_X, MIRROR_Y, MIRROR_X | MIRROR_Y};
|
||||
uint32_t seed = 1;
|
||||
for (const auto rotation : rotations) {
|
||||
for (const uint8_t mirroring : mirrorings) {
|
||||
for (const bool big_endian : {false, true}) {
|
||||
for (const auto &block : blocks) {
|
||||
TestableDisplay<EPaperSSD1683> fast(21, 13);
|
||||
TestableDisplay<EPaperSSD1683> slow(21, 13);
|
||||
for (auto *display : {&fast, &slow}) {
|
||||
display->set_transform(mirroring);
|
||||
display->set_rotation(rotation);
|
||||
}
|
||||
// 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);
|
||||
const auto where = ::testing::Message() << "rotation " << rotation << " mirroring " << int(mirroring)
|
||||
<< " big endian " << big_endian << " block " << block.x << ","
|
||||
<< block.y << " " << block.w << "x" << block.h;
|
||||
EXPECT_EQ(fast.buffer(), slow.buffer()) << where;
|
||||
EXPECT_EQ(fast.bounds(), slow.bounds()) << where;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// With a clipping region set the block goes through draw_pixel_at(), which honours it.
|
||||
TEST(EPaperDrawPixels, ClippingFallsBackToDrawingPixelByPixel) {
|
||||
TestableDisplay<EPaperSSD1683> fast(24, 16);
|
||||
TestableDisplay<EPaperSSD1683> slow(24, 16);
|
||||
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);
|
||||
EXPECT_EQ(fast.buffer(), slow.buffer());
|
||||
EXPECT_EQ(fast.bounds(), (std::vector<uint16_t>{4, 4, 12, 10}));
|
||||
}
|
||||
|
||||
/// A driver whose buffer is not 1 bit per pixel keeps its own draw_pixel_at() for blocks too.
|
||||
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);
|
||||
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) {
|
||||
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;
|
||||
for (uint32_t value = 0; value != 0x10000; value++) {
|
||||
const uint8_t expected = color_to_mono(ColorUtil::to_color(value, order, display::COLOR_BITNESS_565));
|
||||
if (to_mono(static_cast<uint16_t>(value)) != expected)
|
||||
mismatches++;
|
||||
}
|
||||
EXPECT_EQ(mismatches, 0u) << "order " << int(order);
|
||||
}
|
||||
}
|
||||
|
||||
/// Other bit depths, and a buffer split into several blocks, go through draw_pixel_at() and give the same result.
|
||||
TEST(EPaperDrawPixels, OtherCasesDrawPixelByPixel) {
|
||||
{
|
||||
TestableDisplay<EPaperSSD1683> fast(24, 16);
|
||||
TestableDisplay<EPaperSSD1683> slow(24, 16);
|
||||
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);
|
||||
EXPECT_EQ(fast.buffer(), slow.buffer()) << "split buffer";
|
||||
EXPECT_EQ(fast.bounds(), slow.bounds()) << "split buffer";
|
||||
}
|
||||
for (const auto bitness : {display::COLOR_BITNESS_888, display::COLOR_BITNESS_332}) {
|
||||
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);
|
||||
EXPECT_EQ(fast.buffer(), slow.buffer()) << "bitness " << int(bitness);
|
||||
EXPECT_EQ(fast.bounds(), slow.bounds()) << "bitness " << int(bitness);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::epaper_spi::testing
|
||||
Reference in New Issue
Block a user