mirror of
https://github.com/esphome/esphome.git
synced 2026-08-22 22:26:21 +00:00
[runtime_image] Allow decoding into a caller-owned buffer (#17936)
This commit is contained in:
@@ -248,9 +248,15 @@ void RuntimeImage::release() {
|
|||||||
|
|
||||||
void RuntimeImage::release_buffer_() {
|
void RuntimeImage::release_buffer_() {
|
||||||
if (this->buffer_) {
|
if (this->buffer_) {
|
||||||
ESP_LOGV(TAG, "Releasing buffer of size %zu", this->get_buffer_size_(this->buffer_width_, this->buffer_height_));
|
if (this->external_buffer_) {
|
||||||
RAMAllocator<uint8_t> allocator;
|
// The caller owns this memory and goes on using it after the image lets go of it.
|
||||||
allocator.deallocate(this->buffer_, this->get_buffer_size_(this->buffer_width_, this->buffer_height_));
|
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->buffer_ = nullptr;
|
||||||
this->data_start_ = nullptr;
|
this->data_start_ = nullptr;
|
||||||
this->width_ = 0;
|
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 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) {
|
if (new_size == 0) {
|
||||||
ESP_LOGE(TAG, "Refusing to allocate buffer for invalid image dimensions %dx%d", width, height);
|
ESP_LOGE(TAG, "Refusing to allocate buffer for invalid image dimensions %dx%d", width, height);
|
||||||
return 0;
|
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
|
// Release old buffer if dimensions changed
|
||||||
if (this->buffer_) {
|
if (this->buffer_) {
|
||||||
this->release_buffer_();
|
this->release_buffer_();
|
||||||
@@ -300,7 +333,7 @@ size_t RuntimeImage::resize_buffer_(int width, int height) {
|
|||||||
return new_size;
|
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
|
// 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) {
|
if (width <= 0 || height <= 0 || width > MAX_IMAGE_DIMENSION || height > MAX_IMAGE_DIMENSION) {
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -121,9 +121,48 @@ class RuntimeImage : public image::Image {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Release the image buffer and free memory.
|
* @brief Release the image buffer and free memory.
|
||||||
|
*
|
||||||
|
* An external buffer is let go of rather than freed.
|
||||||
*/
|
*/
|
||||||
void release();
|
void release();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Decode into a buffer the caller owns, instead of one allocated here.
|
||||||
|
*
|
||||||
|
* The image never frees an external buffer and never resizes it: a decode that needs other
|
||||||
|
* dimensions fails as if the allocation had failed, and the buffer is let go of so a decoder
|
||||||
|
* that ignores that failure cannot publish a picture it did not paint. The caller keeps the
|
||||||
|
* buffer alive for as long as anything can draw the image, and calls release() (or hands over
|
||||||
|
* another buffer) before reusing it.
|
||||||
|
*
|
||||||
|
* Hand a buffer over before every decode. The image lets go of one whenever a decode fails and
|
||||||
|
* whenever release() is called, and it does not remember that it ever had one: a decode that
|
||||||
|
* starts without a buffer allocates its own, which is the runtime allocation this method exists
|
||||||
|
* to avoid.
|
||||||
|
*
|
||||||
|
* The buffer is decoded into as it is handed over, so the caller owns its initial contents.
|
||||||
|
* Zero it first if anything can draw the image before a decode has painted every pixel.
|
||||||
|
*
|
||||||
|
* Do not hand a buffer over while is_decoding() is true. A running decoder keeps scaling values
|
||||||
|
* for the buffer it started with.
|
||||||
|
*
|
||||||
|
* A null buffer or dimensions the image cannot decode at are refused, leaving the image with
|
||||||
|
* no buffer at all.
|
||||||
|
*
|
||||||
|
* @param buffer Memory for a picture of the given size, at least get_buffer_size() bytes.
|
||||||
|
* @param width Width of the buffer in pixels.
|
||||||
|
* @param height Height of the buffer in pixels.
|
||||||
|
* @return true if the image took the buffer, false if it was refused.
|
||||||
|
*/
|
||||||
|
bool set_external_buffer(uint8_t *buffer, int width, int height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the buffer size in bytes needed for a picture of the given dimensions.
|
||||||
|
*
|
||||||
|
* Returns 0 for dimensions the image cannot decode at.
|
||||||
|
*/
|
||||||
|
size_t get_buffer_size(int width, int height) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Set whether to allow progressive display during decode.
|
* @brief Set whether to allow progressive display during decode.
|
||||||
*
|
*
|
||||||
@@ -149,11 +188,6 @@ class RuntimeImage : public image::Image {
|
|||||||
*/
|
*/
|
||||||
void release_buffer_();
|
void release_buffer_();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Get the buffer size in bytes for given dimensions.
|
|
||||||
*/
|
|
||||||
size_t get_buffer_size_(int width, int height) const;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the position in the buffer for a pixel.
|
* @brief Get the position in the buffer for a pixel.
|
||||||
*/
|
*/
|
||||||
@@ -208,6 +242,8 @@ class RuntimeImage : public image::Image {
|
|||||||
* This is used to determine how to store 16 bit colors in the buffer.
|
* This is used to determine how to store 16 bit colors in the buffer.
|
||||||
*/
|
*/
|
||||||
bool is_big_endian_{false};
|
bool is_big_endian_{false};
|
||||||
|
/** Whether buffer_ belongs to the caller, so it must not be freed or resized here. */
|
||||||
|
bool external_buffer_{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::runtime_image
|
} // namespace esphome::runtime_image
|
||||||
|
|||||||
Reference in New Issue
Block a user