diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index 4f62a62c6d..bd468dcbc3 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -204,31 +204,33 @@ void DisplayBuffer::vprintf_(int x, int y, Font *font, Color color, TextAlign al if (ret > 0) this->print(x, y, font, color, align, buffer); } -void DisplayBuffer::image(int x, int y, Image *image) { this->image(x, y, COLOR_ON, image); } -void DisplayBuffer::image(int x, int y, Color color, Image *image, bool invert) { - if (image->get_type() == BINARY) { - for (int img_x = 0; img_x < image->get_width(); img_x++) { - for (int img_y = 0; img_y < image->get_height(); img_y++) { - if (invert) - this->draw_pixel_at(x + img_x, y + img_y, image->get_pixel(img_x, img_y) ? COLOR_OFF : color); - else - this->draw_pixel_at(x + img_x, y + img_y, image->get_pixel(img_x, img_y) ? color : COLOR_OFF); + +void DisplayBuffer::image(int x, int y, Image *image, Color color_on, Color color_off) { + switch (image->get_type()) { + case IMAGE_TYPE_BINARY: + for (int img_x = 0; img_x < image->get_width(); img_x++) { + for (int img_y = 0; img_y < image->get_height(); img_y++) { + this->draw_pixel_at(x + img_x, y + img_y, image->get_pixel(img_x, img_y) ? color_on : color_off); + } } - } - } else if (image->get_type() == GRAYSCALE) { - for (int img_x = 0; img_x < image->get_width(); img_x++) { - for (int img_y = 0; img_y < image->get_height(); img_y++) { - this->draw_pixel_at(x + img_x, y + img_y, image->get_grayscale_pixel(img_x, img_y)); + break; + case IMAGE_TYPE_GRAYSCALE: + for (int img_x = 0; img_x < image->get_width(); img_x++) { + for (int img_y = 0; img_y < image->get_height(); img_y++) { + this->draw_pixel_at(x + img_x, y + img_y, image->get_grayscale_pixel(img_x, img_y)); + } } - } - } else if (image->get_type() == RGB) { - for (int img_x = 0; img_x < image->get_width(); img_x++) { - for (int img_y = 0; img_y < image->get_height(); img_y++) { - this->draw_pixel_at(x + img_x, y + img_y, image->get_color_pixel(img_x, img_y)); + break; + case IMAGE_TYPE_RGB24: + for (int img_x = 0; img_x < image->get_width(); img_x++) { + for (int img_y = 0; img_y < image->get_height(); img_y++) { + this->draw_pixel_at(x + img_x, y + img_y, image->get_color_pixel(img_x, img_y)); + } } - } + break; } } + void DisplayBuffer::get_text_bounds(int x, int y, const char *text, Font *font, TextAlign align, int *x1, int *y1, int *width, int *height) { int x_offset, baseline; @@ -463,15 +465,14 @@ Color Image::get_grayscale_pixel(int x, int y) const { if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) return 0; const uint32_t pos = (x + y * this->width_); - return Color(pgm_read_byte(this->data_start_ + pos) << 24); + const uint8_t gray = pgm_read_byte(this->data_start_ + pos); + return Color(gray | gray << 8 | gray << 16 | gray << 24); } int Image::get_width() const { return this->width_; } int Image::get_height() const { return this->height_; } ImageType Image::get_type() const { return this->type_; } -Image::Image(const uint8_t *data_start, int width, int height) - : width_(width), height_(height), data_start_(data_start) {} -Image::Image(const uint8_t *data_start, int width, int height, int type) - : width_(width), height_(height), type_((ImageType) type), data_start_(data_start) {} +Image::Image(const uint8_t *data_start, int width, int height, ImageType type) + : width_(width), height_(height), type_(type), data_start_(data_start) {} DisplayPage::DisplayPage(const display_writer_t &writer) : writer_(writer) {} void DisplayPage::show() { this->parent_->show_page(this); } diff --git a/esphome/components/display/display_buffer.h b/esphome/components/display/display_buffer.h index a8a308538e..e402b4b021 100644 --- a/esphome/components/display/display_buffer.h +++ b/esphome/components/display/display_buffer.h @@ -68,7 +68,7 @@ extern const Color COLOR_OFF; /// Turn the pixel ON. extern const Color COLOR_ON; -enum ImageType { BINARY = 0, GRAYSCALE = 1, RGB = 2 }; +enum ImageType { IMAGE_TYPE_BINARY = 0, IMAGE_TYPE_GRAYSCALE = 1, IMAGE_TYPE_RGB24 = 2 }; enum DisplayRotation { DISPLAY_ROTATION_0_DEGREES = 0, @@ -262,9 +262,15 @@ class DisplayBuffer { __attribute__((format(strftime, 5, 0))); #endif - /// Draw the `image` with the top-left corner at [x,y] to the screen. - void image(int x, int y, Image *image); - void image(int x, int y, Color color, Image *image, bool invert = false); + /** Draw the `image` with the top-left corner at [x,y] to the screen. + * + * @param x The x coordinate of the upper left corner. + * @param y The y coordinate of the upper left corner. + * @param image The image to draw + * @param color_on The color to replace in binary images for the on bits. + * @param color_off The color to replace in binary images for the off bits. + */ + void image(int x, int y, Image *image, Color color_on = COLOR_ON, Color color_off = COLOR_OFF); /** Get the text bounds of the given string. * @@ -381,8 +387,7 @@ class Font { class Image { public: - Image(const uint8_t *data_start, int width, int height); - Image(const uint8_t *data_start, int width, int height, int type); + Image(const uint8_t *data_start, int width, int height, ImageType type); bool get_pixel(int x, int y) const; Color get_color_pixel(int x, int y) const; Color get_grayscale_pixel(int x, int y) const; @@ -393,7 +398,7 @@ class Image { protected: int width_; int height_; - ImageType type_{BINARY}; + ImageType type_; const uint8_t *data_start_; }; diff --git a/esphome/components/image/__init__.py b/esphome/components/image/__init__.py index b5c9e29f97..dfe387afd1 100644 --- a/esphome/components/image/__init__.py +++ b/esphome/components/image/__init__.py @@ -4,14 +4,20 @@ from esphome import core from esphome.components import display, font import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_FILE, CONF_ID, CONF_RESIZE, CONF_TYPE +from esphome.const import CONF_FILE, CONF_ID, CONF_TYPE, CONF_RESIZE from esphome.core import CORE, HexInt _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['display'] MULTI_CONF = True -ImageType = {'binary': 0, 'grayscale': 1, 'rgb': 2} + +ImageType = display.display_ns.enum('ImageType') +IMAGE_TYPE = { + 'BINARY': ImageType.IMAGE_TYPE_BINARY, + 'GRAYSCALE': ImageType.IMAGE_TYPE_GRAYSCALE, + 'RGB24': ImageType.IMAGE_TYPE_RGB24, +} Image_ = display.display_ns.class_('Image') @@ -21,7 +27,7 @@ IMAGE_SCHEMA = cv.Schema({ cv.Required(CONF_ID): cv.declare_id(Image_), cv.Required(CONF_FILE): cv.file_, cv.Optional(CONF_RESIZE): cv.dimensions, - cv.Optional(CONF_TYPE): cv.string, + cv.Optional(CONF_TYPE, default='BINARY'): cv.enum(IMAGE_TYPE, upper=True), cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), }) @@ -37,44 +43,40 @@ def to_code(config): except Exception as e: raise core.EsphomeError(f"Could not load image file {path}: {e}") + width, height = image.size + if CONF_RESIZE in config: image.thumbnail(config[CONF_RESIZE]) - - if CONF_TYPE in config: - if config[CONF_TYPE].startswith('GRAYSCALE'): - width, height = image.size - image = image.convert('L', dither=Image.NONE) - pixels = list(image.getdata()) - data = [0 for _ in range(height * width)] - pos = 0 - for pix in pixels: - data[pos] = pix - pos += 1 - rhs = [HexInt(x) for x in data] - prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) - cg.new_Pvariable(config[CONF_ID], prog_arr, width, height, ImageType['grayscale']) - elif config[CONF_TYPE].startswith('RGB'): - width, height = image.size - image = image.convert('RGB') - pixels = list(image.getdata()) - data = [0 for _ in range(height * width * 3)] - pos = 0 - for pix in pixels: - data[pos] = pix[0] - pos += 1 - data[pos] = pix[1] - pos += 1 - data[pos] = pix[2] - pos += 1 - rhs = [HexInt(x) for x in data] - prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) - cg.new_Pvariable(config[CONF_ID], prog_arr, width, height, ImageType['rgb']) - else: - image = image.convert('1', dither=Image.NONE) width, height = image.size + else: if width > 500 or height > 500: _LOGGER.warning("The image you requested is very big. Please consider using" " the resize parameter.") + + if config[CONF_TYPE] == 'GRAYSCALE': + image = image.convert('L', dither=Image.NONE) + pixels = list(image.getdata()) + data = [0 for _ in range(height * width)] + pos = 0 + for pix in pixels: + data[pos] = pix + pos += 1 + + elif config[CONF_TYPE] == 'RGB24': + image = image.convert('RGB') + pixels = list(image.getdata()) + data = [0 for _ in range(height * width * 3)] + pos = 0 + for pix in pixels: + data[pos] = pix[0] + pos += 1 + data[pos] = pix[1] + pos += 1 + data[pos] = pix[2] + pos += 1 + + elif config[CONF_TYPE] == 'BINARY': + image = image.convert('1', dither=Image.NONE) width8 = ((width + 7) // 8) * 8 data = [0 for _ in range(height * width8 // 8)] for y in range(height): @@ -84,6 +86,7 @@ def to_code(config): pos = x + y * width8 data[pos // 8] |= 0x80 >> (pos % 8) - rhs = [HexInt(x) for x in data] - prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) - cg.new_Pvariable(config[CONF_ID], prog_arr, width, height) + rhs = [HexInt(x) for x in data] + prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) + cg.new_Pvariable(config[CONF_ID], prog_arr, width, height, + IMAGE_TYPE[config[CONF_TYPE]])