BaseUI: Color Support for TFT Nodes (#10233)
* True Colors on TFT (Heltec Mesh Node T114, Heltec Vision Master T190, CardPuter Adv, T-Deck, T-Lora Pager) * Theme support - New and some Classic Themes! * Colored Compass --------- Co-authored-by: Jason P <applewiz@mac.com> Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz> Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
This commit is contained in:
co-authored by
Jason P
Jonathan Bennett
Ben Meadors
parent
4d4e14600c
commit
8dde4eeee1
54 files changed
+2536
-674
No files matched your search
+142
-43
@@ -16,12 +16,6 @@
|
||||
extern SX1509 gpioExtender;
|
||||
#endif
|
||||
|
||||
#ifdef TFT_MESH_OVERRIDE
|
||||
uint16_t TFT_MESH = TFT_MESH_OVERRIDE;
|
||||
#else
|
||||
uint16_t TFT_MESH = COLOR565(0x67, 0xEA, 0x94);
|
||||
#endif
|
||||
|
||||
#if defined(ST7735S)
|
||||
#include <LovyanGFX.hpp> // Graphics and font library for ST7735 driver chip
|
||||
|
||||
@@ -1140,7 +1134,9 @@ static LGFX *tft = nullptr;
|
||||
#endif
|
||||
|
||||
#include "SPILock.h"
|
||||
#include "TFTColorRegions.h"
|
||||
#include "TFTDisplay.h"
|
||||
#include "TFTPalette.h"
|
||||
#include <SPI.h>
|
||||
|
||||
#ifdef UNPHONE
|
||||
@@ -1150,6 +1146,25 @@ extern unPhone unphone;
|
||||
|
||||
GpioPin *TFTDisplay::backlightEnable = NULL;
|
||||
|
||||
namespace
|
||||
{
|
||||
static constexpr uint8_t kFullRepaintChunkRows = 8;
|
||||
|
||||
static inline uint16_t getThemeDefaultOnColor()
|
||||
{
|
||||
return graphics::TFTPalette::White;
|
||||
}
|
||||
|
||||
static inline uint16_t getThemeDefaultOffColor()
|
||||
{
|
||||
#if GRAPHICS_TFT_COLORING_ENABLED
|
||||
return graphics::getThemeBodyBg();
|
||||
#else
|
||||
return TFT_BLACK;
|
||||
#endif
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TFTDisplay::TFTDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus)
|
||||
{
|
||||
LOG_DEBUG("TFTDisplay!");
|
||||
@@ -1189,14 +1204,15 @@ TFTDisplay::~TFTDisplay()
|
||||
free(linePixelBuffer);
|
||||
linePixelBuffer = nullptr;
|
||||
}
|
||||
if (repaintChunkBuffer != nullptr) {
|
||||
free(repaintChunkBuffer);
|
||||
repaintChunkBuffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Write the buffer to the display memory
|
||||
void TFTDisplay::display(bool fromBlank)
|
||||
{
|
||||
if (fromBlank)
|
||||
tft->fillScreen(TFT_BLACK);
|
||||
|
||||
concurrency::LockGuard g(spiLock);
|
||||
|
||||
uint32_t x, y;
|
||||
@@ -1205,12 +1221,70 @@ void TFTDisplay::display(bool fromBlank)
|
||||
uint32_t x_FirstPixelUpdate;
|
||||
uint32_t x_LastPixelUpdate;
|
||||
bool isset, dblbuf_isset;
|
||||
uint16_t colorTftMesh, colorTftBlack;
|
||||
uint16_t colorTftWhite, colorTftBlack;
|
||||
bool somethingChanged = false;
|
||||
|
||||
// Store colors byte-reversed so that TFT_eSPI doesn't have to swap bytes in a separate step
|
||||
colorTftMesh = __builtin_bswap16(TFT_MESH);
|
||||
colorTftBlack = __builtin_bswap16(TFT_BLACK);
|
||||
// Theme defaults for non-role pixels.
|
||||
const uint16_t defaultOnColor = getThemeDefaultOnColor();
|
||||
const uint16_t defaultOffColor = getThemeDefaultOffColor();
|
||||
static uint16_t lastDefaultOnColor = 0;
|
||||
static uint16_t lastDefaultOffColor = 0;
|
||||
static bool haveLastDefaults = false;
|
||||
const bool themeDefaultsChanged =
|
||||
!haveLastDefaults || (defaultOnColor != lastDefaultOnColor) || (defaultOffColor != lastDefaultOffColor);
|
||||
const bool forceFullRepaint = fromBlank || themeDefaultsChanged;
|
||||
|
||||
// If theme defaults changed, reset panel background immediately so stale pixels don't linger.
|
||||
if (forceFullRepaint) {
|
||||
tft->fillScreen(defaultOffColor);
|
||||
}
|
||||
|
||||
colorTftWhite = (defaultOnColor >> 8) | ((defaultOnColor & 0xFF) << 8);
|
||||
colorTftBlack = (defaultOffColor >> 8) | ((defaultOffColor & 0xFF) << 8);
|
||||
|
||||
#if GRAPHICS_TFT_COLORING_ENABLED
|
||||
static uint32_t lastColorFrameSignature = 0;
|
||||
const bool hasColorRegions = graphics::getTFTColorRegionCount() > 0;
|
||||
const uint32_t colorFrameSignature = graphics::getTFTColorFrameSignature();
|
||||
const bool forceFullColorRepaint = forceFullRepaint || (colorFrameSignature != lastColorFrameSignature);
|
||||
|
||||
// When region roles/layout changed, color can differ even with identical monochrome glyph bits.
|
||||
// Repaint full frame only for those frames, then return to diff-based updates.
|
||||
if (forceFullColorRepaint) {
|
||||
for (uint32_t yStart = 0; yStart < displayHeight; yStart += kFullRepaintChunkRows) {
|
||||
const uint32_t rowsThisChunk = min<uint32_t>(kFullRepaintChunkRows, displayHeight - yStart);
|
||||
for (uint32_t row = 0; row < rowsThisChunk; row++) {
|
||||
y = yStart + row;
|
||||
y_byteIndex = (y / 8) * displayWidth;
|
||||
y_byteMask = (1 << (y & 7));
|
||||
|
||||
uint16_t *chunkRow = repaintChunkBuffer + (row * displayWidth);
|
||||
for (x = 0; x < displayWidth; x++) {
|
||||
isset = (buffer[x + y_byteIndex] & y_byteMask) != 0;
|
||||
if (hasColorRegions) {
|
||||
chunkRow[x] = graphics::resolveTFTColorPixel(static_cast<int16_t>(x), static_cast<int16_t>(y), isset,
|
||||
colorTftWhite, colorTftBlack);
|
||||
} else {
|
||||
chunkRow[x] = isset ? colorTftWhite : colorTftBlack;
|
||||
}
|
||||
}
|
||||
}
|
||||
#if defined(HACKADAY_COMMUNICATOR)
|
||||
tft->draw16bitBeRGBBitmap(0, yStart, repaintChunkBuffer, displayWidth, rowsThisChunk);
|
||||
#else
|
||||
tft->pushImage(0, yStart, displayWidth, rowsThisChunk, repaintChunkBuffer);
|
||||
#endif
|
||||
}
|
||||
|
||||
memcpy(buffer_back, buffer, displayBufferSize);
|
||||
lastColorFrameSignature = colorFrameSignature;
|
||||
haveLastDefaults = true;
|
||||
lastDefaultOnColor = defaultOnColor;
|
||||
lastDefaultOffColor = defaultOffColor;
|
||||
graphics::clearTFTColorRegions();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
y = 0;
|
||||
while (y < displayHeight) {
|
||||
@@ -1219,7 +1293,7 @@ void TFTDisplay::display(bool fromBlank)
|
||||
|
||||
// Step 1: Do a quick scan of 8 rows together. This allows fast-forwarding over unchanged screen areas.
|
||||
if (y_byteMask == 1) {
|
||||
if (!fromBlank) {
|
||||
if (!forceFullRepaint) {
|
||||
for (x = 0; x < displayWidth; x++) {
|
||||
if (buffer[x + y_byteIndex] != buffer_back[x + y_byteIndex])
|
||||
break;
|
||||
@@ -1237,13 +1311,14 @@ void TFTDisplay::display(bool fromBlank)
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Scan each of the 8 rows individually. Find the first pixel in each row that needs updating
|
||||
for (x_FirstPixelUpdate = 0; x_FirstPixelUpdate < displayWidth; x_FirstPixelUpdate++) {
|
||||
isset = buffer[x_FirstPixelUpdate + y_byteIndex] & y_byteMask;
|
||||
// Step 2: Scan this row for changed span (first and last changed pixel).
|
||||
uint32_t x_FirstChanged = 0;
|
||||
for (x_FirstChanged = 0; x_FirstChanged < displayWidth; x_FirstChanged++) {
|
||||
isset = buffer[x_FirstChanged + y_byteIndex] & y_byteMask;
|
||||
|
||||
if (!fromBlank) {
|
||||
if (!forceFullRepaint) {
|
||||
// get src pixel in the page based ordering the OLED lib uses
|
||||
dblbuf_isset = buffer_back[x_FirstPixelUpdate + y_byteIndex] & y_byteMask;
|
||||
dblbuf_isset = buffer_back[x_FirstChanged + y_byteIndex] & y_byteMask;
|
||||
if (isset != dblbuf_isset) {
|
||||
break;
|
||||
}
|
||||
@@ -1253,43 +1328,51 @@ void TFTDisplay::display(bool fromBlank)
|
||||
}
|
||||
|
||||
// Did we find a pixel that needs updating on this row?
|
||||
if (x_FirstPixelUpdate < displayWidth) {
|
||||
// Align the first pixel for update to an even number so the total alignment of
|
||||
// the data will be at 32-bit boundary, which is required by GDMA SPI transfers.
|
||||
x_FirstPixelUpdate &= ~1;
|
||||
|
||||
// Step 3a: copy rest of the pixels in this row into the pixel line buffer,
|
||||
// while also recording the last pixel in the row that needs updating.
|
||||
// Since the first changed pixel will be looked up, the x_LastPixelUpdate will be set.
|
||||
for (x = x_FirstPixelUpdate; x < displayWidth; x++) {
|
||||
isset = buffer[x + y_byteIndex] & y_byteMask;
|
||||
linePixelBuffer[x] = isset ? colorTftMesh : colorTftBlack;
|
||||
|
||||
if (!fromBlank) {
|
||||
dblbuf_isset = buffer_back[x + y_byteIndex] & y_byteMask;
|
||||
if (x_FirstChanged < displayWidth) {
|
||||
uint32_t x_LastChanged = displayWidth - 1;
|
||||
while (x_LastChanged > x_FirstChanged) {
|
||||
isset = buffer[x_LastChanged + y_byteIndex] & y_byteMask;
|
||||
if (!forceFullRepaint) {
|
||||
dblbuf_isset = buffer_back[x_LastChanged + y_byteIndex] & y_byteMask;
|
||||
if (isset != dblbuf_isset) {
|
||||
x_LastPixelUpdate = x;
|
||||
break;
|
||||
}
|
||||
} else if (isset) {
|
||||
x_LastPixelUpdate = x;
|
||||
break;
|
||||
}
|
||||
x_LastChanged--;
|
||||
}
|
||||
// Step 3b: Round up the last pixel to odd number to maintain 32-bit alignment for SPIs.
|
||||
// Most displays will have even number of pixels in a row -- this will be in bounds
|
||||
// of the displayWidth. (Hopefully odd displays will just ignore that extra pixel.)
|
||||
x_LastPixelUpdate |= 1;
|
||||
// Ensure the last pixel index does not exceed the display width.
|
||||
|
||||
// Align the first pixel for update to an even number so the total alignment of
|
||||
// the data will be at 32-bit boundary, which is required by GDMA SPI transfers.
|
||||
x_FirstPixelUpdate = x_FirstChanged & ~1U;
|
||||
x_LastPixelUpdate = x_LastChanged | 1U;
|
||||
if (x_LastPixelUpdate >= displayWidth) {
|
||||
x_LastPixelUpdate = displayWidth - 1;
|
||||
}
|
||||
|
||||
// Step 3: Copy only the changed span into the pixel line buffer.
|
||||
for (x = x_FirstPixelUpdate; x <= x_LastPixelUpdate; x++) {
|
||||
isset = buffer[x + y_byteIndex] & y_byteMask;
|
||||
#if GRAPHICS_TFT_COLORING_ENABLED
|
||||
if (hasColorRegions) {
|
||||
linePixelBuffer[x] = graphics::resolveTFTColorPixel(static_cast<int16_t>(x), static_cast<int16_t>(y), isset,
|
||||
colorTftWhite, colorTftBlack);
|
||||
} else {
|
||||
linePixelBuffer[x] = isset ? colorTftWhite : colorTftBlack;
|
||||
}
|
||||
#else
|
||||
linePixelBuffer[x] = isset ? colorTftWhite : colorTftBlack;
|
||||
#endif
|
||||
}
|
||||
#if defined(HACKADAY_COMMUNICATOR)
|
||||
tft->draw16bitBeRGBBitmap(x_FirstPixelUpdate, y, &linePixelBuffer[x_FirstPixelUpdate],
|
||||
(x_LastPixelUpdate - x_FirstPixelUpdate + 1), 1);
|
||||
#else
|
||||
// Step 4: Send the changed pixels on this line to the screen as a single block transfer.
|
||||
// This function accepts pixel data MSB first so it can dump the memory straight out the SPI port.
|
||||
tft->pushRect(x_FirstPixelUpdate, y, (x_LastPixelUpdate - x_FirstPixelUpdate + 1), 1,
|
||||
&linePixelBuffer[x_FirstPixelUpdate]);
|
||||
tft->pushImage(x_FirstPixelUpdate, y, (x_LastPixelUpdate - x_FirstPixelUpdate + 1), 1,
|
||||
&linePixelBuffer[x_FirstPixelUpdate]);
|
||||
#endif
|
||||
somethingChanged = true;
|
||||
}
|
||||
@@ -1298,6 +1381,14 @@ void TFTDisplay::display(bool fromBlank)
|
||||
// Copy the Buffer to the Back Buffer
|
||||
if (somethingChanged)
|
||||
memcpy(buffer_back, buffer, displayBufferSize);
|
||||
|
||||
#if GRAPHICS_TFT_COLORING_ENABLED
|
||||
lastColorFrameSignature = colorFrameSignature;
|
||||
#endif
|
||||
haveLastDefaults = true;
|
||||
lastDefaultOnColor = defaultOnColor;
|
||||
lastDefaultOffColor = defaultOffColor;
|
||||
graphics::clearTFTColorRegions();
|
||||
}
|
||||
|
||||
void TFTDisplay::sdlLoop()
|
||||
@@ -1511,7 +1602,7 @@ bool TFTDisplay::connect()
|
||||
#else
|
||||
tft->setRotation(3); // Orient horizontal and wide underneath the silkscreen name label
|
||||
#endif
|
||||
tft->fillScreen(TFT_BLACK);
|
||||
tft->fillScreen(getThemeDefaultOffColor());
|
||||
|
||||
if (this->linePixelBuffer == NULL) {
|
||||
this->linePixelBuffer = (uint16_t *)malloc(sizeof(uint16_t) * displayWidth);
|
||||
@@ -1521,6 +1612,14 @@ bool TFTDisplay::connect()
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (this->repaintChunkBuffer == NULL) {
|
||||
this->repaintChunkBuffer = (uint16_t *)malloc(sizeof(uint16_t) * displayWidth * kFullRepaintChunkRows);
|
||||
|
||||
if (!this->repaintChunkBuffer) {
|
||||
LOG_ERROR("Not enough memory to create TFT repaint chunk buffer\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user