[online_image] Add binary bmp support (#8116)

Co-authored-by: guillempages <guillempages@users.noreply.github.com>
This commit is contained in:
Jesse Hills 2025-01-23 15:10:19 +13:00 committed by GitHub
parent dee1d84979
commit 7fccc9ff86
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 187 additions and 8 deletions

View File

@ -61,8 +61,22 @@ class PNGFormat(Format):
cg.add_library("pngle", "1.0.2")
class BMPFormat(Format):
def __init__(self):
super().__init__("BMP")
def actions(self):
cg.add_define("USE_ONLINE_IMAGE_BMP_SUPPORT")
# New formats can be added here.
IMAGE_FORMATS = {x.image_type: x for x in (PNGFormat(),)}
IMAGE_FORMATS = {
x.image_type: x
for x in (
PNGFormat(),
BMPFormat(),
)
}
OnlineImage = online_image_ns.class_("OnlineImage", cg.PollingComponent, Image_)

View File

@ -0,0 +1,101 @@
#include "bmp_image.h"
#ifdef USE_ONLINE_IMAGE_BMP_SUPPORT
#include "esphome/components/display/display.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace online_image {
static const char *const TAG = "online_image.bmp";
int HOT BmpDecoder::decode(uint8_t *buffer, size_t size) {
size_t index = 0;
if (this->current_index_ == 0 && index == 0 && size > 14) {
/**
* BMP file format:
* 0-1: Signature (BM)
* 2-5: File size
* 6-9: Reserved
* 10-13: Pixel data offset
*
* Integer values are stored in little-endian format.
*/
// Check if the file is a BMP image
if (buffer[0] != 'B' || buffer[1] != 'M') {
ESP_LOGE(TAG, "Not a BMP file");
return DECODE_ERROR_INVALID_TYPE;
}
this->download_size_ = encode_uint32(buffer[5], buffer[4], buffer[3], buffer[2]);
this->data_offset_ = encode_uint32(buffer[13], buffer[12], buffer[11], buffer[10]);
this->current_index_ = 14;
index = 14;
}
if (this->current_index_ == 14 && index == 14 && size > this->data_offset_) {
/**
* BMP DIB header:
* 14-17: DIB header size
* 18-21: Image width
* 22-25: Image height
* 26-27: Number of color planes
* 28-29: Bits per pixel
* 30-33: Compression method
* 34-37: Image data size
* 38-41: Horizontal resolution
* 42-45: Vertical resolution
* 46-49: Number of colors in the color table
*/
this->width_ = encode_uint32(buffer[21], buffer[20], buffer[19], buffer[18]);
this->height_ = encode_uint32(buffer[25], buffer[24], buffer[23], buffer[22]);
this->bits_per_pixel_ = encode_uint16(buffer[29], buffer[28]);
this->compression_method_ = encode_uint32(buffer[33], buffer[32], buffer[31], buffer[30]);
this->image_data_size_ = encode_uint32(buffer[37], buffer[36], buffer[35], buffer[34]);
this->color_table_entries_ = encode_uint32(buffer[49], buffer[48], buffer[47], buffer[46]);
switch (this->bits_per_pixel_) {
case 1:
this->width_bytes_ = (this->width_ % 8 == 0) ? (this->width_ / 8) : (this->width_ / 8 + 1);
break;
default:
ESP_LOGE(TAG, "Unsupported bits per pixel: %d", this->bits_per_pixel_);
return DECODE_ERROR_UNSUPPORTED_FORMAT;
}
if (this->compression_method_ != 0) {
ESP_LOGE(TAG, "Unsupported compression method: %d", this->compression_method_);
return DECODE_ERROR_UNSUPPORTED_FORMAT;
}
if (!this->set_size(this->width_, this->height_)) {
return DECODE_ERROR_OUT_OF_MEMORY;
}
this->current_index_ = this->data_offset_;
index = this->data_offset_;
}
while (index < size) {
size_t paint_index = this->current_index_ - this->data_offset_;
uint8_t current_byte = buffer[index];
for (uint8_t i = 0; i < 8; i++) {
size_t x = (paint_index * 8) % this->width_ + i;
size_t y = (this->height_ - 1) - (paint_index / this->width_bytes_);
Color c = (current_byte & (1 << (7 - i))) ? display::COLOR_ON : display::COLOR_OFF;
this->draw(x, y, 1, 1, c);
}
this->current_index_++;
index++;
}
this->decoded_bytes_ += size;
return size;
};
} // namespace online_image
} // namespace esphome
#endif // USE_ONLINE_IMAGE_BMP_SUPPORT

View File

