mirror of
https://github.com/Xinyuan-LilyGO/TTGO_TWatch_Library.git
synced 2026-03-20 06:56:59 +01:00
8256 lines
275 KiB
C++
8256 lines
275 KiB
C++
/***************************************************
|
|
Arduino TFT graphics library targeted at 32 bit
|
|
processors such as ESP32, ESP8266 and STM32.
|
|
|
|
This is a standalone library that contains the
|
|
hardware driver, the graphics functions and the
|
|
proportional fonts.
|
|
|
|
The larger fonts are Run Length Encoded to reduce their
|
|
size.
|
|
|
|
Created by Bodmer 2/12/16
|
|
Last update by Bodmer 20/03/20
|
|
****************************************************/
|
|
|
|
|
|
#include "TFT_eSPI.h"
|
|
|
|
#if defined (ESP32)
|
|
////////////////////////////////////////////////////
|
|
// TFT_eSPI driver functions for ESP32 processors //
|
|
////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// Global variables
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Select the SPI port to use, ESP32 has 2 options
|
|
#if !defined (TFT_PARALLEL_8_BIT)
|
|
#ifdef USE_HSPI_PORT
|
|
SPIClass spi = SPIClass(HSPI);
|
|
#else // use default VSPI port
|
|
//SPIClass& spi = SPI;
|
|
SPIClass spi = SPIClass(VSPI);
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef ESP32_DMA
|
|
// DMA SPA handle
|
|
spi_device_handle_t dmaHAL;
|
|
#ifdef USE_HSPI_PORT
|
|
spi_host_device_t spi_host = HSPI_HOST;
|
|
#else
|
|
spi_host_device_t spi_host = VSPI_HOST;
|
|
#endif
|
|
#endif
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
#if defined (TFT_SDA_READ) && !defined (TFT_PARALLEL_8_BIT)
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/***************************************************************************************
|
|
** Function name: beginSDA
|
|
** Description: Detach SPI from pin to permit software SPI
|
|
***************************************************************************************/
|
|
void TFT_eSPI::begin_SDA_Read(void)
|
|
{
|
|
pinMatrixOutDetach(TFT_MOSI, false, false);
|
|
pinMode(TFT_MOSI, INPUT);
|
|
pinMatrixInAttach(TFT_MOSI, VSPIQ_IN_IDX, false);
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: endSDA
|
|
** Description: Attach SPI pins after software SPI
|
|
***************************************************************************************/
|
|
void TFT_eSPI::end_SDA_Read(void)
|
|
{
|
|
pinMode(TFT_MOSI, OUTPUT);
|
|
pinMatrixOutAttach(TFT_MOSI, VSPID_OUT_IDX, false, false);
|
|
pinMode(TFT_MISO, INPUT);
|
|
pinMatrixInAttach(TFT_MISO, VSPIQ_IN_IDX, false);
|
|
}
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
#endif // #if defined (TFT_SDA_READ)
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: read byte - supports class functions
|
|
** Description: Read a byte from ESP32 8 bit data port
|
|
***************************************************************************************/
|
|
// Parallel bus MUST be set to input before calling this function!
|
|
uint8_t TFT_eSPI::readByte(void)
|
|
{
|
|
uint8_t b = 0xAA;
|
|
|
|
#if defined (TFT_PARALLEL_8_BIT)
|
|
RD_L;
|
|
uint32_t reg; // Read all GPIO pins 0-31
|
|
reg = gpio_input_get(); // Read three times to allow for bus access time
|
|
reg = gpio_input_get();
|
|
reg = gpio_input_get(); // Data should be stable now
|
|
RD_H;
|
|
|
|
// Check GPIO bits used and build value
|
|
b = (((reg >> TFT_D0) & 1) << 0);
|
|
b |= (((reg >> TFT_D1) & 1) << 1);
|
|
b |= (((reg >> TFT_D2) & 1) << 2);
|
|
b |= (((reg >> TFT_D3) & 1) << 3);
|
|
b |= (((reg >> TFT_D4) & 1) << 4);
|
|
b |= (((reg >> TFT_D5) & 1) << 5);
|
|
b |= (((reg >> TFT_D6) & 1) << 6);
|
|
b |= (((reg >> TFT_D7) & 1) << 7);
|
|
#endif
|
|
|
|
return b;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
#ifdef TFT_PARALLEL_8_BIT
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/***************************************************************************************
|
|
** Function name: GPIO direction control - supports class functions
|
|
** Description: Set parallel bus to INPUT or OUTPUT
|
|
***************************************************************************************/
|
|
void TFT_eSPI::busDir(uint32_t mask, uint8_t mode)
|
|
{
|
|
gpioMode(TFT_D0, mode);
|
|
gpioMode(TFT_D1, mode);
|
|
gpioMode(TFT_D2, mode);
|
|
gpioMode(TFT_D3, mode);
|
|
gpioMode(TFT_D4, mode);
|
|
gpioMode(TFT_D5, mode);
|
|
gpioMode(TFT_D6, mode);
|
|
gpioMode(TFT_D7, mode);
|
|
return;
|
|
/*
|
|
// Arduino generic native function, but slower
|
|
pinMode(TFT_D0, mode);
|
|
pinMode(TFT_D1, mode);
|
|
pinMode(TFT_D2, mode);
|
|
pinMode(TFT_D3, mode);
|
|
pinMode(TFT_D4, mode);
|
|
pinMode(TFT_D5, mode);
|
|
pinMode(TFT_D6, mode);
|
|
pinMode(TFT_D7, mode);
|
|
return; //*/
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: GPIO direction control - supports class functions
|
|
** Description: Set ESP32 GPIO pin to input or output (set high) ASAP
|
|
***************************************************************************************/
|
|
void TFT_eSPI::gpioMode(uint8_t gpio, uint8_t mode)
|
|
{
|
|
if (mode == INPUT) GPIO.enable_w1tc = ((uint32_t)1 << gpio);
|
|
else GPIO.enable_w1ts = ((uint32_t)1 << gpio);
|
|
|
|
ESP_REG(DR_REG_IO_MUX_BASE + esp32_gpioMux[gpio].reg) // Register lookup
|
|
= ((uint32_t)2 << FUN_DRV_S) // Set drive strength 2
|
|
| (FUN_IE) // Input enable
|
|
| ((uint32_t)2 << MCU_SEL_S); // Function select 2
|
|
GPIO.pin[gpio].val = 1; // Set pin HIGH
|
|
}
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
#endif // #ifdef TFT_PARALLEL_8_BIT
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#ifdef DISABLE_CONDITIONAL_MACROS
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
#if defined (RPI_WRITE_STROBE) && !defined (TFT_PARALLEL_8_BIT) // Code for RPi TFT
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/***************************************************************************************
|
|
** Function name: pushBlock - for ESP32 or ESP8266 RPi TFT
|
|
** Description: Write a block of pixels of the same colour
|
|
***************************************************************************************/
|
|
void TFT_eSPI::pushBlock(uint16_t color, uint32_t len)
|
|
{
|
|
uint8_t colorBin[] = { (uint8_t) (color >> 8), (uint8_t) color };
|
|
if (len) spi.writePattern(&colorBin[0], 2, 1); len--;
|
|
while (len--) {
|
|
WR_L;
|
|
WR_H;
|
|
}
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: pushPixels - for ESP32 or ESP8266 RPi TFT
|
|
** Description: Write a sequence of pixels
|
|
***************************************************************************************/
|
|
void TFT_eSPI::pushPixels(const void *data_in, uint32_t len)
|
|
{
|
|
uint8_t *data = (uint8_t *)data_in;
|
|
|
|
if (_swapBytes) {
|
|
while ( len-- ) {
|
|
tft_Write_16(*data);
|
|
data++;
|
|
}
|
|
return;
|
|
}
|
|
|
|
while ( len >= 64 ) {
|
|
spi.writePattern(data, 64, 1);
|
|
data += 64;
|
|
len -= 64;
|
|
}
|
|
if (len) spi.writePattern(data, len, 1);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
#elif !defined (ILI9488_DRIVER) && !defined (TFT_PARALLEL_8_BIT) // Most displays
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/***************************************************************************************
|
|
** Function name: pushBlock - for ESP32
|
|
** Description: Write a block of pixels of the same colour
|
|
***************************************************************************************/
|
|
void TFT_eSPI::pushBlock(uint16_t color, uint32_t len)
|
|
{
|
|
|
|
uint32_t color32 = (color << 8 | color >> 8) << 16 | (color << 8 | color >> 8);
|
|
|
|
if (len > 31) {
|
|
WRITE_PERI_REG(SPI_MOSI_DLEN_REG(SPI_PORT), 511);
|
|
while (len > 31) {
|
|
while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
|
|
WRITE_PERI_REG(SPI_W0_REG(SPI_PORT), color32);
|
|
WRITE_PERI_REG(SPI_W1_REG(SPI_PORT), color32);
|
|
WRITE_PERI_REG(SPI_W2_REG(SPI_PORT), color32);
|
|
WRITE_PERI_REG(SPI_W3_REG(SPI_PORT), color32);
|
|
WRITE_PERI_REG(SPI_W4_REG(SPI_PORT), color32);
|
|
WRITE_PERI_REG(SPI_W5_REG(SPI_PORT), color32);
|
|
WRITE_PERI_REG(SPI_W6_REG(SPI_PORT), color32);
|
|
WRITE_PERI_REG(SPI_W7_REG(SPI_PORT), color32);
|
|
WRITE_PERI_REG(SPI_W8_REG(SPI_PORT), color32);
|
|
WRITE_PERI_REG(SPI_W9_REG(SPI_PORT), color32);
|
|
WRITE_PERI_REG(SPI_W10_REG(SPI_PORT), color32);
|
|
WRITE_PERI_REG(SPI_W11_REG(SPI_PORT), color32);
|
|
WRITE_PERI_REG(SPI_W12_REG(SPI_PORT), color32);
|
|
WRITE_PERI_REG(SPI_W13_REG(SPI_PORT), color32);
|
|
WRITE_PERI_REG(SPI_W14_REG(SPI_PORT), color32);
|
|
WRITE_PERI_REG(SPI_W15_REG(SPI_PORT), color32);
|
|
SET_PERI_REG_MASK(SPI_CMD_REG(SPI_PORT), SPI_USR);
|
|
len -= 32;
|
|
}
|
|
while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
|
|
}
|
|
|
|
if (len) {
|
|
WRITE_PERI_REG(SPI_MOSI_DLEN_REG(SPI_PORT), (len << 4) - 1);
|
|
for (uint32_t i = 0; i <= (len << 1); i += 4) WRITE_PERI_REG(SPI_W0_REG(SPI_PORT) + i, color32);
|
|
SET_PERI_REG_MASK(SPI_CMD_REG(SPI_PORT), SPI_USR);
|
|
while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
|
|
}
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: pushSwapBytePixels - for ESP32
|
|
** Description: Write a sequence of pixels with swapped bytes
|
|
***************************************************************************************/
|
|
void TFT_eSPI::pushSwapBytePixels(const void *data_in, uint32_t len)
|
|
{
|
|
|
|
uint8_t *data = (uint8_t *)data_in;
|
|
uint32_t color[16];
|
|
|
|
if (len > 31) {
|
|
WRITE_PERI_REG(SPI_MOSI_DLEN_REG(SPI_PORT), 511);
|
|
while (len > 31) {
|
|
uint32_t i = 0;
|
|
while (i < 16) {
|
|
color[i++] = DAT8TO32(data);
|
|
data += 4;
|
|
}
|
|
while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
|
|
WRITE_PERI_REG(SPI_W0_REG(SPI_PORT), color[0]);
|
|
WRITE_PERI_REG(SPI_W1_REG(SPI_PORT), color[1]);
|
|
WRITE_PERI_REG(SPI_W2_REG(SPI_PORT), color[2]);
|
|
WRITE_PERI_REG(SPI_W3_REG(SPI_PORT), color[3]);
|
|
WRITE_PERI_REG(SPI_W4_REG(SPI_PORT), color[4]);
|
|
WRITE_PERI_REG(SPI_W5_REG(SPI_PORT), color[5]);
|
|
WRITE_PERI_REG(SPI_W6_REG(SPI_PORT), color[6]);
|
|
WRITE_PERI_REG(SPI_W7_REG(SPI_PORT), color[7]);
|
|
WRITE_PERI_REG(SPI_W8_REG(SPI_PORT), color[8]);
|
|
WRITE_PERI_REG(SPI_W9_REG(SPI_PORT), color[9]);
|
|
WRITE_PERI_REG(SPI_W10_REG(SPI_PORT), color[10]);
|
|
WRITE_PERI_REG(SPI_W11_REG(SPI_PORT), color[11]);
|
|
WRITE_PERI_REG(SPI_W12_REG(SPI_PORT), color[12]);
|
|
WRITE_PERI_REG(SPI_W13_REG(SPI_PORT), color[13]);
|
|
WRITE_PERI_REG(SPI_W14_REG(SPI_PORT), color[14]);
|
|
WRITE_PERI_REG(SPI_W15_REG(SPI_PORT), color[15]);
|
|
SET_PERI_REG_MASK(SPI_CMD_REG(SPI_PORT), SPI_USR);
|
|
len -= 32;
|
|
}
|
|
}
|
|
|
|
if (len > 15) {
|
|
uint32_t i = 0;
|
|
while (i < 8) {
|
|
color[i++] = DAT8TO32(data);
|
|
data += 4;
|
|
}
|
|
while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
|
|
WRITE_PERI_REG(SPI_MOSI_DLEN_REG(SPI_PORT), 255);
|
|
WRITE_PERI_REG(SPI_W0_REG(SPI_PORT), color[0]);
|
|
WRITE_PERI_REG(SPI_W1_REG(SPI_PORT), color[1]);
|
|
WRITE_PERI_REG(SPI_W2_REG(SPI_PORT), color[2]);
|
|
WRITE_PERI_REG(SPI_W3_REG(SPI_PORT), color[3]);
|
|
WRITE_PERI_REG(SPI_W4_REG(SPI_PORT), color[4]);
|
|
WRITE_PERI_REG(SPI_W5_REG(SPI_PORT), color[5]);
|
|
WRITE_PERI_REG(SPI_W6_REG(SPI_PORT), color[6]);
|
|
WRITE_PERI_REG(SPI_W7_REG(SPI_PORT), color[7]);
|
|
SET_PERI_REG_MASK(SPI_CMD_REG(SPI_PORT), SPI_USR);
|
|
len -= 16;
|
|
}
|
|
|
|
if (len) {
|
|
while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
|
|
WRITE_PERI_REG(SPI_MOSI_DLEN_REG(SPI_PORT), (len << 4) - 1);
|
|
for (uint32_t i = 0; i <= (len << 1); i += 4) {
|
|
WRITE_PERI_REG(SPI_W0_REG(SPI_PORT) + i, DAT8TO32(data)); data += 4;
|
|
}
|
|
SET_PERI_REG_MASK(SPI_CMD_REG(SPI_PORT), SPI_USR);
|
|
}
|
|
while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
|
|
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: pushPixels - for ESP32
|
|
** Description: Write a sequence of pixels
|
|
***************************************************************************************/
|
|
void TFT_eSPI::pushPixels(const void *data_in, uint32_t len)
|
|
{
|
|
|
|
if (_swapBytes) {
|
|
pushSwapBytePixels(data_in, len);
|
|
return;
|
|
}
|
|
|
|
uint32_t *data = (uint32_t *)data_in;
|
|
|
|
if (len > 31) {
|
|
WRITE_PERI_REG(SPI_MOSI_DLEN_REG(SPI_PORT), 511);
|
|
while (len > 31) {
|
|
while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
|
|
WRITE_PERI_REG(SPI_W0_REG(SPI_PORT), *data++);
|
|
WRITE_PERI_REG(SPI_W1_REG(SPI_PORT), *data++);
|
|
WRITE_PERI_REG(SPI_W2_REG(SPI_PORT), *data++);
|
|
WRITE_PERI_REG(SPI_W3_REG(SPI_PORT), *data++);
|
|
WRITE_PERI_REG(SPI_W4_REG(SPI_PORT), *data++);
|
|
WRITE_PERI_REG(SPI_W5_REG(SPI_PORT), *data++);
|
|
WRITE_PERI_REG(SPI_W6_REG(SPI_PORT), *data++);
|
|
WRITE_PERI_REG(SPI_W7_REG(SPI_PORT), *data++);
|
|
WRITE_PERI_REG(SPI_W8_REG(SPI_PORT), *data++);
|
|
WRITE_PERI_REG(SPI_W9_REG(SPI_PORT), *data++);
|
|
WRITE_PERI_REG(SPI_W10_REG(SPI_PORT), *data++);
|
|
WRITE_PERI_REG(SPI_W11_REG(SPI_PORT), *data++);
|
|
WRITE_PERI_REG(SPI_W12_REG(SPI_PORT), *data++);
|
|
WRITE_PERI_REG(SPI_W13_REG(SPI_PORT), *data++);
|
|
WRITE_PERI_REG(SPI_W14_REG(SPI_PORT), *data++);
|
|
WRITE_PERI_REG(SPI_W15_REG(SPI_PORT), *data++);
|
|
SET_PERI_REG_MASK(SPI_CMD_REG(SPI_PORT), SPI_USR);
|
|
len -= 32;
|
|
}
|
|
}
|
|
|
|
if (len) {
|
|
while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
|
|
WRITE_PERI_REG(SPI_MOSI_DLEN_REG(SPI_PORT), (len << 4) - 1);
|
|
for (uint32_t i = 0; i <= (len << 1); i += 4) WRITE_PERI_REG((SPI_W0_REG(SPI_PORT) + i), *data++);
|
|
SET_PERI_REG_MASK(SPI_CMD_REG(SPI_PORT), SPI_USR);
|
|
}
|
|
while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
#elif defined (ILI9488_DRIVER) && !defined (TFT_PARALLEL_8_BIT)// Now code for ILI9488
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/***************************************************************************************
|
|
** Function name: pushBlock - for ESP32 and 3 byte RGB display
|
|
** Description: Write a block of pixels of the same colour
|
|
***************************************************************************************/
|
|
void TFT_eSPI::pushBlock(uint16_t color, uint32_t len)
|
|
{
|
|
// Split out the colours
|
|
uint32_t r = (color & 0xF800) >> 8;
|
|
uint32_t g = (color & 0x07E0) << 5;
|
|
uint32_t b = (color & 0x001F) << 19;
|
|
// Concatenate 4 pixels into three 32 bit blocks
|
|
uint32_t r0 = r << 24 | b | g | r;
|
|
uint32_t r1 = r0 >> 8 | g << 16;
|
|
uint32_t r2 = r1 >> 8 | b << 8;
|
|
|
|
if (len > 19) {
|
|
SET_PERI_REG_BITS(SPI_MOSI_DLEN_REG(SPI_PORT), SPI_USR_MOSI_DBITLEN, 479, SPI_USR_MOSI_DBITLEN_S);
|
|
|
|
while (len > 19) {
|
|
while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
|
|
WRITE_PERI_REG(SPI_W0_REG(SPI_PORT), r0);
|
|
WRITE_PERI_REG(SPI_W1_REG(SPI_PORT), r1);
|
|
WRITE_PERI_REG(SPI_W2_REG(SPI_PORT), r2);
|
|
WRITE_PERI_REG(SPI_W3_REG(SPI_PORT), r0);
|
|
WRITE_PERI_REG(SPI_W4_REG(SPI_PORT), r1);
|
|
WRITE_PERI_REG(SPI_W5_REG(SPI_PORT), r2);
|
|
WRITE_PERI_REG(SPI_W6_REG(SPI_PORT), r0);
|
|
WRITE_PERI_REG(SPI_W7_REG(SPI_PORT), r1);
|
|
WRITE_PERI_REG(SPI_W8_REG(SPI_PORT), r2);
|
|
WRITE_PERI_REG(SPI_W9_REG(SPI_PORT), r0);
|
|
WRITE_PERI_REG(SPI_W10_REG(SPI_PORT), r1);
|
|
WRITE_PERI_REG(SPI_W11_REG(SPI_PORT), r2);
|
|
WRITE_PERI_REG(SPI_W12_REG(SPI_PORT), r0);
|
|
WRITE_PERI_REG(SPI_W13_REG(SPI_PORT), r1);
|
|
WRITE_PERI_REG(SPI_W14_REG(SPI_PORT), r2);
|
|
SET_PERI_REG_MASK(SPI_CMD_REG(SPI_PORT), SPI_USR);
|
|
len -= 20;
|
|
}
|
|
while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
|
|
}
|
|
|
|
if (len) {
|
|
SET_PERI_REG_BITS(SPI_MOSI_DLEN_REG(SPI_PORT), SPI_USR_MOSI_DBITLEN, (len * 24) - 1, SPI_USR_MOSI_DBITLEN_S);
|
|
WRITE_PERI_REG(SPI_W0_REG(SPI_PORT), r0);
|
|
WRITE_PERI_REG(SPI_W1_REG(SPI_PORT), r1);
|
|
WRITE_PERI_REG(SPI_W2_REG(SPI_PORT), r2);
|
|
WRITE_PERI_REG(SPI_W3_REG(SPI_PORT), r0);
|
|
WRITE_PERI_REG(SPI_W4_REG(SPI_PORT), r1);
|
|
WRITE_PERI_REG(SPI_W5_REG(SPI_PORT), r2);
|
|
if (len > 8 ) {
|
|
WRITE_PERI_REG(SPI_W6_REG(SPI_PORT), r0);
|
|
WRITE_PERI_REG(SPI_W7_REG(SPI_PORT), r1);
|
|
WRITE_PERI_REG(SPI_W8_REG(SPI_PORT), r2);
|
|
WRITE_PERI_REG(SPI_W9_REG(SPI_PORT), r0);
|
|
WRITE_PERI_REG(SPI_W10_REG(SPI_PORT), r1);
|
|
WRITE_PERI_REG(SPI_W11_REG(SPI_PORT), r2);
|
|
WRITE_PERI_REG(SPI_W12_REG(SPI_PORT), r0);
|
|
WRITE_PERI_REG(SPI_W13_REG(SPI_PORT), r1);
|
|
WRITE_PERI_REG(SPI_W14_REG(SPI_PORT), r2);
|
|
}
|
|
|
|
SET_PERI_REG_MASK(SPI_CMD_REG(SPI_PORT), SPI_USR);
|
|
while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
|
|
}
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: pushPixels - for ESP32 and 3 byte RGB display
|
|
** Description: Write a sequence of pixels
|
|
***************************************************************************************/
|
|
void TFT_eSPI::pushPixels(const void *data_in, uint32_t len)
|
|
{
|
|
|
|
uint16_t *data = (uint16_t *)data_in;
|
|
// ILI9488 write macro is not endianess dependant, hence !_swapBytes
|
|
if (!_swapBytes) {
|
|
while ( len-- ) {
|
|
tft_Write_16S(*data);
|
|
data++;
|
|
}
|
|
} else {
|
|
while ( len-- ) {
|
|
tft_Write_16(*data);
|
|
data++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: pushSwapBytePixels - for ESP32 and 3 byte RGB display
|
|
** Description: Write a sequence of pixels with swapped bytes
|
|
***************************************************************************************/
|
|
void TFT_eSPI::pushSwapBytePixels(const void *data_in, uint32_t len)
|
|
{
|
|
|
|
uint16_t *data = (uint16_t *)data_in;
|
|
// ILI9488 write macro is not endianess dependant, so swap byte macro not used here
|
|
while ( len-- ) {
|
|
tft_Write_16(*data);
|
|
data++;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
#elif defined (TFT_PARALLEL_8_BIT) // Now the code for ESP32 8 bit parallel
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/***************************************************************************************
|
|
** Function name: pushBlock - for ESP32 and parallel display
|
|
** Description: Write a block of pixels of the same colour
|
|
***************************************************************************************/
|
|
void TFT_eSPI::pushBlock(uint16_t color, uint32_t len)
|
|
{
|
|
if ( (color >> 8) == (color & 0x00FF) ) {
|
|
if (!len) return;
|
|
tft_Write_16(color);
|
|
while (--len) {
|
|
WR_L;
|
|
WR_H;
|
|
WR_L;
|
|
WR_H;
|
|
}
|
|
} else while (len--) {
|
|
tft_Write_16(color);
|
|
}
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: pushSwapBytePixels - for ESP32 and parallel display
|
|
** Description: Write a sequence of pixels with swapped bytes
|
|
***************************************************************************************/
|
|
void TFT_eSPI::pushSwapBytePixels(const void *data_in, uint32_t len)
|
|
{
|
|
|
|
uint16_t *data = (uint16_t *)data_in;
|
|
while ( len-- ) {
|
|
tft_Write_16(*data);
|
|
data++;
|
|
}
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: pushPixels - for ESP32 and parallel display
|
|
** Description: Write a sequence of pixels
|
|
***************************************************************************************/
|
|
void TFT_eSPI::pushPixels(const void *data_in, uint32_t len)
|
|
{
|
|
|
|
uint16_t *data = (uint16_t *)data_in;
|
|
if (_swapBytes) {
|
|
while ( len-- ) {
|
|
tft_Write_16(*data);
|
|
data++;
|
|
}
|
|
} else {
|
|
while ( len-- ) {
|
|
tft_Write_16S(*data);
|
|
data++;
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
#endif // End of display interface specific functions
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
#else /*DISABLE_CONDITIONAL_MACROS*/
|
|
void TFT_eSPI::ILI9488_pushBlock(uint16_t color, uint32_t len)
|
|
{
|
|
// Split out the colours
|
|
uint32_t r = (color & 0xF800) >> 8;
|
|
uint32_t g = (color & 0x07E0) << 5;
|
|
uint32_t b = (color & 0x001F) << 19;
|
|
// Concatenate 4 pixels into three 32 bit blocks
|
|
uint32_t r0 = r << 24 | b | g | r;
|
|
uint32_t r1 = r0 >> 8 | g << 16;
|
|
uint32_t r2 = r1 >> 8 | b << 8;
|
|
|
|
if (len > 19) {
|
|
SET_PERI_REG_BITS(SPI_MOSI_DLEN_REG(SPI_PORT), SPI_USR_MOSI_DBITLEN, 479, SPI_USR_MOSI_DBITLEN_S);
|
|
|
|
while (len > 19) {
|
|
while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
|
|
WRITE_PERI_REG(SPI_W0_REG(SPI_PORT), r0);
|
|
WRITE_PERI_REG(SPI_W1_REG(SPI_PORT), r1);
|
|
WRITE_PERI_REG(SPI_W2_REG(SPI_PORT), r2);
|
|
WRITE_PERI_REG(SPI_W3_REG(SPI_PORT), r0);
|
|
WRITE_PERI_REG(SPI_W4_REG(SPI_PORT), r1);
|
|
WRITE_PERI_REG(SPI_W5_REG(SPI_PORT), r2);
|
|
WRITE_PERI_REG(SPI_W6_REG(SPI_PORT), r0);
|
|
WRITE_PERI_REG(SPI_W7_REG(SPI_PORT), r1);
|
|
WRITE_PERI_REG(SPI_W8_REG(SPI_PORT), r2);
|
|
WRITE_PERI_REG(SPI_W9_REG(SPI_PORT), r0);
|
|
WRITE_PERI_REG(SPI_W10_REG(SPI_PORT), r1);
|
|
WRITE_PERI_REG(SPI_W11_REG(SPI_PORT), r2);
|
|
WRITE_PERI_REG(SPI_W12_REG(SPI_PORT), r0);
|
|
WRITE_PERI_REG(SPI_W13_REG(SPI_PORT), r1);
|
|
WRITE_PERI_REG(SPI_W14_REG(SPI_PORT), r2);
|
|
SET_PERI_REG_MASK(SPI_CMD_REG(SPI_PORT), SPI_USR);
|
|
len -= 20;
|
|
}
|
|
while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
|
|
}
|
|
|
|
if (len) {
|
|
SET_PERI_REG_BITS(SPI_MOSI_DLEN_REG(SPI_PORT), SPI_USR_MOSI_DBITLEN, (len * 24) - 1, SPI_USR_MOSI_DBITLEN_S);
|
|
WRITE_PERI_REG(SPI_W0_REG(SPI_PORT), r0);
|
|
WRITE_PERI_REG(SPI_W1_REG(SPI_PORT), r1);
|
|
WRITE_PERI_REG(SPI_W2_REG(SPI_PORT), r2);
|
|
WRITE_PERI_REG(SPI_W3_REG(SPI_PORT), r0);
|
|
WRITE_PERI_REG(SPI_W4_REG(SPI_PORT), r1);
|
|
WRITE_PERI_REG(SPI_W5_REG(SPI_PORT), r2);
|
|
if (len > 8 ) {
|
|
WRITE_PERI_REG(SPI_W6_REG(SPI_PORT), r0);
|
|
WRITE_PERI_REG(SPI_W7_REG(SPI_PORT), r1);
|
|
WRITE_PERI_REG(SPI_W8_REG(SPI_PORT), r2);
|
|
WRITE_PERI_REG(SPI_W9_REG(SPI_PORT), r0);
|
|
WRITE_PERI_REG(SPI_W10_REG(SPI_PORT), r1);
|
|
WRITE_PERI_REG(SPI_W11_REG(SPI_PORT), r2);
|
|
WRITE_PERI_REG(SPI_W12_REG(SPI_PORT), r0);
|
|
WRITE_PERI_REG(SPI_W13_REG(SPI_PORT), r1);
|
|
WRITE_PERI_REG(SPI_W14_REG(SPI_PORT), r2);
|
|
}
|
|
|
|
SET_PERI_REG_MASK(SPI_CMD_REG(SPI_PORT), SPI_USR);
|
|
while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
|
|
}
|
|
}
|
|
|
|
void TFT_eSPI::ILI9488_pushPixels(const void *data_in, uint32_t len)
|
|
{
|
|
uint16_t *data = (uint16_t *)data_in;
|
|
// ILI9488 write macro is not endianess dependant, hence !_swapBytes
|
|
if (!_swapBytes) {
|
|
while ( len-- ) {
|
|
tft_Write_16S(*data);
|
|
data++;
|
|
}
|
|
} else {
|
|
while ( len-- ) {
|
|
tft_Write_16(*data);
|
|
data++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void TFT_eSPI::ILI9488_pushSwapBytePixels(const void *data_in, uint32_t len)
|
|
{
|
|
|
|
uint16_t *data = (uint16_t *)data_in;
|
|
// ILI9488 write macro is not endianess dependant, so swap byte macro not used here
|
|
while ( len-- ) {
|
|
tft_Write_16(*data);
|
|
data++;
|
|
}
|
|
}
|
|
|
|
void TFT_eSPI::General_pushBlock(uint16_t color, uint32_t len)
|
|
{
|
|
uint32_t color32 = (color << 8 | color >> 8) << 16 | (color << 8 | color >> 8);
|
|
if (len > 31) {
|
|
WRITE_PERI_REG(SPI_MOSI_DLEN_REG(SPI_PORT), 511);
|
|
while (len > 31) {
|
|
while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
|
|
WRITE_PERI_REG(SPI_W0_REG(SPI_PORT), color32);
|
|
WRITE_PERI_REG(SPI_W1_REG(SPI_PORT), color32);
|
|
WRITE_PERI_REG(SPI_W2_REG(SPI_PORT), color32);
|
|
WRITE_PERI_REG(SPI_W3_REG(SPI_PORT), color32);
|
|
WRITE_PERI_REG(SPI_W4_REG(SPI_PORT), color32);
|
|
WRITE_PERI_REG(SPI_W5_REG(SPI_PORT), color32);
|
|
WRITE_PERI_REG(SPI_W6_REG(SPI_PORT), color32);
|
|
WRITE_PERI_REG(SPI_W7_REG(SPI_PORT), color32);
|
|
WRITE_PERI_REG(SPI_W8_REG(SPI_PORT), color32);
|
|
WRITE_PERI_REG(SPI_W9_REG(SPI_PORT), color32);
|
|
WRITE_PERI_REG(SPI_W10_REG(SPI_PORT), color32);
|
|
WRITE_PERI_REG(SPI_W11_REG(SPI_PORT), color32);
|
|
WRITE_PERI_REG(SPI_W12_REG(SPI_PORT), color32);
|
|
WRITE_PERI_REG(SPI_W13_REG(SPI_PORT), color32);
|
|
WRITE_PERI_REG(SPI_W14_REG(SPI_PORT), color32);
|
|
WRITE_PERI_REG(SPI_W15_REG(SPI_PORT), color32);
|
|
SET_PERI_REG_MASK(SPI_CMD_REG(SPI_PORT), SPI_USR);
|
|
len -= 32;
|
|
}
|
|
while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
|
|
}
|
|
|
|
if (len) {
|
|
WRITE_PERI_REG(SPI_MOSI_DLEN_REG(SPI_PORT), (len << 4) - 1);
|
|
for (uint32_t i = 0; i <= (len << 1); i += 4) WRITE_PERI_REG(SPI_W0_REG(SPI_PORT) + i, color32);
|
|
SET_PERI_REG_MASK(SPI_CMD_REG(SPI_PORT), SPI_USR);
|
|
while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
|
|
}
|
|
}
|
|
|
|
void TFT_eSPI::General_pushSwapBytePixels(const void *data_in, uint32_t len)
|
|
{
|
|
|
|
uint8_t *data = (uint8_t *)data_in;
|
|
uint32_t color[16];
|
|
|
|
if (len > 31) {
|
|
WRITE_PERI_REG(SPI_MOSI_DLEN_REG(SPI_PORT), 511);
|
|
while (len > 31) {
|
|
uint32_t i = 0;
|
|
while (i < 16) {
|
|
color[i++] = DAT8TO32(data);
|
|
data += 4;
|
|
}
|
|
while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
|
|
WRITE_PERI_REG(SPI_W0_REG(SPI_PORT), color[0]);
|
|
WRITE_PERI_REG(SPI_W1_REG(SPI_PORT), color[1]);
|
|
WRITE_PERI_REG(SPI_W2_REG(SPI_PORT), color[2]);
|
|
WRITE_PERI_REG(SPI_W3_REG(SPI_PORT), color[3]);
|
|
WRITE_PERI_REG(SPI_W4_REG(SPI_PORT), color[4]);
|
|
WRITE_PERI_REG(SPI_W5_REG(SPI_PORT), color[5]);
|
|
WRITE_PERI_REG(SPI_W6_REG(SPI_PORT), color[6]);
|
|
WRITE_PERI_REG(SPI_W7_REG(SPI_PORT), color[7]);
|
|
WRITE_PERI_REG(SPI_W8_REG(SPI_PORT), color[8]);
|
|
WRITE_PERI_REG(SPI_W9_REG(SPI_PORT), color[9]);
|
|
WRITE_PERI_REG(SPI_W10_REG(SPI_PORT), color[10]);
|
|
WRITE_PERI_REG(SPI_W11_REG(SPI_PORT), color[11]);
|
|
WRITE_PERI_REG(SPI_W12_REG(SPI_PORT), color[12]);
|
|
WRITE_PERI_REG(SPI_W13_REG(SPI_PORT), color[13]);
|
|
WRITE_PERI_REG(SPI_W14_REG(SPI_PORT), color[14]);
|
|
WRITE_PERI_REG(SPI_W15_REG(SPI_PORT), color[15]);
|
|
SET_PERI_REG_MASK(SPI_CMD_REG(SPI_PORT), SPI_USR);
|
|
len -= 32;
|
|
}
|
|
}
|
|
|
|
if (len > 15) {
|
|
uint32_t i = 0;
|
|
while (i < 8) {
|
|
color[i++] = DAT8TO32(data);
|
|
data += 4;
|
|
}
|
|
while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
|
|
WRITE_PERI_REG(SPI_MOSI_DLEN_REG(SPI_PORT), 255);
|
|
WRITE_PERI_REG(SPI_W0_REG(SPI_PORT), color[0]);
|
|
WRITE_PERI_REG(SPI_W1_REG(SPI_PORT), color[1]);
|
|
WRITE_PERI_REG(SPI_W2_REG(SPI_PORT), color[2]);
|
|
WRITE_PERI_REG(SPI_W3_REG(SPI_PORT), color[3]);
|
|
WRITE_PERI_REG(SPI_W4_REG(SPI_PORT), color[4]);
|
|
WRITE_PERI_REG(SPI_W5_REG(SPI_PORT), color[5]);
|
|
WRITE_PERI_REG(SPI_W6_REG(SPI_PORT), color[6]);
|
|
WRITE_PERI_REG(SPI_W7_REG(SPI_PORT), color[7]);
|
|
SET_PERI_REG_MASK(SPI_CMD_REG(SPI_PORT), SPI_USR);
|
|
len -= 16;
|
|
}
|
|
|
|
if (len) {
|
|
while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
|
|
WRITE_PERI_REG(SPI_MOSI_DLEN_REG(SPI_PORT), (len << 4) - 1);
|
|
for (uint32_t i = 0; i <= (len << 1); i += 4) {
|
|
WRITE_PERI_REG(SPI_W0_REG(SPI_PORT) + i, DAT8TO32(data)); data += 4;
|
|
}
|
|
SET_PERI_REG_MASK(SPI_CMD_REG(SPI_PORT), SPI_USR);
|
|
}
|
|
while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
|
|
|
|
}
|
|
|
|
void TFT_eSPI::General_pushPixels(const void *data_in, uint32_t len)
|
|
{
|
|
|
|
if (_swapBytes) {
|
|
pushSwapBytePixels(data_in, len);
|
|
return;
|
|
}
|
|
|
|
uint32_t *data = (uint32_t *)data_in;
|
|
|
|
if (len > 31) {
|
|
WRITE_PERI_REG(SPI_MOSI_DLEN_REG(SPI_PORT), 511);
|
|
while (len > 31) {
|
|
while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
|
|
WRITE_PERI_REG(SPI_W0_REG(SPI_PORT), *data++);
|
|
WRITE_PERI_REG(SPI_W1_REG(SPI_PORT), *data++);
|
|
WRITE_PERI_REG(SPI_W2_REG(SPI_PORT), *data++);
|
|
WRITE_PERI_REG(SPI_W3_REG(SPI_PORT), *data++);
|
|
WRITE_PERI_REG(SPI_W4_REG(SPI_PORT), *data++);
|
|
WRITE_PERI_REG(SPI_W5_REG(SPI_PORT), *data++);
|
|
WRITE_PERI_REG(SPI_W6_REG(SPI_PORT), *data++);
|
|
WRITE_PERI_REG(SPI_W7_REG(SPI_PORT), *data++);
|
|
WRITE_PERI_REG(SPI_W8_REG(SPI_PORT), *data++);
|
|
WRITE_PERI_REG(SPI_W9_REG(SPI_PORT), *data++);
|
|
WRITE_PERI_REG(SPI_W10_REG(SPI_PORT), *data++);
|
|
WRITE_PERI_REG(SPI_W11_REG(SPI_PORT), *data++);
|
|
WRITE_PERI_REG(SPI_W12_REG(SPI_PORT), *data++);
|
|
WRITE_PERI_REG(SPI_W13_REG(SPI_PORT), *data++);
|
|
WRITE_PERI_REG(SPI_W14_REG(SPI_PORT), *data++);
|
|
WRITE_PERI_REG(SPI_W15_REG(SPI_PORT), *data++);
|
|
SET_PERI_REG_MASK(SPI_CMD_REG(SPI_PORT), SPI_USR);
|
|
len -= 32;
|
|
}
|
|
}
|
|
|
|
if (len) {
|
|
while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
|
|
WRITE_PERI_REG(SPI_MOSI_DLEN_REG(SPI_PORT), (len << 4) - 1);
|
|
for (uint32_t i = 0; i <= (len << 1); i += 4) WRITE_PERI_REG((SPI_W0_REG(SPI_PORT) + i), *data++);
|
|
SET_PERI_REG_MASK(SPI_CMD_REG(SPI_PORT), SPI_USR);
|
|
}
|
|
while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/***************************************************************************************
|
|
** Function name: pushBlock - for ESP32
|
|
** Description: Write a block of pixels of the same colour
|
|
***************************************************************************************/
|
|
void TFT_eSPI::pushBlock(uint16_t color, uint32_t len)
|
|
{
|
|
switch (drv.tft_driver) {
|
|
case 0x9488:
|
|
ILI9488_pushBlock(color, len);
|
|
break;
|
|
default:
|
|
General_pushBlock(color, len);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: pushSwapBytePixels - for ESP32
|
|
** Description: Write a sequence of pixels with swapped bytes
|
|
***************************************************************************************/
|
|
void TFT_eSPI::pushSwapBytePixels(const void *data_in, uint32_t len)
|
|
{
|
|
switch (drv.tft_driver) {
|
|
case 0x9488:
|
|
ILI9488_pushSwapBytePixels(data_in, len);
|
|
break;
|
|
default:
|
|
General_pushSwapBytePixels(data_in, len);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: pushPixels - for ESP32
|
|
** Description: Write a sequence of pixels
|
|
***************************************************************************************/
|
|
void TFT_eSPI::pushPixels(const void *data_in, uint32_t len)
|
|
{
|
|
switch (drv.tft_driver) {
|
|
case 0x9488:
|
|
ILI9488_pushPixels(data_in, len);
|
|
break;
|
|
default:
|
|
General_pushPixels(data_in, len);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
#endif /*DISABLE_CONDITIONAL_MACROS**/
|
|
|
|
#if 1
|
|
|
|
#define TFT_WRITE_BITS(D, B) \
|
|
WRITE_PERI_REG(SPI_MOSI_DLEN_REG(SPI_PORT), B-1); \
|
|
WRITE_PERI_REG(SPI_W0_REG(SPI_PORT), D); \
|
|
SET_PERI_REG_MASK(SPI_CMD_REG(SPI_PORT), SPI_USR); \
|
|
while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
|
|
|
|
void TFT_eSPI::tft_Write_8(uint8_t C)
|
|
{
|
|
switch (drv.tft_driver) {
|
|
case 0x9488:
|
|
spi.transfer(C);
|
|
break;
|
|
default:
|
|
TFT_WRITE_BITS(C, 8);
|
|
break;
|
|
}
|
|
}
|
|
void TFT_eSPI::tft_Write_16(uint16_t C)
|
|
{
|
|
switch (drv.tft_driver) {
|
|
case 0x9488:
|
|
spi.transfer(((C) & 0xF800) >> 8);
|
|
spi.transfer(((C) & 0x07E0) >> 3);
|
|
spi.transfer(((C) & 0x001F) << 3);
|
|
break;
|
|
default:
|
|
TFT_WRITE_BITS((C) << 8 | (C) >> 8, 16);
|
|
break;
|
|
}
|
|
}
|
|
void TFT_eSPI::tft_Write_16S(uint16_t C)
|
|
{
|
|
switch (drv.tft_driver) {
|
|
case 0x9488:
|
|
spi.transfer((C) & 0xF8);
|
|
spi.transfer(((C) & 0xE000) >> 11 | ((C) & 0x07) << 5);
|
|
spi.transfer(((C) & 0x1F00) >> 5);
|
|
break;
|
|
default:
|
|
TFT_WRITE_BITS(C, 16);
|
|
break;
|
|
}
|
|
}
|
|
void TFT_eSPI::tft_Write_32(uint32_t C)
|
|
{
|
|
switch (drv.tft_driver) {
|
|
case 0x9488:
|
|
spi.write32(C);
|
|
break;
|
|
default:
|
|
TFT_WRITE_BITS(C, 32);
|
|
break;
|
|
}
|
|
}
|
|
void TFT_eSPI::tft_Write_32C(uint32_t C, uint32_t D)
|
|
{
|
|
switch (drv.tft_driver) {
|
|
case 0x9488:
|
|
spi.write32((C) << 16 | (D));
|
|
break;
|
|
default:
|
|
TFT_WRITE_BITS((uint16_t)((D) << 8 | (D) >> 8) << 16 | (uint16_t)((C) << 8 | (C) >> 8), 32);
|
|
break;
|
|
}
|
|
}
|
|
void TFT_eSPI::tft_Write_32D(uint32_t C)
|
|
{
|
|
switch (drv.tft_driver) {
|
|
case 0x9488:
|
|
spi.write32((C) << 16 | (C));
|
|
break;
|
|
default:
|
|
TFT_WRITE_BITS((uint16_t)((C) << 8 | (C) >> 8) << 16 | (uint16_t)((C) << 8 | (C) >> 8), 32);
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
#if defined ESP32_DMA && !defined (TFT_PARALLEL_8_BIT) // DMA FUNCTIONS
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/***************************************************************************************
|
|
** Function name: dmaBusy
|
|
** Description: Check if DMA is busy
|
|
***************************************************************************************/
|
|
bool TFT_eSPI::dmaBusy(void)
|
|
{
|
|
if (!DMA_Enabled || !spiBusyCheck) return false;
|
|
|
|
spi_transaction_t *rtrans;
|
|
esp_err_t ret;
|
|
uint8_t checks = spiBusyCheck;
|
|
for (int i = 0; i < checks; ++i) {
|
|
ret = spi_device_get_trans_result(dmaHAL, &rtrans, 0);
|
|
if (ret == ESP_OK) spiBusyCheck--;
|
|
}
|
|
|
|
//Serial.print("spiBusyCheck=");Serial.println(spiBusyCheck);
|
|
if (spiBusyCheck == 0) return false;
|
|
return true;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: dmaWait
|
|
** Description: Wait until DMA is over (blocking!)
|
|
***************************************************************************************/
|
|
void TFT_eSPI::dmaWait(void)
|
|
{
|
|
if (!DMA_Enabled || !spiBusyCheck) return;
|
|
spi_transaction_t *rtrans;
|
|
esp_err_t ret;
|
|
for (int i = 0; i < spiBusyCheck; ++i) {
|
|
ret = spi_device_get_trans_result(dmaHAL, &rtrans, portMAX_DELAY);
|
|
assert(ret == ESP_OK);
|
|
}
|
|
spiBusyCheck = 0;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: pushImageDMA
|
|
** Description: Push pixels to TFT (len must be less than 32767)
|
|
***************************************************************************************/
|
|
// This will byte swap the original image if setSwapBytes(true) was called by sketch.
|
|
void TFT_eSPI::pushPixelsDMA(uint16_t *image, uint32_t len)
|
|
{
|
|
if ((len == 0) || (!DMA_Enabled)) return;
|
|
dmaWait();
|
|
|
|
if (_swapBytes) {
|
|
for (uint32_t i = 0; i < len; i++) (image[i] = image[i] << 8 | image[i] >> 8);
|
|
}
|
|
|
|
esp_err_t ret;
|
|
static spi_transaction_t trans;
|
|
|
|
memset(&trans, 0, sizeof(spi_transaction_t));
|
|
|
|
trans.user = (void *)1;
|
|
trans.tx_buffer = image; //finally send the line data
|
|
trans.length = len * 16; //Data length, in bits
|
|
trans.flags = 0; //SPI_TRANS_USE_TXDATA flag
|
|
|
|
ret = spi_device_queue_trans(dmaHAL, &trans, portMAX_DELAY);
|
|
assert(ret == ESP_OK);
|
|
|
|
spiBusyCheck++;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: pushImageDMA
|
|
** Description: Push image to a window (w*h must be less than 65536)
|
|
***************************************************************************************/
|
|
// This will clip and also swap bytes if setSwapBytes(true) was called by sketch
|
|
void TFT_eSPI::pushImageDMA(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t *image, uint16_t *buffer)
|
|
{
|
|
if ((x >= _width) || (y >= _height) || (!DMA_Enabled)) return;
|
|
|
|
int32_t dx = 0;
|
|
int32_t dy = 0;
|
|
int32_t dw = w;
|
|
int32_t dh = h;
|
|
|
|
if (x < 0) {
|
|
dw += x;
|
|
dx = -x;
|
|
x = 0;
|
|
}
|
|
if (y < 0) {
|
|
dh += y;
|
|
dy = -y;
|
|
y = 0;
|
|
}
|
|
|
|
if ((x + dw) > _width ) dw = _width - x;
|
|
if ((y + dh) > _height) dh = _height - y;
|
|
|
|
if (dw < 1 || dh < 1) return;
|
|
|
|
uint32_t len = dw * dh;
|
|
|
|
if (buffer == nullptr) {
|
|
buffer = image;
|
|
dmaWait();
|
|
}
|
|
|
|
// If image is clipped, copy pixels into a contiguous block
|
|
if ( (dw != w) || (dh != h) ) {
|
|
if (_swapBytes) {
|
|
for (int32_t yb = 0; yb < dh; yb++) {
|
|
for (int32_t xb = 0; xb < dw; xb++) {
|
|
uint32_t src = xb + dx + w * (yb + dy);
|
|
(buffer[xb + yb * dw] = image[src] << 8 | image[src] >> 8);
|
|
}
|
|
}
|
|
} else {
|
|
for (int32_t yb = 0; yb < dh; yb++) {
|
|
memcpy((uint8_t *) (buffer + yb * dw), (uint8_t *) (image + dx + w * (yb + dy)), dw << 1);
|
|
}
|
|
}
|
|
}
|
|
// else, if a buffer pointer has been provided copy whole image to the buffer
|
|
else if (buffer != image || _swapBytes) {
|
|
if (_swapBytes) {
|
|
for (uint32_t i = 0; i < len; i++) (buffer[i] = image[i] << 8 | image[i] >> 8);
|
|
} else {
|
|
memcpy(buffer, image, len * 2);
|
|
}
|
|
}
|
|
|
|
if (spiBusyCheck) dmaWait(); // Incase we did not wait earlier
|
|
|
|
setAddrWindow(x, y, dw, dh);
|
|
|
|
esp_err_t ret;
|
|
static spi_transaction_t trans;
|
|
|
|
memset(&trans, 0, sizeof(spi_transaction_t));
|
|
|
|
trans.user = (void *)1;
|
|
trans.tx_buffer = buffer; //finally send the line data
|
|
trans.length = len * 16; //Data length, in bits
|
|
trans.flags = 0; //SPI_TRANS_USE_TXDATA flag
|
|
|
|
ret = spi_device_queue_trans(dmaHAL, &trans, portMAX_DELAY);
|
|
assert(ret == ESP_OK);
|
|
|
|
spiBusyCheck++;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// Processor specific DMA initialisation
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// The DMA functions here work with SPI only (not parallel)
|
|
/***************************************************************************************
|
|
** Function name: dc_callback
|
|
** Description: Toggles DC line during transaction
|
|
***************************************************************************************/
|
|
extern "C" void dc_callback();
|
|
|
|
void IRAM_ATTR dc_callback(spi_transaction_t *spi_tx)
|
|
{
|
|
if ((bool)spi_tx->user) DC_D;
|
|
else DC_C;
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: initDMA
|
|
** Description: Initialise the DMA engine - returns true if init OK
|
|
***************************************************************************************/
|
|
bool TFT_eSPI::initDMA(void)
|
|
{
|
|
if (DMA_Enabled) return false;
|
|
esp_err_t ret;
|
|
spi_bus_config_t buscfg = {
|
|
.mosi_io_num = TFT_MOSI,
|
|
.miso_io_num = TFT_MISO,
|
|
.sclk_io_num = TFT_SCLK,
|
|
.quadwp_io_num = -1,
|
|
.quadhd_io_num = -1,
|
|
.max_transfer_sz = TFT_WIDTH * TFT_HEIGHT * 2 + 8, // TFT screen size
|
|
.flags = 0,
|
|
.intr_flags = 0
|
|
};
|
|
spi_device_interface_config_t devcfg = {
|
|
.command_bits = 0,
|
|
.address_bits = 0,
|
|
.dummy_bits = 0,
|
|
.mode = TFT_SPI_MODE,
|
|
.duty_cycle_pos = 0,
|
|
.cs_ena_pretrans = 0,
|
|
.cs_ena_posttrans = 0,
|
|
.clock_speed_hz = drv.tft_spi_freq, // lewis add. Mark : Used to set different models
|
|
.input_delay_ns = 0,
|
|
.spics_io_num = TFT_CS,
|
|
.flags = SPI_DEVICE_NO_DUMMY,
|
|
.queue_size = 7,
|
|
.pre_cb = dc_callback, //Callback to handle D/C line
|
|
.post_cb = 0
|
|
};
|
|
ret = spi_bus_initialize(spi_host, &buscfg, 1);
|
|
ESP_ERROR_CHECK(ret);
|
|
ret = spi_bus_add_device(spi_host, &devcfg, &dmaHAL);
|
|
ESP_ERROR_CHECK(ret);
|
|
|
|
DMA_Enabled = true;
|
|
spiBusyCheck = 0;
|
|
return true;
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: deInitDMA
|
|
** Description: Disconnect the DMA engine from SPI
|
|
***************************************************************************************/
|
|
void TFT_eSPI::deInitDMA(void)
|
|
{
|
|
if (!DMA_Enabled) return;
|
|
spi_bus_remove_device(dmaHAL);
|
|
spi_bus_free(spi_host);
|
|
DMA_Enabled = false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
#endif // End of DMA FUNCTIONS
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#endif
|
|
|
|
/***************************************************************************************
|
|
** Function name: begin_tft_write (was called spi_begin)
|
|
** Description: Start SPI transaction for writes and select TFT
|
|
***************************************************************************************/
|
|
inline void TFT_eSPI::begin_tft_write(void)
|
|
{
|
|
#if defined (SPI_HAS_TRANSACTION) && defined (SUPPORT_TRANSACTIONS) && !defined(TFT_PARALLEL_8_BIT)
|
|
if (locked) {
|
|
locked = false;
|
|
spi.beginTransaction(SPISettings(drv.tft_spi_freq, MSBFIRST, TFT_SPI_MODE));
|
|
CS_L;
|
|
}
|
|
#else
|
|
CS_L;
|
|
#endif
|
|
SET_BUS_WRITE_MODE;
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: end_tft_write (was called spi_end)
|
|
** Description: End transaction for write and deselect TFT
|
|
***************************************************************************************/
|
|
inline void TFT_eSPI::end_tft_write(void)
|
|
{
|
|
#if defined (SPI_HAS_TRANSACTION) && defined (SUPPORT_TRANSACTIONS) && !defined(TFT_PARALLEL_8_BIT)
|
|
if (!inTransaction) {
|
|
if (!locked) {
|
|
locked = true;
|
|
CS_H;
|
|
spi.endTransaction();
|
|
}
|
|
}
|
|
SET_BUS_READ_MODE;
|
|
#else
|
|
if (!inTransaction) {
|
|
CS_H;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: begin_tft_read (was called spi_begin_read)
|
|
** Description: Start transaction for reads and select TFT
|
|
***************************************************************************************/
|
|
// Reads require a lower SPI clock rate than writes
|
|
inline void TFT_eSPI::begin_tft_read(void)
|
|
{
|
|
DMA_BUSY_CHECK; // Wait for any DMA transfer to complete before changing SPI settings
|
|
#if defined (SPI_HAS_TRANSACTION) && defined (SUPPORT_TRANSACTIONS) && !defined(TFT_PARALLEL_8_BIT)
|
|
if (locked) {
|
|
locked = false;
|
|
spi.beginTransaction(SPISettings(SPI_READ_FREQUENCY, MSBFIRST, TFT_SPI_MODE));
|
|
CS_L;
|
|
}
|
|
#else
|
|
#if !defined(TFT_PARALLEL_8_BIT)
|
|
spi.setFrequency(SPI_READ_FREQUENCY);
|
|
#endif
|
|
CS_L;
|
|
#endif
|
|
SET_BUS_READ_MODE;
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: end_tft_read (was called spi_end_read)
|
|
** Description: End transaction for reads and deselect TFT
|
|
***************************************************************************************/
|
|
inline void TFT_eSPI::end_tft_read(void)
|
|
{
|
|
#if defined (SPI_HAS_TRANSACTION) && defined (SUPPORT_TRANSACTIONS) && !defined(TFT_PARALLEL_8_BIT)
|
|
if (!inTransaction) {
|
|
if (!locked) {
|
|
locked = true;
|
|
CS_H;
|
|
spi.endTransaction();
|
|
}
|
|
}
|
|
#else
|
|
#if !defined(TFT_PARALLEL_8_BIT)
|
|
spi.setFrequency(drv.tft_spi_freq);
|
|
#endif
|
|
if (!inTransaction) {
|
|
CS_H;
|
|
}
|
|
#endif
|
|
SET_BUS_WRITE_MODE;
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: Legacy - deprecated
|
|
** Description: Start/end transaction
|
|
***************************************************************************************/
|
|
void TFT_eSPI::spi_begin()
|
|
{
|
|
begin_tft_write();
|
|
}
|
|
void TFT_eSPI::spi_end()
|
|
{
|
|
end_tft_write();
|
|
}
|
|
void TFT_eSPI::spi_begin_read()
|
|
{
|
|
begin_tft_read();
|
|
}
|
|
void TFT_eSPI::spi_end_read()
|
|
{
|
|
end_tft_read();
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: TFT_eSPI
|
|
** Description: Constructor , we must use hardware SPI pins
|
|
***************************************************************************************/
|
|
TFT_eSPI::TFT_eSPI(int16_t w, int16_t h)
|
|
{
|
|
|
|
// lewis add. Mark : Used to set different models
|
|
memset(&drv, 0, sizeof(drv));
|
|
|
|
// The control pins are deliberately set to the inactive state (CS high) as setup()
|
|
// might call and initialise other SPI peripherals which would could cause conflicts
|
|
// if CS is floating or undefined.
|
|
#ifdef TFT_CS
|
|
pinMode(TFT_CS, OUTPUT);
|
|
digitalWrite(TFT_CS, HIGH); // Chip select high (inactive)
|
|
#endif
|
|
|
|
// Configure chip select for touchscreen controller if present
|
|
#ifdef TOUCH_CS
|
|
pinMode(TOUCH_CS, OUTPUT);
|
|
digitalWrite(TOUCH_CS, HIGH); // Chip select high (inactive)
|
|
#endif
|
|
|
|
#ifdef TFT_WR
|
|
pinMode(TFT_WR, OUTPUT);
|
|
digitalWrite(TFT_WR, HIGH); // Set write strobe high (inactive)
|
|
#endif
|
|
|
|
#ifdef TFT_DC
|
|
pinMode(TFT_DC, OUTPUT);
|
|
digitalWrite(TFT_DC, HIGH); // Data/Command high = data mode
|
|
#endif
|
|
|
|
#ifdef TFT_RST
|
|
if (TFT_RST >= 0) {
|
|
pinMode(TFT_RST, OUTPUT);
|
|
digitalWrite(TFT_RST, HIGH); // Set high, do not share pin with another SPI device
|
|
}
|
|
#endif
|
|
|
|
#if defined (TFT_PARALLEL_8_BIT)
|
|
|
|
// Make sure read is high before we set the bus to output
|
|
pinMode(TFT_RD, OUTPUT);
|
|
digitalWrite(TFT_RD, HIGH);
|
|
|
|
// Set TFT data bus lines to output
|
|
pinMode(TFT_D0, OUTPUT); digitalWrite(TFT_D0, HIGH);
|
|
pinMode(TFT_D1, OUTPUT); digitalWrite(TFT_D1, HIGH);
|
|
pinMode(TFT_D2, OUTPUT); digitalWrite(TFT_D2, HIGH);
|
|
pinMode(TFT_D3, OUTPUT); digitalWrite(TFT_D3, HIGH);
|
|
pinMode(TFT_D4, OUTPUT); digitalWrite(TFT_D4, HIGH);
|
|
pinMode(TFT_D5, OUTPUT); digitalWrite(TFT_D5, HIGH);
|
|
pinMode(TFT_D6, OUTPUT); digitalWrite(TFT_D6, HIGH);
|
|
pinMode(TFT_D7, OUTPUT); digitalWrite(TFT_D7, HIGH);
|
|
|
|
CONSTRUCTOR_INIT_TFT_DATA_BUS;
|
|
|
|
#endif
|
|
drv.tft_height = _height;
|
|
drv.tft_width = _width;
|
|
|
|
_init_width = _width = w; // Set by specific xxxxx_Defines.h file or by users sketch
|
|
_init_height = _height = h; // Set by specific xxxxx_Defines.h file or by users sketch
|
|
rotation = 0;
|
|
cursor_y = cursor_x = 0;
|
|
textfont = 1;
|
|
textsize = 1;
|
|
textcolor = bitmap_fg = 0xFFFF; // White
|
|
textbgcolor = bitmap_bg = 0x0000; // Black
|
|
padX = 0; // No padding
|
|
isDigits = false; // No bounding box adjustment
|
|
textwrapX = true; // Wrap text at end of line when using print stream
|
|
textwrapY = false; // Wrap text at bottom of screen when using print stream
|
|
textdatum = TL_DATUM; // Top Left text alignment is default
|
|
fontsloaded = 0;
|
|
|
|
_swapBytes = false; // Do not swap colour bytes by default
|
|
|
|
locked = true; // Transaction mutex lock flags
|
|
inTransaction = false;
|
|
|
|
_booted = true; // Default attributes
|
|
_cp437 = true;
|
|
_utf8 = true;
|
|
|
|
#ifdef FONT_FS_AVAILABLE
|
|
fs_font = true; // Smooth font filing system or array (fs_font = false) flag
|
|
#endif
|
|
|
|
#if defined (ESP32) && defined (CONFIG_SPIRAM_SUPPORT)
|
|
if (psramFound()) _psram_enable = true; // Enable the use of PSRAM (if available)
|
|
else
|
|
#endif
|
|
_psram_enable = false;
|
|
|
|
addr_row = 0xFFFF;
|
|
addr_col = 0xFFFF;
|
|
|
|
_xpivot = 0;
|
|
_ypivot = 0;
|
|
|
|
cspinmask = 0;
|
|
dcpinmask = 0;
|
|
wrpinmask = 0;
|
|
sclkpinmask = 0;
|
|
|
|
#ifdef LOAD_GLCD
|
|
fontsloaded = 0x0002; // Bit 1 set
|
|
#endif
|
|
|
|
#ifdef LOAD_FONT2
|
|
fontsloaded |= 0x0004; // Bit 2 set
|
|
#endif
|
|
|
|
#ifdef LOAD_FONT4
|
|
fontsloaded |= 0x0010; // Bit 4 set
|
|
#endif
|
|
|
|
#ifdef LOAD_FONT6
|
|
fontsloaded |= 0x0040; // Bit 6 set
|
|
#endif
|
|
|
|
#ifdef LOAD_FONT7
|
|
fontsloaded |= 0x0080; // Bit 7 set
|
|
#endif
|
|
|
|
#ifdef LOAD_FONT8
|
|
fontsloaded |= 0x0100; // Bit 8 set
|
|
#endif
|
|
|
|
#ifdef LOAD_FONT8N
|
|
fontsloaded |= 0x0200; // Bit 9 set
|
|
#endif
|
|
|
|
#ifdef SMOOTH_FONT
|
|
fontsloaded |= 0x8000; // Bit 15 set
|
|
#endif
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: begin
|
|
** Description: Included for backwards compatibility
|
|
***************************************************************************************/
|
|
void TFT_eSPI::begin(uint8_t tc)
|
|
{
|
|
init(tc);
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: init (tc is tab colour for ST7735 displays only)
|
|
** Description: Reset, then initialise the TFT display registers
|
|
***************************************************************************************/
|
|
void TFT_eSPI::init(uint8_t tc)
|
|
{
|
|
if (_booted) {
|
|
#if !defined (ESP32) && !defined(TFT_PARALLEL_8_BIT)
|
|
#if defined (TFT_CS) && (TFT_CS >= 0)
|
|
cspinmask = (uint32_t) digitalPinToBitMask(TFT_CS);
|
|
#endif
|
|
|
|
#if defined (TFT_DC) && (TFT_DC >= 0)
|
|
dcpinmask = (uint32_t) digitalPinToBitMask(TFT_DC);
|
|
#endif
|
|
|
|
#if defined (TFT_WR) && (TFT_WR >= 0)
|
|
wrpinmask = (uint32_t) digitalPinToBitMask(TFT_WR);
|
|
#endif
|
|
|
|
#if defined (TFT_SCLK) && (TFT_SCLK >= 0)
|
|
sclkpinmask = (uint32_t) digitalPinToBitMask(TFT_SCLK);
|
|
#endif
|
|
|
|
#if defined (TFT_SPI_OVERLAP) && defined (ESP8266)
|
|
// Overlap mode SD0=MISO, SD1=MOSI, CLK=SCLK must use D3 as CS
|
|
// pins(int8_t sck, int8_t miso, int8_t mosi, int8_t ss);
|
|
//spi.pins( 6, 7, 8, 0);
|
|
spi.pins(6, 7, 8, 0);
|
|
#endif
|
|
|
|
spi.begin(); // This will set HMISO to input
|
|
|
|
#else
|
|
#if !defined(TFT_PARALLEL_8_BIT)
|
|
#if defined (TFT_MOSI) && !defined (TFT_SPI_OVERLAP)
|
|
spi.begin(TFT_SCLK, TFT_MISO, TFT_MOSI, -1);
|
|
#else
|
|
spi.begin();
|
|
#endif
|
|
#endif
|
|
#endif
|
|
|
|
inTransaction = false;
|
|
locked = true;
|
|
|
|
INIT_TFT_DATA_BUS;
|
|
|
|
|
|
|
|
#ifdef TFT_CS
|
|
// Set to output once again in case D6 (MISO) is used for CS
|
|
pinMode(TFT_CS, OUTPUT);
|
|
digitalWrite(TFT_CS, HIGH); // Chip select high (inactive)
|
|
#elif defined (ESP8266) && !defined (TFT_PARALLEL_8_BIT)
|
|
spi.setHwCs(1); // Use hardware SS toggling
|
|
#endif
|
|
|
|
|
|
|
|
// Set to output once again in case D6 (MISO) is used for DC
|
|
#ifdef TFT_DC
|
|
pinMode(TFT_DC, OUTPUT);
|
|
digitalWrite(TFT_DC, HIGH); // Data/Command high = data mode
|
|
#endif
|
|
|
|
_booted = false;
|
|
end_tft_write();
|
|
} // end of: if just _booted
|
|
|
|
// Toggle RST low to reset
|
|
begin_tft_write();
|
|
|
|
#ifdef TFT_RST
|
|
if (TFT_RST >= 0) {
|
|
digitalWrite(TFT_RST, HIGH);
|
|
delay(5);
|
|
digitalWrite(TFT_RST, LOW);
|
|
delay(20);
|
|
digitalWrite(TFT_RST, HIGH);
|
|
} else writecommand(TFT_SWRST); // Software reset
|
|
#else
|
|
writecommand(TFT_SWRST); // Software reset
|
|
#endif
|
|
|
|
end_tft_write();
|
|
|
|
delay(150); // Wait for reset to complete
|
|
|
|
begin_tft_write();
|
|
|
|
tc = tc; // Supress warning
|
|
|
|
// lewis add. Mark : Used to set different models
|
|
switch (drv.tft_driver) {
|
|
case 0x7789:/*ST7789_DRIVER*/
|
|
#include "TFT_Drivers/ST7789_Init.h"
|
|
break;
|
|
case 0x7796:/*ST7796_DRIVER*/
|
|
#include "TFT_Drivers/ST7796_Init.h"
|
|
break;
|
|
case 0x9488:/*ILI9488_DRIVER*/
|
|
#include "TFT_Drivers/ILI9488_Init.h"
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
#ifdef TFT_INVERSION_ON
|
|
writecommand(TFT_INVON);
|
|
#endif
|
|
|
|
#ifdef TFT_INVERSION_OFF
|
|
writecommand(TFT_INVOFF);
|
|
#endif
|
|
|
|
end_tft_write();
|
|
|
|
setRotation(rotation);
|
|
|
|
#if defined (TFT_BL) && defined (TFT_BACKLIGHT_ON)
|
|
pinMode(TFT_BL, OUTPUT);
|
|
digitalWrite(TFT_BL, TFT_BACKLIGHT_ON);
|
|
#else
|
|
#if defined (TFT_BL) && defined (M5STACK)
|
|
// Turn on the back-light LED
|
|
pinMode(TFT_BL, OUTPUT);
|
|
digitalWrite(TFT_BL, HIGH);
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: setRotation
|
|
** Description: rotate the screen orientation m = 0-3 or 4-7 for BMP drawing
|
|
***************************************************************************************/
|
|
void TFT_eSPI::setRotation(uint8_t m)
|
|
{
|
|
|
|
begin_tft_write();
|
|
|
|
// lewis add. Mark : Used to set different models
|
|
switch (drv.tft_driver) {
|
|
case 0x7789:/*ST7789_DRIVER*/
|
|
#include "TFT_Drivers/ST7789_Rotation.h"
|
|
break;
|
|
case 0x7796:/*ST7796_DRIVER*/
|
|
#include "TFT_Drivers/ST7796_Rotation.h"
|
|
break;
|
|
case 0x9488:/*ILI9488_DRIVER*/
|
|
#include "TFT_Drivers/ILI9488_Rotation.h"
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
delayMicroseconds(10);
|
|
|
|
end_tft_write();
|
|
|
|
addr_row = 0xFFFF;
|
|
addr_col = 0xFFFF;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: commandList, used for FLASH based lists only (e.g. ST7735)
|
|
** Description: Get initialisation commands from FLASH and send to TFT
|
|
***************************************************************************************/
|
|
void TFT_eSPI::commandList (const uint8_t *addr)
|
|
{
|
|
uint8_t numCommands;
|
|
uint8_t numArgs;
|
|
uint8_t ms;
|
|
|
|
numCommands = pgm_read_byte(addr++); // Number of commands to follow
|
|
|
|
while (numCommands--) { // For each command...
|
|
writecommand(pgm_read_byte(addr++)); // Read, issue command
|
|
numArgs = pgm_read_byte(addr++); // Number of args to follow
|
|
ms = numArgs & TFT_INIT_DELAY; // If hibit set, delay follows args
|
|
numArgs &= ~TFT_INIT_DELAY; // Mask out delay bit
|
|
|
|
while (numArgs--) { // For each argument...
|
|
writedata(pgm_read_byte(addr++)); // Read, issue argument
|
|
}
|
|
|
|
if (ms) {
|
|
ms = pgm_read_byte(addr++); // Read post-command delay time (ms)
|
|
delay( (ms == 255 ? 500 : ms) );
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: spiwrite
|
|
** Description: Write 8 bits to SPI port (legacy support only)
|
|
***************************************************************************************/
|
|
void TFT_eSPI::spiwrite(uint8_t c)
|
|
{
|
|
begin_tft_write();
|
|
tft_Write_8(c);
|
|
end_tft_write();
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: writecommand
|
|
** Description: Send an 8 bit command to the TFT
|
|
***************************************************************************************/
|
|
void TFT_eSPI::writecommand(uint8_t c)
|
|
{
|
|
begin_tft_write();
|
|
|
|
DC_C;
|
|
|
|
tft_Write_8(c);
|
|
|
|
DC_D;
|
|
|
|
end_tft_write();
|
|
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: writedata
|
|
** Description: Send a 8 bit data value to the TFT
|
|
***************************************************************************************/
|
|
void TFT_eSPI::writedata(uint8_t d)
|
|
{
|
|
begin_tft_write();
|
|
|
|
DC_D; // Play safe, but should already be in data mode
|
|
|
|
tft_Write_8(d);
|
|
|
|
CS_L; // Allow more hold time for low VDI rail
|
|
|
|
end_tft_write();
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: readcommand8
|
|
** Description: Read a 8 bit data value from an indexed command register
|
|
***************************************************************************************/
|
|
uint8_t TFT_eSPI::readcommand8(uint8_t cmd_function, uint8_t index)
|
|
{
|
|
uint8_t reg = 0;
|
|
#ifdef TFT_PARALLEL_8_BIT
|
|
|
|
writecommand(cmd_function); // Sets DC and CS high
|
|
|
|
busDir(dir_mask, INPUT);
|
|
|
|
CS_L;
|
|
|
|
// Read nth parameter (assumes caller discards 1st parameter or points index to 2nd)
|
|
while (index--) reg = readByte();
|
|
|
|
busDir(dir_mask, OUTPUT);
|
|
|
|
CS_H;
|
|
|
|
#else // SPI interface
|
|
// Tested with ILI9341 set to Interface II i.e. IM [3:0] = "1101"
|
|
begin_tft_read();
|
|
index = 0x10 + (index & 0x0F);
|
|
|
|
DC_C; tft_Write_8(0xD9);
|
|
DC_D; tft_Write_8(index);
|
|
|
|
CS_H; // Some displays seem to need CS to be pulsed here, or is just a delay needed?
|
|
CS_L;
|
|
|
|
DC_C; tft_Write_8(cmd_function);
|
|
DC_D;
|
|
reg = tft_Read_8();
|
|
|
|
end_tft_read();
|
|
#endif
|
|
return reg;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: readcommand16
|
|
** Description: Read a 16 bit data value from an indexed command register
|
|
***************************************************************************************/
|
|
uint16_t TFT_eSPI::readcommand16(uint8_t cmd_function, uint8_t index)
|
|
{
|
|
uint32_t reg;
|
|
|
|
reg = (readcommand8(cmd_function, index + 0) << 8);
|
|
reg |= (readcommand8(cmd_function, index + 1) << 0);
|
|
|
|
return reg;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: readcommand32
|
|
** Description: Read a 32 bit data value from an indexed command register
|
|
***************************************************************************************/
|
|
uint32_t TFT_eSPI::readcommand32(uint8_t cmd_function, uint8_t index)
|
|
{
|
|
uint32_t reg;
|
|
|
|
reg = ((uint32_t)readcommand8(cmd_function, index + 0) << 24);
|
|
reg |= ((uint32_t)readcommand8(cmd_function, index + 1) << 16);
|
|
reg |= ((uint32_t)readcommand8(cmd_function, index + 2) << 8);
|
|
reg |= ((uint32_t)readcommand8(cmd_function, index + 3) << 0);
|
|
|
|
return reg;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: read pixel (for SPI Interface II i.e. IM [3:0] = "1101")
|
|
** Description: Read 565 pixel colours from a pixel
|
|
***************************************************************************************/
|
|
uint16_t TFT_eSPI::readPixel(int32_t x0, int32_t y0)
|
|
{
|
|
#if defined(TFT_PARALLEL_8_BIT)
|
|
|
|
CS_L;
|
|
|
|
readAddrWindow(x0, y0, 1, 1);
|
|
|
|
// Set masked pins D0- D7 to input
|
|
busDir(dir_mask, INPUT);
|
|
|
|
// Dummy read to throw away don't care value
|
|
readByte();
|
|
|
|
// Fetch the 16 bit BRG pixel
|
|
//uint16_t rgb = (readByte() << 8) | readByte();
|
|
|
|
#if defined (ILI9341_DRIVER) | defined (ILI9488_DRIVER) // Read 3 bytes
|
|
|
|
// Read window pixel 24 bit RGB values and fill in LS bits
|
|
uint16_t rgb = ((readByte() & 0xF8) << 8) | ((readByte() & 0xFC) << 3) | (readByte() >> 3);
|
|
|
|
CS_H;
|
|
|
|
// Set masked pins D0- D7 to output
|
|
busDir(dir_mask, OUTPUT);
|
|
|
|
return rgb;
|
|
|
|
#else // ILI9481 or ILI9486 16 bit read
|
|
|
|
// Fetch the 16 bit BRG pixel
|
|
uint16_t bgr = (readByte() << 8) | readByte();
|
|
|
|
CS_H;
|
|
|
|
// Set masked pins D0- D7 to output
|
|
busDir(dir_mask, OUTPUT);
|
|
|
|
#ifdef ILI9486_DRIVER
|
|
return bgr;
|
|
#else
|
|
// Swap Red and Blue (could check MADCTL setting to see if this is needed)
|
|
return (bgr >> 11) | (bgr << 11) | (bgr & 0x7E0);
|
|
#endif
|
|
|
|
#endif
|
|
|
|
#else // Not TFT_PARALLEL_8_BIT
|
|
|
|
// This function can get called during antialiased font rendering
|
|
// so a transaction may be in progress
|
|
bool wasInTransaction = inTransaction;
|
|
if (inTransaction) {
|
|
inTransaction = false;
|
|
end_tft_write();
|
|
}
|
|
|
|
begin_tft_read();
|
|
|
|
readAddrWindow(x0, y0, 1, 1); // Sets CS low
|
|
|
|
#ifdef TFT_SDA_READ
|
|
begin_SDA_Read();
|
|
#endif
|
|
|
|
// Dummy read to throw away don't care value
|
|
tft_Read_8();
|
|
|
|
//#if !defined (ILI9488_DRIVER)
|
|
|
|
// Read the 3 RGB bytes, colour is actually only in the top 6 bits of each byte
|
|
// as the TFT stores colours as 18 bits
|
|
uint8_t r = tft_Read_8();
|
|
uint8_t g = tft_Read_8();
|
|
uint8_t b = tft_Read_8();
|
|
/*
|
|
#else
|
|
|
|
// The 6 colour bits are in MS 6 bits of each byte, but the ILI9488 needs an extra clock pulse
|
|
// so bits appear shifted right 1 bit, so mask the middle 6 bits then shift 1 place left
|
|
uint8_t r = (tft_Read_8()&0x7E)<<1;
|
|
uint8_t g = (tft_Read_8()&0x7E)<<1;
|
|
uint8_t b = (tft_Read_8()&0x7E)<<1;
|
|
|
|
#endif
|
|
*/
|
|
CS_H;
|
|
|
|
#ifdef TFT_SDA_READ
|
|
end_SDA_Read();
|
|
#endif
|
|
|
|
end_tft_read();
|
|
|
|
// Reinstate the transaction if one was in progress
|
|
if (wasInTransaction) {
|
|
begin_tft_write();
|
|
inTransaction = true;
|
|
}
|
|
|
|
return color565(r, g, b);
|
|
|
|
#endif
|
|
}
|
|
|
|
void TFT_eSPI::setCallback(getColorCallback getCol)
|
|
{
|
|
getColor = getCol;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: read rectangle (for SPI Interface II i.e. IM [3:0] = "1101")
|
|
** Description: Read 565 pixel colours from a defined area
|
|
***************************************************************************************/
|
|
void TFT_eSPI::readRect(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t *data)
|
|
{
|
|
if ((x > _width) || (y > _height) || (w == 0) || (h == 0)) return;
|
|
|
|
#if defined(TFT_PARALLEL_8_BIT)
|
|
|
|
CS_L;
|
|
|
|
readAddrWindow(x, y, w, h);
|
|
|
|
// Set masked pins D0- D7 to input
|
|
busDir(dir_mask, INPUT);
|
|
|
|
// Dummy read to throw away don't care value
|
|
readByte();
|
|
|
|
// Total pixel count
|
|
uint32_t len = w * h;
|
|
|
|
#if defined (ILI9341_DRIVER) | defined (ILI9488_DRIVER) // Read 3 bytes
|
|
// Fetch the 24 bit RGB value
|
|
while (len--) {
|
|
// Assemble the RGB 16 bit colour
|
|
uint16_t rgb = ((readByte() & 0xF8) << 8) | ((readByte() & 0xFC) << 3) | (readByte() >> 3);
|
|
|
|
// Swapped byte order for compatibility with pushRect()
|
|
*data++ = (rgb << 8) | (rgb >> 8);
|
|
}
|
|
#else // ILI9481 reads as 16 bits
|
|
// Fetch the 16 bit BRG pixels
|
|
while (len--) {
|
|
#ifdef ILI9486_DRIVER
|
|
// Read the RGB 16 bit colour
|
|
*data++ = readByte() | (readByte() << 8);
|
|
#else
|
|
// Read the BRG 16 bit colour
|
|
uint16_t bgr = (readByte() << 8) | readByte();
|
|
// Swap Red and Blue (could check MADCTL setting to see if this is needed)
|
|
uint16_t rgb = (bgr >> 11) | (bgr << 11) | (bgr & 0x7E0);
|
|
// Swapped byte order for compatibility with pushRect()
|
|
*data++ = (rgb << 8) | (rgb >> 8);
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
CS_H;
|
|
|
|
// Set masked pins D0- D7 to output
|
|
busDir(dir_mask, OUTPUT);
|
|
|
|
#else // SPI interface
|
|
|
|
begin_tft_read();
|
|
|
|
readAddrWindow(x, y, w, h);
|
|
|
|
#ifdef TFT_SDA_READ
|
|
begin_SDA_Read();
|
|
#endif
|
|
|
|
// Dummy read to throw away don't care value
|
|
tft_Read_8();
|
|
|
|
// Read window pixel 24 bit RGB values
|
|
uint32_t len = w * h;
|
|
while (len--) {
|
|
|
|
#if !defined (ILI9488_DRIVER)
|
|
|
|
// Read the 3 RGB bytes, colour is actually only in the top 6 bits of each byte
|
|
// as the TFT stores colours as 18 bits
|
|
uint8_t r = tft_Read_8();
|
|
uint8_t g = tft_Read_8();
|
|
uint8_t b = tft_Read_8();
|
|
|
|
#else
|
|
|
|
// The 6 colour bits are in MS 6 bits of each byte but we do not include the extra clock pulse
|
|
// so we use a trick and mask the middle 6 bits of the byte, then only shift 1 place left
|
|
uint8_t r = (tft_Read_8() & 0x7E) << 1;
|
|
uint8_t g = (tft_Read_8() & 0x7E) << 1;
|
|
uint8_t b = (tft_Read_8() & 0x7E) << 1;
|
|
|
|
#endif
|
|
|
|
// Swapped colour byte order for compatibility with pushRect()
|
|
*data++ = (r & 0xF8) | (g & 0xE0) >> 5 | (b & 0xF8) << 5 | (g & 0x1C) << 11;
|
|
}
|
|
|
|
CS_H;
|
|
|
|
#ifdef TFT_SDA_READ
|
|
end_SDA_Read();
|
|
#endif
|
|
|
|
end_tft_read();
|
|
|
|
#endif
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: push rectangle
|
|
** Description: push 565 pixel colours into a defined area
|
|
***************************************************************************************/
|
|
void TFT_eSPI::pushRect(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t *data)
|
|
{
|
|
bool swap = _swapBytes; _swapBytes = false;
|
|
pushImage(x, y, w, h, data);
|
|
_swapBytes = swap;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: pushImage
|
|
** Description: plot 16 bit colour sprite or image onto TFT
|
|
***************************************************************************************/
|
|
void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t *data)
|
|
{
|
|
|
|
if ((x >= _width) || (y >= _height)) return;
|
|
|
|
int32_t dx = 0;
|
|
int32_t dy = 0;
|
|
int32_t dw = w;
|
|
int32_t dh = h;
|
|
|
|
if (x < 0) {
|
|
dw += x;
|
|
dx = -x;
|
|
x = 0;
|
|
}
|
|
if (y < 0) {
|
|
dh += y;
|
|
dy = -y;
|
|
y = 0;
|
|
}
|
|
|
|
if ((x + dw) > _width ) dw = _width - x;
|
|
if ((y + dh) > _height) dh = _height - y;
|
|
|
|
if (dw < 1 || dh < 1) return;
|
|
|
|
begin_tft_write();
|
|
inTransaction = true;
|
|
|
|
setWindow(x, y, x + dw - 1, y + dh - 1);
|
|
|
|
data += dx + dy * w;
|
|
|
|
// Check if whole image can be pushed
|
|
if (dw == w) pushPixels(data, dw * dh);
|
|
else {
|
|
// Push line segments to crop image
|
|
while (dh--) {
|
|
pushPixels(data, dw);
|
|
data += w;
|
|
}
|
|
}
|
|
|
|
inTransaction = false;
|
|
end_tft_write();
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: pushImage
|
|
** Description: plot 16 bit sprite or image with 1 colour being transparent
|
|
***************************************************************************************/
|
|
void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t *data, uint16_t transp)
|
|
{
|
|
|
|
if ((x >= _width) || (y >= _height)) return;
|
|
|
|
int32_t dx = 0;
|
|
int32_t dy = 0;
|
|
int32_t dw = w;
|
|
int32_t dh = h;
|
|
|
|
if (x < 0) {
|
|
dw += x;
|
|
dx = -x;
|
|
x = 0;
|
|
}
|
|
if (y < 0) {
|
|
dh += y;
|
|
dy = -y;
|
|
y = 0;
|
|
}
|
|
|
|
if ((x + dw) > _width ) dw = _width - x;
|
|
if ((y + dh) > _height) dh = _height - y;
|
|
|
|
if (dw < 1 || dh < 1) return;
|
|
|
|
begin_tft_write();
|
|
inTransaction = true;
|
|
|
|
data += dx + dy * w;
|
|
|
|
int32_t xe = x + dw - 1, ye = y + dh - 1;
|
|
|
|
uint16_t lineBuf[dw]; // Use buffer to minimise setWindow call count
|
|
|
|
if (!_swapBytes) transp = transp >> 8 | transp << 8;
|
|
|
|
while (dh--) {
|
|
int32_t len = dw;
|
|
uint16_t *ptr = data;
|
|
int32_t px = x;
|
|
bool move = true;
|
|
uint16_t np = 0;
|
|
|
|
while (len--) {
|
|
if (transp != *ptr) {
|
|
if (move) {
|
|
move = false;
|
|
setWindow(px, y, xe, ye);
|
|
}
|
|
lineBuf[np] = *ptr;
|
|
np++;
|
|
} else {
|
|
move = true;
|
|
if (np) {
|
|
pushPixels((uint16_t *)lineBuf, np);
|
|
np = 0;
|
|
}
|
|
}
|
|
px++;
|
|
ptr++;
|
|
}
|
|
if (np) pushPixels((uint16_t *)lineBuf, np);
|
|
|
|
y++;
|
|
data += w;
|
|
}
|
|
|
|
inTransaction = false;
|
|
end_tft_write();
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: pushImage - for FLASH (PROGMEM) stored images
|
|
** Description: plot 16 bit image
|
|
***************************************************************************************/
|
|
void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, const uint16_t *data)
|
|
{
|
|
// Requires 32 bit aligned access, so use PROGMEM 16 bit word functions
|
|
if ((x >= _width) || (y >= _height)) return;
|
|
|
|
int32_t dx = 0;
|
|
int32_t dy = 0;
|
|
int32_t dw = w;
|
|
int32_t dh = h;
|
|
|
|
if (x < 0) {
|
|
dw += x;
|
|
dx = -x;
|
|
x = 0;
|
|
}
|
|
if (y < 0) {
|
|
dh += y;
|
|
dy = -y;
|
|
y = 0;
|
|
}
|
|
|
|
if ((x + dw) > _width ) dw = _width - x;
|
|
if ((y + dh) > _height) dh = _height - y;
|
|
|
|
if (dw < 1 || dh < 1) return;
|
|
|
|
begin_tft_write();
|
|
inTransaction = true;
|
|
|
|
data += dx + dy * w;
|
|
|
|
uint16_t buffer[dw];
|
|
|
|
setWindow(x, y, x + dw - 1, y + dh - 1);
|
|
|
|
// Fill and send line buffers to TFT
|
|
for (int32_t i = 0; i < dh; i++) {
|
|
for (int32_t j = 0; j < dw; j++) {
|
|
buffer[j] = pgm_read_word(&data[i * w + j]);
|
|
}
|
|
pushPixels(buffer, dw);
|
|
}
|
|
|
|
inTransaction = false;
|
|
end_tft_write();
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: pushImage - for FLASH (PROGMEM) stored images
|
|
** Description: plot 16 bit image with 1 colour being transparent
|
|
***************************************************************************************/
|
|
void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, const uint16_t *data, uint16_t transp)
|
|
{
|
|
// Requires 32 bit aligned access, so use PROGMEM 16 bit word functions
|
|
if ((x >= _width) || (y >= (int32_t)_height)) return;
|
|
|
|
int32_t dx = 0;
|
|
int32_t dy = 0;
|
|
int32_t dw = w;
|
|
int32_t dh = h;
|
|
|
|
if (x < 0) {
|
|
dw += x;
|
|
dx = -x;
|
|
x = 0;
|
|
}
|
|
if (y < 0) {
|
|
dh += y;
|
|
dy = -y;
|
|
y = 0;
|
|
}
|
|
|
|
if ((x + dw) > _width ) dw = _width - x;
|
|
if ((y + dh) > _height) dh = _height - y;
|
|
|
|
if (dw < 1 || dh < 1) return;
|
|
|
|
begin_tft_write();
|
|
inTransaction = true;
|
|
|
|
data += dx + dy * w;
|
|
|
|
int32_t xe = x + dw - 1, ye = y + dh - 1;
|
|
|
|
uint16_t lineBuf[dw];
|
|
|
|
if (!_swapBytes) transp = transp >> 8 | transp << 8;
|
|
|
|
while (dh--) {
|
|
int32_t len = dw;
|
|
uint16_t *ptr = (uint16_t *)data;
|
|
int32_t px = x;
|
|
bool move = true;
|
|
|
|
uint16_t np = 0;
|
|
|
|
while (len--) {
|
|
uint16_t color = pgm_read_word(ptr);
|
|
if (transp != color) {
|
|
if (move) {
|
|
move = false;
|
|
setWindow(px, y, xe, ye);
|
|
}
|
|
lineBuf[np] = color;
|
|
np++;
|
|
} else {
|
|
move = true;
|
|
if (np) {
|
|
pushPixels(lineBuf, np);
|
|
np = 0;
|
|
}
|
|
}
|
|
px++;
|
|
ptr++;
|
|
}
|
|
if (np) pushPixels(lineBuf, np);
|
|
|
|
y++;
|
|
data += w;
|
|
}
|
|
|
|
inTransaction = false;
|
|
end_tft_write();
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: pushImage
|
|
** Description: plot 8 bit or 4 bit or 1 bit image or sprite using a line buffer
|
|
***************************************************************************************/
|
|
void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t *data, bool bpp8, uint16_t *cmap)
|
|
{
|
|
|
|
if ((x >= _width) || (y >= (int32_t)_height)) return;
|
|
|
|
int32_t dx = 0;
|
|
int32_t dy = 0;
|
|
int32_t dw = w;
|
|
int32_t dh = h;
|
|
|
|
if (x < 0) {
|
|
dw += x;
|
|
dx = -x;
|
|
x = 0;
|
|
}
|
|
if (y < 0) {
|
|
dh += y;
|
|
dy = -y;
|
|
y = 0;
|
|
}
|
|
|
|
if ((x + dw) > _width ) dw = _width - x;
|
|
if ((y + dh) > _height) dh = _height - y;
|
|
|
|
if (dw < 1 || dh < 1) return;
|
|
|
|
begin_tft_write();
|
|
inTransaction = true;
|
|
|
|
setWindow(x, y, x + dw - 1, y + dh - 1); // Sets CS low and sent RAMWR
|
|
|
|
// Line buffer makes plotting faster
|
|
uint16_t lineBuf[dw];
|
|
|
|
if (bpp8) {
|
|
bool swap = _swapBytes; _swapBytes = false;
|
|
|
|
uint8_t blue[] = {0, 11, 21, 31}; // blue 2 to 5 bit colour lookup table
|
|
|
|
_lastColor = -1; // Set to illegal value
|
|
|
|
// Used to store last shifted colour
|
|
uint8_t msbColor = 0;
|
|
uint8_t lsbColor = 0;
|
|
|
|
data += dx + dy * w;
|
|
while (dh--) {
|
|
uint32_t len = dw;
|
|
uint8_t *ptr = data;
|
|
uint8_t *linePtr = (uint8_t *)lineBuf;
|
|
|
|
while (len--) {
|
|
uint32_t color = *ptr++;
|
|
|
|
// Shifts are slow so check if colour has changed first
|
|
if (color != _lastColor) {
|
|
// =====Green===== ===============Red==============
|
|
msbColor = (color & 0x1C) >> 2 | (color & 0xC0) >> 3 | (color & 0xE0);
|
|
// =====Green===== =======Blue======
|
|
lsbColor = (color & 0x1C) << 3 | blue[color & 0x03];
|
|
_lastColor = color;
|
|
}
|
|
|
|
*linePtr++ = msbColor;
|
|
*linePtr++ = lsbColor;
|
|
}
|
|
|
|
pushPixels(lineBuf, dw);
|
|
|
|
data += w;
|
|
}
|
|
_swapBytes = swap; // Restore old value
|
|
} else if (cmap != nullptr) { // Must be 4bpp
|
|
bool swap = _swapBytes; _swapBytes = true;
|
|
|
|
w = (w + 1) & 0xFFFE; // if this is a sprite, w will already be even; this does no harm.
|
|
bool splitFirst = (dx & 0x01) != 0; // split first means we have to push a single px from the left of the sprite / image
|
|
|
|
if (splitFirst) {
|
|
data += ((dx - 1 + dy * w) >> 1);
|
|
} else {
|
|
data += ((dx + dy * w) >> 1);
|
|
}
|
|
|
|
while (dh--) {
|
|
uint32_t len = dw;
|
|
uint8_t *ptr = data;
|
|
uint16_t *linePtr = lineBuf;
|
|
uint8_t colors; // two colors in one byte
|
|
uint16_t index;
|
|
|
|
if (splitFirst) {
|
|
colors = *ptr;
|
|
index = (colors & 0x0F);
|
|
*linePtr++ = cmap[index];
|
|
len--;
|
|
ptr++;
|
|
}
|
|
|
|
while (len--) {
|
|
colors = *ptr;
|
|
index = ((colors & 0xF0) >> 4) & 0x0F;
|
|
*linePtr++ = cmap[index];
|
|
|
|
if (len--) {
|
|
index = colors & 0x0F;
|
|
*linePtr++ = cmap[index];
|
|
} else {
|
|
break; // nothing to do here
|
|
}
|
|
|
|
ptr++;
|
|
}
|
|
|
|
pushPixels(lineBuf, dw);
|
|
data += (w >> 1);
|
|
}
|
|
_swapBytes = swap; // Restore old value
|
|
} else { // Must be 1bpp
|
|
bool swap = _swapBytes; _swapBytes = false;
|
|
while (dh--) {
|
|
w = (w + 7) & 0xFFF8;
|
|
|
|
int32_t len = dw;
|
|
uint8_t *ptr = data;
|
|
uint8_t *linePtr = (uint8_t *)lineBuf;
|
|
uint8_t bits = 8;
|
|
while (len > 0) {
|
|
if (len < 8) bits = len;
|
|
uint32_t xp = dx;
|
|
for (uint16_t i = 0; i < bits; i++) {
|
|
uint8_t col = (ptr[(xp + dy * w) >> 3] << (xp & 0x7)) & 0x80;
|
|
if (col) {
|
|
*linePtr++ = bitmap_fg >> 8;
|
|
*linePtr++ = (uint8_t) bitmap_fg;
|
|
} else {
|
|
*linePtr++ = bitmap_bg >> 8;
|
|
*linePtr++ = (uint8_t) bitmap_bg;
|
|
}
|
|
//if (col) drawPixel((dw-len)+xp,h-dh,bitmap_fg);
|
|
//else drawPixel((dw-len)+xp,h-dh,bitmap_bg);
|
|
xp++;
|
|
}
|
|
ptr++;
|
|
len -= 8;
|
|
}
|
|
|
|
pushPixels(lineBuf, dw);
|
|
|
|
dy++;
|
|
}
|
|
_swapBytes = swap; // Restore old value
|
|
}
|
|
|
|
inTransaction = false;
|
|
end_tft_write();
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: pushImage
|
|
** Description: plot 8 or 4 or 1 bit image or sprite with a transparent colour
|
|
***************************************************************************************/
|
|
void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t *data, uint8_t transp, bool bpp8, uint16_t *cmap)
|
|
{
|
|
if ((x >= _width) || (y >= _height)) return;
|
|
|
|
int32_t dx = 0;
|
|
int32_t dy = 0;
|
|
int32_t dw = w;
|
|
int32_t dh = h;
|
|
|
|
if (x < 0) {
|
|
dw += x;
|
|
dx = -x;
|
|
x = 0;
|
|
}
|
|
if (y < 0) {
|
|
dh += y;
|
|
dy = -y;
|
|
y = 0;
|
|
}
|
|
|
|
if ((x + dw) > _width ) dw = _width - x;
|
|
if ((y + dh) > _height) dh = _height - y;
|
|
|
|
if (dw < 1 || dh < 1) return;
|
|
|
|
begin_tft_write();
|
|
inTransaction = true;
|
|
|
|
int32_t xe = x + dw - 1, ye = y + dh - 1;
|
|
|
|
// Line buffer makes plotting faster
|
|
uint16_t lineBuf[dw];
|
|
|
|
if (bpp8) { // 8 bits per pixel
|
|
bool swap = _swapBytes; _swapBytes = false;
|
|
|
|
data += dx + dy * w;
|
|
|
|
uint8_t blue[] = {0, 11, 21, 31}; // blue 2 to 5 bit colour lookup table
|
|
|
|
_lastColor = -1; // Set to illegal value
|
|
|
|
// Used to store last shifted colour
|
|
uint8_t msbColor = 0;
|
|
uint8_t lsbColor = 0;
|
|
|
|
//int32_t spx = x, spy = y;
|
|
|
|
while (dh--) {
|
|
int32_t len = dw;
|
|
uint8_t *ptr = data;
|
|
uint8_t *linePtr = (uint8_t *)lineBuf;
|
|
|
|
int32_t px = x;
|
|
bool move = true;
|
|
uint16_t np = 0;
|
|
|
|
while (len--) {
|
|
if (transp != *ptr) {
|
|
if (move) {
|
|
move = false;
|
|
setWindow(px, y, xe, ye);
|
|
}
|
|
uint8_t color = *ptr;
|
|
|
|
// Shifts are slow so check if colour has changed first
|
|
if (color != _lastColor) {
|
|
// =====Green===== ===============Red==============
|
|
msbColor = (color & 0x1C) >> 2 | (color & 0xC0) >> 3 | (color & 0xE0);
|
|
// =====Green===== =======Blue======
|
|
lsbColor = (color & 0x1C) << 3 | blue[color & 0x03];
|
|
_lastColor = color;
|
|
}
|
|
*linePtr++ = msbColor;
|
|
*linePtr++ = lsbColor;
|
|
np++;
|
|
} else {
|
|
move = true;
|
|
if (np) {
|
|
pushPixels(lineBuf, np);
|
|
linePtr = (uint8_t *)lineBuf;
|
|
np = 0;
|
|
}
|
|
}
|
|
px++;
|
|
ptr++;
|
|
}
|
|
|
|
if (np) pushPixels(lineBuf, np);
|
|
y++;
|
|
data += w;
|
|
}
|
|
_swapBytes = swap; // Restore old value
|
|
} else if (cmap != nullptr) { // 4bpp with color map
|
|
bool swap = _swapBytes; _swapBytes = true;
|
|
|
|
w = (w + 1) & 0xFFFE; // here we try to recreate iwidth from dwidth.
|
|
bool splitFirst = ((dx & 0x01) != 0);
|
|
if (splitFirst) {
|
|
data += ((dx - 1 + dy * w) >> 1);
|
|
} else {
|
|
data += ((dx + dy * w) >> 1);
|
|
}
|
|
|
|
while (dh--) {
|
|
uint32_t len = dw;
|
|
uint8_t *ptr = data;
|
|
|
|
int32_t px = x;
|
|
bool move = true;
|
|
uint16_t np = 0;
|
|
|
|
uint8_t index; // index into cmap.
|
|
|
|
if (splitFirst) {
|
|
index = (*ptr & 0x0F); // odd = bits 3 .. 0
|
|
if (index != transp) {
|
|
move = false; setWindow(px, y, xe, ye);
|
|
lineBuf[np] = cmap[index];
|
|
np++;
|
|
}
|
|
px++; ptr++;
|
|
len--;
|
|
}
|
|
|
|
while (len--) {
|
|
uint8_t color = *ptr;
|
|
|
|
// find the actual color you care about. There will be two pixels here!
|
|
// but we may only want one at the end of the row
|
|
uint16_t index = ((color & 0xF0) >> 4) & 0x0F; // high bits are the even numbers
|
|
if (index != transp) {
|
|
if (move) {
|
|
move = false; setWindow(px, y, xe, ye);
|
|
}
|
|
lineBuf[np] = cmap[index];
|
|
np++; // added a pixel
|
|
} else {
|
|
move = true;
|
|
if (np) {
|
|
pushPixels(lineBuf, np);
|
|
np = 0;
|
|
}
|
|
}
|
|
px++;
|
|
|
|
if (len--) {
|
|
index = color & 0x0F; // the odd number is 3 .. 0
|
|
if (index != transp) {
|
|
if (move) {
|
|
move = false; setWindow(px, y, xe, ye);
|
|
}
|
|
lineBuf[np] = cmap[index];
|
|
np++;
|
|
} else {
|
|
move = true;
|
|
if (np) {
|
|
pushPixels(lineBuf, np);
|
|
np = 0;
|
|
}
|
|
}
|
|
px++;
|
|
} else {
|
|
break; // we are done with this row.
|
|
}
|
|
ptr++; // we only increment ptr once in the loop (deliberate)
|
|
}
|
|
|
|
if (np) {
|
|
pushPixels(lineBuf, np);
|
|
np = 0;
|
|
}
|
|
data += (w >> 1);
|
|
y++;
|
|
}
|
|
_swapBytes = swap; // Restore old value
|
|
} else { // 1 bit per pixel
|
|
bool swap = _swapBytes; _swapBytes = false;
|
|
w = (w + 7) & 0xFFF8;
|
|
while (dh--) {
|
|
int32_t px = x;
|
|
bool move = true;
|
|
uint16_t np = 0;
|
|
int32_t len = dw;
|
|
uint8_t *ptr = data;
|
|
uint8_t bits = 8;
|
|
while (len > 0) {
|
|
if (len < 8) bits = len;
|
|
uint32_t xp = dx;
|
|
uint32_t yp = (dy * w) >> 3;
|
|
for (uint16_t i = 0; i < bits; i++) {
|
|
//uint8_t col = (ptr[(xp + dy * w)>>3] << (xp & 0x7)) & 0x80;
|
|
if ((ptr[(xp >> 3) + yp] << (xp & 0x7)) & 0x80) {
|
|
if (move) {
|
|
move = false;
|
|
setWindow(px, y, xe, ye);
|
|
}
|
|
np++;
|
|
} else {
|
|
if (np) {
|
|
pushBlock(bitmap_fg, np);
|
|
np = 0;
|
|
move = true;
|
|
}
|
|
}
|
|
px++;
|
|
xp++;
|
|
}
|
|
ptr++;
|
|
len -= 8;
|
|
}
|
|
|
|
if (np) pushBlock(bitmap_fg, np);
|
|
y++;
|
|
dy++;
|
|
}
|
|
_swapBytes = swap; // Restore old value
|
|
}
|
|
|
|
inTransaction = false;
|
|
end_tft_write();
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: setSwapBytes
|
|
** Description: Used by 16 bit pushImage() to swap byte order in colours
|
|
***************************************************************************************/
|
|
void TFT_eSPI::setSwapBytes(bool swap)
|
|
{
|
|
_swapBytes = swap;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: getSwapBytes
|
|
** Description: Return the swap byte order for colours
|
|
***************************************************************************************/
|
|
bool TFT_eSPI::getSwapBytes(void)
|
|
{
|
|
return _swapBytes;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: read rectangle (for SPI Interface II i.e. IM [3:0] = "1101")
|
|
** Description: Read RGB pixel colours from a defined area
|
|
***************************************************************************************/
|
|
// If w and h are 1, then 1 pixel is read, *data array size must be 3 bytes per pixel
|
|
void TFT_eSPI::readRectRGB(int32_t x0, int32_t y0, int32_t w, int32_t h, uint8_t *data)
|
|
{
|
|
#if defined(TFT_PARALLEL_8_BIT)
|
|
|
|
uint32_t len = w * h;
|
|
uint8_t *buf565 = data + len;
|
|
|
|
readRect(x0, y0, w, h, (uint16_t *)buf565);
|
|
|
|
while (len--) {
|
|
uint16_t pixel565 = (*buf565++) << 8;
|
|
pixel565 |= *buf565++;
|
|
uint8_t red = (pixel565 & 0xF800) >> 8; red |= red >> 5;
|
|
uint8_t green = (pixel565 & 0x07E0) >> 3; green |= green >> 6;
|
|
uint8_t blue = (pixel565 & 0x001F) << 3; blue |= blue >> 5;
|
|
*data++ = red;
|
|
*data++ = green;
|
|
*data++ = blue;
|
|
}
|
|
|
|
#else // Not TFT_PARALLEL_8_BIT
|
|
|
|
begin_tft_read();
|
|
|
|
readAddrWindow(x0, y0, w, h); // Sets CS low
|
|
|
|
#ifdef TFT_SDA_READ
|
|
begin_SDA_Read();
|
|
#endif
|
|
|
|
// Dummy read to throw away don't care value
|
|
tft_Read_8();
|
|
|
|
// Read window pixel 24 bit RGB values, buffer must be set in sketch to 3 * w * h
|
|
uint32_t len = w * h;
|
|
while (len--) {
|
|
|
|
#if !defined (ILI9488_DRIVER)
|
|
|
|
// Read the 3 RGB bytes, colour is actually only in the top 6 bits of each byte
|
|
// as the TFT stores colours as 18 bits
|
|
*data++ = tft_Read_8();
|
|
*data++ = tft_Read_8();
|
|
*data++ = tft_Read_8();
|
|
|
|
#else
|
|
|
|
// The 6 colour bits are in MS 6 bits of each byte, but the ILI9488 needs an extra clock pulse
|
|
// so bits appear shifted right 1 bit, so mask the middle 6 bits then shift 1 place left
|
|
*data++ = (tft_Read_8() & 0x7E) << 1;
|
|
*data++ = (tft_Read_8() & 0x7E) << 1;
|
|
*data++ = (tft_Read_8() & 0x7E) << 1;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
CS_H;
|
|
|
|
#ifdef TFT_SDA_READ
|
|
end_SDA_Read();
|
|
#endif
|
|
|
|
end_tft_read();
|
|
|
|
#endif
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: drawCircle
|
|
** Description: Draw a circle outline
|
|
***************************************************************************************/
|
|
// Optimised midpoint circle algorithm
|
|
void TFT_eSPI::drawCircle(int32_t x0, int32_t y0, int32_t r, uint32_t color)
|
|
{
|
|
int32_t x = 1;
|
|
int32_t dx = 1;
|
|
int32_t dy = r + r;
|
|
int32_t p = -(r >> 1);
|
|
|
|
//begin_tft_write(); // Sprite class can use this function, avoiding begin_tft_write()
|
|
inTransaction = true;
|
|
|
|
// These are ordered to minimise coordinate changes in x or y
|
|
// drawPixel can then send fewer bounding box commands
|
|
drawPixel(x0 + r, y0, color);
|
|
drawPixel(x0 - r, y0, color);
|
|
drawPixel(x0, y0 - r, color);
|
|
drawPixel(x0, y0 + r, color);
|
|
|
|
while (x < r) {
|
|
|
|
if (p >= 0) {
|
|
dy -= 2;
|
|
p -= dy;
|
|
r--;
|
|
}
|
|
|
|
dx += 2;
|
|
p += dx;
|
|
|
|
// These are ordered to minimise coordinate changes in x or y
|
|
// drawPixel can then send fewer bounding box commands
|
|
drawPixel(x0 + x, y0 + r, color);
|
|
drawPixel(x0 - x, y0 + r, color);
|
|
drawPixel(x0 - x, y0 - r, color);
|
|
drawPixel(x0 + x, y0 - r, color);
|
|
if (r != x) {
|
|
drawPixel(x0 + r, y0 + x, color);
|
|
drawPixel(x0 - r, y0 + x, color);
|
|
drawPixel(x0 - r, y0 - x, color);
|
|
drawPixel(x0 + r, y0 - x, color);
|
|
}
|
|
x++;
|
|
}
|
|
|
|
inTransaction = false;
|
|
end_tft_write(); // Does nothing if Sprite class uses this function
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: drawCircleHelper
|
|
** Description: Support function for drawRoundRect()
|
|
***************************************************************************************/
|
|
void TFT_eSPI::drawCircleHelper( int32_t x0, int32_t y0, int32_t r, uint8_t cornername, uint32_t color)
|
|
{
|
|
int32_t f = 1 - r;
|
|
int32_t ddF_x = 1;
|
|
int32_t ddF_y = -2 * r;
|
|
int32_t x = 0;
|
|
|
|
while (x < r) {
|
|
if (f >= 0) {
|
|
r--;
|
|
ddF_y += 2;
|
|
f += ddF_y;
|
|
}
|
|
x++;
|
|
ddF_x += 2;
|
|
f += ddF_x;
|
|
if (cornername & 0x4) {
|
|
drawPixel(x0 + x, y0 + r, color);
|
|
drawPixel(x0 + r, y0 + x, color);
|
|
}
|
|
if (cornername & 0x2) {
|
|
drawPixel(x0 + x, y0 - r, color);
|
|
drawPixel(x0 + r, y0 - x, color);
|
|
}
|
|
if (cornername & 0x8) {
|
|
drawPixel(x0 - r, y0 + x, color);
|
|
drawPixel(x0 - x, y0 + r, color);
|
|
}
|
|
if (cornername & 0x1) {
|
|
drawPixel(x0 - r, y0 - x, color);
|
|
drawPixel(x0 - x, y0 - r, color);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: fillCircle
|
|
** Description: draw a filled circle
|
|
***************************************************************************************/
|
|
// Optimised midpoint circle algorithm, changed to horizontal lines (faster in sprites)
|
|
// Improved algorithm avoids repetition of lines
|
|
void TFT_eSPI::fillCircle(int32_t x0, int32_t y0, int32_t r, uint32_t color)
|
|
{
|
|
int32_t x = 0;
|
|
int32_t dx = 1;
|
|
int32_t dy = r + r;
|
|
int32_t p = -(r >> 1);
|
|
|
|
//begin_tft_write(); // Sprite class can use this function, avoiding begin_tft_write()
|
|
inTransaction = true;
|
|
|
|
drawFastHLine(x0 - r, y0, dy + 1, color);
|
|
|
|
while (x < r) {
|
|
|
|
if (p >= 0) {
|
|
drawFastHLine(x0 - x + 1, y0 + r, dx - 1, color);
|
|
drawFastHLine(x0 - x + 1, y0 - r, dx - 1, color);
|
|
dy -= 2;
|
|
p -= dy;
|
|
r--;
|
|
}
|
|
|
|
dx += 2;
|
|
p += dx;
|
|
x++;
|
|
|
|
drawFastHLine(x0 - r, y0 + x, dy + 1, color);
|
|
drawFastHLine(x0 - r, y0 - x, dy + 1, color);
|
|
|
|
}
|
|
|
|
inTransaction = false;
|
|
end_tft_write(); // Does nothing if Sprite class uses this function
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: fillCircleHelper
|
|
** Description: Support function for fillRoundRect()
|
|
***************************************************************************************/
|
|
// Support drawing roundrects, changed to horizontal lines (faster in sprites)
|
|
void TFT_eSPI::fillCircleHelper(int32_t x0, int32_t y0, int32_t r, uint8_t cornername, int32_t delta, uint32_t color)
|
|
{
|
|
int32_t f = 1 - r;
|
|
int32_t ddF_x = 1;
|
|
int32_t ddF_y = -r - r;
|
|
int32_t y = 0;
|
|
|
|
delta++;
|
|
|
|
while (y < r) {
|
|
if (f >= 0) {
|
|
if (cornername & 0x1) drawFastHLine(x0 - y, y0 + r, y + y + delta, color);
|
|
if (cornername & 0x2) drawFastHLine(x0 - y, y0 - r, y + y + delta, color);
|
|
r--;
|
|
ddF_y += 2;
|
|
f += ddF_y;
|
|
}
|
|
|
|
y++;
|
|
ddF_x += 2;
|
|
f += ddF_x;
|
|
|
|
if (cornername & 0x1) drawFastHLine(x0 - r, y0 + y, r + r + delta, color);
|
|
if (cornername & 0x2) drawFastHLine(x0 - r, y0 - y, r + r + delta, color);
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: drawEllipse
|
|
** Description: Draw a ellipse outline
|
|
***************************************************************************************/
|
|
void TFT_eSPI::drawEllipse(int16_t x0, int16_t y0, int32_t rx, int32_t ry, uint16_t color)
|
|
{
|
|
if (rx < 2) return;
|
|
if (ry < 2) return;
|
|
int32_t x, y;
|
|
int32_t rx2 = rx * rx;
|
|
int32_t ry2 = ry * ry;
|
|
int32_t fx2 = 4 * rx2;
|
|
int32_t fy2 = 4 * ry2;
|
|
int32_t s;
|
|
|
|
//begin_tft_write(); // Sprite class can use this function, avoiding begin_tft_write()
|
|
inTransaction = true;
|
|
|
|
for (x = 0, y = ry, s = 2 * ry2 + rx2 * (1 - 2 * ry); ry2 * x <= rx2 * y; x++) {
|
|
// These are ordered to minimise coordinate changes in x or y
|
|
// drawPixel can then send fewer bounding box commands
|
|
drawPixel(x0 + x, y0 + y, color);
|
|
drawPixel(x0 - x, y0 + y, color);
|
|
drawPixel(x0 - x, y0 - y, color);
|
|
drawPixel(x0 + x, y0 - y, color);
|
|
if (s >= 0) {
|
|
s += fx2 * (1 - y);
|
|
y--;
|
|
}
|
|
s += ry2 * ((4 * x) + 6);
|
|
}
|
|
|
|
for (x = rx, y = 0, s = 2 * rx2 + ry2 * (1 - 2 * rx); rx2 * y <= ry2 * x; y++) {
|
|
// These are ordered to minimise coordinate changes in x or y
|
|
// drawPixel can then send fewer bounding box commands
|
|
drawPixel(x0 + x, y0 + y, color);
|
|
drawPixel(x0 - x, y0 + y, color);
|
|
drawPixel(x0 - x, y0 - y, color);
|
|
drawPixel(x0 + x, y0 - y, color);
|
|
if (s >= 0) {
|
|
s += fy2 * (1 - x);
|
|
x--;
|
|
}
|
|
s += rx2 * ((4 * y) + 6);
|
|
}
|
|
|
|
inTransaction = false;
|
|
end_tft_write(); // Does nothing if Sprite class uses this function
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: fillEllipse
|
|
** Description: draw a filled ellipse
|
|
***************************************************************************************/
|
|
void TFT_eSPI::fillEllipse(int16_t x0, int16_t y0, int32_t rx, int32_t ry, uint16_t color)
|
|
{
|
|
if (rx < 2) return;
|
|
if (ry < 2) return;
|
|
int32_t x, y;
|
|
int32_t rx2 = rx * rx;
|
|
int32_t ry2 = ry * ry;
|
|
int32_t fx2 = 4 * rx2;
|
|
int32_t fy2 = 4 * ry2;
|
|
int32_t s;
|
|
|
|
//begin_tft_write(); // Sprite class can use this function, avoiding begin_tft_write()
|
|
inTransaction = true;
|
|
|
|
for (x = 0, y = ry, s = 2 * ry2 + rx2 * (1 - 2 * ry); ry2 * x <= rx2 * y; x++) {
|
|
drawFastHLine(x0 - x, y0 - y, x + x + 1, color);
|
|
drawFastHLine(x0 - x, y0 + y, x + x + 1, color);
|
|
|
|
if (s >= 0) {
|
|
s += fx2 * (1 - y);
|
|
y--;
|
|
}
|
|
s += ry2 * ((4 * x) + 6);
|
|
}
|
|
|
|
for (x = rx, y = 0, s = 2 * rx2 + ry2 * (1 - 2 * rx); rx2 * y <= ry2 * x; y++) {
|
|
drawFastHLine(x0 - x, y0 - y, x + x + 1, color);
|
|
drawFastHLine(x0 - x, y0 + y, x + x + 1, color);
|
|
|
|
if (s >= 0) {
|
|
s += fy2 * (1 - x);
|
|
x--;
|
|
}
|
|
s += rx2 * ((4 * y) + 6);
|
|
}
|
|
|
|
inTransaction = false;
|
|
end_tft_write(); // Does nothing if Sprite class uses this function
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: fillScreen
|
|
** Description: Clear the screen to defined colour
|
|
***************************************************************************************/
|
|
void TFT_eSPI::fillScreen(uint32_t color)
|
|
{
|
|
fillRect(0, 0, _width, _height, color);
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: drawRect
|
|
** Description: Draw a rectangle outline
|
|
***************************************************************************************/
|
|
// Draw a rectangle
|
|
void TFT_eSPI::drawRect(int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color)
|
|
{
|
|
//begin_tft_write(); // Sprite class can use this function, avoiding begin_tft_write()
|
|
inTransaction = true;
|
|
|
|
drawFastHLine(x, y, w, color);
|
|
drawFastHLine(x, y + h - 1, w, color);
|
|
// Avoid drawing corner pixels twice
|
|
drawFastVLine(x, y + 1, h - 2, color);
|
|
drawFastVLine(x + w - 1, y + 1, h - 2, color);
|
|
|
|
inTransaction = false;
|
|
end_tft_write(); // Does nothing if Sprite class uses this function
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: drawRoundRect
|
|
** Description: Draw a rounded corner rectangle outline
|
|
***************************************************************************************/
|
|
// Draw a rounded rectangle
|
|
void TFT_eSPI::drawRoundRect(int32_t x, int32_t y, int32_t w, int32_t h, int32_t r, uint32_t color)
|
|
{
|
|
//begin_tft_write(); // Sprite class can use this function, avoiding begin_tft_write()
|
|
inTransaction = true;
|
|
|
|
// smarter version
|
|
drawFastHLine(x + r, y, w - r - r, color); // Top
|
|
drawFastHLine(x + r, y + h - 1, w - r - r, color); // Bottom
|
|
drawFastVLine(x, y + r, h - r - r, color); // Left
|
|
drawFastVLine(x + w - 1, y + r, h - r - r, color); // Right
|
|
// draw four corners
|
|
drawCircleHelper(x + r, y + r, r, 1, color);
|
|
drawCircleHelper(x + w - r - 1, y + r, r, 2, color);
|
|
drawCircleHelper(x + w - r - 1, y + h - r - 1, r, 4, color);
|
|
drawCircleHelper(x + r, y + h - r - 1, r, 8, color);
|
|
|
|
inTransaction = false;
|
|
end_tft_write(); // Does nothing if Sprite class uses this function
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: fillRoundRect
|
|
** Description: Draw a rounded corner filled rectangle
|
|
***************************************************************************************/
|
|
// Fill a rounded rectangle, changed to horizontal lines (faster in sprites)
|
|
void TFT_eSPI::fillRoundRect(int32_t x, int32_t y, int32_t w, int32_t h, int32_t r, uint32_t color)
|
|
{
|
|
//begin_tft_write(); // Sprite class can use this function, avoiding begin_tft_write()
|
|
inTransaction = true;
|
|
|
|
// smarter version
|
|
fillRect(x, y + r, w, h - r - r, color);
|
|
|
|
// draw four corners
|
|
fillCircleHelper(x + r, y + h - r - 1, r, 1, w - r - r - 1, color);
|
|
fillCircleHelper(x + r, y + r, r, 2, w - r - r - 1, color);
|
|
|
|
inTransaction = false;
|
|
end_tft_write(); // Does nothing if Sprite class uses this function
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: drawTriangle
|
|
** Description: Draw a triangle outline using 3 arbitrary points
|
|
***************************************************************************************/
|
|
// Draw a triangle
|
|
void TFT_eSPI::drawTriangle(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint32_t color)
|
|
{
|
|
//begin_tft_write(); // Sprite class can use this function, avoiding begin_tft_write()
|
|
inTransaction = true;
|
|
|
|
drawLine(x0, y0, x1, y1, color);
|
|
drawLine(x1, y1, x2, y2, color);
|
|
drawLine(x2, y2, x0, y0, color);
|
|
|
|
inTransaction = false;
|
|
end_tft_write(); // Does nothing if Sprite class uses this function
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: fillTriangle
|
|
** Description: Draw a filled triangle using 3 arbitrary points
|
|
***************************************************************************************/
|
|
// Fill a triangle - original Adafruit function works well and code footprint is small
|
|
void TFT_eSPI::fillTriangle ( int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint32_t color)
|
|
{
|
|
int32_t a, b, y, last;
|
|
|
|
// Sort coordinates by Y order (y2 >= y1 >= y0)
|
|
if (y0 > y1) {
|
|
swap_coord(y0, y1); swap_coord(x0, x1);
|
|
}
|
|
if (y1 > y2) {
|
|
swap_coord(y2, y1); swap_coord(x2, x1);
|
|
}
|
|
if (y0 > y1) {
|
|
swap_coord(y0, y1); swap_coord(x0, x1);
|
|
}
|
|
|
|
if (y0 == y2) { // Handle awkward all-on-same-line case as its own thing
|
|
a = b = x0;
|
|
if (x1 < a) a = x1;
|
|
else if (x1 > b) b = x1;
|
|
if (x2 < a) a = x2;
|
|
else if (x2 > b) b = x2;
|
|
drawFastHLine(a, y0, b - a + 1, color);
|
|
return;
|
|
}
|
|
|
|
//begin_tft_write(); // Sprite class can use this function, avoiding begin_tft_write()
|
|
inTransaction = true;
|
|
|
|
int32_t
|
|
dx01 = x1 - x0,
|
|
dy01 = y1 - y0,
|
|
dx02 = x2 - x0,
|
|
dy02 = y2 - y0,
|
|
dx12 = x2 - x1,
|
|
dy12 = y2 - y1,
|
|
sa = 0,
|
|
sb = 0;
|
|
|
|
// For upper part of triangle, find scanline crossings for segments
|
|
// 0-1 and 0-2. If y1=y2 (flat-bottomed triangle), the scanline y1
|
|
// is included here (and second loop will be skipped, avoiding a /0
|
|
// error there), otherwise scanline y1 is skipped here and handled
|
|
// in the second loop...which also avoids a /0 error here if y0=y1
|
|
// (flat-topped triangle).
|
|
if (y1 == y2) last = y1; // Include y1 scanline
|
|
else last = y1 - 1; // Skip it
|
|
|
|
for (y = y0; y <= last; y++) {
|
|
a = x0 + sa / dy01;
|
|
b = x0 + sb / dy02;
|
|
sa += dx01;
|
|
sb += dx02;
|
|
|
|
if (a > b) swap_coord(a, b);
|
|
drawFastHLine(a, y, b - a + 1, color);
|
|
}
|
|
|
|
// For lower part of triangle, find scanline crossings for segments
|
|
// 0-2 and 1-2. This loop is skipped if y1=y2.
|
|
sa = dx12 * (y - y1);
|
|
sb = dx02 * (y - y0);
|
|
for (; y <= y2; y++) {
|
|
a = x1 + sa / dy12;
|
|
b = x0 + sb / dy02;
|
|
sa += dx12;
|
|
sb += dx02;
|
|
|
|
if (a > b) swap_coord(a, b);
|
|
drawFastHLine(a, y, b - a + 1, color);
|
|
}
|
|
|
|
inTransaction = false;
|
|
end_tft_write(); // Does nothing if Sprite class uses this function
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: drawBitmap
|
|
** Description: Draw an image stored in an array on the TFT
|
|
***************************************************************************************/
|
|
void TFT_eSPI::drawBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h, uint16_t color)
|
|
{
|
|
//begin_tft_write(); // Sprite class can use this function, avoiding begin_tft_write()
|
|
inTransaction = true;
|
|
|
|
int32_t i, j, byteWidth = (w + 7) / 8;
|
|
|
|
for (j = 0; j < h; j++) {
|
|
for (i = 0; i < w; i++ ) {
|
|
if (pgm_read_byte(bitmap + j * byteWidth + i / 8) & (128 >> (i & 7))) {
|
|
drawPixel(x + i, y + j, color);
|
|
}
|
|
}
|
|
}
|
|
|
|
inTransaction = false;
|
|
end_tft_write(); // Does nothing if Sprite class uses this function
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: drawBitmap
|
|
** Description: Draw an image stored in an array on the TFT
|
|
***************************************************************************************/
|
|
void TFT_eSPI::drawBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h, uint16_t fgcolor, uint16_t bgcolor)
|
|
{
|
|
//begin_tft_write(); // Sprite class can use this function, avoiding begin_tft_write()
|
|
inTransaction = true;
|
|
|
|
int32_t i, j, byteWidth = (w + 7) / 8;
|
|
|
|
for (j = 0; j < h; j++) {
|
|
for (i = 0; i < w; i++ ) {
|
|
if (pgm_read_byte(bitmap + j * byteWidth + i / 8) & (128 >> (i & 7)))
|
|
drawPixel(x + i, y + j, fgcolor);
|
|
else drawPixel(x + i, y + j, bgcolor);
|
|
}
|
|
}
|
|
|
|
inTransaction = false;
|
|
end_tft_write(); // Does nothing if Sprite class uses this function
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: drawXBitmap
|
|
** Description: Draw an image stored in an XBM array onto the TFT
|
|
***************************************************************************************/
|
|
void TFT_eSPI::drawXBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h, uint16_t color)
|
|
{
|
|
//begin_tft_write(); // Sprite class can use this function, avoiding begin_tft_write()
|
|
inTransaction = true;
|
|
|
|
int32_t i, j, byteWidth = (w + 7) / 8;
|
|
|
|
for (j = 0; j < h; j++) {
|
|
for (i = 0; i < w; i++ ) {
|
|
if (pgm_read_byte(bitmap + j * byteWidth + i / 8) & (1 << (i & 7))) {
|
|
drawPixel(x + i, y + j, color);
|
|
}
|
|
}
|
|
}
|
|
|
|
inTransaction = false;
|
|
end_tft_write(); // Does nothing if Sprite class uses this function
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: drawXBitmap
|
|
** Description: Draw an XBM image with foreground and background colors
|
|
***************************************************************************************/
|
|
void TFT_eSPI::drawXBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h, uint16_t color, uint16_t bgcolor)
|
|
{
|
|
//begin_tft_write(); // Sprite class can use this function, avoiding begin_tft_write()
|
|
inTransaction = true;
|
|
|
|
int32_t i, j, byteWidth = (w + 7) / 8;
|
|
|
|
for (j = 0; j < h; j++) {
|
|
for (i = 0; i < w; i++ ) {
|
|
if (pgm_read_byte(bitmap + j * byteWidth + i / 8) & (1 << (i & 7)))
|
|
drawPixel(x + i, y + j, color);
|
|
else drawPixel(x + i, y + j, bgcolor);
|
|
}
|
|
}
|
|
|
|
inTransaction = false;
|
|
end_tft_write(); // Does nothing if Sprite class uses this function
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: setCursor
|
|
** Description: Set the text cursor x,y position
|
|
***************************************************************************************/
|
|
void TFT_eSPI::setCursor(int16_t x, int16_t y)
|
|
{
|
|
cursor_x = x;
|
|
cursor_y = y;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: setCursor
|
|
** Description: Set the text cursor x,y position and font
|
|
***************************************************************************************/
|
|
void TFT_eSPI::setCursor(int16_t x, int16_t y, uint8_t font)
|
|
{
|
|
textfont = font;
|
|
cursor_x = x;
|
|
cursor_y = y;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: getCursorX
|
|
** Description: Get the text cursor x position
|
|
***************************************************************************************/
|
|
int16_t TFT_eSPI::getCursorX(void)
|
|
{
|
|
return cursor_x;
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: getCursorY
|
|
** Description: Get the text cursor y position
|
|
***************************************************************************************/
|
|
int16_t TFT_eSPI::getCursorY(void)
|
|
{
|
|
return cursor_y;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: setTextSize
|
|
** Description: Set the text size multiplier
|
|
***************************************************************************************/
|
|
void TFT_eSPI::setTextSize(uint8_t s)
|
|
{
|
|
if (s > 7) s = 7; // Limit the maximum size multiplier so byte variables can be used for rendering
|
|
textsize = (s > 0) ? s : 1; // Don't allow font size 0
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: setTextColor
|
|
** Description: Set the font foreground colour (background is transparent)
|
|
***************************************************************************************/
|
|
void TFT_eSPI::setTextColor(uint16_t c)
|
|
{
|
|
// For 'transparent' background, we'll set the bg
|
|
// to the same as fg instead of using a flag
|
|
textcolor = textbgcolor = c;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: setTextColor
|
|
** Description: Set the font foreground and background colour
|
|
***************************************************************************************/
|
|
void TFT_eSPI::setTextColor(uint16_t c, uint16_t b)
|
|
{
|
|
textcolor = c;
|
|
textbgcolor = b;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: setPivot
|
|
** Description: Set the pivot point on the TFT
|
|
*************************************************************************************x*/
|
|
void TFT_eSPI::setPivot(int16_t x, int16_t y)
|
|
{
|
|
_xpivot = x;
|
|
_ypivot = y;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: getPivotX
|
|
** Description: Get the x pivot position
|
|
***************************************************************************************/
|
|
int16_t TFT_eSPI::getPivotX(void)
|
|
{
|
|
return _xpivot;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: getPivotY
|
|
** Description: Get the y pivot position
|
|
***************************************************************************************/
|
|
int16_t TFT_eSPI::getPivotY(void)
|
|
{
|
|
return _ypivot;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: setBitmapColor
|
|
** Description: Set the foreground foreground and background colour
|
|
***************************************************************************************/
|
|
void TFT_eSPI::setBitmapColor(uint16_t c, uint16_t b)
|
|
{
|
|
if (c == b) b = ~c;
|
|
bitmap_fg = c;
|
|
bitmap_bg = b;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: setTextWrap
|
|
** Description: Define if text should wrap at end of line
|
|
***************************************************************************************/
|
|
void TFT_eSPI::setTextWrap(bool wrapX, bool wrapY)
|
|
{
|
|
textwrapX = wrapX;
|
|
textwrapY = wrapY;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: setTextDatum
|
|
** Description: Set the text position reference datum
|
|
***************************************************************************************/
|
|
void TFT_eSPI::setTextDatum(uint8_t d)
|
|
{
|
|
textdatum = d;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: setTextPadding
|
|
** Description: Define padding width (aids erasing old text and numbers)
|
|
***************************************************************************************/
|
|
void TFT_eSPI::setTextPadding(uint16_t x_width)
|
|
{
|
|
padX = x_width;
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: setTextPadding
|
|
** Description: Define padding width (aids erasing old text and numbers)
|
|
***************************************************************************************/
|
|
uint16_t TFT_eSPI::getTextPadding(void)
|
|
{
|
|
return padX;
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: getRotation
|
|
** Description: Return the rotation value (as used by setRotation())
|
|
***************************************************************************************/
|
|
uint8_t TFT_eSPI::getRotation(void)
|
|
{
|
|
return rotation;
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: getTextDatum
|
|
** Description: Return the text datum value (as used by setTextDatum())
|
|
***************************************************************************************/
|
|
uint8_t TFT_eSPI::getTextDatum(void)
|
|
{
|
|
return textdatum;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: width
|
|
** Description: Return the pixel width of display (per current rotation)
|
|
***************************************************************************************/
|
|
// Return the size of the display (per current rotation)
|
|
int16_t TFT_eSPI::width(void)
|
|
{
|
|
return _width;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: height
|
|
** Description: Return the pixel height of display (per current rotation)
|
|
***************************************************************************************/
|
|
int16_t TFT_eSPI::height(void)
|
|
{
|
|
return _height;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: textWidth
|
|
** Description: Return the width in pixels of a string in a given font
|
|
***************************************************************************************/
|
|
int16_t TFT_eSPI::textWidth(const String &string)
|
|
{
|
|
int16_t len = string.length() + 2;
|
|
char buffer[len];
|
|
string.toCharArray(buffer, len);
|
|
return textWidth(buffer, textfont);
|
|
}
|
|
|
|
int16_t TFT_eSPI::textWidth(const String &string, uint8_t font)
|
|
{
|
|
int16_t len = string.length() + 2;
|
|
char buffer[len];
|
|
string.toCharArray(buffer, len);
|
|
return textWidth(buffer, font);
|
|
}
|
|
|
|
int16_t TFT_eSPI::textWidth(const char *string)
|
|
{
|
|
return textWidth(string, textfont);
|
|
}
|
|
|
|
int16_t TFT_eSPI::textWidth(const char *string, uint8_t font)
|
|
{
|
|
int32_t str_width = 0;
|
|
uint16_t uniCode = 0;
|
|
|
|
#ifdef SMOOTH_FONT
|
|
if (fontLoaded) {
|
|
while (*string) {
|
|
uniCode = decodeUTF8(*string++);
|
|
if (uniCode) {
|
|
if (uniCode == 0x20) str_width += gFont.spaceWidth;
|
|
else {
|
|
uint16_t gNum = 0;
|
|
bool found = getUnicodeIndex(uniCode, &gNum);
|
|
if (found) {
|
|
if (str_width == 0 && gdX[gNum] < 0) str_width -= gdX[gNum];
|
|
if (*string || isDigits) str_width += gxAdvance[gNum];
|
|
else str_width += (gdX[gNum] + gWidth[gNum]);
|
|
} else str_width += gFont.spaceWidth + 1;
|
|
}
|
|
}
|
|
}
|
|
isDigits = false;
|
|
return str_width;
|
|
}
|
|
#endif
|
|
|
|
if (font > 1 && font < 9) {
|
|
char *widthtable = (char *)pgm_read_dword( &(fontdata[font].widthtbl ) ) - 32; //subtract the 32 outside the loop
|
|
|
|
while (*string) {
|
|
uniCode = *(string++);
|
|
if (uniCode > 31 && uniCode < 128)
|
|
str_width += pgm_read_byte( widthtable + uniCode); // Normally we need to subtract 32 from uniCode
|
|
else str_width += pgm_read_byte( widthtable + 32); // Set illegal character = space width
|
|
}
|
|
|
|
} else {
|
|
|
|
#ifdef LOAD_GFXFF
|
|
if (gfxFont) { // New font
|
|
while (*string) {
|
|
uniCode = decodeUTF8(*string++);
|
|
if ((uniCode >= pgm_read_word(&gfxFont->first)) && (uniCode <= pgm_read_word(&gfxFont->last ))) {
|
|
uniCode -= pgm_read_word(&gfxFont->first);
|
|
GFXglyph *glyph = &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[uniCode]);
|
|
// If this is not the last character or is a digit then use xAdvance
|
|
if (*string || isDigits) str_width += pgm_read_byte(&glyph->xAdvance);
|
|
// Else use the offset plus width since this can be bigger than xAdvance
|
|
else str_width += ((int8_t)pgm_read_byte(&glyph->xOffset) + pgm_read_byte(&glyph->width));
|
|
}
|
|
}
|
|
} else
|
|
#endif
|
|
{
|
|
#ifdef LOAD_GLCD
|
|
while (*string++) str_width += 6;
|
|
#endif
|
|
}
|
|
}
|
|
isDigits = false;
|
|
return str_width * textsize;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: fontsLoaded
|
|
** Description: return an encoded 16 bit value showing the fonts loaded
|
|
***************************************************************************************/
|
|
// Returns a value showing which fonts are loaded (bit N set = Font N loaded)
|
|
|
|
uint16_t TFT_eSPI::fontsLoaded(void)
|
|
{
|
|
return fontsloaded;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: fontHeight
|
|
** Description: return the height of a font (yAdvance for free fonts)
|
|
***************************************************************************************/
|
|
int16_t TFT_eSPI::fontHeight(int16_t font)
|
|
{
|
|
#ifdef SMOOTH_FONT
|
|
if (fontLoaded) return gFont.yAdvance;
|
|
#endif
|
|
|
|
#ifdef LOAD_GFXFF
|
|
if (font == 1) {
|
|
if (gfxFont) { // New font
|
|
return pgm_read_byte(&gfxFont->yAdvance) * textsize;
|
|
}
|
|
}
|
|
#endif
|
|
return pgm_read_byte( &fontdata[font].height ) * textsize;
|
|
}
|
|
|
|
int16_t TFT_eSPI::fontHeight(void)
|
|
{
|
|
return fontHeight(textfont);
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: drawChar
|
|
** Description: draw a single character in the GLCD or GFXFF font
|
|
***************************************************************************************/
|
|
void TFT_eSPI::drawChar(int32_t x, int32_t y, uint16_t c, uint32_t color, uint32_t bg, uint8_t size)
|
|
{
|
|
if ((x >= _width) || // Clip right
|
|
(y >= _height) || // Clip bottom
|
|
((x + 6 * size - 1) < 0) || // Clip left
|
|
((y + 8 * size - 1) < 0)) // Clip top
|
|
return;
|
|
|
|
if (c < 32) return;
|
|
#ifdef LOAD_GLCD
|
|
//>>>>>>>>>>>>>>>>>>
|
|
#ifdef LOAD_GFXFF
|
|
if (!gfxFont) { // 'Classic' built-in font
|
|
#endif
|
|
//>>>>>>>>>>>>>>>>>>
|
|
|
|
bool fillbg = (bg != color);
|
|
|
|
if ((size == 1) && fillbg) {
|
|
uint8_t column[6];
|
|
uint8_t mask = 0x1;
|
|
begin_tft_write();
|
|
|
|
setWindow(x, y, x + 5, y + 8);
|
|
|
|
for (int8_t i = 0; i < 5; i++ ) column[i] = pgm_read_byte(font + (c * 5) + i);
|
|
column[5] = 0;
|
|
|
|
for (int8_t j = 0; j < 8; j++) {
|
|
for (int8_t k = 0; k < 5; k++ ) {
|
|
if (column[k] & mask) {
|
|
tft_Write_16(color);
|
|
} else {
|
|
tft_Write_16(bg);
|
|
}
|
|
}
|
|
mask <<= 1;
|
|
tft_Write_16(bg);
|
|
}
|
|
|
|
end_tft_write();
|
|
} else {
|
|
//begin_tft_write(); // Sprite class can use this function, avoiding begin_tft_write()
|
|
inTransaction = true;
|
|
for (int8_t i = 0; i < 6; i++ ) {
|
|
uint8_t line;
|
|
if (i == 5)
|
|
line = 0x0;
|
|
else
|
|
line = pgm_read_byte(font + (c * 5) + i);
|
|
|
|
if (size == 1) { // default size
|
|
for (int8_t j = 0; j < 8; j++) {
|
|
if (line & 0x1) drawPixel(x + i, y + j, color);
|
|
line >>= 1;
|
|
}
|
|
} else { // big size
|
|
for (int8_t j = 0; j < 8; j++) {
|
|
if (line & 0x1) fillRect(x + (i * size), y + (j * size), size, size, color);
|
|
else if (fillbg) fillRect(x + i * size, y + j * size, size, size, bg);
|
|
line >>= 1;
|
|
}
|
|
}
|
|
}
|
|
inTransaction = false;
|
|
end_tft_write(); // Does nothing if Sprite class uses this function
|
|
}
|
|
|
|
//>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
|
#ifdef LOAD_GFXFF
|
|
} else { // Custom font
|
|
#endif
|
|
//>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
|
#endif // LOAD_GLCD
|
|
|
|
#ifdef LOAD_GFXFF
|
|
// Filter out bad characters not present in font
|
|
if ((c >= pgm_read_word(&gfxFont->first)) && (c <= pgm_read_word(&gfxFont->last ))) {
|
|
//begin_tft_write(); // Sprite class can use this function, avoiding begin_tft_write()
|
|
inTransaction = true;
|
|
//>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
|
|
|
c -= pgm_read_word(&gfxFont->first);
|
|
GFXglyph *glyph = &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c]);
|
|
uint8_t *bitmap = (uint8_t *)pgm_read_dword(&gfxFont->bitmap);
|
|
|
|
uint32_t bo = pgm_read_word(&glyph->bitmapOffset);
|
|
uint8_t w = pgm_read_byte(&glyph->width),
|
|
h = pgm_read_byte(&glyph->height);
|
|
//xa = pgm_read_byte(&glyph->xAdvance);
|
|
int8_t xo = pgm_read_byte(&glyph->xOffset),
|
|
yo = pgm_read_byte(&glyph->yOffset);
|
|
uint8_t xx, yy, bits = 0, bit = 0;
|
|
int16_t xo16 = 0, yo16 = 0;
|
|
|
|
if (size > 1) {
|
|
xo16 = xo;
|
|
yo16 = yo;
|
|
}
|
|
|
|
// GFXFF rendering speed up
|
|
uint16_t hpc = 0; // Horizontal foreground pixel count
|
|
for (yy = 0; yy < h; yy++) {
|
|
for (xx = 0; xx < w; xx++) {
|
|
if (bit == 0) {
|
|
bits = pgm_read_byte(&bitmap[bo++]);
|
|
bit = 0x80;
|
|
}
|
|
if (bits & bit) hpc++;
|
|
else {
|
|
if (hpc) {
|
|
if (size == 1) drawFastHLine(x + xo + xx - hpc, y + yo + yy, hpc, color);
|
|
else fillRect(x + (xo16 + xx - hpc)*size, y + (yo16 + yy)*size, size * hpc, size, color);
|
|
hpc = 0;
|
|
}
|
|
}
|
|
bit >>= 1;
|
|
}
|
|
// Draw pixels for this line as we are about to increment yy
|
|
if (hpc) {
|
|
if (size == 1) drawFastHLine(x + xo + xx - hpc, y + yo + yy, hpc, color);
|
|
else fillRect(x + (xo16 + xx - hpc)*size, y + (yo16 + yy)*size, size * hpc, size, color);
|
|
hpc = 0;
|
|
}
|
|
}
|
|
|
|
inTransaction = false;
|
|
end_tft_write(); // Does nothing if Sprite class uses this function
|
|
}
|
|
#endif
|
|
|
|
#ifdef LOAD_GLCD
|
|
#ifdef LOAD_GFXFF
|
|
} // End classic vs custom font
|
|
#endif
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: setAddrWindow
|
|
** Description: define an area to receive a stream of pixels
|
|
***************************************************************************************/
|
|
// Chip select is high at the end of this function
|
|
void TFT_eSPI::setAddrWindow(int32_t x0, int32_t y0, int32_t w, int32_t h)
|
|
{
|
|
begin_tft_write();
|
|
|
|
setWindow(x0, y0, x0 + w - 1, y0 + h - 1);
|
|
|
|
end_tft_write();
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: setWindow
|
|
** Description: define an area to receive a stream of pixels
|
|
***************************************************************************************/
|
|
// Chip select stays low, call begin_tft_write first. Use setAddrWindow() from sketches
|
|
void TFT_eSPI::setWindow(int32_t x0, int32_t y0, int32_t x1, int32_t y1)
|
|
{
|
|
//begin_tft_write(); // Must be called before setWindow
|
|
|
|
#ifdef CGRAM_OFFSET
|
|
x0 += colstart;
|
|
x1 += colstart;
|
|
y0 += rowstart;
|
|
y1 += rowstart;
|
|
#endif
|
|
|
|
#ifdef MULTI_TFT_SUPPORT
|
|
// No optimisation to permit multiple screens
|
|
DC_C; tft_Write_8(TFT_CASET);
|
|
DC_D; tft_Write_32C(x0, x1);
|
|
DC_C; tft_Write_8(TFT_PASET);
|
|
DC_D; tft_Write_32C(y0, y1);
|
|
#else
|
|
// No need to send x if it has not changed (speeds things up)
|
|
if (addr_col != (x0 << 16 | x1)) {
|
|
DC_C; tft_Write_8(TFT_CASET);
|
|
DC_D; tft_Write_32C(x0, x1);
|
|
addr_col = (x0 << 16 | x1);
|
|
}
|
|
|
|
// No need to send y if it has not changed (speeds things up)
|
|
if (addr_row != (y0 << 16 | y1)) {
|
|
DC_C; tft_Write_8(TFT_PASET);
|
|
DC_D; tft_Write_32C(y0, y1);
|
|
addr_row = (y0 << 16 | y1);
|
|
}
|
|
#endif
|
|
|
|
DC_C; tft_Write_8(TFT_RAMWR);
|
|
DC_D;
|
|
|
|
//end_tft_write(); // Must be called after setWindow
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: readAddrWindow
|
|
** Description: define an area to read a stream of pixels
|
|
***************************************************************************************/
|
|
void TFT_eSPI::readAddrWindow(int32_t xs, int32_t ys, int32_t w, int32_t h)
|
|
{
|
|
//begin_tft_write(); // Must be called before readAddrWindow or CS set low
|
|
|
|
int32_t xe = xs + w - 1;
|
|
int32_t ye = ys + h - 1;
|
|
|
|
addr_col = 0xFFFF;
|
|
addr_row = 0xFFFF;
|
|
|
|
#ifdef CGRAM_OFFSET
|
|
xs += colstart;
|
|
xe += colstart;
|
|
ys += rowstart;
|
|
ye += rowstart;
|
|
#endif
|
|
|
|
// Column addr set
|
|
DC_C; tft_Write_8(TFT_CASET);
|
|
DC_D; tft_Write_32C(xs, xe);
|
|
|
|
// Row addr set
|
|
DC_C; tft_Write_8(TFT_PASET);
|
|
DC_D; tft_Write_32C(ys, ye);
|
|
|
|
// Read CGRAM command
|
|
DC_C; tft_Write_8(TFT_RAMRD);
|
|
|
|
DC_D;
|
|
|
|
//end_tft_write(); // Must be called after readAddrWindow or CS set high
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: drawPixel
|
|
** Description: push a single pixel at an arbitrary position
|
|
***************************************************************************************/
|
|
void TFT_eSPI::drawPixel(int32_t x, int32_t y, uint32_t color)
|
|
{
|
|
// Range checking
|
|
if ((x < 0) || (y < 0) || (x >= _width) || (y >= _height)) return;
|
|
|
|
#ifdef CGRAM_OFFSET
|
|
x += colstart;
|
|
y += rowstart;
|
|
#endif
|
|
|
|
begin_tft_write();
|
|
|
|
#ifdef MULTI_TFT_SUPPORT
|
|
// No optimisation
|
|
DC_C; tft_Write_8(TFT_CASET);
|
|
DC_D; tft_Write_32D(x);
|
|
DC_C; tft_Write_8(TFT_PASET);
|
|
DC_D; tft_Write_32D(y);
|
|
#else
|
|
// No need to send x if it has not changed (speeds things up)
|
|
if (addr_col != (x << 16 | x)) {
|
|
DC_C; tft_Write_8(TFT_CASET);
|
|
DC_D; tft_Write_32D(x);
|
|
addr_col = (x << 16 | x);
|
|
}
|
|
|
|
// No need to send y if it has not changed (speeds things up)
|
|
if (addr_row != (y << 16 | y)) {
|
|
DC_C; tft_Write_8(TFT_PASET);
|
|
DC_D; tft_Write_32D(y);
|
|
addr_row = (y << 16 | y);
|
|
}
|
|
#endif
|
|
|
|
DC_C; tft_Write_8(TFT_RAMWR);
|
|
DC_D; tft_Write_16(color);
|
|
|
|
end_tft_write();
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: pushColor
|
|
** Description: push a single pixel
|
|
***************************************************************************************/
|
|
void TFT_eSPI::pushColor(uint16_t color)
|
|
{
|
|
begin_tft_write();
|
|
|
|
tft_Write_16(color);
|
|
|
|
end_tft_write();
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: pushColor
|
|
** Description: push a single colour to "len" pixels
|
|
***************************************************************************************/
|
|
void TFT_eSPI::pushColor(uint16_t color, uint32_t len)
|
|
{
|
|
begin_tft_write();
|
|
|
|
pushBlock(color, len);
|
|
|
|
end_tft_write();
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: startWrite
|
|
** Description: begin transaction with CS low, MUST later call endWrite
|
|
***************************************************************************************/
|
|
void TFT_eSPI::startWrite(void)
|
|
{
|
|
begin_tft_write();
|
|
inTransaction = true;
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: endWrite
|
|
** Description: end transaction with CS high
|
|
***************************************************************************************/
|
|
void TFT_eSPI::endWrite(void)
|
|
{
|
|
inTransaction = false;
|
|
DMA_BUSY_CHECK; // Safety check - user code should have checked this!
|
|
end_tft_write();
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: writeColor (use startWrite() and endWrite() before & after)
|
|
** Description: raw write of "len" pixels avoiding transaction check
|
|
***************************************************************************************/
|
|
void TFT_eSPI::writeColor(uint16_t color, uint32_t len)
|
|
{
|
|
pushBlock(color, len);
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: pushColors
|
|
** Description: push an array of pixels for 16 bit raw image drawing
|
|
***************************************************************************************/
|
|
// Assumed that setAddrWindow() has previously been called
|
|
// len is number of bytes, not pixels
|
|
void TFT_eSPI::pushColors(uint8_t *data, uint32_t len)
|
|
{
|
|
begin_tft_write();
|
|
|
|
pushPixels(data, len >> 1);
|
|
|
|
end_tft_write();
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: pushColors
|
|
** Description: push an array of pixels, for image drawing
|
|
***************************************************************************************/
|
|
void TFT_eSPI::pushColors(uint16_t *data, uint32_t len, bool swap)
|
|
{
|
|
begin_tft_write();
|
|
if (swap) {
|
|
swap = _swapBytes;
|
|
_swapBytes = true;
|
|
}
|
|
|
|
pushPixels(data, len);
|
|
|
|
_swapBytes = swap; // Restore old value
|
|
end_tft_write();
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: drawLine
|
|
** Description: draw a line between 2 arbitrary points
|
|
***************************************************************************************/
|
|
// Bresenham's algorithm - thx wikipedia - speed enhanced by Bodmer to use
|
|
// an efficient FastH/V Line draw routine for line segments of 2 pixels or more
|
|
void TFT_eSPI::drawLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint32_t color)
|
|
{
|
|
//begin_tft_write(); // Sprite class can use this function, avoiding begin_tft_write()
|
|
inTransaction = true;
|
|
|
|
bool steep = abs(y1 - y0) > abs(x1 - x0);
|
|
if (steep) {
|
|
swap_coord(x0, y0);
|
|
swap_coord(x1, y1);
|
|
}
|
|
|
|
if (x0 > x1) {
|
|
swap_coord(x0, x1);
|
|
swap_coord(y0, y1);
|
|
}
|
|
|
|
int32_t dx = x1 - x0, dy = abs(y1 - y0);;
|
|
|
|
int32_t err = dx >> 1, ystep = -1, xs = x0, dlen = 0;
|
|
|
|
if (y0 < y1) ystep = 1;
|
|
|
|
// Split into steep and not steep for FastH/V separation
|
|
if (steep) {
|
|
for (; x0 <= x1; x0++) {
|
|
dlen++;
|
|
err -= dy;
|
|
if (err < 0) {
|
|
err += dx;
|
|
if (dlen == 1) drawPixel(y0, xs, color);
|
|
else drawFastVLine(y0, xs, dlen, color);
|
|
dlen = 0;
|
|
y0 += ystep; xs = x0 + 1;
|
|
}
|
|
}
|
|
if (dlen) drawFastVLine(y0, xs, dlen, color);
|
|
} else {
|
|
for (; x0 <= x1; x0++) {
|
|
dlen++;
|
|
err -= dy;
|
|
if (err < 0) {
|
|
err += dx;
|
|
if (dlen == 1) drawPixel(xs, y0, color);
|
|
else drawFastHLine(xs, y0, dlen, color);
|
|
dlen = 0;
|
|
y0 += ystep; xs = x0 + 1;
|
|
}
|
|
}
|
|
if (dlen) drawFastHLine(xs, y0, dlen, color);
|
|
}
|
|
|
|
inTransaction = false;
|
|
end_tft_write();
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: drawFastVLine
|
|
** Description: draw a vertical line
|
|
***************************************************************************************/
|
|
void TFT_eSPI::drawFastVLine(int32_t x, int32_t y, int32_t h, uint32_t color)
|
|
{
|
|
// Clipping
|
|
if ((x < 0) || (x >= _width) || (y >= _height)) return;
|
|
|
|
if (y < 0) {
|
|
h += y;
|
|
y = 0;
|
|
}
|
|
|
|
if ((y + h) > _height) h = _height - y;
|
|
|
|
if (h < 1) return;
|
|
|
|
begin_tft_write();
|
|
|
|
setWindow(x, y, x, y + h - 1);
|
|
|
|
pushBlock(color, h);
|
|
|
|
end_tft_write();
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: drawFastHLine
|
|
** Description: draw a horizontal line
|
|
***************************************************************************************/
|
|
void TFT_eSPI::drawFastHLine(int32_t x, int32_t y, int32_t w, uint32_t color)
|
|
{
|
|
// Clipping
|
|
if ((y < 0) || (x >= _width) || (y >= _height)) return;
|
|
|
|
if (x < 0) {
|
|
w += x;
|
|
x = 0;
|
|
}
|
|
|
|
if ((x + w) > _width) w = _width - x;
|
|
|
|
if (w < 1) return;
|
|
|
|
begin_tft_write();
|
|
|
|
setWindow(x, y, x + w - 1, y);
|
|
|
|
pushBlock(color, w);
|
|
|
|
end_tft_write();
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: fillRect
|
|
** Description: draw a filled rectangle
|
|
***************************************************************************************/
|
|
void TFT_eSPI::fillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color)
|
|
{
|
|
// Clipping
|
|
if ((x >= _width) || (y >= _height)) return;
|
|
|
|
if (x < 0) {
|
|
w += x;
|
|
x = 0;
|
|
}
|
|
if (y < 0) {
|
|
h += y;
|
|
y = 0;
|
|
}
|
|
|
|
if ((x + w) > _width) w = _width - x;
|
|
if ((y + h) > _height) h = _height - y;
|
|
|
|
if ((w < 1) || (h < 1)) return;
|
|
|
|
begin_tft_write();
|
|
|
|
setWindow(x, y, x + w - 1, y + h - 1);
|
|
|
|
pushBlock(color, w * h);
|
|
|
|
end_tft_write();
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: color565
|
|
** Description: convert three 8 bit RGB levels to a 16 bit colour value
|
|
***************************************************************************************/
|
|
uint16_t TFT_eSPI::color565(uint8_t r, uint8_t g, uint8_t b)
|
|
{
|
|
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: color16to8
|
|
** Description: convert 16 bit colour to an 8 bit 332 RGB colour value
|
|
***************************************************************************************/
|
|
uint8_t TFT_eSPI::color16to8(uint16_t c)
|
|
{
|
|
return ((c & 0xE000) >> 8) | ((c & 0x0700) >> 6) | ((c & 0x0018) >> 3);
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: color8to16
|
|
** Description: convert 8 bit colour to a 16 bit 565 colour value
|
|
***************************************************************************************/
|
|
uint16_t TFT_eSPI::color8to16(uint8_t color)
|
|
{
|
|
uint8_t blue[] = {0, 11, 21, 31}; // blue 2 to 5 bit colour lookup table
|
|
uint16_t color16 = 0;
|
|
|
|
// =====Green===== ===============Red==============
|
|
color16 = (color & 0x1C) << 6 | (color & 0xC0) << 5 | (color & 0xE0) << 8;
|
|
// =====Green===== =======Blue======
|
|
color16 |= (color & 0x1C) << 3 | blue[color & 0x03];
|
|
|
|
return color16;
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: color16to24
|
|
** Description: convert 16 bit colour to a 24 bit 888 colour value
|
|
***************************************************************************************/
|
|
uint32_t TFT_eSPI::color16to24(uint16_t color565)
|
|
{
|
|
uint8_t r = (color565 >> 8) & 0xF8; r |= (r >> 5);
|
|
uint8_t g = (color565 >> 3) & 0xFC; g |= (g >> 6);
|
|
uint8_t b = (color565 << 3) & 0xF8; b |= (b >> 5);
|
|
|
|
return ((uint32_t)r << 16) | ((uint32_t)g << 8) | ((uint32_t)b << 0);
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: color24to16
|
|
** Description: convert 24 bit colour to a 16 bit 565 colour value
|
|
***************************************************************************************/
|
|
uint32_t TFT_eSPI::color24to16(uint32_t color888)
|
|
{
|
|
uint16_t r = (color888 >> 8) & 0xF800;
|
|
uint16_t g = (color888 >> 5) & 0x07E0;
|
|
uint16_t b = (color888 >> 3) & 0x001F;
|
|
|
|
return (r | g | b);
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: invertDisplay
|
|
** Description: invert the display colours i = 1 invert, i = 0 normal
|
|
***************************************************************************************/
|
|
void TFT_eSPI::invertDisplay(bool i)
|
|
{
|
|
begin_tft_write();
|
|
// Send the command twice as otherwise it does not always work!
|
|
writecommand(i ? TFT_INVON : TFT_INVOFF);
|
|
writecommand(i ? TFT_INVON : TFT_INVOFF);
|
|
end_tft_write();
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
** Function name: setAttribute
|
|
** Description: Sets a control parameter of an attribute
|
|
**************************************************************************/
|
|
void TFT_eSPI::setAttribute(uint8_t attr_id, uint8_t param)
|
|
{
|
|
switch (attr_id) {
|
|
break;
|
|
case CP437_SWITCH:
|
|
_cp437 = param;
|
|
break;
|
|
case UTF8_SWITCH:
|
|
_utf8 = param;
|
|
decoderState = 0;
|
|
break;
|
|
case PSRAM_ENABLE:
|
|
#if defined (ESP32) && defined (CONFIG_SPIRAM_SUPPORT)
|
|
if (psramFound()) _psram_enable = param; // Enable the use of PSRAM (if available)
|
|
else
|
|
#endif
|
|
_psram_enable = false;
|
|
break;
|
|
//case 4: // TBD future feature control
|
|
// _tbd = param;
|
|
// break;
|
|
}
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
** Function name: getAttribute
|
|
** Description: Get value of an attribute (control parameter)
|
|
**************************************************************************/
|
|
uint8_t TFT_eSPI::getAttribute(uint8_t attr_id)
|
|
{
|
|
switch (attr_id) {
|
|
case CP437_SWITCH: // ON/OFF control of full CP437 character set
|
|
return _cp437;
|
|
case UTF8_SWITCH: // ON/OFF control of UTF-8 decoding
|
|
return _utf8;
|
|
case PSRAM_ENABLE:
|
|
return _psram_enable;
|
|
//case 3: // TBD future feature control
|
|
// return _tbd;
|
|
// break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: decodeUTF8
|
|
** Description: Serial UTF-8 decoder with fall-back to extended ASCII
|
|
*************************************************************************************x*/
|
|
#define DECODE_UTF8 // Test only, comment out to stop decoding
|
|
uint16_t TFT_eSPI::decodeUTF8(uint8_t c)
|
|
{
|
|
#ifdef DECODE_UTF8
|
|
// 7 bit Unicode Code Point
|
|
if ((c & 0x80) == 0x00) {
|
|
decoderState = 0;
|
|
return (uint16_t)c;
|
|
}
|
|
|
|
if (decoderState == 0) {
|
|
// 11 bit Unicode Code Point
|
|
if ((c & 0xE0) == 0xC0) {
|
|
decoderBuffer = ((c & 0x1F) << 6);
|
|
decoderState = 1;
|
|
return 0;
|
|
}
|
|
// 16 bit Unicode Code Point
|
|
if ((c & 0xF0) == 0xE0) {
|
|
decoderBuffer = ((c & 0x0F) << 12);
|
|
decoderState = 2;
|
|
return 0;
|
|
}
|
|
// 21 bit Unicode Code Point not supported so fall-back to extended ASCII
|
|
// if ((c & 0xF8) == 0xF0) return (uint16_t)c;
|
|
} else {
|
|
if (decoderState == 2) {
|
|
decoderBuffer |= ((c & 0x3F) << 6);
|
|
decoderState--;
|
|
return 0;
|
|
} else {
|
|
decoderBuffer |= (c & 0x3F);
|
|
decoderState = 0;
|
|
return decoderBuffer;
|
|
}
|
|
}
|
|
|
|
decoderState = 0;
|
|
#endif
|
|
|
|
return (uint16_t)c; // fall-back to extended ASCII
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: decodeUTF8
|
|
** Description: Line buffer UTF-8 decoder with fall-back to extended ASCII
|
|
*************************************************************************************x*/
|
|
uint16_t TFT_eSPI::decodeUTF8(uint8_t *buf, uint16_t *index, uint16_t remaining)
|
|
{
|
|
uint16_t c = buf[(*index)++];
|
|
//Serial.print("Byte from string = 0x"); Serial.println(c, HEX);
|
|
|
|
#ifdef DECODE_UTF8
|
|
// 7 bit Unicode
|
|
if ((c & 0x80) == 0x00) return c;
|
|
|
|
// 11 bit Unicode
|
|
if (((c & 0xE0) == 0xC0) && (remaining > 1))
|
|
return ((c & 0x1F) << 6) | (buf[(*index)++] & 0x3F);
|
|
|
|
// 16 bit Unicode
|
|
if (((c & 0xF0) == 0xE0) && (remaining > 2)) {
|
|
c = ((c & 0x0F) << 12) | ((buf[(*index)++] & 0x3F) << 6);
|
|
return c | ((buf[(*index)++] & 0x3F));
|
|
}
|
|
|
|
// 21 bit Unicode not supported so fall-back to extended ASCII
|
|
// if ((c & 0xF8) == 0xF0) return c;
|
|
#endif
|
|
|
|
return c; // fall-back to extended ASCII
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: alphaBlend
|
|
** Description: Blend 16bit foreground and background
|
|
*************************************************************************************x*/
|
|
uint16_t TFT_eSPI::alphaBlend(uint8_t alpha, uint16_t fgc, uint16_t bgc)
|
|
{
|
|
// For speed use fixed point maths and rounding to permit a power of 2 division
|
|
uint16_t fgR = ((fgc >> 10) & 0x3E) + 1;
|
|
uint16_t fgG = ((fgc >> 4) & 0x7E) + 1;
|
|
uint16_t fgB = ((fgc << 1) & 0x3E) + 1;
|
|
|
|
uint16_t bgR = ((bgc >> 10) & 0x3E) + 1;
|
|
uint16_t bgG = ((bgc >> 4) & 0x7E) + 1;
|
|
uint16_t bgB = ((bgc << 1) & 0x3E) + 1;
|
|
|
|
// Shift right 1 to drop rounding bit and shift right 8 to divide by 256
|
|
uint16_t r = (((fgR * alpha) + (bgR * (255 - alpha))) >> 9);
|
|
uint16_t g = (((fgG * alpha) + (bgG * (255 - alpha))) >> 9);
|
|
uint16_t b = (((fgB * alpha) + (bgB * (255 - alpha))) >> 9);
|
|
|
|
// Combine RGB565 colours into 16 bits
|
|
//return ((r&0x18) << 11) | ((g&0x30) << 5) | ((b&0x18) << 0); // 2 bit greyscale
|
|
//return ((r&0x1E) << 11) | ((g&0x3C) << 5) | ((b&0x1E) << 0); // 4 bit greyscale
|
|
return (r << 11) | (g << 5) | (b << 0);
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: alphaBlend
|
|
** Description: Blend 16bit foreground and background with dither
|
|
*************************************************************************************x*/
|
|
uint16_t TFT_eSPI::alphaBlend(uint8_t alpha, uint16_t fgc, uint16_t bgc, uint8_t dither)
|
|
{
|
|
if (dither) {
|
|
int16_t alphaDither = (int16_t)alpha - dither + random(2 * dither + 1); // +/-4 randomised
|
|
alpha = (uint8_t)alphaDither;
|
|
if (alphaDither < 0) alpha = 0;
|
|
if (alphaDither > 255) alpha = 255;
|
|
}
|
|
|
|
return alphaBlend(alpha, fgc, bgc);
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: alphaBlend
|
|
** Description: Blend 24bit foreground and background with optional dither
|
|
*************************************************************************************x*/
|
|
uint32_t TFT_eSPI::alphaBlend24(uint8_t alpha, uint32_t fgc, uint32_t bgc, uint8_t dither)
|
|
{
|
|
|
|
if (dither) {
|
|
int16_t alphaDither = (int16_t)alpha - dither + random(2 * dither + 1); // +/-dither randomised
|
|
alpha = (uint8_t)alphaDither;
|
|
if (alphaDither < 0) alpha = 0;
|
|
if (alphaDither > 255) alpha = 255;
|
|
}
|
|
|
|
// For speed use fixed point maths and rounding to permit a power of 2 division
|
|
uint16_t fgR = ((fgc >> 15) & 0x1FE) + 1;
|
|
uint16_t fgG = ((fgc >> 7) & 0x1FE) + 1;
|
|
uint16_t fgB = ((fgc << 1) & 0x1FE) + 1;
|
|
|
|
uint16_t bgR = ((bgc >> 15) & 0x1FE) + 1;
|
|
uint16_t bgG = ((bgc >> 7) & 0x1FE) + 1;
|
|
uint16_t bgB = ((bgc << 1) & 0x1FE) + 1;
|
|
|
|
// Shift right 1 to drop rounding bit and shift right 8 to divide by 256
|
|
uint16_t r = (((fgR * alpha) + (bgR * (255 - alpha))) >> 9);
|
|
uint16_t g = (((fgG * alpha) + (bgG * (255 - alpha))) >> 9);
|
|
uint16_t b = (((fgB * alpha) + (bgB * (255 - alpha))) >> 9);
|
|
|
|
// Combine RGB colours into 24 bits
|
|
return (r << 16) | (g << 8) | (b << 0);
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: write
|
|
** Description: draw characters piped through serial stream
|
|
***************************************************************************************/
|
|
size_t TFT_eSPI::write(uint8_t utf8)
|
|
{
|
|
if (utf8 == '\r') return 1;
|
|
|
|
uint16_t uniCode = utf8;
|
|
|
|
if (_utf8) uniCode = decodeUTF8(utf8);
|
|
|
|
if (uniCode == 0) return 1;
|
|
|
|
#ifdef SMOOTH_FONT
|
|
if (fontLoaded) {
|
|
//Serial.print("UniCode="); Serial.println(uniCode);
|
|
//Serial.print("UTF8 ="); Serial.println(utf8);
|
|
|
|
//fontFile = SPIFFS.open( _gFontFilename, "r" );
|
|
|
|
//if(!fontFile)
|
|
//{
|
|
// fontLoaded = false;
|
|
// return 1;
|
|
//}
|
|
|
|
drawGlyph(uniCode);
|
|
|
|
//fontFile.close();
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
if (uniCode == '\n') uniCode += 22; // Make it a valid space character to stop errors
|
|
else if (uniCode < 32) return 1;
|
|
|
|
uint16_t width = 0;
|
|
uint16_t height = 0;
|
|
|
|
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv DEBUG vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
|
|
//Serial.print((uint8_t) uniCode); // Debug line sends all printed TFT text to serial port
|
|
//Serial.println(uniCode, HEX); // Debug line sends all printed TFT text to serial port
|
|
//delay(5); // Debug optional wait for serial port to flush through
|
|
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DEBUG ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
|
#ifdef LOAD_GFXFF
|
|
if (!gfxFont) {
|
|
#endif
|
|
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
|
|
|
#ifdef LOAD_FONT2
|
|
if (textfont == 2) {
|
|
if (uniCode > 127) return 1;
|
|
|
|
width = pgm_read_byte(widtbl_f16 + uniCode - 32);
|
|
height = chr_hgt_f16;
|
|
// Font 2 is rendered in whole byte widths so we must allow for this
|
|
width = (width + 6) / 8; // Width in whole bytes for font 2, should be + 7 but must allow for font width change
|
|
width = width * 8; // Width converted back to pixels
|
|
}
|
|
#ifdef LOAD_RLE
|
|
else
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef LOAD_RLE
|
|
{
|
|
if ((textfont > 2) && (textfont < 9)) {
|
|
if (uniCode > 127) return 1;
|
|
// Uses the fontinfo struct array to avoid lots of 'if' or 'switch' statements
|
|
width = pgm_read_byte( (uint8_t *)pgm_read_dword( &(fontdata[textfont].widthtbl ) ) + uniCode - 32 );
|
|
height = pgm_read_byte( &fontdata[textfont].height );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef LOAD_GLCD
|
|
if (textfont == 1) {
|
|
width = 6;
|
|
height = 8;
|
|
}
|
|
#else
|
|
if (textfont == 1) return 1;
|
|
#endif
|
|
|
|
height = height * textsize;
|
|
|
|
if (utf8 == '\n') {
|
|
cursor_y += height;
|
|
cursor_x = 0;
|
|
} else {
|
|
if (textwrapX && (cursor_x + width * textsize > _width)) {
|
|
cursor_y += height;
|
|
cursor_x = 0;
|
|
}
|
|
if (textwrapY && (cursor_y >= (int32_t)_height)) cursor_y = 0;
|
|
cursor_x += drawChar(uniCode, cursor_x, cursor_y, textfont);
|
|
}
|
|
|
|
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
|
#ifdef LOAD_GFXFF
|
|
} // Custom GFX font
|
|
else {
|
|
if (utf8 == '\n') {
|
|
cursor_x = 0;
|
|
cursor_y += (int16_t)textsize *
|
|
(uint8_t)pgm_read_byte(&gfxFont->yAdvance);
|
|
} else {
|
|
if (uniCode > pgm_read_word(&gfxFont->last )) return 1;
|
|
if (uniCode < pgm_read_word(&gfxFont->first)) return 1;
|
|
|
|
uint16_t c2 = uniCode - pgm_read_word(&gfxFont->first);
|
|
GFXglyph *glyph = &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c2]);
|
|
uint8_t w = pgm_read_byte(&glyph->width),
|
|
h = pgm_read_byte(&glyph->height);
|
|
if ((w > 0) && (h > 0)) { // Is there an associated bitmap?
|
|
int16_t xo = (int8_t)pgm_read_byte(&glyph->xOffset);
|
|
if (textwrapX && ((cursor_x + textsize * (xo + w)) > _width)) {
|
|
// Drawing character would go off right edge; wrap to new line
|
|
cursor_x = 0;
|
|
cursor_y += (int16_t)textsize *
|
|
(uint8_t)pgm_read_byte(&gfxFont->yAdvance);
|
|
}
|
|
if (textwrapY && (cursor_y >= (int32_t)_height)) cursor_y = 0;
|
|
drawChar(cursor_x, cursor_y, uniCode, textcolor, textbgcolor, textsize);
|
|
}
|
|
cursor_x += pgm_read_byte(&glyph->xAdvance) * (int16_t)textsize;
|
|
}
|
|
}
|
|
#endif // LOAD_GFXFF
|
|
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: drawChar
|
|
** Description: draw a Unicode glyph onto the screen
|
|
***************************************************************************************/
|
|
// Any UTF-8 decoding must be done before calling drawChar()
|
|
int16_t TFT_eSPI::drawChar(uint16_t uniCode, int32_t x, int32_t y)
|
|
{
|
|
return drawChar(uniCode, x, y, textfont);
|
|
}
|
|
|
|
// Any UTF-8 decoding must be done before calling drawChar()
|
|
int16_t TFT_eSPI::drawChar(uint16_t uniCode, int32_t x, int32_t y, uint8_t font)
|
|
{
|
|
if (!uniCode) return 0;
|
|
|
|
if (font == 1) {
|
|
#ifdef LOAD_GLCD
|
|
#ifndef LOAD_GFXFF
|
|
drawChar(x, y, uniCode, textcolor, textbgcolor, textsize);
|
|
return 6 * textsize;
|
|
#endif
|
|
#else
|
|
#ifndef LOAD_GFXFF
|
|
return 0;
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef LOAD_GFXFF
|
|
drawChar(x, y, uniCode, textcolor, textbgcolor, textsize);
|
|
if (!gfxFont) { // 'Classic' built-in font
|
|
#ifdef LOAD_GLCD
|
|
return 6 * textsize;
|
|
#else
|
|
return 0;
|
|
#endif
|
|
} else {
|
|
if ((uniCode >= pgm_read_word(&gfxFont->first)) && (uniCode <= pgm_read_word(&gfxFont->last) )) {
|
|
uint16_t c2 = uniCode - pgm_read_word(&gfxFont->first);
|
|
GFXglyph *glyph = &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c2]);
|
|
return pgm_read_byte(&glyph->xAdvance) * textsize;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if ((font > 1) && (font < 9) && ((uniCode < 32) || (uniCode > 127))) return 0;
|
|
|
|
int32_t width = 0;
|
|
int32_t height = 0;
|
|
uint32_t flash_address = 0;
|
|
uniCode -= 32;
|
|
|
|
#ifdef LOAD_FONT2
|
|
if (font == 2) {
|
|
flash_address = pgm_read_dword(&chrtbl_f16[uniCode]);
|
|
width = pgm_read_byte(widtbl_f16 + uniCode);
|
|
height = chr_hgt_f16;
|
|
}
|
|
#ifdef LOAD_RLE
|
|
else
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef LOAD_RLE
|
|
{
|
|
if ((font > 2) && (font < 9)) {
|
|
flash_address = pgm_read_dword( (const void *)(pgm_read_dword( &(fontdata[font].chartbl ) ) + uniCode * sizeof(void *)) );
|
|
width = pgm_read_byte( (uint8_t *)pgm_read_dword( &(fontdata[font].widthtbl ) ) + uniCode );
|
|
height = pgm_read_byte( &fontdata[font].height );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
int32_t w = width;
|
|
int32_t pX = 0;
|
|
int32_t pY = y;
|
|
uint8_t line = 0;
|
|
|
|
#ifdef LOAD_FONT2 // chop out code if we do not need it
|
|
if (font == 2) {
|
|
w = w + 6; // Should be + 7 but we need to compensate for width increment
|
|
w = w / 8;
|
|
if (x + width * textsize >= (int16_t)_width) return width * textsize ;
|
|
|
|
if (textcolor == textbgcolor || textsize != 1) {
|
|
//begin_tft_write(); // Sprite class can use this function, avoiding begin_tft_write()
|
|
inTransaction = true;
|
|
|
|
for (int32_t i = 0; i < height; i++) {
|
|
if (textcolor != textbgcolor) fillRect(x, pY, width * textsize, textsize, textbgcolor);
|
|
|
|
for (int32_t k = 0; k < w; k++) {
|
|
line = pgm_read_byte((uint8_t *)flash_address + w * i + k);
|
|
if (line) {
|
|
if (textsize == 1) {
|
|
pX = x + k * 8;
|
|
if (line & 0x80) drawPixel(pX, pY, textcolor);
|
|
if (line & 0x40) drawPixel(pX + 1, pY, textcolor);
|
|
if (line & 0x20) drawPixel(pX + 2, pY, textcolor);
|
|
if (line & 0x10) drawPixel(pX + 3, pY, textcolor);
|
|
if (line & 0x08) drawPixel(pX + 4, pY, textcolor);
|
|
if (line & 0x04) drawPixel(pX + 5, pY, textcolor);
|
|
if (line & 0x02) drawPixel(pX + 6, pY, textcolor);
|
|
if (line & 0x01) drawPixel(pX + 7, pY, textcolor);
|
|
} else {
|
|
pX = x + k * 8 * textsize;
|
|
if (line & 0x80) fillRect(pX, pY, textsize, textsize, textcolor);
|
|
if (line & 0x40) fillRect(pX + textsize, pY, textsize, textsize, textcolor);
|
|
if (line & 0x20) fillRect(pX + 2 * textsize, pY, textsize, textsize, textcolor);
|
|
if (line & 0x10) fillRect(pX + 3 * textsize, pY, textsize, textsize, textcolor);
|
|
if (line & 0x08) fillRect(pX + 4 * textsize, pY, textsize, textsize, textcolor);
|
|
if (line & 0x04) fillRect(pX + 5 * textsize, pY, textsize, textsize, textcolor);
|
|
if (line & 0x02) fillRect(pX + 6 * textsize, pY, textsize, textsize, textcolor);
|
|
if (line & 0x01) fillRect(pX + 7 * textsize, pY, textsize, textsize, textcolor);
|
|
}
|
|
}
|
|
}
|
|
pY += textsize;
|
|
}
|
|
|
|
inTransaction = false;
|
|
end_tft_write();
|
|
} else { // Faster drawing of characters and background using block write
|
|
begin_tft_write();
|
|
|
|
setWindow(x, y, x + width - 1, y + height - 1);
|
|
|
|
uint8_t mask;
|
|
for (int32_t i = 0; i < height; i++) {
|
|
pX = width;
|
|
for (int32_t k = 0; k < w; k++) {
|
|
line = pgm_read_byte((uint8_t *) (flash_address + w * i + k) );
|
|
mask = 0x80;
|
|
while (mask && pX) {
|
|
if (line & mask) {
|
|
tft_Write_16(textcolor);
|
|
} else {
|
|
tft_Write_16(textbgcolor);
|
|
}
|
|
pX--;
|
|
mask = mask >> 1;
|
|
}
|
|
}
|
|
if (pX) {
|
|
tft_Write_16(textbgcolor);
|
|
}
|
|
}
|
|
|
|
end_tft_write();
|
|
}
|
|
}
|
|
|
|
#ifdef LOAD_RLE
|
|
else
|
|
#endif
|
|
#endif //FONT2
|
|
|
|
#ifdef LOAD_RLE //674 bytes of code
|
|
// Font is not 2 and hence is RLE encoded
|
|
{
|
|
begin_tft_write();
|
|
inTransaction = true;
|
|
|
|
w *= height; // Now w is total number of pixels in the character
|
|
if ((textsize != 1) || (textcolor == textbgcolor)) {
|
|
if (textcolor != textbgcolor) fillRect(x, pY, width * textsize, textsize * height, textbgcolor);
|
|
int32_t px = 0, py = pY; // To hold character block start and end column and row values
|
|
int32_t pc = 0; // Pixel count
|
|
uint8_t np = textsize * textsize; // Number of pixels in a drawn pixel
|
|
|
|
uint8_t tnp = 0; // Temporary copy of np for while loop
|
|
uint8_t ts = textsize - 1; // Temporary copy of textsize
|
|
// 16 bit pixel count so maximum font size is equivalent to 180x180 pixels in area
|
|
// w is total number of pixels to plot to fill character block
|
|
while (pc < w) {
|
|
line = pgm_read_byte((uint8_t *)flash_address);
|
|
flash_address++;
|
|
if (line & 0x80) {
|
|
line &= 0x7F;
|
|
line++;
|
|
if (ts) {
|
|
px = x + textsize * (pc % width); // Keep these px and py calculations outside the loop as they are slow
|
|
py = y + textsize * (pc / width);
|
|
} else {
|
|
px = x + pc % width; // Keep these px and py calculations outside the loop as they are slow
|
|
py = y + pc / width;
|
|
}
|
|
while (line--) { // In this case the while(line--) is faster
|
|
pc++; // This is faster than putting pc+=line before while()?
|
|
setWindow(px, py, px + ts, py + ts);
|
|
|
|
if (ts) {
|
|
tnp = np;
|
|
while (tnp--) {
|
|
tft_Write_16(textcolor);
|
|
}
|
|
} else {
|
|
tft_Write_16(textcolor);
|
|
}
|
|
px += textsize;
|
|
|
|
if (px >= (x + width * textsize)) {
|
|
px = x;
|
|
py += textsize;
|
|
}
|
|
}
|
|
} else {
|
|
line++;
|
|
pc += line;
|
|
}
|
|
}
|
|
} else {
|
|
// Text colour != background && textsize = 1 and character is within screen area
|
|
// so use faster drawing of characters and background using block write
|
|
if ((x >= 0) && (x + width <= _width) && (y >= 0) && (y + height <= _height)) {
|
|
setWindow(x, y, x + width - 1, y + height - 1);
|
|
|
|
// Maximum font size is equivalent to 180x180 pixels in area
|
|
while (w > 0) {
|
|
line = pgm_read_byte((uint8_t *)flash_address++); // 8 bytes smaller when incrementing here
|
|
if (line & 0x80) {
|
|
line &= 0x7F;
|
|
line++; w -= line;
|
|
pushBlock(textcolor, line);
|
|
} else {
|
|
line++; w -= line;
|
|
pushBlock(textbgcolor, line);
|
|
}
|
|
}
|
|
} else {
|
|
int32_t px = x, py = y; // To hold character block start and end column and row values
|
|
int32_t pc = 0; // Pixel count
|
|
int32_t pl = 0; // Pixel line length
|
|
uint16_t pcol = 0; // Pixel color
|
|
|
|
while (pc < w) {
|
|
line = pgm_read_byte((uint8_t *)flash_address);
|
|
flash_address++;
|
|
if (line & 0x80) {
|
|
pcol = textcolor;
|
|
line &= 0x7F;
|
|
} else pcol = textbgcolor;
|
|
line++;
|
|
px = x + pc % width;
|
|
py = y + pc / width;
|
|
|
|
pl = 0;
|
|
pc += line;
|
|
while (line--) { // In this case the while(line--) is faster
|
|
pl++;
|
|
if ((px + pl) >= (x + width)) {
|
|
drawFastHLine(px, py, pl, pcol);
|
|
pl = 0;
|
|
px = x;
|
|
py ++;
|
|
}
|
|
}
|
|
if (pl)drawFastHLine(px, py, pl, pcol);
|
|
}
|
|
}
|
|
}
|
|
inTransaction = false;
|
|
end_tft_write();
|
|
}
|
|
// End of RLE font rendering
|
|
#endif
|
|
return width * textsize; // x +
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: drawString (with or without user defined font)
|
|
** Description : draw string with padding if it is defined
|
|
***************************************************************************************/
|
|
// Without font number, uses font set by setTextFont()
|
|
int16_t TFT_eSPI::drawString(const String &string, int32_t poX, int32_t poY)
|
|
{
|
|
int16_t len = string.length() + 2;
|
|
char buffer[len];
|
|
string.toCharArray(buffer, len);
|
|
return drawString(buffer, poX, poY, textfont);
|
|
}
|
|
// With font number
|
|
int16_t TFT_eSPI::drawString(const String &string, int32_t poX, int32_t poY, uint8_t font)
|
|
{
|
|
int16_t len = string.length() + 2;
|
|
char buffer[len];
|
|
string.toCharArray(buffer, len);
|
|
return drawString(buffer, poX, poY, font);
|
|
}
|
|
|
|
// Without font number, uses font set by setTextFont()
|
|
int16_t TFT_eSPI::drawString(const char *string, int32_t poX, int32_t poY)
|
|
{
|
|
return drawString(string, poX, poY, textfont);
|
|
}
|
|
|
|
// With font number. Note: font number is over-ridden if a smooth font is loaded
|
|
int16_t TFT_eSPI::drawString(const char *string, int32_t poX, int32_t poY, uint8_t font)
|
|
{
|
|
int16_t sumX = 0;
|
|
uint8_t padding = 1, baseline = 0;
|
|
uint16_t cwidth = textWidth(string, font); // Find the pixel width of the string in the font
|
|
uint16_t cheight = 8 * textsize;
|
|
|
|
#ifdef LOAD_GFXFF
|
|
#ifdef SMOOTH_FONT
|
|
bool freeFont = (font == 1 && gfxFont && !fontLoaded);
|
|
#else
|
|
bool freeFont = (font == 1 && gfxFont);
|
|
#endif
|
|
|
|
if (freeFont) {
|
|
cheight = glyph_ab * textsize;
|
|
poY += cheight; // Adjust for baseline datum of free fonts
|
|
baseline = cheight;
|
|
padding = 101; // Different padding method used for Free Fonts
|
|
|
|
// We need to make an adjustment for the bottom of the string (eg 'y' character)
|
|
if ((textdatum == BL_DATUM) || (textdatum == BC_DATUM) || (textdatum == BR_DATUM)) {
|
|
cheight += glyph_bb * textsize;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
// If it is not font 1 (GLCD or free font) get the baseline and pixel height of the font
|
|
#ifdef SMOOTH_FONT
|
|
if (fontLoaded) {
|
|
baseline = gFont.maxAscent;
|
|
cheight = fontHeight();
|
|
} else
|
|
#endif
|
|
if (font != 1) {
|
|
baseline = pgm_read_byte( &fontdata[font].baseline ) * textsize;
|
|
cheight = fontHeight(font);
|
|
}
|
|
|
|
if (textdatum || padX) {
|
|
|
|
switch (textdatum) {
|
|
case TC_DATUM:
|
|
poX -= cwidth / 2;
|
|
padding += 1;
|
|
break;
|
|
case TR_DATUM:
|
|
poX -= cwidth;
|
|
padding += 2;
|
|
break;
|
|
case ML_DATUM:
|
|
poY -= cheight / 2;
|
|
//padding += 0;
|
|
break;
|
|
case MC_DATUM:
|
|
poX -= cwidth / 2;
|
|
poY -= cheight / 2;
|
|
padding += 1;
|
|
break;
|
|
case MR_DATUM:
|
|
poX -= cwidth;
|
|
poY -= cheight / 2;
|
|
padding += 2;
|
|
break;
|
|
case BL_DATUM:
|
|
poY -= cheight;
|
|
//padding += 0;
|
|
break;
|
|
case BC_DATUM:
|
|
poX -= cwidth / 2;
|
|
poY -= cheight;
|
|
padding += 1;
|
|
break;
|
|
case BR_DATUM:
|
|
poX -= cwidth;
|
|
poY -= cheight;
|
|
padding += 2;
|
|
break;
|
|
case L_BASELINE:
|
|
poY -= baseline;
|
|
//padding += 0;
|
|
break;
|
|
case C_BASELINE:
|
|
poX -= cwidth / 2;
|
|
poY -= baseline;
|
|
padding += 1;
|
|
break;
|
|
case R_BASELINE:
|
|
poX -= cwidth;
|
|
poY -= baseline;
|
|
padding += 2;
|
|
break;
|
|
}
|
|
// Check coordinates are OK, adjust if not
|
|
if (poX < 0) poX = 0;
|
|
if (poX + cwidth > width()) poX = width() - cwidth;
|
|
if (poY < 0) poY = 0;
|
|
if (poY + cheight - baseline > height()) poY = height() - cheight;
|
|
}
|
|
|
|
|
|
int8_t xo = 0;
|
|
#ifdef LOAD_GFXFF
|
|
if (freeFont && (textcolor != textbgcolor)) {
|
|
cheight = (glyph_ab + glyph_bb) * textsize;
|
|
// Get the offset for the first character only to allow for negative offsets
|
|
uint16_t c2 = 0;
|
|
uint16_t len = strlen(string);
|
|
uint16_t n = 0;
|
|
|
|
while (n < len && c2 == 0) c2 = decodeUTF8((uint8_t *)string, &n, len - n);
|
|
|
|
if ((c2 >= pgm_read_word(&gfxFont->first)) && (c2 <= pgm_read_word(&gfxFont->last) )) {
|
|
c2 -= pgm_read_word(&gfxFont->first);
|
|
GFXglyph *glyph = &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c2]);
|
|
xo = pgm_read_byte(&glyph->xOffset) * textsize;
|
|
// Adjust for negative xOffset
|
|
if (xo > 0) xo = 0;
|
|
else cwidth -= xo;
|
|
// Add 1 pixel of padding all round
|
|
//cheight +=2;
|
|
//fillRect(poX+xo-1, poY - 1 - glyph_ab * textsize, cwidth+2, cheight, textbgcolor);
|
|
fillRect(poX + xo, poY - glyph_ab * textsize, cwidth, cheight, textbgcolor);
|
|
}
|
|
padding -= 100;
|
|
}
|
|
#endif
|
|
|
|
uint16_t len = strlen(string);
|
|
uint16_t n = 0;
|
|
|
|
#ifdef SMOOTH_FONT
|
|
if (fontLoaded) {
|
|
if (textcolor != textbgcolor) fillRect(poX, poY, cwidth, cheight, textbgcolor);
|
|
|
|
setCursor(poX, poY);
|
|
|
|
while (n < len) {
|
|
uint16_t uniCode = decodeUTF8((uint8_t *)string, &n, len - n);
|
|
drawGlyph(uniCode);
|
|
}
|
|
sumX += cwidth;
|
|
//fontFile.close();
|
|
} else
|
|
#endif
|
|
{
|
|
while (n < len) {
|
|
uint16_t uniCode = decodeUTF8((uint8_t *)string, &n, len - n);
|
|
sumX += drawChar(uniCode, poX + sumX, poY, font);
|
|
}
|
|
}
|
|
|
|
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv DEBUG vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
|
|
// Switch on debugging for the padding areas
|
|
//#define PADDING_DEBUG
|
|
|
|
#ifndef PADDING_DEBUG
|
|
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DEBUG ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
if ((padX > cwidth) && (textcolor != textbgcolor)) {
|
|
int16_t padXc = poX + cwidth + xo;
|
|
#ifdef LOAD_GFXFF
|
|
if (freeFont) {
|
|
poX += xo; // Adjust for negative offset start character
|
|
poY -= glyph_ab * textsize;
|
|
sumX += poX;
|
|
}
|
|
#endif
|
|
switch (padding) {
|
|
case 1:
|
|
fillRect(padXc, poY, padX - cwidth, cheight, textbgcolor);
|
|
break;
|
|
case 2:
|
|
fillRect(padXc, poY, (padX - cwidth) >> 1, cheight, textbgcolor);
|
|
padXc = (padX - cwidth) >> 1;
|
|
if (padXc > poX) padXc = poX;
|
|
fillRect(poX - padXc, poY, (padX - cwidth) >> 1, cheight, textbgcolor);
|
|
break;
|
|
case 3:
|
|
if (padXc > padX) padXc = padX;
|
|
fillRect(poX + cwidth - padXc, poY, padXc - cwidth, cheight, textbgcolor);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
#else
|
|
|
|
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv DEBUG vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
|
|
// This is debug code to show text (green box) and blanked (white box) areas
|
|
// It shows that the padding areas are being correctly sized and positioned
|
|
|
|
if ((padX > sumX) && (textcolor != textbgcolor)) {
|
|
int16_t padXc = poX + sumX; // Maximum left side padding
|
|
#ifdef LOAD_GFXFF
|
|
if ((font == 1) && (gfxFont)) poY -= glyph_ab;
|
|
#endif
|
|
drawRect(poX, poY, sumX, cheight, TFT_GREEN);
|
|
switch (padding) {
|
|
case 1:
|
|
drawRect(padXc, poY, padX - sumX, cheight, TFT_WHITE);
|
|
break;
|
|
case 2:
|
|
drawRect(padXc, poY, (padX - sumX) >> 1, cheight, TFT_WHITE);
|
|
padXc = (padX - sumX) >> 1;
|
|
if (padXc > poX) padXc = poX;
|
|
drawRect(poX - padXc, poY, (padX - sumX) >> 1, cheight, TFT_WHITE);
|
|
break;
|
|
case 3:
|
|
if (padXc > padX) padXc = padX;
|
|
drawRect(poX + sumX - padXc, poY, padXc - sumX, cheight, TFT_WHITE);
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DEBUG ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
return sumX;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: drawCentreString (deprecated, use setTextDatum())
|
|
** Descriptions: draw string centred on dX
|
|
***************************************************************************************/
|
|
int16_t TFT_eSPI::drawCentreString(const String &string, int32_t dX, int32_t poY, uint8_t font)
|
|
{
|
|
int16_t len = string.length() + 2;
|
|
char buffer[len];
|
|
string.toCharArray(buffer, len);
|
|
return drawCentreString(buffer, dX, poY, font);
|
|
}
|
|
|
|
int16_t TFT_eSPI::drawCentreString(const char *string, int32_t dX, int32_t poY, uint8_t font)
|
|
{
|
|
uint8_t tempdatum = textdatum;
|
|
int32_t sumX = 0;
|
|
textdatum = TC_DATUM;
|
|
sumX = drawString(string, dX, poY, font);
|
|
textdatum = tempdatum;
|
|
return sumX;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: drawRightString (deprecated, use setTextDatum())
|
|
** Descriptions: draw string right justified to dX
|
|
***************************************************************************************/
|
|
int16_t TFT_eSPI::drawRightString(const String &string, int32_t dX, int32_t poY, uint8_t font)
|
|
{
|
|
int16_t len = string.length() + 2;
|
|
char buffer[len];
|
|
string.toCharArray(buffer, len);
|
|
return drawRightString(buffer, dX, poY, font);
|
|
}
|
|
|
|
int16_t TFT_eSPI::drawRightString(const char *string, int32_t dX, int32_t poY, uint8_t font)
|
|
{
|
|
uint8_t tempdatum = textdatum;
|
|
int16_t sumX = 0;
|
|
textdatum = TR_DATUM;
|
|
sumX = drawString(string, dX, poY, font);
|
|
textdatum = tempdatum;
|
|
return sumX;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: drawNumber
|
|
** Description: draw a long integer
|
|
***************************************************************************************/
|
|
int16_t TFT_eSPI::drawNumber(long long_num, int32_t poX, int32_t poY)
|
|
{
|
|
isDigits = true; // Eliminate jiggle in monospaced fonts
|
|
char str[12];
|
|
ltoa(long_num, str, 10);
|
|
return drawString(str, poX, poY, textfont);
|
|
}
|
|
|
|
int16_t TFT_eSPI::drawNumber(long long_num, int32_t poX, int32_t poY, uint8_t font)
|
|
{
|
|
isDigits = true; // Eliminate jiggle in monospaced fonts
|
|
char str[12];
|
|
ltoa(long_num, str, 10);
|
|
return drawString(str, poX, poY, font);
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: drawFloat
|
|
** Descriptions: drawFloat, prints 7 non zero digits maximum
|
|
***************************************************************************************/
|
|
// Assemble and print a string, this permits alignment relative to a datum
|
|
// looks complicated but much more compact and actually faster than using print class
|
|
int16_t TFT_eSPI::drawFloat(float floatNumber, uint8_t dp, int32_t poX, int32_t poY)
|
|
{
|
|
return drawFloat(floatNumber, dp, poX, poY, textfont);
|
|
}
|
|
|
|
int16_t TFT_eSPI::drawFloat(float floatNumber, uint8_t dp, int32_t poX, int32_t poY, uint8_t font)
|
|
{
|
|
isDigits = true;
|
|
char str[14]; // Array to contain decimal string
|
|
uint8_t ptr = 0; // Initialise pointer for array
|
|
int8_t digits = 1; // Count the digits to avoid array overflow
|
|
float rounding = 0.5; // Round up down delta
|
|
|
|
if (dp > 7) dp = 7; // Limit the size of decimal portion
|
|
|
|
// Adjust the rounding value
|
|
for (uint8_t i = 0; i < dp; ++i) rounding /= 10.0;
|
|
|
|
if (floatNumber < -rounding) { // add sign, avoid adding - sign to 0.0!
|
|
str[ptr++] = '-'; // Negative number
|
|
str[ptr] = 0; // Put a null in the array as a precaution
|
|
digits = 0; // Set digits to 0 to compensate so pointer value can be used later
|
|
floatNumber = -floatNumber; // Make positive
|
|
}
|
|
|
|
floatNumber += rounding; // Round up or down
|
|
|
|
// For error put ... in string and return (all TFT_eSPI library fonts contain . character)
|
|
if (floatNumber >= 2147483647) {
|
|
strcpy(str, "...");
|
|
return drawString(str, poX, poY, font);
|
|
}
|
|
// No chance of overflow from here on
|
|
|
|
// Get integer part
|
|
uint32_t temp = (uint32_t)floatNumber;
|
|
|
|
// Put integer part into array
|
|
ltoa(temp, str + ptr, 10);
|
|
|
|
// Find out where the null is to get the digit count loaded
|
|
while ((uint8_t)str[ptr] != 0) ptr++; // Move the pointer along
|
|
digits += ptr; // Count the digits
|
|
|
|
str[ptr++] = '.'; // Add decimal point
|
|
str[ptr] = '0'; // Add a dummy zero
|
|
str[ptr + 1] = 0; // Add a null but don't increment pointer so it can be overwritten
|
|
|
|
// Get the decimal portion
|
|
floatNumber = floatNumber - temp;
|
|
|
|
// Get decimal digits one by one and put in array
|
|
// Limit digit count so we don't get a false sense of resolution
|
|
uint8_t i = 0;
|
|
while ((i < dp) && (digits < 9)) { // while (i < dp) for no limit but array size must be increased
|
|
i++;
|
|
floatNumber *= 10; // for the next decimal
|
|
temp = floatNumber; // get the decimal
|
|
ltoa(temp, str + ptr, 10);
|
|
ptr++; digits++; // Increment pointer and digits count
|
|
floatNumber -= temp; // Remove that digit
|
|
}
|
|
|
|
// Finally we can plot the string and return pixel length
|
|
return drawString(str, poX, poY, font);
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: setFreeFont
|
|
** Descriptions: Sets the GFX free font to use
|
|
***************************************************************************************/
|
|
|
|
#ifdef LOAD_GFXFF
|
|
|
|
void TFT_eSPI::setFreeFont(const GFXfont *f)
|
|
{
|
|
if (f == nullptr) { // Fix issue #400 (ESP32 crash)
|
|
setTextFont(1); // Use GLCD font
|
|
return;
|
|
}
|
|
|
|
textfont = 1;
|
|
gfxFont = (GFXfont *)f;
|
|
|
|
glyph_ab = 0;
|
|
glyph_bb = 0;
|
|
uint16_t numChars = pgm_read_word(&gfxFont->last) - pgm_read_word(&gfxFont->first);
|
|
|
|
// Find the biggest above and below baseline offsets
|
|
for (uint8_t c = 0; c < numChars; c++) {
|
|
GFXglyph *glyph1 = &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c]);
|
|
int8_t ab = -pgm_read_byte(&glyph1->yOffset);
|
|
if (ab > glyph_ab) glyph_ab = ab;
|
|
int8_t bb = pgm_read_byte(&glyph1->height) - ab;
|
|
if (bb > glyph_bb) glyph_bb = bb;
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: setTextFont
|
|
** Description: Set the font for the print stream
|
|
***************************************************************************************/
|
|
void TFT_eSPI::setTextFont(uint8_t f)
|
|
{
|
|
textfont = (f > 0) ? f : 1; // Don't allow font 0
|
|
gfxFont = NULL;
|
|
}
|
|
|
|
#else
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: setFreeFont
|
|
** Descriptions: Sets the GFX free font to use
|
|
***************************************************************************************/
|
|
|
|
// Alternative to setTextFont() so we don't need two different named functions
|
|
void TFT_eSPI::setFreeFont(uint8_t font)
|
|
{
|
|
setTextFont(font);
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: setTextFont
|
|
** Description: Set the font for the print stream
|
|
***************************************************************************************/
|
|
void TFT_eSPI::setTextFont(uint8_t f)
|
|
{
|
|
textfont = (f > 0) ? f : 1; // Don't allow font 0
|
|
}
|
|
#endif
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: getSPIinstance
|
|
** Description: Get the instance of the SPI class
|
|
***************************************************************************************/
|
|
#if !defined (TFT_PARALLEL_8_BIT)
|
|
SPIClass &TFT_eSPI::getSPIinstance(void)
|
|
{
|
|
return spi;
|
|
}
|
|
#endif
|
|
|
|
/***************************************************************************************
|
|
** Function name: getSetup
|
|
** Description: Get the setup details for diagnostic and sketch access
|
|
***************************************************************************************/
|
|
void TFT_eSPI::getSetup(setup_t &tft_settings)
|
|
{
|
|
// tft_settings.version is set in header file
|
|
|
|
#if defined (PROCESSOR_ID)
|
|
tft_settings.esp = PROCESSOR_ID;
|
|
#else
|
|
tft_settings.esp = -1;
|
|
#endif
|
|
|
|
#if defined (SUPPORT_TRANSACTIONS)
|
|
tft_settings.trans = true;
|
|
#else
|
|
tft_settings.trans = false;
|
|
#endif
|
|
|
|
#if defined (TFT_PARALLEL_8_BIT)
|
|
tft_settings.serial = false;
|
|
tft_settings.tft_spi_freq = 0;
|
|
#else
|
|
tft_settings.serial = true;
|
|
tft_settings.tft_spi_freq = SPI_FREQUENCY / 100000;
|
|
#ifdef SPI_READ_FREQUENCY
|
|
tft_settings.tft_rd_freq = SPI_READ_FREQUENCY / 100000;
|
|
#endif
|
|
#endif
|
|
|
|
#if defined(TFT_SPI_OVERLAP)
|
|
tft_settings.overlap = true;
|
|
#else
|
|
tft_settings.overlap = false;
|
|
#endif
|
|
|
|
tft_settings.tft_driver = drv.tft_driver;
|
|
tft_settings.tft_width = _init_width;
|
|
tft_settings.tft_height = _init_height;
|
|
|
|
#ifdef CGRAM_OFFSET
|
|
tft_settings.r0_x_offset = colstart;
|
|
tft_settings.r0_y_offset = rowstart;
|
|
tft_settings.r1_x_offset = 0;
|
|
tft_settings.r1_y_offset = 0;
|
|
tft_settings.r2_x_offset = 0;
|
|
tft_settings.r2_y_offset = 0;
|
|
tft_settings.r3_x_offset = 0;
|
|
tft_settings.r3_y_offset = 0;
|
|
#else
|
|
tft_settings.r0_x_offset = 0;
|
|
tft_settings.r0_y_offset = 0;
|
|
tft_settings.r1_x_offset = 0;
|
|
tft_settings.r1_y_offset = 0;
|
|
tft_settings.r2_x_offset = 0;
|
|
tft_settings.r2_y_offset = 0;
|
|
tft_settings.r3_x_offset = 0;
|
|
tft_settings.r3_y_offset = 0;
|
|
#endif
|
|
|
|
#if defined (TFT_MOSI)
|
|
tft_settings.pin_tft_mosi = TFT_MOSI;
|
|
#else
|
|
tft_settings.pin_tft_mosi = -1;
|
|
#endif
|
|
|
|
#if defined (TFT_MISO)
|
|
tft_settings.pin_tft_miso = TFT_MISO;
|
|
#else
|
|
tft_settings.pin_tft_miso = -1;
|
|
#endif
|
|
|
|
#if defined (TFT_SCLK)
|
|
tft_settings.pin_tft_clk = TFT_SCLK;
|
|
#else
|
|
tft_settings.pin_tft_clk = -1;
|
|
#endif
|
|
|
|
#if defined (TFT_CS)
|
|
tft_settings.pin_tft_cs = TFT_CS;
|
|
#else
|
|
tft_settings.pin_tft_cs = -1;
|
|
#endif
|
|
|
|
#if defined (TFT_DC)
|
|
tft_settings.pin_tft_dc = TFT_DC;
|
|
#else
|
|
tft_settings.pin_tft_dc = -1;
|
|
#endif
|
|
|
|
#if defined (TFT_RD)
|
|
tft_settings.pin_tft_rd = TFT_RD;
|
|
#else
|
|
tft_settings.pin_tft_rd = -1;
|
|
#endif
|
|
|
|
#if defined (TFT_WR)
|
|
tft_settings.pin_tft_wr = TFT_WR;
|
|
#else
|
|
tft_settings.pin_tft_wr = -1;
|
|
#endif
|
|
|
|
#if defined (TFT_RST)
|
|
tft_settings.pin_tft_rst = TFT_RST;
|
|
#else
|
|
tft_settings.pin_tft_rst = -1;
|
|
#endif
|
|
|
|
#if defined (TFT_PARALLEL_8_BIT)
|
|
tft_settings.pin_tft_d0 = TFT_D0;
|
|
tft_settings.pin_tft_d1 = TFT_D1;
|
|
tft_settings.pin_tft_d2 = TFT_D2;
|
|
tft_settings.pin_tft_d3 = TFT_D3;
|
|
tft_settings.pin_tft_d4 = TFT_D4;
|
|
tft_settings.pin_tft_d5 = TFT_D5;
|
|
tft_settings.pin_tft_d6 = TFT_D6;
|
|
tft_settings.pin_tft_d7 = TFT_D7;
|
|
#else
|
|
tft_settings.pin_tft_d0 = -1;
|
|
tft_settings.pin_tft_d1 = -1;
|
|
tft_settings.pin_tft_d2 = -1;
|
|
tft_settings.pin_tft_d3 = -1;
|
|
tft_settings.pin_tft_d4 = -1;
|
|
tft_settings.pin_tft_d5 = -1;
|
|
tft_settings.pin_tft_d6 = -1;
|
|
tft_settings.pin_tft_d7 = -1;
|
|
#endif
|
|
|
|
#if defined (TFT_BL)
|
|
tft_settings.pin_tft_led = TFT_BL;
|
|
#endif
|
|
|
|
#if defined (TFT_BACKLIGHT_ON)
|
|
tft_settings.pin_tft_led_on = TFT_BACKLIGHT_ON;
|
|
#endif
|
|
|
|
#if defined (TOUCH_CS)
|
|
tft_settings.pin_tch_cs = TOUCH_CS;
|
|
tft_settings.tch_spi_freq = SPI_TOUCH_FREQUENCY / 100000;
|
|
#else
|
|
tft_settings.pin_tch_cs = -1;
|
|
tft_settings.tch_spi_freq = 0;
|
|
#endif
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
/**************************************************************************************
|
|
// The following class creates Sprites in RAM, graphics can then be drawn in the Sprite
|
|
// and rendered quickly onto the TFT screen. The class inherits the graphics functions
|
|
// from the TFT_eSPI class. Some functions are overridden by this class so that the
|
|
// graphics are written to the Sprite rather than the TFT.
|
|
// Coded by Bodmer, see license file in root folder
|
|
***************************************************************************************/
|
|
/***************************************************************************************
|
|
// Color bytes are swapped when writing to RAM, this introduces a small overhead but
|
|
// there is a nett performance gain by using swapped bytes.
|
|
***************************************************************************************/
|
|
|
|
/***************************************************************************************
|
|
** Function name: TFT_eSprite
|
|
** Description: Class constructor
|
|
*************************************************************************************x*/
|
|
TFT_eSprite::TFT_eSprite(TFT_eSPI *tft)
|
|
{
|
|
_tft = tft; // Pointer to tft class so we can call member functions
|
|
|
|
_iwidth = 0; // Initialise width and height to 0 (it does not exist yet)
|
|
_iheight = 0;
|
|
_bpp = 16;
|
|
_iswapBytes = false; // Do not swap pushImage colour bytes by default
|
|
|
|
_created = false;
|
|
|
|
_xs = 0; // window bounds for pushColor
|
|
_ys = 0;
|
|
_xe = 0;
|
|
_ye = 0;
|
|
|
|
_xptr = 0; // pushColor coordinate
|
|
_yptr = 0;
|
|
|
|
_xpivot = 0;
|
|
_ypivot = 0;
|
|
|
|
_colorMap = nullptr;
|
|
|
|
this->cursor_y = this->cursor_x = 0; // Text cursor position
|
|
|
|
this->_psram_enable = true;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: createSprite
|
|
** Description: Create a sprite (bitmap) of defined width and height
|
|
*************************************************************************************x*/
|
|
// cast returned value to (uint8_t*) for 8 bit or (uint16_t*) for 16 bit colours
|
|
void *TFT_eSprite::createSprite(int16_t w, int16_t h, uint8_t frames)
|
|
{
|
|
|
|
if ( _created ) return _img8_1;
|
|
|
|
if ( w < 1 || h < 1 ) return NULL;
|
|
|
|
_iwidth = _dwidth = _bitwidth = w;
|
|
_iheight = _dheight = h;
|
|
|
|
this->cursor_x = 0;
|
|
this->cursor_y = 0;
|
|
|
|
// Default scroll rectangle and gap fill colour
|
|
_sx = 0;
|
|
_sy = 0;
|
|
_sw = w;
|
|
_sh = h;
|
|
_scolor = TFT_BLACK;
|
|
|
|
_xpivot = w / 2;
|
|
_ypivot = h / 2;
|
|
|
|
_img8 = (uint8_t *) callocSprite(w, h, frames);
|
|
_img8_1 = _img8;
|
|
_img8_2 = _img8;
|
|
_img = (uint16_t *) _img8;
|
|
_img4 = _img8;
|
|
|
|
if ( (_bpp == 16) && (frames > 1) ) {
|
|
_img8_2 = _img8 + (w * h * 2 + 1);
|
|
}
|
|
|
|
// ESP32 only 16bpp check
|
|
//if (esp_ptr_dma_capable(_img8_1)) Serial.println("DMA capable Sprite pointer _img8_1");
|
|
//else Serial.println("Not a DMA capable Sprite pointer _img8_1");
|
|
//if (esp_ptr_dma_capable(_img8_2)) Serial.println("DMA capable Sprite pointer _img8_2");
|
|
//else Serial.println("Not a DMA capable Sprite pointer _img8_2");
|
|
|
|
if ( (_bpp == 8) && (frames > 1) ) {
|
|
_img8_2 = _img8 + (w * h + 1);
|
|
}
|
|
|
|
if ( (_bpp == 4) && (_colorMap == nullptr)) createPalette(default_4bit_palette);
|
|
|
|
// This is to make it clear what pointer size is expected to be used
|
|
// but casting in the user sketch is needed due to the use of void*
|
|
if ( (_bpp == 1) && (frames > 1) ) {
|
|
w = (w + 7) & 0xFFF8;
|
|
_img8_2 = _img8 + ( (w >> 3) * h + 1 );
|
|
}
|
|
|
|
if (_img8) {
|
|
_created = true;
|
|
return _img8;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: created
|
|
** Description: Returns true is sprite has been created
|
|
*************************************************************************************x*/
|
|
bool TFT_eSprite::created(void)
|
|
{
|
|
return _created;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: ~TFT_eSprite
|
|
** Description: Class destructor
|
|
*************************************************************************************x*/
|
|
TFT_eSprite::~TFT_eSprite(void)
|
|
{
|
|
deleteSprite();
|
|
|
|
#ifdef SMOOTH_FONT
|
|
if (this->fontLoaded) this->unloadFont();
|
|
#endif
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: callocSprite
|
|
** Description: Allocate a memory area for the Sprite and return pointer
|
|
*************************************************************************************x*/
|
|
|
|
void *TFT_eSprite::callocSprite(int16_t w, int16_t h, uint8_t frames)
|
|
{
|
|
// Add one extra "off screen" pixel to point out-of-bounds setWindow() coordinates
|
|
// this means push/writeColor functions do not need additional bounds checks and
|
|
// hence will run faster in normal circumstances.
|
|
uint8_t *ptr8 = NULL;
|
|
|
|
if (frames > 2) frames = 2; // Currently restricted to 2 frame buffers
|
|
if (frames < 1) frames = 1;
|
|
|
|
if (_bpp == 16) {
|
|
#if defined (ESP32) && defined (CONFIG_SPIRAM_SUPPORT)
|
|
if ( psramFound() && this->_psram_enable && !_tft->DMA_Enabled) ptr8 = ( uint8_t *) ps_calloc(frames * w * h + frames, sizeof(uint16_t));
|
|
else
|
|
#endif
|
|
ptr8 = ( uint8_t *) calloc(frames * w * h + frames, sizeof(uint16_t));
|
|
}
|
|
|
|
else if (_bpp == 8) {
|
|
#if defined (ESP32) && defined (CONFIG_SPIRAM_SUPPORT)
|
|
if ( psramFound() && this->_psram_enable ) ptr8 = ( uint8_t *) ps_calloc(frames * w * h + frames, sizeof(uint8_t));
|
|
else
|
|
#endif
|
|
ptr8 = ( uint8_t *) calloc(frames * w * h + frames, sizeof(uint8_t));
|
|
}
|
|
|
|
else if (_bpp == 4) {
|
|
w = (w + 1) & 0xFFFE; // width needs to be multiple of 2, with an extra "off screen" pixel
|
|
_iwidth = w;
|
|
#if defined (ESP32) && defined (CONFIG_SPIRAM_SUPPORT)
|
|
if ( psramFound() && this->_psram_enable ) ptr8 = ( uint8_t *) ps_calloc(((frames * w * h) >> 1) + frames, sizeof(uint8_t));
|
|
else
|
|
#endif
|
|
ptr8 = ( uint8_t *) calloc(((frames * w * h) >> 1) + frames, sizeof(uint8_t));
|
|
}
|
|
|
|
else { // Must be 1 bpp
|
|
//_dwidth Display width+height in pixels always in rotation 0 orientation
|
|
//_dheight Not swapped for sprite rotations
|
|
// Note: for 1bpp _iwidth and _iheight are swapped during Sprite rotations
|
|
|
|
w = (w + 7) & 0xFFF8; // width should be the multiple of 8 bits to be compatible with epdpaint
|
|
_iwidth = w; // _iwidth is rounded up to be multiple of 8, so might not be = _dwidth
|
|
_bitwidth = w;
|
|
|
|
#if defined (ESP32) && defined (CONFIG_SPIRAM_SUPPORT)
|
|
if ( psramFound() && this->_psram_enable ) ptr8 = ( uint8_t *) ps_calloc(frames * (w >> 3) * h + frames, sizeof(uint8_t));
|
|
else
|
|
#endif
|
|
ptr8 = ( uint8_t *) calloc(frames * (w >> 3) * h + frames, sizeof(uint8_t));
|
|
}
|
|
|
|
return ptr8;
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: createPalette (from RAM array)
|
|
** Description: Set a palette for a 4-bit per pixel sprite
|
|
*************************************************************************************x*/
|
|
|
|
void TFT_eSprite::createPalette(uint16_t colorMap[], uint8_t colors)
|
|
{
|
|
if (_colorMap != nullptr) {
|
|
free(_colorMap);
|
|
}
|
|
|
|
if (colorMap == nullptr) {
|
|
// Create a color map using the default FLASH map
|
|
createPalette(default_4bit_palette);
|
|
return;
|
|
}
|
|
|
|
// Allocate and clear memory for 16 color map
|
|
_colorMap = (uint16_t *)calloc(16, sizeof(uint16_t));
|
|
|
|
if (colors > 16) colors = 16;
|
|
|
|
// Copy map colors
|
|
for (uint8_t i = 0; i < colors; i++) {
|
|
_colorMap[i] = colorMap[i];
|
|
}
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: createPalette (from FLASH array)
|
|
** Description: Set a palette for a 4-bit per pixel sprite
|
|
*************************************************************************************x*/
|
|
|
|
void TFT_eSprite::createPalette(const uint16_t colorMap[], uint8_t colors)
|
|
{
|
|
if (_colorMap != nullptr) {
|
|
free(_colorMap);
|
|
}
|
|
|
|
if (colorMap == nullptr) {
|
|
// Create a color map using the default FLASH map
|
|
colorMap = default_4bit_palette;
|
|
}
|
|
|
|
// Allocate and clear memory for 16 color map
|
|
_colorMap = (uint16_t *)calloc(16, sizeof(uint16_t));
|
|
|
|
if (colors > 16) colors = 16;
|
|
|
|
// Copy map colors
|
|
for (uint8_t i = 0; i < colors; i++) {
|
|
_colorMap[i] = pgm_read_word(colorMap++);
|
|
}
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: frameBuffer
|
|
** Description: For 1 bpp Sprites, select the frame used for graphics
|
|
*************************************************************************************x*/
|
|
// Frames are numbered 1 and 2
|
|
void *TFT_eSprite::frameBuffer(int8_t f)
|
|
{
|
|
if (!_created) return NULL;
|
|
|
|
if ( f == 2 ) _img8 = _img8_2;
|
|
else _img8 = _img8_1;
|
|
|
|
if (_bpp == 16) _img = (uint16_t *)_img8;
|
|
|
|
//if (_bpp == 8) _img8 = _img8;
|
|
|
|
if (_bpp == 4) _img4 = _img8;
|
|
|
|
return _img8;
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: setColorDepth
|
|
** Description: Set bits per pixel for colour (1, 8 or 16)
|
|
*************************************************************************************x*/
|
|
|
|
void *TFT_eSprite::setColorDepth(int8_t b)
|
|
{
|
|
// Can't change an existing sprite's colour depth so delete it
|
|
if (_created) free(_img8_1);
|
|
|
|
// Now define the new colour depth
|
|
if ( b > 8 ) _bpp = 16; // Bytes per pixel
|
|
else if ( b > 4 ) _bpp = 8;
|
|
else if ( b > 1 ) _bpp = 4;
|
|
else _bpp = 1;
|
|
|
|
// If it existed, re-create the sprite with the new colour depth
|
|
if (_created) {
|
|
_created = false;
|
|
return createSprite(_iwidth, _iheight);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: getColorDepth
|
|
** Description: Get bits per pixel for colour (1, 8 or 16)
|
|
*************************************************************************************x*/
|
|
|
|
int8_t TFT_eSprite::getColorDepth(void)
|
|
{
|
|
if (_created) return _bpp;
|
|
else return 0;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: setBitmapColor
|
|
** Description: Set the 1bpp foreground foreground and background colour
|
|
***************************************************************************************/
|
|
void TFT_eSprite::setBitmapColor(uint16_t c, uint16_t b)
|
|
{
|
|
if (c == b) b = ~c;
|
|
_tft->bitmap_fg = c;
|
|
_tft->bitmap_bg = b;
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: setPaletteColor
|
|
** Description: Set the 4bpp palette color at the given index
|
|
***************************************************************************************/
|
|
void TFT_eSprite::setPaletteColor(uint8_t index, uint16_t color)
|
|
{
|
|
if (_colorMap == nullptr || index > 15) return; // out of bounds
|
|
|
|
_colorMap[index] = color;
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: getPaletteColor
|
|
** Description: Return the palette color at 4bpp index, or 0 on error.
|
|
***************************************************************************************/
|
|
uint16_t TFT_eSprite::getPaletteColor(uint8_t index)
|
|
{
|
|
if (_colorMap == nullptr || index > 15) return 0; // out of bounds
|
|
|
|
return _colorMap[index];
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: deleteSprite
|
|
** Description: Delete the sprite to free up memory (RAM)
|
|
*************************************************************************************x*/
|
|
void TFT_eSprite::deleteSprite(void)
|
|
{
|
|
if (!_created ) return;
|
|
|
|
if (_colorMap != nullptr) {
|
|
free(_colorMap);
|
|
}
|
|
|
|
free(_img8_1);
|
|
|
|
_created = false;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: setPivot
|
|
** Description: Set the pivot point in this Sprite
|
|
*************************************************************************************x*/
|
|
void TFT_eSprite::setPivot(int16_t x, int16_t y)
|
|
{
|
|
_xpivot = x;
|
|
_ypivot = y;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: getPivotX
|
|
** Description: Get the x pivot position
|
|
***************************************************************************************/
|
|
int16_t TFT_eSprite::getPivotX(void)
|
|
{
|
|
return _xpivot;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: getPivotY
|
|
** Description: Get the y pivot position
|
|
***************************************************************************************/
|
|
int16_t TFT_eSprite::getPivotY(void)
|
|
{
|
|
return _ypivot;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: pushRotated - Fast fixed point integer maths version
|
|
** Description: Push rotated Sprite to TFT screen
|
|
*************************************************************************************x*/
|
|
#define FP_SCALE 10
|
|
bool TFT_eSprite::pushRotated(int16_t angle, int32_t transp)
|
|
{
|
|
if ( !_created) return false;
|
|
|
|
// Bounding box parameters
|
|
int16_t min_x;
|
|
int16_t min_y;
|
|
int16_t max_x;
|
|
int16_t max_y;
|
|
|
|
// Get the bounding box of this rotated source Sprite relative to Sprite pivot
|
|
if ( !getRotatedBounds(angle, &min_x, &min_y, &max_x, &max_y) ) return false;
|
|
|
|
uint16_t sline_buffer[max_x - min_x + 1];
|
|
|
|
int32_t xt = min_x - _tft->_xpivot;
|
|
int32_t yt = min_y - _tft->_ypivot;
|
|
uint32_t xe = _iwidth << FP_SCALE;
|
|
uint32_t ye = _iheight << FP_SCALE;
|
|
uint32_t tpcolor = transp; // convert to unsigned
|
|
if (_bpp == 4) tpcolor = _colorMap[transp & 0x0F];
|
|
|
|
_tft->startWrite(); // Avoid transaction overhead for every tft pixel
|
|
|
|
// Scan destination bounding box and fetch transformed pixels from source Sprite
|
|
for (int32_t y = min_y; y <= max_y; y++, yt++) {
|
|
int32_t x = min_x;
|
|
uint32_t xs = (_cosra * xt - (_sinra * yt - (_xpivot << FP_SCALE)) + (1 << (FP_SCALE - 1)));
|
|
uint32_t ys = (_sinra * xt + (_cosra * yt + (_ypivot << FP_SCALE)) + (1 << (FP_SCALE - 1)));
|
|
|
|
while ((xs >= xe || ys >= ye) && x < max_x) {
|
|
x++;
|
|
xs += _cosra;
|
|
ys += _sinra;
|
|
}
|
|
if (x == max_x) continue;
|
|
|
|
uint32_t pixel_count = 0;
|
|
do {
|
|
uint32_t rp;
|
|
int32_t xp = xs >> FP_SCALE;
|
|
int32_t yp = ys >> FP_SCALE;
|
|
if (_bpp == 16) {
|
|
rp = _img[xp + yp * _iwidth];
|
|
rp = rp >> 8 | rp << 8;
|
|
} else rp = readPixel(xp, yp);
|
|
if (tpcolor == rp) {
|
|
if (pixel_count) {
|
|
// TFT window is already clipped, so this is faster than pushImage()
|
|
_tft->setWindow(x - pixel_count, y, x, y);
|
|
_tft->pushPixels(sline_buffer, pixel_count);
|
|
pixel_count = 0;
|
|
}
|
|
} else {
|
|
sline_buffer[pixel_count++] = rp >> 8 | rp << 8;
|
|
}
|
|
} while (++x < max_x && (xs += _cosra) < xe && (ys += _sinra) < ye);
|
|
if (pixel_count) {
|
|
// TFT window is already clipped, so this is faster than pushImage()
|
|
_tft->setWindow(x - pixel_count, y, x, y);
|
|
_tft->pushPixels(sline_buffer, pixel_count);
|
|
}
|
|
}
|
|
|
|
_tft->endWrite(); // End transaction
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: pushRotated - Fast fixed point integer maths version
|
|
** Description: Push a rotated copy of the Sprite to another Sprite
|
|
*************************************************************************************x*/
|
|
// Not compatible with 4bpp
|
|
bool TFT_eSprite::pushRotated(TFT_eSprite *spr, int16_t angle, int32_t transp)
|
|
{
|
|
if ( !_created || _bpp == 4) return false; // Check this Sprite is created
|
|
if ( !spr->_created || spr->_bpp == 4) return false; // Ckeck destination Sprite is created
|
|
|
|
// Bounding box parameters
|
|
int16_t min_x;
|
|
int16_t min_y;
|
|
int16_t max_x;
|
|
int16_t max_y;
|
|
|
|
// Get the bounding box of this rotated source Sprite
|
|
if ( !getRotatedBounds(spr, angle, &min_x, &min_y, &max_x, &max_y) ) return false;
|
|
|
|
uint16_t sline_buffer[max_x - min_x + 1];
|
|
|
|
int32_t xt = min_x - spr->_xpivot;
|
|
int32_t yt = min_y - spr->_ypivot;
|
|
uint32_t xe = _iwidth << FP_SCALE;
|
|
uint32_t ye = _iheight << FP_SCALE;
|
|
uint32_t tpcolor = transp; // convert to unsigned
|
|
|
|
bool oldSwapBytes = spr->getSwapBytes();
|
|
spr->setSwapBytes(false);
|
|
|
|
// Scan destination bounding box and fetch transformed pixels from source Sprite
|
|
for (int32_t y = min_y; y <= max_y; y++, yt++) {
|
|
int32_t x = min_x;
|
|
uint32_t xs = (_cosra * xt - (_sinra * yt - (_xpivot << FP_SCALE)) + (1 << (FP_SCALE - 1)));
|
|
uint32_t ys = (_sinra * xt + (_cosra * yt + (_ypivot << FP_SCALE)) + (1 << (FP_SCALE - 1)));
|
|
|
|
while ((xs >= xe || ys >= ye) && x < max_x) {
|
|
x++;
|
|
xs += _cosra;
|
|
ys += _sinra;
|
|
}
|
|
if (x == max_x) continue;
|
|
|
|
uint32_t pixel_count = 0;
|
|
do {
|
|
uint32_t rp;
|
|
int32_t xp = xs >> FP_SCALE;
|
|
int32_t yp = ys >> FP_SCALE;
|
|
if (_bpp == 16) {
|
|
rp = _img[xp + yp * _iwidth];
|
|
rp = rp >> 8 | rp << 8;
|
|
} else rp = readPixel(xp, yp);
|
|
if (tpcolor == rp) {
|
|
if (pixel_count) {
|
|
spr->pushImage(x - pixel_count, y, pixel_count, 1, sline_buffer);
|
|
pixel_count = 0;
|
|
}
|
|
} else {
|
|
sline_buffer[pixel_count++] = rp;
|
|
}
|
|
} while (++x < max_x && (xs += _cosra) < xe && (ys += _sinra) < ye);
|
|
if (pixel_count) spr->pushImage(x - pixel_count, y, pixel_count, 1, sline_buffer);
|
|
}
|
|
spr->setSwapBytes(oldSwapBytes);
|
|
return true;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: getRotatedBounds
|
|
** Description: Get TFT bounding box of a rotated Sprite wrt pivot
|
|
*************************************************************************************x*/
|
|
bool TFT_eSprite::getRotatedBounds(int16_t angle, int16_t *min_x, int16_t *min_y,
|
|
int16_t *max_x, int16_t *max_y)
|
|
{
|
|
// Get the bounding box of this rotated source Sprite relative to Sprite pivot
|
|
getRotatedBounds(angle, width(), height(), _xpivot, _ypivot, min_x, min_y, max_x, max_y);
|
|
|
|
// Move bounding box so source Sprite pivot coincides with TFT pivot
|
|
*min_x += _tft->_xpivot;
|
|
*max_x += _tft->_xpivot;
|
|
*min_y += _tft->_ypivot;
|
|
*max_y += _tft->_ypivot;
|
|
|
|
// Return if bounding box is outside of TFT area
|
|
if (*min_x > _tft->width()) return false;
|
|
if (*min_y > _tft->height()) return false;
|
|
if (*max_x < 0) return false;
|
|
if (*max_y < 0) return false;
|
|
|
|
// Clip bounding box to be within TFT area
|
|
if (*min_x < 0) *min_x = 0;
|
|
if (*min_y < 0) *min_y = 0;
|
|
if (*max_x > _tft->width()) *max_x = _tft->width();
|
|
if (*max_y > _tft->height()) *max_y = _tft->height();
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: getRotatedBounds
|
|
** Description: Get destination Sprite bounding box of a rotated Sprite wrt pivot
|
|
*************************************************************************************x*/
|
|
bool TFT_eSprite::getRotatedBounds(TFT_eSprite *spr, int16_t angle, int16_t *min_x, int16_t *min_y,
|
|
int16_t *max_x, int16_t *max_y)
|
|
{
|
|
// Get the bounding box of this rotated source Sprite relative to Sprite pivot
|
|
getRotatedBounds(angle, width(), height(), _xpivot, _ypivot, min_x, min_y, max_x, max_y);
|
|
|
|
// Move bounding box so source Sprite pivot coincides with destination Sprite pivot
|
|
*min_x += spr->_xpivot;
|
|
*max_x += spr->_xpivot;
|
|
*min_y += spr->_ypivot;
|
|
*max_y += spr->_ypivot;
|
|
|
|
// Test only to show bounding box
|
|
// spr->fillSprite(TFT_BLACK);
|
|
// spr->drawRect(min_x, min_y, max_x - min_x + 1, max_y - min_y + 1, TFT_GREEN);
|
|
|
|
// Return if bounding box is completely outside of destination Sprite
|
|
if (*min_x > spr->width()) return true;
|
|
if (*min_y > spr->height()) return true;
|
|
if (*max_x < 0) return true;
|
|
if (*max_y < 0) return true;
|
|
|
|
// Clip bounding box if it is partially within destination Sprite
|
|
if (*min_x < 0) min_x = 0;
|
|
if (*min_y < 0) min_y = 0;
|
|
if (*max_x > spr->width()) *max_x = spr->width();
|
|
if (*max_y > spr->height()) *max_y = spr->height();
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: rotatedBounds
|
|
** Description: Get bounding box of a rotated Sprite wrt pivot
|
|
*************************************************************************************x*/
|
|
void TFT_eSprite::getRotatedBounds(int16_t angle, int16_t w, int16_t h, int16_t xp, int16_t yp,
|
|
int16_t *min_x, int16_t *min_y, int16_t *max_x, int16_t *max_y)
|
|
{
|
|
// Trig values for the rotation
|
|
float radAngle = -angle * 0.0174532925; // Convert degrees to radians
|
|
float sina = sin(radAngle);
|
|
float cosa = cos(radAngle);
|
|
|
|
w -= xp; // w is now right edge coordinate relative to xp
|
|
h -= yp; // h is now bottom edge coordinate relative to yp
|
|
|
|
// Calculate new corner coordinates
|
|
int16_t x0 = -xp * cosa - yp * sina;
|
|
int16_t y0 = xp * sina - yp * cosa;
|
|
|
|
int16_t x1 = w * cosa - yp * sina;
|
|
int16_t y1 = -w * sina - yp * cosa;
|
|
|
|
int16_t x2 = h * sina + w * cosa;
|
|
int16_t y2 = h * cosa - w * sina;
|
|
|
|
int16_t x3 = h * sina - xp * cosa;
|
|
int16_t y3 = h * cosa + xp * sina;
|
|
|
|
// Find bounding box extremes, enlarge box to accomodate rounding errors
|
|
*min_x = x0 - 2;
|
|
if (x1 < *min_x) *min_x = x1 - 2;
|
|
if (x2 < *min_x) *min_x = x2 - 2;
|
|
if (x3 < *min_x) *min_x = x3 - 2;
|
|
|
|
*max_x = x0 + 2;
|
|
if (x1 > *max_x) *max_x = x1 + 2;
|
|
if (x2 > *max_x) *max_x = x2 + 2;
|
|
if (x3 > *max_x) *max_x = x3 + 2;
|
|
|
|
*min_y = y0 - 2;
|
|
if (y1 < *min_y) *min_y = y1 - 2;
|
|
if (y2 < *min_y) *min_y = y2 - 2;
|
|
if (y3 < *min_y) *min_y = y3 - 2;
|
|
|
|
*max_y = y0 + 2;
|
|
if (y1 > *max_y) *max_y = y1 + 2;
|
|
if (y2 > *max_y) *max_y = y2 + 2;
|
|
if (y3 > *max_y) *max_y = y3 + 2;
|
|
|
|
_sinra = round(sina * (1 << FP_SCALE));
|
|
_cosra = round(cosa * (1 << FP_SCALE));
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: pushSprite
|
|
** Description: Push the sprite to the TFT at x, y
|
|
*************************************************************************************x*/
|
|
void TFT_eSprite::pushSprite(int32_t x, int32_t y)
|
|
{
|
|
if (!_created) return;
|
|
|
|
if (_bpp == 16) {
|
|
bool oldSwapBytes = _tft->getSwapBytes();
|
|
_tft->setSwapBytes(false);
|
|
_tft->pushImage(x, y, _iwidth, _iheight, _img );
|
|
_tft->setSwapBytes(oldSwapBytes);
|
|
} else if (_bpp == 4) {
|
|
_tft->pushImage(x, y, _dwidth, _dheight, _img4, false, _colorMap);
|
|
} else _tft->pushImage(x, y, _dwidth, _dheight, _img8, (bool)(_bpp == 8));
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: pushSprite
|
|
** Description: Push the sprite to the TFT at x, y with transparent colour
|
|
*************************************************************************************x*/
|
|
void TFT_eSprite::pushSprite(int32_t x, int32_t y, uint16_t transp)
|
|
{
|
|
if (!_created) return;
|
|
|
|
if (_bpp == 16) {
|
|
bool oldSwapBytes = _tft->getSwapBytes();
|
|
_tft->setSwapBytes(false);
|
|
_tft->pushImage(x, y, _iwidth, _iheight, _img, transp );
|
|
_tft->setSwapBytes(oldSwapBytes);
|
|
} else if (_bpp == 8) {
|
|
transp = (uint8_t)((transp & 0xE000) >> 8 | (transp & 0x0700) >> 6 | (transp & 0x0018) >> 3);
|
|
_tft->pushImage(x, y, _dwidth, _dheight, _img8, (uint8_t)transp, (bool)true);
|
|
} else if (_bpp == 4) {
|
|
_tft->pushImage(x, y, _dwidth, _dheight, _img4, (uint8_t)(transp & 0x0F), false, _colorMap);
|
|
} else _tft->pushImage(x, y, _dwidth, _dheight, _img8, 0, (bool)false);
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: readPixelValue
|
|
** Description: Read the color map index of a pixel at defined coordinates
|
|
*************************************************************************************x*/
|
|
uint16_t TFT_eSprite::readPixelValue(int32_t x, int32_t y)
|
|
{
|
|
if ((x < 0) || (x >= _iwidth) || (y < 0) || (y >= _iheight) || !_created) return 0xFF;
|
|
|
|
if (_bpp == 16) {
|
|
// Return the pixel colour
|
|
return readPixel(x, y);
|
|
}
|
|
|
|
if (_bpp == 8) {
|
|
// Return the pixel byte value
|
|
return _img8[x + y * _iwidth];
|
|
}
|
|
|
|
if (_bpp == 4) {
|
|
if ((x & 0x01) == 0)
|
|
return _img4[((x + y * _iwidth) >> 1)] >> 4; // even index = bits 7 .. 4
|
|
else
|
|
return _img4[((x + y * _iwidth) >> 1)] & 0x0F; // odd index = bits 3 .. 0.
|
|
}
|
|
|
|
if (_bpp == 1) {
|
|
if (_rotation == 1) {
|
|
uint16_t tx = x;
|
|
x = _dwidth - y - 1;
|
|
y = tx;
|
|
} else if (_rotation == 2) {
|
|
x = _dwidth - x - 1;
|
|
y = _dheight - y - 1;
|
|
} else if (_rotation == 3) {
|
|
uint16_t tx = x;
|
|
x = y;
|
|
y = _dheight - tx - 1;
|
|
}
|
|
// Return 1 or 0
|
|
return (_img8[(x + y * _bitwidth) >> 3] >> (7 - (x & 0x7))) & 0x01;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: readPixel
|
|
** Description: Read 565 colour of a pixel at defined coordinates
|
|
*************************************************************************************x*/
|
|
uint16_t TFT_eSprite::readPixel(int32_t x, int32_t y)
|
|
{
|
|
if ((x < 0) || (x >= _iwidth) || (y < 0) || (y >= _iheight) || !_created) return 0xFFFF;
|
|
|
|
if (_bpp == 16) {
|
|
uint16_t color = _img[x + y * _iwidth];
|
|
return (color >> 8) | (color << 8);
|
|
}
|
|
|
|
if (_bpp == 8) {
|
|
uint16_t color = _img8[x + y * _iwidth];
|
|
if (color != 0) {
|
|
uint8_t blue[] = {0, 11, 21, 31};
|
|
color = (color & 0xE0) << 8 | (color & 0xC0) << 5
|
|
| (color & 0x1C) << 6 | (color & 0x1C) << 3
|
|
| blue[color & 0x03];
|
|
}
|
|
return color;
|
|
}
|
|
|
|
if (_bpp == 4) {
|
|
uint16_t color;
|
|
if ((x & 0x01) == 0)
|
|
color = _colorMap[_img4[((x + y * _iwidth) >> 1)] >> 4]; // even index = bits 7 .. 4
|
|
else
|
|
color = _colorMap[_img4[((x + y * _iwidth) >> 1)] & 0x0F]; // odd index = bits 3 .. 0.
|
|
return color;
|
|
}
|
|
|
|
if (_rotation == 1) {
|
|
uint16_t tx = x;
|
|
x = _dwidth - y - 1;
|
|
y = tx;
|
|
} else if (_rotation == 2) {
|
|
x = _dwidth - x - 1;
|
|
y = _dheight - y - 1;
|
|
} else if (_rotation == 3) {
|
|
uint16_t tx = x;
|
|
x = y;
|
|
y = _dheight - tx - 1;
|
|
}
|
|
|
|
uint16_t color = (_img8[(x + y * _bitwidth) >> 3] << (x & 0x7)) & 0x80;
|
|
|
|
if (color) return _tft->bitmap_fg;
|
|
else return _tft->bitmap_bg;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: pushImage
|
|
** Description: push 565 colour image into a defined area of a sprite
|
|
*************************************************************************************x*/
|
|
void TFT_eSprite::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t *data)
|
|
{
|
|
if ((x >= _iwidth) || (y >= _iheight) || (w == 0) || (h == 0) || !_created) return;
|
|
if ((x + w < 0) || (y + h < 0)) return;
|
|
|
|
int32_t xo = 0;
|
|
int32_t yo = 0;
|
|
|
|
int32_t xs = x;
|
|
int32_t ys = y;
|
|
|
|
int32_t ws = w;
|
|
int32_t hs = h;
|
|
|
|
if (x < 0) {
|
|
xo = -x;
|
|
ws += x;
|
|
xs = 0;
|
|
}
|
|
if (y < 0) {
|
|
yo = -y;
|
|
hs += y;
|
|
ys = 0;
|
|
}
|
|
|
|
if (xs + ws >= (int32_t)_iwidth) ws = _iwidth - xs;
|
|
if (ys + hs >= (int32_t)_iheight) hs = _iheight - ys;
|
|
|
|
if (_bpp == 16) { // Plot a 16 bpp image into a 16 bpp Sprite
|
|
for (int32_t yp = yo; yp < yo + hs; yp++) {
|
|
x = xs;
|
|
for (int32_t xp = xo; xp < xo + ws; xp++) {
|
|
uint16_t color = data[xp + yp * w];
|
|
if (_iswapBytes) color = color << 8 | color >> 8;
|
|
_img[x + ys * _iwidth] = color;
|
|
x++;
|
|
}
|
|
ys++;
|
|
}
|
|
} else if (_bpp == 8) { // Plot a 16 bpp image into a 8 bpp Sprite
|
|
for (int32_t yp = yo; yp < yo + hs; yp++) {
|
|
x = xs;
|
|
for (int32_t xp = xo; xp < xo + ws; xp++) {
|
|
uint16_t color = data[xp + yp * w];
|
|
if (_iswapBytes) color = color << 8 | color >> 8;
|
|
_img8[x + ys * _iwidth] = (uint8_t)((color & 0xE000) >> 8 | (color & 0x0700) >> 6 | (color & 0x0018) >> 3);
|
|
x++;
|
|
}
|
|
ys++;
|
|
}
|
|
} else if (_bpp == 4) {
|
|
// the image is assumed to be 4 bit, where each byte corresponds to two pixels.
|
|
// much faster when aligned to a byte boundary, because the alternative is slower, requiring
|
|
// tedious bit operations.
|
|
|
|
const uint8_t *dataBuf = (uint8_t *)data;
|
|
int sWidth = (_iwidth >> 1);
|
|
|
|
if ((xs & 0x01) == 0 && (xo & 0x01) == 0 && (ws & 0x01) == 0) {
|
|
if ((ws & 0x01) == 0) { // use memcpy for better perf.
|
|
xs = (xs >> 1) + ys * sWidth;
|
|
ws = (ws >> 1);
|
|
xo = (xo >> 1) + yo * (w >> 1);
|
|
while (hs--) {
|
|
memcpy(_img4 + xs, dataBuf + xo, ws);
|
|
xo += (w >> 1);
|
|
xs += sWidth;
|
|
}
|
|
}
|
|
} else { // not optimized
|
|
for (int32_t yp = yo; yp < yo + hs; yp++) {
|
|
x = xs;
|
|
for (int32_t xp = xo; xp < xo + ws; xp++) {
|
|
uint32_t color;
|
|
if ((xp & 0x01) == 0)
|
|
color = (dataBuf[((xp + yp * w) >> 1)] & 0xF0) >> 4; // even index = bits 7 .. 4
|
|
else
|
|
color = dataBuf[((xp - 1 + yp * w) >> 1)] & 0x0F; // odd index = bits 3 .. 0.
|
|
drawPixel(x, ys, color);
|
|
x++;
|
|
}
|
|
ys++;
|
|
}
|
|
}
|
|
}
|
|
|
|
else { // 1bpp
|
|
// Move coordinate rotation to support fn
|
|
if (_rotation == 1) {
|
|
int32_t tx = x;
|
|
x = _dwidth - y - 1;
|
|
y = tx;
|
|
} else if (_rotation == 2) {
|
|
x = _dwidth - x - 1;
|
|
y = _dheight - y - 1;
|
|
} else if (_rotation == 3) {
|
|
int32_t tx = x;
|
|
x = y;
|
|
y = _dheight - tx - 1;
|
|
}
|
|
// Plot a 1bpp image into a 1bpp Sprite
|
|
uint8_t *pdata = (uint8_t * ) data;
|
|
uint32_t ww = (w + 7) & 0xFFF8;
|
|
for (int32_t yp = 0; yp < h; yp++) {
|
|
uint32_t yw = (yp * ww) >> 3;
|
|
uint32_t yyp = y + yp;
|
|
for (int32_t xp = 0; xp < w; xp++) {
|
|
uint16_t readPixel = (pdata[(xp >> 3) + yw] & (0x80 >> (xp & 0x7)) );
|
|
drawPixel(x + xp, yyp, readPixel);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: pushImage
|
|
** Description: push 565 colour FLASH (PROGMEM) image into a defined area
|
|
*************************************************************************************x*/
|
|
void TFT_eSprite::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, const uint16_t *data)
|
|
{
|
|
#ifdef ESP32
|
|
pushImage(x, y, w, h, (uint16_t *) data);
|
|
#else
|
|
// Partitioned memory FLASH processor
|
|
if ((x >= _iwidth) || (y >= _iheight) || (w == 0) || (h == 0) || !_created) return;
|
|
if ((x + w < 0) || (y + h < 0)) return;
|
|
|
|
int32_t xo = 0;
|
|
int32_t yo = 0;
|
|
|
|
int32_t xs = x;
|
|
int32_t ys = y;
|
|
|
|
int32_t ws = w;
|
|
int32_t hs = h;
|
|
|
|
if (x < 0) {
|
|
xo = -x;
|
|
ws += x;
|
|
xs = 0;
|
|
}
|
|
if (y < 0) {
|
|
yo = -y;
|
|
hs += y;
|
|
ys = 0;
|
|
}
|
|
|
|
if (xs + ws >= (int32_t)_iwidth) ws = _iwidth - xs;
|
|
if (ys + hs >= (int32_t)_iheight) hs = _iheight - ys;
|
|
|
|
if (_bpp == 16) { // Plot a 16 bpp image into a 16 bpp Sprite
|
|
for (int32_t yp = yo; yp < yo + hs; yp++) {
|
|
x = xs;
|
|
for (int32_t xp = xo; xp < xo + ws; xp++) {
|
|
uint16_t color = pgm_read_word(data + xp + yp * w);
|
|
if (_iswapBytes) color = color << 8 | color >> 8;
|
|
_img[x + ys * _iwidth] = color;
|
|
x++;
|
|
}
|
|
ys++;
|
|
}
|
|
}
|
|
|
|
else if (_bpp == 8) { // Plot a 16 bpp image into a 8 bpp Sprite
|
|
for (int32_t yp = yo; yp < yo + hs; yp++) {
|
|
x = xs;
|
|
for (int32_t xp = xo; xp < xo + ws; xp++) {
|
|
uint16_t color = pgm_read_word(data + xp + yp * w);
|
|
if (_iswapBytes) color = color << 8 | color >> 8;
|
|
_img8[x + ys * _iwidth] = (uint8_t)((color & 0xE000) >> 8 | (color & 0x0700) >> 6 | (color & 0x0018) >> 3);
|
|
x++;
|
|
}
|
|
ys++;
|
|
}
|
|
}
|
|
|
|
else if (_bpp == 4) {
|
|
#ifdef TFT_eSPI_DEBUG
|
|
Serial.println("TFT_eSprite::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, const uint16_t *data) not implemented");
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
else { // 1bpp
|
|
// Move coordinate rotation to support fn
|
|
if (_rotation == 1) {
|
|
int32_t tx = x;
|
|
x = _dwidth - y - 1;
|
|
y = tx;
|
|
} else if (_rotation == 2) {
|
|
x = _dwidth - x - 1;
|
|
y = _dheight - y - 1;
|
|
} else if (_rotation == 3) {
|
|
int32_t tx = x;
|
|
x = y;
|
|
y = _dheight - tx - 1;
|
|
}
|
|
// Plot a 1bpp image into a 1bpp Sprite
|
|
const uint8_t *pdata = (const uint8_t * ) data;
|
|
uint32_t ww = (w + 7) & 0xFFF8;
|
|
for (int32_t yp = 0; yp < h; yp++) {
|
|
for (uint32_t xp = 0; xp < ww; xp += 8) {
|
|
uint8_t pbyte = pgm_read_byte(pdata++);
|
|
for (uint8_t xc = 0; xc < 8; xc++) {
|
|
if (xp + xc < (uint32_t)w) drawPixel(x + xp + xc, y + yp, (pbyte << xc) & 0x80);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif // if ESP32 else ESP8266 check
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: setSwapBytes
|
|
** Description: Used by 16 bit pushImage() to swap byte order in colours
|
|
***************************************************************************************/
|
|
void TFT_eSprite::setSwapBytes(bool swap)
|
|
{
|
|
_iswapBytes = swap;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: getSwapBytes
|
|
** Description: Return the swap byte order for colours
|
|
***************************************************************************************/
|
|
bool TFT_eSprite::getSwapBytes(void)
|
|
{
|
|
return _iswapBytes;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: setWindow
|
|
** Description: Set the bounds of a window for pushColor and writeColor
|
|
*************************************************************************************x*/
|
|
void TFT_eSprite::setWindow(int32_t x0, int32_t y0, int32_t x1, int32_t y1)
|
|
{
|
|
if (x0 > x1) swap_coord(x0, x1);
|
|
if (y0 > y1) swap_coord(y0, y1);
|
|
|
|
if ((x0 >= _iwidth) || (x1 < 0) || (y0 >= _iheight) || (y1 < 0)) {
|
|
// Point to that extra "off screen" pixel
|
|
_xs = 0;
|
|
_ys = _iheight;
|
|
_xe = 0;
|
|
_ye = _iheight;
|
|
} else {
|
|
if (x0 < 0) x0 = 0;
|
|
if (x1 >= _iwidth) x1 = _iwidth - 1;
|
|
if (y0 < 0) y0 = 0;
|
|
if (y1 >= _iheight) y1 = _iheight - 1;
|
|
|
|
_xs = x0;
|
|
_ys = y0;
|
|
_xe = x1;
|
|
_ye = y1;
|
|
}
|
|
|
|
_xptr = _xs;
|
|
_yptr = _ys;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: pushColor
|
|
** Description: Send a new pixel to the set window
|
|
*************************************************************************************x*/
|
|
void TFT_eSprite::pushColor(uint32_t color)
|
|
{
|
|
if (!_created ) return;
|
|
|
|
// Write the colour to RAM in set window
|
|
if (_bpp == 16)
|
|
_img [_xptr + _yptr * _iwidth] = (uint16_t) (color >> 8) | (color << 8);
|
|
|
|
else if (_bpp == 8)
|
|
_img8[_xptr + _yptr * _iwidth] = (uint8_t )((color & 0xE000) >> 8 | (color & 0x0700) >> 6 | (color & 0x0018) >> 3);
|
|
|
|
else if (_bpp == 4) {
|
|
uint8_t c = (uint8_t)color & 0x0F;
|
|
if ((_xptr & 0x01) == 0) {
|
|
_img4[(_xptr + _yptr * _iwidth) >> 1] = (c << 4) | (_img4[(_xptr + _yptr * _iwidth) >> 1] & 0x0F); // new color is in bits 7 .. 4
|
|
} else {
|
|
_img4[(_xptr + _yptr * _iwidth) >> 1] = (_img4[(_xptr + _yptr * _iwidth) >> 1] & 0xF0) | c; // new color is the low bits
|
|
}
|
|
}
|
|
|
|
else drawPixel(_xptr, _yptr, color);
|
|
|
|
// Increment x
|
|
_xptr++;
|
|
|
|
// Wrap on x and y to start, increment y if needed
|
|
if (_xptr > _xe) {
|
|
_xptr = _xs;
|
|
_yptr++;
|
|
if (_yptr > _ye) _yptr = _ys;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: pushColor
|
|
** Description: Send a "len" new pixels to the set window
|
|
*************************************************************************************x*/
|
|
void TFT_eSprite::pushColor(uint32_t color, uint16_t len)
|
|
{
|
|
if (!_created ) return;
|
|
|
|
uint16_t pixelColor;
|
|
|
|
if (_bpp == 16)
|
|
pixelColor = (uint16_t) (color >> 8) | (color << 8);
|
|
|
|
else if (_bpp == 8)
|
|
pixelColor = (color & 0xE000) >> 8 | (color & 0x0700) >> 6 | (color & 0x0018) >> 3;
|
|
|
|
else pixelColor = (uint16_t) color; // for 1bpp or 4bpp
|
|
|
|
while (len--) writeColor(pixelColor);
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: writeColor
|
|
** Description: Write a pixel with pre-formatted colour to the set window
|
|
*************************************************************************************x*/
|
|
void TFT_eSprite::writeColor(uint16_t color)
|
|
{
|
|
if (!_created ) return;
|
|
|
|
// Write 16 bit RGB 565 encoded colour to RAM
|
|
if (_bpp == 16) _img [_xptr + _yptr * _iwidth] = color;
|
|
|
|
// Write 8 bit RGB 332 encoded colour to RAM
|
|
else if (_bpp == 8) _img8[_xptr + _yptr * _iwidth] = (uint8_t) color;
|
|
|
|
else if (_bpp == 4) {
|
|
uint8_t c = (uint8_t)color & 0x0F;
|
|
if ((_xptr & 0x01) == 0)
|
|
_img4[(_xptr + _yptr * _iwidth) >> 1] = (c << 4) | (_img4[(_xptr + _yptr * _iwidth) >> 1] & 0x0F); // new color is in bits 7 .. 4
|
|
else
|
|
_img4[(_xptr + _yptr * _iwidth) >> 1] = (_img4[(_xptr + _yptr * _iwidth) >> 1] & 0xF0) | c; // new color is the low bits (x is odd)
|
|
}
|
|
|
|
else drawPixel(_xptr, _yptr, color);
|
|
|
|
// Increment x
|
|
_xptr++;
|
|
|
|
// Wrap on x and y to start, increment y if needed
|
|
if (_xptr > _xe) {
|
|
_xptr = _xs;
|
|
_yptr++;
|
|
if (_yptr > _ye) _yptr = _ys;
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: setScrollRect
|
|
** Description: Set scroll area within the sprite and the gap fill colour
|
|
*************************************************************************************x*/
|
|
void TFT_eSprite::setScrollRect(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t color)
|
|
{
|
|
if ((x >= _iwidth) || (y >= _iheight) || !_created ) return;
|
|
|
|
if (x < 0) {
|
|
w += x;
|
|
x = 0;
|
|
}
|
|
if (y < 0) {
|
|
h += y;
|
|
y = 0;
|
|
}
|
|
|
|
if ((x + w) > _iwidth ) w = _iwidth - x;
|
|
if ((y + h) > _iheight) h = _iheight - y;
|
|
|
|
if ( w < 1 || h < 1) return;
|
|
|
|
_sx = x;
|
|
_sy = y;
|
|
_sw = w;
|
|
_sh = h;
|
|
|
|
_scolor = color;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: scroll
|
|
** Description: Scroll dx,dy pixels, positive right,down, negative left,up
|
|
*************************************************************************************x*/
|
|
void TFT_eSprite::scroll(int16_t dx, int16_t dy)
|
|
{
|
|
if (abs(dx) >= _sw || abs(dy) >= _sh) {
|
|
fillRect (_sx, _sy, _sw, _sh, _scolor);
|
|
return;
|
|
}
|
|
|
|
// Fetch the scroll area width and height set by setScrollRect()
|
|
uint32_t w = _sw - abs(dx); // line width to copy
|
|
uint32_t h = _sh - abs(dy); // lines to copy
|
|
int32_t iw = _iwidth; // width of sprite
|
|
|
|
// Fetch the x,y origin set by setScrollRect()
|
|
uint32_t tx = _sx; // to x
|
|
uint32_t fx = _sx; // from x
|
|
uint32_t ty = _sy; // to y
|
|
uint32_t fy = _sy; // from y
|
|
|
|
// Adjust for x delta
|
|
if (dx <= 0) fx -= dx;
|
|
else tx += dx;
|
|
|
|
// Adjust for y delta
|
|
if (dy <= 0) fy -= dy;
|
|
else {
|
|
// Scrolling down so start copy from bottom
|
|
ty = ty + _sh - 1; // "To" pointer
|
|
iw = -iw; // Pointer moves backwards
|
|
fy = ty - dy; // "From" pointer
|
|
}
|
|
|
|
// Calculate "from y" and "to y" pointers in RAM
|
|
uint32_t fyp = fx + fy * _iwidth;
|
|
uint32_t typ = tx + ty * _iwidth;
|
|
|
|
// Now move the pixels in RAM
|
|
if (_bpp == 16) {
|
|
while (h--) {
|
|
// move pixel lines (to, from, byte count)
|
|
memmove( _img + typ, _img + fyp, w << 1);
|
|
typ += iw;
|
|
fyp += iw;
|
|
}
|
|
} else if (_bpp == 8) {
|
|
while (h--) {
|
|
// move pixel lines (to, from, byte count)
|
|
memmove( _img8 + typ, _img8 + fyp, w);
|
|
typ += iw;
|
|
fyp += iw;
|
|
}
|
|
} else if (_bpp == 4) {
|
|
// could optimize for scrolling by even # pixels using memove (later)
|
|
if (dx > 0) {
|
|
tx += w; // Start from right edge
|
|
fx += w;
|
|
}
|
|
while (h--) {
|
|
// move pixels one by one
|
|
for (uint16_t xp = 0; xp < w; xp++) {
|
|
if (dx <= 0) drawPixel(tx + xp, ty, readPixelValue(fx + xp, fy));
|
|
if (dx > 0) drawPixel(tx - xp, ty, readPixelValue(fx - xp, fy));
|
|
}
|
|
if (dy <= 0) {
|
|
ty++;
|
|
fy++;
|
|
} else {
|
|
ty--;
|
|
fy--;
|
|
}
|
|
}
|
|
} else if (_bpp == 1 ) {
|
|
if (dx > 0) {
|
|
tx += w; // Start from right edge
|
|
fx += w;
|
|
}
|
|
while (h--) {
|
|
// move pixels one by one
|
|
for (uint16_t xp = 0; xp < w; xp++) {
|
|
if (dx <= 0) drawPixel(tx + xp, ty, readPixelValue(fx + xp, fy));
|
|
if (dx > 0) drawPixel(tx - xp, ty, readPixelValue(fx - xp, fy));
|
|
}
|
|
if (dy <= 0) {
|
|
ty++;
|
|
fy++;
|
|
} else {
|
|
ty--;
|
|
fy--;
|
|
}
|
|
}
|
|
} else return; // Not 1, 4, 8 or 16 bpp
|
|
|
|
// Fill the gap left by the scrolling
|
|
if (dx > 0) fillRect(_sx, _sy, dx, _sh, _scolor);
|
|
if (dx < 0) fillRect(_sx + _sw + dx, _sy, -dx, _sh, _scolor);
|
|
if (dy > 0) fillRect(_sx, _sy, _sw, dy, _scolor);
|
|
if (dy < 0) fillRect(_sx, _sy + _sh + dy, _sw, -dy, _scolor);
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: fillSprite
|
|
** Description: Fill the whole sprite with defined colour
|
|
*************************************************************************************x*/
|
|
void TFT_eSprite::fillSprite(uint32_t color)
|
|
{
|
|
if (!_created ) return;
|
|
|
|
// Use memset if possible as it is super fast
|
|
if (( (uint8_t)color == (uint8_t)(color >> 8) ) && _bpp == 16)
|
|
memset(_img, (uint8_t)color, _iwidth * _iheight * 2);
|
|
else if (_bpp == 8) {
|
|
color = (color & 0xE000) >> 8 | (color & 0x0700) >> 6 | (color & 0x0018) >> 3;
|
|
memset(_img8, (uint8_t)color, _iwidth * _iheight);
|
|
} else if (_bpp == 4) {
|
|
uint8_t c = ((color & 0x0F) | (((color & 0x0F) << 4) & 0xF0));
|
|
memset(_img4, c, (_iwidth * _iheight) >> 1);
|
|
} else if (_bpp == 1) {
|
|
if (color) memset(_img8, 0xFF, (_iwidth >> 3) * _iheight + 1);
|
|
else memset(_img8, 0x00, (_iwidth >> 3) * _iheight + 1);
|
|
}
|
|
|
|
else fillRect(0, 0, _iwidth, _iheight, color);
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: setCursor
|
|
** Description: Set the sprite text cursor x,y position
|
|
*************************************************************************************x*/
|
|
// Not needed - using TFT_eSPI class function and this->cursor_x/y
|
|
//void TFT_eSprite::setCursor(int16_t x, int16_t y)
|
|
//{
|
|
// this->cursor_x = x;
|
|
// this->cursor_y = y;
|
|
//}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: width
|
|
** Description: Return the width of sprite
|
|
*************************************************************************************x*/
|
|
// Return the size of the display
|
|
int16_t TFT_eSprite::width(void)
|
|
{
|
|
if (!_created ) return 0;
|
|
|
|
if (_bpp > 1) return _iwidth;
|
|
|
|
if (_rotation == 1 || _rotation == 3) return _dheight;
|
|
|
|
return _dwidth;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: height
|
|
** Description: Return the height of sprite
|
|
*************************************************************************************x*/
|
|
int16_t TFT_eSprite::height(void)
|
|
{
|
|
if (!_created ) return 0;
|
|
|
|
if (_bpp > 1) return _iheight;
|
|
|
|
if (_rotation == 1 || _rotation == 3) return _dwidth;
|
|
|
|
return _dheight;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: setRotation
|
|
** Description: Rotate coordinate frame for 1bpp sprite
|
|
*************************************************************************************x*/
|
|
// Does nothing for 8 and 16 bpp sprites. TODO allow rotation of these sprites
|
|
void TFT_eSprite::setRotation(uint8_t rotation)
|
|
{
|
|
if (_bpp != 1) return;
|
|
|
|
_rotation = rotation;
|
|
if (rotation == 0 && _iwidth > _iheight) swap_coord(_iwidth, _iheight);
|
|
if (rotation == 1 && _iwidth < _iheight) swap_coord(_iwidth, _iheight);
|
|
if (rotation == 2 && _iwidth > _iheight) swap_coord(_iwidth, _iheight);
|
|
if (rotation == 3 && _iwidth < _iheight) swap_coord(_iwidth, _iheight);
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: getRotation
|
|
** Description: Get rotation for 1bpp sprite
|
|
*************************************************************************************x*/
|
|
|
|
uint8_t TFT_eSprite::getRotation(void)
|
|
{
|
|
return _rotation;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: drawPixel
|
|
** Description: push a single pixel at an arbitrary position
|
|
*************************************************************************************x*/
|
|
void TFT_eSprite::drawPixel(int32_t x, int32_t y, uint32_t color)
|
|
{
|
|
// Range checking
|
|
if ((x < 0) || (y < 0) || !_created) return;
|
|
if ((x >= _iwidth) || (y >= _iheight)) return;
|
|
|
|
if (_bpp == 16) {
|
|
color = (color >> 8) | (color << 8);
|
|
_img[x + y * _iwidth] = (uint16_t) color;
|
|
} else if (_bpp == 8) {
|
|
_img8[x + y * _iwidth] = (uint8_t)((color & 0xE000) >> 8 | (color & 0x0700) >> 6 | (color & 0x0018) >> 3);
|
|
} else if (_bpp == 4) {
|
|
uint8_t c = color & 0x0F;
|
|
int index = (x + y * _iwidth) >> 1;;
|
|
if ((x & 0x01) == 0) {
|
|
_img4[index] = (uint8_t)((c << 4) | (_img4[index] & 0x0F));
|
|
} else {
|
|
_img4[index] = (uint8_t)(c | (_img4[index] & 0xF0));
|
|
}
|
|
} else { // 1 bpp
|
|
if (_rotation == 1) {
|
|
uint16_t tx = x;
|
|
x = _dwidth - y - 1;
|
|
y = tx;
|
|
} else if (_rotation == 2) {
|
|
x = _dwidth - x - 1;
|
|
y = _dheight - y - 1;
|
|
} else if (_rotation == 3) {
|
|
uint16_t tx = x;
|
|
x = y;
|
|
y = _dheight - tx - 1;
|
|
}
|
|
|
|
if (color) _img8[(x + y * _bitwidth) >> 3] |= (0x80 >> (x & 0x7));
|
|
else _img8[(x + y * _bitwidth) >> 3] &= ~(0x80 >> (x & 0x7));
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: drawLine
|
|
** Description: draw a line between 2 arbitrary points
|
|
*************************************************************************************x*/
|
|
void TFT_eSprite::drawLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint32_t color)
|
|
{
|
|
if (!_created ) return;
|
|
|
|
bool steep = abs(y1 - y0) > abs(x1 - x0);
|
|
if (steep) {
|
|
swap_coord(x0, y0);
|
|
swap_coord(x1, y1);
|
|
}
|
|
|
|
if (x0 > x1) {
|
|
swap_coord(x0, x1);
|
|
swap_coord(y0, y1);
|
|
}
|
|
|
|
int32_t dx = x1 - x0, dy = abs(y1 - y0);;
|
|
|
|
int32_t err = dx >> 1, ystep = -1, xs = x0, dlen = 0;
|
|
|
|
if (y0 < y1) ystep = 1;
|
|
|
|
// Split into steep and not steep for FastH/V separation
|
|
if (steep) {
|
|
for (; x0 <= x1; x0++) {
|
|
dlen++;
|
|
err -= dy;
|
|
if (err < 0) {
|
|
err += dx;
|
|
if (dlen == 1) drawPixel(y0, xs, color);
|
|
else drawFastVLine(y0, xs, dlen, color);
|
|
dlen = 0; y0 += ystep; xs = x0 + 1;
|
|
}
|
|
}
|
|
if (dlen) drawFastVLine(y0, xs, dlen, color);
|
|
} else {
|
|
for (; x0 <= x1; x0++) {
|
|
dlen++;
|
|
err -= dy;
|
|
if (err < 0) {
|
|
err += dx;
|
|
if (dlen == 1) drawPixel(xs, y0, color);
|
|
else drawFastHLine(xs, y0, dlen, color);
|
|
dlen = 0; y0 += ystep; xs = x0 + 1;
|
|
}
|
|
}
|
|
if (dlen) drawFastHLine(xs, y0, dlen, color);
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: drawFastVLine
|
|
** Description: draw a vertical line
|
|
*************************************************************************************x*/
|
|
void TFT_eSprite::drawFastVLine(int32_t x, int32_t y, int32_t h, uint32_t color)
|
|
{
|
|
|
|
if ((x < 0) || (x >= _iwidth) || (y >= _iheight) || !_created) return;
|
|
|
|
if (y < 0) {
|
|
h += y;
|
|
y = 0;
|
|
}
|
|
|
|
if ((y + h) > _iheight) h = _iheight - y;
|
|
|
|
if (h < 1) return;
|
|
|
|
if (_bpp == 16) {
|
|
color = (color >> 8) | (color << 8);
|
|
int32_t yp = x + _iwidth * y;
|
|
while (h--) {
|
|
_img[yp] = (uint16_t) color;
|
|
yp += _iwidth;
|
|
}
|
|
} else if (_bpp == 8) {
|
|
color = (color & 0xE000) >> 8 | (color & 0x0700) >> 6 | (color & 0x0018) >> 3;
|
|
while (h--) _img8[x + _iwidth * y++] = (uint8_t) color;
|
|
} else if (_bpp == 4) {
|
|
if ((x & 0x01) == 0) {
|
|
uint8_t c = (uint8_t) (color & 0xF) << 4;
|
|
while (h--) {
|
|
_img4[(x + _iwidth * y) >> 1] = (uint8_t) (c | (_img4[(x + _iwidth * y) >> 1] & 0x0F));
|
|
y++;
|
|
}
|
|
} else {
|
|
uint8_t c = (uint8_t)color & 0xF;
|
|
while (h--) {
|
|
_img4[(x + _iwidth * y) >> 1] = (uint8_t) (c | (_img4[(x + _iwidth * y) >> 1] & 0xF0)); // x is odd; new color goes into the low bits.
|
|
y++;
|
|
}
|
|
}
|
|
} else {
|
|
while (h--) {
|
|
drawPixel(x, y, color);
|
|
y++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: drawFastHLine
|
|
** Description: draw a horizontal line
|
|
*************************************************************************************x*/
|
|
void TFT_eSprite::drawFastHLine(int32_t x, int32_t y, int32_t w, uint32_t color)
|
|
{
|
|
|
|
if ((y < 0) || (x >= _iwidth) || (y >= _iheight) || !_created) return;
|
|
|
|
if (x < 0) {
|
|
w += x;
|
|
x = 0;
|
|
}
|
|
|
|
if ((x + w) > _iwidth) w = _iwidth - x;
|
|
|
|
if (w < 1) return;
|
|
|
|
if (_bpp == 16) {
|
|
color = (color >> 8) | (color << 8);
|
|
while (w--) _img[_iwidth * y + x++] = (uint16_t) color;
|
|
} else if (_bpp == 8) {
|
|
color = (color & 0xE000) >> 8 | (color & 0x0700) >> 6 | (color & 0x0018) >> 3;
|
|
memset(_img8 + _iwidth * y + x, (uint8_t)color, w);
|
|
} else if (_bpp == 4) {
|
|
uint8_t c = (uint8_t)color & 0x0F;
|
|
uint8_t c2 = (c | ((c << 4) & 0xF0));
|
|
if ((x & 0x01) == 1) {
|
|
drawPixel(x, y, color);
|
|
x++; w--;
|
|
if (w < 1)
|
|
return;
|
|
}
|
|
|
|
if (((w + x) & 0x01) == 1) {
|
|
// handle the extra one at the other end
|
|
drawPixel(x + w - 1, y, color);
|
|
w--;
|
|
if (w < 1) return;
|
|
}
|
|
memset(_img4 + ((_iwidth * y + x) >> 1), c2, (w >> 1));
|
|
} else {
|
|
while (w--) {
|
|
drawPixel(x, y, color);
|
|
x++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: fillRect
|
|
** Description: draw a filled rectangle
|
|
*************************************************************************************x*/
|
|
void TFT_eSprite::fillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color)
|
|
{
|
|
if (!_created ) return;
|
|
|
|
if ((x >= _iwidth) || (y >= _iheight)) return;
|
|
|
|
if (x < 0) {
|
|
w += x;
|
|
x = 0;
|
|
}
|
|
if (y < 0) {
|
|
h += y;
|
|
y = 0;
|
|
}
|
|
|
|
if ((x + w) > _iwidth) w = _iwidth - x;
|
|
if ((y + h) > _iheight) h = _iheight - y;
|
|
|
|
if ((w < 1) || (h < 1)) return;
|
|
|
|
int32_t yp = _iwidth * y + x;
|
|
|
|
if (_bpp == 16) {
|
|
color = (color >> 8) | (color << 8);
|
|
uint32_t iw = w;
|
|
int32_t ys = yp;
|
|
if (h--) {
|
|
while (iw--) _img[yp++] = (uint16_t) color;
|
|
}
|
|
yp = ys;
|
|
while (h--) {
|
|
yp += _iwidth;
|
|
memcpy( _img + yp, _img + ys, w << 1);
|
|
}
|
|
} else if (_bpp == 8) {
|
|
color = (color & 0xE000) >> 8 | (color & 0x0700) >> 6 | (color & 0x0018) >> 3;
|
|
while (h--) {
|
|
memset(_img8 + yp, (uint8_t)color, w);
|
|
yp += _iwidth;
|
|
}
|
|
} else if (_bpp == 4) {
|
|
uint8_t c1 = (uint8_t)color & 0x0F;
|
|
uint8_t c2 = c1 | ((c1 << 4) & 0xF0);
|
|
if ((x & 0x01) == 0 && (w & 0x01) == 0) {
|
|
yp = (yp >> 1);
|
|
while (h--) {
|
|
memset(_img4 + yp, c2, (w >> 1));
|
|
yp += (_iwidth >> 1);
|
|
}
|
|
} else if ((x & 0x01) == 0) {
|
|
// same as above but you have a hangover on the right.
|
|
yp = (yp >> 1);
|
|
while (h--) {
|
|
if (w > 1)
|
|
memset(_img4 + yp, c2, (w - 1) >> 1);
|
|
// handle the rightmost pixel by calling drawPixel
|
|
drawPixel(x + w - 1, y + h, c1);
|
|
yp += (_iwidth >> 1);
|
|
}
|
|
} else if ((w & 0x01) == 1) {
|
|
yp = (yp + 1) >> 1;
|
|
while (h--) {
|
|
drawPixel(x, y + h, color & 0x0F);
|
|
if (w > 1)
|
|
memset(_img4 + yp, c2, (w - 1) >> 1);
|
|
// same as above but you have a hangover on the left instead
|
|
yp += (_iwidth >> 1);
|
|
}
|
|
} else {
|
|
yp = (yp + 1) >> 1;
|
|
while (h--) {
|
|
drawPixel(x, y + h, color & 0x0F);
|
|
if (w > 1) drawPixel(x + w - 1, y + h, color & 0x0F);
|
|
if (w > 2)
|
|
memset(_img4 + yp, c2, (w - 2) >> 1);
|
|
// maximal hacking, single pixels on left and right.
|
|
yp += (_iwidth >> 1);
|
|
}
|
|
}
|
|
} else {
|
|
while (h--) {
|
|
int32_t ww = w;
|
|
int32_t xx = x;
|
|
while (ww--) drawPixel(xx++, y, color);
|
|
y++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: write
|
|
** Description: draw characters piped through serial stream
|
|
*************************************************************************************x*/
|
|
size_t TFT_eSprite::write(uint8_t utf8)
|
|
{
|
|
uint16_t uniCode = decodeUTF8(utf8);
|
|
|
|
if (!uniCode) return 1;
|
|
|
|
if (utf8 == '\r') return 1;
|
|
|
|
#ifdef SMOOTH_FONT
|
|
if (this->fontLoaded) {
|
|
if (uniCode < 32 && utf8 != '\n') return 1;
|
|
|
|
//Serial.print("Decoded Unicode = 0x");Serial.println(unicode,HEX);
|
|
|
|
drawGlyph(uniCode);
|
|
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
if (!_created ) return 1;
|
|
|
|
if (uniCode == '\n') uniCode += 22; // Make it a valid space character to stop errors
|
|
else if (uniCode < 32) return 1;
|
|
|
|
uint16_t width = 0;
|
|
uint16_t height = 0;
|
|
|
|
//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv DEBUG vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
|
|
//Serial.print((uint8_t) uniCode); // Debug line sends all printed TFT text to serial port
|
|
//Serial.println(uniCode, HEX); // Debug line sends all printed TFT text to serial port
|
|
//delay(5); // Debug optional wait for serial port to flush through
|
|
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DEBUG ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
|
#ifdef LOAD_GFXFF
|
|
if (!gfxFont) {
|
|
#endif
|
|
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
|
|
|
#ifdef LOAD_FONT2
|
|
if (textfont == 2) {
|
|
if (utf8 > 127) return 1;
|
|
|
|
width = pgm_read_byte(widtbl_f16 + uniCode - 32);
|
|
height = chr_hgt_f16;
|
|
// Font 2 is rendered in whole byte widths so we must allow for this
|
|
width = (width + 6) / 8; // Width in whole bytes for font 2, should be + 7 but must allow for font width change
|
|
width = width * 8; // Width converted back to pixles
|
|
}
|
|
#ifdef LOAD_RLE
|
|
else
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef LOAD_RLE
|
|
{
|
|
if ((textfont > 2) && (textfont < 9)) {
|
|
if (utf8 > 127) return 1;
|
|
// Uses the fontinfo struct array to avoid lots of 'if' or 'switch' statements
|
|
width = pgm_read_byte( (uint8_t *)pgm_read_dword( &(fontdata[textfont].widthtbl ) ) + uniCode - 32 );
|
|
height = pgm_read_byte( &fontdata[textfont].height );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef LOAD_GLCD
|
|
if (textfont == 1) {
|
|
width = 6;
|
|
height = 8;
|
|
}
|
|
#else
|
|
if (textfont == 1) return 1;
|
|
#endif
|
|
|
|
height = height * textsize;
|
|
|
|
if (utf8 == '\n') {
|
|
this->cursor_y += height;
|
|
this->cursor_x = 0;
|
|
} else {
|
|
if (textwrapX && (this->cursor_x + width * textsize > _iwidth)) {
|
|
this->cursor_y += height;
|
|
this->cursor_x = 0;
|
|
}
|
|
if (textwrapY && (this->cursor_y >= _iheight)) this->cursor_y = 0;
|
|
this->cursor_x += drawChar(uniCode, this->cursor_x, this->cursor_y, textfont);
|
|
}
|
|
|
|
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
|
#ifdef LOAD_GFXFF
|
|
} // Custom GFX font
|
|
else {
|
|
if (utf8 == '\n') {
|
|
this->cursor_x = 0;
|
|
this->cursor_y += (int16_t)textsize * (uint8_t)pgm_read_byte(&gfxFont->yAdvance);
|
|
} else {
|
|
if (uniCode > pgm_read_word(&gfxFont->last )) return 1;
|
|
if (uniCode < pgm_read_word(&gfxFont->first)) return 1;
|
|
|
|
uint8_t c2 = uniCode - pgm_read_word(&gfxFont->first);
|
|
GFXglyph *glyph = &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c2]);
|
|
uint8_t w = pgm_read_byte(&glyph->width),
|
|
h = pgm_read_byte(&glyph->height);
|
|
if ((w > 0) && (h > 0)) { // Is there an associated bitmap?
|
|
int16_t xo = (int8_t)pgm_read_byte(&glyph->xOffset);
|
|
if (textwrapX && ((this->cursor_x + textsize * (xo + w)) > _iwidth)) {
|
|
// Drawing character would go off right edge; wrap to new line
|
|
this->cursor_x = 0;
|
|
this->cursor_y += (int16_t)textsize * (uint8_t)pgm_read_byte(&gfxFont->yAdvance);
|
|
}
|
|
if (textwrapY && (this->cursor_y >= _iheight)) this->cursor_y = 0;
|
|
drawChar(this->cursor_x, this->cursor_y, uniCode, textcolor, textbgcolor, textsize);
|
|
}
|
|
this->cursor_x += pgm_read_byte(&glyph->xAdvance) * (int16_t)textsize;
|
|
}
|
|
}
|
|
#endif // LOAD_GFXFF
|
|
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: drawChar
|
|
** Description: draw a single character in the Adafruit GLCD or freefont
|
|
*************************************************************************************x*/
|
|
void TFT_eSprite::drawChar(int32_t x, int32_t y, uint16_t c, uint32_t color, uint32_t bg, uint8_t size)
|
|
{
|
|
if (!_created ) return;
|
|
|
|
if ((x >= _iwidth) || // Clip right
|
|
(y >= _iheight) || // Clip bottom
|
|
((x + 6 * size - 1) < 0) || // Clip left
|
|
((y + 8 * size - 1) < 0)) // Clip top
|
|
return;
|
|
|
|
if (c < 32) return;
|
|
#ifdef LOAD_GLCD
|
|
//>>>>>>>>>>>>>>>>>>
|
|
#ifdef LOAD_GFXFF
|
|
if (!gfxFont) { // 'Classic' built-in font
|
|
#endif
|
|
//>>>>>>>>>>>>>>>>>>
|
|
|
|
bool fillbg = (bg != color);
|
|
|
|
if ((size == 1) && fillbg) {
|
|
uint8_t column[6];
|
|
uint8_t mask = 0x1;
|
|
|
|
for (int8_t i = 0; i < 5; i++ ) column[i] = pgm_read_byte(font + (c * 5) + i);
|
|
column[5] = 0;
|
|
|
|
int8_t j, k;
|
|
for (j = 0; j < 8; j++) {
|
|
for (k = 0; k < 5; k++ ) {
|
|
if (column[k] & mask) {
|
|
drawPixel(x + k, y + j, color);
|
|
} else {
|
|
drawPixel(x + k, y + j, bg);
|
|
}
|
|
}
|
|
|
|
mask <<= 1;
|
|
|
|
drawPixel(x + k, y + j, bg);
|
|
}
|
|
} else {
|
|
for (int8_t i = 0; i < 6; i++ ) {
|
|
uint8_t line;
|
|
if (i == 5)
|
|
line = 0x0;
|
|
else
|
|
line = pgm_read_byte(font + (c * 5) + i);
|
|
|
|
if (size == 1) { // default size
|
|
for (int8_t j = 0; j < 8; j++) {
|
|
if (line & 0x1) drawPixel(x + i, y + j, color);
|
|
line >>= 1;
|
|
}
|
|
} else { // big size
|
|
for (int8_t j = 0; j < 8; j++) {
|
|
if (line & 0x1) fillRect(x + (i * size), y + (j * size), size, size, color);
|
|
else if (fillbg) fillRect(x + i * size, y + j * size, size, size, bg);
|
|
line >>= 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
|
#ifdef LOAD_GFXFF
|
|
} else { // Custom font
|
|
#endif
|
|
//>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
|
#endif // LOAD_GLCD
|
|
|
|
#ifdef LOAD_GFXFF
|
|
// Filter out bad characters not present in font
|
|
if ((c >= pgm_read_word(&gfxFont->first)) && (c <= pgm_read_word(&gfxFont->last ))) {
|
|
//>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
|
|
|
c -= pgm_read_word(&gfxFont->first);
|
|
GFXglyph *glyph = &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c]);
|
|
uint8_t *bitmap = (uint8_t *)pgm_read_dword(&gfxFont->bitmap);
|
|
|
|
uint32_t bo = pgm_read_word(&glyph->bitmapOffset);
|
|
uint8_t w = pgm_read_byte(&glyph->width),
|
|
h = pgm_read_byte(&glyph->height);
|
|
//xa = pgm_read_byte(&glyph->xAdvance);
|
|
int8_t xo = pgm_read_byte(&glyph->xOffset),
|
|
yo = pgm_read_byte(&glyph->yOffset);
|
|
uint8_t xx, yy, bits = 0, bit = 0;
|
|
int16_t xo16 = 0, yo16 = 0;
|
|
|
|
if (size > 1) {
|
|
xo16 = xo;
|
|
yo16 = yo;
|
|
}
|
|
|
|
uint16_t hpc = 0; // Horizontal foreground pixel count
|
|
for (yy = 0; yy < h; yy++) {
|
|
for (xx = 0; xx < w; xx++) {
|
|
if (bit == 0) {
|
|
bits = pgm_read_byte(&bitmap[bo++]);
|
|
bit = 0x80;
|
|
}
|
|
if (bits & bit) hpc++;
|
|
else {
|
|
if (hpc) {
|
|
if (size == 1) drawFastHLine(x + xo + xx - hpc, y + yo + yy, hpc, color);
|
|
else fillRect(x + (xo16 + xx - hpc)*size, y + (yo16 + yy)*size, size * hpc, size, color);
|
|
hpc = 0;
|
|
}
|
|
}
|
|
bit >>= 1;
|
|
}
|
|
// Draw pixels for this line as we are about to increment yy
|
|
if (hpc) {
|
|
if (size == 1) drawFastHLine(x + xo + xx - hpc, y + yo + yy, hpc, color);
|
|
else fillRect(x + (xo16 + xx - hpc)*size, y + (yo16 + yy)*size, size * hpc, size, color);
|
|
hpc = 0;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
#ifdef LOAD_GLCD
|
|
#ifdef LOAD_GFXFF
|
|
} // End classic vs custom font
|
|
#endif
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: drawChar
|
|
** Description: draw a unicode onto the screen
|
|
*************************************************************************************x*/
|
|
// Any UTF-8 decoding must be done before calling drawChar()
|
|
int16_t TFT_eSprite::drawChar(uint16_t uniCode, int32_t x, int32_t y)
|
|
{
|
|
return drawChar(uniCode, x, y, textfont);
|
|
}
|
|
|
|
// Any UTF-8 decoding must be done before calling drawChar()
|
|
int16_t TFT_eSprite::drawChar(uint16_t uniCode, int32_t x, int32_t y, uint8_t font)
|
|
{
|
|
if (!_created ) return 0;
|
|
|
|
if (!uniCode) return 0;
|
|
|
|
if (font == 1) {
|
|
#ifdef LOAD_GLCD
|
|
#ifndef LOAD_GFXFF
|
|
drawChar(x, y, uniCode, textcolor, textbgcolor, textsize);
|
|
return 6 * textsize;
|
|
#endif
|
|
#else
|
|
#ifndef LOAD_GFXFF
|
|
return 0;
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef LOAD_GFXFF
|
|
drawChar(x, y, uniCode, textcolor, textbgcolor, textsize);
|
|
if (!gfxFont) { // 'Classic' built-in font
|
|
#ifdef LOAD_GLCD
|
|
return 6 * textsize;
|
|
#else
|
|
return 0;
|
|
#endif
|
|
} else {
|
|
if ((uniCode >= pgm_read_word(&gfxFont->first)) && (uniCode <= pgm_read_word(&gfxFont->last) )) {
|
|
uint16_t c2 = uniCode - pgm_read_word(&gfxFont->first);
|
|
GFXglyph *glyph = &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c2]);
|
|
return pgm_read_byte(&glyph->xAdvance) * textsize;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if ((font > 1) && (font < 9) && ((uniCode < 32) || (uniCode > 127))) return 0;
|
|
|
|
int32_t width = 0;
|
|
int32_t height = 0;
|
|
uint32_t flash_address = 0;
|
|
uniCode -= 32;
|
|
|
|
#ifdef LOAD_FONT2
|
|
if (font == 2) {
|
|
// This is faster than using the fontdata structure
|
|
flash_address = pgm_read_dword(&chrtbl_f16[uniCode]);
|
|
width = pgm_read_byte(widtbl_f16 + uniCode);
|
|
height = chr_hgt_f16;
|
|
}
|
|
#ifdef LOAD_RLE
|
|
else
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef LOAD_RLE
|
|
{
|
|
if ((font > 2) && (font < 9)) {
|
|
// This is slower than above but is more convenient for the RLE fonts
|
|
flash_address = pgm_read_dword( (const void *) (pgm_read_dword( &(fontdata[font].chartbl ) ) + uniCode * sizeof(void *)) );
|
|
width = pgm_read_byte( (uint8_t *)pgm_read_dword( &(fontdata[font].widthtbl ) ) + uniCode );
|
|
height = pgm_read_byte( &fontdata[font].height );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
int32_t w = width;
|
|
int32_t pX = 0;
|
|
int32_t pY = y;
|
|
uint8_t line = 0;
|
|
|
|
#ifdef LOAD_FONT2 // chop out code if we do not need it
|
|
if (font == 2) {
|
|
w = w + 6; // Should be + 7 but we need to compensate for width increment
|
|
w = w / 8;
|
|
if (x + width * textsize >= _iwidth) return width * textsize ;
|
|
|
|
for (int32_t i = 0; i < height; i++) {
|
|
if (textcolor != textbgcolor) fillRect(x, pY, width * textsize, textsize, textbgcolor);
|
|
|
|
for (int32_t k = 0; k < w; k++) {
|
|
line = pgm_read_byte((uint8_t *)flash_address + w * i + k);
|
|
if (line) {
|
|
if (textsize == 1) {
|
|
pX = x + k * 8;
|
|
if (line & 0x80) drawPixel(pX, pY, textcolor);
|
|
if (line & 0x40) drawPixel(pX + 1, pY, textcolor);
|
|
if (line & 0x20) drawPixel(pX + 2, pY, textcolor);
|
|
if (line & 0x10) drawPixel(pX + 3, pY, textcolor);
|
|
if (line & 0x08) drawPixel(pX + 4, pY, textcolor);
|
|
if (line & 0x04) drawPixel(pX + 5, pY, textcolor);
|
|
if (line & 0x02) drawPixel(pX + 6, pY, textcolor);
|
|
if (line & 0x01) drawPixel(pX + 7, pY, textcolor);
|
|
} else {
|
|
pX = x + k * 8 * textsize;
|
|
if (line & 0x80) fillRect(pX, pY, textsize, textsize, textcolor);
|
|
if (line & 0x40) fillRect(pX + textsize, pY, textsize, textsize, textcolor);
|
|
if (line & 0x20) fillRect(pX + 2 * textsize, pY, textsize, textsize, textcolor);
|
|
if (line & 0x10) fillRect(pX + 3 * textsize, pY, textsize, textsize, textcolor);
|
|
if (line & 0x08) fillRect(pX + 4 * textsize, pY, textsize, textsize, textcolor);
|
|
if (line & 0x04) fillRect(pX + 5 * textsize, pY, textsize, textsize, textcolor);
|
|
if (line & 0x02) fillRect(pX + 6 * textsize, pY, textsize, textsize, textcolor);
|
|
if (line & 0x01) fillRect(pX + 7 * textsize, pY, textsize, textsize, textcolor);
|
|
}
|
|
}
|
|
}
|
|
pY += textsize;
|
|
}
|
|
}
|
|
|
|
#ifdef LOAD_RLE
|
|
else
|
|
#endif
|
|
#endif //FONT2
|
|
|
|
#ifdef LOAD_RLE //674 bytes of code
|
|
// Font is not 2 and hence is RLE encoded
|
|
{
|
|
w *= height; // Now w is total number of pixels in the character
|
|
|
|
if (textcolor != textbgcolor) fillRect(x, pY, width * textsize, textsize * height, textbgcolor);
|
|
int16_t color = textcolor;
|
|
if (_bpp == 16) color = (textcolor >> 8) | (textcolor << 8);
|
|
else if (_bpp == 8) color = ((textcolor & 0xE000) >> 8 | (textcolor & 0x0700) >> 6 | (textcolor & 0x0018) >> 3);
|
|
int32_t px = 0, py = pY; // To hold character block start and end column and row values
|
|
int32_t pc = 0; // Pixel count
|
|
uint8_t np = textsize * textsize; // Number of pixels in a drawn pixel
|
|
uint8_t tnp = 0; // Temporary copy of np for while loop
|
|
uint8_t ts = textsize - 1; // Temporary copy of textsize
|
|
// 16 bit pixel count so maximum font size is equivalent to 180x180 pixels in area
|
|
// w is total number of pixels to plot to fill character block
|
|
while (pc < w) {
|
|
line = pgm_read_byte((uint8_t *)flash_address);
|
|
flash_address++; // 20 bytes smaller by incrementing here
|
|
if (line & 0x80) {
|
|
line &= 0x7F;
|
|
line++;
|
|
if (ts) {
|
|
px = x + textsize * (pc % width); // Keep these px and py calculations outside the loop as they are slow
|
|
py = y + textsize * (pc / width);
|
|
} else {
|
|
px = x + pc % width; // Keep these px and py calculations outside the loop as they are slow
|
|
py = y + pc / width;
|
|
}
|
|
while (line--) {
|
|
pc++;
|
|
setWindow(px, py, px + ts, py + ts);
|
|
if (ts) {
|
|
tnp = np;
|
|
while (tnp--) writeColor(color);
|
|
} else writeColor(color);
|
|
|
|
px += textsize;
|
|
|
|
if (px >= (x + width * textsize)) {
|
|
px = x;
|
|
py += textsize;
|
|
}
|
|
}
|
|
} else {
|
|
line++;
|
|
pc += line;
|
|
}
|
|
}
|
|
}
|
|
// End of RLE font rendering
|
|
#endif
|
|
return width * textsize; // x +
|
|
}
|
|
|
|
#ifdef SMOOTH_FONT
|
|
/***************************************************************************************
|
|
** Function name: drawGlyph
|
|
** Description: Write a character to the sprite cursor position
|
|
*************************************************************************************x*/
|
|
void TFT_eSprite::drawGlyph(uint16_t code)
|
|
{
|
|
if (code < 0x21) {
|
|
if (code == 0x20) {
|
|
if (_created) this->cursor_x += this->gFont.spaceWidth;
|
|
else this->cursor_x += this->gFont.spaceWidth;
|
|
return;
|
|
}
|
|
|
|
if (code == '\n') {
|
|
if (_created) {
|
|
this->cursor_x = 0;
|
|
this->cursor_y += this->gFont.yAdvance;
|
|
if (this->cursor_y >= _height) this->cursor_y = 0;
|
|
return;
|
|
} else {
|
|
cursor_x = 0;
|
|
cursor_y += gFont.yAdvance;
|
|
if (cursor_y >= _height) cursor_y = 0;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint16_t gNum = 0;
|
|
bool found = this->getUnicodeIndex(code, &gNum);
|
|
|
|
uint16_t fg = this->textcolor;
|
|
uint16_t bg = this->textbgcolor;
|
|
|
|
if (found) {
|
|
|
|
bool newSprite = !_created;
|
|
|
|
if (newSprite) {
|
|
createSprite(this->gWidth[gNum], this->gFont.yAdvance);
|
|
if (bg) fillSprite(bg);
|
|
this->cursor_x = -this->gdX[gNum];
|
|
this->cursor_y = 0;
|
|
} else {
|
|
if ( this->textwrapX && ((this->cursor_x + this->gWidth[gNum] + this->gdX[gNum]) > _iwidth)) {
|
|
this->cursor_y += this->gFont.yAdvance;
|
|
this->cursor_x = 0;
|
|
}
|
|
|
|
if ( this->textwrapY && ((this->cursor_y + this->gFont.yAdvance) > _iheight)) this->cursor_y = 0;
|
|
|
|
if ( this->cursor_x == 0) this->cursor_x -= this->gdX[gNum];
|
|
|
|
}
|
|
|
|
uint8_t *pbuffer = nullptr;
|
|
const uint8_t *gPtr = (const uint8_t *) this->gFont.gArray;
|
|
|
|
#ifdef FONT_FS_AVAILABLE
|
|
if (this->fs_font) {
|
|
this->fontFile.seek(this->gBitmap[gNum], fs::SeekSet); // This is slow for a significant position shift!
|
|
pbuffer = (uint8_t *)malloc(this->gWidth[gNum]);
|
|
}
|
|
#endif
|
|
|
|
int16_t xs = 0;
|
|
uint16_t dl = 0;
|
|
uint8_t pixel = 0;
|
|
|
|
for (int32_t y = 0; y < this->gHeight[gNum]; y++) {
|
|
#ifdef FONT_FS_AVAILABLE
|
|
if (this->fs_font) {
|
|
this->fontFile.read(pbuffer, this->gWidth[gNum]);
|
|
}
|
|
#endif
|
|
for (int32_t x = 0; x < this->gWidth[gNum]; x++) {
|
|
#ifdef FONT_FS_AVAILABLE
|
|
if (this->fs_font) {
|
|
pixel = pbuffer[x];
|
|
} else
|
|
#endif
|
|
pixel = pgm_read_byte(gPtr + this->gBitmap[gNum] + x + this->gWidth[gNum] * y);
|
|
|
|
if (pixel) {
|
|
if (pixel != 0xFF) {
|
|
if (dl) {
|
|
drawFastHLine( xs, y + this->cursor_y + this->gFont.maxAscent - this->gdY[gNum], dl, fg);
|
|
dl = 0;
|
|
}
|
|
if (_bpp != 1) drawPixel(x + this->cursor_x + this->gdX[gNum], y + this->cursor_y + this->gFont.maxAscent - this->gdY[gNum], alphaBlend(pixel, fg, bg));
|
|
else if (pixel > 127) drawPixel(x + this->cursor_x + this->gdX[gNum], y + this->cursor_y + this->gFont.maxAscent - this->gdY[gNum], fg);
|
|
} else {
|
|
if (dl == 0) xs = x + this->cursor_x + this->gdX[gNum];
|
|
dl++;
|
|
}
|
|
} else {
|
|
if (dl) {
|
|
drawFastHLine( xs, y + this->cursor_y + this->gFont.maxAscent - this->gdY[gNum], dl, fg);
|
|
dl = 0;
|
|
}
|
|
}
|
|
}
|
|
if (dl) {
|
|
drawFastHLine( xs, y + this->cursor_y + this->gFont.maxAscent - this->gdY[gNum], dl, fg);
|
|
dl = 0;
|
|
}
|
|
}
|
|
|
|
if (pbuffer) free(pbuffer);
|
|
|
|
if (newSprite) {
|
|
pushSprite(this->cursor_x + this->gdX[gNum], this->cursor_y, bg);
|
|
deleteSprite();
|
|
this->cursor_x += this->gxAdvance[gNum];
|
|
} else this->cursor_x += this->gxAdvance[gNum];
|
|
} else {
|
|
// Not a Unicode in font so draw a rectangle and move on cursor
|
|
drawRect(this->cursor_x, this->cursor_y + this->gFont.maxAscent - this->gFont.ascent, this->gFont.spaceWidth, this->gFont.ascent, fg);
|
|
this->cursor_x += this->gFont.spaceWidth + 1;
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: printToSprite
|
|
** Description: Write a string to the sprite cursor position
|
|
*************************************************************************************x*/
|
|
void TFT_eSprite::printToSprite(String string)
|
|
{
|
|
if (!this->fontLoaded) return;
|
|
uint16_t len = string.length();
|
|
char cbuffer[len + 1]; // Add 1 for the null
|
|
string.toCharArray(cbuffer, len + 1); // Add 1 for the null, otherwise characters get dropped
|
|
printToSprite(cbuffer, len);
|
|
//printToSprite((char*)string.c_str(), string.length());
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: printToSprite
|
|
** Description: Write a string to the sprite cursor position
|
|
*************************************************************************************x*/
|
|
void TFT_eSprite::printToSprite(char *cbuffer, uint16_t len) //String string)
|
|
{
|
|
if (!this->fontLoaded) return;
|
|
|
|
uint16_t n = 0;
|
|
bool newSprite = !_created;
|
|
|
|
if (newSprite) {
|
|
int16_t sWidth = 1;
|
|
uint16_t index = 0;
|
|
|
|
while (n < len) {
|
|
uint16_t unicode = decodeUTF8((uint8_t *)cbuffer, &n, len - n);
|
|
if (this->getUnicodeIndex(unicode, &index)) {
|
|
if (n == 0) sWidth -= this->gdX[index];
|
|
if (n == len - 1) sWidth += ( this->gWidth[index] + this->gdX[index]);
|
|
else sWidth += this->gxAdvance[index];
|
|
} else sWidth += this->gFont.spaceWidth + 1;
|
|
}
|
|
|
|
createSprite(sWidth, this->gFont.yAdvance);
|
|
|
|
if (this->textbgcolor != TFT_BLACK) fillSprite(this->textbgcolor);
|
|
}
|
|
|
|
n = 0;
|
|
|
|
while (n < len) {
|
|
uint16_t unicode = decodeUTF8((uint8_t *)cbuffer, &n, len - n);
|
|
//Serial.print("Decoded Unicode = 0x");Serial.println(unicode,HEX);
|
|
//Serial.print("n = ");Serial.println(n);
|
|
drawGlyph(unicode);
|
|
}
|
|
|
|
if (newSprite) {
|
|
// The sprite had to be created so place at TFT cursor
|
|
pushSprite(_tft->cursor_x, _tft->cursor_y);
|
|
deleteSprite();
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: printToSprite
|
|
** Description: Print character in a Sprite, create sprite if needed
|
|
*************************************************************************************x*/
|
|
int16_t TFT_eSprite::printToSprite(int16_t x, int16_t y, uint16_t index)
|
|
{
|
|
bool newSprite = !_created;
|
|
int16_t sWidth = this->gWidth[index];
|
|
|
|
if (newSprite) {
|
|
createSprite(sWidth, this->gFont.yAdvance);
|
|
|
|
if (this->textbgcolor != TFT_BLACK) fillSprite(this->textbgcolor);
|
|
|
|
drawGlyph(this->gUnicode[index]);
|
|
|
|
pushSprite(x + this->gdX[index], y, this->textbgcolor);
|
|
deleteSprite();
|
|
}
|
|
|
|
else drawGlyph(this->gUnicode[index]);
|
|
|
|
return this->gxAdvance[index];
|
|
}
|
|
#endif
|
|
|
|
|
|
#ifdef SMOOTH_FONT
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// New anti-aliased (smoothed) font functions added below
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/***************************************************************************************
|
|
** Function name: loadFont
|
|
** Description: loads parameters from a font vlw array in memory
|
|
*************************************************************************************x*/
|
|
void TFT_eSPI::loadFont(const uint8_t array[])
|
|
{
|
|
if (array == nullptr) return;
|
|
fontPtr = (uint8_t *) array;
|
|
loadFont("", false);
|
|
}
|
|
|
|
#ifdef FONT_FS_AVAILABLE
|
|
/***************************************************************************************
|
|
** Function name: loadFont
|
|
** Description: loads parameters from a font vlw file
|
|
*************************************************************************************x*/
|
|
void TFT_eSPI::loadFont(String fontName, fs::FS &ffs)
|
|
{
|
|
fontFS = ffs;
|
|
loadFont(fontName, false);
|
|
}
|
|
#endif
|
|
|
|
/***************************************************************************************
|
|
** Function name: loadFont
|
|
** Description: loads parameters from a font vlw file
|
|
*************************************************************************************x*/
|
|
void TFT_eSPI::loadFont(String fontName, bool flash)
|
|
{
|
|
/*
|
|
The vlw font format does not appear to be documented anywhere, so some reverse
|
|
engineering has been applied!
|
|
|
|
Header of vlw file comprises 6 uint32_t parameters (24 bytes total):
|
|
1. The gCount (number of character glyphs)
|
|
2. A version number (0xB = 11 for the one I am using)
|
|
3. The font size (in points, not pixels)
|
|
4. Deprecated mboxY parameter (typically set to 0)
|
|
5. Ascent in pixels from baseline to top of "d"
|
|
6. Descent in pixels from baseline to bottom of "p"
|
|
|
|
Next are gCount sets of values for each glyph, each set comprises 7 int32t parameters (28 bytes):
|
|
1. Glyph Unicode stored as a 32 bit value
|
|
2. Height of bitmap bounding box
|
|
3. Width of bitmap bounding box
|
|
4. gxAdvance for cursor (setWidth in Processing)
|
|
5. dY = distance from cursor baseline to top of glyph bitmap (signed value +ve = up)
|
|
6. dX = distance from cursor to left side of glyph bitmap (signed value -ve = left)
|
|
7. padding value, typically 0
|
|
|
|
The bitmaps start next at 24 + (28 * gCount) bytes from the start of the file.
|
|
Each pixel is 1 byte, an 8 bit Alpha value which represents the transparency from
|
|
0xFF foreground colour, 0x00 background. The sketch uses a linear interpolation
|
|
between the foreground and background RGB component colours. e.g.
|
|
pixelRed = ((fgRed * alpha) + (bgRed * (255 - alpha))/255
|
|
To gain a performance advantage fixed point arithmetic is used with rounding and
|
|
division by 256 (shift right 8 bits is faster).
|
|
|
|
After the bitmaps is:
|
|
1 byte for font name string length (excludes null)
|
|
a zero terminated character string giving the font name
|
|
1 byte for Postscript name string length
|
|
a zero/one terminated character string giving the font name
|
|
last byte is 0 for non-anti-aliased and 1 for anti-aliased (smoothed)
|
|
|
|
|
|
Glyph bitmap example is:
|
|
// Cursor coordinate positions for this and next character are marked by 'C'
|
|
// C<------- gxAdvance ------->C gxAdvance is how far to move cursor for next glyph cursor position
|
|
// | |
|
|
// | | ascent is top of "d", descent is bottom of "p"
|
|
// +-- gdX --+ ascent
|
|
// | +-- gWidth--+ | gdX is offset to left edge of glyph bitmap
|
|
// | + x@.........@x + | gdX may be negative e.g. italic "y" tail extending to left of
|
|
// | | @@.........@@ | | cursor position, plot top left corner of bitmap at (cursorX + gdX)
|
|
// | | @@.........@@ gdY | gWidth and gHeight are glyph bitmap dimensions
|
|
// | | .@@@.....@@@@ | |
|
|
// | gHeight ....@@@@@..@@ + + <-- baseline
|
|
// | | ...........@@ |
|
|
// | | ...........@@ | gdY is the offset to the top edge of the bitmap
|
|
// | | .@@.......@@. descent plot top edge of bitmap at (cursorY + yAdvance - gdY)
|
|
// | + x..@@@@@@@..x | x marks the corner pixels of the bitmap
|
|
// | |
|
|
// +---------------------------+ yAdvance is y delta for the next line, font size or (ascent + descent)
|
|
// some fonts can overlay in y direction so may need a user adjust value
|
|
|
|
*/
|
|
|
|
if (fontLoaded) unloadFont();
|
|
|
|
#ifdef FONT_FS_AVAILABLE
|
|
if (fontName == "") fs_font = false;
|
|
else {
|
|
fontPtr = nullptr;
|
|
fs_font = true;
|
|
}
|
|
|
|
if (fs_font) {
|
|
spiffs = flash; // true if font is in SPIFFS
|
|
|
|
if (spiffs) fontFS = SPIFFS;
|
|
|
|
// Avoid a crash on the ESP32 if the file does not exist
|
|
if (fontFS.exists("/" + fontName + ".vlw") == false) {
|
|
Serial.println("Font file " + fontName + " not found!");
|
|
return;
|
|
}
|
|
|
|
fontFile = fontFS.open( "/" + fontName + ".vlw", "r");
|
|
|
|
if (!fontFile) return;
|
|
|
|
fontFile.seek(0, fs::SeekSet);
|
|
}
|
|
#else
|
|
// Avoid unused varaible warning
|
|
fontName = fontName;
|
|
flash = flash;
|
|
#endif
|
|
|
|
gFont.gArray = (const uint8_t *)fontPtr;
|
|
|
|
gFont.gCount = (uint16_t)readInt32(); // glyph count in file
|
|
readInt32(); // vlw encoder version - discard
|
|
gFont.yAdvance = (uint16_t)readInt32(); // Font size in points, not pixels
|
|
readInt32(); // discard
|
|
gFont.ascent = (uint16_t)readInt32(); // top of "d"
|
|
gFont.descent = (uint16_t)readInt32(); // bottom of "p"
|
|
|
|
// These next gFont values might be updated when the Metrics are fetched
|
|
gFont.maxAscent = gFont.ascent; // Determined from metrics
|
|
gFont.maxDescent = gFont.descent; // Determined from metrics
|
|
gFont.yAdvance = gFont.ascent + gFont.descent;
|
|
gFont.spaceWidth = gFont.yAdvance / 4; // Guess at space width
|
|
|
|
fontLoaded = true;
|
|
|
|
// Fetch the metrics for each glyph
|
|
loadMetrics();
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: loadMetrics
|
|
** Description: Get the metrics for each glyph and store in RAM
|
|
*************************************************************************************x*/
|
|
//#define SHOW_ASCENT_DESCENT
|
|
void TFT_eSPI::loadMetrics(void)
|
|
{
|
|
uint32_t headerPtr = 24;
|
|
uint32_t bitmapPtr = headerPtr + gFont.gCount * 28;
|
|
|
|
#if defined (ESP32) && defined (CONFIG_SPIRAM_SUPPORT)
|
|
if ( psramFound() ) {
|
|
gUnicode = (uint16_t *)ps_malloc( gFont.gCount * 2); // Unicode 16 bit Basic Multilingual Plane (0-FFFF)
|
|
gHeight = (uint8_t *)ps_malloc( gFont.gCount ); // Height of glyph
|
|
gWidth = (uint8_t *)ps_malloc( gFont.gCount ); // Width of glyph
|
|
gxAdvance = (uint8_t *)ps_malloc( gFont.gCount ); // xAdvance - to move x cursor
|
|
gdY = (int16_t *)ps_malloc( gFont.gCount * 2); // offset from bitmap top edge from lowest point in any character
|
|
gdX = (int8_t *)ps_malloc( gFont.gCount ); // offset for bitmap left edge relative to cursor X
|
|
gBitmap = (uint32_t *)ps_malloc( gFont.gCount * 4); // seek pointer to glyph bitmap in the file
|
|
} else
|
|
#endif
|
|
{
|
|
gUnicode = (uint16_t *)malloc( gFont.gCount * 2); // Unicode 16 bit Basic Multilingual Plane (0-FFFF)
|
|
gHeight = (uint8_t *)malloc( gFont.gCount ); // Height of glyph
|
|
gWidth = (uint8_t *)malloc( gFont.gCount ); // Width of glyph
|
|
gxAdvance = (uint8_t *)malloc( gFont.gCount ); // xAdvance - to move x cursor
|
|
gdY = (int16_t *)malloc( gFont.gCount * 2); // offset from bitmap top edge from lowest point in any character
|
|
gdX = (int8_t *)malloc( gFont.gCount ); // offset for bitmap left edge relative to cursor X
|
|
gBitmap = (uint32_t *)malloc( gFont.gCount * 4); // seek pointer to glyph bitmap in the file
|
|
}
|
|
|
|
#ifdef SHOW_ASCENT_DESCENT
|
|
Serial.print("ascent = "); Serial.println(gFont.ascent);
|
|
Serial.print("descent = "); Serial.println(gFont.descent);
|
|
#endif
|
|
|
|
#ifdef FONT_FS_AVAILABLE
|
|
if (fs_font) fontFile.seek(headerPtr, fs::SeekSet);
|
|
#endif
|
|
|
|
uint16_t gNum = 0;
|
|
|
|
while (gNum < gFont.gCount) {
|
|
gUnicode[gNum] = (uint16_t)readInt32(); // Unicode code point value
|
|
gHeight[gNum] = (uint8_t)readInt32(); // Height of glyph
|
|
gWidth[gNum] = (uint8_t)readInt32(); // Width of glyph
|
|
gxAdvance[gNum] = (uint8_t)readInt32(); // xAdvance - to move x cursor
|
|
gdY[gNum] = (int16_t)readInt32(); // y delta from baseline
|
|
gdX[gNum] = (int8_t)readInt32(); // x delta from cursor
|
|
readInt32(); // ignored
|
|
|
|
//Serial.print("Unicode = 0x"); Serial.print(gUnicode[gNum], HEX); Serial.print(", gHeight = "); Serial.println(gHeight[gNum]);
|
|
//Serial.print("Unicode = 0x"); Serial.print(gUnicode[gNum], HEX); Serial.print(", gWidth = "); Serial.println(gWidth[gNum]);
|
|
//Serial.print("Unicode = 0x"); Serial.print(gUnicode[gNum], HEX); Serial.print(", gxAdvance = "); Serial.println(gxAdvance[gNum]);
|
|
//Serial.print("Unicode = 0x"); Serial.print(gUnicode[gNum], HEX); Serial.print(", gdY = "); Serial.println(gdY[gNum]);
|
|
|
|
// Different glyph sets have different ascent values not always based on "d", so we could get
|
|
// the maximum glyph ascent by checking all characters. BUT this method can generate bad values
|
|
// for non-existant glyphs, so we will reply on processing for the value and disable this code for now...
|
|
/*
|
|
if (gdY[gNum] > gFont.maxAscent)
|
|
{
|
|
// Try to avoid UTF coding values and characters that tend to give duff values
|
|
if (((gUnicode[gNum] > 0x20) && (gUnicode[gNum] < 0x7F)) || (gUnicode[gNum] > 0xA0))
|
|
{
|
|
gFont.maxAscent = gdY[gNum];
|
|
#ifdef SHOW_ASCENT_DESCENT
|
|
Serial.print("Unicode = 0x"); Serial.print(gUnicode[gNum], HEX); Serial.print(", maxAscent = "); Serial.println(gFont.maxAscent);
|
|
#endif
|
|
}
|
|
}
|
|
*/
|
|
|
|
// Different glyph sets have different descent values not always based on "p", so get maximum glyph descent
|
|
if (((int16_t)gHeight[gNum] - (int16_t)gdY[gNum]) > gFont.maxDescent) {
|
|
// Avoid UTF coding values and characters that tend to give duff values
|
|
if (((gUnicode[gNum] > 0x20) && (gUnicode[gNum] < 0xA0) && (gUnicode[gNum] != 0x7F)) || (gUnicode[gNum] > 0xFF)) {
|
|
gFont.maxDescent = gHeight[gNum] - gdY[gNum];
|
|
#ifdef SHOW_ASCENT_DESCENT
|
|
Serial.print("Unicode = 0x"); Serial.print(gUnicode[gNum], HEX); Serial.print(", maxDescent = "); Serial.println(gHeight[gNum] - gdY[gNum]);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
gBitmap[gNum] = bitmapPtr;
|
|
|
|
bitmapPtr += gWidth[gNum] * gHeight[gNum];
|
|
|
|
gNum++;
|
|
yield();
|
|
}
|
|
|
|
gFont.yAdvance = gFont.maxAscent + gFont.maxDescent;
|
|
|
|
gFont.spaceWidth = (gFont.ascent + gFont.descent) * 2 / 7; // Guess at space width
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: deleteMetrics
|
|
** Description: Delete the old glyph metrics and free up the memory
|
|
*************************************************************************************x*/
|
|
void TFT_eSPI::unloadFont( void )
|
|
{
|
|
if (gUnicode) {
|
|
free(gUnicode);
|
|
gUnicode = NULL;
|
|
}
|
|
|
|
if (gHeight) {
|
|
free(gHeight);
|
|
gHeight = NULL;
|
|
}
|
|
|
|
if (gWidth) {
|
|
free(gWidth);
|
|
gWidth = NULL;
|
|
}
|
|
|
|
if (gxAdvance) {
|
|
free(gxAdvance);
|
|
gxAdvance = NULL;
|
|
}
|
|
|
|
if (gdY) {
|
|
free(gdY);
|
|
gdY = NULL;
|
|
}
|
|
|
|
if (gdX) {
|
|
free(gdX);
|
|
gdX = NULL;
|
|
}
|
|
|
|
if (gBitmap) {
|
|
free(gBitmap);
|
|
gBitmap = NULL;
|
|
}
|
|
|
|
gFont.gArray = nullptr;
|
|
|
|
#ifdef FONT_FS_AVAILABLE
|
|
if (fs_font && fontFile) fontFile.close();
|
|
#endif
|
|
|
|
fontLoaded = false;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: readInt32
|
|
** Description: Get a 32 bit integer from the font file
|
|
*************************************************************************************x*/
|
|
uint32_t TFT_eSPI::readInt32(void)
|
|
{
|
|
uint32_t val = 0;
|
|
|
|
#ifdef FONT_FS_AVAILABLE
|
|
if (fs_font) {
|
|
val |= fontFile.read() << 24;
|
|
val |= fontFile.read() << 16;
|
|
val |= fontFile.read() << 8;
|
|
val |= fontFile.read();
|
|
} else
|
|
#endif
|
|
{
|
|
val |= pgm_read_byte(fontPtr++) << 24;
|
|
val |= pgm_read_byte(fontPtr++) << 16;
|
|
val |= pgm_read_byte(fontPtr++) << 8;
|
|
val |= pgm_read_byte(fontPtr++);
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: getUnicodeIndex
|
|
** Description: Get the font file index of a Unicode character
|
|
*************************************************************************************x*/
|
|
bool TFT_eSPI::getUnicodeIndex(uint16_t unicode, uint16_t *index)
|
|
{
|
|
for (uint16_t i = 0; i < gFont.gCount; i++) {
|
|
if (gUnicode[i] == unicode) {
|
|
*index = i;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/***************************************************************************************
|
|
** Function name: drawGlyph
|
|
** Description: Write a character to the TFT cursor position
|
|
*************************************************************************************x*/
|
|
// Expects file to be open
|
|
void TFT_eSPI::drawGlyph(uint16_t code)
|
|
{
|
|
if (code < 0x21) {
|
|
if (code == 0x20) {
|
|
cursor_x += gFont.spaceWidth;
|
|
return;
|
|
}
|
|
|
|
if (code == '\n') {
|
|
cursor_x = 0;
|
|
cursor_y += gFont.yAdvance;
|
|
if (cursor_y >= _height) cursor_y = 0;
|
|
return;
|
|
}
|
|
}
|
|
|
|
uint16_t gNum = 0;
|
|
bool found = getUnicodeIndex(code, &gNum);
|
|
|
|
uint16_t fg = textcolor;
|
|
uint16_t bg = textbgcolor;
|
|
|
|
if (found) {
|
|
|
|
if (textwrapX && (cursor_x + gWidth[gNum] + gdX[gNum] > _width)) {
|
|
cursor_y += gFont.yAdvance;
|
|
cursor_x = 0;
|
|
}
|
|
if (textwrapY && ((cursor_y + gFont.yAdvance) >= _height)) cursor_y = 0;
|
|
if (cursor_x == 0) cursor_x -= gdX[gNum];
|
|
|
|
uint8_t *pbuffer = nullptr;
|
|
const uint8_t *gPtr = (const uint8_t *) gFont.gArray;
|
|
|
|
#ifdef FONT_FS_AVAILABLE
|
|
if (fs_font) {
|
|
fontFile.seek(gBitmap[gNum], fs::SeekSet); // This is taking >30ms for a significant position shift
|
|
pbuffer = (uint8_t *)malloc(gWidth[gNum]);
|
|
}
|
|
#endif
|
|
|
|
int16_t xs = 0;
|
|
uint32_t dl = 0;
|
|
uint8_t pixel;
|
|
|
|
int16_t cy = cursor_y + gFont.maxAscent - gdY[gNum];
|
|
int16_t cx = cursor_x + gdX[gNum];
|
|
|
|
startWrite(); // Avoid slow ESP32 transaction overhead for every pixel
|
|
|
|
for (int y = 0; y < gHeight[gNum]; y++) {
|
|
#ifdef FONT_FS_AVAILABLE
|
|
if (fs_font) {
|
|
if (spiffs) {
|
|
fontFile.read(pbuffer, gWidth[gNum]);
|
|
//Serial.println("SPIFFS");
|
|
} else {
|
|
endWrite(); // Release SPI for SD card transaction
|
|
fontFile.read(pbuffer, gWidth[gNum]);
|
|
startWrite(); // Re-start SPI for TFT transaction
|
|
//Serial.println("Not SPIFFS");
|
|
}
|
|
}
|
|
#endif
|
|
for (int x = 0; x < gWidth[gNum]; x++) {
|
|
#ifdef FONT_FS_AVAILABLE
|
|
if (fs_font) pixel = pbuffer[x];
|
|
else
|
|
#endif
|
|
pixel = pgm_read_byte(gPtr + gBitmap[gNum] + x + gWidth[gNum] * y);
|
|
|
|
if (pixel) {
|
|
if (pixel != 0xFF) {
|
|
if (dl) {
|
|
if (dl == 1) drawPixel(xs, y + cy, fg);
|
|
else drawFastHLine( xs, y + cy, dl, fg);
|
|
dl = 0;
|
|
}
|
|
if (getColor) bg = getColor(x + cx, y + cy);
|
|
drawPixel(x + cx, y + cy, alphaBlend(pixel, fg, bg));
|
|
} else {
|
|
if (dl == 0) xs = x + cx;
|
|
dl++;
|
|
}
|
|
} else {
|
|
if (dl) {
|
|
drawFastHLine( xs, y + cy, dl, fg);
|
|
dl = 0;
|
|
}
|
|
}
|
|
}
|
|
if (dl) {
|
|
drawFastHLine( xs, y + cy, dl, fg);
|
|
dl = 0;
|
|
}
|
|
}
|
|
|
|
if (pbuffer) free(pbuffer);
|
|
cursor_x += gxAdvance[gNum];
|
|
endWrite();
|
|
} else {
|
|
// Not a Unicode in font so draw a rectangle and move on cursor
|
|
drawRect(cursor_x, cursor_y + gFont.maxAscent - gFont.ascent, gFont.spaceWidth, gFont.ascent, fg);
|
|
cursor_x += gFont.spaceWidth + 1;
|
|
}
|
|
}
|
|
|
|
/***************************************************************************************
|
|
** Function name: showFont
|
|
** Description: Page through all characters in font, td ms between screens
|
|
*************************************************************************************x*/
|
|
void TFT_eSPI::showFont(uint32_t td)
|
|
{
|
|
if (!fontLoaded) return;
|
|
|
|
int16_t cursorX = width(); // Force start of new page to initialise cursor
|
|
int16_t cursorY = height();// for the first character
|
|
uint32_t timeDelay = 0; // No delay before first page
|
|
|
|
fillScreen(textbgcolor);
|
|
|
|
for (uint16_t i = 0; i < gFont.gCount; i++) {
|
|
// Check if this will need a new screen
|
|
if (cursorX + gdX[i] + gWidth[i] >= width()) {
|
|
cursorX = -gdX[i];
|
|
|
|
cursorY += gFont.yAdvance;
|
|
if (cursorY + gFont.maxAscent + gFont.descent >= height()) {
|
|
cursorX = -gdX[i];
|
|
cursorY = 0;
|
|
delay(timeDelay);
|
|
timeDelay = td;
|
|
fillScreen(textbgcolor);
|
|
}
|
|
}
|
|
|
|
setCursor(cursorX, cursorY);
|
|
drawGlyph(gUnicode[i]);
|
|
cursorX += gxAdvance[i];
|
|
//cursorX += printToSprite( cursorX, cursorY, i );
|
|
yield();
|
|
}
|
|
|
|
delay(timeDelay);
|
|
fillScreen(textbgcolor);
|
|
//fontFile.close();
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// lewis add. Mark : Used to set different models
|
|
void TFT_eSPI::setDriver(uint32_t model, uint32_t freq)
|
|
{
|
|
drv.tft_driver = model;
|
|
drv.tft_spi_freq = freq;
|
|
} |