ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
jpeg_decoder.cpp
Go to the documentation of this file.
1#include "jpeg_decoder.h"
2#ifdef USE_RUNTIME_IMAGE_JPEG
3
9#include "esphome/core/log.h"
10
11#ifdef USE_ESP_IDF
12#include "esp_task_wdt.h"
13#endif
14
15ESPHOME_LOG_TAG(TAG, "image_decoder.jpeg");
16
17namespace esphome::runtime_image {
18
19bool JpegDecoder::prepare_rgb565_scaling_(int width, int height) {
20 if (this->x_boundaries_.capacity() < static_cast<size_t>(width + 1) && !this->x_boundaries_.try_init(width + 1)) {
21 return false;
22 }
23 if (this->y_boundaries_.capacity() < static_cast<size_t>(height + 1) && !this->y_boundaries_.try_init(height + 1)) {
24 return false;
25 }
26
27 this->source_width_ = width;
28 this->source_height_ = height;
29 this->x_boundaries_.clear();
30 this->y_boundaries_.clear();
31 const uint32_t target_width = this->image_->get_buffer_width();
32 const uint32_t target_height = this->image_->get_buffer_height();
33 // Boundary n is the first target pixel source pixel n covers, rounded up. When shrinking, the source
34 // pixel whose span lands on a target pixel paints it, so an 8 to 4 scale keeps columns 0, 2, 4 and 6;
35 // the generic draw() path rounds down and keeps the odd ones instead
36 for (uint32_t x = 0; x <= static_cast<uint32_t>(width); x++) {
37 this->x_boundaries_.push_back(static_cast<uint16_t>((x * target_width + width - 1) / width));
38 }
39 for (uint32_t y = 0; y <= static_cast<uint32_t>(height); y++) {
40 this->y_boundaries_.push_back(static_cast<uint16_t>((y * target_height + height - 1) / height));
41 }
42 return true;
43}
44
45void JpegDecoder::draw_rgb565(int x, int y, int w, int h, const uint8_t *pixels) {
46 if (pixels == nullptr) {
47 return;
48 }
49 uint8_t *const buffer = this->image_buffer_();
50 const uint32_t buffer_width = this->image_->get_buffer_width();
51 // JPEGDEC gives little-endian words; a big-endian image stores the high byte first
52 const uint8_t first = this->image_is_big_endian_() ? 1 : 0;
53 for (int source_y = 0; source_y < h; source_y++) {
54 const int absolute_y = y + source_y;
55 if (absolute_y < 0 || absolute_y >= this->source_height_) {
56 continue;
57 }
58 const uint16_t y1 = this->y_boundaries_[absolute_y];
59 const uint16_t y2 = this->y_boundaries_[absolute_y + 1];
60 for (int source_x = 0; source_x < w; source_x++) {
61 const int absolute_x = x + source_x;
62 if (absolute_x < 0 || absolute_x >= this->source_width_) {
63 continue;
64 }
65 const uint16_t x1 = this->x_boundaries_[absolute_x];
66 const uint16_t x2 = this->x_boundaries_[absolute_x + 1];
67 const uint8_t *pixel = pixels + (source_y * w + source_x) * 2;
68 for (uint16_t destination_y = y1; destination_y < y2; destination_y++) {
69 uint8_t *destination = buffer + (destination_y * buffer_width + x1) * 2;
70 for (uint16_t destination_x = x1; destination_x < x2; destination_x++) {
71 destination[0] = pixel[first];
72 destination[1] = pixel[1 - first];
73 destination += 2;
74 }
75 }
76 }
77 }
78}
79
86static int draw_callback(JPEGDRAW *jpeg) {
87 JpegDecoder *decoder = (JpegDecoder *) jpeg->pUser;
88
89 if (decoder == nullptr) {
90 ESP_LOGE(TAG, "Decoder pointer is null!");
91 return 0;
92 }
93
94 // Some very big images take too long to decode, so feed the watchdog on each callback
95 // to avoid crashing if the executing task has a watchdog enabled.
96#ifdef USE_ESP_IDF
97 if (esp_task_wdt_status(nullptr) == ESP_OK) {
98#endif
99 App.feed_wdt();
100#ifdef USE_ESP_IDF
101 }
102#endif
103 if (decoder->uses_direct_rgb565()) {
104 decoder->draw_rgb565(jpeg->x, jpeg->y, jpeg->iWidth, jpeg->iHeight,
105 reinterpret_cast<const uint8_t *>(jpeg->pPixels));
106 return 1;
107 }
108 size_t position = 0;
109 size_t height = static_cast<size_t>(jpeg->iHeight);
110 size_t width = static_cast<size_t>(jpeg->iWidth);
111 for (size_t y = 0; y < height; y++) {
112 for (size_t x = 0; x < width; x++) {
113 auto rg = decode_value(jpeg->pPixels[position++]);
114 auto ba = decode_value(jpeg->pPixels[position++]);
115 Color color(rg[1], rg[0], ba[1], ba[0]);
116
117 decoder->draw(jpeg->x + x, jpeg->y + y, 1, 1, color);
118 }
119 }
120 return 1;
121}
122
123int HOT JpegDecoder::decode(uint8_t *buffer, size_t size) {
124 // JPEG decoder requires complete data
125 // If we know the expected size, wait for it
127 ESP_LOGV(TAG, "Download not complete. Size: %zu/%zu", size, this->expected_size_);
128 return 0;
129 }
130
131 // If size unknown, try to decode and see if it's valid
132 // The JPEGDEC library will fail gracefully if data is incomplete
133
134 if (!this->jpeg_.openRAM(buffer, size, draw_callback)) {
135 ESP_LOGE(TAG, "Could not open image for decoding: %d", this->jpeg_.getLastError());
137 }
138 auto jpeg_type = this->jpeg_.getJPEGType();
139 if (jpeg_type == JPEG_MODE_INVALID) {
140 ESP_LOGE(TAG, "Unsupported JPEG image");
142 } else if (jpeg_type == JPEG_MODE_PROGRESSIVE) {
143 ESP_LOGE(TAG, "Progressive JPEG images not supported");
145 }
146 ESP_LOGD(TAG, "Image size: %d x %d, bpp: %d", this->jpeg_.getWidth(), this->jpeg_.getHeight(), this->jpeg_.getBpp());
147
148 this->jpeg_.setUserPointer(this);
150 if (this->direct_rgb565_) {
151 // JPEGDEC's RGB565_BIG_ENDIAN output is not byte swapped on every build (the x86 host build
152 // returns little-endian words for it), so the byte order is applied in draw_rgb565 instead
153 this->jpeg_.setPixelType(RGB565_LITTLE_ENDIAN);
154 } else {
155 this->jpeg_.setPixelType(RGB8888);
156 }
157 if (!this->set_size(this->jpeg_.getWidth(), this->jpeg_.getHeight())) {
159 }
160 if (this->direct_rgb565_ && !this->prepare_rgb565_scaling_(this->jpeg_.getWidth(), this->jpeg_.getHeight())) {
162 }
163 if (!this->jpeg_.decode(0, 0, 0)) {
164 auto error = this->jpeg_.getLastError();
165 ESP_LOGE(TAG, "Error while decoding: %d", error);
166 this->jpeg_.close();
167 switch (error) {
168 case JPEG_ERROR_MEMORY:
170 case JPEG_UNSUPPORTED_FEATURE:
172 case JPEG_INVALID_FILE:
173 case JPEG_INVALID_PARAMETER:
175 case JPEG_DECODE_ERROR:
176 default:
178 }
179 }
180 this->decoded_bytes_ = size;
181 this->jpeg_.close();
182 return size;
183}
184
185} // namespace esphome::runtime_image
186
187#endif // USE_RUNTIME_IMAGE_JPEG
uint8_t h
Definition bl0906.h:2
void feed_wdt()
Feed the task watchdog.
size_t capacity() const
Definition helpers.h:794
void push_back(const T &value)
Add element without bounds checking Caller must ensure sufficient capacity was allocated via init() S...
Definition helpers.h:743
bool has_transparency() const
Definition image.h:43
ImageType get_type() const
Definition image.cpp:293
uint8_t * image_buffer_() const
The image's pixel buffer, for decoders that write their output straight into it.
bool set_size(int width, int height)
Request the image to be resized once the actual dimensions are known.
Image decoder specialization for JPEG images.
FixedVector< uint16_t > y_boundaries_
void draw_rgb565(int x, int y, int w, int h, const uint8_t *pixels)
FixedVector< uint16_t > x_boundaries_
int HOT decode(uint8_t *buffer, size_t size) override
bool prepare_rgb565_scaling_(int width, int height)
float position
Definition cover.h:0
uint32_t buffer
Definition gif.cpp:50
ESPHOME_LOG_TAG(TAG, "image_decoder.jpeg")
@ IMAGE_TYPE_RGB565
Definition image.h:15
uint16_t size
Definition helpers.cpp:34
constexpr std::array< uint8_t, sizeof(T)> decode_value(T val)
Decode a value into its constituent bytes (from most to least significant).
Definition helpers.h:1009
Application App
Global storage of Application pointer - only one Application can exist.
static void uint32_t
uint16_t x
Definition tt21100.cpp:5
uint16_t y
Definition tt21100.cpp:6