@ -0,0 +1,40 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_ONLINE_IMAGE_BMP_SUPPORT
#include "image_decoder.h"
namespace esphome {
namespace online_image {
/**
* @brief Image decoder specialization for PNG images.
*/
class BmpDecoder : public ImageDecoder {
public:
/**
* @brief Construct a new BMP Decoder object.
*
* @param display The image to decode the stream into.
*/
BmpDecoder(OnlineImage *image) : ImageDecoder(image) {}
int HOT decode(uint8_t *buffer, size_t size) override;
protected:
size_t current_index_{0};
ssize_t width_{0};
ssize_t height_{0};
uint16_t bits_per_pixel_{0};
uint32_t compression_method_{0};
uint32_t image_data_size_{0};
uint32_t color_table_entries_{0};
size_t width_bytes_{0};
size_t data_offset_{0};
};
} // namespace online_image
} // namespace esphome
#endif // USE_ONLINE_IMAGE_BMP_SUPPORT

View File

@ -8,10 +8,11 @@ namespace online_image {
static const char *const TAG = "online_image.decoder";
void ImageDecoder::set_size(int width, int height) {
this->image_->resize_(width, height);
bool ImageDecoder::set_size(int width, int height) {
bool resized = this->image_->resize_(width, height);
this->x_scale_ = static_cast<double>(this->image_->buffer_width_) / width;
this->y_scale_ = static_cast<double>(this->image_->buffer_height_) / height;
return resized;
}
void ImageDecoder::draw(int x, int y, int w, int h, const Color &color) {

View File

@ -4,6 +4,12 @@
namespace esphome {
namespace online_image {
enum DecodeError : int {
DECODE_ERROR_INVALID_TYPE = -1,
DECODE_ERROR_UNSUPPORTED_FORMAT = -2,
DECODE_ERROR_OUT_OF_MEMORY = -3,
};
class OnlineImage;
/**
@ -45,8 +51,9 @@ class ImageDecoder {
*
* @param width The image's width.
* @param height The image's height.
* @return true if the image was resized, false otherwise.
*/
void set_size(int width, int height);
bool set_size(int width, int height);
/**
* @brief Fill a rectangle on the display_buffer using the defined color.

View File

@ -10,6 +10,10 @@ static const char *const TAG = "online_image";
#include "png_image.h"
#endif
#ifdef USE_ONLINE_IMAGE_BMP_SUPPORT
#include "bmp_image.h"
#endif
namespace esphome {
namespace online_image {
@ -120,9 +124,14 @@ void OnlineImage::update() {
#ifdef USE_ONLINE_IMAGE_PNG_SUPPORT
if (this->format_ == ImageFormat::PNG) {
this->decoder_ = esphome::make_unique<PngDecoder>(this);
this->decoder_ = make_unique<PngDecoder>(this);
}
#endif // ONLINE_IMAGE_PNG_SUPPORT
#ifdef USE_ONLINE_IMAGE_BMP_SUPPORT
if (this->format_ == ImageFormat::BMP) {
this->decoder_ = make_unique<BmpDecoder>(this);
}
#endif // ONLINE_IMAGE_BMP_SUPPORT
if (!this->decoder_) {
ESP_LOGE(TAG, "Could not instantiate decoder. Image format unsupported.");

View File

@ -1,10 +1,10 @@
#pragma once
#include "esphome/components/http_request/http_request.h"
#include "esphome/components/image/image.h"
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#include "esphome/components/http_request/http_request.h"
#include "esphome/components/image/image.h"
#include "image_decoder.h"
@ -27,6 +27,8 @@ enum ImageFormat {
JPEG,
/** PNG format. */
PNG,
/** BMP format. */
BMP,
};
/**
@ -146,7 +148,7 @@ class OnlineImage : public PollingComponent,
*/
int buffer_height_;
friend void ImageDecoder::set_size(int width, int height);
friend bool ImageDecoder::set_size(int width, int height);
friend void ImageDecoder::draw(int x, int y, int w, int h, const Color &color);
};

View File

@ -60,6 +60,7 @@
#define USE_NETWORK
#define USE_NEXTION_TFT_UPLOAD
#define USE_NUMBER
#define USE_ONLINE_IMAGE_BMP_SUPPORT
#define USE_ONLINE_IMAGE_PNG_SUPPORT
#define USE_OTA
#define USE_OTA_PASSWORD

View File

@ -26,6 +26,10 @@ online_image:
format: PNG
type: RGB
transparency: chroma_key
- id: online_binary_bmp
url: https://samples-files.com/samples/images/bmp/480-360-sample.bmp
format: BMP
type: BINARY
# Check the set_url action
esphome: