[runtime_image] keep decoder allocated (#18488)

Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
guillempages
2026-08-20 17:27:44 -05:00
committed by GitHub
co-authored by pre-commit-ci-lite[bot] J. Nick Koston J. Nick Koston
parent ece90ee97b
commit 5177972c04
15 changed files with 487 additions and 72 deletions
@@ -172,33 +172,38 @@ void RuntimeImage::draw(int x, int y, display::Display *display, Color color_on,
}
bool RuntimeImage::begin_decode(size_t expected_size) {
if (this->decoder_) {
if (this->is_decoding()) {
ESP_LOGW(TAG, "Decoding already in progress");
return false;
}
this->decoder_ = this->create_decoder_();
// An idle decoder for a different format cannot be reused
if (this->decoder_ != nullptr && this->decoder_->get_format() != this->format_) {
ESP_LOGD(TAG, "Decoder format mismatch: current: %d, new: %d", this->decoder_->get_format(), this->format_);
this->decoder_ = nullptr;
}
if (!this->decoder_) {
ESP_LOGE(TAG, "Failed to create decoder for format %d", this->format_);
return false;
this->decoder_ = this->create_decoder_(this->format_);
if (!this->decoder_) {
ESP_LOGE(TAG, "Failed to create decoder for format %d", this->format_);
return false;
}
}
this->total_size_ = expected_size;
this->decoded_bytes_ = 0;
// Initialize decoder
int result = this->decoder_->prepare(expected_size);
if (result < 0) {
ESP_LOGE(TAG, "Failed to prepare decoder: %d", result);
this->decoder_ = nullptr;
this->decoder_ = nullptr; // If prepare fails, a full reset is needed
return false;
}
return true;
}
int RuntimeImage::feed_data(uint8_t *data, size_t len) {
if (!this->decoder_) {
if (!this->is_decoding()) {
ESP_LOGE(TAG, "No decoder initialized");
return -1;
}
@@ -212,7 +217,7 @@ int RuntimeImage::feed_data(uint8_t *data, size_t len) {
}
bool RuntimeImage::end_decode() {
if (!this->decoder_) {
if (!this->is_decoding()) {
return false;
}
@@ -224,26 +229,23 @@ bool RuntimeImage::end_decode() {
this->data_start_ = this->buffer_;
}
// Clean up decoder
this->decoder_ = nullptr;
// End the session; the decoder object stays warm so the next decode can
// reuse it (and its buffers) without churning the heap.
this->decoder_->reset();
ESP_LOGD(TAG, "Decoding complete: %dx%d, %zu bytes", this->width_, this->height_, this->decoded_bytes_);
return true;
}
bool RuntimeImage::is_decode_finished() const {
if (!this->decoder_) {
return false;
}
return this->decoder_->is_finished();
}
bool RuntimeImage::is_decode_finished() const { return this->is_decoding() && this->decoder_->is_finished(); }
void RuntimeImage::release() {
this->release_buffer_();
// Reset decoder separately — release() can be called from within the decoder
// (via set_size -> resize -> resize_buffer_), so we must not destroy the decoder here.
// The decoder lifecycle is managed by begin_decode()/end_decode().
this->decoder_ = nullptr;
// End any active decode session; decoders free the format-specific working buffers
// they can (PNG), while the decoder object itself is kept warm for the next decode.
if (this->decoder_) {
this->decoder_->reset();
}
}
void RuntimeImage::release_buffer_() {
@@ -347,8 +349,9 @@ size_t RuntimeImage::get_buffer_size(int width, int height) const {
int RuntimeImage::get_position_(int x, int y) const { return (x + y * this->buffer_width_) * this->get_bpp() / 8; }
std::unique_ptr<ImageDecoder> RuntimeImage::create_decoder_() {
switch (this->format_) {
std::unique_ptr<ImageDecoder> RuntimeImage::create_decoder_(ImageFormat format) {
ESP_LOGV(TAG, "Creating decoder for format %d", format);
switch (format) {
#ifdef USE_RUNTIME_IMAGE_BMP
case BMP:
return make_unique<BmpDecoder>(this);
@@ -362,7 +365,7 @@ std::unique_ptr<ImageDecoder> RuntimeImage::create_decoder_() {
return make_unique<PngDecoder>(this);
#endif
default:
ESP_LOGE(TAG, "Unsupported image format: %d", this->format_);
ESP_LOGE(TAG, "Unsupported image format: %d", format);
return nullptr;
}
}