NerdNos-Firmware/lib/TFT_eSPI/TFT_eSPI.cpp

6145 lines
191 KiB
C++
Raw Normal View History

/***************************************************
Arduino TFT graphics library targeted at 32 bit
processors such as ESP32, ESP8266 and STM32.
This is a stand-alone 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)
#if defined(CONFIG_IDF_TARGET_ESP32S3)
#include "Processors/TFT_eSPI_ESP32_S3.c" // Tested with SPI and 8 bit parallel
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
#include "Processors/TFT_eSPI_ESP32_C3.c" // Tested with SPI (8 bit parallel will probably work too!)
#else
#include "Processors/TFT_eSPI_ESP32.c"
#endif
#elif defined (ARDUINO_ARCH_ESP8266)
#include "Processors/TFT_eSPI_ESP8266.c"
#elif defined (STM32) // (_VARIANT_ARDUINO_STM32_) stm32_def.h
#include "Processors/TFT_eSPI_STM32.c"
#elif defined (ARDUINO_ARCH_RP2040) || defined (ARDUINO_ARCH_MBED) // Raspberry Pi Pico
#include "Processors/TFT_eSPI_RP2040.c"
#else
#include "Processors/TFT_eSPI_Generic.c"
#endif
#ifndef SPI_BUSY_CHECK
#define SPI_BUSY_CHECK
#endif
// Clipping macro for pushImage
#define PI_CLIP \
if (_vpOoB) return; \
x+= _xDatum; \
y+= _yDatum; \
\
if ((x >= _vpW) || (y >= _vpH)) return; \
\
int32_t dx = 0; \
int32_t dy = 0; \
int32_t dw = w; \
int32_t dh = h; \
\
if (x < _vpX) { dx = _vpX - x; dw -= dx; x = _vpX; } \
if (y < _vpY) { dy = _vpY - y; dh -= dy; y = _vpY; } \
\
if ((x + dw) > _vpW ) dw = _vpW - x; \
if ((y + dh) > _vpH ) dh = _vpH - y; \
\
if (dw < 1 || dh < 1) return;
/***************************************************************************************
** 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: 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 (locked) {
locked = false; // Flag to show SPI access now unlocked
#if defined (SPI_HAS_TRANSACTION) && defined (SUPPORT_TRANSACTIONS) && !defined(TFT_PARALLEL_8_BIT) && !defined(RP2040_PIO_INTERFACE)
spi.beginTransaction(SPISettings(SPI_FREQUENCY, MSBFIRST, TFT_SPI_MODE));
#endif
CS_L;
SET_BUS_WRITE_MODE; // Some processors (e.g. ESP32) allow recycling the tx buffer when rx is not used
}
}
// Non-inlined version to permit override
void TFT_eSPI::begin_nin_write(void){
if (locked) {
locked = false; // Flag to show SPI access now unlocked
#if defined (SPI_HAS_TRANSACTION) && defined (SUPPORT_TRANSACTIONS) && !defined(TFT_PARALLEL_8_BIT) && !defined(RP2040_PIO_INTERFACE)
spi.beginTransaction(SPISettings(SPI_FREQUENCY, MSBFIRST, TFT_SPI_MODE));
#endif
CS_L;
SET_BUS_WRITE_MODE; // Some processors (e.g. ESP32) allow recycling the tx buffer when rx is not used
}
}
/***************************************************************************************
** 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(!inTransaction) { // Flag to stop ending transaction during multiple graphics calls
if (!locked) { // Locked when beginTransaction has been called
locked = true; // Flag to show SPI access now locked
SPI_BUSY_CHECK; // Check send complete and clean out unused rx data
CS_H;
SET_BUS_READ_MODE; // In case bus has been configured for tx only
#if defined (SPI_HAS_TRANSACTION) && defined (SUPPORT_TRANSACTIONS) && !defined(TFT_PARALLEL_8_BIT) && !defined(RP2040_PIO_INTERFACE)
spi.endTransaction();
#endif
}
}
}
// Non-inlined version to permit override
inline void TFT_eSPI::end_nin_write(void){
if(!inTransaction) { // Flag to stop ending transaction during multiple graphics calls
if (!locked) { // Locked when beginTransaction has been called
locked = true; // Flag to show SPI access now locked
SPI_BUSY_CHECK; // Check send complete and clean out unused rx data
CS_H;
SET_BUS_READ_MODE; // In case SPI has been configured for tx only
#if defined (SPI_HAS_TRANSACTION) && defined (SUPPORT_TRANSACTIONS) && !defined(TFT_PARALLEL_8_BIT) && !defined(RP2040_PIO_INTERFACE)
spi.endTransaction();
#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) && !defined(RP2040_PIO_INTERFACE)
if (locked) {
locked = false;
spi.beginTransaction(SPISettings(SPI_READ_FREQUENCY, MSBFIRST, TFT_SPI_MODE));
CS_L;
}
#else
#if !defined(TFT_PARALLEL_8_BIT) && !defined(RP2040_PIO_INTERFACE)
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) && !defined(RP2040_PIO_INTERFACE)
if(!inTransaction) {
if (!locked) {
locked = true;
CS_H;
spi.endTransaction();
}
}
#else
#if !defined(TFT_PARALLEL_8_BIT) && !defined(RP2040_PIO_INTERFACE)
spi.setFrequency(SPI_FREQUENCY);
#endif
if(!inTransaction) {CS_H;}
#endif
SET_BUS_WRITE_MODE;
}
/***************************************************************************************
** Function name: setViewport
** Description: Set the clipping region for the TFT screen
***************************************************************************************/
void TFT_eSPI::setViewport(int32_t x, int32_t y, int32_t w, int32_t h, bool vpDatum)
{
// Viewport metrics (not clipped)
_xDatum = x; // Datum x position in screen coordinates
_yDatum = y; // Datum y position in screen coordinates
_xWidth = w; // Viewport width
_yHeight = h; // Viewport height
// Full size default viewport
_vpDatum = false; // Datum is at top left corner of screen (true = top left of viewport)
_vpOoB = false; // Out of Bounds flag (true is all of viewport is off screen)
_vpX = 0; // Viewport top left corner x coordinate
_vpY = 0; // Viewport top left corner y coordinate
_vpW = width(); // Equivalent of TFT width (Nb: viewport right edge coord + 1)
_vpH = height(); // Equivalent of TFT height (Nb: viewport bottom edge coord + 1)
// Clip viewport to screen area
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; }
//Serial.print(" x=");Serial.print( x);Serial.print(", y=");Serial.print( y);
//Serial.print(", w=");Serial.print(w);Serial.print(", h=");Serial.println(h);
// Check if viewport is entirely out of bounds
if (w < 1 || h < 1)
{
// Set default values and Out of Bounds flag in case of error
_xDatum = 0;
_yDatum = 0;
_xWidth = width();
_yHeight = height();
_vpOoB = true; // Set Out of Bounds flag to inhibit all drawing
return;
}
if (!vpDatum)
{
_xDatum = 0; // Reset to top left of screen if not using a viewport datum
_yDatum = 0;
_xWidth = width();
_yHeight = height();
}
// Store the clipped screen viewport metrics and datum position
_vpX = x;
_vpY = y;
_vpW = x + w;
_vpH = y + h;
_vpDatum = vpDatum;
//Serial.print(" _xDatum=");Serial.print( _xDatum);Serial.print(", _yDatum=");Serial.print( _yDatum);
//Serial.print(", _xWidth=");Serial.print(_xWidth);Serial.print(", _yHeight=");Serial.println(_yHeight);
//Serial.print(" _vpX=");Serial.print( _vpX);Serial.print(", _vpY=");Serial.print( _vpY);
//Serial.print(", _vpW=");Serial.print(_vpW);Serial.print(", _vpH=");Serial.println(_vpH);
}
/***************************************************************************************
** Function name: checkViewport
** Description: Check if any part of specified area is visible in viewport
***************************************************************************************/
// Note: Setting w and h to 1 will check if coordinate x,y is in area
bool TFT_eSPI::checkViewport(int32_t x, int32_t y, int32_t w, int32_t h)
{
if (_vpOoB) return false;
x+= _xDatum;
y+= _yDatum;
if ((x >= _vpW) || (y >= _vpH)) return false;
int32_t dx = 0;
int32_t dy = 0;
int32_t dw = w;
int32_t dh = h;
if (x < _vpX) { dx = _vpX - x; dw -= dx; x = _vpX; }
if (y < _vpY) { dy = _vpY - y; dh -= dy; y = _vpY; }
if ((x + dw) > _vpW ) dw = _vpW - x;
if ((y + dh) > _vpH ) dh = _vpH - y;
if (dw < 1 || dh < 1) return false;
return true;
}
/***************************************************************************************
** Function name: resetViewport
** Description: Reset viewport to whole TFT screen, datum at 0,0
***************************************************************************************/
void TFT_eSPI::resetViewport(void)
{
// Reset viewport to the whole screen (or sprite) area
_vpDatum = false;
_vpOoB = false;
_xDatum = 0;
_yDatum = 0;
_vpX = 0;
_vpY = 0;
_vpW = width();
_vpH = height();
_xWidth = width();
_yHeight = height();
}
/***************************************************************************************
** Function name: getViewportX
** Description: Get x position of the viewport datum
***************************************************************************************/
int32_t TFT_eSPI::getViewportX(void)
{
return _xDatum;
}
/***************************************************************************************
** Function name: getViewportY
** Description: Get y position of the viewport datum
***************************************************************************************/
int32_t TFT_eSPI::getViewportY(void)
{
return _yDatum;
}
/***************************************************************************************
** Function name: getViewportWidth
** Description: Get width of the viewport
***************************************************************************************/
int32_t TFT_eSPI::getViewportWidth(void)
{
return _xWidth;
}
/***************************************************************************************
** Function name: getViewportHeight
** Description: Get height of the viewport
***************************************************************************************/
int32_t TFT_eSPI::getViewportHeight(void)
{
return _yHeight;
}
/***************************************************************************************
** Function name: getViewportDatum
** Description: Get datum flag of the viewport (true = viewport corner)
***************************************************************************************/
bool TFT_eSPI::getViewportDatum(void)
{
return _vpDatum;
}
/***************************************************************************************
** Function name: frameViewport
** Description: Draw a frame inside or outside the viewport of width w
***************************************************************************************/
void TFT_eSPI::frameViewport(uint16_t color, int32_t w)
{
// Save datum position
bool _dT = _vpDatum;
// If w is positive the frame is drawn inside the viewport
// a large positive width will clear the screen inside the viewport
if (w>0)
{
// Set vpDatum true to simplify coordinate derivation
_vpDatum = true;
fillRect(0, 0, _vpW - _vpX, w, color); // Top
fillRect(0, w, w, _vpH - _vpY - w - w, color); // Left
fillRect(_xWidth - w, w, w, _yHeight - w - w, color); // Right
fillRect(0, _yHeight - w, _xWidth, w, color); // Bottom
}
else
// If w is negative the frame is drawn outside the viewport
// a large negative width will clear the screen outside the viewport
{
w = -w;
// Save old values
int32_t _xT = _vpX; _vpX = 0;
int32_t _yT = _vpY; _vpY = 0;
int32_t _wT = _vpW;
int32_t _hT = _vpH;
// Set vpDatum false so frame can be drawn outside window
_vpDatum = false; // When false the full width and height is accessed
_vpH = height();
_vpW = width();
// Draw frame
fillRect(_xT - w - _xDatum, _yT - w - _yDatum, _wT - _xT + w + w, w, color); // Top
fillRect(_xT - w - _xDatum, _yT - _yDatum, w, _hT - _yT, color); // Left
fillRect(_wT - _xDatum, _yT - _yDatum, w, _hT - _yT, color); // Right
fillRect(_xT - w - _xDatum, _hT - _yDatum, _wT - _xT + w + w, w, color); // Bottom
// Restore old values
_vpX = _xT;
_vpY = _yT;
_vpW = _wT;
_vpH = _hT;
}
// Restore vpDatum
_vpDatum = _dT;
}
/***************************************************************************************
** Function name: clipAddrWindow
** Description: Clip address window x,y,w,h to screen and viewport
***************************************************************************************/
bool TFT_eSPI::clipAddrWindow(int32_t *x, int32_t *y, int32_t *w, int32_t *h)
{
if (_vpOoB) return false; // Area is outside of viewport
*x+= _xDatum;
*y+= _yDatum;
if ((*x >= _vpW) || (*y >= _vpH)) return false; // Area is outside of viewport
// Crop drawing area bounds
if (*x < _vpX) { *w -= _vpX - *x; *x = _vpX; }
if (*y < _vpY) { *h -= _vpY - *y; *y = _vpY; }
if ((*x + *w) > _vpW ) *w = _vpW - *x;
if ((*y + *h) > _vpH ) *h = _vpH - *y;
if (*w < 1 || *h < 1) return false; // No area is inside viewport
return true; // Area is wholly or partially inside viewport
}
/***************************************************************************************
** Function name: clipWindow
** Description: Clip window xs,yx,xe,ye to screen and viewport
***************************************************************************************/
bool TFT_eSPI::clipWindow(int32_t *xs, int32_t *ys, int32_t *xe, int32_t *ye)
{
if (_vpOoB) return false; // Area is outside of viewport
*xs+= _xDatum;
*ys+= _yDatum;
*xe+= _xDatum;
*ye+= _yDatum;
if ((*xs >= _vpW) || (*ys >= _vpH)) return false; // Area is outside of viewport
if ((*xe < _vpX) || (*ye < _vpY)) return false; // Area is outside of viewport
// Crop drawing area bounds
if (*xs < _vpX) *xs = _vpX;
if (*ys < _vpY) *ys = _vpY;
if (*xe > _vpW) *xe = _vpW - 1;
if (*ye > _vpH) *ye = _vpH - 1;
return true; // Area is wholly or partially inside viewport
}
/***************************************************************************************
** Function name: TFT_eSPI
** Description: Constructor , we must use hardware SPI pins
***************************************************************************************/
TFT_eSPI::TFT_eSPI(int16_t w, int16_t h)
{
_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
// Reset the viewport to the whole screen
resetViewport();
rotation = 0;
cursor_y = cursor_x = last_cursor_x = bg_cursor_x = 0;
textfont = 1;
textsize = 1;
textcolor = bitmap_fg = 0xFFFF; // White
textbgcolor = bitmap_bg = 0x0000; // Black
padX = 0; // No padding
_fillbg = false; // Smooth font only at the moment, force text background fill
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 flag to ensure begin/endTranaction pairing
inTransaction = false; // Flag to prevent multiple sequential functions to keep bus access open
lockTransaction = false; // start/endWrite lock flag to allow sketch to keep SPI bus access open
_booted = true; // Default attributes
_cp437 = true; // Legacy GLCD font bug fix
_utf8 = true; // UTF8 decoding enabled
#if defined (FONT_FS_AVAILABLE) && defined (SMOOTH_FONT)
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; // drawPixel command length optimiser
addr_col = 0xFFFF; // drawPixel command length optimiser
_xPivot = 0;
_yPivot = 0;
// Legacy support for bit GPIO masks
cspinmask = 0;
dcpinmask = 0;
wrpinmask = 0;
sclkpinmask = 0;
// Flags for which fonts are loaded
#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: initBus
** Description: initialise the SPI or parallel bus
***************************************************************************************/
void TFT_eSPI::initBus(void) {
#ifdef TFT_CS
if (TFT_CS >= 0) {
pinMode(TFT_CS, OUTPUT);
digitalWrite(TFT_CS, HIGH); // Chip select high (inactive)
}
#endif
// Configure chip select for touchscreen controller if present
#ifdef TOUCH_CS
if (TOUCH_CS >= 0) {
pinMode(TOUCH_CS, OUTPUT);
digitalWrite(TOUCH_CS, HIGH); // Chip select high (inactive)
}
#endif
// In parallel mode and with the RP2040 processor, the TFT_WR line is handled in the PIO
#if defined (TFT_WR) && !defined (ARDUINO_ARCH_RP2040) && !defined (ARDUINO_ARCH_MBED)
if (TFT_WR >= 0) {
pinMode(TFT_WR, OUTPUT);
digitalWrite(TFT_WR, HIGH); // Set write strobe high (inactive)
}
#endif
#ifdef TFT_DC
if (TFT_DC >= 0) {
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
if (TFT_RD >= 0) {
pinMode(TFT_RD, OUTPUT);
digitalWrite(TFT_RD, HIGH);
}
#if !defined (ARDUINO_ARCH_RP2040) && !defined (ARDUINO_ARCH_MBED)// PIO manages pins
// 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);
#endif
PARALLEL_INIT_TFT_DATA_BUS;
#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)
{
initBus();
#if !defined (ESP32) && !defined(TFT_PARALLEL_8_BIT) && !defined(ARDUINO_ARCH_RP2040) && !defined (ARDUINO_ARCH_MBED)
// Legacy bitmasks for GPIO
#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 (ARDUINO_ARCH_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) && !defined(RP2040_PIO_INTERFACE)
#if defined (TFT_MOSI) && !defined (TFT_SPI_OVERLAP) && !defined(ARDUINO_ARCH_RP2040) && !defined (ARDUINO_ARCH_MBED)
spi.begin(TFT_SCLK, TFT_MISO, TFT_MOSI, -1); // This will set MISO to input
#else
spi.begin(); // This will set MISO to input
#endif
#endif
#endif
lockTransaction = false;
inTransaction = false;
locked = true;
INIT_TFT_DATA_BUS;
#if defined (TFT_CS) && !defined(RP2040_PIO_INTERFACE)
// Set to output once again in case MISO is used for CS
if (TFT_CS >= 0) {
pinMode(TFT_CS, OUTPUT);
digitalWrite(TFT_CS, HIGH); // Chip select high (inactive)
}
#elif defined (ARDUINO_ARCH_ESP8266) && !defined (TFT_PARALLEL_8_BIT) && !defined (RP2040_PIO_SPI)
spi.setHwCs(1); // Use hardware SS toggling
#endif
// Set to output once again in case MISO is used for DC
#if defined (TFT_DC) && !defined(RP2040_PIO_INTERFACE)
if (TFT_DC >= 0) {
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
#ifdef TFT_RST
#if !defined(RP2040_PIO_INTERFACE)
// Set to output once again in case MISO is used for TFT_RST
if (TFT_RST >= 0) {
pinMode(TFT_RST, OUTPUT);
}
#endif
if (TFT_RST >= 0) {
writecommand(0x00); // Put SPI bus in known state for TFT with CS tied low
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
delay(150); // Wait for reset to complete
begin_tft_write();
tc = tc; // Suppress warning
// This loads the driver specific initialisation code <<<<<<<<<<<<<<<<<<<<< ADD NEW DRIVERS TO THE LIST HERE <<<<<<<<<<<<<<<<<<<<<<<
#if defined (ILI9341_DRIVER) || defined(ILI9341_2_DRIVER) || defined (ILI9342_DRIVER)
#include "TFT_Drivers/ILI9341_Init.h"
#elif defined (ST7735_DRIVER)
tabcolor = tc;
#include "TFT_Drivers/ST7735_Init.h"
#elif defined (ILI9163_DRIVER)
#include "TFT_Drivers/ILI9163_Init.h"
#elif defined (S6D02A1_DRIVER)
#include "TFT_Drivers/S6D02A1_Init.h"
#elif defined (ST7796_DRIVER)
#include "TFT_Drivers/ST7796_Init.h"
#elif defined (ILI9486_DRIVER)
#include "TFT_Drivers/ILI9486_Init.h"
#elif defined (ILI9481_DRIVER)
#include "TFT_Drivers/ILI9481_Init.h"
#elif defined (ILI9488_DRIVER)
#include "TFT_Drivers/ILI9488_Init.h"
#elif defined (HX8357D_DRIVER)
#include "TFT_Drivers/HX8357D_Init.h"
#elif defined (ST7789_DRIVER)
#include "TFT_Drivers/ST7789_Init.h"
#elif defined (R61581_DRIVER)
#include "TFT_Drivers/R61581_Init.h"
#elif defined (RM68140_DRIVER)
#include "TFT_Drivers/RM68140_Init.h"
#elif defined (ST7789_2_DRIVER)
#include "TFT_Drivers/ST7789_2_Init.h"
#elif defined (SSD1351_DRIVER)
#include "TFT_Drivers/SSD1351_Init.h"
#elif defined (SSD1963_DRIVER)
#include "TFT_Drivers/SSD1963_Init.h"
#elif defined (GC9A01_DRIVER)
#include "TFT_Drivers/GC9A01_Init.h"
#elif defined (ILI9225_DRIVER)
#include "TFT_Drivers/ILI9225_Init.h"
#elif defined (RM68120_DRIVER)
#include "TFT_Drivers/RM68120_Init.h"
#elif defined (HX8357B_DRIVER)
#include "TFT_Drivers/HX8357B_Init.h"
#elif defined (HX8357C_DRIVER)
#include "TFT_Drivers/HX8357C_Init.h"
#endif
#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)
if (TFT_BL >= 0) {
pinMode(TFT_BL, OUTPUT);
digitalWrite(TFT_BL, TFT_BACKLIGHT_ON);
}
#else
#if defined (TFT_BL) && defined (M5STACK)
// Turn on the back-light LED
if (TFT_BL >= 0) {
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();
// This loads the driver specific rotation code <<<<<<<<<<<<<<<<<<<<< ADD NEW DRIVERS TO THE LIST HERE <<<<<<<<<<<<<<<<<<<<<<<
#if defined (ILI9341_DRIVER) || defined(ILI9341_2_DRIVER) || defined (ILI9342_DRIVER)
#include "TFT_Drivers/ILI9341_Rotation.h"
#elif defined (ST7735_DRIVER)
#include "TFT_Drivers/ST7735_Rotation.h"
#elif defined (ILI9163_DRIVER)
#include "TFT_Drivers/ILI9163_Rotation.h"
#elif defined (S6D02A1_DRIVER)
#include "TFT_Drivers/S6D02A1_Rotation.h"
#elif defined (ST7796_DRIVER)
#include "TFT_Drivers/ST7796_Rotation.h"
#elif defined (ILI9486_DRIVER)
#include "TFT_Drivers/ILI9486_Rotation.h"
#elif defined (ILI9481_DRIVER)
#include "TFT_Drivers/ILI9481_Rotation.h"
#elif defined (ILI9488_DRIVER)
#include "TFT_Drivers/ILI9488_Rotation.h"
#elif defined (HX8357D_DRIVER)
#include "TFT_Drivers/HX8357D_Rotation.h"
#elif defined (ST7789_DRIVER)
#include "TFT_Drivers/ST7789_Rotation.h"
#elif defined (R61581_DRIVER)
#include "TFT_Drivers/R61581_Rotation.h"
#elif defined (RM68140_DRIVER)
#include "TFT_Drivers/RM68140_Rotation.h"
#elif defined (ST7789_2_DRIVER)
#include "TFT_Drivers/ST7789_2_Rotation.h"
#elif defined (SSD1351_DRIVER)
#include "TFT_Drivers/SSD1351_Rotation.h"
#elif defined (SSD1963_DRIVER)
#include "TFT_Drivers/SSD1963_Rotation.h"
#elif defined (GC9A01_DRIVER)
#include "TFT_Drivers/GC9A01_Rotation.h"
#elif defined (ILI9225_DRIVER)
#include "TFT_Drivers/ILI9225_Rotation.h"
#elif defined (RM68120_DRIVER)
#include "TFT_Drivers/RM68120_Rotation.h"
#elif defined (HX8357B_DRIVER)
#include "TFT_Drivers/HX8357B_Rotation.h"
#elif defined (HX8357C_DRIVER)
#include "TFT_Drivers/HX8357C_Rotation.h"
#endif
delayMicroseconds(10);
end_tft_write();
addr_row = 0xFFFF;
addr_col = 0xFFFF;
// Reset the viewport to the whole screen
resetViewport();
}
/***************************************************************************************
** Function name: getRotation
** Description: Return the rotation value (as used by setRotation())
***************************************************************************************/
uint8_t TFT_eSPI::getRotation(void)
{
return rotation;
}
/***************************************************************************************
** Function name: setOrigin
** Description: Set graphics origin to position x,y wrt to top left corner
***************************************************************************************/
//Note: setRotation, setViewport and resetViewport will revert origin to top left
void TFT_eSPI::setOrigin(int32_t x, int32_t y)
{
_xDatum = x;
_yDatum = y;
}
/***************************************************************************************
** Function name: getOriginX
** Description: Set graphics origin to position x
***************************************************************************************/
int32_t TFT_eSPI::getOriginX(void)
{
return _xDatum;
}
/***************************************************************************************
** Function name: getOriginY
** Description: Set graphics origin to position y
***************************************************************************************/
int32_t TFT_eSPI::getOriginY(void)
{
return _yDatum;
}
/***************************************************************************************
** 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
***************************************************************************************/
#ifndef RM68120_DRIVER
void TFT_eSPI::writecommand(uint8_t c)
{
begin_tft_write();
DC_C;
tft_Write_8(c);
DC_D;
end_tft_write();
}
#else
void TFT_eSPI::writecommand(uint16_t c)
{
begin_tft_write();
DC_C;
tft_Write_16(c);
DC_D;
end_tft_write();
}
void TFT_eSPI::writeRegister8(uint16_t c, uint8_t d)
{
begin_tft_write();
DC_C;
tft_Write_16(c);
DC_D;
tft_Write_8(d);
end_tft_write();
}
void TFT_eSPI::writeRegister16(uint16_t c, uint16_t d)
{
begin_tft_write();
DC_C;
tft_Write_16(c);
DC_D;
tft_Write_16(d);
end_tft_write();
}
#endif
/***************************************************************************************
** 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;
#if defined(TFT_PARALLEL_8_BIT) || defined(RP2040_PIO_INTERFACE)
writecommand(cmd_function); // Sets DC and CS high
busDir(GPIO_DIR_MASK, INPUT);
CS_L;
// Read nth parameter (assumes caller discards 1st parameter or points index to 2nd)
while(index--) reg = readByte();
busDir(GPIO_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 (_vpOoB) return 0;
x0+= _xDatum;
y0+= _yDatum;
// Range checking
if ((x0 < _vpX) || (y0 < _vpY) ||(x0 >= _vpW) || (y0 >= _vpH)) return 0;
#if defined(TFT_PARALLEL_8_BIT) || defined(RP2040_PIO_INTERFACE)
if (!inTransaction) { CS_L; } // CS_L can be multi-statement
readAddrWindow(x0, y0, 1, 1);
// Set masked pins D0- D7 to input
busDir(GPIO_DIR_MASK, INPUT);
#if !defined (SSD1963_DRIVER)
// Dummy read to throw away don't care value
readByte();
#endif
// Fetch the 16 bit BRG pixel
//uint16_t rgb = (readByte() << 8) | readByte();
#if defined (ILI9341_DRIVER) || defined(ILI9341_2_DRIVER) || defined (ILI9488_DRIVER) || defined (SSD1963_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);
if (!inTransaction) { CS_H; } // CS_H can be multi-statement
// Set masked pins D0- D7 to output
busDir(GPIO_DIR_MASK, OUTPUT);
return rgb;
#else // ILI9481 or ILI9486 16 bit read
// Fetch the 16 bit BRG pixel
uint16_t bgr = (readByte() << 8) | readByte();
if (!inTransaction) { CS_H; } // CS_H can be multi-statement
// Set masked pins D0- D7 to output
busDir(GPIO_DIR_MASK, OUTPUT);
#if defined (ILI9486_DRIVER) || defined (ST7796_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 anti-aliased font rendering
// so a transaction may be in progress
bool wasInTransaction = inTransaction;
if (inTransaction) { inTransaction= false; end_tft_write();}
uint16_t color = 0;
begin_tft_read(); // Sets CS low
readAddrWindow(x0, y0, 1, 1);
#ifdef TFT_SDA_READ
begin_SDA_Read();
#endif
// Dummy read to throw away don't care value
tft_Read_8();
//#if !defined (ILI9488_DRIVER)
#if defined (ST7796_DRIVER)
// Read the 2 bytes
color = ((tft_Read_8()) << 8) | (tft_Read_8());
#elif defined (ST7735_DRIVER)
// Read the 3 RGB bytes, colour is in LS 6 bits of the top 7 bits of each byte
// as the TFT stores colours as 18 bits
uint8_t r = tft_Read_8()<<1;
uint8_t g = tft_Read_8()<<1;
uint8_t b = tft_Read_8()<<1;
color = color565(r, g, b);
#else
// 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();
color = color565(r, g, b);
#endif
/*
#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;
color = color565(r, g, b);
#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 color;
#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)
{
PI_CLIP ;
#if defined(TFT_PARALLEL_8_BIT) || defined(RP2040_PIO_INTERFACE)
CS_L;
readAddrWindow(x, y, dw, dh);
data += dx + dy * w;
// Set masked pins D0- D7 to input
busDir(GPIO_DIR_MASK, INPUT);
#if defined (ILI9341_DRIVER) || defined(ILI9341_2_DRIVER) || defined (ILI9488_DRIVER) // Read 3 bytes
// Dummy read to throw away don't care value
readByte();
// Fetch the 24 bit RGB value
while (dh--) {
int32_t lw = dw;
uint16_t* line = data;
while (lw--) {
// Assemble the RGB 16 bit colour
uint16_t rgb = ((readByte() & 0xF8) << 8) | ((readByte() & 0xFC) << 3) | (readByte() >> 3);
// Swapped byte order for compatibility with pushRect()
*line++ = (rgb<<8) | (rgb>>8);
}
data += w;
}
#elif defined (SSD1963_DRIVER)
// Fetch the 18 bit BRG pixels
while (dh--) {
int32_t lw = dw;
uint16_t* line = data;
while (lw--) {
uint16_t bgr = ((readByte() & 0xF8) >> 3);; // CS_L adds a small delay
bgr |= ((readByte() & 0xFC) << 3);
bgr |= (readByte() << 8);
// 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()
*line++ = (rgb<<8) | (rgb>>8);
}
data += w;
}
#else // ILI9481 reads as 16 bits
// Dummy read to throw away don't care value
readByte();
// Fetch the 16 bit BRG pixels
while (dh--) {
int32_t lw = dw;
uint16_t* line = data;
while (lw--) {
#if defined (ILI9486_DRIVER) || defined (ST7796_DRIVER)
// Read the RGB 16 bit colour
*line++ = 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()
*line++ = (rgb<<8) | (rgb>>8);
#endif
}
data += w;
}
#endif
CS_H;
// Set masked pins D0- D7 to output
busDir(GPIO_DIR_MASK, OUTPUT);
#else // SPI interface
// This function can get called after a begin_tft_write
// so a transaction may be in progress
bool wasInTransaction = inTransaction;
if (inTransaction) { inTransaction= false; end_tft_write();}
uint16_t color = 0;
begin_tft_read();
readAddrWindow(x, y, dw, dh);
data += dx + dy * w;
#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
while (dh--) {
int32_t lw = dw;
uint16_t* line = data;
while (lw--) {
#if !defined (ILI9488_DRIVER)
#if defined (ST7796_DRIVER)
// Read the 2 bytes
color = ((tft_Read_8()) << 8) | (tft_Read_8());
#elif defined (ST7735_DRIVER)
// Read the 3 RGB bytes, colour is in LS 6 bits of the top 7 bits of each byte
// as the TFT stores colours as 18 bits
uint8_t r = tft_Read_8()<<1;
uint8_t g = tft_Read_8()<<1;
uint8_t b = tft_Read_8()<<1;
color = color565(r, g, b);
#else
// 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();
color = color565(r, g, b);
#endif
#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;
color = color565(r, g, b);
#endif
// Swapped colour byte order for compatibility with pushRect()
*line++ = color << 8 | color >> 8;
}
data += w;
}
//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; }
#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)
{
PI_CLIP;
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 = lockTransaction;
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)
{
PI_CLIP;
begin_tft_write();
inTransaction = true;
data += dx + dy * w;
uint16_t lineBuf[dw]; // Use buffer to minimise setWindow call count
// The little endian transp color must be byte swapped if the image is big endian
if (!_swapBytes) transp = transp >> 8 | transp << 8;
while (dh--)
{
int32_t len = dw;
uint16_t* ptr = data;
int32_t px = x, sx = x;
bool move = true;
uint16_t np = 0;
while (len--)
{
if (transp != *ptr)
{
if (move) { move = false; sx = px; }
lineBuf[np] = *ptr;
np++;
}
else
{
move = true;
if (np)
{
setWindow(sx, y, sx + np - 1, y);
pushPixels((uint16_t*)lineBuf, np);
np = 0;
}
}
px++;
ptr++;
}
if (np) { setWindow(sx, y, sx + np - 1, y); pushPixels((uint16_t*)lineBuf, np); }
y++;
data += w;
}
inTransaction = lockTransaction;
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
PI_CLIP;
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 = lockTransaction;
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
PI_CLIP;
begin_tft_write();
inTransaction = true;
data += dx + dy * w;
uint16_t lineBuf[dw];
// The little endian transp color must be byte swapped if the image is big endian
if (!_swapBytes) transp = transp >> 8 | transp << 8;
while (dh--) {
int32_t len = dw;
uint16_t* ptr = (uint16_t*)data;
int32_t px = x, sx = x;
bool move = true;
uint16_t np = 0;
while (len--) {
uint16_t color = pgm_read_word(ptr);
if (transp != color) {
if (move) { move = false; sx = px; }
lineBuf[np] = color;
np++;
}
else {
move = true;
if (np) {
setWindow(sx, y, sx + np - 1, y);
pushPixels(lineBuf, np);
np = 0;
}
}
px++;
ptr++;
}
if (np) { setWindow(sx, y, sx + np - 1, y); pushPixels(lineBuf, np); }
y++;
data += w;
}
inTransaction = lockTransaction;
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, const uint8_t *data, bool bpp8, uint16_t *cmap)
{
PI_CLIP;
begin_tft_write();
inTransaction = true;
bool swap = _swapBytes;
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)
{
_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 = (uint8_t*)data;
uint8_t* linePtr = (uint8_t*)lineBuf;
while(len--) {
uint32_t color = pgm_read_byte(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
{
_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 = (uint8_t*)data;
uint16_t *linePtr = lineBuf;
uint8_t colors; // two colors in one byte
uint16_t index;
if (splitFirst) {
colors = pgm_read_byte(ptr);
index = (colors & 0x0F);
*linePtr++ = cmap[index];
len--;
ptr++;
}
while (len--)
{
colors = pgm_read_byte(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
{
_swapBytes = false;
uint8_t * ptr = (uint8_t*)data;
uint32_t ww = (w+7)>>3; // Width of source image line in bytes
for (int32_t yp = dy; yp < dy + dh; yp++)
{
uint8_t* linePtr = (uint8_t*)lineBuf;
for (int32_t xp = dx; xp < dx + dw; xp++)
{
uint16_t col = (pgm_read_byte(ptr + (xp>>3)) & (0x80 >> (xp & 0x7)) );
if (col) {*linePtr++ = bitmap_fg>>8; *linePtr++ = (uint8_t) bitmap_fg;}
else {*linePtr++ = bitmap_bg>>8; *linePtr++ = (uint8_t) bitmap_bg;}
}
ptr += ww;
pushPixels(lineBuf, dw);
}
}
_swapBytes = swap; // Restore old value
inTransaction = lockTransaction;
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)
{
PI_CLIP;
begin_tft_write();
inTransaction = true;
bool swap = _swapBytes;
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)
{
_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
{
_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
{
_swapBytes = false;
uint32_t ww = (w+7)>>3; // Width of source image line in bytes
for (int32_t yp = dy; yp < dy + dh; yp++)
{
uint8_t* linePtr = (uint8_t*)lineBuf;
for (int32_t xp = dx; xp < dx + dw; xp++)
{
uint16_t col = (data[(xp>>3)] & (0x80 >> (xp & 0x7)) );
if (col) {*linePtr++ = bitmap_fg>>8; *linePtr++ = (uint8_t) bitmap_fg;}
else {*linePtr++ = bitmap_bg>>8; *linePtr++ = (uint8_t) bitmap_bg;}
}
data += ww;
pushPixels(lineBuf, dw);
}
}
_swapBytes = swap; // Restore old value
inTransaction = lockTransaction;
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)
{
PI_CLIP;
begin_tft_write();
inTransaction = true;
bool swap = _swapBytes;
// Line buffer makes plotting faster
uint16_t lineBuf[dw];
if (bpp8) { // 8 bits per pixel
_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;
while (dh--) {
int32_t len = dw;
uint8_t* ptr = data;
uint8_t* linePtr = (uint8_t*)lineBuf;
int32_t px = x, sx = x;
bool move = true;
uint16_t np = 0;
while (len--) {
if (transp != *ptr) {
if (move) { move = false; sx = px; }
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) {
setWindow(sx, y, sx + np - 1, y);
pushPixels(lineBuf, np);
linePtr = (uint8_t*)lineBuf;
np = 0;
}
}
px++;
ptr++;
}
if (np) { setWindow(sx, y, sx + np - 1, y); pushPixels(lineBuf, np); }
y++;
data += w;
}
}
else if (cmap != nullptr) // 4bpp with color map
{
_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, sx = 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; sx = px;
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; sx = px;
}
lineBuf[np] = cmap[index];
np++; // added a pixel
}
else {
move = true;
if (np) {
setWindow(sx, y, sx + np - 1, y);
pushPixels(lineBuf, np);
np = 0;
}
}
px++;
if (len--)
{
index = color & 0x0F; // the odd number is 3 .. 0
if (index != transp) {
if (move) {
move = false; sx = px;
}
lineBuf[np] = cmap[index];
np++;
}
else {
move = true;
if (np) {
setWindow(sx, y, sx + np - 1, y);
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) {
setWindow(sx, y, sx + np - 1, y);
pushPixels(lineBuf, np);
np = 0;
}
data += (w>>1);
y++;
}
}
else { // 1 bit per pixel
_swapBytes = false;
uint32_t ww = (w+7)>>3; // Width of source image line in bytes
uint16_t np = 0;
for (int32_t yp = dy; yp < dy + dh; yp++)
{
int32_t px = x, sx = x;
bool move = true;
for (int32_t xp = dx; xp < dx + dw; xp++)
{
if (data[(xp>>3)] & (0x80 >> (xp & 0x7))) {
if (move) {
move = false;
sx = px;
}
np++;
}
else {
move = true;
if (np) {
setWindow(sx, y, sx + np - 1, y);
pushBlock(bitmap_fg, np);
np = 0;
}
}
px++;
}
if (np) { setWindow(sx, y, sx + np - 1, y); pushBlock(bitmap_fg, np); np = 0; }
y++;
data += ww;
}
}
_swapBytes = swap; // Restore old value
inTransaction = lockTransaction;
end_tft_write();
}
/***************************************************************************************
** Function name: pushMaskedImage
** Description: Render a 16 bit colour image to TFT with a 1bpp mask
***************************************************************************************/
// Can be used with a 16bpp sprite and a 1bpp sprite for the mask
void TFT_eSPI::pushMaskedImage(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t *img, uint8_t *mask)
{
if (_vpOoB || w < 1 || h < 1) return;
// To simplify mask handling the window clipping is done by the pushImage function
// Each mask image line assumed to be padded to and integer number of bytes & padding bits are 0
begin_tft_write();
inTransaction = true;
uint8_t *mptr = mask;
uint8_t *eptr = mask + ((w + 7) >> 3);
uint16_t *iptr = img;
uint32_t setCount = 0;
// For each line in the image
while (h--) {
uint32_t xp = 0;
uint32_t clearCount = 0;
uint8_t mbyte= *mptr++;
uint32_t bits = 8;
// Scan through each byte of the bitmap and determine run lengths
do {
setCount = 0;
//Get run length for clear bits to determine x offset
while ((mbyte & 0x80) == 0x00) {
// Check if remaining bits in byte are clear (reduce shifts)
if (mbyte == 0) {
clearCount += bits; // bits not always 8 here
if (mptr >= eptr) break; // end of line
mbyte = *mptr++;
bits = 8;
continue;
}
mbyte = mbyte << 1; // 0's shifted in
clearCount ++;
if (--bits) continue;;
if (mptr >= eptr) break;
mbyte = *mptr++;
bits = 8;
}
//Get run length for set bits to determine render width
while ((mbyte & 0x80) == 0x80) {
// Check if all bits are set (reduces shifts)
if (mbyte == 0xFF) {
setCount += bits;
if (mptr >= eptr) break;
mbyte = *mptr++;
//bits = 8; // NR, bits always 8 here unless 1's shifted in
continue;
}
mbyte = mbyte << 1; //or mbyte += mbyte + 1 to shift in 1's
setCount ++;
if (--bits) continue;
if (mptr >= eptr) break;
mbyte = *mptr++;
bits = 8;
}
// A mask boundary or mask end has been found, so render the pixel line
if (setCount) {
xp += clearCount;
clearCount = 0;
pushImage(x + xp, y, setCount, 1, iptr + xp); // pushImage handles clipping
xp += setCount;
}
} while (setCount || mptr < eptr);
y++;
iptr += w;
eptr += ((w + 7) >> 3);
}
inTransaction = lockTransaction;
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) || defined(RP2040_PIO_INTERFACE)
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)
{
if ( r <= 0 ) return;
//begin_tft_write(); // Sprite class can use this function, avoiding begin_tft_write()
inTransaction = true;
int32_t f = 1 - r;
int32_t ddF_y = -2 * r;
int32_t ddF_x = 1;
int32_t xs = -1;
int32_t xe = 0;
int32_t len = 0;
bool first = true;
do {
while (f < 0) {
++xe;
f += (ddF_x += 2);
}
f += (ddF_y += 2);
if (xe-xs>1) {
if (first) {
len = 2*(xe - xs)-1;
drawFastHLine(x0 - xe, y0 + r, len, color);
drawFastHLine(x0 - xe, y0 - r, len, color);
drawFastVLine(x0 + r, y0 - xe, len, color);
drawFastVLine(x0 - r, y0 - xe, len, color);
first = false;
}
else {
len = xe - xs++;
drawFastHLine(x0 - xe, y0 + r, len, color);
drawFastHLine(x0 - xe, y0 - r, len, color);
drawFastHLine(x0 + xs, y0 - r, len, color);
drawFastHLine(x0 + xs, y0 + r, len, color);
drawFastVLine(x0 + r, y0 + xs, len, color);
drawFastVLine(x0 + r, y0 - xe, len, color);
drawFastVLine(x0 - r, y0 - xe, len, color);
drawFastVLine(x0 - r, y0 + xs, len, color);
}
}
else {
++xs;
drawPixel(x0 - xe, y0 + r, color);
drawPixel(x0 - xe, y0 - r, color);
drawPixel(x0 + xs, y0 - r, color);
drawPixel(x0 + xs, y0 + r, color);
drawPixel(x0 + r, y0 + xs, color);
drawPixel(x0 + r, y0 - xe, color);
drawPixel(x0 - r, y0 - xe, color);
drawPixel(x0 - r, y0 + xs, color);
}
xs = xe;
} while (xe < --r);
inTransaction = lockTransaction;
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 rr, uint8_t cornername, uint32_t color)
{
if (rr <= 0) return;
int32_t f = 1 - rr;
int32_t ddF_x = 1;
int32_t ddF_y = -2 * rr;
int32_t xe = 0;
int32_t xs = 0;
int32_t len = 0;
//begin_tft_write(); // Sprite class can use this function, avoiding begin_tft_write()
inTransaction = true;
while (xe < rr--)
{
while (f < 0) {
++xe;
f += (ddF_x += 2);
}
f += (ddF_y += 2);
if (xe-xs==1) {
if (cornername & 0x1) { // left top
drawPixel(x0 - xe, y0 - rr, color);
drawPixel(x0 - rr, y0 - xe, color);
}
if (cornername & 0x2) { // right top
drawPixel(x0 + rr , y0 - xe, color);
drawPixel(x0 + xs + 1, y0 - rr, color);
}
if (cornername & 0x4) { // right bottom
drawPixel(x0 + xs + 1, y0 + rr , color);
drawPixel(x0 + rr, y0 + xs + 1, color);
}
if (cornername & 0x8) { // left bottom
drawPixel(x0 - rr, y0 + xs + 1, color);
drawPixel(x0 - xe, y0 + rr , color);
}
}
else {
len = xe - xs++;
if (cornername & 0x1) { // left top
drawFastHLine(x0 - xe, y0 - rr, len, color);
drawFastVLine(x0 - rr, y0 - xe, len, color);
}
if (cornername & 0x2) { // right top
drawFastVLine(x0 + rr, y0 - xe, len, color);
drawFastHLine(x0 + xs, y0 - rr, len, color);
}
if (cornername & 0x4) { // right bottom
drawFastHLine(x0 + xs, y0 + rr, len, color);
drawFastVLine(x0 + rr, y0 + xs, len, color);
}
if (cornername & 0x8) { // left bottom
drawFastVLine(x0 - rr, y0 + xs, len, color);
drawFastHLine(x0 - xe, y0 + rr, len, color);
}
}
xs = xe;
}
inTransaction = lockTransaction;
end_tft_write(); // Does nothing if Sprite class uses this function
}
/***************************************************************************************
** 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, y0 + r, dx, color);
drawFastHLine(x0 - x, y0 - r, dx, 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 = lockTransaction;
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 = lockTransaction;
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 = lockTransaction;
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 = lockTransaction;
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 = lockTransaction;
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 = lockTransaction;
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 = lockTransaction;
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) {
transpose(y0, y1); transpose(x0, x1);
}
if (y1 > y2) {
transpose(y2, y1); transpose(x2, x1);
}
if (y0 > y1) {
transpose(y0, y1); transpose(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) transpose(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) transpose(a, b);
drawFastHLine(a, y, b - a + 1, color);
}
inTransaction = lockTransaction;
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 = lockTransaction;
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 = lockTransaction;
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 = lockTransaction;
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 = lockTransaction;
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
***************************************************************************************/
// Smooth fonts use the background colour for anti-aliasing and by default the
// background is not filled. If bgfill = true, then a smooth font background fill will
// be used.
void TFT_eSPI::setTextColor(uint16_t c, uint16_t b, bool bgfill)
{
textcolor = c;
textbgcolor = b;
_fillbg = bgfill;
}
/***************************************************************************************
** 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: 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)
{
if (_vpDatum) return _xWidth;
return _width;
}
/***************************************************************************************
** Function name: height
** Description: Return the pixel height of display (per current rotation)
***************************************************************************************/
int16_t TFT_eSPI::height(void)
{
if (_vpDatum) return _yHeight;
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 (_vpOoB) return;
#ifdef LOAD_GLCD
//>>>>>>>>>>>>>>>>>>
#ifdef LOAD_GFXFF
if(!gfxFont) { // 'Classic' built-in GLCD font
#endif
//>>>>>>>>>>>>>>>>>>
if (c > 255) return;
int32_t xd = x + _xDatum;
int32_t yd = y + _yDatum;
if ((xd >= _vpW) || // Clip right
( yd >= _vpH) || // Clip bottom
((xd + 6 * size - 1) < _vpX) || // Clip left
((yd + 8 * size - 1) < _vpY)) // Clip top
return;
bool fillbg = (bg != color);
bool clip = xd < _vpX || xd + 6 * textsize >= _vpW || yd < _vpY || yd + 8 * textsize >= _vpH;
if ((size==1) && fillbg && !clip) {
uint8_t column[6];
uint8_t mask = 0x1;
begin_tft_write();
setWindow(xd, yd, xd+5, yd+7);
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 && !fillbg) { // default size
for (int8_t j = 0; j < 8; j++) {
if (line & 0x1) drawPixel(x + i, y + j, color);
line >>= 1;
}
}
else { // big size or clipped
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 = lockTransaction;
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 = lockTransaction;
end_tft_write(); // Does nothing if Sprite class uses this function
}
#endif
#ifdef LOAD_GLCD
#ifdef LOAD_GFXFF
} // End classic vs custom font
#endif
#else
#ifndef LOAD_GFXFF
// Avoid warnings if fonts are disabled
x = x;
y = y;
color = color;
bg = bg;
size = size;
#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
addr_row = 0xFFFF;
addr_col = 0xFFFF;
#if defined (ILI9225_DRIVER)
if (rotation & 0x01) { transpose(x0, y0); transpose(x1, y1); }
SPI_BUSY_CHECK;
DC_C; tft_Write_8(TFT_CASET1);
DC_D; tft_Write_16(x0);
DC_C; tft_Write_8(TFT_CASET2);
DC_D; tft_Write_16(x1);
DC_C; tft_Write_8(TFT_PASET1);
DC_D; tft_Write_16(y0);
DC_C; tft_Write_8(TFT_PASET2);
DC_D; tft_Write_16(y1);
DC_C; tft_Write_8(TFT_RAM_ADDR1);
DC_D; tft_Write_16(x0);
DC_C; tft_Write_8(TFT_RAM_ADDR2);
DC_D; tft_Write_16(y0);
// write to RAM
DC_C; tft_Write_8(TFT_RAMWR);
DC_D;
// Temporary solution is to include the RP2040 code here
#if (defined(ARDUINO_ARCH_RP2040) || defined (ARDUINO_ARCH_MBED)) && !defined(RP2040_PIO_INTERFACE)
// For ILI9225 and RP2040 the slower Arduino SPI transfer calls were used, so need to swap back to 16 bit mode
while (spi_get_hw(SPI_X)->sr & SPI_SSPSR_BSY_BITS) {};
hw_write_masked(&spi_get_hw(SPI_X)->cr0, (16 - 1) << SPI_SSPCR0_DSS_LSB, SPI_SSPCR0_DSS_BITS);
#endif
#elif defined (SSD1351_DRIVER)
if (rotation & 1) {
transpose(x0, y0);
transpose(x1, y1);
}
SPI_BUSY_CHECK;
DC_C; tft_Write_8(TFT_CASET);
DC_D; tft_Write_16(x1 | (x0 << 8));
DC_C; tft_Write_8(TFT_PASET);
DC_D; tft_Write_16(y1 | (y0 << 8));
DC_C; tft_Write_8(TFT_RAMWR);
DC_D;
#else
#if defined (SSD1963_DRIVER)
if ((rotation & 0x1) == 0) { transpose(x0, y0); transpose(x1, y1); }
#endif
#ifdef CGRAM_OFFSET
x0+=colstart;
x1+=colstart;
y0+=rowstart;
y1+=rowstart;
#endif
// Temporary solution is to include the RP2040 optimised code here
#if (defined(ARDUINO_ARCH_RP2040) || defined (ARDUINO_ARCH_MBED))
#if !defined(RP2040_PIO_INTERFACE)
// Use hardware SPI port, this code does not swap from 8 to 16 bit
// to avoid the spi_set_format() call overhead
while (spi_get_hw(SPI_X)->sr & SPI_SSPSR_BSY_BITS) {};
DC_C;
#if !defined (SPI_18BIT_DRIVER)
#if defined (RPI_DISPLAY_TYPE) // RPi TFT type always needs 16 bit transfers
hw_write_masked(&spi_get_hw(SPI_X)->cr0, (16 - 1) << SPI_SSPCR0_DSS_LSB, SPI_SSPCR0_DSS_BITS);
#else
hw_write_masked(&spi_get_hw(SPI_X)->cr0, (8 - 1) << SPI_SSPCR0_DSS_LSB, SPI_SSPCR0_DSS_BITS);
#endif
#endif
spi_get_hw(SPI_X)->dr = (uint32_t)TFT_CASET;
while (spi_get_hw(SPI_X)->sr & SPI_SSPSR_BSY_BITS) {};
DC_D;
spi_get_hw(SPI_X)->dr = (uint32_t)x0>>8;
spi_get_hw(SPI_X)->dr = (uint32_t)x0;
spi_get_hw(SPI_X)->dr = (uint32_t)x1>>8;
spi_get_hw(SPI_X)->dr = (uint32_t)x1;
while (spi_get_hw(SPI_X)->sr & SPI_SSPSR_BSY_BITS) {};
DC_C;
spi_get_hw(SPI_X)->dr = (uint32_t)TFT_PASET;
while (spi_get_hw(SPI_X)->sr & SPI_SSPSR_BSY_BITS) {};
DC_D;
spi_get_hw(SPI_X)->dr = (uint32_t)y0>>8;
spi_get_hw(SPI_X)->dr = (uint32_t)y0;
spi_get_hw(SPI_X)->dr = (uint32_t)y1>>8;
spi_get_hw(SPI_X)->dr = (uint32_t)y1;
while (spi_get_hw(SPI_X)->sr & SPI_SSPSR_BSY_BITS) {};
DC_C;
spi_get_hw(SPI_X)->dr = (uint32_t)TFT_RAMWR;
while (spi_get_hw(SPI_X)->sr & SPI_SSPSR_BSY_BITS) {};
#if !defined (SPI_18BIT_DRIVER)
hw_write_masked(&spi_get_hw(SPI_X)->cr0, (16 - 1) << SPI_SSPCR0_DSS_LSB, SPI_SSPCR0_DSS_BITS);
#endif
DC_D;
#elif defined (RM68120_DRIVER)
DC_C; tft_Write_16(TFT_CASET+0); DC_D; tft_Write_16(x0 >> 8);
DC_C; tft_Write_16(TFT_CASET+1); DC_D; tft_Write_16(x0 & 0xFF);
DC_C; tft_Write_16(TFT_CASET+2); DC_D; tft_Write_16(x1 >> 8);
DC_C; tft_Write_16(TFT_CASET+3); DC_D; tft_Write_16(x1 & 0xFF);
DC_C; tft_Write_16(TFT_PASET+0); DC_D; tft_Write_16(y0 >> 8);
DC_C; tft_Write_16(TFT_PASET+1); DC_D; tft_Write_16(y0 & 0xFF);
DC_C; tft_Write_16(TFT_PASET+2); DC_D; tft_Write_16(y1 >> 8);
DC_C; tft_Write_16(TFT_PASET+3); DC_D; tft_Write_16(y1 & 0xFF);
DC_C; tft_Write_16(TFT_RAMWR);
DC_D;
#else
// This is for the RP2040 and PIO interface (SPI or parallel)
WAIT_FOR_STALL;
tft_pio->sm[pio_sm].instr = pio_instr_addr;
TX_FIFO = TFT_CASET;
TX_FIFO = (x0<<16) | x1;
TX_FIFO = TFT_PASET;
TX_FIFO = (y0<<16) | y1;
TX_FIFO = TFT_RAMWR;
#endif
#else
SPI_BUSY_CHECK;
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);
DC_C; tft_Write_8(TFT_RAMWR);
DC_D;
#endif // RP2040 SPI
#endif
//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;
#if defined (SSD1963_DRIVER)
if ((rotation & 0x1) == 0) { transpose(xs, ys); transpose(xe, ye); }
#endif
#ifdef CGRAM_OFFSET
xs += colstart;
xe += colstart;
ys += rowstart;
ye += rowstart;
#endif
// Temporary solution is to include the RP2040 optimised code here
#if (defined(ARDUINO_ARCH_RP2040) || defined (ARDUINO_ARCH_MBED)) && !defined(RP2040_PIO_INTERFACE)
// Use hardware SPI port, this code does not swap from 8 to 16 bit
// to avoid the spi_set_format() call overhead
while (spi_get_hw(SPI_X)->sr & SPI_SSPSR_BSY_BITS) {};
DC_C;
hw_write_masked(&spi_get_hw(SPI_X)->cr0, (8 - 1) << SPI_SSPCR0_DSS_LSB, SPI_SSPCR0_DSS_BITS);
spi_get_hw(SPI_X)->dr = (uint32_t)TFT_CASET;
while (spi_get_hw(SPI_X)->sr & SPI_SSPSR_BSY_BITS) {};
DC_D;
spi_get_hw(SPI_X)->dr = (uint32_t)xs>>8;
spi_get_hw(SPI_X)->dr = (uint32_t)xs;
spi_get_hw(SPI_X)->dr = (uint32_t)xe>>8;
spi_get_hw(SPI_X)->dr = (uint32_t)xe;
while (spi_get_hw(SPI_X)->sr & SPI_SSPSR_BSY_BITS) {};
DC_C;
spi_get_hw(SPI_X)->dr = (uint32_t)TFT_PASET;
while (spi_get_hw(SPI_X)->sr & SPI_SSPSR_BSY_BITS) {};
DC_D;
spi_get_hw(SPI_X)->dr = (uint32_t)ys>>8;
spi_get_hw(SPI_X)->dr = (uint32_t)ys;
spi_get_hw(SPI_X)->dr = (uint32_t)ye>>8;
spi_get_hw(SPI_X)->dr = (uint32_t)ye;
while (spi_get_hw(SPI_X)->sr & SPI_SSPSR_BSY_BITS) {};
DC_C;
spi_get_hw(SPI_X)->dr = (uint32_t)TFT_RAMRD;
while (spi_get_hw(SPI_X)->sr & SPI_SSPSR_BSY_BITS) {};
DC_D;
// Flush the rx buffer and reset overflow flag
while (spi_is_readable(SPI_X)) (void)spi_get_hw(SPI_X)->dr;
spi_get_hw(SPI_X)->icr = SPI_SSPICR_RORIC_BITS;
#else
// 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;
#endif // RP2040 SPI
//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)
{
if (_vpOoB) return;
x+= _xDatum;
y+= _yDatum;
// Range checking
if ((x < _vpX) || (y < _vpY) ||(x >= _vpW) || (y >= _vpH)) return;
#ifdef CGRAM_OFFSET
x+=colstart;
y+=rowstart;
#endif
#if (defined (MULTI_TFT_SUPPORT) || defined (GC9A01_DRIVER)) && !defined (ILI9225_DRIVER)
addr_row = 0xFFFF;
addr_col = 0xFFFF;
#endif
begin_tft_write();
#if defined (ILI9225_DRIVER)
if (rotation & 0x01) { transpose(x, y); }
SPI_BUSY_CHECK;
// Set window to full screen to optimise sequential pixel rendering
if (addr_row != 0x9225) {
addr_row = 0x9225; // addr_row used for flag
DC_C; tft_Write_8(TFT_CASET1);
DC_D; tft_Write_16(0);
DC_C; tft_Write_8(TFT_CASET2);
DC_D; tft_Write_16(175);
DC_C; tft_Write_8(TFT_PASET1);
DC_D; tft_Write_16(0);
DC_C; tft_Write_8(TFT_PASET2);
DC_D; tft_Write_16(219);
}
// Define pixel coordinate
DC_C; tft_Write_8(TFT_RAM_ADDR1);
DC_D; tft_Write_16(x);
DC_C; tft_Write_8(TFT_RAM_ADDR2);
DC_D; tft_Write_16(y);
// write to RAM
DC_C; tft_Write_8(TFT_RAMWR);
#if defined(TFT_PARALLEL_8_BIT) || defined(TFT_PARALLEL_16_BIT) || !defined(ESP32)
DC_D; tft_Write_16(color);
#else
DC_D; tft_Write_16N(color);
#endif
// Temporary solution is to include the RP2040 optimised code here
#elif (defined (ARDUINO_ARCH_RP2040) || defined (ARDUINO_ARCH_MBED)) && !defined (SSD1351_DRIVER)
#if defined (SSD1963_DRIVER)
if ((rotation & 0x1) == 0) { transpose(x, y); }
#endif
#if !defined(RP2040_PIO_INTERFACE)
while (spi_get_hw(SPI_X)->sr & SPI_SSPSR_BSY_BITS) {};
#if defined (RPI_DISPLAY_TYPE) // RPi TFT type always needs 16 bit transfers
hw_write_masked(&spi_get_hw(SPI_X)->cr0, (16 - 1) << SPI_SSPCR0_DSS_LSB, SPI_SSPCR0_DSS_BITS);
#else
hw_write_masked(&spi_get_hw(SPI_X)->cr0, (8 - 1) << SPI_SSPCR0_DSS_LSB, SPI_SSPCR0_DSS_BITS);
#endif
if (addr_col != x) {
DC_C;
spi_get_hw(SPI_X)->dr = (uint32_t)TFT_CASET;
while (spi_get_hw(SPI_X)->sr & SPI_SSPSR_BSY_BITS){};
DC_D;
spi_get_hw(SPI_X)->dr = (uint32_t)x>>8;
spi_get_hw(SPI_X)->dr = (uint32_t)x;
spi_get_hw(SPI_X)->dr = (uint32_t)x>>8;
spi_get_hw(SPI_X)->dr = (uint32_t)x;
addr_col = x;
while (spi_get_hw(SPI_X)->sr & SPI_SSPSR_BSY_BITS) {};
}
if (addr_row != y) {
DC_C;
spi_get_hw(SPI_X)->dr = (uint32_t)TFT_PASET;
while (spi_get_hw(SPI_X)->sr & SPI_SSPSR_BSY_BITS) {};
DC_D;
spi_get_hw(SPI_X)->dr = (uint32_t)y>>8;
spi_get_hw(SPI_X)->dr = (uint32_t)y;
spi_get_hw(SPI_X)->dr = (uint32_t)y>>8;
spi_get_hw(SPI_X)->dr = (uint32_t)y;
addr_row = y;
while (spi_get_hw(SPI_X)->sr & SPI_SSPSR_BSY_BITS) {};
}
DC_C;
spi_get_hw(SPI_X)->dr = (uint32_t)TFT_RAMWR;
#if defined (SPI_18BIT_DRIVER) // SPI 18 bit colour
uint8_t r = (color & 0xF800)>>8;
uint8_t g = (color & 0x07E0)>>3;
uint8_t b = (color & 0x001F)<<3;
while (spi_get_hw(SPI_X)->sr & SPI_SSPSR_BSY_BITS) {};
DC_D;
tft_Write_8N(r); tft_Write_8N(g); tft_Write_8N(b);
#else
while (spi_get_hw(SPI_X)->sr & SPI_SSPSR_BSY_BITS) {};
DC_D;
#if defined (RPI_DISPLAY_TYPE) // RPi TFT type always needs 16 bit transfers
spi_get_hw(SPI_X)->dr = (uint32_t)color;
#else
spi_get_hw(SPI_X)->dr = (uint32_t)color>>8;
spi_get_hw(SPI_X)->dr = (uint32_t)color;
#endif
#endif
while (spi_get_hw(SPI_X)->sr & SPI_SSPSR_BSY_BITS) {};
#elif defined (RM68120_DRIVER)
if (addr_col != x) {
DC_C; tft_Write_16(TFT_CASET+0); DC_D; tft_Write_16(x >> 8);
DC_C; tft_Write_16(TFT_CASET+1); DC_D; tft_Write_16(x & 0xFF);
DC_C; tft_Write_16(TFT_CASET+2); DC_D; tft_Write_16(x >> 8);
DC_C; tft_Write_16(TFT_CASET+3); DC_D; tft_Write_16(x & 0xFF);
addr_col = x;
}
if (addr_row != y) {
DC_C; tft_Write_16(TFT_PASET+0); DC_D; tft_Write_16(y >> 8);
DC_C; tft_Write_16(TFT_PASET+1); DC_D; tft_Write_16(y & 0xFF);
DC_C; tft_Write_16(TFT_PASET+2); DC_D; tft_Write_16(y >> 8);
DC_C; tft_Write_16(TFT_PASET+3); DC_D; tft_Write_16(y & 0xFF);
addr_row = y;
}
DC_C; tft_Write_16(TFT_RAMWR); DC_D;
TX_FIFO = color;
#else
// This is for the RP2040 and PIO interface (SPI or parallel)
WAIT_FOR_STALL;
tft_pio->sm[pio_sm].instr = pio_instr_addr;
TX_FIFO = TFT_CASET;
TX_FIFO = (x<<16) | x;
TX_FIFO = TFT_PASET;
TX_FIFO = (y<<16) | y;
TX_FIFO = TFT_RAMWR;
//DC set high by PIO
#if defined (SPI_18BIT_DRIVER) || (defined (SSD1963_DRIVER) && defined (TFT_PARALLEL_8_BIT))
TX_FIFO = ((color & 0xF800)<<8) | ((color & 0x07E0)<<5) | ((color & 0x001F)<<3);
#else
TX_FIFO = color;
#endif
#endif
#else
#if defined (SSD1963_DRIVER)
if ((rotation & 0x1) == 0) { transpose(x, y); }
#endif
SPI_BUSY_CHECK;
#if defined (SSD1351_DRIVER)
if (rotation & 0x1) { transpose(x, y); }
// No need to send x if it has not changed (speeds things up)
if (addr_col != x) {
DC_C; tft_Write_8(TFT_CASET);
DC_D; tft_Write_16(x | (x << 8));
addr_col = x;
}
// No need to send y if it has not changed (speeds things up)
if (addr_row != y) {
DC_C; tft_Write_8(TFT_PASET);
DC_D; tft_Write_16(y | (y << 8));
addr_row = y;
}
#else
// No need to send x if it has not changed (speeds things up)
if (addr_col != x) {
DC_C; tft_Write_8(TFT_CASET);
DC_D; tft_Write_32D(x);
addr_col = x;
}
// No need to send y if it has not changed (speeds things up)
if (addr_row != y) {
DC_C; tft_Write_8(TFT_PASET);
DC_D; tft_Write_32D(y);
addr_row = y;
}
#endif
DC_C; tft_Write_8(TFT_RAMWR);
#if defined(TFT_PARALLEL_8_BIT) || defined(TFT_PARALLEL_16_BIT) || !defined(ESP32)
DC_D; tft_Write_16(color);
#else
DC_D; tft_Write_16N(color);
#endif
#endif
end_tft_write();
}
/***************************************************************************************
** Function name: pushColor
** Description: push a single pixel
***************************************************************************************/
void TFT_eSPI::pushColor(uint16_t color)
{
begin_tft_write();
SPI_BUSY_CHECK;
tft_Write_16N(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();
lockTransaction = true; // Lock transaction for all sequentially run sketch functions
inTransaction = true;
}
/***************************************************************************************
** Function name: endWrite
** Description: end transaction with CS high
***************************************************************************************/
void TFT_eSPI::endWrite(void)
{
lockTransaction = false; // Release sketch induced transaction lock
inTransaction = false;
DMA_BUSY_CHECK; // Safety check - user code should have checked this!
end_tft_write(); // Release SPI bus
}
/***************************************************************************************
** 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)
{
if (_vpOoB) return;
//begin_tft_write(); // Sprite class can use this function, avoiding begin_tft_write()
inTransaction = true;
//x+= _xDatum; // Not added here, added by drawPixel & drawFastXLine
//y+= _yDatum;
bool steep = abs(y1 - y0) > abs(x1 - x0);
if (steep) {
transpose(x0, y0);
transpose(x1, y1);
}
if (x0 > x1) {
transpose(x0, x1);
transpose(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) {
if (dlen == 1) drawPixel(y0, xs, color);
else drawFastVLine(y0, xs, dlen, color);
dlen = 0;
y0 += ystep; xs = x0 + 1;
err += dx;
}
}
if (dlen) drawFastVLine(y0, xs, dlen, color);
}
else
{
for (; x0 <= x1; x0++) {
dlen++;
err -= dy;
if (err < 0) {
if (dlen == 1) drawPixel(xs, y0, color);
else drawFastHLine(xs, y0, dlen, color);
dlen = 0;
y0 += ystep; xs = x0 + 1;
err += dx;
}
}
if (dlen) drawFastHLine(xs, y0, dlen, color);
}
inTransaction = lockTransaction;
end_tft_write();
}
/***************************************************************************************
** Description: Constants for anti-aliased line drawing on TFT and in Sprites
***************************************************************************************/
constexpr float PixelAlphaGain = 255.0;
constexpr float LoAlphaTheshold = 1.0/32.0;
constexpr float HiAlphaTheshold = 1.0 - LoAlphaTheshold;
constexpr float deg2rad = 3.14159265359/180.0;
/***************************************************************************************
** Function name: drawPixel (alpha blended)
** Description: Draw a pixel blended with the screen or bg pixel colour
***************************************************************************************/
uint16_t TFT_eSPI::drawPixel(int32_t x, int32_t y, uint32_t color, uint8_t alpha, uint32_t bg_color)
{
if (bg_color == 0x00FFFFFF) bg_color = readPixel(x, y);
color = alphaBlend(alpha, color, bg_color);
drawPixel(x, y, color);
return color;
}
/***************************************************************************************
** Function name: drawSmoothArc
** Description: Draw a smooth arc clockwise from 6 o'clock
***************************************************************************************/
void TFT_eSPI::drawSmoothArc(int32_t x, int32_t y, int32_t r, int32_t ir, uint32_t startAngle, uint32_t endAngle, uint32_t fg_color, uint32_t bg_color, bool roundEnds)
// Centre at x,y
// r = arc outer radius, ir = arc inner radius. Inclusive so arc thickness = r - ir + 1
// Angles in range 0-360
// Arc foreground colour anti-aliased with background colour at edges
// anti-aliased roundEnd is optional, default is anti-aliased straight end
// Note: rounded ends extend the arc angle so can overlap, user sketch to manage this.
{
inTransaction = true;
if (endAngle != startAngle && (startAngle != 0 || endAngle != 360))
{
float sx = -sinf(startAngle * deg2rad);
float sy = +cosf(startAngle * deg2rad);
float ex = -sinf( endAngle * deg2rad);
float ey = +cosf( endAngle * deg2rad);
if (roundEnds)
{ // Round ends
sx = sx * (r + ir)/2.0 + x;
sy = sy * (r + ir)/2.0 + y;
drawSpot(sx, sy, (r - ir)/2.0, fg_color, bg_color);
ex = ex * (r + ir)/2.0 + x;
ey = ey * (r + ir)/2.0 + y;
drawSpot(ex, ey, (r - ir)/2.0, fg_color, bg_color);
}
else
{ // Square ends
float asx = sx * ir + x;
float asy = sy * ir + y;
float aex = sx * r + x;
float aey = sy * r + y;
drawWedgeLine(asx, asy, aex, aey, 0.3, 0.3, fg_color, bg_color);
asx = ex * ir + x;
asy = ey * ir + y;
aex = ex * r + x;
aey = ey * r + y;
drawWedgeLine(asx, asy, aex, aey, 0.3, 0.3, fg_color, bg_color);
}
// Draw arc
drawArc(x, y, r, ir, startAngle, endAngle, fg_color, bg_color);
}
else // Draw full 360
{
drawArc(x, y, r, ir, 0, 360, fg_color, bg_color);
}
inTransaction = lockTransaction;
end_tft_write();
}
/***************************************************************************************
** Function name: sqrt_fraction (private function)
** Description: Smooth graphics support function for alpha derivation
***************************************************************************************/
// Compute the fixed point square root of an integer and
// return the 8 MS bits of fractional part.
// Quicker than sqrt() for processors that do not have an FPU (e.g. RP2040)
inline uint8_t TFT_eSPI::sqrt_fraction(uint32_t num) {
if (num > (0x40000000)) return 0;
uint32_t bsh = 0x00004000;
uint32_t fpr = 0;
uint32_t osh = 0;
// Auto adjust from U8:8 up to U15:16
while (num>bsh) {bsh <<= 2; osh++;}
do {
uint32_t bod = bsh + fpr;
if(num >= bod)
{
num -= bod;
fpr = bsh + bod;
}
num <<= 1;
} while(bsh >>= 1);
return fpr>>osh;
}
/***************************************************************************************
** Function name: drawArc
** Description: Draw an arc clockwise from 6 o'clock position
***************************************************************************************/
// Centre at x,y
// r = arc outer radius, ir = arc inner radius. Inclusive, so arc thickness = r-ir+1
// Angles MUST be in range 0-360
// Arc foreground fg_color anti-aliased with background colour along sides
// smooth is optional, default is true, smooth=false means no antialiasing
// Note: Arc ends are not anti-aliased (use drawSmoothArc instead for that)
void TFT_eSPI::drawArc(int32_t x, int32_t y, int32_t r, int32_t ir,
uint32_t startAngle, uint32_t endAngle,
uint32_t fg_color, uint32_t bg_color,
bool smooth)
{
if (endAngle > 360) endAngle = 360;
if (startAngle > 360) startAngle = 360;
if (_vpOoB || startAngle == endAngle) return;
if (r < ir) transpose(r, ir); // Required that r > ir
if (r <= 0 || ir < 0) return; // Invalid r, ir can be zero (circle sector)
if (endAngle < startAngle) {
// Arc sweeps through 6 o'clock so draw in two parts
if (startAngle < 360) drawArc(x, y, r, ir, startAngle, 360, fg_color, bg_color, smooth);
if (endAngle == 0) return;
startAngle = 0;
}
inTransaction = true;
int32_t xs = 0; // x start position for quadrant scan
uint8_t alpha = 0; // alpha value for blending pixels
uint32_t r2 = r * r; // Outer arc radius^2
if (smooth) r++; // Outer AA zone radius
uint32_t r1 = r * r; // Outer AA radius^2
int16_t w = r - ir; // Width of arc (r - ir + 1)
uint32_t r3 = ir * ir; // Inner arc radius^2
if (smooth) ir--; // Inner AA zone radius
uint32_t r4 = ir * ir; // Inner AA radius^2
// 1 | 2
// ---¦--- Arc quadrant index
// 0 | 3
// Fixed point U16.16 slope table for arc start/end in each quadrant
uint32_t startSlope[4] = {0, 0, 0xFFFFFFFF, 0};
uint32_t endSlope[4] = {0, 0xFFFFFFFF, 0, 0};
// Ensure maximum U16.16 slope of arc ends is ~ 0x8000 0000
constexpr float minDivisor = 1.0f/0x8000;
// Fill in start slope table and empty quadrants
float fabscos = fabsf(cosf(startAngle * deg2rad));
float fabssin = fabsf(sinf(startAngle * deg2rad));
// U16.16 slope of arc start
uint32_t slope = (fabscos/(fabssin + minDivisor)) * (float)(1<<16);
// Update slope table, add slope for arc start
if (startAngle <= 90) {
startSlope[0] = slope;
}
else if (startAngle <= 180) {
startSlope[1] = slope;
}
else if (startAngle <= 270) {
startSlope[1] = 0xFFFFFFFF;
startSlope[2] = slope;
}
else {
startSlope[1] = 0xFFFFFFFF;
startSlope[2] = 0;
startSlope[3] = slope;
}
// Fill in end slope table and empty quadrants
fabscos = fabsf(cosf(endAngle * deg2rad));
fabssin = fabsf(sinf(endAngle * deg2rad));
// U16.16 slope of arc end
slope = (uint32_t)((fabscos/(fabssin + minDivisor)) * (float)(1<<16));
// Work out which quadrants will need to be drawn and add slope for arc end
if (endAngle <= 90) {
endSlope[0] = slope;
endSlope[1] = 0;
startSlope[2] = 0;
}
else if (endAngle <= 180) {
endSlope[1] = slope;
startSlope[2] = 0;
}
else if (endAngle <= 270) {
endSlope[2] = slope;
}
else {
endSlope[3] = slope;
}
// Scan quadrant
for (int32_t cy = r - 1; cy > 0; cy--)
{
uint32_t len[4] = { 0, 0, 0, 0}; // Pixel run length
int32_t xst[4] = {-1, -1, -1, -1}; // Pixel run x start
uint32_t dy2 = (r - cy) * (r - cy);
// Find and track arc zone start point
while ((r - xs) * (r - xs) + dy2 >= r1) xs++;
for (int32_t cx = xs; cx < r; cx++)
{
// Calculate radius^2
uint32_t hyp = (r - cx) * (r - cx) + dy2;
// If in outer zone calculate alpha
if (hyp > r2) {
alpha = ~sqrt_fraction(hyp); // Outer AA zone
}
// If within arc fill zone, get line start and lengths for each quadrant
else if (hyp >= r3) {
// Calculate U16.16 slope
slope = ((r - cy) << 16)/(r - cx);
if (slope <= startSlope[0] && slope >= endSlope[0]) { // slope hi -> lo
xst[0] = cx; // Bottom left line end
len[0]++;
}
if (slope >= startSlope[1] && slope <= endSlope[1]) { // slope lo -> hi
xst[1] = cx; // Top left line end
len[1]++;
}
if (slope <= startSlope[2] && slope >= endSlope[2]) { // slope hi -> lo
xst[2] = cx; // Bottom right line start
len[2]++;
}
if (slope <= endSlope[3] && slope >= startSlope[3]) { // slope lo -> hi
xst[3] = cx; // Top right line start
len[3]++;
}
continue; // Next x
}
else {
if (hyp <= r4) break; // Skip inner pixels
alpha = sqrt_fraction(hyp); // Inner AA zone
}
if (alpha < 16) continue; // Skip low alpha pixels
// If background is read it must be done in each quadrant
uint16_t pcol = alphaBlend(alpha, fg_color, bg_color);
// Check if an AA pixels need to be drawn
slope = ((r - cy)<<16)/(r - cx);
if (slope <= startSlope[0] && slope >= endSlope[0]) // BL
drawPixel(x + cx - r, y - cy + r, pcol);
if (slope >= startSlope[1] && slope <= endSlope[1]) // TL
drawPixel(x + cx - r, y + cy - r, pcol);
if (slope <= startSlope[2] && slope >= endSlope[2]) // TR
drawPixel(x - cx + r, y + cy - r, pcol);
if (slope <= endSlope[3] && slope >= startSlope[3]) // BR
drawPixel(x - cx + r, y - cy + r, pcol);
}
// Add line in inner zone
if (len[0]) drawFastHLine(x + xst[0] - len[0] + 1 - r, y - cy + r, len[0], fg_color); // BL
if (len[1]) drawFastHLine(x + xst[1] - len[1] + 1 - r, y + cy - r, len[1], fg_color); // TL
if (len[2]) drawFastHLine(x - xst[2] + r, y + cy - r, len[2], fg_color); // TR
if (len[3]) drawFastHLine(x - xst[3] + r, y - cy + r, len[3], fg_color); // BR
}
// Fill in centre lines
if (startAngle == 0 || endAngle == 360) drawFastVLine(x, y + r - w, w, fg_color); // Bottom
if (startAngle <= 90 && endAngle >= 90) drawFastHLine(x - r + 1, y, w, fg_color); // Left
if (startAngle <= 180 && endAngle >= 180) drawFastVLine(x, y - r + 1, w, fg_color); // Top
if (startAngle <= 270 && endAngle >= 270) drawFastHLine(x + r - w, y, w, fg_color); // Right
inTransaction = lockTransaction;
end_tft_write();
}
/***************************************************************************************
** Function name: drawSmoothCircle
** Description: Draw a smooth circle
***************************************************************************************/
// To have effective anti-aliasing the circle will be 3 pixels thick
void TFT_eSPI::drawSmoothCircle(int32_t x, int32_t y, int32_t r, uint32_t fg_color, uint32_t bg_color)
{
drawSmoothRoundRect(x-r, y-r, r, r-1, 0, 0, fg_color, bg_color);
}
/***************************************************************************************
** Function name: fillSmoothCircle
** Description: Draw a filled anti-aliased circle
***************************************************************************************/
void TFT_eSPI::fillSmoothCircle(int32_t x, int32_t y, int32_t r, uint32_t color, uint32_t bg_color)
{
if (r <= 0) return;
inTransaction = true;
drawFastHLine(x - r, y, 2 * r + 1, color);
int32_t xs = 1;
int32_t cx = 0;
int32_t r1 = r * r;
r++;
int32_t r2 = r * r;
for (int32_t cy = r - 1; cy > 0; cy--)
{
int32_t dy2 = (r - cy) * (r - cy);
for (cx = xs; cx < r; cx++)
{
int32_t hyp2 = (r - cx) * (r - cx) + dy2;
if (hyp2 <= r1) break;
if (hyp2 >= r2) continue;
uint8_t alpha = ~sqrt_fraction(hyp2);
if (alpha > 246) break;
xs = cx;
if (alpha < 9) continue;
if (bg_color == 0x00FFFFFF) {
drawPixel(x + cx - r, y + cy - r, color, alpha, bg_color);
drawPixel(x - cx + r, y + cy - r, color, alpha, bg_color);
drawPixel(x - cx + r, y - cy + r, color, alpha, bg_color);
drawPixel(x + cx - r, y - cy + r, color, alpha, bg_color);
}
else {
uint16_t pcol = drawPixel(x + cx - r, y + cy - r, color, alpha, bg_color);
drawPixel(x - cx + r, y + cy - r, pcol);
drawPixel(x - cx + r, y - cy + r, pcol);
drawPixel(x + cx - r, y - cy + r, pcol);
}
}
drawFastHLine(x + cx - r, y + cy - r, 2 * (r - cx) + 1, color);
drawFastHLine(x + cx - r, y - cy + r, 2 * (r - cx) + 1, color);
}
inTransaction = lockTransaction;
end_tft_write();
}
/***************************************************************************************
** Function name: drawSmoothRoundRect
** Description: Draw a rounded rectangle
***************************************************************************************/
// x,y is top left corner of bounding box for a complete rounded rectangle
// r = arc outer corner radius, ir = arc inner radius. Arc thickness = r-ir+1
// w and h are width and height of the bounding rectangle
// If w and h are < radius (e.g. 0,0) a circle will be drawn with centre at x+r,y+r
// Arc foreground fg_color anti-aliased with background colour at edges
// A subset of corners can be drawn by specifying a quadrants mask. A bit set in the
// mask means draw that quadrant (all are drawn if parameter missing):
// 0x1 | 0x2
// ---¦--- Arc quadrant mask select bits (as in drawCircleHelper fn)
// 0x8 | 0x4
void TFT_eSPI::drawSmoothRoundRect(int32_t x, int32_t y, int32_t r, int32_t ir, int32_t w, int32_t h, uint32_t fg_color, uint32_t bg_color, uint8_t quadrants)
{
if (_vpOoB) return;
if (r < ir) transpose(r, ir); // Required that r > ir
if (r <= 0 || ir < 0) return; // Invalid
w -= 2*r;
h -= 2*r;
if (w < 0) w = 0;
if (h < 0) h = 0;
inTransaction = true;
x += r;
y += r;
uint16_t t = r - ir + 1;
int32_t xs = 0;
int32_t cx = 0;
int32_t r2 = r * r; // Outer arc radius^2
r++;
int32_t r1 = r * r; // Outer AA zone radius^2
int32_t r3 = ir * ir; // Inner arc radius^2
ir--;
int32_t r4 = ir * ir; // Inner AA zone radius^2
uint8_t alpha = 0;
// Scan top left quadrant x y r ir fg_color bg_color
for (int32_t cy = r - 1; cy > 0; cy--)
{
int32_t len = 0; // Pixel run length
int32_t lxst = 0; // Left side run x start
int32_t rxst = 0; // Right side run x start
int32_t dy2 = (r - cy) * (r - cy);
// Find and track arc zone start point
while ((r - xs) * (r - xs) + dy2 >= r1) xs++;
for (cx = xs; cx < r; cx++)
{
// Calculate radius^2
int32_t hyp = (r - cx) * (r - cx) + dy2;
// If in outer zone calculate alpha
if (hyp > r2) {
alpha = ~sqrt_fraction(hyp); // Outer AA zone
}
// If within arc fill zone, get line lengths for each quadrant
else if (hyp >= r3) {
rxst = cx; // Right side start
len++; // Line segment length
continue; // Next x
}
else {
if (hyp <= r4) break; // Skip inner pixels
alpha = sqrt_fraction(hyp); // Inner AA zone
}
if (alpha < 16) continue; // Skip low alpha pixels
// If background is read it must be done in each quadrant - TODO
uint16_t pcol = alphaBlend(alpha, fg_color, bg_color);
if (quadrants & 0x8) drawPixel(x + cx - r, y - cy + r + h, pcol); // BL
if (quadrants & 0x1) drawPixel(x + cx - r, y + cy - r, pcol); // TL
if (quadrants & 0x2) drawPixel(x - cx + r + w, y + cy - r, pcol); // TR
if (quadrants & 0x4) drawPixel(x - cx + r + w, y - cy + r + h, pcol); // BR
}
// Fill arc inner zone in each quadrant
lxst = rxst - len + 1; // Calculate line segment start for left side
if (quadrants & 0x8) drawFastHLine(x + lxst - r, y - cy + r + h, len, fg_color); // BL
if (quadrants & 0x1) drawFastHLine(x + lxst - r, y + cy - r, len, fg_color); // TL
if (quadrants & 0x2) drawFastHLine(x - rxst + r + w, y + cy - r, len, fg_color); // TR
if (quadrants & 0x4) drawFastHLine(x - rxst + r + w, y - cy + r + h, len, fg_color); // BR
}
// Draw sides
if ((quadrants & 0xC) == 0xC) fillRect(x, y + r - t + h, w + 1, t, fg_color); // Bottom
if ((quadrants & 0x9) == 0x9) fillRect(x - r + 1, y, t, h + 1, fg_color); // Left
if ((quadrants & 0x3) == 0x3) fillRect(x, y - r + 1, w + 1, t, fg_color); // Top
if ((quadrants & 0x6) == 0x6) fillRect(x + r - t + w, y, t, h + 1, fg_color); // Right
inTransaction = lockTransaction;
end_tft_write();
}
/***************************************************************************************
** Function name: fillSmoothRoundRect
** Description: Draw a filled anti-aliased rounded corner rectangle
***************************************************************************************/
void TFT_eSPI::fillSmoothRoundRect(int32_t x, int32_t y, int32_t w, int32_t h, int32_t r, uint32_t color, uint32_t bg_color)
{
inTransaction = true;
int32_t xs = 0;
int32_t cx = 0;
// Limit radius to half width or height
if (r < 0) r = 0;
if (r > w/2) r = w/2;
if (r > h/2) r = h/2;
y += r;
h -= 2*r;
fillRect(x, y, w, h, color);
h--;
x += r;
w -= 2*r+1;
int32_t r1 = r * r;
r++;
int32_t r2 = r * r;
for (int32_t cy = r - 1; cy > 0; cy--)
{
int32_t dy2 = (r - cy) * (r - cy);
for (cx = xs; cx < r; cx++)
{
int32_t hyp2 = (r - cx) * (r - cx) + dy2;
if (hyp2 <= r1) break;
if (hyp2 >= r2) continue;
uint8_t alpha = ~sqrt_fraction(hyp2);
if (alpha > 246) break;
xs = cx;
if (alpha < 9) continue;
drawPixel(x + cx - r, y + cy - r, color, alpha, bg_color);
drawPixel(x - cx + r + w, y + cy - r, color, alpha, bg_color);
drawPixel(x - cx + r + w, y - cy + r + h, color, alpha, bg_color);
drawPixel(x + cx - r, y - cy + r + h, color, alpha, bg_color);
}
drawFastHLine(x + cx - r, y + cy - r, 2 * (r - cx) + 1 + w, color);
drawFastHLine(x + cx - r, y - cy + r + h, 2 * (r - cx) + 1 + w, color);
}
inTransaction = lockTransaction;
end_tft_write();
}
/***************************************************************************************
** Function name: drawSpot - maths intensive, so for small filled circles
** Description: Draw an anti-aliased filled circle at ax,ay with radius r
***************************************************************************************/
// Coordinates are floating point to achieve sub-pixel positioning
void TFT_eSPI::drawSpot(float ax, float ay, float r, uint32_t fg_color, uint32_t bg_color)
{
// Filled circle can be created by the wide line function with zero line length
drawWedgeLine( ax, ay, ax, ay, r, r, fg_color, bg_color);
}
/***************************************************************************************
** Function name: drawWideLine - background colour specified or pixel read
** Description: draw an anti-aliased line with rounded ends, width wd
***************************************************************************************/
void TFT_eSPI::drawWideLine(float ax, float ay, float bx, float by, float wd, uint32_t fg_color, uint32_t bg_color)
{
drawWedgeLine( ax, ay, bx, by, wd/2.0, wd/2.0, fg_color, bg_color);
}
/***************************************************************************************
** Function name: drawWedgeLine - background colour specified or pixel read
** Description: draw an anti-aliased line with different width radiused ends
***************************************************************************************/
void TFT_eSPI::drawWedgeLine(float ax, float ay, float bx, float by, float ar, float br, uint32_t fg_color, uint32_t bg_color)
{
if ( (ar < 0.0) || (br < 0.0) )return;
if ( (fabsf(ax - bx) < 0.01f) && (fabsf(ay - by) < 0.01f) ) bx += 0.01f; // Avoid divide by zero
// Find line bounding box
int32_t x0 = (int32_t)floorf(fminf(ax-ar, bx-br));
int32_t x1 = (int32_t) ceilf(fmaxf(ax+ar, bx+br));
int32_t y0 = (int32_t)floorf(fminf(ay-ar, by-br));
int32_t y1 = (int32_t) ceilf(fmaxf(ay+ar, by+br));
if (!clipWindow(&x0, &y0, &x1, &y1)) return;
// Establish x start and y start
int32_t ys = ay;
if ((ax-ar)>(bx-br)) ys = by;
float rdt = ar - br; // Radius delta
float alpha = 1.0f;
ar += 0.5;
uint16_t bg = bg_color;
float xpax, ypay, bax = bx - ax, bay = by - ay;
begin_nin_write();
inTransaction = true;
int32_t xs = x0;
// Scan bounding box from ys down, calculate pixel intensity from distance to line
for (int32_t yp = ys; yp <= y1; yp++) {
bool swin = true; // Flag to start new window area
bool endX = false; // Flag to skip pixels
ypay = yp - ay;
for (int32_t xp = xs; xp <= x1; xp++) {
if (endX) if (alpha <= LoAlphaTheshold) break; // Skip right side
xpax = xp - ax;
alpha = ar - wedgeLineDistance(xpax, ypay, bax, bay, rdt);
if (alpha <= LoAlphaTheshold ) continue;
// Track edge to minimise calculations
if (!endX) { endX = true; xs = xp; }
if (alpha > HiAlphaTheshold) {
#ifdef GC9A01_DRIVER
drawPixel(xp, yp, fg_color);
#else
if (swin) { setWindow(xp, yp, x1, yp); swin = false; }
pushColor(fg_color);
#endif
continue;
}
//Blend color with background and plot
if (bg_color == 0x00FFFFFF) {
bg = readPixel(xp, yp); swin = true;
}
#ifdef GC9A01_DRIVER
uint16_t pcol = alphaBlend((uint8_t)(alpha * PixelAlphaGain), fg_color, bg);
drawPixel(xp, yp, pcol);
#else
if (swin) { setWindow(xp, yp, x1, yp); swin = false; }
pushColor(alphaBlend((uint8_t)(alpha * PixelAlphaGain), fg_color, bg));
#endif
}
}
// Reset x start to left side of box
xs = x0;
// Scan bounding box from ys-1 up, calculate pixel intensity from distance to line
for (int32_t yp = ys-1; yp >= y0; yp--) {
bool swin = true; // Flag to start new window area
bool endX = false; // Flag to skip pixels
ypay = yp - ay;
for (int32_t xp = xs; xp <= x1; xp++) {
if (endX) if (alpha <= LoAlphaTheshold) break; // Skip right side of drawn line
xpax = xp - ax;
alpha = ar - wedgeLineDistance(xpax, ypay, bax, bay, rdt);
if (alpha <= LoAlphaTheshold ) continue;
// Track line boundary
if (!endX) { endX = true; xs = xp; }
if (alpha > HiAlphaTheshold) {
#ifdef GC9A01_DRIVER
drawPixel(xp, yp, fg_color);
#else
if (swin) { setWindow(xp, yp, x1, yp); swin = false; }
pushColor(fg_color);
#endif
continue;
}
//Blend colour with background and plot
if (bg_color == 0x00FFFFFF) {
bg = readPixel(xp, yp); swin = true;
}
#ifdef GC9A01_DRIVER
uint16_t pcol = alphaBlend((uint8_t)(alpha * PixelAlphaGain), fg_color, bg);
drawPixel(xp, yp, pcol);
#else
if (swin) { setWindow(xp, yp, x1, yp); swin = false; }
pushColor(alphaBlend((uint8_t)(alpha * PixelAlphaGain), fg_color, bg));
#endif
}
}
inTransaction = lockTransaction;
end_nin_write();
}
/***************************************************************************************
** Function name: lineDistance - private helper function for drawWedgeLine
** Description: returns distance of px,py to closest part of a to b wedge
***************************************************************************************/
inline float TFT_eSPI::wedgeLineDistance(float xpax, float ypay, float bax, float bay, float dr)
{
float h = fmaxf(fminf((xpax * bax + ypay * bay) / (bax * bax + bay * bay), 1.0f), 0.0f);
float dx = xpax - bax * h, dy = ypay - bay * h;
return sqrtf(dx * dx + dy * dy) + h * dr;
}
/***************************************************************************************
** Function name: drawFastVLine
** Description: draw a vertical line
***************************************************************************************/
void TFT_eSPI::drawFastVLine(int32_t x, int32_t y, int32_t h, uint32_t color)
{
if (_vpOoB) return;
x+= _xDatum;
y+= _yDatum;
// Clipping
if ((x < _vpX) || (x >= _vpW) || (y >= _vpH)) return;
if (y < _vpY) { h += y - _vpY; y = _vpY; }
if ((y + h) > _vpH) h = _vpH - 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)
{
if (_vpOoB) return;
x+= _xDatum;
y+= _yDatum;
// Clipping
if ((y < _vpY) || (x >= _vpW) || (y >= _vpH)) return;
if (x < _vpX) { w += x - _vpX; x = _vpX; }
if ((x + w) > _vpW) w = _vpW - 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)
{
if (_vpOoB) return;
x+= _xDatum;
y+= _yDatum;
// Clipping
if ((x >= _vpW) || (y >= _vpH)) return;
if (x < _vpX) { w += x - _vpX; x = _vpX; }
if (y < _vpY) { h += y - _vpY; y = _vpY; }
if ((x + w) > _vpW) w = _vpW - x;
if ((y + h) > _vpH) h = _vpH - y;
if ((w < 1) || (h < 1)) return;
//Serial.print(" _xDatum=");Serial.print( _xDatum);Serial.print(", _yDatum=");Serial.print( _yDatum);
//Serial.print(", _xWidth=");Serial.print(_xWidth);Serial.print(", _yHeight=");Serial.println(_yHeight);
//Serial.print(" _vpX=");Serial.print( _vpX);Serial.print(", _vpY=");Serial.print( _vpY);
//Serial.print(", _vpW=");Serial.print(_vpW);Serial.print(", _vpH=");Serial.println(_vpH);
//Serial.print(" x=");Serial.print( y);Serial.print(", y=");Serial.print( y);
//Serial.print(", w=");Serial.print(w);Serial.print(", h=");Serial.println(h);
begin_tft_write();
setWindow(x, y, x + w - 1, y + h - 1);
pushBlock(color, w * h);
end_tft_write();
}
/***************************************************************************************
** Function name: fillRectVGradient
** Description: draw a filled rectangle with a vertical colour gradient
***************************************************************************************/
void TFT_eSPI::fillRectVGradient(int16_t x, int16_t y, int16_t w, int16_t h, uint32_t color1, uint32_t color2)
{
if (_vpOoB) return;
x+= _xDatum;
y+= _yDatum;
// Clipping
if ((x >= _vpW) || (y >= _vpH)) return;
if (x < _vpX) { w += x - _vpX; x = _vpX; }
if (y < _vpY) { h += y - _vpY; y = _vpY; }
if ((x + w) > _vpW) w = _vpW - x;
if ((y + h) > _vpH) h = _vpH - y;
if ((w < 1) || (h < 1)) return;
begin_nin_write();
float delta = -255.0/h;
float alpha = 255.0;
uint32_t color = color1;
while (h--) {
drawFastHLine(x, y++, w, color);
alpha += delta;
color = alphaBlend((uint8_t)alpha, color1, color2);
}
end_nin_write();
}
/***************************************************************************************
** Function name: fillRectHGradient
** Description: draw a filled rectangle with a horizontal colour gradient
***************************************************************************************/
void TFT_eSPI::fillRectHGradient(int16_t x, int16_t y, int16_t w, int16_t h, uint32_t color1, uint32_t color2)
{
if (_vpOoB) return;
x+= _xDatum;
y+= _yDatum;
// Clipping
if ((x >= _vpW) || (y >= _vpH)) return;
if (x < _vpX) { w += x - _vpX; x = _vpX; }
if (y < _vpY) { h += y - _vpY; y = _vpY; }
if ((x + w) > _vpW) w = _vpW - x;
if ((y + h) > _vpH) h = _vpH - y;
if ((w < 1) || (h < 1)) return;
begin_nin_write();
float delta = -255.0/w;
float alpha = 255.0;
uint32_t color = color1;
while (w--) {
drawFastVLine(x++, y, h, color);
alpha += delta;
color = alphaBlend((uint8_t)alpha, color1, color2);
}
end_nin_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*/
uint16_t TFT_eSPI::decodeUTF8(uint8_t c)
{
if (!_utf8) return c;
// 7 bit Unicode Code Point
if ((c & 0x80) == 0x00) {
decoderState = 0;
return 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 c;
}
else {
if (decoderState == 2) {
decoderBuffer |= ((c & 0x3F)<<6);
decoderState--;
return 0;
}
else {
decoderBuffer |= (c & 0x3F);
decoderState = 0;
return decoderBuffer;
}
}
decoderState = 0;
return 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);
if (!_utf8) return c;
// 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;
return c; // fall-back to extended ASCII
}
/***************************************************************************************
** Function name: alphaBlend
** Description: Blend 16bit foreground and background
*************************************************************************************x*/
inline uint16_t TFT_eSPI::alphaBlend(uint8_t alpha, uint16_t fgc, uint16_t bgc)
{
// Split out and blend 5 bit red and blue channels
uint32_t rxb = bgc & 0xF81F;
rxb += ((fgc & 0xF81F) - rxb) * (alpha >> 2) >> 6;
// Split out and blend 6 bit green channel
uint32_t xgx = bgc & 0x07E0;
xgx += ((fgc & 0x07E0) - xgx) * alpha >> 8;
// Recombine channels
return (rxb & 0xF81F) | (xgx & 0x07E0);
}
/***************************************************************************************
** 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;
}
uint32_t rxx = bgc & 0xFF0000;
rxx += ((fgc & 0xFF0000) - rxx) * alpha >> 8;
uint32_t xgx = bgc & 0x00FF00;
xgx += ((fgc & 0xFF0000) - xgx) * alpha >> 8;
uint32_t xxb = bgc & 0x0000FF;
xxb += ((fgc & 0xFF0000) - xxb) * alpha >> 8;
return (rxx & 0xFF0000) | (xgx & 0x00FF00) | (xxb & 0x0000FF);
}
/***************************************************************************************
** Function name: write
** Description: draw characters piped through serial stream
***************************************************************************************/
/* // Not all processors support buffered write
#ifndef ARDUINO_ARCH_ESP8266 // Avoid ESP8266 board package bug
size_t TFT_eSPI::write(const uint8_t *buf, size_t len)
{
inTransaction = true;
uint8_t *lbuf = (uint8_t *)buf;
while(*lbuf !=0 && len--) write(*lbuf++);
inTransaction = lockTransaction;
end_tft_write();
return 1;
}
#endif
*/
/***************************************************************************************
** Function name: write
** Description: draw characters piped through serial stream
***************************************************************************************/
size_t TFT_eSPI::write(uint8_t utf8)
{
if (_vpOoB) return 1;
uint16_t uniCode = decodeUTF8(utf8);
if (!uniCode) return 1;
if (utf8 == '\r') return 1;
#ifdef SMOOTH_FONT
if(fontLoaded) {
if (uniCode < 32 && utf8 != '\n') return 1;
drawGlyph(uniCode);
return 1;
}
#endif
if (uniCode == '\n') uniCode+=22; // Make it a valid space character to stop errors
uint16_t cwidth = 0;
uint16_t cheight = 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 < 32 || uniCode > 127) return 1;
cwidth = pgm_read_byte(widtbl_f16 + uniCode-32);
cheight = chr_hgt_f16;
// Font 2 is rendered in whole byte widths so we must allow for this
cwidth = (cwidth + 6) / 8; // Width in whole bytes for font 2, should be + 7 but must allow for font width change
cwidth = cwidth * 8; // Width converted back to pixels
}
#ifdef LOAD_RLE
else
#endif
#endif
#ifdef LOAD_RLE
{
if ((textfont>2) && (textfont<9)) {
if (uniCode < 32 || uniCode > 127) return 1;
// Uses the fontinfo struct array to avoid lots of 'if' or 'switch' statements
cwidth = pgm_read_byte( (uint8_t *)pgm_read_dword( &(fontdata[textfont].widthtbl ) ) + uniCode-32 );
cheight= pgm_read_byte( &fontdata[textfont].height );
}
}
#endif
#ifdef LOAD_GLCD
if (textfont==1) {
cwidth = 6;
cheight = 8;
}
#else
if (textfont==1) return 1;
#endif
cheight = cheight * textsize;
if (utf8 == '\n') {
cursor_y += cheight;
cursor_x = 0;
}
else {
if (textwrapX && (cursor_x + cwidth * textsize > width())) {
cursor_y += cheight;
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
***************************************************************************************/
// TODO: Rationalise with TFT_eSprite
// 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 (_vpOoB || !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 xd = x + _xDatum;
int32_t yd = y + _yDatum;
if ((xd + width * textsize < _vpX || xd >= _vpW) && (yd + height * textsize < _vpY || yd >= _vpH)) return width * textsize ;
int32_t w = width;
int32_t pX = 0;
int32_t pY = y;
uint8_t line = 0;
bool clip = xd < _vpX || xd + width * textsize >= _vpW || yd < _vpY || yd + height * textsize >= _vpH;
#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 (textcolor == textbgcolor || textsize != 1 || clip) {
//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 = lockTransaction;
end_tft_write();
}
else { // Faster drawing of characters and background using block write
begin_tft_write();
setWindow(xd, yd, xd + width - 1, yd + 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 (textcolor == textbgcolor && !clip) {
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 = xd + textsize * (pc % width); // Keep these px and py calculations outside the loop as they are slow
py = yd + textsize * (pc / width);
}
else {
px = xd + pc % width; // Keep these px and py calculations outside the loop as they are slow
py = yd + 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 >= (xd + width * textsize)) {
px = xd;
py += textsize;
}
}
}
else {
line++;
pc += line;
}
}
}
else {
// Text colour != background and textsize = 1 and character is within viewport area
// so use faster drawing of characters and background using block write
if (textcolor != textbgcolor && textsize == 1 && !clip)
{
setWindow(xd, yd, xd + width - 1, yd + 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 = 0, py = 0; // To hold character pixel coords
int32_t tx = 0, ty = 0; // To hold character TFT pixel coords
int32_t pc = 0; // Pixel count
int32_t pl = 0; // Pixel line length
uint16_t pcol = 0; // Pixel color
bool pf = true; // Flag for plotting
while (pc < w) {
line = pgm_read_byte((uint8_t *)flash_address);
flash_address++;
if (line & 0x80) { pcol = textcolor; line &= 0x7F; pf = true;}
else { pcol = textbgcolor; if (textcolor == textbgcolor) pf = false;}
line++;
px = pc % width;
tx = x + textsize * px;
py = pc / width;
ty = y + textsize * py;
pl = 0;
pc += line;
while (line--) {
pl++;
if ((px+pl) >= width) {
if (pf) fillRect(tx, ty, pl * textsize, textsize, pcol);
pl = 0;
px = 0;
tx = x;
py ++;
ty += textsize;
}
}
if (pl && pf) fillRect(tx, ty, pl * textsize, textsize, pcol);
}
}
}
inTransaction = lockTransaction;
end_tft_write();
}
// End of RLE font rendering
#endif
#if !defined (LOAD_FONT2) && !defined (LOAD_RLE)
// Stop warnings
flash_address = flash_address;
w = w;
pX = pX;
pY = pY;
line = line;
clip = clip;
#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;
}
}
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) {
setCursor(poX, poY);
bool fillbg = _fillbg;
// If padding is requested then fill the text background
if (padX && !_fillbg) _fillbg = true;
while (n < len) {
uint16_t uniCode = decodeUTF8((uint8_t*)string, &n, len - n);
drawGlyph(uniCode);
}
_fillbg = fillbg; // restore state
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 = poX - ((padX-cwidth)>>1);
fillRect(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;
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
bool negative = false;
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
negative = true;
}
floatNumber += rounding; // Round up or down
if (dp == 0) {
if (negative) floatNumber = -floatNumber;
return drawNumber((long)floatNumber, poX, poY, font);
}
// 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 (uint16_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) && ! defined (RP2040_PIO_INTERFACE)
SPIClass& TFT_eSPI::getSPIinstance(void)
{
return spi;
}
#endif
/***************************************************************************************
** Function name: verifySetupID
** Description: Compare the ID if USER_SETUP_ID defined in user setup file
***************************************************************************************/
bool TFT_eSPI::verifySetupID(uint32_t id)
{
#if defined (USER_SETUP_ID)
if (USER_SETUP_ID == id) return true;
#else
id = id; // Avoid warning
#endif
return false;
}
/***************************************************************************************
** 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 (USER_SETUP_INFO)
tft_settings.setup_info = USER_SETUP_INFO;
#else
tft_settings.setup_info = "NA";
#endif
#if defined (USER_SETUP_ID)
tft_settings.setup_id = USER_SETUP_ID;
#else
tft_settings.setup_id = 0;
#endif
#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) || defined(TFT_PARALLEL_16_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
#ifndef GENERIC_PROCESSOR
#ifdef TFT_SPI_PORT
tft_settings.port = TFT_SPI_PORT;
#else
tft_settings.port = 255;
#endif
#endif
#ifdef RP2040_PIO_SPI
tft_settings.interface = 0x10;
#else
tft_settings.interface = 0x0;
#endif
#endif
#if defined(TFT_SPI_OVERLAP)
tft_settings.overlap = true;
#else
tft_settings.overlap = false;
#endif
tft_settings.tft_driver = 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) || defined(TFT_PARALLEL_16_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
}
////////////////////////////////////////////////////////////////////////////////////////
#ifdef TOUCH_CS
#include "Extensions/Touch.cpp"
#endif
#include "Extensions/Button.cpp"
#include "Extensions/Sprite.cpp"
#ifdef SMOOTH_FONT
#include "Extensions/Smooth_font.cpp"
#endif
#ifdef AA_GRAPHICS
#include "Extensions/AA_graphics.cpp" // Loaded if SMOOTH_FONT is defined by user
#endif
////////////////////////////////////////////////////////////////////////////////////////