[runtime_image] Allow decoding into a caller-owned buffer (#17936)

This commit is contained in:
Kevin Ahrendt
2026-07-29 23:32:21 -04:00
committed by GitHub
parent fb1b1db87f
commit 4224d90567
2 changed files with 84 additions and 15 deletions
@@ -248,9 +248,15 @@ void RuntimeImage::release() {
void RuntimeImage::release_buffer_() {
if (this->buffer_) {
ESP_LOGV(TAG, "Releasing buffer of size %zu", this->get_buffer_size_(this->buffer_width_, this->buffer_height_));
RAMAllocator<uint8_t> allocator;
allocator.deallocate(this->buffer_, this->get_buffer_size_(this->buffer_width_, this->buffer_height_));
if (this->external_buffer_) {
// The caller owns this memory and goes on using it after the image lets go of it.
ESP_LOGV(TAG, "Letting go of the external %dx%d buffer", this->buffer_width_, this->buffer_height_);
this->external_buffer_ = false;
} else {
ESP_LOGV(TAG, "Releasing buffer of size %zu", this->get_buffer_size(this->buffer_width_, this->buffer_height_));
RAMAllocator<uint8_t> allocator;
allocator.deallocate(this->buffer_, this->get_buffer_size(this->buffer_width_, this->buffer_height_));
}
this->buffer_ = nullptr;
this->data_start_ = nullptr;
this->width_ = 0;
@@ -263,19 +269,46 @@ void RuntimeImage::release_buffer_() {
}
}
bool RuntimeImage::set_external_buffer(uint8_t *buffer, int width, int height) {
this->release_buffer_();
if (buffer == nullptr || this->get_buffer_size(width, height) == 0) {
// Keep the released state rather than remembering a buffer that cannot be decoded into: an
// external buffer that is never handed back would otherwise block every later allocation.
ESP_LOGE(TAG, "Refusing an invalid external buffer for %dx%d", width, height);
return false;
}
this->buffer_ = buffer;
this->external_buffer_ = true;
this->buffer_width_ = width;
this->buffer_height_ = height;
return true;
}
size_t RuntimeImage::resize_buffer_(int width, int height) {
size_t new_size = this->get_buffer_size_(width, height);
size_t new_size = this->get_buffer_size(width, height);
// A buffer only ever exists with dimensions the image can decode at, so a match here means
// new_size is non-zero. Checking it before the invalid dimension case below lets the external
// buffer be let go of for every decode it cannot serve, not just for valid other dimensions.
if (this->buffer_ && this->buffer_width_ == width && this->buffer_height_ == height) {
// Buffer already allocated with correct size
return new_size;
}
if (this->external_buffer_) {
ESP_LOGE(TAG, "Image decoded to %dx%d, but the external buffer is %dx%d", width, height, this->buffer_width_,
this->buffer_height_);
// Let the buffer go rather than free memory that belongs to the caller. Dropping it also stops
// a decoder that ignores this failure from publishing a picture it never painted.
this->release_buffer_();
return 0;
}
if (new_size == 0) {
ESP_LOGE(TAG, "Refusing to allocate buffer for invalid image dimensions %dx%d", width, height);
return 0;
}
if (this->buffer_ && this->buffer_width_ == width && this->buffer_height_ == height) {
// Buffer already allocated with correct size
return new_size;
}
// Release old buffer if dimensions changed
if (this->buffer_) {
this->release_buffer_();
@@ -300,7 +333,7 @@ size_t RuntimeImage::resize_buffer_(int width, int height) {
return new_size;
}
size_t RuntimeImage::get_buffer_size_(int width, int height) const {
size_t RuntimeImage::get_buffer_size(int width, int height) const {
// Dimensions come from a remote image header; reject absurd values so the size math cannot overflow
if (width <= 0 || height <= 0 || width > MAX_IMAGE_DIMENSION || height > MAX_IMAGE_DIMENSION) {
return 0;