ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
image.cpp
Go to the documentation of this file.
1#include "image.h"
2
3#include <algorithm>
4
5#include "esphome/core/hal.h"
7
8ESPHOME_LOG_TAG(TAG, "image");
9
10namespace esphome::image {
11
12// Walks the clipped range with rows (ROWS_OUTER) or columns outer and hands the
13// body its coordinates and row major index. The body must be ESPHOME_ALWAYS_INLINE,
14// at -Os GCC otherwise calls it once per pixel.
15template<bool ROWS_OUTER, typename F>
16static void for_each_pixel(int x_start, int x_end, int y_start, int y_end, int width, F &&f) {
17 const int outer_start = ROWS_OUTER ? y_start : x_start;
18 const int outer_end = ROWS_OUTER ? y_end : x_end;
19 const int inner_start = ROWS_OUTER ? x_start : y_start;
20 const int inner_end = ROWS_OUTER ? x_end : y_end;
21 for (int outer = outer_start; outer < outer_end; outer++) {
22 const int outer_pos = ROWS_OUTER ? outer * width : outer;
23 for (int inner = inner_start; inner < inner_end; inner++) {
24 f(ROWS_OUTER ? inner : outer, ROWS_OUTER ? outer : inner,
25 ROWS_OUTER ? outer_pos + inner : outer_pos + inner * width);
26 }
27 }
28}
29
30void Image::draw(int x, int y, display::Display *display, Color color_on, Color color_off) {
31 int x_start = 0;
32 int y_start = 0;
33 int x_end = width_;
34 int y_end = height_;
35
36 auto clipping = display->get_clipping();
37 if (clipping.is_set()) {
38 if (clipping.x > x)
39 x_start += clipping.x - x;
40 if (clipping.y > y)
41 y_start += clipping.y - y;
42 if (x_end > clipping.x2() - x)
43 x_end = clipping.x2() - x;
44 if (y_end > clipping.y2() - y)
45 y_end = clipping.y2() - y;
46 }
47
48#ifndef USE_ESP8266
49 // Opaque RGB565 and RGB are already laid out for draw_pixels_at(), so the display
50 // gets the whole window. Not on the ESP8266, whose image bytes need progmem_read_byte().
52 (this->type_ == IMAGE_TYPE_RGB565 || this->type_ == IMAGE_TYPE_RGB)) {
53 // Keep the block on screen
54 x_start = std::max(x_start, -x);
55 y_start = std::max(y_start, -y);
56 x_end = std::min(x_end, display->get_width() - x);
57 y_end = std::min(y_end, display->get_height() - y);
58 if (x_end <= x_start || y_end <= y_start)
59 return;
60 const bool rgb565 = this->type_ == IMAGE_TYPE_RGB565;
61 display->draw_pixels_at(x + x_start, y + y_start, x_end - x_start, y_end - y_start, this->data_start_,
63 x_start, y_start, this->width_ - x_end);
64 return;
65 }
66#endif
67
68 // Rows keep the read and the write sequential; when the display swaps the axes
69 // columns keep the write sequential instead, and a strided write costs more.
70#ifdef USE_PSRAM
71 if (display->pixel_axes_swapped()) {
72 this->draw_<false>(x, y, display, color_on, color_off, x_start, x_end, y_start, y_end);
73 return;
74 }
75#endif
76 // Without PSRAM a strided write is free, so one copy of the loops is enough
77 this->draw_<true>(x, y, display, color_on, color_off, x_start, x_end, y_start, y_end);
78}
79
80template<bool ROWS_OUTER>
81void Image::draw_(int x, int y, display::Display *display, Color color_on, Color color_off, int x_start, int x_end,
82 int y_start, int y_end) {
83 // Local copies, or the virtual draw_pixel_at() forces a reload every pixel
84 const Transparency transparency = this->transparency_;
85 const int width = this->width_;
86 switch (this->type_) {
88 for_each_pixel<ROWS_OUTER>(x_start, x_end, y_start, y_end, width,
89 [&](int img_x, int img_y, int pos) ESPHOME_ALWAYS_INLINE {
90 if (this->get_binary_pixel_(img_x, img_y)) {
91 display->draw_pixel_at(x + img_x, y + img_y, color_on);
92 } else if (transparency == TRANSPARENCY_OPAQUE) {
93 display->draw_pixel_at(x + img_x, y + img_y, color_off);
94 }
95 });
96 break;
98 for_each_pixel<ROWS_OUTER>(x_start, x_end, y_start, y_end, width,
99 [&](int img_x, int img_y, int pos) ESPHOME_ALWAYS_INLINE {
100 auto color = this->get_rgb111_pixel_(img_x, img_y);
101 if (color.w >= 0x80) {
102 display->draw_pixel_at(x + img_x, y + img_y, color);
103 }
104 });
105 break;
107 const uint8_t *const data = this->data_start_;
108 for_each_pixel<ROWS_OUTER>(x_start, x_end, y_start, y_end, width,
109 [&](int img_x, int img_y, int pos) ESPHOME_ALWAYS_INLINE {
110 const uint8_t gray = progmem_read_byte(data + pos);
111 Color color = Color(gray, gray, gray, 0xFF);
112 if (transparency == TRANSPARENCY_CHROMA_KEY) {
113 if (gray == 1) {
114 return; // transparent pixel, skip drawing
115 }
116 } else if (transparency == TRANSPARENCY_ALPHA_CHANNEL) {
117 // gray is the alpha: blend from color_off to color_on, drawn opaque
118 color = Color(Color::blend_channel(color_off.r, color_on.r, gray),
119 Color::blend_channel(color_off.g, color_on.g, gray),
120 Color::blend_channel(color_off.b, color_on.b, gray), 0xFF);
121 }
122 display->draw_pixel_at(x + img_x, y + img_y, color);
123 });
124 break;
125 }
127 for_each_pixel<ROWS_OUTER>(x_start, x_end, y_start, y_end, width,
128 [&](int img_x, int img_y, int pos) ESPHOME_ALWAYS_INLINE {
129 auto color = this->get_rgb565_pixel_(img_x, img_y);
130 if (color.w >= 0x80) {
131 display->draw_pixel_at(x + img_x, y + img_y, color);
132 }
133 });
134 break;
135 case IMAGE_TYPE_RGB:
136 for_each_pixel<ROWS_OUTER>(x_start, x_end, y_start, y_end, width,
137 [&](int img_x, int img_y, int pos) ESPHOME_ALWAYS_INLINE {
138 auto color = this->get_rgb_pixel_(img_x, img_y);
139 if (color.w >= 0x80) {
140 display->draw_pixel_at(x + img_x, y + img_y, color);
141 }
142 });
143 break;
144 }
145}
146Color Image::get_pixel(int x, int y, const Color color_on, const Color color_off) const {
147 if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
148 return color_off;
149 switch (this->type_) {
151 if (this->get_binary_pixel_(x, y))
152 return color_on;
153 return color_off;
155 return this->get_rgb111_pixel_(x, y);
157 return this->get_grayscale_pixel_(x, y);
159 return this->get_rgb565_pixel_(x, y);
160 case IMAGE_TYPE_RGB:
161 return this->get_rgb_pixel_(x, y);
162 default:
163 return color_off;
164 }
165}
166#ifdef USE_LVGL
167lv_image_dsc_t *Image::get_lv_image_dsc() {
168 // lazily construct lvgl image_dsc.
169 if (this->dsc_.data != this->data_start_) {
170 this->dsc_.data = this->data_start_;
171 this->dsc_.header.reserved_2 = 0;
172 this->dsc_.header.stride = this->get_width_stride();
173 this->dsc_.header.w = this->width_;
174 this->dsc_.header.h = this->height_;
175 this->dsc_.data_size = this->get_width_stride() * this->get_height();
176 switch (this->get_type()) {
178 this->dsc_.header.cf = LV_COLOR_FORMAT_A1;
179 break;
180
182 this->dsc_.header.cf = LV_COLOR_FORMAT_A8;
183 break;
184
186 // Rejected by the lvgl config validation
187 ESP_LOGE(TAG, "RGB111 images are not supported in LVGL.");
188 break;
189 case IMAGE_TYPE_RGB:
190 switch (this->transparency_) {
192 this->dsc_.header.cf = LV_COLOR_FORMAT_ARGB8888;
193 break;
195 default:
196 this->dsc_.header.cf = LV_COLOR_FORMAT_RGB888;
197 break;
198 }
199 break;
200
202 switch (this->transparency_) {
204 this->dsc_.header.cf = LV_COLOR_FORMAT_RGB565A8;
205 break;
206 default:
207 this->dsc_.header.cf = LV_COLOR_FORMAT_RGB565;
208 }
209 break;
210 }
211 }
212 return &this->dsc_;
213}
214#endif // USE_LVGL
215
216bool Image::get_binary_pixel_(int x, int y) const {
217 const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u;
218 const uint32_t pos = x + y * width_8;
219 return progmem_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u));
220}
222 const uint32_t bitpos = y * this->get_width_stride() * 8 + x * this->bpp_;
223 const uint8_t *byte = this->data_start_ + bitpos / 8;
224 const uint8_t shift = bitpos % 8;
225 // A pixel can straddle two bytes; read the second one only when it does.
226 uint16_t window = progmem_read_byte(byte) << 8;
227 if (shift + this->bpp_ > 8)
228 window |= progmem_read_byte(byte + 1);
229 const uint8_t bits = (window >> (16 - this->bpp_ - shift)) & ((1u << this->bpp_) - 1);
230 const uint8_t rgb = this->bpp_ == 4 ? bits >> 1 : bits;
231 const bool opaque = this->bpp_ == 3 || (bits & 1);
232 return Color(rgb & 4 ? 0xFF : 0x00, rgb & 2 ? 0xFF : 0x00, rgb & 1 ? 0xFF : 0x00, opaque ? 0xFF : 0x00);
233}
235 const uint32_t pos = (x + y * this->width_) * static_cast<size_t>(this->bpp_) / 8;
236 Color color = Color(progmem_read_byte(this->data_start_ + pos + 2), progmem_read_byte(this->data_start_ + pos + 1),
237 progmem_read_byte(this->data_start_ + pos + 0), 0xFF);
238
239 switch (this->transparency_) {
241 if (color.g == 1 && color.r == 0 && color.b == 0) {
242 // (0, 1, 0) has been defined as transparent color for non-alpha images.
243 color.w = 0;
244 }
245 break;
247 color.w = progmem_read_byte(this->data_start_ + (pos + 3));
248 break;
249 default:
250 break;
251 }
252 return color;
253}
255 const uint8_t *pos = this->data_start_ + (x + y * this->width_) * static_cast<size_t>(this->bpp_) / 8;
256 // High byte first when big endian
257 const uint8_t high = this->big_endian_ ? 0 : 1;
258 const uint16_t rgb565 = encode_uint16(progmem_read_byte(pos + high), progmem_read_byte(pos + 1 - high));
259 auto r = (rgb565 & 0xF800) >> 11;
260 auto g = (rgb565 & 0x07E0) >> 5;
261 auto b = rgb565 & 0x001F;
262 auto a = 0xFF;
263 switch (this->transparency_) {
265 a = progmem_read_byte(this->data_start_ + this->width_ * this->height_ * 2 + (x + y * this->width_));
266 break;
268 if (rgb565 == 0x0020)
269 a = 0;
270 break;
271 default:
272 break;
273 }
274 return Color((r << 3) | (r >> 2), (g << 2) | (g >> 4), (b << 3) | (b >> 2), a);
275}
276
278 const uint32_t pos = (x + y * this->width_);
279 const uint8_t gray = progmem_read_byte(this->data_start_ + pos);
280 switch (this->transparency_) {
282 if (gray == 1)
283 return Color(0, 0, 0, 0);
284 return Color(gray, gray, gray, 0xFF);
286 return Color(0, 0, 0, gray);
287 default:
288 return Color(gray, gray, gray, 0xFF);
289 }
290}
291int Image::get_width() const { return this->width_; }
292int Image::get_height() const { return this->height_; }
293ImageType Image::get_type() const { return this->type_; }
294Image::Image(const uint8_t *data_start, int width, int height, ImageType type, Transparency transparency,
295 bool big_endian)
296 : width_(width),
297 height_(height),
298 type_(type),
299 data_start_(data_start),
300 transparency_(transparency),
301 big_endian_(big_endian) {
302 switch (this->type_) {
304 this->bpp_ = 1;
305 break;
307 this->bpp_ = this->transparency_ == TRANSPARENCY_ALPHA_CHANNEL ? 4 : 3;
308 break;
310 this->bpp_ = 8;
311 break;
313 this->bpp_ = 16;
314 break;
315 case IMAGE_TYPE_RGB:
316 this->bpp_ = this->transparency_ == TRANSPARENCY_ALPHA_CHANNEL ? 32 : 24;
317 break;
318 }
319}
320
321} // namespace esphome::image
virtual bool pixel_axes_swapped() const
True when draw_pixel_at() swaps x and y before writing, so pixels that are neighbours along x land a ...
Definition display.h:757
virtual int get_height()
Get the calculated height of the display in pixels with rotation applied.
Definition display.h:338
virtual int get_width()
Get the calculated width of the display in pixels with rotation applied.
Definition display.h:336
void draw_pixel_at(int x, int y)
Set a single pixel at the specified coordinates to default color.
Definition display.h:346
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)
Given an array of pixels encoded in the nominated format, draw these into the display's buffer.
Definition display.h:370
Rect get_clipping() const
Get the current the clipping rectangle.
Definition display.cpp:826
Color get_rgb565_pixel_(int x, int y) const
Definition image.cpp:254
const uint8_t * data_start_
Definition image.h:62
Color get_rgb_pixel_(int x, int y) const
Definition image.cpp:234
size_t get_width_stride() const
Return the stride of the image in bytes, that is, the distance in bytes between two consecutive rows ...
Definition image.h:40
Color get_pixel(int x, int y, Color color_on=display::COLOR_ON, Color color_off=display::COLOR_OFF) const
Definition image.cpp:146
ImageType type_
Definition image.h:61
int get_height() const override
Definition image.cpp:292
ImageType get_type() const
Definition image.cpp:293
bool get_binary_pixel_(int x, int y) const
Definition image.cpp:216
Image(const uint8_t *data_start, int width, int height, ImageType type, Transparency transparency, bool big_endian=false)
big_endian is the RGB565 byte order; codegen writes little endian unless configured otherwise.
Definition image.cpp:294
void draw_(int x, int y, display::Display *display, Color color_on, Color color_off, int x_start, int x_end, int y_start, int y_end)
Per-pixel draw of the clipped range, rows (true) or columns (false) outer.
Definition image.cpp:81
Transparency transparency_
Definition image.h:63
lv_img_dsc_t dsc_
Definition image.h:67
Color get_rgb111_pixel_(int x, int y) const
Definition image.cpp:221
lv_image_dsc_t * get_lv_image_dsc()
Definition image.cpp:167
void draw(int x, int y, display::Display *display, Color color_on, Color color_off) override
Definition image.cpp:30
int get_width() const override
Definition image.cpp:291
Color get_grayscale_pixel_(int x, int y) const
Definition image.cpp:277
uint8_t type
ESPHOME_LOG_TAG(TAG, "image")
@ TRANSPARENCY_ALPHA_CHANNEL
Definition image.h:22
@ TRANSPARENCY_CHROMA_KEY
Definition image.h:21
@ TRANSPARENCY_OPAQUE
Definition image.h:20
@ IMAGE_TYPE_GRAYSCALE
Definition image.h:13
@ IMAGE_TYPE_BINARY
Definition image.h:12
@ IMAGE_TYPE_RGB565
Definition image.h:15
@ IMAGE_TYPE_RGB111
Definition image.h:16
@ IMAGE_TYPE_RGB
Definition image.h:14
size_t size_t pos
Definition helpers.h:1255
constexpr uint16_t encode_uint16(uint8_t msb, uint8_t lsb)
Encode a 16-bit value given the most and least significant byte.
Definition helpers.h:998
uint8_t progmem_read_byte(const uint8_t *addr)
Definition hal.h:51
static void uint32_t
uint8_t w
Definition color.h:42
static uint8_t blend_channel(uint8_t from, uint8_t to, uint8_t amnt) ESPHOME_ALWAYS_INLINE
One channel of gradient(): from at amnt 0 to to at amnt 255.
Definition color.h:180
uint8_t g
Definition color.h:34
uint8_t b
Definition color.h:38
uint8_t r
Definition color.h:30
uint16_t x
Definition tt21100.cpp:5
uint16_t y
Definition tt21100.cpp:6