Tasmota/lib/lib_display/UDisplay/uDisplay_graphics.cpp
2025-10-12 17:15:02 +02:00

565 lines
14 KiB
C++

#include "uDisplay.h"
#include "uDisplay_config.h"
#include "uDisplay_spi.h"
// ===== Basic Drawing Primitives =====
static constexpr uint16_t RGB16_TO_MONO = 0x8410;
static constexpr uint16_t RGB16_SWAP_TO_MONO = 0x1084;
#define renderer_swap(a, b) { int16_t t = a; a = b; b = t; }
void uDisplay::drawPixel(int16_t x, int16_t y, uint16_t color) {
#ifdef USE_ESP32_S3
if (interface == _UDSP_RGB) {
drawPixel_RGB(x, y, color);
return;
}
#endif
if (ep_mode) {
drawPixel_EPD(x, y, color);
return;
}
if (framebuffer) {
Renderer::drawPixel(x, y, color);
return;
}
if ((x < 0) || (x >= _width) || (y < 0) || (y >= _height)) return;
SPI_BEGIN_TRANSACTION
SPI_CS_LOW
setAddrWindow_int(x, y, 1, 1);
WriteColor(color);
SPI_CS_HIGH
SPI_END_TRANSACTION
}
void uDisplay::drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) {
if (ep_mode) {
drawFastHLine_EPD(x, y, w, color);
return;
}
if (framebuffer) {
Renderer::drawFastHLine(x, y, w, color);
return;
}
// Rudimentary clipping
if((x >= _width) || (y >= _height)) return;
if((x + w - 1) >= _width) w = _width - x;
#ifdef USE_ESP32_S3
if (interface == _UDSP_RGB) {
if (cur_rot > 0) {
while (w--) {
drawPixel_RGB(x , y , color);
x++;
}
} else {
uint16_t *fb = rgb_fb;
fb += (int32_t)y * _width;
fb += x;
while (w--) {
*fb = color;
Cache_WriteBack_Addr((uint32_t)fb, 2);
fb++;
x++;
}
}
return;
}
#endif
SPI_BEGIN_TRANSACTION
SPI_CS_LOW
setAddrWindow_int(x, y, w, 1);
if (col_mode == 18) {
uint8_t r = (color & 0xF800) >> 11;
uint8_t g = (color & 0x07E0) >> 5;
uint8_t b = color & 0x001F;
r = (r * 255) / 31;
g = (g * 255) / 63;
b = (b * 255) / 31;
while (w--) {
ulcd_data8(r);
ulcd_data8(g);
ulcd_data8(b);
}
} else {
while (w--) {
WriteColor(color);
}
}
SPI_CS_HIGH
SPI_END_TRANSACTION
}
void uDisplay::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) {
if (ep_mode) {
drawFastVLine_EPD(x, y, h, color);
return;
}
if (framebuffer) {
Renderer::drawFastVLine(x, y, h, color);
return;
}
// Rudimentary clipping
if ((x >= _width) || (y >= _height)) return;
if ((y + h - 1) >= _height) h = _height - y;
#ifdef USE_ESP32_S3
if (interface == _UDSP_RGB) {
if (cur_rot > 0) {
while (h--) {
drawPixel_RGB(x , y , color);
y++;
}
} else {
uint16_t *fb = rgb_fb;
fb += (int32_t)y * _width;
fb += x;
while (h--) {
*fb = color;
Cache_WriteBack_Addr((uint32_t)fb, 2);
fb+=_width;
y++;
}
}
return;
}
#endif
SPI_BEGIN_TRANSACTION
SPI_CS_LOW
setAddrWindow_int(x, y, 1, h);
if (col_mode == 18) {
uint8_t r = (color & 0xF800) >> 11;
uint8_t g = (color & 0x07E0) >> 5;
uint8_t b = color & 0x001F;
r = (r * 255) / 31;
g = (g * 255) / 63;
b = (b * 255) / 31;
while (h--) {
ulcd_data8(r);
ulcd_data8(g);
ulcd_data8(b);
}
} else {
while (h--) {
WriteColor(color);
}
}
SPI_CS_HIGH
SPI_END_TRANSACTION
}
void uDisplay::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) {
#ifdef USE_ESP32_S3
if (interface == _UDSP_RGB) {
for (uint32_t yp = y; yp < y + h; yp++) {
drawFastHLine(x, yp, w, color);
}
return;
}
#endif
if (ep_mode) {
fillRect_EPD(x, y, w, h, color);
return;
}
if (framebuffer) {
Renderer::fillRect(x, y, w, h, color);
return;
}
if((x >= _width) || (y >= _height)) return;
if((x + w - 1) >= _width) w = _width - x;
if((y + h - 1) >= _height) h = _height - y;
SPI_BEGIN_TRANSACTION
SPI_CS_LOW
setAddrWindow_int(x, y, w, h);
if (col_mode == 18) {
uint8_t r = (color & 0xF800) >> 11;
uint8_t g = (color & 0x07E0) >> 5;
uint8_t b = color & 0x001F;
r = (r * 255) / 31;
g = (g * 255) / 63;
b = (b * 255) / 31;
for (y = h; y > 0; y--) {
for (x = w; x > 0; x--) {
ulcd_data8(r);
ulcd_data8(g);
ulcd_data8(b);
}
}
} else {
for (y = h; y > 0; y--) {
for (x = w; x > 0; x--) {
WriteColor(color);
}
}
}
SPI_CS_HIGH
SPI_END_TRANSACTION
}
void uDisplay::fillScreen(uint16_t color) {
fillRect(0, 0, width(), height(), color);
}
static inline void lvgl_color_swap(uint16_t *data, uint16_t len) { for (uint32_t i = 0; i < len; i++) (data[i] = data[i] << 8 | data[i] >> 8); }
void uDisplay::pushColors(uint16_t *data, uint16_t len, boolean not_swapped) {
if (lvgl_param.swap_color) {
not_swapped = !not_swapped;
}
//Serial.printf("push %x - %d - %d - %d\n", (uint32_t)data, len, not_swapped, lvgl_param.data);
// Isolating _UDSP_RGB to increase code sharing
//
// Use ESP-IDF LCD driver to push colors and rely on the following assumptions:
// * bytes swapping is already handled in the driver configuration (see uDisplay::Init()),
// * pushColors() is only called with not_swapped equals true,
// * cache flushing is done by the LCD driver.
if (interface == _UDSP_RGB) {
#ifdef USE_ESP32_S3
if (!not_swapped) {
// internal error -> write error message but continue (with possibly wrong colors)
AddLog(LOG_LEVEL_ERROR, PSTR("DSP: Unexpected byte-swapping requested in pushColors()"));
}
// check that bytes count matches the size of area, and remove from inner loop
if ((seta_yp2 - seta_yp1) * (seta_xp2 - seta_xp2) > len) { return; }
esp_lcd_panel_draw_bitmap(_panel_handle, seta_xp1, seta_yp1, seta_xp2, seta_yp2, (void *)data);
#endif
return;
}
if (not_swapped == false) {
// called from LVGL bytes are swapped
if (bpp != 16) {
// lvgl_color_swap(data, len); -- no need to swap anymore, we have inverted the mask
pushColorsMono(data, len, true);
return;
}
if ( (col_mode != 18) && (spi_dc >= 0) && (spi_nr <= 2) ) {
// special version 8 bit spi I or II
#ifdef ESP8266
lvgl_color_swap(data, len);
while (len--) {
uspi->write(*data++);
}
#else
if (lvgl_param.use_dma) {
pushPixelsDMA(data, len );
} else {
uspi->writeBytes((uint8_t*)data, len * 2);
}
#endif
} else {
#ifdef ESP32
if ( (col_mode == 18) && (spi_dc >= 0) && (spi_nr <= 2) ) {
uint8_t *line = (uint8_t*)malloc(len * 3);
uint8_t *lp = line;
if (line) {
uint16_t color;
for (uint32_t cnt = 0; cnt < len; cnt++) {
color = *data++;
color = (color << 8) | (color >> 8);
uint8_t r = (color & 0xF800) >> 11;
uint8_t g = (color & 0x07E0) >> 5;
uint8_t b = color & 0x001F;
r = (r * 255) / 31;
g = (g * 255) / 63;
b = (b * 255) / 31;
*lp++ = r;
*lp++ = g;
*lp++ = b;
}
if (lvgl_param.use_dma) {
pushPixels3DMA(line, len );
} else {
uspi->writeBytes(line, len * 3);
}
free(line);
}
} else {
// 9 bit and others
if (interface == _UDSP_PAR8 || interface == _UDSP_PAR16) {
#ifdef USE_ESP32_S3
pb_pushPixels(data, len, true, false);
#endif // USE_ESP32_S3
} else {
lvgl_color_swap(data, len);
while (len--) {
WriteColor(*data++);
}
}
}
#endif // ESP32
#ifdef ESP8266
lvgl_color_swap(data, len);
while (len--) {
WriteColor(*data++);
}
#endif
}
} else {
// called from displaytext, no byte swap, currently no dma here
if (bpp != 16) {
pushColorsMono(data, len);
return;
}
if ( (col_mode != 18) && (spi_dc >= 0) && (spi_nr <= 2) ) {
// special version 8 bit spi I or II
#ifdef ESP8266
while (len--) {
//uspi->write(*data++);
WriteColor(*data++);
}
#else
uspi->writePixels(data, len * 2);
#endif
} else {
// 9 bit and others
if (interface == _UDSP_PAR8 || interface == _UDSP_PAR16) {
#ifdef USE_ESP32_S3
pb_pushPixels(data, len, false, false);
#endif // USE_ESP32_S3
} else {
while (len--) {
WriteColor(*data++);
}
}
}
}
}
// convert to mono, these are framebuffer based
void uDisplay::pushColorsMono(uint16_t *data, uint16_t len, bool rgb16_swap) {
// pixel is white if at least one of the 3 components is above 50%
// this is tested with a simple mask, swapped if needed
uint16_t rgb16_to_mono_mask = rgb16_swap ? RGB16_SWAP_TO_MONO : RGB16_TO_MONO;
for (uint32_t y = seta_yp1; y < seta_yp2; y++) {
seta_yp1++;
if (lvgl_param.invert_bw) {
for (uint32_t x = seta_xp1; x < seta_xp2; x++) {
uint16_t color = *data++;
if (bpp == 1) color = (color & rgb16_to_mono_mask) ? 0 : 1;
drawPixel(x, y, color); // todo - inline the method to save speed
len--;
if (!len) return; // failsafe - exist if len (pixel number) is exhausted
}
} else {
for (uint32_t x = seta_xp1; x < seta_xp2; x++) {
uint16_t color = *data++;
if (bpp == 1) color = (color & rgb16_to_mono_mask) ? 1 : 0;
drawPixel(x, y, color); // todo - inline the method to save speed
len--;
if (!len) return; // failsafe - exist if len (pixel number) is exhausted
}
}
}
}
void uDisplay::setAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
if (bpp != 16 || interface == _UDSP_RGB) {
// Just save params or update frame
if (!x0 && !y0 && !x1 && !y1) {
if (!ep_mode) {
Updateframe();
}
} else {
seta_xp1 = x0;
seta_xp2 = x1;
seta_yp1 = y0;
seta_yp2 = y1;
}
return;
}
if (interface == _UDSP_RGB) {
return;
}
if (!x0 && !y0 && !x1 && !y1) {
SPI_CS_HIGH
SPI_END_TRANSACTION
} else {
SPI_BEGIN_TRANSACTION
SPI_CS_LOW
setAddrWindow_int(x0, y0, x1 - x0, y1 - y0);
}
}
void uDisplay::setAddrWindow_int(uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
if (interface == _UDSP_RGB) {
return;
}
x += x_addr_offs[cur_rot];
y += y_addr_offs[cur_rot];
if (sa_mode != 8) {
uint32_t xa = ((uint32_t)x << 16) | (x + w - 1);
uint32_t ya = ((uint32_t)y << 16) | (y + h - 1);
ulcd_command(saw_1);
ulcd_data32(xa);
ulcd_command(saw_2);
ulcd_data32(ya);
if (saw_3 != 0xff) {
ulcd_command(saw_3); // write to RAM
}
} else {
uint16_t x2 = x + w - 1,
y2 = y + h - 1;
if (cur_rot & 1) { // Vertical address increment mode
renderer_swap(x,y);
renderer_swap(x2,y2);
}
ulcd_command(saw_1);
if (allcmd_mode) {
ulcd_data8(x);
ulcd_data8(x2);
} else {
ulcd_command(x);
ulcd_command(x2);
}
ulcd_command(saw_2);
if (allcmd_mode) {
ulcd_data8(y);
ulcd_data8(y2);
} else {
ulcd_command(y);
ulcd_command(y2);
}
if (saw_3 != 0xff) {
ulcd_command(saw_3); // write to RAM
}
}
}
void uDisplay::setRotation(uint8_t rotation) {
cur_rot = rotation;
if (framebuffer) {
Renderer::setRotation(cur_rot);
return;
}
if (interface == _UDSP_SPI || interface == _UDSP_PAR8 || interface == _UDSP_PAR16) {
if (ep_mode) {
Renderer::setRotation(cur_rot);
return;
}
SPI_BEGIN_TRANSACTION
SPI_CS_LOW
ulcd_command(madctrl);
if (!allcmd_mode) {
ulcd_data8(rot[cur_rot]);
} else {
ulcd_command(rot[cur_rot]);
}
if ((sa_mode == 8) && !allcmd_mode) {
ulcd_command(startline);
ulcd_data8((cur_rot < 2) ? height() : 0);
}
SPI_CS_HIGH
SPI_END_TRANSACTION
}
switch (rotation) {
case 0:
_width = gxs;
_height = gys;
break;
case 1:
_width = gys;
_height = gxs;
break;
case 2:
_width = gxs;
_height = gys;
break;
case 3:
_width = gys;
_height = gxs;
break;
}
#ifdef USE_ESP32_S3
if (interface == _UDSP_RGB) {
// Utilize the ESP-IDF LCD driver's support for display rotation
esp_lcd_panel_mirror(_panel_handle, rotation == 1 || rotation == 2, rotation & 2);
esp_lcd_panel_swap_xy(_panel_handle, rotation & 1);
}
#endif
}
#ifdef USE_ESP32_S3
void uDisplay::drawPixel_RGB(int16_t x, int16_t y, uint16_t color) {
int16_t w = _width, h = _height;
if ((x < 0) || (x >= w) || (y < 0) || (y >= h)) {
return;
}
// check rotation, move pixel around if necessary
switch (cur_rot) {
case 1:
renderer_swap(w, h);
renderer_swap(x, y);
x = w - x - 1;
break;
case 2:
x = w - x - 1;
y = h - y - 1;
break;
case 3:
renderer_swap(w, h);
renderer_swap(x, y);
y = h - y - 1;
break;
}
uint16_t *fb = rgb_fb;
fb += (int32_t)y * w;
fb += x;
*fb = color;
Cache_WriteBack_Addr((uint32_t)fb, 2);
}
#endif