ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
display_color_utils.h
Go to the documentation of this file.
1#pragma once
2#include <array>
3#include <cstdint>
6
7namespace esphome::display {
10inline static uint8_t esp_scale(uint8_t i, uint8_t scale, uint8_t max_value = 255) { return (max_value * i / scale); }
11
14constexpr uint8_t expand_field(uint8_t value, uint8_t bits) {
15 switch (bits) {
16 case 2:
17 return (value * 170) >> 1;
18 case 3:
19 return (value * 583) >> 4;
20 case 5:
21 return (value * 1053) >> 7;
22 case 6:
23 return (value * 4145) >> 10;
24 case 8:
25 return value;
26 default:
27 return esp_scale(value, (1 << bits) - 1);
28 }
29}
30
31// Remove before 2027.5.0, with the forms that take a color order. Maps a color read in RGB order to the
32// given order, by moving its channels.
34 switch (order) {
35 case COLOR_ORDER_BGR:
36 return Color(color.b, color.g, color.r);
37 case COLOR_ORDER_GRB:
38 return Color(color.g, color.r, color.b);
39 default:
40 return color;
41 }
42}
43
44class ColorUtil {
45 public:
50 template<ColorBitness BITNESS>
51 static ESPHOME_ALWAYS_INLINE uint32_t read_packed(const uint8_t *ptr, size_t index, bool big_endian) {
52 if constexpr (BITNESS == COLOR_BITNESS_565) {
53 const size_t idx = index * 2;
54 if (big_endian)
55 return (ptr[idx] << 8) + ptr[idx + 1];
56 return ptr[idx] + (ptr[idx + 1] << 8);
57 } else if constexpr (BITNESS == COLOR_BITNESS_888) {
58 // Always the LVGL RGB888 layout, b,g,r in memory; byte order applies to 565 only
59 const size_t idx = index * 3;
60 return ptr[idx + 0] + (ptr[idx + 1] << 8) + (ptr[idx + 2] << 16);
61 } else {
62 return ptr[index];
63 }
64 }
65
68 bool right_bit_aligned = true) {
69 uint8_t first_bits = 0;
70 uint8_t second_bits = 0;
71 uint8_t third_bits = 0;
72
73 switch (color_bitness) {
75 first_bits = 8;
76 second_bits = 8;
77 third_bits = 8;
78 break;
80 first_bits = 5;
81 second_bits = 6;
82 third_bits = 5;
83 break;
85 first_bits = 3;
86 second_bits = 3;
87 third_bits = 2;
88 break;
89 }
90
91 const uint8_t red =
92 right_bit_aligned
93 ? expand_field((colorcode >> (second_bits + third_bits)) & ((1 << first_bits) - 1), first_bits)
94 : esp_scale(((colorcode >> 16) & 0xFF), (1 << first_bits) - 1);
95
96 const uint8_t green = right_bit_aligned
97 ? expand_field((colorcode >> third_bits) & ((1 << second_bits) - 1), second_bits)
98 : esp_scale(((colorcode >> 8) & 0xFF), ((1 << second_bits) - 1));
99
100 const uint8_t blue = right_bit_aligned ? expand_field(colorcode & ((1 << third_bits) - 1), third_bits)
101 : esp_scale(((colorcode >> 0) & 0xFF), (1 << third_bits) - 1);
102
103 return Color(red, green, blue);
104 }
105 // Remove before 2027.5.0
106 ESPDEPRECATED("Packed colors are always RGB, drop the ColorOrder argument. Removed in 2027.5.0", "2026.11.0")
108 ColorBitness color_bitness = ColorBitness::COLOR_BITNESS_888, bool right_bit_aligned = true) {
109 return apply_color_order(to_color(colorcode, color_bitness, right_bit_aligned), color_order);
110 }
111 static inline Color rgb332_to_color(uint8_t rgb332_color) {
112 return to_color((uint32_t) rgb332_color, COLOR_BITNESS_332);
113 }
114 static uint8_t color_to_332(Color color) {
115 const uint16_t red_color = esp_scale8(color.red, ((1 << 3) - 1));
116 const uint16_t green_color = esp_scale8(color.green, ((1 << 3) - 1));
117 const uint16_t blue_color = esp_scale8(color.blue, (1 << 2) - 1);
118 return red_color << 5 | green_color << 2 | blue_color;
119 }
120 // Remove before 2027.5.0
121 ESPDEPRECATED("Packed colors are always RGB, drop the ColorOrder argument. Removed in 2027.5.0", "2026.11.0")
122 static uint8_t color_to_332(Color color, ColorOrder color_order) {
124 return color_to_332(color);
125 // The other orders move the 2 bit field, so they cannot reuse the RGB packing
126 const uint16_t red_color = esp_scale8(color.red, ((1 << 3) - 1));
127 const uint16_t green_color = esp_scale8(color.green, ((1 << 3) - 1));
129 return esp_scale8(color.blue, (1 << 2) - 1) << 6 | green_color << 3 | red_color;
130 return green_color << 5 | red_color << 2 | esp_scale8(color.blue, (1 << 2) - 1);
131 }
132 static uint16_t color_to_565(Color color) {
133 const uint16_t red_color = esp_scale8(color.red, ((1 << 5) - 1));
134 const uint16_t green_color = esp_scale8(color.green, ((1 << 6) - 1));
135 const uint16_t blue_color = esp_scale8(color.blue, (1 << 5) - 1);
136 return red_color << 11 | green_color << 5 | blue_color;
137 }
138 // Remove before 2027.5.0
139 ESPDEPRECATED("Packed colors are always RGB, drop the ColorOrder argument. Removed in 2027.5.0", "2026.11.0")
140 static uint16_t color_to_565(Color color, ColorOrder color_order) {
141 switch (color_order) {
142 case COLOR_ORDER_BGR:
143 return color_to_565(Color(color.b, color.g, color.r));
144 case COLOR_ORDER_GRB:
145 // Green moves to the 5 bit field at the top, so it cannot reuse the RGB packing
146 return esp_scale8(color.green, (1 << 6) - 1) << 10 | esp_scale8(color.red, (1 << 5) - 1) << 5 |
147 esp_scale8(color.blue, (1 << 5) - 1);
148 default:
149 return color_to_565(color);
150 }
151 }
152 static uint32_t color_to_grayscale4(Color color) {
153 uint32_t gs4 = esp_scale8(color.white, 15);
154 return gs4;
155 }
156 /***
157 * Converts a Color value to an 8bit index using a 24bit 888 palette.
158 * Uses euclidiean distance to calculate the linear distance between
159 * two points in an RGB cube, then iterates through the full palette
160 * returning the closest match.
161 * @param[in] color The target color.
162 * @param[in] palette The 256*3 byte RGB palette.
163 * @return The 8 bit index of the closest color (e.g. for display buffer).
164 */
165 // static uint8_t color_to_index8_palette888(Color color, uint8_t *palette) {
166 static uint8_t color_to_index8_palette888(Color color, const uint8_t *palette) {
167 uint8_t closest_index = 0;
168 uint32_t minimum_dist2 = UINT32_MAX; // Smallest distance^2 to the target
169 // so far
170 // int8_t(*plt)[][3] = palette;
171 int16_t tgt_r = color.r;
172 int16_t tgt_g = color.g;
173 int16_t tgt_b = color.b;
174 uint16_t x, y, z;
175 // Loop through each row of the palette
176 for (uint16_t i = 0; i < 256; i++) {
177 // Get the pallet rgb color
178 int16_t plt_r = (int16_t) palette[i * 3 + 0];
179 int16_t plt_g = (int16_t) palette[i * 3 + 1];
180 int16_t plt_b = (int16_t) palette[i * 3 + 2];
181 // Calculate euclidean distance (linear distance in rgb cube).
182 x = (uint32_t) std::abs(tgt_r - plt_r);
183 y = (uint32_t) std::abs(tgt_g - plt_g);
184 z = (uint32_t) std::abs(tgt_b - plt_b);
185 uint32_t dist2 = x * x + y * y + z * z;
186 if (dist2 < minimum_dist2) {
187 minimum_dist2 = dist2;
188 closest_index = (uint8_t) i;
189 }
190 }
191 return closest_index;
192 }
193 /***
194 * Converts an 8bit palette index (e.g. from a display buffer) to a color.
195 * @param[in] index The index to look up.
196 * @param[in] palette The 256*3 byte RGB palette.
197 * @return The RGBW Color object looked up by the palette.
198 */
199 static Color index8_to_color_palette888(uint8_t index, const uint8_t *palette) {
200 Color color = Color(palette[index * 3 + 0], palette[index * 3 + 1], palette[index * 3 + 2], 0);
201 return color;
202 }
203};
204
206static constexpr uint16_t REC601_LUMA_R = 77;
207static constexpr uint16_t REC601_LUMA_G = 150;
208static constexpr uint16_t REC601_LUMA_B = 29;
209
211constexpr uint8_t rec601_luma(Color color) {
212 return (uint8_t) ((REC601_LUMA_R * color.r + REC601_LUMA_G * color.g + REC601_LUMA_B * color.b + 128u) >> 8);
213}
214
216constexpr uint8_t color_to_mono(Color color) { return rec601_luma(color) >= 128 ? 1 : 0; }
217
222 public:
223 Rgb565ToMono() { this->fill_tables_(REC601_LUMA_R, REC601_LUMA_G, REC601_LUMA_B); }
224 // Remove before 2027.5.0
225 ESPDEPRECATED("Packed colors are always RGB, drop the ColorOrder argument. Removed in 2027.5.0", "2026.11.0")
226 explicit Rgb565ToMono(ColorOrder order) {
227 // The weight of the channel each 5, 6 and 5 bit field holds in this order
228 const Color weights = apply_color_order(Color(REC601_LUMA_R, REC601_LUMA_G, REC601_LUMA_B), order);
229 this->fill_tables_(weights.r, weights.g, weights.b);
230 }
231
232 uint8_t operator()(uint16_t value) const {
233 const uint32_t weighted = this->high_[value >> 11] + this->middle_[(value >> 5) & 0x3F] + this->low_[value & 0x1F];
234 return ((weighted + 128u) >> 8) >= 128 ? 1 : 0;
235 }
236
237 protected:
238 // Each field value scaled to 0..255, then weighted
239 void fill_tables_(uint16_t high, uint16_t middle, uint16_t low) {
240 for (unsigned i = 0; i != 32; i++) {
241 this->high_[i] = high * esp_scale(i, 31);
242 this->low_[i] = low * esp_scale(i, 31);
243 }
244 for (unsigned i = 0; i != 64; i++)
245 this->middle_[i] = middle * esp_scale(i, 63);
246 }
247
248 // Weighted contribution of each value of each field
249 std::array<uint16_t, 32> high_{};
250 std::array<uint16_t, 64> middle_{};
251 std::array<uint16_t, 32> low_{};
252};
253
254} // namespace esphome::display
static Color to_color(uint32_t colorcode, ColorBitness color_bitness=ColorBitness::COLOR_BITNESS_888, bool right_bit_aligned=true)
Unpacks a color of the given bitness, red in the most significant bits.
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 ColorBitness color_bitness
static ESPHOME_ALWAYS_INLINE uint32_t read_packed(const uint8_t *ptr, size_t index, bool big_endian)
Reads one packed pixel of the given bitness from a source buffer into the value to_color() takes.
color_to_mono() of an RGB565 value as ColorUtil::to_color() reads it, from table lookups instead of a...
std::array< uint16_t, 32 > low_
void fill_tables_(uint16_t high, uint16_t middle, uint16_t low)
std::array< uint16_t, 32 > high_
std::array< uint16_t, 64 > middle_
ESPDEPRECATED("Packed colors are always RGB, drop the ColorOrder argument. Removed in 2027.5.0", "2026.11.0") explicit Rgb565ToMono(ColorOrder order)
uint8_t operator()(uint16_t value) const
bool z
Definition msa3xx.h:1
constexpr uint8_t rec601_luma(Color color)
Rec.601 luma of an RGB color, for optimum perceptual brightness.
static void int int int int const uint8_t ColorOrder ColorBitness bool big_endian
Definition display.cpp:101
constexpr uint8_t expand_field(uint8_t value, uint8_t bits)
esp_scale(value, (1 << bits) - 1) for a field of 2, 3, 5, 6 or 8 bits, as a multiply and shift that g...
static void int int int int const uint8_t ColorOrder order
Definition display.cpp:100
Color apply_color_order(Color color, ColorOrder order)
constexpr uint8_t color_to_mono(Color color)
Map an RGB color to a single monochrome bit: 1 = white, 0 = black.
static void int int int int const uint8_t * ptr
Definition display.cpp:63
static void uint32_t
uint8_t red
Definition color.h:31
uint8_t g
Definition color.h:34
uint8_t green
Definition color.h:35
uint8_t b
Definition color.h:38
uint8_t r
Definition color.h:30
uint8_t blue
Definition color.h:39
uint16_t x
Definition tt21100.cpp:5
uint16_t y
Definition tt21100.cpp:6