ESPHome 2026.9.0-dev
Loading...
Searching...
No Matches
qoi_decoder.cpp
Go to the documentation of this file.
1#include "qoi_decoder.h"
2
3#ifdef USE_RUNTIME_IMAGE_QOI
4
7#include "esphome/core/log.h"
8
9#include <cinttypes>
10
11namespace esphome::runtime_image {
12
13static const char *const TAG = "image_decoder.qoi";
14
15constexpr uint8_t QOI_OP_RGB = 0b11111110;
16constexpr uint8_t QOI_OP_RGBA = 0b11111111;
17constexpr uint8_t QOI_OP_INDEX = 0b00000000; // 00xxxxxx
18constexpr uint8_t QOI_OP_DIFF = 0b01000000; // 01xxxxxx
19constexpr uint8_t QOI_OP_LUMA = 0b10000000; // 10xxxxxx
20constexpr uint8_t QOI_OP_RUN = 0b11000000; // 11xxxxxx
21
22constexpr uint8_t QOI_RGB_CHUNK_SIZE = 4;
23constexpr uint8_t QOI_RGBA_CHUNK_SIZE = 5;
24constexpr uint8_t QOI_LUMA_CHUNK_SIZE = 2;
25
26constexpr uint8_t QOI_MASK_OP = 0b11000000;
27constexpr uint8_t QOI_MASK_VALUE = 0b00111111;
28
29constexpr size_t QOI_HEADER_SIZE = 14;
30constexpr size_t QOI_COLOR_TABLE_SIZE = 64;
31
32inline size_t qoi_color_table_index(const Color &color) {
33 // QOI color hash function: (r * 3 + g * 5 + b * 7 + a * 11) % 64
34 return (color.r * 3 + color.g * 5 + color.b * 7 + color.w * 11) &
35 63; // modulo 64 is equivalent to bitwise AND with 63 (0b00111111)
36}
37
40 this->current_index_ = 0;
41 this->paint_index_ = 0;
42 this->width_ = 0;
43 this->height_ = 0;
44 this->bits_per_pixel_ = 0;
45 this->last_pixel_ = Color(0, 0, 0, 255);
46 if (this->color_table_) {
47 std::fill_n(this->color_table_.get(), QOI_COLOR_TABLE_SIZE, Color());
48 }
49}
50
51int HOT QoiDecoder::decode(uint8_t *buffer, size_t size) {
52 size_t index = 0;
53 if (this->current_index_ == 0) {
54 if (size < QOI_HEADER_SIZE) {
55 return 0; // Need more data for file header
56 }
57
65 // Check if the file is a QOI image
66 if (buffer[0] != 'q' || buffer[1] != 'o' || buffer[2] != 'i' || buffer[3] != 'f') {
67 ESP_LOGE(TAG, "Not a QOI file");
69 }
70
71 this->width_ = encode_uint32(buffer[4], buffer[5], buffer[6], buffer[7]);
72 this->height_ = encode_uint32(buffer[8], buffer[9], buffer[10], buffer[11]);
73 if (this->width_ == 0 || this->height_ == 0) {
74 ESP_LOGE(TAG, "Invalid image dimensions: (%zux%zu)", this->width_, this->height_);
76 }
77 uint8_t channels = buffer[12];
78 if (channels < 3 || channels > 4) {
79 ESP_LOGE(TAG, "Unsupported number of channels: %d", channels);
81 }
82 this->bits_per_pixel_ = channels * 8;
83 uint8_t colorspace = buffer[13];
84 if (colorspace > 1) {
85 ESP_LOGE(TAG, "Unsupported colorspace value: %d", colorspace);
87 }
88 ESP_LOGD(TAG, "QOI image header: width=%zu, height=%zu, channels=%d, colorspace=%d", this->width_, this->height_,
89 channels, colorspace);
90
91 if (!this->color_table_) {
92 this->color_table_ = std::make_unique<Color[]>(QOI_COLOR_TABLE_SIZE);
93 }
94
95 if (!this->set_size(this->width_, this->height_)) {
97 }
98
100 index = QOI_HEADER_SIZE;
101 } // Current_index == 0
102
103 Color color;
104 const size_t total_pixels = this->width_ * this->height_;
105 while (index < size && this->paint_index_ < total_pixels) {
106 color = this->last_pixel_;
107 uint8_t byte = buffer[index];
108 if (byte == QOI_OP_RGB) {
109 if (size < index + QOI_RGB_CHUNK_SIZE) {
110 return index; // Need more data for RGB chunk
111 }
112 index++;
113 color.r = buffer[index++];
114 color.g = buffer[index++];
115 color.b = buffer[index++];
116 this->draw(this->paint_index_ % this->width_, this->paint_index_ / this->width_, 1, 1, color);
117 this->paint_index_++;
118 } else if (byte == QOI_OP_RGBA) {
119 if (size < index + QOI_RGBA_CHUNK_SIZE) {
120 return index; // Need more data for RGBA chunk
121 }
122 index++;
123 color.r = buffer[index++];
124 color.g = buffer[index++];
125 color.b = buffer[index++];
126 color.w = buffer[index++];
127 this->draw(this->paint_index_ % this->width_, this->paint_index_ / this->width_, 1, 1, color);
128 this->paint_index_++;
129 } else if ((byte & QOI_MASK_OP) == QOI_OP_RUN) {
130 // QOI run chunk
131 size_t run_length = (byte & QOI_MASK_VALUE) + 1; // run length is encoded in the lower 6 bits, plus one
132 for (size_t i = 0; i < run_length; i++) {
133 // TODO: optimize by drawing runs of pixels at once instead of one by one
134 this->draw(this->paint_index_ % this->width_, this->paint_index_ / this->width_, 1, 1, color);
135 this->paint_index_++;
136 }
137 index++;
138 } else if ((byte & QOI_MASK_OP) == QOI_OP_LUMA) {
139 if (size < index + QOI_LUMA_CHUNK_SIZE) {
140 return index; // Need more data for LUMA chunk
141 }
142 index++;
143 uint8_t byte2 = buffer[index++];
144 uint8_t delta_g = (byte & QOI_MASK_VALUE) - 32;
145 color.r += delta_g - 8 + ((byte2 >> 4) & 0x0f);
146 color.g += delta_g;
147 color.b += delta_g - 8 + (byte2 & 0x0f);
148
149 this->draw(this->paint_index_ % this->width_, this->paint_index_ / this->width_, 1, 1, color);
150 this->paint_index_++;
151 } else if ((byte & QOI_MASK_OP) == QOI_OP_DIFF) {
152 color.r += ((byte >> 4) & 0x03) - 2;
153 color.g += ((byte >> 2) & 0x03) - 2;
154 color.b += (byte & 0x03) - 2;
155
156 this->draw(this->paint_index_ % this->width_, this->paint_index_ / this->width_, 1, 1, color);
157 this->paint_index_++;
158 index++;
159 } else if ((byte & QOI_MASK_OP) == QOI_OP_INDEX) {
160 color = this->color_table_[byte];
161 this->draw(this->paint_index_ % this->width_, this->paint_index_ / this->width_, 1, 1, color);
162 this->paint_index_++;
163 index++;
164 }
165 this->last_pixel_ = color;
166 this->color_table_[qoi_color_table_index(color)] = color;
167 }
168 this->decoded_bytes_ += size;
169 return size;
170}
171
172} // namespace esphome::runtime_image
173
174#endif // USE_RUNTIME_IMAGE_QOI
virtual void reset()
Reset the decoder state, ending any decoding session.
void draw(int x, int y, int w, int h, const Color &color)
Fill a rectangle on the display_buffer using the defined color.
bool set_size(int width, int height)
Request the image to be resized once the actual dimensions are known.
int HOT decode(uint8_t *buffer, size_t size) override
std::unique_ptr< Color[]> color_table_
Definition qoi_decoder.h:38
constexpr uint8_t QOI_LUMA_CHUNK_SIZE
constexpr uint8_t QOI_RGB_CHUNK_SIZE
size_t qoi_color_table_index(const Color &color)
constexpr uint8_t QOI_OP_RUN
constexpr uint8_t QOI_OP_RGBA
constexpr uint8_t QOI_RGBA_CHUNK_SIZE
constexpr uint8_t QOI_OP_DIFF
constexpr uint8_t QOI_MASK_VALUE
constexpr uint8_t QOI_OP_LUMA
constexpr uint8_t QOI_MASK_OP
constexpr size_t QOI_HEADER_SIZE
constexpr size_t QOI_COLOR_TABLE_SIZE
constexpr uint8_t QOI_OP_RGB
constexpr uint8_t QOI_OP_INDEX
uint16_t size
Definition helpers.cpp:25
constexpr uint32_t encode_uint32(uint8_t byte1, uint8_t byte2, uint8_t byte3, uint8_t byte4)
Encode a 32-bit value given four bytes in most to least significant byte order.
Definition helpers.h:892
uint8_t w
Definition color.h:42
uint8_t g
Definition color.h:34
uint8_t b
Definition color.h:38
uint8_t r
Definition color.h:30