mirror of
https://github.com/esphome/esphome.git
synced 2024-11-29 18:24:13 +01:00
Split display_buffer sub-components into own files (#4950)
* Split display_buffer sub-components into own files Move the Image, Animation and Font classes to their own h/cpp pairs, instead of having everything into the display_buffer h/cpp files. * Fixed COLOR_ON duplicate definition
This commit is contained in:
parent
abca47f36f
commit
6aa3092be0
9 changed files with 502 additions and 431 deletions
69
esphome/components/display/animation.cpp
Normal file
69
esphome/components/display/animation.cpp
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
#include "animation.h"
|
||||||
|
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace display {
|
||||||
|
|
||||||
|
Animation::Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type)
|
||||||
|
: Image(data_start, width, height, type),
|
||||||
|
animation_data_start_(data_start),
|
||||||
|
current_frame_(0),
|
||||||
|
animation_frame_count_(animation_frame_count),
|
||||||
|
loop_start_frame_(0),
|
||||||
|
loop_end_frame_(animation_frame_count_),
|
||||||
|
loop_count_(0),
|
||||||
|
loop_current_iteration_(1) {}
|
||||||
|
void Animation::set_loop(uint32_t start_frame, uint32_t end_frame, int count) {
|
||||||
|
loop_start_frame_ = std::min(start_frame, animation_frame_count_);
|
||||||
|
loop_end_frame_ = std::min(end_frame, animation_frame_count_);
|
||||||
|
loop_count_ = count;
|
||||||
|
loop_current_iteration_ = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t Animation::get_animation_frame_count() const { return this->animation_frame_count_; }
|
||||||
|
int Animation::get_current_frame() const { return this->current_frame_; }
|
||||||
|
void Animation::next_frame() {
|
||||||
|
this->current_frame_++;
|
||||||
|
if (loop_count_ && this->current_frame_ == loop_end_frame_ &&
|
||||||
|
(this->loop_current_iteration_ < loop_count_ || loop_count_ < 0)) {
|
||||||
|
this->current_frame_ = loop_start_frame_;
|
||||||
|
this->loop_current_iteration_++;
|
||||||
|
}
|
||||||
|
if (this->current_frame_ >= animation_frame_count_) {
|
||||||
|
this->loop_current_iteration_ = 1;
|
||||||
|
this->current_frame_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->update_data_start_();
|
||||||
|
}
|
||||||
|
void Animation::prev_frame() {
|
||||||
|
this->current_frame_--;
|
||||||
|
if (this->current_frame_ < 0) {
|
||||||
|
this->current_frame_ = this->animation_frame_count_ - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->update_data_start_();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animation::set_frame(int frame) {
|
||||||
|
unsigned abs_frame = abs(frame);
|
||||||
|
|
||||||
|
if (abs_frame < this->animation_frame_count_) {
|
||||||
|
if (frame >= 0) {
|
||||||
|
this->current_frame_ = frame;
|
||||||
|
} else {
|
||||||
|
this->current_frame_ = this->animation_frame_count_ - abs_frame;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this->update_data_start_();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animation::update_data_start_() {
|
||||||
|
const uint32_t image_size = image_type_to_width_stride(this->width_, this->type_) * this->height_;
|
||||||
|
this->data_start_ = this->animation_data_start_ + image_size * this->current_frame_;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace display
|
||||||
|
} // namespace esphome
|
37
esphome/components/display/animation.h
Normal file
37
esphome/components/display/animation.h
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
#pragma once
|
||||||
|
#include "image.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace display {
|
||||||
|
|
||||||
|
class Animation : public Image {
|
||||||
|
public:
|
||||||
|
Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type);
|
||||||
|
|
||||||
|
uint32_t get_animation_frame_count() const;
|
||||||
|
int get_current_frame() const;
|
||||||
|
void next_frame();
|
||||||
|
void prev_frame();
|
||||||
|
|
||||||
|
/** Selects a specific frame within the animation.
|
||||||
|
*
|
||||||
|
* @param frame If possitive, advance to the frame. If negative, recede to that frame from the end frame.
|
||||||
|
*/
|
||||||
|
void set_frame(int frame);
|
||||||
|
|
||||||
|
void set_loop(uint32_t start_frame, uint32_t end_frame, int count);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void update_data_start_();
|
||||||
|
|
||||||
|
const uint8_t *animation_data_start_;
|
||||||
|
int current_frame_;
|
||||||
|
uint32_t animation_frame_count_;
|
||||||
|
uint32_t loop_start_frame_;
|
||||||
|
uint32_t loop_end_frame_;
|
||||||
|
int loop_count_;
|
||||||
|
int loop_current_iteration_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace display
|
||||||
|
} // namespace esphome
|
|
@ -7,6 +7,10 @@
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#include "animation.h"
|
||||||
|
#include "image.h"
|
||||||
|
#include "font.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace display {
|
namespace display {
|
||||||
|
|
||||||
|
@ -15,25 +19,6 @@ static const char *const TAG = "display";
|
||||||
const Color COLOR_OFF(0, 0, 0, 255);
|
const Color COLOR_OFF(0, 0, 0, 255);
|
||||||
const Color COLOR_ON(255, 255, 255, 255);
|
const Color COLOR_ON(255, 255, 255, 255);
|
||||||
|
|
||||||
static int image_type_to_bpp(ImageType type) {
|
|
||||||
switch (type) {
|
|
||||||
case IMAGE_TYPE_BINARY:
|
|
||||||
return 1;
|
|
||||||
case IMAGE_TYPE_GRAYSCALE:
|
|
||||||
return 8;
|
|
||||||
case IMAGE_TYPE_RGB565:
|
|
||||||
return 16;
|
|
||||||
case IMAGE_TYPE_RGB24:
|
|
||||||
return 24;
|
|
||||||
case IMAGE_TYPE_RGBA:
|
|
||||||
return 32;
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int image_type_to_width_stride(int width, ImageType type) { return (width * image_type_to_bpp(type) + 7u) / 8u; }
|
|
||||||
|
|
||||||
void Rect::expand(int16_t horizontal, int16_t vertical) {
|
void Rect::expand(int16_t horizontal, int16_t vertical) {
|
||||||
if (this->is_set() && (this->w >= (-2 * horizontal)) && (this->h >= (-2 * vertical))) {
|
if (this->is_set() && (this->w >= (-2 * horizontal)) && (this->h >= (-2 * vertical))) {
|
||||||
this->x = this->x - horizontal;
|
this->x = this->x - horizontal;
|
||||||
|
@ -505,286 +490,6 @@ Rect DisplayBuffer::get_clipping() {
|
||||||
return this->clipping_rectangle_.back();
|
return this->clipping_rectangle_.back();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bool Glyph::get_pixel(int x, int y) const {
|
|
||||||
const int x_data = x - this->glyph_data_->offset_x;
|
|
||||||
const int y_data = y - this->glyph_data_->offset_y;
|
|
||||||
if (x_data < 0 || x_data >= this->glyph_data_->width || y_data < 0 || y_data >= this->glyph_data_->height)
|
|
||||||
return false;
|
|
||||||
const uint32_t width_8 = ((this->glyph_data_->width + 7u) / 8u) * 8u;
|
|
||||||
const uint32_t pos = x_data + y_data * width_8;
|
|
||||||
return progmem_read_byte(this->glyph_data_->data + (pos / 8u)) & (0x80 >> (pos % 8u));
|
|
||||||
}
|
|
||||||
const char *Glyph::get_char() const { return this->glyph_data_->a_char; }
|
|
||||||
bool Glyph::compare_to(const char *str) const {
|
|
||||||
// 1 -> this->char_
|
|
||||||
// 2 -> str
|
|
||||||
for (uint32_t i = 0;; i++) {
|
|
||||||
if (this->glyph_data_->a_char[i] == '\0')
|
|
||||||
return true;
|
|
||||||
if (str[i] == '\0')
|
|
||||||
return false;
|
|
||||||
if (this->glyph_data_->a_char[i] > str[i])
|
|
||||||
return false;
|
|
||||||
if (this->glyph_data_->a_char[i] < str[i])
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// this should not happen
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
int Glyph::match_length(const char *str) const {
|
|
||||||
for (uint32_t i = 0;; i++) {
|
|
||||||
if (this->glyph_data_->a_char[i] == '\0')
|
|
||||||
return i;
|
|
||||||
if (str[i] != this->glyph_data_->a_char[i])
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
// this should not happen
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
void Glyph::scan_area(int *x1, int *y1, int *width, int *height) const {
|
|
||||||
*x1 = this->glyph_data_->offset_x;
|
|
||||||
*y1 = this->glyph_data_->offset_y;
|
|
||||||
*width = this->glyph_data_->width;
|
|
||||||
*height = this->glyph_data_->height;
|
|
||||||
}
|
|
||||||
int Font::match_next_glyph(const char *str, int *match_length) {
|
|
||||||
int lo = 0;
|
|
||||||
int hi = this->glyphs_.size() - 1;
|
|
||||||
while (lo != hi) {
|
|
||||||
int mid = (lo + hi + 1) / 2;
|
|
||||||
if (this->glyphs_[mid].compare_to(str)) {
|
|
||||||
lo = mid;
|
|
||||||
} else {
|
|
||||||
hi = mid - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*match_length = this->glyphs_[lo].match_length(str);
|
|
||||||
if (*match_length <= 0)
|
|
||||||
return -1;
|
|
||||||
return lo;
|
|
||||||
}
|
|
||||||
void Font::measure(const char *str, int *width, int *x_offset, int *baseline, int *height) {
|
|
||||||
*baseline = this->baseline_;
|
|
||||||
*height = this->height_;
|
|
||||||
int i = 0;
|
|
||||||
int min_x = 0;
|
|
||||||
bool has_char = false;
|
|
||||||
int x = 0;
|
|
||||||
while (str[i] != '\0') {
|
|
||||||
int match_length;
|
|
||||||
int glyph_n = this->match_next_glyph(str + i, &match_length);
|
|
||||||
if (glyph_n < 0) {
|
|
||||||
// Unknown char, skip
|
|
||||||
if (!this->get_glyphs().empty())
|
|
||||||
x += this->get_glyphs()[0].glyph_data_->width;
|
|
||||||
i++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Glyph &glyph = this->glyphs_[glyph_n];
|
|
||||||
if (!has_char) {
|
|
||||||
min_x = glyph.glyph_data_->offset_x;
|
|
||||||
} else {
|
|
||||||
min_x = std::min(min_x, x + glyph.glyph_data_->offset_x);
|
|
||||||
}
|
|
||||||
x += glyph.glyph_data_->width + glyph.glyph_data_->offset_x;
|
|
||||||
|
|
||||||
i += match_length;
|
|
||||||
has_char = true;
|
|
||||||
}
|
|
||||||
*x_offset = min_x;
|
|
||||||
*width = x - min_x;
|
|
||||||
}
|
|
||||||
Font::Font(const GlyphData *data, int data_nr, int baseline, int height) : baseline_(baseline), height_(height) {
|
|
||||||
glyphs_.reserve(data_nr);
|
|
||||||
for (int i = 0; i < data_nr; ++i)
|
|
||||||
glyphs_.emplace_back(&data[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Image::draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) {
|
|
||||||
switch (type_) {
|
|
||||||
case IMAGE_TYPE_BINARY: {
|
|
||||||
for (int img_x = 0; img_x < width_; img_x++) {
|
|
||||||
for (int img_y = 0; img_y < height_; img_y++) {
|
|
||||||
if (this->get_binary_pixel_(img_x, img_y)) {
|
|
||||||
display->draw_pixel_at(x + img_x, y + img_y, color_on);
|
|
||||||
} else if (!this->transparent_) {
|
|
||||||
display->draw_pixel_at(x + img_x, y + img_y, color_off);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case IMAGE_TYPE_GRAYSCALE:
|
|
||||||
for (int img_x = 0; img_x < width_; img_x++) {
|
|
||||||
for (int img_y = 0; img_y < height_; img_y++) {
|
|
||||||
auto color = this->get_grayscale_pixel_(img_x, img_y);
|
|
||||||
if (color.w >= 0x80) {
|
|
||||||
display->draw_pixel_at(x + img_x, y + img_y, color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case IMAGE_TYPE_RGB565:
|
|
||||||
for (int img_x = 0; img_x < width_; img_x++) {
|
|
||||||
for (int img_y = 0; img_y < height_; img_y++) {
|
|
||||||
auto color = this->get_rgb565_pixel_(img_x, img_y);
|
|
||||||
if (color.w >= 0x80) {
|
|
||||||
display->draw_pixel_at(x + img_x, y + img_y, color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case IMAGE_TYPE_RGB24:
|
|
||||||
for (int img_x = 0; img_x < width_; img_x++) {
|
|
||||||
for (int img_y = 0; img_y < height_; img_y++) {
|
|
||||||
auto color = this->get_rgb24_pixel_(img_x, img_y);
|
|
||||||
if (color.w >= 0x80) {
|
|
||||||
display->draw_pixel_at(x + img_x, y + img_y, color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case IMAGE_TYPE_RGBA:
|
|
||||||
for (int img_x = 0; img_x < width_; img_x++) {
|
|
||||||
for (int img_y = 0; img_y < height_; img_y++) {
|
|
||||||
auto color = this->get_rgba_pixel_(img_x, img_y);
|
|
||||||
if (color.w >= 0x80) {
|
|
||||||
display->draw_pixel_at(x + img_x, y + img_y, color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Color Image::get_pixel(int x, int y, Color color_on, Color color_off) const {
|
|
||||||
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
|
|
||||||
return color_off;
|
|
||||||
switch (this->type_) {
|
|
||||||
case IMAGE_TYPE_BINARY:
|
|
||||||
return this->get_binary_pixel_(x, y) ? color_on : color_off;
|
|
||||||
case IMAGE_TYPE_GRAYSCALE:
|
|
||||||
return this->get_grayscale_pixel_(x, y);
|
|
||||||
case IMAGE_TYPE_RGB565:
|
|
||||||
return this->get_rgb565_pixel_(x, y);
|
|
||||||
case IMAGE_TYPE_RGB24:
|
|
||||||
return this->get_rgb24_pixel_(x, y);
|
|
||||||
case IMAGE_TYPE_RGBA:
|
|
||||||
return this->get_rgba_pixel_(x, y);
|
|
||||||
default:
|
|
||||||
return color_off;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bool Image::get_binary_pixel_(int x, int y) const {
|
|
||||||
const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u;
|
|
||||||
const uint32_t pos = x + y * width_8;
|
|
||||||
return progmem_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u));
|
|
||||||
}
|
|
||||||
Color Image::get_rgba_pixel_(int x, int y) const {
|
|
||||||
const uint32_t pos = (x + y * this->width_) * 4;
|
|
||||||
return Color(progmem_read_byte(this->data_start_ + pos + 0), progmem_read_byte(this->data_start_ + pos + 1),
|
|
||||||
progmem_read_byte(this->data_start_ + pos + 2), progmem_read_byte(this->data_start_ + pos + 3));
|
|
||||||
}
|
|
||||||
Color Image::get_rgb24_pixel_(int x, int y) const {
|
|
||||||
const uint32_t pos = (x + y * this->width_) * 3;
|
|
||||||
Color color = Color(progmem_read_byte(this->data_start_ + pos + 0), progmem_read_byte(this->data_start_ + pos + 1),
|
|
||||||
progmem_read_byte(this->data_start_ + pos + 2));
|
|
||||||
if (color.b == 1 && color.r == 0 && color.g == 0 && transparent_) {
|
|
||||||
// (0, 0, 1) has been defined as transparent color for non-alpha images.
|
|
||||||
// putting blue == 1 as a first condition for performance reasons (least likely value to short-cut the if)
|
|
||||||
color.w = 0;
|
|
||||||
} else {
|
|
||||||
color.w = 0xFF;
|
|
||||||
}
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
Color Image::get_rgb565_pixel_(int x, int y) const {
|
|
||||||
const uint32_t pos = (x + y * this->width_) * 2;
|
|
||||||
uint16_t rgb565 =
|
|
||||||
progmem_read_byte(this->data_start_ + pos + 0) << 8 | progmem_read_byte(this->data_start_ + pos + 1);
|
|
||||||
auto r = (rgb565 & 0xF800) >> 11;
|
|
||||||
auto g = (rgb565 & 0x07E0) >> 5;
|
|
||||||
auto b = rgb565 & 0x001F;
|
|
||||||
Color color = Color((r << 3) | (r >> 2), (g << 2) | (g >> 4), (b << 3) | (b >> 2));
|
|
||||||
if (rgb565 == 0x0020 && transparent_) {
|
|
||||||
// darkest green has been defined as transparent color for transparent RGB565 images.
|
|
||||||
color.w = 0;
|
|
||||||
} else {
|
|
||||||
color.w = 0xFF;
|
|
||||||
}
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
Color Image::get_grayscale_pixel_(int x, int y) const {
|
|
||||||
const uint32_t pos = (x + y * this->width_);
|
|
||||||
const uint8_t gray = progmem_read_byte(this->data_start_ + pos);
|
|
||||||
uint8_t alpha = (gray == 1 && transparent_) ? 0 : 0xFF;
|
|
||||||
return Color(gray, gray, gray, alpha);
|
|
||||||
}
|
|
||||||
int Image::get_width() const { return this->width_; }
|
|
||||||
int Image::get_height() const { return this->height_; }
|
|
||||||
ImageType Image::get_type() const { return this->type_; }
|
|
||||||
Image::Image(const uint8_t *data_start, int width, int height, ImageType type)
|
|
||||||
: width_(width), height_(height), type_(type), data_start_(data_start) {}
|
|
||||||
|
|
||||||
Animation::Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type)
|
|
||||||
: Image(data_start, width, height, type),
|
|
||||||
animation_data_start_(data_start),
|
|
||||||
current_frame_(0),
|
|
||||||
animation_frame_count_(animation_frame_count),
|
|
||||||
loop_start_frame_(0),
|
|
||||||
loop_end_frame_(animation_frame_count_),
|
|
||||||
loop_count_(0),
|
|
||||||
loop_current_iteration_(1) {}
|
|
||||||
void Animation::set_loop(uint32_t start_frame, uint32_t end_frame, int count) {
|
|
||||||
loop_start_frame_ = std::min(start_frame, animation_frame_count_);
|
|
||||||
loop_end_frame_ = std::min(end_frame, animation_frame_count_);
|
|
||||||
loop_count_ = count;
|
|
||||||
loop_current_iteration_ = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t Animation::get_animation_frame_count() const { return this->animation_frame_count_; }
|
|
||||||
int Animation::get_current_frame() const { return this->current_frame_; }
|
|
||||||
void Animation::next_frame() {
|
|
||||||
this->current_frame_++;
|
|
||||||
if (loop_count_ && this->current_frame_ == loop_end_frame_ &&
|
|
||||||
(this->loop_current_iteration_ < loop_count_ || loop_count_ < 0)) {
|
|
||||||
this->current_frame_ = loop_start_frame_;
|
|
||||||
this->loop_current_iteration_++;
|
|
||||||
}
|
|
||||||
if (this->current_frame_ >= animation_frame_count_) {
|
|
||||||
this->loop_current_iteration_ = 1;
|
|
||||||
this->current_frame_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->update_data_start_();
|
|
||||||
}
|
|
||||||
void Animation::prev_frame() {
|
|
||||||
this->current_frame_--;
|
|
||||||
if (this->current_frame_ < 0) {
|
|
||||||
this->current_frame_ = this->animation_frame_count_ - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->update_data_start_();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Animation::set_frame(int frame) {
|
|
||||||
unsigned abs_frame = abs(frame);
|
|
||||||
|
|
||||||
if (abs_frame < this->animation_frame_count_) {
|
|
||||||
if (frame >= 0) {
|
|
||||||
this->current_frame_ = frame;
|
|
||||||
} else {
|
|
||||||
this->current_frame_ = this->animation_frame_count_ - abs_frame;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this->update_data_start_();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Animation::update_data_start_() {
|
|
||||||
const uint32_t image_size = image_type_to_width_stride(this->width_, this->type_) * this->height_;
|
|
||||||
this->data_start_ = this->animation_data_start_ + image_size * this->current_frame_;
|
|
||||||
}
|
|
||||||
|
|
||||||
DisplayPage::DisplayPage(display_writer_t writer) : writer_(std::move(writer)) {}
|
DisplayPage::DisplayPage(display_writer_t writer) : writer_(std::move(writer)) {}
|
||||||
void DisplayPage::show() { this->parent_->show_page(this); }
|
void DisplayPage::show() { this->parent_->show_page(this); }
|
||||||
|
|
|
@ -16,6 +16,10 @@
|
||||||
#include "esphome/components/qr_code/qr_code.h"
|
#include "esphome/components/qr_code/qr_code.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "animation.h"
|
||||||
|
#include "font.h"
|
||||||
|
#include "image.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace display {
|
namespace display {
|
||||||
|
|
||||||
|
@ -70,19 +74,6 @@ enum class TextAlign {
|
||||||
BOTTOM_RIGHT = BOTTOM | RIGHT,
|
BOTTOM_RIGHT = BOTTOM | RIGHT,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Turn the pixel OFF.
|
|
||||||
extern const Color COLOR_OFF;
|
|
||||||
/// Turn the pixel ON.
|
|
||||||
extern const Color COLOR_ON;
|
|
||||||
|
|
||||||
enum ImageType {
|
|
||||||
IMAGE_TYPE_BINARY = 0,
|
|
||||||
IMAGE_TYPE_GRAYSCALE = 1,
|
|
||||||
IMAGE_TYPE_RGB24 = 2,
|
|
||||||
IMAGE_TYPE_RGB565 = 3,
|
|
||||||
IMAGE_TYPE_RGBA = 4,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum DisplayType {
|
enum DisplayType {
|
||||||
DISPLAY_TYPE_BINARY = 1,
|
DISPLAY_TYPE_BINARY = 1,
|
||||||
DISPLAY_TYPE_GRAYSCALE = 2,
|
DISPLAY_TYPE_GRAYSCALE = 2,
|
||||||
|
@ -123,8 +114,6 @@ class Rect {
|
||||||
void info(const std::string &prefix = "rect info:");
|
void info(const std::string &prefix = "rect info:");
|
||||||
};
|
};
|
||||||
|
|
||||||
class BaseImage;
|
|
||||||
class Font;
|
|
||||||
class DisplayBuffer;
|
class DisplayBuffer;
|
||||||
class DisplayPage;
|
class DisplayPage;
|
||||||
class DisplayOnPageChangeTrigger;
|
class DisplayOnPageChangeTrigger;
|
||||||
|
@ -475,123 +464,6 @@ class DisplayPage {
|
||||||
DisplayPage *next_{nullptr};
|
DisplayPage *next_{nullptr};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GlyphData {
|
|
||||||
const char *a_char;
|
|
||||||
const uint8_t *data;
|
|
||||||
int offset_x;
|
|
||||||
int offset_y;
|
|
||||||
int width;
|
|
||||||
int height;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Glyph {
|
|
||||||
public:
|
|
||||||
Glyph(const GlyphData *data) : glyph_data_(data) {}
|
|
||||||
|
|
||||||
bool get_pixel(int x, int y) const;
|
|
||||||
|
|
||||||
const char *get_char() const;
|
|
||||||
|
|
||||||
bool compare_to(const char *str) const;
|
|
||||||
|
|
||||||
int match_length(const char *str) const;
|
|
||||||
|
|
||||||
void scan_area(int *x1, int *y1, int *width, int *height) const;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
friend Font;
|
|
||||||
friend DisplayBuffer;
|
|
||||||
|
|
||||||
const GlyphData *glyph_data_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Font {
|
|
||||||
public:
|
|
||||||
/** Construct the font with the given glyphs.
|
|
||||||
*
|
|
||||||
* @param glyphs A vector of glyphs, must be sorted lexicographically.
|
|
||||||
* @param baseline The y-offset from the top of the text to the baseline.
|
|
||||||
* @param bottom The y-offset from the top of the text to the bottom (i.e. height).
|
|
||||||
*/
|
|
||||||
Font(const GlyphData *data, int data_nr, int baseline, int height);
|
|
||||||
|
|
||||||
int match_next_glyph(const char *str, int *match_length);
|
|
||||||
|
|
||||||
void measure(const char *str, int *width, int *x_offset, int *baseline, int *height);
|
|
||||||
inline int get_baseline() { return this->baseline_; }
|
|
||||||
inline int get_height() { return this->height_; }
|
|
||||||
|
|
||||||
const std::vector<Glyph, ExternalRAMAllocator<Glyph>> &get_glyphs() const { return glyphs_; }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
std::vector<Glyph, ExternalRAMAllocator<Glyph>> glyphs_;
|
|
||||||
int baseline_;
|
|
||||||
int height_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class BaseImage {
|
|
||||||
public:
|
|
||||||
virtual void draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) = 0;
|
|
||||||
virtual int get_width() const = 0;
|
|
||||||
virtual int get_height() const = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Image : public BaseImage {
|
|
||||||
public:
|
|
||||||
Image(const uint8_t *data_start, int width, int height, ImageType type);
|
|
||||||
Color get_pixel(int x, int y, Color color_on = COLOR_ON, Color color_off = COLOR_OFF) const;
|
|
||||||
int get_width() const override;
|
|
||||||
int get_height() const override;
|
|
||||||
ImageType get_type() const;
|
|
||||||
|
|
||||||
void draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) override;
|
|
||||||
|
|
||||||
void set_transparency(bool transparent) { transparent_ = transparent; }
|
|
||||||
bool has_transparency() const { return transparent_; }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
bool get_binary_pixel_(int x, int y) const;
|
|
||||||
Color get_rgb24_pixel_(int x, int y) const;
|
|
||||||
Color get_rgba_pixel_(int x, int y) const;
|
|
||||||
Color get_rgb565_pixel_(int x, int y) const;
|
|
||||||
Color get_grayscale_pixel_(int x, int y) const;
|
|
||||||
|
|
||||||
int width_;
|
|
||||||
int height_;
|
|
||||||
ImageType type_;
|
|
||||||
const uint8_t *data_start_;
|
|
||||||
bool transparent_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Animation : public Image {
|
|
||||||
public:
|
|
||||||
Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type);
|
|
||||||
|
|
||||||
uint32_t get_animation_frame_count() const;
|
|
||||||
int get_current_frame() const;
|
|
||||||
void next_frame();
|
|
||||||
void prev_frame();
|
|
||||||
|
|
||||||
/** Selects a specific frame within the animation.
|
|
||||||
*
|
|
||||||
* @param frame If possitive, advance to the frame. If negative, recede to that frame from the end frame.
|
|
||||||
*/
|
|
||||||
void set_frame(int frame);
|
|
||||||
|
|
||||||
void set_loop(uint32_t start_frame, uint32_t end_frame, int count);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void update_data_start_();
|
|
||||||
|
|
||||||
const uint8_t *animation_data_start_;
|
|
||||||
int current_frame_;
|
|
||||||
uint32_t animation_frame_count_;
|
|
||||||
uint32_t loop_start_frame_;
|
|
||||||
uint32_t loop_end_frame_;
|
|
||||||
int loop_count_;
|
|
||||||
int loop_current_iteration_;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename... Ts> class DisplayPageShowAction : public Action<Ts...> {
|
template<typename... Ts> class DisplayPageShowAction : public Action<Ts...> {
|
||||||
public:
|
public:
|
||||||
TEMPLATABLE_VALUE(DisplayPage *, page)
|
TEMPLATABLE_VALUE(DisplayPage *, page)
|
||||||
|
|
105
esphome/components/display/font.cpp
Normal file
105
esphome/components/display/font.cpp
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
#include "font.h"
|
||||||
|
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace display {
|
||||||
|
|
||||||
|
bool Glyph::get_pixel(int x, int y) const {
|
||||||
|
const int x_data = x - this->glyph_data_->offset_x;
|
||||||
|
const int y_data = y - this->glyph_data_->offset_y;
|
||||||
|
if (x_data < 0 || x_data >= this->glyph_data_->width || y_data < 0 || y_data >= this->glyph_data_->height)
|
||||||
|
return false;
|
||||||
|
const uint32_t width_8 = ((this->glyph_data_->width + 7u) / 8u) * 8u;
|
||||||
|
const uint32_t pos = x_data + y_data * width_8;
|
||||||
|
return progmem_read_byte(this->glyph_data_->data + (pos / 8u)) & (0x80 >> (pos % 8u));
|
||||||
|
}
|
||||||
|
const char *Glyph::get_char() const { return this->glyph_data_->a_char; }
|
||||||
|
bool Glyph::compare_to(const char *str) const {
|
||||||
|
// 1 -> this->char_
|
||||||
|
// 2 -> str
|
||||||
|
for (uint32_t i = 0;; i++) {
|
||||||
|
if (this->glyph_data_->a_char[i] == '\0')
|
||||||
|
return true;
|
||||||
|
if (str[i] == '\0')
|
||||||
|
return false;
|
||||||
|
if (this->glyph_data_->a_char[i] > str[i])
|
||||||
|
return false;
|
||||||
|
if (this->glyph_data_->a_char[i] < str[i])
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// this should not happen
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int Glyph::match_length(const char *str) const {
|
||||||
|
for (uint32_t i = 0;; i++) {
|
||||||
|
if (this->glyph_data_->a_char[i] == '\0')
|
||||||
|
return i;
|
||||||
|
if (str[i] != this->glyph_data_->a_char[i])
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// this should not happen
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
void Glyph::scan_area(int *x1, int *y1, int *width, int *height) const {
|
||||||
|
*x1 = this->glyph_data_->offset_x;
|
||||||
|
*y1 = this->glyph_data_->offset_y;
|
||||||
|
*width = this->glyph_data_->width;
|
||||||
|
*height = this->glyph_data_->height;
|
||||||
|
}
|
||||||
|
int Font::match_next_glyph(const char *str, int *match_length) {
|
||||||
|
int lo = 0;
|
||||||
|
int hi = this->glyphs_.size() - 1;
|
||||||
|
while (lo != hi) {
|
||||||
|
int mid = (lo + hi + 1) / 2;
|
||||||
|
if (this->glyphs_[mid].compare_to(str)) {
|
||||||
|
lo = mid;
|
||||||
|
} else {
|
||||||
|
hi = mid - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*match_length = this->glyphs_[lo].match_length(str);
|
||||||
|
if (*match_length <= 0)
|
||||||
|
return -1;
|
||||||
|
return lo;
|
||||||
|
}
|
||||||
|
void Font::measure(const char *str, int *width, int *x_offset, int *baseline, int *height) {
|
||||||
|
*baseline = this->baseline_;
|
||||||
|
*height = this->height_;
|
||||||
|
int i = 0;
|
||||||
|
int min_x = 0;
|
||||||
|
bool has_char = false;
|
||||||
|
int x = 0;
|
||||||
|
while (str[i] != '\0') {
|
||||||
|
int match_length;
|
||||||
|
int glyph_n = this->match_next_glyph(str + i, &match_length);
|
||||||
|
if (glyph_n < 0) {
|
||||||
|
// Unknown char, skip
|
||||||
|
if (!this->get_glyphs().empty())
|
||||||
|
x += this->get_glyphs()[0].glyph_data_->width;
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Glyph &glyph = this->glyphs_[glyph_n];
|
||||||
|
if (!has_char) {
|
||||||
|
min_x = glyph.glyph_data_->offset_x;
|
||||||
|
} else {
|
||||||
|
min_x = std::min(min_x, x + glyph.glyph_data_->offset_x);
|
||||||
|
}
|
||||||
|
x += glyph.glyph_data_->width + glyph.glyph_data_->offset_x;
|
||||||
|
|
||||||
|
i += match_length;
|
||||||
|
has_char = true;
|
||||||
|
}
|
||||||
|
*x_offset = min_x;
|
||||||
|
*width = x - min_x;
|
||||||
|
}
|
||||||
|
Font::Font(const GlyphData *data, int data_nr, int baseline, int height) : baseline_(baseline), height_(height) {
|
||||||
|
glyphs_.reserve(data_nr);
|
||||||
|
for (int i = 0; i < data_nr; ++i)
|
||||||
|
glyphs_.emplace_back(&data[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace display
|
||||||
|
} // namespace esphome
|
66
esphome/components/display/font.h
Normal file
66
esphome/components/display/font.h
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/datatypes.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace display {
|
||||||
|
|
||||||
|
class DisplayBuffer;
|
||||||
|
class Font;
|
||||||
|
|
||||||
|
struct GlyphData {
|
||||||
|
const char *a_char;
|
||||||
|
const uint8_t *data;
|
||||||
|
int offset_x;
|
||||||
|
int offset_y;
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Glyph {
|
||||||
|
public:
|
||||||
|
Glyph(const GlyphData *data) : glyph_data_(data) {}
|
||||||
|
|
||||||
|
bool get_pixel(int x, int y) const;
|
||||||
|
|
||||||
|
const char *get_char() const;
|
||||||
|
|
||||||
|
bool compare_to(const char *str) const;
|
||||||
|
|
||||||
|
int match_length(const char *str) const;
|
||||||
|
|
||||||
|
void scan_area(int *x1, int *y1, int *width, int *height) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
friend Font;
|
||||||
|
friend DisplayBuffer;
|
||||||
|
|
||||||
|
const GlyphData *glyph_data_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Font {
|
||||||
|
public:
|
||||||
|
/** Construct the font with the given glyphs.
|
||||||
|
*
|
||||||
|
* @param glyphs A vector of glyphs, must be sorted lexicographically.
|
||||||
|
* @param baseline The y-offset from the top of the text to the baseline.
|
||||||
|
* @param bottom The y-offset from the top of the text to the bottom (i.e. height).
|
||||||
|
*/
|
||||||
|
Font(const GlyphData *data, int data_nr, int baseline, int height);
|
||||||
|
|
||||||
|
int match_next_glyph(const char *str, int *match_length);
|
||||||
|
|
||||||
|
void measure(const char *str, int *width, int *x_offset, int *baseline, int *height);
|
||||||
|
inline int get_baseline() { return this->baseline_; }
|
||||||
|
inline int get_height() { return this->height_; }
|
||||||
|
|
||||||
|
const std::vector<Glyph, ExternalRAMAllocator<Glyph>> &get_glyphs() const { return glyphs_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::vector<Glyph, ExternalRAMAllocator<Glyph>> glyphs_;
|
||||||
|
int baseline_;
|
||||||
|
int height_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace display
|
||||||
|
} // namespace esphome
|
135
esphome/components/display/image.cpp
Normal file
135
esphome/components/display/image.cpp
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
#include "image.h"
|
||||||
|
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
#include "display_buffer.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace display {
|
||||||
|
|
||||||
|
void Image::draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) {
|
||||||
|
switch (type_) {
|
||||||
|
case IMAGE_TYPE_BINARY: {
|
||||||
|
for (int img_x = 0; img_x < width_; img_x++) {
|
||||||
|
for (int img_y = 0; img_y < height_; img_y++) {
|
||||||
|
if (this->get_binary_pixel_(img_x, img_y)) {
|
||||||
|
display->draw_pixel_at(x + img_x, y + img_y, color_on);
|
||||||
|
} else if (!this->transparent_) {
|
||||||
|
display->draw_pixel_at(x + img_x, y + img_y, color_off);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IMAGE_TYPE_GRAYSCALE:
|
||||||
|
for (int img_x = 0; img_x < width_; img_x++) {
|
||||||
|
for (int img_y = 0; img_y < height_; img_y++) {
|
||||||
|
auto color = this->get_grayscale_pixel_(img_x, img_y);
|
||||||
|
if (color.w >= 0x80) {
|
||||||
|
display->draw_pixel_at(x + img_x, y + img_y, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case IMAGE_TYPE_RGB565:
|
||||||
|
for (int img_x = 0; img_x < width_; img_x++) {
|
||||||
|
for (int img_y = 0; img_y < height_; img_y++) {
|
||||||
|
auto color = this->get_rgb565_pixel_(img_x, img_y);
|
||||||
|
if (color.w >= 0x80) {
|
||||||
|
display->draw_pixel_at(x + img_x, y + img_y, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case IMAGE_TYPE_RGB24:
|
||||||
|
for (int img_x = 0; img_x < width_; img_x++) {
|
||||||
|
for (int img_y = 0; img_y < height_; img_y++) {
|
||||||
|
auto color = this->get_rgb24_pixel_(img_x, img_y);
|
||||||
|
if (color.w >= 0x80) {
|
||||||
|
display->draw_pixel_at(x + img_x, y + img_y, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case IMAGE_TYPE_RGBA:
|
||||||
|
for (int img_x = 0; img_x < width_; img_x++) {
|
||||||
|
for (int img_y = 0; img_y < height_; img_y++) {
|
||||||
|
auto color = this->get_rgba_pixel_(img_x, img_y);
|
||||||
|
if (color.w >= 0x80) {
|
||||||
|
display->draw_pixel_at(x + img_x, y + img_y, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Color Image::get_pixel(int x, int y, Color color_on, Color color_off) const {
|
||||||
|
if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_)
|
||||||
|
return color_off;
|
||||||
|
switch (this->type_) {
|
||||||
|
case IMAGE_TYPE_BINARY:
|
||||||
|
return this->get_binary_pixel_(x, y) ? color_on : color_off;
|
||||||
|
case IMAGE_TYPE_GRAYSCALE:
|
||||||
|
return this->get_grayscale_pixel_(x, y);
|
||||||
|
case IMAGE_TYPE_RGB565:
|
||||||
|
return this->get_rgb565_pixel_(x, y);
|
||||||
|
case IMAGE_TYPE_RGB24:
|
||||||
|
return this->get_rgb24_pixel_(x, y);
|
||||||
|
case IMAGE_TYPE_RGBA:
|
||||||
|
return this->get_rgba_pixel_(x, y);
|
||||||
|
default:
|
||||||
|
return color_off;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool Image::get_binary_pixel_(int x, int y) const {
|
||||||
|
const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u;
|
||||||
|
const uint32_t pos = x + y * width_8;
|
||||||
|
return progmem_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u));
|
||||||
|
}
|
||||||
|
Color Image::get_rgba_pixel_(int x, int y) const {
|
||||||
|
const uint32_t pos = (x + y * this->width_) * 4;
|
||||||
|
return Color(progmem_read_byte(this->data_start_ + pos + 0), progmem_read_byte(this->data_start_ + pos + 1),
|
||||||
|
progmem_read_byte(this->data_start_ + pos + 2), progmem_read_byte(this->data_start_ + pos + 3));
|
||||||
|
}
|
||||||
|
Color Image::get_rgb24_pixel_(int x, int y) const {
|
||||||
|
const uint32_t pos = (x + y * this->width_) * 3;
|
||||||
|
Color color = Color(progmem_read_byte(this->data_start_ + pos + 0), progmem_read_byte(this->data_start_ + pos + 1),
|
||||||
|
progmem_read_byte(this->data_start_ + pos + 2));
|
||||||
|
if (color.b == 1 && color.r == 0 && color.g == 0 && transparent_) {
|
||||||
|
// (0, 0, 1) has been defined as transparent color for non-alpha images.
|
||||||
|
// putting blue == 1 as a first condition for performance reasons (least likely value to short-cut the if)
|
||||||
|
color.w = 0;
|
||||||
|
} else {
|
||||||
|
color.w = 0xFF;
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
Color Image::get_rgb565_pixel_(int x, int y) const {
|
||||||
|
const uint32_t pos = (x + y * this->width_) * 2;
|
||||||
|
uint16_t rgb565 =
|
||||||
|
progmem_read_byte(this->data_start_ + pos + 0) << 8 | progmem_read_byte(this->data_start_ + pos + 1);
|
||||||
|
auto r = (rgb565 & 0xF800) >> 11;
|
||||||
|
auto g = (rgb565 & 0x07E0) >> 5;
|
||||||
|
auto b = rgb565 & 0x001F;
|
||||||
|
Color color = Color((r << 3) | (r >> 2), (g << 2) | (g >> 4), (b << 3) | (b >> 2));
|
||||||
|
if (rgb565 == 0x0020 && transparent_) {
|
||||||
|
// darkest green has been defined as transparent color for transparent RGB565 images.
|
||||||
|
color.w = 0;
|
||||||
|
} else {
|
||||||
|
color.w = 0xFF;
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
Color Image::get_grayscale_pixel_(int x, int y) const {
|
||||||
|
const uint32_t pos = (x + y * this->width_);
|
||||||
|
const uint8_t gray = progmem_read_byte(this->data_start_ + pos);
|
||||||
|
uint8_t alpha = (gray == 1 && transparent_) ? 0 : 0xFF;
|
||||||
|
return Color(gray, gray, gray, alpha);
|
||||||
|
}
|
||||||
|
int Image::get_width() const { return this->width_; }
|
||||||
|
int Image::get_height() const { return this->height_; }
|
||||||
|
ImageType Image::get_type() const { return this->type_; }
|
||||||
|
Image::Image(const uint8_t *data_start, int width, int height, ImageType type)
|
||||||
|
: width_(width), height_(height), type_(type), data_start_(data_start) {}
|
||||||
|
|
||||||
|
} // namespace display
|
||||||
|
} // namespace esphome
|
75
esphome/components/display/image.h
Normal file
75
esphome/components/display/image.h
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
#pragma once
|
||||||
|
#include "esphome/core/color.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace display {
|
||||||
|
|
||||||
|
enum ImageType {
|
||||||
|
IMAGE_TYPE_BINARY = 0,
|
||||||
|
IMAGE_TYPE_GRAYSCALE = 1,
|
||||||
|
IMAGE_TYPE_RGB24 = 2,
|
||||||
|
IMAGE_TYPE_RGB565 = 3,
|
||||||
|
IMAGE_TYPE_RGBA = 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
inline int image_type_to_bpp(ImageType type) {
|
||||||
|
switch (type) {
|
||||||
|
case IMAGE_TYPE_BINARY:
|
||||||
|
return 1;
|
||||||
|
case IMAGE_TYPE_GRAYSCALE:
|
||||||
|
return 8;
|
||||||
|
case IMAGE_TYPE_RGB565:
|
||||||
|
return 16;
|
||||||
|
case IMAGE_TYPE_RGB24:
|
||||||
|
return 24;
|
||||||
|
case IMAGE_TYPE_RGBA:
|
||||||
|
return 32;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int image_type_to_width_stride(int width, ImageType type) { return (width * image_type_to_bpp(type) + 7u) / 8u; }
|
||||||
|
|
||||||
|
/// Turn the pixel OFF.
|
||||||
|
extern const Color COLOR_OFF;
|
||||||
|
/// Turn the pixel ON.
|
||||||
|
extern const Color COLOR_ON;
|
||||||
|
|
||||||
|
class DisplayBuffer;
|
||||||
|
|
||||||
|
class BaseImage {
|
||||||
|
public:
|
||||||
|
virtual void draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) = 0;
|
||||||
|
virtual int get_width() const = 0;
|
||||||
|
virtual int get_height() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Image : public BaseImage {
|
||||||
|
public:
|
||||||
|
Image(const uint8_t *data_start, int width, int height, ImageType type);
|
||||||
|
Color get_pixel(int x, int y, Color color_on = COLOR_ON, Color color_off = COLOR_OFF) const;
|
||||||
|
int get_width() const override;
|
||||||
|
int get_height() const override;
|
||||||
|
ImageType get_type() const;
|
||||||
|
|
||||||
|
void draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) override;
|
||||||
|
|
||||||
|
void set_transparency(bool transparent) { transparent_ = transparent; }
|
||||||
|
bool has_transparency() const { return transparent_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool get_binary_pixel_(int x, int y) const;
|
||||||
|
Color get_rgb24_pixel_(int x, int y) const;
|
||||||
|
Color get_rgba_pixel_(int x, int y) const;
|
||||||
|
Color get_rgb565_pixel_(int x, int y) const;
|
||||||
|
Color get_grayscale_pixel_(int x, int y) const;
|
||||||
|
|
||||||
|
int width_;
|
||||||
|
int height_;
|
||||||
|
ImageType type_;
|
||||||
|
const uint8_t *data_start_;
|
||||||
|
bool transparent_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace display
|
||||||
|
} // namespace esphome
|
|
@ -695,6 +695,13 @@ image:
|
||||||
file: mdi:alert-outline
|
file: mdi:alert-outline
|
||||||
type: BINARY
|
type: BINARY
|
||||||
|
|
||||||
|
graph:
|
||||||
|
- id: my_graph
|
||||||
|
sensor: ha_hello_world_temperature
|
||||||
|
duration: 1h
|
||||||
|
width: 100
|
||||||
|
height: 100
|
||||||
|
|
||||||
cap1188:
|
cap1188:
|
||||||
id: cap1188_component
|
id: cap1188_component
|
||||||
address: 0x29
|
address: 0x29
|
||||||
|
|
Loading…
Reference in a new issue