20260326
This commit is contained in:
224
esphome/components/display/__init__.py
Normal file
224
esphome/components/display/__init__.py
Normal file
@@ -0,0 +1,224 @@
|
||||
from esphome import automation, core
|
||||
from esphome.automation import maybe_simple_id
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_AUTO_CLEAR_ENABLED,
|
||||
CONF_FROM,
|
||||
CONF_ID,
|
||||
CONF_LAMBDA,
|
||||
CONF_PAGE_ID,
|
||||
CONF_PAGES,
|
||||
CONF_ROTATION,
|
||||
CONF_TO,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_UPDATE_INTERVAL,
|
||||
SCHEDULER_DONT_RUN,
|
||||
)
|
||||
from esphome.core import CoroPriority, coroutine_with_priority
|
||||
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
display_ns = cg.esphome_ns.namespace("display")
|
||||
Display = display_ns.class_("Display", cg.PollingComponent)
|
||||
DisplayBuffer = display_ns.class_("DisplayBuffer", Display)
|
||||
DisplayPage = display_ns.class_("DisplayPage")
|
||||
DisplayPagePtr = DisplayPage.operator("ptr")
|
||||
DisplayRef = Display.operator("ref")
|
||||
DisplayPageShowAction = display_ns.class_("DisplayPageShowAction", automation.Action)
|
||||
DisplayPageShowNextAction = display_ns.class_(
|
||||
"DisplayPageShowNextAction", automation.Action
|
||||
)
|
||||
DisplayPageShowPrevAction = display_ns.class_(
|
||||
"DisplayPageShowPrevAction", automation.Action
|
||||
)
|
||||
DisplayIsDisplayingPageCondition = display_ns.class_(
|
||||
"DisplayIsDisplayingPageCondition", automation.Condition
|
||||
)
|
||||
DisplayOnPageChangeTrigger = display_ns.class_(
|
||||
"DisplayOnPageChangeTrigger", automation.Trigger
|
||||
)
|
||||
|
||||
CONF_ON_PAGE_CHANGE = "on_page_change"
|
||||
CONF_SHOW_TEST_CARD = "show_test_card"
|
||||
CONF_UNSPECIFIED = "unspecified"
|
||||
|
||||
DISPLAY_ROTATIONS = {
|
||||
0: display_ns.DISPLAY_ROTATION_0_DEGREES,
|
||||
90: display_ns.DISPLAY_ROTATION_90_DEGREES,
|
||||
180: display_ns.DISPLAY_ROTATION_180_DEGREES,
|
||||
270: display_ns.DISPLAY_ROTATION_270_DEGREES,
|
||||
}
|
||||
|
||||
|
||||
def validate_rotation(value):
|
||||
value = cv.string(value)
|
||||
value = value.removesuffix("°")
|
||||
return cv.enum(DISPLAY_ROTATIONS, int=True)(value)
|
||||
|
||||
|
||||
def validate_auto_clear(value):
|
||||
if value == CONF_UNSPECIFIED:
|
||||
return value
|
||||
return cv.boolean(value)
|
||||
|
||||
|
||||
BASIC_DISPLAY_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Exclusive(CONF_LAMBDA, CONF_LAMBDA): cv.lambda_,
|
||||
}
|
||||
).extend(cv.polling_component_schema("1s"))
|
||||
|
||||
|
||||
def _validate_test_card(config):
|
||||
if (
|
||||
config.get(CONF_SHOW_TEST_CARD, False)
|
||||
and config.get(CONF_UPDATE_INTERVAL, False) == SCHEDULER_DONT_RUN
|
||||
):
|
||||
raise cv.Invalid(
|
||||
f"`{CONF_SHOW_TEST_CARD}: True` cannot be used with `{CONF_UPDATE_INTERVAL}: never` because this combination will not show a test_card."
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
FULL_DISPLAY_SCHEMA = BASIC_DISPLAY_SCHEMA.extend(
|
||||
{
|
||||
cv.Optional(CONF_ROTATION): validate_rotation,
|
||||
cv.Exclusive(CONF_PAGES, CONF_LAMBDA): cv.All(
|
||||
cv.ensure_list(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DisplayPage),
|
||||
cv.Required(CONF_LAMBDA): cv.lambda_,
|
||||
}
|
||||
),
|
||||
cv.Length(min=1),
|
||||
),
|
||||
cv.Optional(CONF_ON_PAGE_CHANGE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
DisplayOnPageChangeTrigger
|
||||
),
|
||||
cv.Optional(CONF_FROM): cv.use_id(DisplayPage),
|
||||
cv.Optional(CONF_TO): cv.use_id(DisplayPage),
|
||||
}
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_AUTO_CLEAR_ENABLED, default=CONF_UNSPECIFIED
|
||||
): validate_auto_clear,
|
||||
cv.Optional(CONF_SHOW_TEST_CARD): cv.boolean,
|
||||
}
|
||||
)
|
||||
FULL_DISPLAY_SCHEMA.add_extra(_validate_test_card)
|
||||
|
||||
|
||||
async def setup_display_core_(var, config):
|
||||
if CONF_ROTATION in config:
|
||||
cg.add(var.set_rotation(DISPLAY_ROTATIONS[config[CONF_ROTATION]]))
|
||||
|
||||
if (auto_clear := config.get(CONF_AUTO_CLEAR_ENABLED)) is not None:
|
||||
# Default to true if pages or lambda is specified. Ideally this would be done during validation, but
|
||||
# the possible schemas are too complex to do this easily.
|
||||
if auto_clear == CONF_UNSPECIFIED:
|
||||
auto_clear = CONF_LAMBDA in config or CONF_PAGES in config
|
||||
cg.add(var.set_auto_clear(auto_clear))
|
||||
|
||||
if CONF_PAGES in config:
|
||||
pages = []
|
||||
for conf in config[CONF_PAGES]:
|
||||
lambda_ = await cg.process_lambda(
|
||||
conf[CONF_LAMBDA], [(DisplayRef, "it")], return_type=cg.void
|
||||
)
|
||||
page = cg.new_Pvariable(conf[CONF_ID], lambda_)
|
||||
pages.append(page)
|
||||
cg.add(var.set_pages(pages))
|
||||
for conf in config.get(CONF_ON_PAGE_CHANGE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
if CONF_FROM in conf:
|
||||
page = await cg.get_variable(conf[CONF_FROM])
|
||||
cg.add(trigger.set_from(page))
|
||||
if CONF_TO in conf:
|
||||
page = await cg.get_variable(conf[CONF_TO])
|
||||
cg.add(trigger.set_to(page))
|
||||
await automation.build_automation(
|
||||
trigger, [(DisplayPagePtr, "from"), (DisplayPagePtr, "to")], conf
|
||||
)
|
||||
if config.get(CONF_SHOW_TEST_CARD):
|
||||
cg.add(var.show_test_card())
|
||||
|
||||
|
||||
async def register_display(var, config):
|
||||
await cg.register_component(var, config)
|
||||
await setup_display_core_(var, config)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"display.page.show",
|
||||
DisplayPageShowAction,
|
||||
maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.templatable(cv.use_id(DisplayPage)),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def display_page_show_to_code(config, action_id, template_arg, args):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
if isinstance(config[CONF_ID], core.Lambda):
|
||||
template_ = await cg.templatable(config[CONF_ID], args, DisplayPagePtr)
|
||||
cg.add(var.set_page(template_))
|
||||
else:
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
cg.add(var.set_page(paren))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"display.page.show_next",
|
||||
DisplayPageShowNextAction,
|
||||
maybe_simple_id(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.templatable(cv.use_id(Display)),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def display_page_show_next_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"display.page.show_previous",
|
||||
DisplayPageShowPrevAction,
|
||||
maybe_simple_id(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.templatable(cv.use_id(Display)),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def display_page_show_previous_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_condition(
|
||||
"display.is_displaying_page",
|
||||
DisplayIsDisplayingPageCondition,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.use_id(Display),
|
||||
cv.Required(CONF_PAGE_ID): cv.use_id(DisplayPage),
|
||||
},
|
||||
key=CONF_PAGE_ID,
|
||||
),
|
||||
)
|
||||
async def display_is_displaying_page_to_code(config, condition_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
page = await cg.get_variable(config[CONF_PAGE_ID])
|
||||
var = cg.new_Pvariable(condition_id, template_arg, paren)
|
||||
cg.add(var.set_page(page))
|
||||
return var
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.CORE)
|
||||
async def to_code(config):
|
||||
cg.add_global(display_ns.using)
|
||||
cg.add_define("USE_DISPLAY")
|
||||
870
esphome/components/display/display.cpp
Normal file
870
esphome/components/display/display.cpp
Normal file
@@ -0,0 +1,870 @@
|
||||
#include "display.h"
|
||||
#include <utility>
|
||||
#include <numbers>
|
||||
#include "display_color_utils.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace display {
|
||||
|
||||
static const char *const TAG = "display";
|
||||
|
||||
const Color COLOR_OFF(0, 0, 0, 0);
|
||||
const Color COLOR_ON(255, 255, 255, 255);
|
||||
|
||||
void Display::fill(Color color) { this->filled_rectangle(0, 0, this->get_width(), this->get_height(), color); }
|
||||
void Display::clear() { this->fill(COLOR_OFF); }
|
||||
void Display::set_rotation(DisplayRotation rotation) { this->rotation_ = rotation; }
|
||||
void HOT Display::line(int x1, int y1, int x2, int y2, Color color) {
|
||||
const int32_t dx = abs(x2 - x1), sx = x1 < x2 ? 1 : -1;
|
||||
const int32_t dy = -abs(y2 - y1), sy = y1 < y2 ? 1 : -1;
|
||||
int32_t err = dx + dy;
|
||||
|
||||
while (true) {
|
||||
this->draw_pixel_at(x1, y1, color);
|
||||
if (x1 == x2 && y1 == y2)
|
||||
break;
|
||||
int32_t e2 = 2 * err;
|
||||
if (e2 >= dy) {
|
||||
err += dy;
|
||||
x1 += sx;
|
||||
}
|
||||
if (e2 <= dx) {
|
||||
err += dx;
|
||||
y1 += sy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Display::line_at_angle(int x, int y, int angle, int length, Color color) {
|
||||
this->line_at_angle(x, y, angle, 0, length, color);
|
||||
}
|
||||
|
||||
void Display::line_at_angle(int x, int y, int angle, int start_radius, int stop_radius, Color color) {
|
||||
// Calculate start and end points
|
||||
int x1 = (start_radius * cos(angle * M_PI / 180)) + x;
|
||||
int y1 = (start_radius * sin(angle * M_PI / 180)) + y;
|
||||
int x2 = (stop_radius * cos(angle * M_PI / 180)) + x;
|
||||
int y2 = (stop_radius * sin(angle * M_PI / 180)) + y;
|
||||
|
||||
// Draw line
|
||||
this->line(x1, y1, x2, y2, color);
|
||||
}
|
||||
|
||||
void Display::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order,
|
||||
ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) {
|
||||
size_t line_stride = x_offset + w + x_pad; // length of each source line in pixels
|
||||
uint32_t color_value;
|
||||
for (int y = 0; y != h; y++) {
|
||||
size_t source_idx = (y_offset + y) * line_stride + x_offset;
|
||||
size_t source_idx_mod;
|
||||
for (int x = 0; x != w; x++, source_idx++) {
|
||||
switch (bitness) {
|
||||
default:
|
||||
color_value = ptr[source_idx];
|
||||
break;
|
||||
case COLOR_BITNESS_565:
|
||||
source_idx_mod = source_idx * 2;
|
||||
if (big_endian) {
|
||||
color_value = (ptr[source_idx_mod] << 8) + ptr[source_idx_mod + 1];
|
||||
} else {
|
||||
color_value = ptr[source_idx_mod] + (ptr[source_idx_mod + 1] << 8);
|
||||
}
|
||||
break;
|
||||
case COLOR_BITNESS_888:
|
||||
source_idx_mod = source_idx * 3;
|
||||
if (big_endian) {
|
||||
color_value = (ptr[source_idx_mod + 0] << 16) + (ptr[source_idx_mod + 1] << 8) + ptr[source_idx_mod + 2];
|
||||
} else {
|
||||
color_value = ptr[source_idx_mod + 0] + (ptr[source_idx_mod + 1] << 8) + (ptr[source_idx_mod + 2] << 16);
|
||||
}
|
||||
break;
|
||||
}
|
||||
this->draw_pixel_at(x + x_start, y + y_start, ColorUtil::to_color(color_value, order, bitness));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HOT Display::horizontal_line(int x, int y, int width, Color color) {
|
||||
// Future: Could be made more efficient by manipulating buffer directly in certain rotations.
|
||||
for (int i = x; i < x + width; i++)
|
||||
this->draw_pixel_at(i, y, color);
|
||||
}
|
||||
void HOT Display::vertical_line(int x, int y, int height, Color color) {
|
||||
// Future: Could be made more efficient by manipulating buffer directly in certain rotations.
|
||||
for (int i = y; i < y + height; i++)
|
||||
this->draw_pixel_at(x, i, color);
|
||||
}
|
||||
void Display::rectangle(int x1, int y1, int width, int height, Color color) {
|
||||
this->horizontal_line(x1, y1, width, color);
|
||||
this->horizontal_line(x1, y1 + height - 1, width, color);
|
||||
this->vertical_line(x1, y1, height, color);
|
||||
this->vertical_line(x1 + width - 1, y1, height, color);
|
||||
}
|
||||
void Display::filled_rectangle(int x1, int y1, int width, int height, Color color) {
|
||||
// Future: Use vertical_line and horizontal_line methods depending on rotation to reduce memory accesses.
|
||||
for (int i = y1; i < y1 + height; i++) {
|
||||
this->horizontal_line(x1, i, width, color);
|
||||
}
|
||||
}
|
||||
void HOT Display::circle(int center_x, int center_xy, int radius, Color color) {
|
||||
int dx = -radius;
|
||||
int dy = 0;
|
||||
int err = 2 - 2 * radius;
|
||||
int e2;
|
||||
|
||||
do {
|
||||
this->draw_pixel_at(center_x - dx, center_xy + dy, color);
|
||||
this->draw_pixel_at(center_x + dx, center_xy + dy, color);
|
||||
this->draw_pixel_at(center_x + dx, center_xy - dy, color);
|
||||
this->draw_pixel_at(center_x - dx, center_xy - dy, color);
|
||||
e2 = err;
|
||||
if (e2 < dy) {
|
||||
err += ++dy * 2 + 1;
|
||||
if (-dx == dy && e2 <= dx) {
|
||||
e2 = 0;
|
||||
}
|
||||
}
|
||||
if (e2 > dx) {
|
||||
err += ++dx * 2 + 1;
|
||||
}
|
||||
} while (dx <= 0);
|
||||
}
|
||||
void Display::filled_circle(int center_x, int center_y, int radius, Color color) {
|
||||
int dx = -int32_t(radius);
|
||||
int dy = 0;
|
||||
int err = 2 - 2 * radius;
|
||||
int e2;
|
||||
|
||||
do {
|
||||
this->draw_pixel_at(center_x - dx, center_y + dy, color);
|
||||
this->draw_pixel_at(center_x + dx, center_y + dy, color);
|
||||
this->draw_pixel_at(center_x + dx, center_y - dy, color);
|
||||
this->draw_pixel_at(center_x - dx, center_y - dy, color);
|
||||
int hline_width = 2 * (-dx) + 1;
|
||||
this->horizontal_line(center_x + dx, center_y + dy, hline_width, color);
|
||||
this->horizontal_line(center_x + dx, center_y - dy, hline_width, color);
|
||||
e2 = err;
|
||||
if (e2 < dy) {
|
||||
err += ++dy * 2 + 1;
|
||||
if (-dx == dy && e2 <= dx) {
|
||||
e2 = 0;
|
||||
}
|
||||
}
|
||||
if (e2 > dx) {
|
||||
err += ++dx * 2 + 1;
|
||||
}
|
||||
} while (dx <= 0);
|
||||
}
|
||||
void Display::filled_ring(int center_x, int center_y, int radius1, int radius2, Color color) {
|
||||
int rmax = radius1 > radius2 ? radius1 : radius2;
|
||||
int rmin = radius1 < radius2 ? radius1 : radius2;
|
||||
int dxmax = -int32_t(rmax), dxmin = -int32_t(rmin);
|
||||
int dymax = 0, dymin = 0;
|
||||
int errmax = 2 - 2 * rmax, errmin = 2 - 2 * rmin;
|
||||
int e2max, e2min;
|
||||
do {
|
||||
// 8 dots for borders
|
||||
this->draw_pixel_at(center_x - dxmax, center_y + dymax, color);
|
||||
this->draw_pixel_at(center_x + dxmax, center_y + dymax, color);
|
||||
this->draw_pixel_at(center_x - dxmin, center_y + dymin, color);
|
||||
this->draw_pixel_at(center_x + dxmin, center_y + dymin, color);
|
||||
this->draw_pixel_at(center_x + dxmax, center_y - dymax, color);
|
||||
this->draw_pixel_at(center_x - dxmax, center_y - dymax, color);
|
||||
this->draw_pixel_at(center_x + dxmin, center_y - dymin, color);
|
||||
this->draw_pixel_at(center_x - dxmin, center_y - dymin, color);
|
||||
if (dymin < rmin) {
|
||||
// two parts - four lines
|
||||
int hline_width = -(dxmax - dxmin) + 1;
|
||||
this->horizontal_line(center_x + dxmax, center_y + dymax, hline_width, color);
|
||||
this->horizontal_line(center_x - dxmin, center_y + dymax, hline_width, color);
|
||||
this->horizontal_line(center_x + dxmax, center_y - dymax, hline_width, color);
|
||||
this->horizontal_line(center_x - dxmin, center_y - dymax, hline_width, color);
|
||||
} else {
|
||||
// one part - top and bottom
|
||||
int hline_width = 2 * (-dxmax) + 1;
|
||||
this->horizontal_line(center_x + dxmax, center_y + dymax, hline_width, color);
|
||||
this->horizontal_line(center_x + dxmax, center_y - dymax, hline_width, color);
|
||||
}
|
||||
e2max = errmax;
|
||||
// tune external
|
||||
if (e2max < dymax) {
|
||||
errmax += ++dymax * 2 + 1;
|
||||
if (-dxmax == dymax && e2max <= dxmax) {
|
||||
e2max = 0;
|
||||
}
|
||||
}
|
||||
if (e2max > dxmax) {
|
||||
errmax += ++dxmax * 2 + 1;
|
||||
}
|
||||
// tune internal
|
||||
while (dymin < dymax && dymin < rmin) {
|
||||
e2min = errmin;
|
||||
if (e2min < dymin) {
|
||||
errmin += ++dymin * 2 + 1;
|
||||
if (-dxmin == dymin && e2min <= dxmin) {
|
||||
e2min = 0;
|
||||
}
|
||||
}
|
||||
if (e2min > dxmin) {
|
||||
errmin += ++dxmin * 2 + 1;
|
||||
}
|
||||
}
|
||||
} while (dxmax <= 0);
|
||||
}
|
||||
void Display::filled_gauge(int center_x, int center_y, int radius1, int radius2, int progress, Color color) {
|
||||
int rmax = radius1 > radius2 ? radius1 : radius2;
|
||||
int rmin = radius1 < radius2 ? radius1 : radius2;
|
||||
int dxmax = -int32_t(rmax), dxmin = -int32_t(rmin), upd_dxmax, upd_dxmin;
|
||||
int dymax = 0, dymin = 0;
|
||||
int errmax = 2 - 2 * rmax, errmin = 2 - 2 * rmin;
|
||||
int e2max, e2min;
|
||||
progress = std::max(0, std::min(progress, 100)); // 0..100
|
||||
int draw_progress = progress > 50 ? (100 - progress) : progress;
|
||||
float tan_a = (progress == 50) ? 65535 : tan(float(draw_progress) * M_PI / 100); // slope
|
||||
|
||||
do {
|
||||
// outer dots
|
||||
this->draw_pixel_at(center_x + dxmax, center_y - dymax, color);
|
||||
this->draw_pixel_at(center_x - dxmax, center_y - dymax, color);
|
||||
if (dymin < rmin) { // side parts
|
||||
int lhline_width = -(dxmax - dxmin) + 1;
|
||||
if (progress >= 50) {
|
||||
if (float(dymax) < float(-dxmax) * tan_a) {
|
||||
upd_dxmax = ceil(float(dymax) / tan_a);
|
||||
} else {
|
||||
upd_dxmax = -dxmax;
|
||||
}
|
||||
this->horizontal_line(center_x + dxmax, center_y - dymax, lhline_width, color); // left
|
||||
if (!dymax)
|
||||
this->horizontal_line(center_x - dxmin, center_y, lhline_width, color); // right horizontal border
|
||||
if (upd_dxmax > -dxmin) { // right
|
||||
int rhline_width = (upd_dxmax + dxmin) + 1;
|
||||
this->horizontal_line(center_x - dxmin, center_y - dymax,
|
||||
rhline_width > lhline_width ? lhline_width : rhline_width, color);
|
||||
}
|
||||
} else {
|
||||
if (float(dymin) > float(-dxmin) * tan_a) {
|
||||
upd_dxmin = ceil(float(dymin) / tan_a);
|
||||
} else {
|
||||
upd_dxmin = -dxmin;
|
||||
}
|
||||
lhline_width = -(dxmax + upd_dxmin) + 1;
|
||||
if (!dymax)
|
||||
this->horizontal_line(center_x - dxmin, center_y, lhline_width, color); // right horizontal border
|
||||
if (lhline_width > 0)
|
||||
this->horizontal_line(center_x + dxmax, center_y - dymax, lhline_width, color);
|
||||
}
|
||||
} else { // top part
|
||||
int hline_width = 2 * (-dxmax) + 1;
|
||||
if (progress >= 50) {
|
||||
if (dymax < float(-dxmax) * tan_a) {
|
||||
upd_dxmax = ceil(float(dymax) / tan_a);
|
||||
hline_width = -dxmax + upd_dxmax + 1;
|
||||
}
|
||||
} else {
|
||||
if (dymax < float(-dxmax) * tan_a) {
|
||||
upd_dxmax = ceil(float(dymax) / tan_a);
|
||||
hline_width = -dxmax - upd_dxmax + 1;
|
||||
} else {
|
||||
hline_width = 0;
|
||||
}
|
||||
}
|
||||
if (hline_width > 0)
|
||||
this->horizontal_line(center_x + dxmax, center_y - dymax, hline_width, color);
|
||||
}
|
||||
e2max = errmax;
|
||||
if (e2max < dymax) {
|
||||
errmax += ++dymax * 2 + 1;
|
||||
if (-dxmax == dymax && e2max <= dxmax) {
|
||||
e2max = 0;
|
||||
}
|
||||
}
|
||||
if (e2max > dxmax) {
|
||||
errmax += ++dxmax * 2 + 1;
|
||||
}
|
||||
while (dymin <= dymax && dymin <= rmin && dxmin <= 0) {
|
||||
this->draw_pixel_at(center_x + dxmin, center_y - dymin, color);
|
||||
this->draw_pixel_at(center_x - dxmin, center_y - dymin, color);
|
||||
e2min = errmin;
|
||||
if (e2min < dymin) {
|
||||
errmin += ++dymin * 2 + 1;
|
||||
if (-dxmin == dymin && e2min <= dxmin) {
|
||||
e2min = 0;
|
||||
}
|
||||
}
|
||||
if (e2min > dxmin) {
|
||||
errmin += ++dxmin * 2 + 1;
|
||||
}
|
||||
}
|
||||
} while (dxmax <= 0);
|
||||
}
|
||||
void HOT Display::triangle(int x1, int y1, int x2, int y2, int x3, int y3, Color color) {
|
||||
this->line(x1, y1, x2, y2, color);
|
||||
this->line(x1, y1, x3, y3, color);
|
||||
this->line(x2, y2, x3, y3, color);
|
||||
}
|
||||
void Display::sort_triangle_points_by_y_(int *x1, int *y1, int *x2, int *y2, int *x3, int *y3) {
|
||||
if (*y1 > *y2) {
|
||||
int x_temp = *x1, y_temp = *y1;
|
||||
*x1 = *x2, *y1 = *y2;
|
||||
*x2 = x_temp, *y2 = y_temp;
|
||||
}
|
||||
if (*y1 > *y3) {
|
||||
int x_temp = *x1, y_temp = *y1;
|
||||
*x1 = *x3, *y1 = *y3;
|
||||
*x3 = x_temp, *y3 = y_temp;
|
||||
}
|
||||
if (*y2 > *y3) {
|
||||
int x_temp = *x2, y_temp = *y2;
|
||||
*x2 = *x3, *y2 = *y3;
|
||||
*x3 = x_temp, *y3 = y_temp;
|
||||
}
|
||||
}
|
||||
void Display::filled_flat_side_triangle_(int x1, int y1, int x2, int y2, int x3, int y3, Color color) {
|
||||
// y2 must be equal to y3 (same horizontal line)
|
||||
|
||||
// Initialize Bresenham's algorithm for side 1
|
||||
int s1_current_x = x1;
|
||||
int s1_current_y = y1;
|
||||
bool s1_axis_swap = false;
|
||||
int s1_dx = abs(x2 - x1);
|
||||
int s1_dy = abs(y2 - y1);
|
||||
int s1_sign_x = ((x2 - x1) >= 0) ? 1 : -1;
|
||||
int s1_sign_y = ((y2 - y1) >= 0) ? 1 : -1;
|
||||
if (s1_dy > s1_dx) { // swap values
|
||||
int tmp = s1_dx;
|
||||
s1_dx = s1_dy;
|
||||
s1_dy = tmp;
|
||||
s1_axis_swap = true;
|
||||
}
|
||||
int s1_error = 2 * s1_dy - s1_dx;
|
||||
|
||||
// Initialize Bresenham's algorithm for side 2
|
||||
int s2_current_x = x1;
|
||||
int s2_current_y = y1;
|
||||
bool s2_axis_swap = false;
|
||||
int s2_dx = abs(x3 - x1);
|
||||
int s2_dy = abs(y3 - y1);
|
||||
int s2_sign_x = ((x3 - x1) >= 0) ? 1 : -1;
|
||||
int s2_sign_y = ((y3 - y1) >= 0) ? 1 : -1;
|
||||
if (s2_dy > s2_dx) { // swap values
|
||||
int tmp = s2_dx;
|
||||
s2_dx = s2_dy;
|
||||
s2_dy = tmp;
|
||||
s2_axis_swap = true;
|
||||
}
|
||||
int s2_error = 2 * s2_dy - s2_dx;
|
||||
|
||||
// Iterate on side 1 and allow side 2 to be processed to match the advance of the y-axis.
|
||||
for (int i = 0; i <= s1_dx; i++) {
|
||||
if (s1_current_x <= s2_current_x) {
|
||||
this->horizontal_line(s1_current_x, s1_current_y, s2_current_x - s1_current_x + 1, color);
|
||||
} else {
|
||||
this->horizontal_line(s2_current_x, s2_current_y, s1_current_x - s2_current_x + 1, color);
|
||||
}
|
||||
|
||||
// Bresenham's #1
|
||||
// Side 1 s1_current_x and s1_current_y calculation
|
||||
while (s1_error >= 0) {
|
||||
if (s1_axis_swap) {
|
||||
s1_current_x += s1_sign_x;
|
||||
} else {
|
||||
s1_current_y += s1_sign_y;
|
||||
}
|
||||
s1_error = s1_error - 2 * s1_dx;
|
||||
}
|
||||
if (s1_axis_swap) {
|
||||
s1_current_y += s1_sign_y;
|
||||
} else {
|
||||
s1_current_x += s1_sign_x;
|
||||
}
|
||||
s1_error = s1_error + 2 * s1_dy;
|
||||
|
||||
// Bresenham's #2
|
||||
// Side 2 s2_current_x and s2_current_y calculation
|
||||
while (s2_current_y != s1_current_y) {
|
||||
while (s2_error >= 0) {
|
||||
if (s2_axis_swap) {
|
||||
s2_current_x += s2_sign_x;
|
||||
} else {
|
||||
s2_current_y += s2_sign_y;
|
||||
}
|
||||
s2_error = s2_error - 2 * s2_dx;
|
||||
}
|
||||
if (s2_axis_swap) {
|
||||
s2_current_y += s2_sign_y;
|
||||
} else {
|
||||
s2_current_x += s2_sign_x;
|
||||
}
|
||||
s2_error = s2_error + 2 * s2_dy;
|
||||
}
|
||||
}
|
||||
}
|
||||
void Display::filled_triangle(int x1, int y1, int x2, int y2, int x3, int y3, Color color) {
|
||||
// Sort the three points by y-coordinate ascending, so [x1,y1] is the topmost point
|
||||
this->sort_triangle_points_by_y_(&x1, &y1, &x2, &y2, &x3, &y3);
|
||||
|
||||
if (y2 == y3) { // Check for special case of a bottom-flat triangle
|
||||
this->filled_flat_side_triangle_(x1, y1, x2, y2, x3, y3, color);
|
||||
} else if (y1 == y2) { // Check for special case of a top-flat triangle
|
||||
this->filled_flat_side_triangle_(x3, y3, x1, y1, x2, y2, color);
|
||||
} else { // General case: split the no-flat-side triangle in a top-flat triangle and bottom-flat triangle
|
||||
int x_temp = (int) (x1 + ((float) (y2 - y1) / (float) (y3 - y1)) * (x3 - x1)), y_temp = y2;
|
||||
this->filled_flat_side_triangle_(x1, y1, x2, y2, x_temp, y_temp, color);
|
||||
this->filled_flat_side_triangle_(x3, y3, x2, y2, x_temp, y_temp, color);
|
||||
}
|
||||
}
|
||||
void HOT Display::get_regular_polygon_vertex(int vertex_id, int *vertex_x, int *vertex_y, int center_x, int center_y,
|
||||
int radius, int edges, RegularPolygonVariation variation,
|
||||
float rotation_degrees) {
|
||||
if (edges >= 2) {
|
||||
// Given the orientation of the display component, an angle is measured clockwise from the x axis.
|
||||
// For a regular polygon, the human reference would be the top of the polygon,
|
||||
// hence we rotate the shape by 270° to orient the polygon up.
|
||||
rotation_degrees += ROTATION_270_DEGREES;
|
||||
// Convert the rotation to radians, easier to use in trigonometrical calculations
|
||||
float rotation_radians = rotation_degrees * std::numbers::pi / 180;
|
||||
// A pointy top variation means the first vertex of the polygon is at the top center of the shape, this requires no
|
||||
// additional rotation of the shape.
|
||||
// A flat top variation means the first point of the polygon has to be rotated so that the first edge is horizontal,
|
||||
// this requires to rotate the shape by π/edges radians counter-clockwise so that the first point is located on the
|
||||
// left side of the first horizontal edge.
|
||||
rotation_radians -= (variation == VARIATION_FLAT_TOP) ? std::numbers::pi / edges : 0.0;
|
||||
|
||||
float vertex_angle = ((float) vertex_id) / edges * 2 * std::numbers::pi + rotation_radians;
|
||||
*vertex_x = (int) round(cos(vertex_angle) * radius) + center_x;
|
||||
*vertex_y = (int) round(sin(vertex_angle) * radius) + center_y;
|
||||
}
|
||||
}
|
||||
|
||||
void HOT Display::regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation,
|
||||
float rotation_degrees, Color color, RegularPolygonDrawing drawing) {
|
||||
if (edges >= 2) {
|
||||
int previous_vertex_x, previous_vertex_y;
|
||||
for (int current_vertex_id = 0; current_vertex_id <= edges; current_vertex_id++) {
|
||||
int current_vertex_x, current_vertex_y;
|
||||
get_regular_polygon_vertex(current_vertex_id, ¤t_vertex_x, ¤t_vertex_y, x, y, radius, edges,
|
||||
variation, rotation_degrees);
|
||||
if (current_vertex_id > 0) { // Start drawing after the 2nd vertex coordinates has been calculated
|
||||
if (drawing == DRAWING_FILLED) {
|
||||
this->filled_triangle(x, y, previous_vertex_x, previous_vertex_y, current_vertex_x, current_vertex_y, color);
|
||||
} else if (drawing == DRAWING_OUTLINE) {
|
||||
this->line(previous_vertex_x, previous_vertex_y, current_vertex_x, current_vertex_y, color);
|
||||
}
|
||||
}
|
||||
previous_vertex_x = current_vertex_x;
|
||||
previous_vertex_y = current_vertex_y;
|
||||
}
|
||||
}
|
||||
}
|
||||
void HOT Display::regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation, Color color,
|
||||
RegularPolygonDrawing drawing) {
|
||||
regular_polygon(x, y, radius, edges, variation, ROTATION_0_DEGREES, color, drawing);
|
||||
}
|
||||
void HOT Display::regular_polygon(int x, int y, int radius, int edges, Color color, RegularPolygonDrawing drawing) {
|
||||
regular_polygon(x, y, radius, edges, VARIATION_POINTY_TOP, ROTATION_0_DEGREES, color, drawing);
|
||||
}
|
||||
void Display::filled_regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation,
|
||||
float rotation_degrees, Color color) {
|
||||
regular_polygon(x, y, radius, edges, variation, rotation_degrees, color, DRAWING_FILLED);
|
||||
}
|
||||
void Display::filled_regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation,
|
||||
Color color) {
|
||||
regular_polygon(x, y, radius, edges, variation, ROTATION_0_DEGREES, color, DRAWING_FILLED);
|
||||
}
|
||||
void Display::filled_regular_polygon(int x, int y, int radius, int edges, Color color) {
|
||||
regular_polygon(x, y, radius, edges, VARIATION_POINTY_TOP, ROTATION_0_DEGREES, color, DRAWING_FILLED);
|
||||
}
|
||||
|
||||
void Display::print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text, Color background) {
|
||||
int x_start, y_start;
|
||||
int width, height;
|
||||
this->get_text_bounds(x, y, text, font, align, &x_start, &y_start, &width, &height);
|
||||
font->print(x_start, y_start, this, color, text, background);
|
||||
}
|
||||
|
||||
void Display::vprintf_(int x, int y, BaseFont *font, Color color, Color background, TextAlign align, const char *format,
|
||||
va_list arg) {
|
||||
char buffer[256];
|
||||
int ret = vsnprintf(buffer, sizeof(buffer), format, arg);
|
||||
if (ret > 0)
|
||||
this->print(x, y, font, color, align, buffer, background);
|
||||
}
|
||||
|
||||
void Display::image(int x, int y, BaseImage *image, Color color_on, Color color_off) {
|
||||
this->image(x, y, image, ImageAlign::TOP_LEFT, color_on, color_off);
|
||||
}
|
||||
|
||||
void Display::image(int x, int y, BaseImage *image, ImageAlign align, Color color_on, Color color_off) {
|
||||
auto x_align = ImageAlign(int(align) & (int(ImageAlign::HORIZONTAL_ALIGNMENT)));
|
||||
auto y_align = ImageAlign(int(align) & (int(ImageAlign::VERTICAL_ALIGNMENT)));
|
||||
|
||||
switch (x_align) {
|
||||
case ImageAlign::RIGHT:
|
||||
x -= image->get_width();
|
||||
break;
|
||||
case ImageAlign::CENTER_HORIZONTAL:
|
||||
x -= image->get_width() / 2;
|
||||
break;
|
||||
case ImageAlign::LEFT:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (y_align) {
|
||||
case ImageAlign::BOTTOM:
|
||||
y -= image->get_height();
|
||||
break;
|
||||
case ImageAlign::CENTER_VERTICAL:
|
||||
y -= image->get_height() / 2;
|
||||
break;
|
||||
case ImageAlign::TOP:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
image->draw(x, y, this, color_on, color_off);
|
||||
}
|
||||
|
||||
#ifdef USE_GRAPH
|
||||
void Display::graph(int x, int y, graph::Graph *graph, Color color_on) { graph->draw(this, x, y, color_on); }
|
||||
void Display::legend(int x, int y, graph::Graph *graph, Color color_on) { graph->draw_legend(this, x, y, color_on); }
|
||||
#endif // USE_GRAPH
|
||||
|
||||
#ifdef USE_QR_CODE
|
||||
void Display::qr_code(int x, int y, qr_code::QrCode *qr_code, Color color_on, int scale) {
|
||||
qr_code->draw(this, x, y, color_on, scale);
|
||||
}
|
||||
#endif // USE_QR_CODE
|
||||
|
||||
#ifdef USE_GRAPHICAL_DISPLAY_MENU
|
||||
void Display::menu(int x, int y, graphical_display_menu::GraphicalDisplayMenu *menu, int width, int height) {
|
||||
Rect rect(x, y, width, height);
|
||||
menu->draw(this, &rect);
|
||||
}
|
||||
#endif // USE_GRAPHICAL_DISPLAY_MENU
|
||||
|
||||
void Display::get_text_bounds(int x, int y, const char *text, BaseFont *font, TextAlign align, int *x1, int *y1,
|
||||
int *width, int *height) {
|
||||
int x_offset, baseline;
|
||||
font->measure(text, width, &x_offset, &baseline, height);
|
||||
|
||||
auto x_align = TextAlign(int(align) & 0x18);
|
||||
auto y_align = TextAlign(int(align) & 0x07);
|
||||
|
||||
switch (x_align) {
|
||||
case TextAlign::RIGHT:
|
||||
*x1 = x - *width - x_offset;
|
||||
break;
|
||||
case TextAlign::CENTER_HORIZONTAL:
|
||||
*x1 = x - (*width + x_offset) / 2;
|
||||
break;
|
||||
case TextAlign::LEFT:
|
||||
default:
|
||||
// LEFT
|
||||
*x1 = x;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (y_align) {
|
||||
case TextAlign::BOTTOM:
|
||||
*y1 = y - *height;
|
||||
break;
|
||||
case TextAlign::BASELINE:
|
||||
*y1 = y - baseline;
|
||||
break;
|
||||
case TextAlign::CENTER_VERTICAL:
|
||||
*y1 = y - (*height) / 2;
|
||||
break;
|
||||
case TextAlign::TOP:
|
||||
default:
|
||||
*y1 = y;
|
||||
break;
|
||||
}
|
||||
}
|
||||
void Display::print(int x, int y, BaseFont *font, Color color, const char *text, Color background) {
|
||||
this->print(x, y, font, color, TextAlign::TOP_LEFT, text, background);
|
||||
}
|
||||
void Display::print(int x, int y, BaseFont *font, TextAlign align, const char *text) {
|
||||
this->print(x, y, font, COLOR_ON, align, text);
|
||||
}
|
||||
void Display::print(int x, int y, BaseFont *font, const char *text) {
|
||||
this->print(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, text);
|
||||
}
|
||||
void Display::printf(int x, int y, BaseFont *font, Color color, Color background, TextAlign align, const char *format,
|
||||
...) {
|
||||
va_list arg;
|
||||
va_start(arg, format);
|
||||
this->vprintf_(x, y, font, color, background, align, format, arg);
|
||||
va_end(arg);
|
||||
}
|
||||
void Display::printf(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ...) {
|
||||
va_list arg;
|
||||
va_start(arg, format);
|
||||
this->vprintf_(x, y, font, color, COLOR_OFF, align, format, arg);
|
||||
va_end(arg);
|
||||
}
|
||||
void Display::printf(int x, int y, BaseFont *font, Color color, const char *format, ...) {
|
||||
va_list arg;
|
||||
va_start(arg, format);
|
||||
this->vprintf_(x, y, font, color, COLOR_OFF, TextAlign::TOP_LEFT, format, arg);
|
||||
va_end(arg);
|
||||
}
|
||||
void Display::printf(int x, int y, BaseFont *font, TextAlign align, const char *format, ...) {
|
||||
va_list arg;
|
||||
va_start(arg, format);
|
||||
this->vprintf_(x, y, font, COLOR_ON, COLOR_OFF, align, format, arg);
|
||||
va_end(arg);
|
||||
}
|
||||
void Display::printf(int x, int y, BaseFont *font, const char *format, ...) {
|
||||
va_list arg;
|
||||
va_start(arg, format);
|
||||
this->vprintf_(x, y, font, COLOR_ON, COLOR_OFF, TextAlign::TOP_LEFT, format, arg);
|
||||
va_end(arg);
|
||||
}
|
||||
void Display::set_writer(display_writer_t &&writer) { this->writer_ = writer; }
|
||||
void Display::set_pages(std::vector<DisplayPage *> pages) {
|
||||
for (auto *page : pages)
|
||||
page->set_parent(this);
|
||||
|
||||
for (uint32_t i = 0; i < pages.size() - 1; i++) {
|
||||
pages[i]->set_next(pages[i + 1]);
|
||||
pages[i + 1]->set_prev(pages[i]);
|
||||
}
|
||||
pages[0]->set_prev(pages[pages.size() - 1]);
|
||||
pages[pages.size() - 1]->set_next(pages[0]);
|
||||
this->show_page(pages[0]);
|
||||
}
|
||||
void Display::show_page(DisplayPage *page) {
|
||||
this->previous_page_ = this->page_;
|
||||
this->page_ = page;
|
||||
if (this->previous_page_ != this->page_) {
|
||||
for (auto *t : on_page_change_triggers_)
|
||||
t->process(this->previous_page_, this->page_);
|
||||
}
|
||||
}
|
||||
void Display::show_next_page() { this->page_->show_next(); }
|
||||
void Display::show_prev_page() { this->page_->show_prev(); }
|
||||
void Display::do_update_() {
|
||||
if (this->auto_clear_enabled_) {
|
||||
this->clear();
|
||||
}
|
||||
if (this->show_test_card_) {
|
||||
this->test_card();
|
||||
} else if (this->page_ != nullptr) {
|
||||
this->page_->get_writer()(*this);
|
||||
} else if (this->writer_.has_value()) {
|
||||
(*this->writer_)(*this);
|
||||
}
|
||||
this->clear_clipping_();
|
||||
}
|
||||
void DisplayOnPageChangeTrigger::process(DisplayPage *from, DisplayPage *to) {
|
||||
if ((this->from_ == nullptr || this->from_ == from) && (this->to_ == nullptr || this->to_ == to))
|
||||
this->trigger(from, to);
|
||||
}
|
||||
void Display::strftime(int x, int y, BaseFont *font, Color color, Color background, TextAlign align, const char *format,
|
||||
ESPTime time) {
|
||||
char buffer[64];
|
||||
size_t ret = time.strftime(buffer, sizeof(buffer), format);
|
||||
if (ret > 0)
|
||||
this->print(x, y, font, color, align, buffer, background);
|
||||
}
|
||||
void Display::strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ESPTime time) {
|
||||
this->strftime(x, y, font, color, COLOR_OFF, align, format, time);
|
||||
}
|
||||
void Display::strftime(int x, int y, BaseFont *font, Color color, const char *format, ESPTime time) {
|
||||
this->strftime(x, y, font, color, COLOR_OFF, TextAlign::TOP_LEFT, format, time);
|
||||
}
|
||||
void Display::strftime(int x, int y, BaseFont *font, TextAlign align, const char *format, ESPTime time) {
|
||||
this->strftime(x, y, font, COLOR_ON, COLOR_OFF, align, format, time);
|
||||
}
|
||||
void Display::strftime(int x, int y, BaseFont *font, const char *format, ESPTime time) {
|
||||
this->strftime(x, y, font, COLOR_ON, COLOR_OFF, TextAlign::TOP_LEFT, format, time);
|
||||
}
|
||||
|
||||
void Display::start_clipping(Rect rect) {
|
||||
if (!this->clipping_rectangle_.empty()) {
|
||||
Rect r = this->clipping_rectangle_.back();
|
||||
rect.shrink(r);
|
||||
}
|
||||
this->clipping_rectangle_.push_back(rect);
|
||||
}
|
||||
void Display::end_clipping() {
|
||||
if (this->clipping_rectangle_.empty()) {
|
||||
ESP_LOGE(TAG, "clear: Clipping is not set.");
|
||||
} else {
|
||||
this->clipping_rectangle_.pop_back();
|
||||
}
|
||||
}
|
||||
void Display::extend_clipping(Rect add_rect) {
|
||||
if (this->clipping_rectangle_.empty()) {
|
||||
ESP_LOGE(TAG, "add: Clipping is not set.");
|
||||
} else {
|
||||
this->clipping_rectangle_.back().extend(add_rect);
|
||||
}
|
||||
}
|
||||
void Display::shrink_clipping(Rect add_rect) {
|
||||
if (this->clipping_rectangle_.empty()) {
|
||||
ESP_LOGE(TAG, "add: Clipping is not set.");
|
||||
} else {
|
||||
this->clipping_rectangle_.back().shrink(add_rect);
|
||||
}
|
||||
}
|
||||
Rect Display::get_clipping() const {
|
||||
if (this->clipping_rectangle_.empty()) {
|
||||
return Rect();
|
||||
} else {
|
||||
return this->clipping_rectangle_.back();
|
||||
}
|
||||
}
|
||||
void Display::clear_clipping_() { this->clipping_rectangle_.clear(); }
|
||||
bool Display::clip(int x, int y) {
|
||||
if (x < 0 || x >= this->get_width() || y < 0 || y >= this->get_height())
|
||||
return false;
|
||||
if (!this->get_clipping().inside(x, y))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
bool Display::clamp_x_(int x, int w, int &min_x, int &max_x) {
|
||||
min_x = std::max(x, 0);
|
||||
max_x = std::min(x + w, this->get_width());
|
||||
|
||||
if (!this->clipping_rectangle_.empty()) {
|
||||
const auto &rect = this->clipping_rectangle_.back();
|
||||
if (!rect.is_set())
|
||||
return false;
|
||||
|
||||
min_x = std::max(min_x, (int) rect.x);
|
||||
max_x = std::min(max_x, (int) rect.x2());
|
||||
}
|
||||
|
||||
return min_x < max_x;
|
||||
}
|
||||
bool Display::clamp_y_(int y, int h, int &min_y, int &max_y) {
|
||||
min_y = std::max(y, 0);
|
||||
max_y = std::min(y + h, this->get_height());
|
||||
|
||||
if (!this->clipping_rectangle_.empty()) {
|
||||
const auto &rect = this->clipping_rectangle_.back();
|
||||
if (!rect.is_set())
|
||||
return false;
|
||||
|
||||
min_y = std::max(min_y, (int) rect.y);
|
||||
max_y = std::min(max_y, (int) rect.y2());
|
||||
}
|
||||
|
||||
return min_y < max_y;
|
||||
}
|
||||
|
||||
const uint8_t TESTCARD_FONT[3][8] PROGMEM = {{0x41, 0x7F, 0x7F, 0x09, 0x19, 0x7F, 0x66, 0x00}, // 'R'
|
||||
{0x1C, 0x3E, 0x63, 0x41, 0x51, 0x73, 0x72, 0x00}, // 'G'
|
||||
{0x41, 0x7F, 0x7F, 0x49, 0x49, 0x7F, 0x36, 0x00}}; // 'B'
|
||||
|
||||
void Display::test_card() {
|
||||
int w = get_width(), h = get_height(), image_w, image_h;
|
||||
this->clear();
|
||||
this->show_test_card_ = false;
|
||||
if (this->get_display_type() == DISPLAY_TYPE_COLOR) {
|
||||
Color r(255, 0, 0), g(0, 255, 0), b(0, 0, 255);
|
||||
image_w = std::min(w - 20, 310);
|
||||
image_h = std::min(h - 20, 255);
|
||||
|
||||
int shift_x = (w - image_w) / 2;
|
||||
int shift_y = (h - image_h) / 2;
|
||||
int line_w = (image_w - 6) / 6;
|
||||
int image_c = image_w / 2;
|
||||
for (auto i = 0; i <= image_h; i++) {
|
||||
int c = esp_scale(i, image_h);
|
||||
this->horizontal_line(shift_x + 0, shift_y + i, line_w, r.fade_to_white(c));
|
||||
this->horizontal_line(shift_x + line_w, shift_y + i, line_w, r.fade_to_black(c)); //
|
||||
|
||||
this->horizontal_line(shift_x + image_c - line_w, shift_y + i, line_w, g.fade_to_white(c));
|
||||
this->horizontal_line(shift_x + image_c, shift_y + i, line_w, g.fade_to_black(c));
|
||||
|
||||
this->horizontal_line(shift_x + image_w - (line_w * 2), shift_y + i, line_w, b.fade_to_white(c));
|
||||
this->horizontal_line(shift_x + image_w - line_w, shift_y + i, line_w, b.fade_to_black(c));
|
||||
}
|
||||
this->rectangle(shift_x, shift_y, image_w, image_h, Color(127, 127, 0));
|
||||
|
||||
uint16_t shift_r = shift_x + line_w - (8 * 3);
|
||||
uint16_t shift_g = shift_x + image_c - (8 * 3);
|
||||
uint16_t shift_b = shift_x + image_w - line_w - (8 * 3);
|
||||
shift_y = h / 2 - (8 * 3);
|
||||
for (auto i = 0; i < 8; i++) {
|
||||
uint8_t ftr = progmem_read_byte(&TESTCARD_FONT[0][i]);
|
||||
uint8_t ftg = progmem_read_byte(&TESTCARD_FONT[1][i]);
|
||||
uint8_t ftb = progmem_read_byte(&TESTCARD_FONT[2][i]);
|
||||
for (auto k = 0; k < 8; k++) {
|
||||
if ((ftr & (1 << k)) != 0) {
|
||||
this->filled_rectangle(shift_r + (i * 6), shift_y + (k * 6), 6, 6, COLOR_OFF);
|
||||
}
|
||||
if ((ftg & (1 << k)) != 0) {
|
||||
this->filled_rectangle(shift_g + (i * 6), shift_y + (k * 6), 6, 6, COLOR_OFF);
|
||||
}
|
||||
if ((ftb & (1 << k)) != 0) {
|
||||
this->filled_rectangle(shift_b + (i * 6), shift_y + (k * 6), 6, 6, COLOR_OFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this->rectangle(0, 0, w, h, Color(127, 0, 127));
|
||||
this->filled_rectangle(0, 0, 10, 10, Color(255, 0, 255));
|
||||
this->stop_poller();
|
||||
}
|
||||
|
||||
DisplayPage::DisplayPage(display_writer_t writer) : writer_(std::move(writer)) {}
|
||||
void DisplayPage::show() { this->parent_->show_page(this); }
|
||||
void DisplayPage::show_next() {
|
||||
if (this->next_ == nullptr) {
|
||||
ESP_LOGE(TAG, "no next page");
|
||||
return;
|
||||
}
|
||||
this->next_->show();
|
||||
}
|
||||
void DisplayPage::show_prev() {
|
||||
if (this->prev_ == nullptr) {
|
||||
ESP_LOGE(TAG, "no previous page");
|
||||
return;
|
||||
}
|
||||
this->prev_->show();
|
||||
}
|
||||
void DisplayPage::set_parent(Display *parent) { this->parent_ = parent; }
|
||||
void DisplayPage::set_prev(DisplayPage *prev) { this->prev_ = prev; }
|
||||
void DisplayPage::set_next(DisplayPage *next) { this->next_ = next; }
|
||||
const display_writer_t &DisplayPage::get_writer() const { return this->writer_; }
|
||||
|
||||
const LogString *text_align_to_string(TextAlign textalign) {
|
||||
switch (textalign) {
|
||||
case TextAlign::TOP_LEFT:
|
||||
return LOG_STR("TOP_LEFT");
|
||||
case TextAlign::TOP_CENTER:
|
||||
return LOG_STR("TOP_CENTER");
|
||||
case TextAlign::TOP_RIGHT:
|
||||
return LOG_STR("TOP_RIGHT");
|
||||
case TextAlign::CENTER_LEFT:
|
||||
return LOG_STR("CENTER_LEFT");
|
||||
case TextAlign::CENTER:
|
||||
return LOG_STR("CENTER");
|
||||
case TextAlign::CENTER_RIGHT:
|
||||
return LOG_STR("CENTER_RIGHT");
|
||||
case TextAlign::BASELINE_LEFT:
|
||||
return LOG_STR("BASELINE_LEFT");
|
||||
case TextAlign::BASELINE_CENTER:
|
||||
return LOG_STR("BASELINE_CENTER");
|
||||
case TextAlign::BASELINE_RIGHT:
|
||||
return LOG_STR("BASELINE_RIGHT");
|
||||
case TextAlign::BOTTOM_LEFT:
|
||||
return LOG_STR("BOTTOM_LEFT");
|
||||
case TextAlign::BOTTOM_CENTER:
|
||||
return LOG_STR("BOTTOM_CENTER");
|
||||
case TextAlign::BOTTOM_RIGHT:
|
||||
return LOG_STR("BOTTOM_RIGHT");
|
||||
default:
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace display
|
||||
} // namespace esphome
|
||||
765
esphome/components/display/display.h
Normal file
765
esphome/components/display/display.h
Normal file
@@ -0,0 +1,765 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdarg>
|
||||
#include <vector>
|
||||
|
||||
#include "rect.h"
|
||||
|
||||
#include "esphome/core/color.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/time.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "display_color_utils.h"
|
||||
|
||||
#ifdef USE_GRAPH
|
||||
#include "esphome/components/graph/graph.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_QR_CODE
|
||||
#include "esphome/components/qr_code/qr_code.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_GRAPHICAL_DISPLAY_MENU
|
||||
#include "esphome/components/graphical_display_menu/graphical_display_menu.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace display {
|
||||
|
||||
/** TextAlign is used to tell the display class how to position a piece of text. By default
|
||||
* the coordinates you enter for the print*() functions take the upper left corner of the text
|
||||
* as the "anchor" point. You can customize this behavior to, for example, make the coordinates
|
||||
* refer to the *center* of the text.
|
||||
*
|
||||
* All text alignments consist of an X and Y-coordinate alignment. For the alignment along the X-axis
|
||||
* these options are allowed:
|
||||
*
|
||||
* - LEFT (x-coordinate of anchor point is on left)
|
||||
* - CENTER_HORIZONTAL (x-coordinate of anchor point is in the horizontal center of the text)
|
||||
* - RIGHT (x-coordinate of anchor point is on right)
|
||||
*
|
||||
* For the Y-Axis alignment these options are allowed:
|
||||
*
|
||||
* - TOP (y-coordinate of anchor is on the top of the text)
|
||||
* - CENTER_VERTICAL (y-coordinate of anchor is in the vertical center of the text)
|
||||
* - BASELINE (y-coordinate of anchor is on the baseline of the text)
|
||||
* - BOTTOM (y-coordinate of anchor is on the bottom of the text)
|
||||
*
|
||||
* These options are then combined to create combined TextAlignment options like:
|
||||
* - TOP_LEFT (default)
|
||||
* - CENTER (anchor point is in the middle of the text bounds)
|
||||
* - ...
|
||||
*/
|
||||
enum class TextAlign {
|
||||
TOP = 0x00,
|
||||
CENTER_VERTICAL = 0x01,
|
||||
BASELINE = 0x02,
|
||||
BOTTOM = 0x04,
|
||||
|
||||
LEFT = 0x00,
|
||||
CENTER_HORIZONTAL = 0x08,
|
||||
RIGHT = 0x10,
|
||||
|
||||
TOP_LEFT = TOP | LEFT,
|
||||
TOP_CENTER = TOP | CENTER_HORIZONTAL,
|
||||
TOP_RIGHT = TOP | RIGHT,
|
||||
|
||||
CENTER_LEFT = CENTER_VERTICAL | LEFT,
|
||||
CENTER = CENTER_VERTICAL | CENTER_HORIZONTAL,
|
||||
CENTER_RIGHT = CENTER_VERTICAL | RIGHT,
|
||||
|
||||
BASELINE_LEFT = BASELINE | LEFT,
|
||||
BASELINE_CENTER = BASELINE | CENTER_HORIZONTAL,
|
||||
BASELINE_RIGHT = BASELINE | RIGHT,
|
||||
|
||||
BOTTOM_LEFT = BOTTOM | LEFT,
|
||||
BOTTOM_CENTER = BOTTOM | CENTER_HORIZONTAL,
|
||||
BOTTOM_RIGHT = BOTTOM | RIGHT,
|
||||
};
|
||||
|
||||
/** ImageAlign is used to tell the display class how to position a image. By default
|
||||
* the coordinates you enter for the image() functions take the upper left corner of the image
|
||||
* as the "anchor" point. You can customize this behavior to, for example, make the coordinates
|
||||
* refer to the *center* of the image.
|
||||
*
|
||||
* All image alignments consist of an X and Y-coordinate alignment. For the alignment along the X-axis
|
||||
* these options are allowed:
|
||||
*
|
||||
* - LEFT (x-coordinate of anchor point is on left)
|
||||
* - CENTER_HORIZONTAL (x-coordinate of anchor point is in the horizontal center of the image)
|
||||
* - RIGHT (x-coordinate of anchor point is on right)
|
||||
*
|
||||
* For the Y-Axis alignment these options are allowed:
|
||||
*
|
||||
* - TOP (y-coordinate of anchor is on the top of the image)
|
||||
* - CENTER_VERTICAL (y-coordinate of anchor is in the vertical center of the image)
|
||||
* - BOTTOM (y-coordinate of anchor is on the bottom of the image)
|
||||
*
|
||||
* These options are then combined to create combined TextAlignment options like:
|
||||
* - TOP_LEFT (default)
|
||||
* - CENTER (anchor point is in the middle of the image bounds)
|
||||
* - ...
|
||||
*/
|
||||
enum class ImageAlign {
|
||||
TOP = 0x00,
|
||||
CENTER_VERTICAL = 0x01,
|
||||
BOTTOM = 0x02,
|
||||
|
||||
LEFT = 0x00,
|
||||
CENTER_HORIZONTAL = 0x04,
|
||||
RIGHT = 0x08,
|
||||
|
||||
TOP_LEFT = TOP | LEFT,
|
||||
TOP_CENTER = TOP | CENTER_HORIZONTAL,
|
||||
TOP_RIGHT = TOP | RIGHT,
|
||||
|
||||
CENTER_LEFT = CENTER_VERTICAL | LEFT,
|
||||
CENTER = CENTER_VERTICAL | CENTER_HORIZONTAL,
|
||||
CENTER_RIGHT = CENTER_VERTICAL | RIGHT,
|
||||
|
||||
BOTTOM_LEFT = BOTTOM | LEFT,
|
||||
BOTTOM_CENTER = BOTTOM | CENTER_HORIZONTAL,
|
||||
BOTTOM_RIGHT = BOTTOM | RIGHT,
|
||||
|
||||
HORIZONTAL_ALIGNMENT = LEFT | CENTER_HORIZONTAL | RIGHT,
|
||||
VERTICAL_ALIGNMENT = TOP | CENTER_VERTICAL | BOTTOM
|
||||
};
|
||||
|
||||
enum DisplayType {
|
||||
DISPLAY_TYPE_BINARY = 1,
|
||||
DISPLAY_TYPE_GRAYSCALE = 2,
|
||||
DISPLAY_TYPE_COLOR = 3,
|
||||
};
|
||||
|
||||
enum DisplayRotation {
|
||||
DISPLAY_ROTATION_0_DEGREES = 0,
|
||||
DISPLAY_ROTATION_90_DEGREES = 90,
|
||||
DISPLAY_ROTATION_180_DEGREES = 180,
|
||||
DISPLAY_ROTATION_270_DEGREES = 270,
|
||||
};
|
||||
|
||||
const int EDGES_TRIGON = 3;
|
||||
const int EDGES_TRIANGLE = 3;
|
||||
const int EDGES_TETRAGON = 4;
|
||||
const int EDGES_QUADRILATERAL = 4;
|
||||
const int EDGES_PENTAGON = 5;
|
||||
const int EDGES_HEXAGON = 6;
|
||||
const int EDGES_HEPTAGON = 7;
|
||||
const int EDGES_OCTAGON = 8;
|
||||
const int EDGES_NONAGON = 9;
|
||||
const int EDGES_ENNEAGON = 9;
|
||||
const int EDGES_DECAGON = 10;
|
||||
const int EDGES_HENDECAGON = 11;
|
||||
const int EDGES_DODECAGON = 12;
|
||||
const int EDGES_TRIDECAGON = 13;
|
||||
const int EDGES_TETRADECAGON = 14;
|
||||
const int EDGES_PENTADECAGON = 15;
|
||||
const int EDGES_HEXADECAGON = 16;
|
||||
|
||||
const float ROTATION_0_DEGREES = 0.0;
|
||||
const float ROTATION_45_DEGREES = 45.0;
|
||||
const float ROTATION_90_DEGREES = 90.0;
|
||||
const float ROTATION_180_DEGREES = 180.0;
|
||||
const float ROTATION_270_DEGREES = 270.0;
|
||||
|
||||
enum RegularPolygonVariation {
|
||||
VARIATION_POINTY_TOP = 0,
|
||||
VARIATION_FLAT_TOP = 1,
|
||||
};
|
||||
|
||||
enum RegularPolygonDrawing {
|
||||
DRAWING_OUTLINE = 0,
|
||||
DRAWING_FILLED = 1,
|
||||
};
|
||||
|
||||
class Display;
|
||||
class DisplayPage;
|
||||
class DisplayOnPageChangeTrigger;
|
||||
|
||||
using display_writer_t = std::function<void(Display &)>;
|
||||
|
||||
#define LOG_DISPLAY(prefix, type, obj) \
|
||||
if ((obj) != nullptr) { \
|
||||
ESP_LOGCONFIG(TAG, \
|
||||
prefix type "\n" \
|
||||
"%s Rotations: %d °\n" \
|
||||
"%s Dimensions: %dpx x %dpx", \
|
||||
prefix, (obj)->rotation_, prefix, (obj)->get_width(), (obj)->get_height()); \
|
||||
}
|
||||
|
||||
/// Turn the pixel OFF.
|
||||
extern const Color COLOR_OFF;
|
||||
/// Turn the pixel ON.
|
||||
extern const Color COLOR_ON;
|
||||
|
||||
class BaseImage {
|
||||
public:
|
||||
virtual void draw(int x, int y, Display *display, Color color_on, Color color_off) = 0;
|
||||
virtual int get_width() const = 0;
|
||||
virtual int get_height() const = 0;
|
||||
};
|
||||
|
||||
class BaseFont {
|
||||
public:
|
||||
virtual void print(int x, int y, Display *display, Color color, const char *text, Color background) = 0;
|
||||
virtual void measure(const char *str, int *width, int *x_offset, int *baseline, int *height) = 0;
|
||||
};
|
||||
|
||||
class Display : public PollingComponent {
|
||||
public:
|
||||
/// Fill the entire screen with the given color.
|
||||
virtual void fill(Color color);
|
||||
/// Clear the entire screen by filling it with OFF pixels.
|
||||
void clear();
|
||||
|
||||
/// Get the calculated width of the display in pixels with rotation applied.
|
||||
virtual int get_width() { return this->get_width_internal(); }
|
||||
/// Get the calculated height of the display in pixels with rotation applied.
|
||||
virtual int get_height() { return this->get_height_internal(); }
|
||||
|
||||
/// Get the native (original) width of the display in pixels.
|
||||
int get_native_width() { return this->get_width_internal(); }
|
||||
/// Get the native (original) height of the display in pixels.
|
||||
int get_native_height() { return this->get_height_internal(); }
|
||||
|
||||
/// Set a single pixel at the specified coordinates to default color.
|
||||
inline void draw_pixel_at(int x, int y) { this->draw_pixel_at(x, y, COLOR_ON); }
|
||||
|
||||
/// Set a single pixel at the specified coordinates to the given color.
|
||||
virtual void draw_pixel_at(int x, int y, Color color) = 0;
|
||||
|
||||
/** Given an array of pixels encoded in the nominated format, draw these into the display's buffer.
|
||||
* The naive implementation here will work in all cases, but can be overridden by sub-classes
|
||||
* in order to optimise the procedure.
|
||||
* The parameters describe a rectangular block of pixels, potentially within a larger buffer.
|
||||
*
|
||||
* \param x_start The starting destination x position
|
||||
* \param y_start The starting destination y position
|
||||
* \param w the width of the pixel block
|
||||
* \param h the height of the pixel block
|
||||
* \param ptr A pointer to the start of the data to be copied
|
||||
* \param order The ordering of the colors
|
||||
* \param bitness Defines the number of bits and their format for each pixel
|
||||
* \param big_endian True if 16 bit values are stored big-endian
|
||||
* \param x_offset The initial x-offset into the source buffer.
|
||||
* \param y_offset The initial y-offset into the source buffer.
|
||||
* \param x_pad How many pixels are in each line after the end of the pixels to be copied.
|
||||
*
|
||||
* The length of each source buffer line (stride) will be x_offset + w + x_pad.
|
||||
*/
|
||||
virtual void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order,
|
||||
ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad);
|
||||
|
||||
/// Convenience overload for base case where the pixels are packed into the buffer with no gaps (e.g. suits LVGL.)
|
||||
void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorOrder order,
|
||||
ColorBitness bitness, bool big_endian) {
|
||||
this->draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, 0, 0, 0);
|
||||
}
|
||||
|
||||
/// Draw a straight line from the point [x1,y1] to [x2,y2] with the given color.
|
||||
void line(int x1, int y1, int x2, int y2, Color color = COLOR_ON);
|
||||
|
||||
/// Draw a straight line at the given angle based on the origin [x, y] for a specified length with the given color.
|
||||
void line_at_angle(int x, int y, int angle, int length, Color color = COLOR_ON);
|
||||
|
||||
/// Draw a straight line at the given angle based on the origin [x, y] from a specified start and stop radius with the
|
||||
/// given color.
|
||||
void line_at_angle(int x, int y, int angle, int start_radius, int stop_radius, Color color = COLOR_ON);
|
||||
|
||||
/// Draw a horizontal line from the point [x,y] to [x+width,y] with the given color.
|
||||
void horizontal_line(int x, int y, int width, Color color = COLOR_ON);
|
||||
|
||||
/// Draw a vertical line from the point [x,y] to [x,y+width] with the given color.
|
||||
void vertical_line(int x, int y, int height, Color color = COLOR_ON);
|
||||
|
||||
/// Draw the outline of a rectangle with the top left point at [x1,y1] and the bottom right point at
|
||||
/// [x1+width,y1+height].
|
||||
void rectangle(int x1, int y1, int width, int height, Color color = COLOR_ON);
|
||||
|
||||
/// Fill a rectangle with the top left point at [x1,y1] and the bottom right point at [x1+width,y1+height].
|
||||
void filled_rectangle(int x1, int y1, int width, int height, Color color = COLOR_ON);
|
||||
|
||||
/// Draw the outline of a circle centered around [center_x,center_y] with the radius radius with the given color.
|
||||
void circle(int center_x, int center_xy, int radius, Color color = COLOR_ON);
|
||||
|
||||
/// Fill a circle centered around [center_x,center_y] with the radius radius with the given color.
|
||||
void filled_circle(int center_x, int center_y, int radius, Color color = COLOR_ON);
|
||||
|
||||
/// Fill a ring centered around [center_x,center_y] between two circles with the radius1 and radius2 with the given
|
||||
/// color.
|
||||
void filled_ring(int center_x, int center_y, int radius1, int radius2, Color color = COLOR_ON);
|
||||
/// Fill a half-ring "gauge" centered around [center_x,center_y] between two circles with the radius1 and radius2
|
||||
/// with he given color and filled up to 'progress' percent
|
||||
void filled_gauge(int center_x, int center_y, int radius1, int radius2, int progress, Color color = COLOR_ON);
|
||||
|
||||
/// Draw the outline of a triangle contained between the points [x1,y1], [x2,y2] and [x3,y3] with the given color.
|
||||
void triangle(int x1, int y1, int x2, int y2, int x3, int y3, Color color = COLOR_ON);
|
||||
|
||||
/// Fill a triangle contained between the points [x1,y1], [x2,y2] and [x3,y3] with the given color.
|
||||
void filled_triangle(int x1, int y1, int x2, int y2, int x3, int y3, Color color = COLOR_ON);
|
||||
|
||||
/// Get the specified vertex (x,y) coordinates for the regular polygon inscribed in the circle centered on
|
||||
/// [center_x,center_y] with the given radius. Vertex id are 0-indexed and rotate clockwise. In a pointy-topped
|
||||
/// variation of a polygon with a 0° rotation, the vertex #0 is located at the top of the polygon. In a flat-topped
|
||||
/// variation of a polygon with a 0° rotation, the vertex #0 is located on the left-side of the horizontal top
|
||||
/// edge, and the vertex #1 is located on the right-side of the horizontal top edge.
|
||||
/// Use the edges constants (e.g.: EDGES_HEXAGON) or any integer to specify the number of edges of the polygon.
|
||||
/// Use the variation to switch between the flat-topped or the pointy-topped variation of the polygon.
|
||||
/// Use the rotation in degrees to rotate the shape clockwise.
|
||||
void get_regular_polygon_vertex(int vertex_id, int *vertex_x, int *vertex_y, int center_x, int center_y, int radius,
|
||||
int edges, RegularPolygonVariation variation = VARIATION_POINTY_TOP,
|
||||
float rotation_degrees = ROTATION_0_DEGREES);
|
||||
|
||||
/// Draw the outline of a regular polygon inscribed in the circle centered on [x,y] with the given
|
||||
/// radius and color.
|
||||
/// Use the edges constants (e.g.: EDGES_HEXAGON) or any integer to specify the number of edges of the polygon.
|
||||
/// Use the variation to switch between the flat-topped or the pointy-topped variation of the polygon.
|
||||
/// Use the rotation in degrees to rotate the shape clockwise.
|
||||
/// Use the drawing to switch between outlining or filling the polygon.
|
||||
void regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation = VARIATION_POINTY_TOP,
|
||||
float rotation_degrees = ROTATION_0_DEGREES, Color color = COLOR_ON,
|
||||
RegularPolygonDrawing drawing = DRAWING_OUTLINE);
|
||||
void regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation, Color color,
|
||||
RegularPolygonDrawing drawing = DRAWING_OUTLINE);
|
||||
void regular_polygon(int x, int y, int radius, int edges, Color color,
|
||||
RegularPolygonDrawing drawing = DRAWING_OUTLINE);
|
||||
|
||||
/// Fill a regular polygon inscribed in the circle centered on [x,y] with the given radius and color.
|
||||
/// Use the edges constants (e.g.: EDGES_HEXAGON) or any integer to specify the number of edges of the polygon.
|
||||
/// Use the variation to switch between the flat-topped or the pointy-topped variation of the polygon.
|
||||
/// Use the rotation in degrees to rotate the shape clockwise.
|
||||
void filled_regular_polygon(int x, int y, int radius, int edges,
|
||||
RegularPolygonVariation variation = VARIATION_POINTY_TOP,
|
||||
float rotation_degrees = ROTATION_0_DEGREES, Color color = COLOR_ON);
|
||||
void filled_regular_polygon(int x, int y, int radius, int edges, RegularPolygonVariation variation, Color color);
|
||||
void filled_regular_polygon(int x, int y, int radius, int edges, Color color);
|
||||
|
||||
/** Print `text` with the anchor point at [x,y] with `font`.
|
||||
*
|
||||
* @param x The x coordinate of the text alignment anchor point.
|
||||
* @param y The y coordinate of the text alignment anchor point.
|
||||
* @param font The font to draw the text with.
|
||||
* @param color The color to draw the text with.
|
||||
* @param align The alignment of the text.
|
||||
* @param text The text to draw.
|
||||
* @param background When using multi-bit (anti-aliased) fonts, blend this background color into pixels
|
||||
*/
|
||||
void print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text,
|
||||
Color background = COLOR_OFF);
|
||||
|
||||
/** Print `text` with the top left at [x,y] with `font`.
|
||||
*
|
||||
* @param x The x coordinate of the upper left corner.
|
||||
* @param y The y coordinate of the upper left corner.
|
||||
* @param font The font to draw the text with.
|
||||
* @param color The color to draw the text with.
|
||||
* @param text The text to draw.
|
||||
* @param background When using multi-bit (anti-aliased) fonts, blend this background color into pixels
|
||||
*/
|
||||
void print(int x, int y, BaseFont *font, Color color, const char *text, Color background = COLOR_OFF);
|
||||
|
||||
/** Print `text` with the anchor point at [x,y] with `font`.
|
||||
*
|
||||
* @param x The x coordinate of the text alignment anchor point.
|
||||
* @param y The y coordinate of the text alignment anchor point.
|
||||
* @param font The font to draw the text with.
|
||||
* @param align The alignment of the text.
|
||||
* @param text The text to draw.
|
||||
*/
|
||||
void print(int x, int y, BaseFont *font, TextAlign align, const char *text);
|
||||
|
||||
/** Print `text` with the top left at [x,y] with `font`.
|
||||
*
|
||||
* @param x The x coordinate of the upper left corner.
|
||||
* @param y The y coordinate of the upper left corner.
|
||||
* @param font The font to draw the text with.
|
||||
* @param text The text to draw.
|
||||
*/
|
||||
void print(int x, int y, BaseFont *font, const char *text);
|
||||
|
||||
/** Evaluate the printf-format `format` and print the result with the anchor point at [x,y] with `font`.
|
||||
*
|
||||
* @param x The x coordinate of the text alignment anchor point.
|
||||
* @param y The y coordinate of the text alignment anchor point.
|
||||
* @param font The font to draw the text with.
|
||||
* @param color The color to draw the text with.
|
||||
* @param background The background color to use for anti-aliasing
|
||||
* @param align The alignment of the text.
|
||||
* @param format The format to use.
|
||||
* @param ... The arguments to use for the text formatting.
|
||||
*/
|
||||
void printf(int x, int y, BaseFont *font, Color color, Color background, TextAlign align, const char *format, ...)
|
||||
__attribute__((format(printf, 8, 9)));
|
||||
|
||||
/** Evaluate the printf-format `format` and print the result with the anchor point at [x,y] with `font`.
|
||||
*
|
||||
* @param x The x coordinate of the text alignment anchor point.
|
||||
* @param y The y coordinate of the text alignment anchor point.
|
||||
* @param font The font to draw the text with.
|
||||
* @param color The color to draw the text with.
|
||||
* @param align The alignment of the text.
|
||||
* @param format The format to use.
|
||||
* @param ... The arguments to use for the text formatting.
|
||||
*/
|
||||
void printf(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ...)
|
||||
__attribute__((format(printf, 7, 8)));
|
||||
|
||||
/** Evaluate the printf-format `format` and print the result with the top left at [x,y] with `font`.
|
||||
*
|
||||
* @param x The x coordinate of the upper left corner.
|
||||
* @param y The y coordinate of the upper left corner.
|
||||
* @param font The font to draw the text with.
|
||||
* @param color The color to draw the text with.
|
||||
* @param format The format to use.
|
||||
* @param ... The arguments to use for the text formatting.
|
||||
*/
|
||||
void printf(int x, int y, BaseFont *font, Color color, const char *format, ...) __attribute__((format(printf, 6, 7)));
|
||||
|
||||
/** Evaluate the printf-format `format` and print the result with the anchor point at [x,y] with `font`.
|
||||
*
|
||||
* @param x The x coordinate of the text alignment anchor point.
|
||||
* @param y The y coordinate of the text alignment anchor point.
|
||||
* @param font The font to draw the text with.
|
||||
* @param align The alignment of the text.
|
||||
* @param format The format to use.
|
||||
* @param ... The arguments to use for the text formatting.
|
||||
*/
|
||||
void printf(int x, int y, BaseFont *font, TextAlign align, const char *format, ...)
|
||||
__attribute__((format(printf, 6, 7)));
|
||||
|
||||
/** Evaluate the printf-format `format` and print the result with the top left at [x,y] with `font`.
|
||||
*
|
||||
* @param x The x coordinate of the upper left corner.
|
||||
* @param y The y coordinate of the upper left corner.
|
||||
* @param font The font to draw the text with.
|
||||
* @param format The format to use.
|
||||
* @param ... The arguments to use for the text formatting.
|
||||
*/
|
||||
void printf(int x, int y, BaseFont *font, const char *format, ...) __attribute__((format(printf, 5, 6)));
|
||||
|
||||
/** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`.
|
||||
*
|
||||
* @param x The x coordinate of the text alignment anchor point.
|
||||
* @param y The y coordinate of the text alignment anchor point.
|
||||
* @param font The font to draw the text with.
|
||||
* @param color The color to draw the text with.
|
||||
* @param background The background color to draw the text with.
|
||||
* @param align The alignment of the text.
|
||||
* @param format The format to use.
|
||||
* @param ... The arguments to use for the text formatting.
|
||||
*/
|
||||
void strftime(int x, int y, BaseFont *font, Color color, Color background, TextAlign align, const char *format,
|
||||
ESPTime time) __attribute__((format(strftime, 8, 0)));
|
||||
|
||||
/** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`.
|
||||
*
|
||||
* @param x The x coordinate of the text alignment anchor point.
|
||||
* @param y The y coordinate of the text alignment anchor point.
|
||||
* @param font The font to draw the text with.
|
||||
* @param color The color to draw the text with.
|
||||
* @param align The alignment of the text.
|
||||
* @param format The strftime format to use.
|
||||
* @param time The time to format.
|
||||
*/
|
||||
void strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ESPTime time)
|
||||
__attribute__((format(strftime, 7, 0)));
|
||||
|
||||
/** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`.
|
||||
*
|
||||
* @param x The x coordinate of the upper left corner.
|
||||
* @param y The y coordinate of the upper left corner.
|
||||
* @param font The font to draw the text with.
|
||||
* @param color The color to draw the text with.
|
||||
* @param format The strftime format to use.
|
||||
* @param time The time to format.
|
||||
*/
|
||||
void strftime(int x, int y, BaseFont *font, Color color, const char *format, ESPTime time)
|
||||
__attribute__((format(strftime, 6, 0)));
|
||||
|
||||
/** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`.
|
||||
*
|
||||
* @param x The x coordinate of the text alignment anchor point.
|
||||
* @param y The y coordinate of the text alignment anchor point.
|
||||
* @param font The font to draw the text with.
|
||||
* @param align The alignment of the text.
|
||||
* @param format The strftime format to use.
|
||||
* @param time The time to format.
|
||||
*/
|
||||
void strftime(int x, int y, BaseFont *font, TextAlign align, const char *format, ESPTime time)
|
||||
__attribute__((format(strftime, 6, 0)));
|
||||
|
||||
/** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`.
|
||||
*
|
||||
* @param x The x coordinate of the upper left corner.
|
||||
* @param y The y coordinate of the upper left corner.
|
||||
* @param font The font to draw the text with.
|
||||
* @param format The strftime format to use.
|
||||
* @param time The time to format.
|
||||
*/
|
||||
void strftime(int x, int y, BaseFont *font, const char *format, ESPTime time) __attribute__((format(strftime, 5, 0)));
|
||||
|
||||
/** 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, BaseImage *image, Color color_on = COLOR_ON, Color color_off = COLOR_OFF);
|
||||
|
||||
/** Draw the `image` 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 align The alignment of the image.
|
||||
* @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, BaseImage *image, ImageAlign align, Color color_on = COLOR_ON, Color color_off = COLOR_OFF);
|
||||
|
||||
#ifdef USE_GRAPH
|
||||
/** Draw the `graph` 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 graph The graph id to draw
|
||||
* @param color_on The color to replace in binary images for the on bits.
|
||||
*/
|
||||
void graph(int x, int y, graph::Graph *graph, Color color_on = COLOR_ON);
|
||||
|
||||
/** Draw the `legend` for graph 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 graph The graph id for which the legend applies to
|
||||
* @param graph The graph id for which the legend applies to
|
||||
* @param graph The graph id for which the legend applies to
|
||||
* @param name_font The font used for the trace name
|
||||
* @param value_font The font used for the trace value and units
|
||||
* @param color_on The color of the border
|
||||
*/
|
||||
void legend(int x, int y, graph::Graph *graph, Color color_on = COLOR_ON);
|
||||
#endif // USE_GRAPH
|
||||
|
||||
#ifdef USE_QR_CODE
|
||||
/** Draw the `qr_code` 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 qr_code The qr_code to draw
|
||||
* @param color_on The color to replace in binary images for the on bits.
|
||||
*/
|
||||
void qr_code(int x, int y, qr_code::QrCode *qr_code, Color color_on = COLOR_ON, int scale = 1);
|
||||
#endif
|
||||
|
||||
#ifdef USE_GRAPHICAL_DISPLAY_MENU
|
||||
/**
|
||||
* @param x The x coordinate of the upper left corner
|
||||
* @param y The y coordinate of the upper left corner
|
||||
* @param menu The GraphicalDisplayMenu to draw
|
||||
* @param width Width of the menu
|
||||
* @param height Height of the menu
|
||||
*/
|
||||
void menu(int x, int y, graphical_display_menu::GraphicalDisplayMenu *menu, int width, int height);
|
||||
#endif // USE_GRAPHICAL_DISPLAY_MENU
|
||||
|
||||
/** Get the text bounds of the given string.
|
||||
*
|
||||
* @param x The x coordinate to place the string at, can be 0 if only interested in dimensions.
|
||||
* @param y The y coordinate to place the string at, can be 0 if only interested in dimensions.
|
||||
* @param text The text to measure.
|
||||
* @param font The font to measure the text bounds with.
|
||||
* @param align The alignment of the text. Set to TextAlign::TOP_LEFT if only interested in dimensions.
|
||||
* @param x1 A pointer to store the returned x coordinate of the upper left corner in.
|
||||
* @param y1 A pointer to store the returned y coordinate of the upper left corner in.
|
||||
* @param width A pointer to store the returned text width in.
|
||||
* @param height A pointer to store the returned text height in.
|
||||
*/
|
||||
void get_text_bounds(int x, int y, const char *text, BaseFont *font, TextAlign align, int *x1, int *y1, int *width,
|
||||
int *height);
|
||||
|
||||
/// Internal method to set the display writer lambda.
|
||||
void set_writer(display_writer_t &&writer);
|
||||
|
||||
void show_page(DisplayPage *page);
|
||||
void show_next_page();
|
||||
void show_prev_page();
|
||||
|
||||
void set_pages(std::vector<DisplayPage *> pages);
|
||||
|
||||
const DisplayPage *get_active_page() const { return this->page_; }
|
||||
|
||||
void add_on_page_change_trigger(DisplayOnPageChangeTrigger *t) { this->on_page_change_triggers_.push_back(t); }
|
||||
|
||||
/// Internal method to set the display rotation with.
|
||||
void set_rotation(DisplayRotation rotation);
|
||||
|
||||
// Internal method to set display auto clearing.
|
||||
void set_auto_clear(bool auto_clear_enabled) { this->auto_clear_enabled_ = auto_clear_enabled; }
|
||||
|
||||
DisplayRotation get_rotation() const { return this->rotation_; }
|
||||
|
||||
/** Get the type of display that the buffer corresponds to. In case of dynamically configurable displays,
|
||||
* returns the type the display is currently configured to.
|
||||
*/
|
||||
virtual DisplayType get_display_type() = 0;
|
||||
|
||||
/** Set the clipping rectangle for further drawing
|
||||
*
|
||||
* @param[in] rect: Pointer to Rect for clipping (or NULL for entire screen)
|
||||
*
|
||||
* return true if success, false if error
|
||||
*/
|
||||
void start_clipping(Rect rect);
|
||||
void start_clipping(int16_t left, int16_t top, int16_t right, int16_t bottom) {
|
||||
start_clipping(Rect(left, top, right - left, bottom - top));
|
||||
};
|
||||
|
||||
/** Add a rectangular region to the invalidation region
|
||||
* - This is usually called when an element has been modified
|
||||
*
|
||||
* @param[in] rect: Rectangle to add to the invalidation region
|
||||
*/
|
||||
void extend_clipping(Rect rect);
|
||||
void extend_clipping(int16_t left, int16_t top, int16_t right, int16_t bottom) {
|
||||
this->extend_clipping(Rect(left, top, right - left, bottom - top));
|
||||
};
|
||||
|
||||
/** substract a rectangular region to the invalidation region
|
||||
* - This is usually called when an element has been modified
|
||||
*
|
||||
* @param[in] rect: Rectangle to add to the invalidation region
|
||||
*/
|
||||
void shrink_clipping(Rect rect);
|
||||
void shrink_clipping(uint16_t left, uint16_t top, uint16_t right, uint16_t bottom) {
|
||||
this->shrink_clipping(Rect(left, top, right - left, bottom - top));
|
||||
};
|
||||
|
||||
/** Reset the invalidation region
|
||||
*/
|
||||
void end_clipping();
|
||||
|
||||
/** Get the current the clipping rectangle
|
||||
*
|
||||
* return rect for active clipping region
|
||||
*/
|
||||
Rect get_clipping() const;
|
||||
|
||||
bool is_clipping() const { return !this->clipping_rectangle_.empty(); }
|
||||
|
||||
/** Check if pixel is within region of display.
|
||||
*/
|
||||
bool clip(int x, int y);
|
||||
|
||||
void test_card();
|
||||
void show_test_card() { this->show_test_card_ = true; }
|
||||
|
||||
protected:
|
||||
bool clamp_x_(int x, int w, int &min_x, int &max_x);
|
||||
bool clamp_y_(int y, int h, int &min_y, int &max_y);
|
||||
void vprintf_(int x, int y, BaseFont *font, Color color, Color background, TextAlign align, const char *format,
|
||||
va_list arg);
|
||||
|
||||
void do_update_();
|
||||
void clear_clipping_();
|
||||
|
||||
virtual int get_height_internal() = 0;
|
||||
virtual int get_width_internal() = 0;
|
||||
|
||||
/**
|
||||
* This method fills a triangle using only integer variables by using a
|
||||
* modified bresenham algorithm.
|
||||
* It is mandatory that [x2,y2] and [x3,y3] lie on the same horizontal line,
|
||||
* so y2 must be equal to y3.
|
||||
*/
|
||||
void filled_flat_side_triangle_(int x1, int y1, int x2, int y2, int x3, int y3, Color color);
|
||||
void sort_triangle_points_by_y_(int *x1, int *y1, int *x2, int *y2, int *x3, int *y3);
|
||||
|
||||
DisplayRotation rotation_{DISPLAY_ROTATION_0_DEGREES};
|
||||
optional<display_writer_t> writer_{};
|
||||
DisplayPage *page_{nullptr};
|
||||
DisplayPage *previous_page_{nullptr};
|
||||
std::vector<DisplayOnPageChangeTrigger *> on_page_change_triggers_;
|
||||
bool auto_clear_enabled_{true};
|
||||
std::vector<Rect> clipping_rectangle_;
|
||||
bool show_test_card_{false};
|
||||
};
|
||||
|
||||
class DisplayPage {
|
||||
public:
|
||||
DisplayPage(display_writer_t writer);
|
||||
void show();
|
||||
void show_next();
|
||||
void show_prev();
|
||||
void set_parent(Display *parent);
|
||||
void set_prev(DisplayPage *prev);
|
||||
void set_next(DisplayPage *next);
|
||||
const display_writer_t &get_writer() const;
|
||||
|
||||
protected:
|
||||
Display *parent_;
|
||||
display_writer_t writer_;
|
||||
DisplayPage *prev_{nullptr};
|
||||
DisplayPage *next_{nullptr};
|
||||
};
|
||||
|
||||
template<typename... Ts> class DisplayPageShowAction : public Action<Ts...> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(DisplayPage *, page)
|
||||
|
||||
void play(Ts... x) override {
|
||||
auto *page = this->page_.value(x...);
|
||||
if (page != nullptr) {
|
||||
page->show();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class DisplayPageShowNextAction : public Action<Ts...> {
|
||||
public:
|
||||
DisplayPageShowNextAction(Display *buffer) : buffer_(buffer) {}
|
||||
|
||||
void play(Ts... x) override { this->buffer_->show_next_page(); }
|
||||
|
||||
Display *buffer_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class DisplayPageShowPrevAction : public Action<Ts...> {
|
||||
public:
|
||||
DisplayPageShowPrevAction(Display *buffer) : buffer_(buffer) {}
|
||||
|
||||
void play(Ts... x) override { this->buffer_->show_prev_page(); }
|
||||
|
||||
Display *buffer_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class DisplayIsDisplayingPageCondition : public Condition<Ts...> {
|
||||
public:
|
||||
DisplayIsDisplayingPageCondition(Display *parent) : parent_(parent) {}
|
||||
|
||||
void set_page(DisplayPage *page) { this->page_ = page; }
|
||||
bool check(Ts... x) override { return this->parent_->get_active_page() == this->page_; }
|
||||
|
||||
protected:
|
||||
Display *parent_;
|
||||
DisplayPage *page_;
|
||||
};
|
||||
|
||||
class DisplayOnPageChangeTrigger : public Trigger<DisplayPage *, DisplayPage *> {
|
||||
public:
|
||||
explicit DisplayOnPageChangeTrigger(Display *parent) { parent->add_on_page_change_trigger(this); }
|
||||
void process(DisplayPage *from, DisplayPage *to);
|
||||
void set_from(DisplayPage *p) { this->from_ = p; }
|
||||
void set_to(DisplayPage *p) { this->to_ = p; }
|
||||
|
||||
protected:
|
||||
DisplayPage *from_{nullptr};
|
||||
DisplayPage *to_{nullptr};
|
||||
};
|
||||
|
||||
const LogString *text_align_to_string(TextAlign textalign);
|
||||
|
||||
} // namespace display
|
||||
} // namespace esphome
|
||||
72
esphome/components/display/display_buffer.cpp
Normal file
72
esphome/components/display/display_buffer.cpp
Normal file
@@ -0,0 +1,72 @@
|
||||
#include "display_buffer.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace display {
|
||||
|
||||
static const char *const TAG = "display";
|
||||
|
||||
void DisplayBuffer::init_internal_(uint32_t buffer_length) {
|
||||
RAMAllocator<uint8_t> allocator;
|
||||
this->buffer_ = allocator.allocate(buffer_length);
|
||||
if (this->buffer_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Could not allocate buffer for display!");
|
||||
return;
|
||||
}
|
||||
this->clear();
|
||||
}
|
||||
|
||||
int DisplayBuffer::get_width() {
|
||||
switch (this->rotation_) {
|
||||
case DISPLAY_ROTATION_90_DEGREES:
|
||||
case DISPLAY_ROTATION_270_DEGREES:
|
||||
return this->get_height_internal();
|
||||
case DISPLAY_ROTATION_0_DEGREES:
|
||||
case DISPLAY_ROTATION_180_DEGREES:
|
||||
default:
|
||||
return this->get_width_internal();
|
||||
}
|
||||
}
|
||||
|
||||
int DisplayBuffer::get_height() {
|
||||
switch (this->rotation_) {
|
||||
case DISPLAY_ROTATION_0_DEGREES:
|
||||
case DISPLAY_ROTATION_180_DEGREES:
|
||||
return this->get_height_internal();
|
||||
case DISPLAY_ROTATION_90_DEGREES:
|
||||
case DISPLAY_ROTATION_270_DEGREES:
|
||||
default:
|
||||
return this->get_width_internal();
|
||||
}
|
||||
}
|
||||
|
||||
void HOT DisplayBuffer::draw_pixel_at(int x, int y, Color color) {
|
||||
if (!this->get_clipping().inside(x, y))
|
||||
return; // NOLINT
|
||||
|
||||
switch (this->rotation_) {
|
||||
case DISPLAY_ROTATION_0_DEGREES:
|
||||
break;
|
||||
case DISPLAY_ROTATION_90_DEGREES:
|
||||
std::swap(x, y);
|
||||
x = this->get_width_internal() - x - 1;
|
||||
break;
|
||||
case DISPLAY_ROTATION_180_DEGREES:
|
||||
x = this->get_width_internal() - x - 1;
|
||||
y = this->get_height_internal() - y - 1;
|
||||
break;
|
||||
case DISPLAY_ROTATION_270_DEGREES:
|
||||
std::swap(x, y);
|
||||
y = this->get_height_internal() - y - 1;
|
||||
break;
|
||||
}
|
||||
this->draw_absolute_pixel_internal(x, y, color);
|
||||
App.feed_wdt();
|
||||
}
|
||||
|
||||
} // namespace display
|
||||
} // namespace esphome
|
||||
34
esphome/components/display/display_buffer.h
Normal file
34
esphome/components/display/display_buffer.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdarg>
|
||||
#include <vector>
|
||||
|
||||
#include "display.h"
|
||||
#include "display_color_utils.h"
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace display {
|
||||
|
||||
class DisplayBuffer : public Display {
|
||||
public:
|
||||
/// Get the width of the image in pixels with rotation applied.
|
||||
int get_width() override;
|
||||
/// Get the height of the image in pixels with rotation applied.
|
||||
int get_height() override;
|
||||
|
||||
/// Set a single pixel at the specified coordinates to the given color.
|
||||
void draw_pixel_at(int x, int y, Color color) override;
|
||||
|
||||
protected:
|
||||
virtual void draw_absolute_pixel_internal(int x, int y, Color color) = 0;
|
||||
|
||||
void init_internal_(uint32_t buffer_length);
|
||||
|
||||
uint8_t *buffer_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace display
|
||||
} // namespace esphome
|
||||
159
esphome/components/display/display_color_utils.h
Normal file
159
esphome/components/display/display_color_utils.h
Normal file
@@ -0,0 +1,159 @@
|
||||
#pragma once
|
||||
#include "esphome/core/color.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace display {
|
||||
enum ColorOrder : uint8_t { COLOR_ORDER_RGB = 0, COLOR_ORDER_BGR = 1, COLOR_ORDER_GRB = 2 };
|
||||
enum ColorBitness : uint8_t { COLOR_BITNESS_888 = 0, COLOR_BITNESS_565 = 1, COLOR_BITNESS_332 = 2 };
|
||||
inline static uint8_t esp_scale(uint8_t i, uint8_t scale, uint8_t max_value = 255) { return (max_value * i / scale); }
|
||||
|
||||
class ColorUtil {
|
||||
public:
|
||||
static Color to_color(uint32_t colorcode, ColorOrder color_order,
|
||||
ColorBitness color_bitness = ColorBitness::COLOR_BITNESS_888, bool right_bit_aligned = true) {
|
||||
uint8_t first_color, second_color, third_color;
|
||||
uint8_t first_bits = 0;
|
||||
uint8_t second_bits = 0;
|
||||
uint8_t third_bits = 0;
|
||||
|
||||
switch (color_bitness) {
|
||||
case COLOR_BITNESS_888:
|
||||
first_bits = 8;
|
||||
second_bits = 8;
|
||||
third_bits = 8;
|
||||
break;
|
||||
case COLOR_BITNESS_565:
|
||||
first_bits = 5;
|
||||
second_bits = 6;
|
||||
third_bits = 5;
|
||||
break;
|
||||
case COLOR_BITNESS_332:
|
||||
first_bits = 3;
|
||||
second_bits = 3;
|
||||
third_bits = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
first_color = right_bit_aligned ? esp_scale(((colorcode >> (second_bits + third_bits)) & ((1 << first_bits) - 1)),
|
||||
((1 << first_bits) - 1))
|
||||
: esp_scale(((colorcode >> 16) & 0xFF), (1 << first_bits) - 1);
|
||||
|
||||
second_color = right_bit_aligned
|
||||
? esp_scale(((colorcode >> third_bits) & ((1 << second_bits) - 1)), ((1 << second_bits) - 1))
|
||||
: esp_scale(((colorcode >> 8) & 0xFF), ((1 << second_bits) - 1));
|
||||
|
||||
third_color = (right_bit_aligned ? esp_scale(((colorcode >> 0) & ((1 << third_bits) - 1)), ((1 << third_bits) - 1))
|
||||
: esp_scale(((colorcode >> 0) & 0xFF), (1 << third_bits) - 1));
|
||||
|
||||
Color color_return;
|
||||
|
||||
switch (color_order) {
|
||||
case COLOR_ORDER_RGB:
|
||||
color_return.r = first_color;
|
||||
color_return.g = second_color;
|
||||
color_return.b = third_color;
|
||||
break;
|
||||
case COLOR_ORDER_BGR:
|
||||
color_return.b = first_color;
|
||||
color_return.g = second_color;
|
||||
color_return.r = third_color;
|
||||
break;
|
||||
case COLOR_ORDER_GRB:
|
||||
color_return.g = first_color;
|
||||
color_return.r = second_color;
|
||||
color_return.b = third_color;
|
||||
break;
|
||||
}
|
||||
return color_return;
|
||||
}
|
||||
static inline Color rgb332_to_color(uint8_t rgb332_color) {
|
||||
return to_color((uint32_t) rgb332_color, COLOR_ORDER_RGB, COLOR_BITNESS_332);
|
||||
}
|
||||
static uint8_t color_to_332(Color color, ColorOrder color_order = ColorOrder::COLOR_ORDER_RGB) {
|
||||
uint16_t red_color, green_color, blue_color;
|
||||
|
||||
red_color = esp_scale8(color.red, ((1 << 3) - 1));
|
||||
green_color = esp_scale8(color.green, ((1 << 3) - 1));
|
||||
blue_color = esp_scale8(color.blue, (1 << 2) - 1);
|
||||
|
||||
switch (color_order) {
|
||||
case COLOR_ORDER_RGB:
|
||||
return red_color << 5 | green_color << 2 | blue_color;
|
||||
case COLOR_ORDER_BGR:
|
||||
return blue_color << 6 | green_color << 3 | red_color;
|
||||
case COLOR_ORDER_GRB:
|
||||
return green_color << 5 | red_color << 2 | blue_color;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
static uint16_t color_to_565(Color color, ColorOrder color_order = ColorOrder::COLOR_ORDER_RGB) {
|
||||
uint16_t red_color, green_color, blue_color;
|
||||
|
||||
red_color = esp_scale8(color.red, ((1 << 5) - 1));
|
||||
green_color = esp_scale8(color.green, ((1 << 6) - 1));
|
||||
blue_color = esp_scale8(color.blue, (1 << 5) - 1);
|
||||
|
||||
switch (color_order) {
|
||||
case COLOR_ORDER_RGB:
|
||||
return red_color << 11 | green_color << 5 | blue_color;
|
||||
case COLOR_ORDER_BGR:
|
||||
return blue_color << 11 | green_color << 5 | red_color;
|
||||
case COLOR_ORDER_GRB:
|
||||
return green_color << 10 | red_color << 5 | blue_color;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
static uint32_t color_to_grayscale4(Color color) {
|
||||
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 euclidean 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
|
||||
94
esphome/components/display/rect.cpp
Normal file
94
esphome/components/display/rect.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
#include "rect.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace display {
|
||||
|
||||
static const char *const TAG = "display";
|
||||
|
||||
void Rect::expand(int16_t horizontal, int16_t vertical) {
|
||||
if (this->is_set() && (this->w >= (-2 * horizontal)) && (this->h >= (-2 * vertical))) {
|
||||
this->x = this->x - horizontal;
|
||||
this->y = this->y - vertical;
|
||||
this->w = this->w + (2 * horizontal);
|
||||
this->h = this->h + (2 * vertical);
|
||||
}
|
||||
}
|
||||
|
||||
void Rect::extend(Rect rect) {
|
||||
if (!this->is_set()) {
|
||||
this->x = rect.x;
|
||||
this->y = rect.y;
|
||||
this->w = rect.w;
|
||||
this->h = rect.h;
|
||||
} else {
|
||||
if (this->x > rect.x) {
|
||||
this->w = this->w + (this->x - rect.x);
|
||||
this->x = rect.x;
|
||||
}
|
||||
if (this->y > rect.y) {
|
||||
this->h = this->h + (this->y - rect.y);
|
||||
this->y = rect.y;
|
||||
}
|
||||
if (this->x2() < rect.x2()) {
|
||||
this->w = rect.x2() - this->x;
|
||||
}
|
||||
if (this->y2() < rect.y2()) {
|
||||
this->h = rect.y2() - this->y;
|
||||
}
|
||||
}
|
||||
}
|
||||
void Rect::shrink(Rect rect) {
|
||||
if (!this->inside(rect)) {
|
||||
(*this) = Rect();
|
||||
} else {
|
||||
if (this->x2() > rect.x2()) {
|
||||
this->w = rect.x2() - this->x;
|
||||
}
|
||||
if (this->x < rect.x) {
|
||||
this->w = this->w + (this->x - rect.x);
|
||||
this->x = rect.x;
|
||||
}
|
||||
if (this->y2() > rect.y2()) {
|
||||
this->h = rect.y2() - this->y;
|
||||
}
|
||||
if (this->y < rect.y) {
|
||||
this->h = this->h + (this->y - rect.y);
|
||||
this->y = rect.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Rect::equal(Rect rect) const {
|
||||
return (rect.x == this->x) && (rect.w == this->w) && (rect.y == this->y) && (rect.h == this->h);
|
||||
}
|
||||
|
||||
bool Rect::inside(int16_t test_x, int16_t test_y, bool absolute) const { // NOLINT
|
||||
if (!this->is_set()) {
|
||||
return true;
|
||||
}
|
||||
if (absolute) {
|
||||
return test_x >= this->x && test_x < this->x2() && test_y >= this->y && test_y < this->y2();
|
||||
}
|
||||
return test_x >= 0 && test_x < this->w && test_y >= 0 && test_y < this->h;
|
||||
}
|
||||
|
||||
bool Rect::inside(Rect rect) const {
|
||||
if (!this->is_set() || !rect.is_set()) {
|
||||
return true;
|
||||
}
|
||||
return this->x2() >= rect.x && this->x <= rect.x2() && this->y2() >= rect.y && this->y <= rect.y2();
|
||||
}
|
||||
|
||||
void Rect::info(const std::string &prefix) {
|
||||
if (this->is_set()) {
|
||||
ESP_LOGI(TAG, "%s [%3d,%3d,%3d,%3d] (%3d,%3d)", prefix.c_str(), this->x, this->y, this->w, this->h, this->x2(),
|
||||
this->y2());
|
||||
} else {
|
||||
ESP_LOGI(TAG, "%s ** IS NOT SET **", prefix.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace display
|
||||
} // namespace esphome
|
||||
36
esphome/components/display/rect.h
Normal file
36
esphome/components/display/rect.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace display {
|
||||
|
||||
static const int16_t VALUE_NO_SET = 32766;
|
||||
|
||||
class Rect {
|
||||
public:
|
||||
int16_t x; ///< X coordinate of corner
|
||||
int16_t y; ///< Y coordinate of corner
|
||||
int16_t w; ///< Width of region
|
||||
int16_t h; ///< Height of region
|
||||
|
||||
Rect() : x(VALUE_NO_SET), y(VALUE_NO_SET), w(VALUE_NO_SET), h(VALUE_NO_SET) {} // NOLINT
|
||||
inline Rect(int16_t x, int16_t y, int16_t w, int16_t h) ESPHOME_ALWAYS_INLINE : x(x), y(y), w(w), h(h) {}
|
||||
inline int16_t x2() const { return this->x + this->w; }; ///< X coordinate of corner
|
||||
inline int16_t y2() const { return this->y + this->h; }; ///< Y coordinate of corner
|
||||
|
||||
inline bool is_set() const ESPHOME_ALWAYS_INLINE { return (this->h != VALUE_NO_SET) && (this->w != VALUE_NO_SET); }
|
||||
|
||||
void expand(int16_t horizontal, int16_t vertical);
|
||||
|
||||
void extend(Rect rect);
|
||||
void shrink(Rect rect);
|
||||
|
||||
bool inside(Rect rect) const;
|
||||
bool inside(int16_t test_x, int16_t test_y, bool absolute = true) const;
|
||||
bool equal(Rect rect) const;
|
||||
void info(const std::string &prefix = "rect info:");
|
||||
};
|
||||
|
||||
} // namespace display
|
||||
} // namespace esphome
|
||||
Reference in New Issue
Block a user