diff --git a/esphome/components/display/display_color_utils.h b/esphome/components/display/display_color_utils.h index 7f29586932..bf6d5445f1 100644 --- a/esphome/components/display/display_color_utils.h +++ b/esphome/components/display/display_color_utils.h @@ -107,6 +107,53 @@ class ColorUtil { uint32_t gs4 = esp_scale8(color.white, 15); return gs4; } + /*** + * Converts a Color value to an 8bit index using a 24bit 888 palette. + * Uses euclidiean distance to calculate the linear distance between + * two points in an RGB cube, then iterates through the full palette + * returning the closest match. + * @param[in] color The target color. + * @param[in] palette The 256*3 byte RGB palette. + * @return The 8 bit index of the closest color (e.g. for display buffer). + */ + // static uint8_t color_to_index8_palette888(Color color, uint8_t *palette) { + static uint8_t color_to_index8_palette888(Color color, const uint8_t *palette) { + uint8_t closest_index = 0; + uint32_t minimum_dist2 = UINT32_MAX; // Smallest distance^2 to the target + // so far + // int8_t(*plt)[][3] = palette; + int16_t tgt_r = color.r; + int16_t tgt_g = color.g; + int16_t tgt_b = color.b; + uint16_t x, y, z; + // Loop through each row of the palette + for (uint16_t i = 0; i < 256; i++) { + // Get the pallet rgb color + int16_t plt_r = (int16_t) palette[i * 3 + 0]; + int16_t plt_g = (int16_t) palette[i * 3 + 1]; + int16_t plt_b = (int16_t) palette[i * 3 + 2]; + // Calculate euclidian distance (linear distance in rgb cube). + x = (uint32_t) std::abs(tgt_r - plt_r); + y = (uint32_t) std::abs(tgt_g - plt_g); + z = (uint32_t) std::abs(tgt_b - plt_b); + uint32_t dist2 = x * x + y * y + z * z; + if (dist2 < minimum_dist2) { + minimum_dist2 = dist2; + closest_index = (uint8_t) i; + } + } + return closest_index; + } + /*** + * Converts an 8bit palette index (e.g. from a display buffer) to a color. + * @param[in] index The index to look up. + * @param[in] palette The 256*3 byte RGB palette. + * @return The RGBW Color object looked up by the palette. + */ + static Color index8_to_color_palette888(uint8_t index, const uint8_t *palette) { + Color color = Color(palette[index * 3 + 0], palette[index * 3 + 1], palette[index * 3 + 2], 0); + return color; + } }; } // namespace display } // namespace esphome diff --git a/esphome/components/ili9341/display.py b/esphome/components/ili9341/display.py index 157e8212bd..6b18196ef1 100644 --- a/esphome/components/ili9341/display.py +++ b/esphome/components/ili9341/display.py @@ -3,13 +3,16 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import display, spi from esphome.const import ( + CONF_COLOR_PALETTE, CONF_DC_PIN, CONF_ID, CONF_LAMBDA, CONF_MODEL, CONF_PAGES, + CONF_RAW_DATA_ID, CONF_RESET_PIN, ) +from esphome.core import HexInt DEPENDENCIES = ["spi"] @@ -23,6 +26,7 @@ ILI9341M5Stack = ili9341_ns.class_("ILI9341M5Stack", ili9341) ILI9341TFT24 = ili9341_ns.class_("ILI9341TFT24", ili9341) ILI9341Model = ili9341_ns.enum("ILI9341Model") +ILI9341ColorMode = ili9341_ns.enum("ILI9341ColorMode") MODELS = { "M5STACK": ILI9341Model.M5STACK, @@ -31,6 +35,8 @@ MODELS = { ILI9341_MODEL = cv.enum(MODELS, upper=True, space="_") +COLOR_PALETTE = cv.one_of("NONE", "GRAYSCALE") + CONFIG_SCHEMA = cv.All( display.FULL_DISPLAY_SCHEMA.extend( { @@ -39,6 +45,8 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_LED_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_COLOR_PALETTE, default="NONE"): COLOR_PALETTE, + cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8), } ) .extend(cv.polling_component_schema("1s")) @@ -73,3 +81,13 @@ async def to_code(config): if CONF_LED_PIN in config: led_pin = await cg.gpio_pin_expression(config[CONF_LED_PIN]) cg.add(var.set_led_pin(led_pin)) + + if config[CONF_COLOR_PALETTE] == "GRAYSCALE": + cg.add(var.set_buffer_color_mode(ILI9341ColorMode.BITS_8_INDEXED)) + rhs = [] + for x in range(256): + rhs.extend([HexInt(x), HexInt(x), HexInt(x)]) + prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) + cg.add(var.set_palette(prog_arr)) + else: + pass diff --git a/esphome/components/ili9341/ili9341_display.cpp b/esphome/components/ili9341/ili9341_display.cpp index 09524ba787..0ad5446d9a 100644 --- a/esphome/components/ili9341/ili9341_display.cpp +++ b/esphome/components/ili9341/ili9341_display.cpp @@ -161,8 +161,13 @@ void HOT ILI9341Display::draw_absolute_pixel_internal(int x, int y, Color color) this->y_high_ = (y > this->y_high_) ? y : this->y_high_; uint32_t pos = (y * width_) + x; - uint8_t color332 = display::ColorUtil::color_to_332(color, display::ColorOrder::COLOR_ORDER_RGB); - buffer_[pos] = color332; + if (this->buffer_color_mode_ == BITS_8) { + uint8_t color332 = display::ColorUtil::color_to_332(color, display::ColorOrder::COLOR_ORDER_RGB); + buffer_[pos] = color332; + } else { // if (this->buffer_color_mode_ == BITS_8_INDEXED) { + uint8_t index = display::ColorUtil::color_to_index8_palette888(color, this->palette_); + buffer_[pos] = index; + } } // should return the total size: return this->get_width_internal() * this->get_height_internal() * 2 // 16bit color @@ -227,7 +232,13 @@ uint32_t ILI9341Display::buffer_to_transfer_(uint32_t pos, uint32_t sz) { } for (uint32_t i = 0; i < sz; ++i) { - uint16_t color = display::ColorUtil::color_to_565(display::ColorUtil::rgb332_to_color(*src++)); + uint16_t color; + if (this->buffer_color_mode_ == BITS_8) { + color = display::ColorUtil::color_to_565(display::ColorUtil::rgb332_to_color(*src++)); + } else { // if (this->buffer_color_mode == BITS_8_INDEXED) { + Color col = display::ColorUtil::index8_to_color_palette888(*src++, this->palette_); + color = display::ColorUtil::color_to_565(col); + } *dst++ = (uint8_t)(color >> 8); *dst++ = (uint8_t) color; } diff --git a/esphome/components/ili9341/ili9341_display.h b/esphome/components/ili9341/ili9341_display.h index eeff688f4f..6014dbf9a6 100644 --- a/esphome/components/ili9341/ili9341_display.h +++ b/esphome/components/ili9341/ili9341_display.h @@ -14,6 +14,11 @@ enum ILI9341Model { TFT_24, }; +enum ILI9341ColorMode { + BITS_8, + BITS_8_INDEXED, +}; + class ILI9341Display : public PollingComponent, public display::DisplayBuffer, public spi::SPIDevicereset_pin_ = reset; } void set_led_pin(GPIOPin *led) { this->led_pin_ = led; } void set_model(ILI9341Model model) { this->model_ = model; } + void set_palette(const uint8_t *palette) { this->palette_ = palette; } + void set_buffer_color_mode(ILI9341ColorMode color_mode) { this->buffer_color_mode_ = color_mode; } void command(uint8_t value); void data(uint8_t value); @@ -59,6 +66,9 @@ class ILI9341Display : public PollingComponent, uint16_t y_low_{0}; uint16_t x_high_{0}; uint16_t y_high_{0}; + const uint8_t *palette_; + + ILI9341ColorMode buffer_color_mode_{BITS_8}; uint32_t get_buffer_length_(); int get_width_internal() override; diff --git a/esphome/const.py b/esphome/const.py index c2aa53be70..b73d7e33bd 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -105,6 +105,7 @@ CONF_COLOR_BRIGHTNESS = "color_brightness" CONF_COLOR_CORRECT = "color_correct" CONF_COLOR_INTERLOCK = "color_interlock" CONF_COLOR_MODE = "color_mode" +CONF_COLOR_PALETTE = "color_palette" CONF_COLOR_TEMPERATURE = "color_temperature" CONF_COLORS = "colors" CONF_COMMAND = "command"