mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	[online_image] Add JPEG support to online_image (#8127)
Co-authored-by: Jimmy Hedman <jimmy.hedman@gmail.com> Co-authored-by: J. Nick Koston <nick@koston.org> Co-authored-by: Rodrigo Martín <contact@rodrigomartin.dev> Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
This commit is contained in:
		| @@ -60,6 +60,15 @@ class BMPFormat(Format): | |||||||
|         cg.add_define("USE_ONLINE_IMAGE_BMP_SUPPORT") |         cg.add_define("USE_ONLINE_IMAGE_BMP_SUPPORT") | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class JPEGFormat(Format): | ||||||
|  |     def __init__(self): | ||||||
|  |         super().__init__("JPEG") | ||||||
|  |  | ||||||
|  |     def actions(self): | ||||||
|  |         cg.add_define("USE_ONLINE_IMAGE_JPEG_SUPPORT") | ||||||
|  |         cg.add_library("JPEGDEC", "1.6.2", "https://github.com/bitbank2/JPEGDEC") | ||||||
|  |  | ||||||
|  |  | ||||||
| class PNGFormat(Format): | class PNGFormat(Format): | ||||||
|     def __init__(self): |     def __init__(self): | ||||||
|         super().__init__("PNG") |         super().__init__("PNG") | ||||||
| @@ -69,14 +78,15 @@ class PNGFormat(Format): | |||||||
|         cg.add_library("pngle", "1.0.2") |         cg.add_library("pngle", "1.0.2") | ||||||
|  |  | ||||||
|  |  | ||||||
| # New formats can be added here. |  | ||||||
| IMAGE_FORMATS = { | IMAGE_FORMATS = { | ||||||
|     x.image_type: x |     x.image_type: x | ||||||
|     for x in ( |     for x in ( | ||||||
|         BMPFormat(), |         BMPFormat(), | ||||||
|  |         JPEGFormat(), | ||||||
|         PNGFormat(), |         PNGFormat(), | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
|  | IMAGE_FORMATS.update({"JPG": IMAGE_FORMATS["JPEG"]}) | ||||||
|  |  | ||||||
| OnlineImage = online_image_ns.class_("OnlineImage", cg.PollingComponent, Image_) | OnlineImage = online_image_ns.class_("OnlineImage", cg.PollingComponent, Image_) | ||||||
|  |  | ||||||
| @@ -116,7 +126,7 @@ ONLINE_IMAGE_SCHEMA = ( | |||||||
|             cv.Required(CONF_URL): cv.url, |             cv.Required(CONF_URL): cv.url, | ||||||
|             cv.Required(CONF_FORMAT): cv.one_of(*IMAGE_FORMATS, upper=True), |             cv.Required(CONF_FORMAT): cv.one_of(*IMAGE_FORMATS, upper=True), | ||||||
|             cv.Optional(CONF_PLACEHOLDER): cv.use_id(Image_), |             cv.Optional(CONF_PLACEHOLDER): cv.use_id(Image_), | ||||||
|             cv.Optional(CONF_BUFFER_SIZE, default=2048): cv.int_range(256, 65536), |             cv.Optional(CONF_BUFFER_SIZE, default=65536): cv.int_range(256, 65536), | ||||||
|             cv.Optional(CONF_ON_DOWNLOAD_FINISHED): automation.validate_automation( |             cv.Optional(CONF_ON_DOWNLOAD_FINISHED): automation.validate_automation( | ||||||
|                 { |                 { | ||||||
|                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( |                     cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( | ||||||
|   | |||||||
| @@ -41,5 +41,20 @@ size_t DownloadBuffer::read(size_t len) { | |||||||
|   return this->unread_; |   return this->unread_; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | size_t DownloadBuffer::resize(size_t size) { | ||||||
|  |   if (this->size_ == size) { | ||||||
|  |     return size; | ||||||
|  |   } | ||||||
|  |   this->allocator_.deallocate(this->buffer_, this->size_); | ||||||
|  |   this->size_ = size; | ||||||
|  |   this->buffer_ = this->allocator_.allocate(size); | ||||||
|  |   this->reset(); | ||||||
|  |   if (this->buffer_) { | ||||||
|  |     return size; | ||||||
|  |   } else { | ||||||
|  |     return 0; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
| }  // namespace online_image | }  // namespace online_image | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -106,6 +106,8 @@ class DownloadBuffer { | |||||||
|  |  | ||||||
|   void reset() { this->unread_ = 0; } |   void reset() { this->unread_ = 0; } | ||||||
|  |  | ||||||
|  |   size_t resize(size_t size); | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   RAMAllocator<uint8_t> allocator_{}; |   RAMAllocator<uint8_t> allocator_{}; | ||||||
|   uint8_t *buffer_; |   uint8_t *buffer_; | ||||||
|   | |||||||
							
								
								
									
										89
									
								
								esphome/components/online_image/jpeg_image.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								esphome/components/online_image/jpeg_image.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | |||||||
|  | #include "jpeg_image.h" | ||||||
|  | #ifdef USE_ONLINE_IMAGE_JPEG_SUPPORT | ||||||
|  |  | ||||||
|  | #include "esphome/components/display/display_buffer.h" | ||||||
|  | #include "esphome/core/application.h" | ||||||
|  | #include "esphome/core/helpers.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | #include "online_image.h" | ||||||
|  | static const char *const TAG = "online_image.jpeg"; | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace online_image { | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Callback method that will be called by the JPEGDEC engine when a chunk | ||||||
|  |  * of the image is decoded. | ||||||
|  |  * | ||||||
|  |  * @param jpeg  The JPEGDRAW object, including the context data. | ||||||
|  |  */ | ||||||
|  | static int draw_callback(JPEGDRAW *jpeg) { | ||||||
|  |   ImageDecoder *decoder = (ImageDecoder *) jpeg->pUser; | ||||||
|  |  | ||||||
|  |   // Some very big images take too long to decode, so feed the watchdog on each callback | ||||||
|  |   // to avoid crashing. | ||||||
|  |   App.feed_wdt(); | ||||||
|  |   size_t position = 0; | ||||||
|  |   for (size_t y = 0; y < jpeg->iHeight; y++) { | ||||||
|  |     for (size_t x = 0; x < jpeg->iWidth; x++) { | ||||||
|  |       auto rg = decode_value(jpeg->pPixels[position++]); | ||||||
|  |       auto ba = decode_value(jpeg->pPixels[position++]); | ||||||
|  |       Color color(rg[1], rg[0], ba[1], ba[0]); | ||||||
|  |  | ||||||
|  |       if (!decoder) { | ||||||
|  |         ESP_LOGE(TAG, "Decoder pointer is null!"); | ||||||
|  |         return 0; | ||||||
|  |       } | ||||||
|  |       decoder->draw(jpeg->x + x, jpeg->y + y, 1, 1, color); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return 1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void JpegDecoder::prepare(size_t download_size) { | ||||||
|  |   ImageDecoder::prepare(download_size); | ||||||
|  |   auto size = this->image_->resize_download_buffer(download_size); | ||||||
|  |   if (size < download_size) { | ||||||
|  |     ESP_LOGE(TAG, "Resize failed!"); | ||||||
|  |     // TODO: return an error code; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int HOT JpegDecoder::decode(uint8_t *buffer, size_t size) { | ||||||
|  |   if (size < this->download_size_) { | ||||||
|  |     ESP_LOGV(TAG, "Download not complete. Size: %d/%d", size, this->download_size_); | ||||||
|  |     return 0; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (!this->jpeg_.openRAM(buffer, size, draw_callback)) { | ||||||
|  |     ESP_LOGE(TAG, "Could not open image for decoding."); | ||||||
|  |     return DECODE_ERROR_INVALID_TYPE; | ||||||
|  |   } | ||||||
|  |   auto jpeg_type = this->jpeg_.getJPEGType(); | ||||||
|  |   if (jpeg_type == JPEG_MODE_INVALID) { | ||||||
|  |     ESP_LOGE(TAG, "Unsupported JPEG image"); | ||||||
|  |     return DECODE_ERROR_INVALID_TYPE; | ||||||
|  |   } else if (jpeg_type == JPEG_MODE_PROGRESSIVE) { | ||||||
|  |     ESP_LOGE(TAG, "Progressive JPEG images not supported"); | ||||||
|  |     return DECODE_ERROR_INVALID_TYPE; | ||||||
|  |   } | ||||||
|  |   ESP_LOGD(TAG, "Image size: %d x %d, bpp: %d", this->jpeg_.getWidth(), this->jpeg_.getHeight(), this->jpeg_.getBpp()); | ||||||
|  |  | ||||||
|  |   this->jpeg_.setUserPointer(this); | ||||||
|  |   this->jpeg_.setPixelType(RGB8888); | ||||||
|  |   this->set_size(this->jpeg_.getWidth(), this->jpeg_.getHeight()); | ||||||
|  |   if (!this->jpeg_.decode(0, 0, 0)) { | ||||||
|  |     ESP_LOGE(TAG, "Error while decoding."); | ||||||
|  |     this->jpeg_.close(); | ||||||
|  |     return DECODE_ERROR_UNSUPPORTED_FORMAT; | ||||||
|  |   } | ||||||
|  |   this->decoded_bytes_ = size; | ||||||
|  |   this->jpeg_.close(); | ||||||
|  |   return size; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace online_image | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif  // USE_ONLINE_IMAGE_JPEG_SUPPORT | ||||||
							
								
								
									
										34
									
								
								esphome/components/online_image/jpeg_image.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								esphome/components/online_image/jpeg_image.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "image_decoder.h" | ||||||
|  | #include "esphome/core/defines.h" | ||||||
|  | #ifdef USE_ONLINE_IMAGE_JPEG_SUPPORT | ||||||
|  | #include <JPEGDEC.h> | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace online_image { | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Image decoder specialization for JPEG images. | ||||||
|  |  */ | ||||||
|  | class JpegDecoder : public ImageDecoder { | ||||||
|  |  public: | ||||||
|  |   /** | ||||||
|  |    * @brief Construct a new JPEG Decoder object. | ||||||
|  |    * | ||||||
|  |    * @param display The image to decode the stream into. | ||||||
|  |    */ | ||||||
|  |   JpegDecoder(OnlineImage *image) : ImageDecoder(image) {} | ||||||
|  |   ~JpegDecoder() override {} | ||||||
|  |  | ||||||
|  |   void prepare(size_t download_size) override; | ||||||
|  |   int HOT decode(uint8_t *buffer, size_t size) override; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   JPEGDEC jpeg_{}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace online_image | ||||||
|  | }  // namespace esphome | ||||||
|  |  | ||||||
|  | #endif  // USE_ONLINE_IMAGE_JPEG_SUPPORT | ||||||
| @@ -9,6 +9,9 @@ static const char *const TAG = "online_image"; | |||||||
| #ifdef USE_ONLINE_IMAGE_BMP_SUPPORT | #ifdef USE_ONLINE_IMAGE_BMP_SUPPORT | ||||||
| #include "bmp_image.h" | #include "bmp_image.h" | ||||||
| #endif | #endif | ||||||
|  | #ifdef USE_ONLINE_IMAGE_JPEG_SUPPORT | ||||||
|  | #include "jpeg_image.h" | ||||||
|  | #endif | ||||||
| #ifdef USE_ONLINE_IMAGE_PNG_SUPPORT | #ifdef USE_ONLINE_IMAGE_PNG_SUPPORT | ||||||
| #include "png_image.h" | #include "png_image.h" | ||||||
| #endif | #endif | ||||||
| @@ -32,6 +35,7 @@ OnlineImage::OnlineImage(const std::string &url, int width, int height, ImageFor | |||||||
|     : Image(nullptr, 0, 0, type, transparency), |     : Image(nullptr, 0, 0, type, transparency), | ||||||
|       buffer_(nullptr), |       buffer_(nullptr), | ||||||
|       download_buffer_(download_buffer_size), |       download_buffer_(download_buffer_size), | ||||||
|  |       download_buffer_initial_size_(download_buffer_size), | ||||||
|       format_(format), |       format_(format), | ||||||
|       fixed_width_(width), |       fixed_width_(width), | ||||||
|       fixed_height_(height) { |       fixed_height_(height) { | ||||||
| @@ -123,23 +127,32 @@ void OnlineImage::update() { | |||||||
|  |  | ||||||
| #ifdef USE_ONLINE_IMAGE_BMP_SUPPORT | #ifdef USE_ONLINE_IMAGE_BMP_SUPPORT | ||||||
|   if (this->format_ == ImageFormat::BMP) { |   if (this->format_ == ImageFormat::BMP) { | ||||||
|  |     ESP_LOGD(TAG, "Allocating BMP decoder"); | ||||||
|     this->decoder_ = make_unique<BmpDecoder>(this); |     this->decoder_ = make_unique<BmpDecoder>(this); | ||||||
|   } |   } | ||||||
| #endif  // ONLINE_IMAGE_BMP_SUPPORT | #endif  // ONLINE_IMAGE_BMP_SUPPORT | ||||||
|  | #ifdef USE_ONLINE_IMAGE_JPEG_SUPPORT | ||||||
|  |   if (this->format_ == ImageFormat::JPEG) { | ||||||
|  |     ESP_LOGD(TAG, "Allocating JPEG decoder"); | ||||||
|  |     this->decoder_ = esphome::make_unique<JpegDecoder>(this); | ||||||
|  |   } | ||||||
|  | #endif  // USE_ONLINE_IMAGE_JPEG_SUPPORT | ||||||
| #ifdef USE_ONLINE_IMAGE_PNG_SUPPORT | #ifdef USE_ONLINE_IMAGE_PNG_SUPPORT | ||||||
|   if (this->format_ == ImageFormat::PNG) { |   if (this->format_ == ImageFormat::PNG) { | ||||||
|  |     ESP_LOGD(TAG, "Allocating PNG decoder"); | ||||||
|     this->decoder_ = make_unique<PngDecoder>(this); |     this->decoder_ = make_unique<PngDecoder>(this); | ||||||
|   } |   } | ||||||
| #endif  // ONLINE_IMAGE_PNG_SUPPORT | #endif  // ONLINE_IMAGE_PNG_SUPPORT | ||||||
|  |  | ||||||
|   if (!this->decoder_) { |   if (!this->decoder_) { | ||||||
|     ESP_LOGE(TAG, "Could not instantiate decoder. Image format unsupported."); |     ESP_LOGE(TAG, "Could not instantiate decoder. Image format unsupported: %d", this->format_); | ||||||
|     this->end_connection_(); |     this->end_connection_(); | ||||||
|     this->download_error_callback_.call(); |     this->download_error_callback_.call(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|   this->decoder_->prepare(total_size); |   this->decoder_->prepare(total_size); | ||||||
|   ESP_LOGI(TAG, "Downloading image"); |   ESP_LOGI(TAG, "Downloading image (Size: %d)", total_size); | ||||||
|  |   this->start_time_ = ::time(nullptr); | ||||||
| } | } | ||||||
|  |  | ||||||
| void OnlineImage::loop() { | void OnlineImage::loop() { | ||||||
| @@ -153,6 +166,7 @@ void OnlineImage::loop() { | |||||||
|     this->height_ = buffer_height_; |     this->height_ = buffer_height_; | ||||||
|     ESP_LOGD(TAG, "Image fully downloaded, read %zu bytes, width/height = %d/%d", this->downloader_->get_bytes_read(), |     ESP_LOGD(TAG, "Image fully downloaded, read %zu bytes, width/height = %d/%d", this->downloader_->get_bytes_read(), | ||||||
|              this->width_, this->height_); |              this->width_, this->height_); | ||||||
|  |     ESP_LOGD(TAG, "Total time: %lds", ::time(nullptr) - this->start_time_); | ||||||
|     this->end_connection_(); |     this->end_connection_(); | ||||||
|     this->download_finished_callback_.call(); |     this->download_finished_callback_.call(); | ||||||
|     return; |     return; | ||||||
| @@ -163,6 +177,10 @@ void OnlineImage::loop() { | |||||||
|   } |   } | ||||||
|   size_t available = this->download_buffer_.free_capacity(); |   size_t available = this->download_buffer_.free_capacity(); | ||||||
|   if (available) { |   if (available) { | ||||||
|  |     // Some decoders need to fully download the image before downloading. | ||||||
|  |     // In case of huge images, don't wait blocking until the whole image has been downloaded, | ||||||
|  |     // use smaller chunks | ||||||
|  |     available = std::min(available, this->download_buffer_initial_size_); | ||||||
|     auto len = this->downloader_->read(this->download_buffer_.append(), available); |     auto len = this->downloader_->read(this->download_buffer_.append(), available); | ||||||
|     if (len > 0) { |     if (len > 0) { | ||||||
|       this->download_buffer_.write(len); |       this->download_buffer_.write(len); | ||||||
|   | |||||||
| @@ -23,7 +23,7 @@ using t_http_codes = enum { | |||||||
| enum ImageFormat { | enum ImageFormat { | ||||||
|   /** Automatically detect from MIME type. Not supported yet. */ |   /** Automatically detect from MIME type. Not supported yet. */ | ||||||
|   AUTO, |   AUTO, | ||||||
|   /** JPEG format. Not supported yet. */ |   /** JPEG format. */ | ||||||
|   JPEG, |   JPEG, | ||||||
|   /** PNG format. */ |   /** PNG format. */ | ||||||
|   PNG, |   PNG, | ||||||
| @@ -79,6 +79,13 @@ class OnlineImage : public PollingComponent, | |||||||
|    */ |    */ | ||||||
|   void release(); |   void release(); | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Resize the download buffer | ||||||
|  |    * | ||||||
|  |    * @param size The new size for the download buffer. | ||||||
|  |    */ | ||||||
|  |   size_t resize_download_buffer(size_t size) { return this->download_buffer_.resize(size); } | ||||||
|  |  | ||||||
|   void add_on_finished_callback(std::function<void()> &&callback); |   void add_on_finished_callback(std::function<void()> &&callback); | ||||||
|   void add_on_error_callback(std::function<void()> &&callback); |   void add_on_error_callback(std::function<void()> &&callback); | ||||||
|  |  | ||||||
| @@ -119,6 +126,12 @@ class OnlineImage : public PollingComponent, | |||||||
|  |  | ||||||
|   uint8_t *buffer_; |   uint8_t *buffer_; | ||||||
|   DownloadBuffer download_buffer_; |   DownloadBuffer download_buffer_; | ||||||
|  |   /** | ||||||
|  |    * This is the *initial* size of the download buffer, not the current size. | ||||||
|  |    * The download buffer can be resized at runtime; the download_buffer_initial_size_ | ||||||
|  |    * will *not* change even if the download buffer has been resized. | ||||||
|  |    */ | ||||||
|  |   size_t download_buffer_initial_size_; | ||||||
|  |  | ||||||
|   const ImageFormat format_; |   const ImageFormat format_; | ||||||
|   image::Image *placeholder_{nullptr}; |   image::Image *placeholder_{nullptr}; | ||||||
| @@ -148,6 +161,8 @@ class OnlineImage : public PollingComponent, | |||||||
|    */ |    */ | ||||||
|   int buffer_height_; |   int buffer_height_; | ||||||
|  |  | ||||||
|  |   time_t start_time_; | ||||||
|  |  | ||||||
|   friend bool 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); |   friend void ImageDecoder::draw(int x, int y, int w, int h, const Color &color); | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -2,7 +2,6 @@ | |||||||
| #ifdef USE_ONLINE_IMAGE_PNG_SUPPORT | #ifdef USE_ONLINE_IMAGE_PNG_SUPPORT | ||||||
|  |  | ||||||
| #include "esphome/components/display/display_buffer.h" | #include "esphome/components/display/display_buffer.h" | ||||||
| #include "esphome/core/application.h" |  | ||||||
| #include "esphome/core/helpers.h" | #include "esphome/core/helpers.h" | ||||||
| #include "esphome/core/log.h" | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|   | |||||||
| @@ -62,6 +62,7 @@ | |||||||
| #define USE_NUMBER | #define USE_NUMBER | ||||||
| #define USE_ONLINE_IMAGE_BMP_SUPPORT | #define USE_ONLINE_IMAGE_BMP_SUPPORT | ||||||
| #define USE_ONLINE_IMAGE_PNG_SUPPORT | #define USE_ONLINE_IMAGE_PNG_SUPPORT | ||||||
|  | #define USE_ONLINE_IMAGE_JPEG_SUPPORT | ||||||
| #define USE_OTA | #define USE_OTA | ||||||
| #define USE_OTA_PASSWORD | #define USE_OTA_PASSWORD | ||||||
| #define USE_OTA_STATE_CALLBACK | #define USE_OTA_STATE_CALLBACK | ||||||
|   | |||||||
| @@ -41,6 +41,8 @@ lib_deps = | |||||||
|     functionpointer/arduino-MLX90393@1.0.2 ; mlx90393 |     functionpointer/arduino-MLX90393@1.0.2 ; mlx90393 | ||||||
|     pavlodn/HaierProtocol@0.9.31           ; haier |     pavlodn/HaierProtocol@0.9.31           ; haier | ||||||
|     kikuchan98/pngle@1.0.2                 ; online_image |     kikuchan98/pngle@1.0.2                 ; online_image | ||||||
|  |     ; Using the repository directly, otherwise ESP-IDF can't use the library | ||||||
|  |     https://github.com/bitbank2/JPEGDEC.git#1.6.2            ; online_image | ||||||
|     ; This is using the repository until a new release is published to PlatformIO |     ; This is using the repository until a new release is published to PlatformIO | ||||||
|     https://github.com/Sensirion/arduino-gas-index-algorithm.git#3.2.1 ; Sensirion Gas Index Algorithm Arduino Library |     https://github.com/Sensirion/arduino-gas-index-algorithm.git#3.2.1 ; Sensirion Gas Index Algorithm Arduino Library | ||||||
|     lvgl/lvgl@8.4.0                                       ; lvgl |     lvgl/lvgl@8.4.0                                       ; lvgl | ||||||
|   | |||||||
| @@ -30,6 +30,14 @@ online_image: | |||||||
|     url: https://samples-files.com/samples/images/bmp/480-360-sample.bmp |     url: https://samples-files.com/samples/images/bmp/480-360-sample.bmp | ||||||
|     format: BMP |     format: BMP | ||||||
|     type: BINARY |     type: BINARY | ||||||
|  |   - id: online_jpeg_image | ||||||
|  |     url: http://www.faqs.org/images/library.jpg | ||||||
|  |     format: JPEG | ||||||
|  |     type: RGB | ||||||
|  |   - id: online_jpg_image | ||||||
|  |     url: http://www.faqs.org/images/library.jpg | ||||||
|  |     format: JPG | ||||||
|  |     type: RGB565 | ||||||
|  |  | ||||||
| # Check the set_url action | # Check the set_url action | ||||||
| esphome: | esphome: | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user