TFT eSPI
TFT eSPI
by Bodmer
Fast link to
TFT_eSP
Touch functions
Smooth font functions
Button class
Sprite class
/***************************************************************************************
** Function name: TFT_eSPI Library
** Description: Arduino TFT graphics library targeted at 32 bit
***************************************************************************************/
/***************************************************
Arduino TFT graphics library targeted at 32 bit
processors such as ESP32, ESP8266 and STM32.
#include "TFT_eSPI.h"
#ifndef SPI_BUSY_CHECK
#define SPI_BUSY_CHECK
#endif
}
/***************************************************************************************
** Function name: end_tft_write (was called spi_end)
** Description: End transaction for write and deselect TFT
***************************************************************************************/
inline void TFT_eSPI::end_tft_write(void){
#if defined (SPI_HAS_TRANSACTION) && defined (SUPPORT_TRANSACTIONS) && !defined(TFT_PARALLEL_8_BIT)
if(!inTransaction) { // Flag to stop ending tranaction 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;
spi.endTransaction(); // RP2040 SDK -> 0.7us delay
}
SET_BUS_READ_MODE; // In case SPI has been configured for tx only
}
#else
if(!inTransaction) {SPI_BUSY_CHECK; CS_H; SET_BUS_READ_MODE;}
#endif
}
/***************************************************************************************
** Function name: begin_tft_read (was called spi_begin_read)
** Description: Start transaction for reads and select TFT
***************************************************************************************/
// Reads require a lower SPI clock rate than writes
inline void TFT_eSPI::begin_tft_read(void){
DMA_BUSY_CHECK; // Wait for any DMA transfer to complete before changing SPI settings
#if defined (SPI_HAS_TRANSACTION) && defined (SUPPORT_TRANSACTIONS) && !defined(TFT_PARALLEL_8_BIT)
if (locked) {
locked = false;
spi.beginTransaction(SPISettings(SPI_READ_FREQUENCY, MSBFIRST, TFT_SPI_MODE));
CS_L;
}
#else
#if !defined(TFT_PARALLEL_8_BIT)
spi.setFrequency(SPI_READ_FREQUENCY);
#endif
CS_L;
#endif
SET_BUS_READ_MODE;
}
/***************************************************************************************
** Function name: 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
if (!vpDatum)
{
_xDatum = 0; // Reset to top left of screen if not using a viewport datum
_yDatum = 0;
_xWidth = width();
_yHeight = height();
}
}
/***************************************************************************************
** 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;
int32_t dx = 0;
int32_t dy = 0;
int32_t dw = w;
int32_t dh = h;
return true;
}
/***************************************************************************************
** Function name: resetViewport
** Description: Reset viewport to whle 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;
// 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 vpDatum
_vpDatum = _dT;
}
/***************************************************************************************
** Function name: end_tft_read (was called spi_end_read)
** Description: End transaction for reads and deselect TFT
***************************************************************************************/
inline void TFT_eSPI::end_tft_read(void){
#if defined (SPI_HAS_TRANSACTION) && defined (SUPPORT_TRANSACTIONS) && !defined(TFT_PARALLEL_8_BIT)
if(!inTransaction) {
if (!locked) {
locked = true;
CS_H;
spi.endTransaction();
}
}
#else
#if !defined(TFT_PARALLEL_8_BIT)
spi.setFrequency(SPI_FREQUENCY);
#endif
if(!inTransaction) {CS_H;}
#endif
SET_BUS_WRITE_MODE;
// The ST7796 appears to need a 4ms delay after a CGRAM read, otherwise subsequent writes will fail!
#ifdef ST7796_DRIVER
delay(4);
#endif
}
/***************************************************************************************
** Function name: Legacy - deprecated
** Description: Start/end transaction
***************************************************************************************/
void TFT_eSPI::spi_begin() {begin_tft_write();}
void TFT_eSPI::spi_end() { end_tft_write();}
void TFT_eSPI::spi_begin_read() {begin_tft_read(); }
void TFT_eSPI::spi_end_read() { end_tft_read(); }
/***************************************************************************************
** Function name: TFT_eSPI
** Description: Constructor , we must use hardware SPI pins
***************************************************************************************/
TFT_eSPI::TFT_eSPI(int16_t w, int16_t h)
{
// The control pins are deliberately set to the inactive state (CS high) as setup()
// might call and initialise other SPI peripherals which would could cause conflicts
// if CS is floating or undefined.
#ifdef TFT_CS
pinMode(TFT_CS, OUTPUT);
digitalWrite(TFT_CS, HIGH); // Chip select high (inactive)
#endif
#ifdef TFT_WR
pinMode(TFT_WR, OUTPUT);
digitalWrite(TFT_WR, HIGH); // Set write strobe high (inactive)
#endif
#ifdef TFT_DC
pinMode(TFT_DC, OUTPUT);
digitalWrite(TFT_DC, HIGH); // Data/Command high = data mode
#endif
#ifdef TFT_RST
if (TFT_RST >= 0) {
pinMode(TFT_RST, OUTPUT);
digitalWrite(TFT_RST, HIGH); // Set high, do not share pin with another SPI device
}
#endif
CONSTRUCTOR_INIT_TFT_DATA_BUS;
#endif
rotation = 0;
cursor_y = cursor_x = 0;
textfont = 1;
textsize = 1;
textcolor = bitmap_fg = 0xFFFF; // White
textbgcolor = bitmap_bg = 0x0000; // Black
padX = 0; // No padding
isDigits = false; // No bounding box adjustment
textwrapX = true; // Wrap text at end of line when using print stream
textwrapY = false; // Wrap text at bottom of screen when using print stream
textdatum = TL_DATUM; // Top Left text alignment is default
fontsloaded = 0;
#ifdef FONT_FS_AVAILABLE
fs_font = true; // Smooth font filing system or array (fs_font = false) flag
#endif
_xPivot = 0;
_yPivot = 0;
#ifdef LOAD_FONT2
fontsloaded |= 0x0004; // Bit 2 set
#endif
#ifdef LOAD_FONT4
fontsloaded |= 0x0010; // Bit 4 set
#endif
#ifdef LOAD_FONT6
fontsloaded |= 0x0040; // Bit 6 set
#endif
#ifdef LOAD_FONT7
fontsloaded |= 0x0080; // Bit 7 set
#endif
#ifdef LOAD_FONT8
fontsloaded |= 0x0100; // Bit 8 set
#endif
#ifdef LOAD_FONT8N
fontsloaded |= 0x0200; // Bit 9 set
#endif
#ifdef SMOOTH_FONT
fontsloaded |= 0x8000; // Bit 15 set
#endif
}
/***************************************************************************************
** Function name: begin
** Description: Included for backwards compatibility
***************************************************************************************/
void TFT_eSPI::begin(uint8_t tc)
{
init(tc);
}
/***************************************************************************************
** Function name: init (tc is tab colour for ST7735 displays only)
** Description: Reset, then initialise the TFT display registers
***************************************************************************************/
void TFT_eSPI::init(uint8_t tc)
{
if (_booted)
{
#if !defined (ESP32) && !defined(TFT_PARALLEL_8_BIT) && !defined(ARDUINO_ARCH_RP2040)
// Legacy bitmasks for GPIO
#if defined (TFT_CS) && (TFT_CS >= 0)
cspinmask = (uint32_t) digitalPinToBitMask(TFT_CS);
#endif
#else
#if !defined(TFT_PARALLEL_8_BIT)
#if defined (TFT_MOSI) && !defined (TFT_SPI_OVERLAP) && !defined(ARDUINO_ARCH_RP2040)
spi.begin(TFT_SCLK, TFT_MISO, TFT_MOSI, -1);
#else
spi.begin();
#endif
#endif
#endif
lockTransaction = false;
inTransaction = false;
locked = true;
INIT_TFT_DATA_BUS;
#ifdef TFT_CS
// Set to output once again in case ESP8266 D6 (MISO) is used for CS
pinMode(TFT_CS, OUTPUT);
digitalWrite(TFT_CS, HIGH); // Chip select high (inactive)
#elif defined (ESP8266) && !defined (TFT_PARALLEL_8_BIT)
spi.setHwCs(1); // Use hardware SS toggling
#endif
_booted = false;
end_tft_write();
} // end of: if just _booted
begin_tft_write();
// This loads the driver specific initialisation code <<<<<<<<<<<<<<<<<<<<< ADD NEW DRIVERS TO THE LI
#if defined (ILI9341_DRIVER) || defined(ILI9341_2_DRIVER)
#include "TFT_Drivers/ILI9341_Init.h"
#endif
#ifdef TFT_INVERSION_ON
writecommand(TFT_INVON);
#endif
#ifdef TFT_INVERSION_OFF
writecommand(TFT_INVOFF);
#endif
end_tft_write();
setRotation(rotation);
begin_tft_write();
// This loads the driver specific rotation code <<<<<<<<<<<<<<<<<<<<< ADD NEW DRIVERS TO THE LIST
#if defined (ILI9341_DRIVER) || defined(ILI9341_2_DRIVER)
#include "TFT_Drivers/ILI9341_Rotation.h"
#endif
delayMicroseconds(10);
end_tft_write();
addr_row = 0xFFFF;
addr_col = 0xFFFF;
if (ms)
{
ms = pgm_read_byte(addr++); // Read post-command delay time (ms)
delay( (ms==255 ? 500 : ms) );
}
}
}
/***************************************************************************************
** Function name: spiwrite
** Description: Write 8 bits to SPI port (legacy support only)
***************************************************************************************/
void TFT_eSPI::spiwrite(uint8_t c)
{
begin_tft_write();
tft_Write_8(c);
end_tft_write();
}
/***************************************************************************************
** Function name: writecommand
** Description: Send an 8 bit command to the TFT
***************************************************************************************/
void TFT_eSPI::writecommand(uint8_t c)
{
begin_tft_write();
DC_C;
tft_Write_8(c);
DC_D;
end_tft_write();
}
/***************************************************************************************
** Function name: writedata
** Description: Send a 8 bit data value to the TFT
***************************************************************************************/
void TFT_eSPI::writedata(uint8_t d)
{
begin_tft_write();
tft_Write_8(d);
end_tft_write();
}
/***************************************************************************************
** Function name: readcommand8
** Description: Read a 8 bit data value from an indexed command register
***************************************************************************************/
uint8_t TFT_eSPI::readcommand8(uint8_t cmd_function, uint8_t index)
{
uint8_t reg = 0;
#ifdef TFT_PARALLEL_8_BIT
busDir(dir_mask, INPUT);
CS_L;
// Read nth parameter (assumes caller discards 1st parameter or points index to 2nd)
while(index--) reg = readByte();
busDir(dir_mask, OUTPUT);
CS_H;
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;
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;
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)
CS_L;
CS_H;
return rgb;
CS_H;
#ifdef ILI9486_DRIVER
return bgr;
#else
// Swap Red and Blue (could check MADCTL setting to see if this is needed)
return (bgr>>11) | (bgr<<11) | (bgr & 0x7E0);
#endif
#endif
uint16_t color = 0;
begin_tft_read();
#ifdef TFT_SDA_READ
begin_SDA_Read();
#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();
return color;
#endif
}
#if defined(TFT_PARALLEL_8_BIT)
CS_L;
data += dx + dy * w;
CS_H;
uint16_t color = 0;
begin_tft_read();
data += dx + dy * w;
#ifdef TFT_SDA_READ
begin_SDA_Read();
#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
//CS_H;
#ifdef TFT_SDA_READ
end_SDA_Read();
#endif
end_tft_read();
begin_tft_write();
inTransaction = true;
setWindow(x, y, x + dw - 1, y + dh - 1);
data += dx + dy * 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;
int32_t xe = x + dw - 1, ye = y + dh - 1;
// 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;
bool move = true;
uint16_t np = 0;
while (len--)
{
if (transp != *ptr)
{
if (move) { move = false; setWindow(px, y, xe, ye); }
lineBuf[np] = *ptr;
np++;
}
else
{
move = true;
if (np)
{
pushPixels((uint16_t*)lineBuf, np);
np = 0;
}
}
px++;
ptr++;
}
if (np) pushPixels((uint16_t*)lineBuf, np);
y++;
data += w;
}
inTransaction = 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);
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;
int32_t xe = x + dw - 1, ye = y + dh - 1;
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;
bool move = true;
uint16_t np = 0;
while (len--) {
uint16_t color = pgm_read_word(ptr);
if (transp != color) {
if (move) { move = false; setWindow(px, y, xe, ye); }
lineBuf[np] = color;
np++;
}
else {
move = true;
if (np) {
pushPixels(lineBuf, np);
np = 0;
}
}
px++;
ptr++;
}
if (np) pushPixels(lineBuf, np);
y++;
data += w;
}
inTransaction = 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 *cm
{
PI_CLIP;
begin_tft_write();
inTransaction = true;
bool swap = _swapBytes;
if (bpp8)
{
_swapBytes = false;
uint8_t blue[] = {0, 11, 21, 31}; // blue 2 to 5 bit colour lookup table
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++);
*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 / ima
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);
}
}
begin_tft_write();
inTransaction = true;
bool swap = _swapBytes;
if (bpp8)
{
_swapBytes = false;
uint8_t blue[] = {0, 11, 21, 31}; // blue 2 to 5 bit colour lookup table
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++;
*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 / ima
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;
begin_tft_write();
inTransaction = true;
bool swap = _swapBytes;
int32_t xe = x + dw - 1, ye = y + dh - 1;
data += dx + dy * w;
uint8_t blue[] = {0, 11, 21, 31}; // blue 2 to 5 bit colour lookup table
while (dh--) {
int32_t len = dw;
uint8_t* ptr = data;
uint8_t* linePtr = (uint8_t*)lineBuf;
int32_t px = x;
bool move = true;
uint16_t np = 0;
while (len--) {
if (transp != *ptr) {
if (move) { move = false; setWindow(px, y, xe, ye);}
uint8_t color = *ptr;
int32_t px = x;
bool move = true;
uint16_t np = 0;
if (splitFirst) {
index = (*ptr & 0x0F); // odd = bits 3 .. 0
if (index != transp) {
move = false; setWindow(px, y, xe, ye);
lineBuf[np] = cmap[index];
np++;
}
px++; ptr++;
len--;
}
while (len--)
{
uint8_t color = *ptr;
// find the actual color you care about. There will be two pixels here!
// but we may only want one at the end of the row
uint16_t index = ((color & 0xF0) >> 4) & 0x0F; // high bits are the even numbers
if (index != transp) {
if (move) {
move = false; setWindow(px, y, xe, ye);
}
lineBuf[np] = cmap[index];
np++; // added a pixel
}
else {
move = true;
if (np) {
pushPixels(lineBuf, np);
np = 0;
}
}
px++;
if (len--)
{
index = color & 0x0F; // the odd number is 3 .. 0
if (index != transp) {
if (move) {
move = false; setWindow(px, y, xe, ye);
}
lineBuf[np] = cmap[index];
np++;
}
else {
move = true;
if (np) {
pushPixels(lineBuf, np);
np = 0;
}
}
px++;
}
else {
break; // we are done with this row.
}
ptr++; // we only increment ptr once in the loop (deliberate)
}
if (np) {
pushPixels(lineBuf, np);
np = 0;
}
data += (w>>1);
y++;
}
}
else { // 1 bit per pixel
_swapBytes = false;
uint32_t len = w * h;
uint8_t* buf565 = data + len;
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;
}
begin_tft_read();
#ifdef TFT_SDA_READ
begin_SDA_Read();
#endif
// Read window pixel 24 bit RGB values, buffer must be set in sketch to 3 * w * h
uint32_t len = w * h;
while (len--) {
// 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;
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;
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);
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;
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);
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++;
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;
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;
if (s >= 0) {
s += fx2 * (1 - y);
y--;
}
s += ry2 * ((4 * x) + 6);
}
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);
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;
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;
int32_t
dx01 = x1 - x0,
dy01 = y1 - y0,
dx02 = x2 - x0,
dy02 = y2 - y0,
dx12 = x2 - x1,
dy12 = y2 - y1,
sa = 0,
sb = 0;
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;
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, uint1
{
//begin_tft_write(); // Sprite class can use this function, avoiding begin_tft_write()
inTransaction = true;
int32_t i, j, byteWidth = (w + 7) / 8;
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;
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, uint1
{
//begin_tft_write(); // Sprite class can use this function, avoiding begin_tft_write()
inTransaction = true;
int32_t i, j, byteWidth = (w + 7) / 8;
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
***************************************************************************************/
void TFT_eSPI::setTextColor(uint16_t c, uint16_t b)
{
textcolor = c;
textbgcolor = b;
}
/***************************************************************************************
** Function name: setPivot
** Description: Set the pivot point on the TFT
*************************************************************************************x*/
void TFT_eSPI::setPivot(int16_t x, int16_t y)
{
_xPivot = x;
_yPivot = y;
}
/***************************************************************************************
** Function name: getPivotX
** Description: Get the x pivot position
***************************************************************************************/
int16_t TFT_eSPI::getPivotX(void)
{
return _xPivot;
}
/***************************************************************************************
** Function name: getPivotY
** Description: Get the y pivot position
***************************************************************************************/
int16_t TFT_eSPI::getPivotY(void)
{
return _yPivot;
}
/***************************************************************************************
** Function name: setBitmapColor
** Description: Set the foreground foreground and background colour
***************************************************************************************/
void TFT_eSPI::setBitmapColor(uint16_t c, uint16_t b)
{
if (c == b) b = ~c;
bitmap_fg = c;
bitmap_bg = b;
}
/***************************************************************************************
** Function name: setTextWrap
** Description: Define if text should wrap at end of line
***************************************************************************************/
void TFT_eSPI::setTextWrap(bool wrapX, bool wrapY)
{
textwrapX = wrapX;
textwrapY = wrapY;
}
/***************************************************************************************
** Function name: setTextDatum
** Description: Set the text position reference datum
***************************************************************************************/
void TFT_eSPI::setTextDatum(uint8_t d)
{
textdatum = d;
}
/***************************************************************************************
** Function name: setTextPadding
** Description: Define padding width (aids erasing old text and numbers)
***************************************************************************************/
void TFT_eSPI::setTextPadding(uint16_t x_width)
{
padX = x_width;
}
/***************************************************************************************
** Function name: setTextPadding
** Description: Define padding width (aids erasing old text and numbers)
***************************************************************************************/
uint16_t TFT_eSPI::getTextPadding(void)
{
return padX;
}
/***************************************************************************************
** Function name: getRotation
** Description: Return the rotation value (as used by setRotation())
***************************************************************************************/
uint8_t TFT_eSPI::getRotation(void)
{
return rotation;
}
/***************************************************************************************
** Function name: getTextDatum
** Description: Return the text datum value (as used by setTextDatum())
***************************************************************************************/
uint8_t TFT_eSPI::getTextDatum(void)
{
return textdatum;
}
/***************************************************************************************
** Function name: width
** Description: Return the pixel width of display (per current rotation)
***************************************************************************************/
// Return the size of the display (per current rotation)
int16_t TFT_eSPI::width(void)
{
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);
}
#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
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
#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;
int32_t xd = x + _xDatum;
int32_t yd = y + _yDatum;
end_tft_write();
}
else {
//begin_tft_write(); // Sprite class can use this function, avoiding begin_tft_write()
inTransaction = true;
//>>>>>>>>>>>>>>>>>>>>>>>>>>>
#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;
}
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
#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();
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;
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;
#elif defined (SSD1351_DRIVER)
if (rotation & 1) {
swap_coord(x0, y0);
swap_coord(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) { swap_coord(x0, y0); swap_coord(x1, y1); }
#endif
#ifdef CGRAM_OFFSET
x0+=colstart;
x1+=colstart;
y0+=rowstart;
y1+=rowstart;
#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;
#ifdef CGRAM_OFFSET
xs += colstart;
xe += colstart;
ys += rowstart;
ye += rowstart;
#endif
#else
// Column addr set
DC_C; tft_Write_8(TFT_CASET);
DC_D; tft_Write_32C(xs, xe);
DC_D;
#endif // RP2040 SPI
x+= _xDatum;
y+= _yDatum;
// Range checking
if ((x < _vpX) || (y < _vpY) ||(x >= _vpW) || (y >= _vpH)) return;
#ifdef CGRAM_OFFSET
x+=colstart;
y+=rowstart;
#endif
begin_tft_write();
SPI_BUSY_CHECK;
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);
}
// write to RAM
DC_C; tft_Write_8(TFT_RAMWR);
#if defined(TFT_PARALLEL_8_BIT) || !defined(ESP32)
DC_D; tft_Write_16(color);
#else
DC_D; tft_Write_16N(color);
#endif
// Next call will start with 8 bit command so changing to 16 bit not needed here
//spi_set_format(spi0, 16, (spi_cpol_t)0, (spi_cpha_t)0, SPI_MSB_FIRST);
#else
SPI_BUSY_CHECK;
end_tft_write();
}
/***************************************************************************************
** Function name: pushColor
** Description: push a single pixel
***************************************************************************************/
void TFT_eSPI::pushColor(uint16_t color)
{
begin_tft_write();
tft_Write_16(color);
end_tft_write();
}
/***************************************************************************************
** Function name: pushColor
** Description: push a single colour to "len" pixels
***************************************************************************************/
void TFT_eSPI::pushColor(uint16_t color, uint32_t len)
{
begin_tft_write();
pushBlock(color, len);
end_tft_write();
}
/***************************************************************************************
** Function name: startWrite
** Description: begin transaction with CS low, MUST later call endWrite
***************************************************************************************/
void TFT_eSPI::startWrite(void)
{
begin_tft_write();
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);
inTransaction = lockTransaction;
end_tft_write();
}
/***************************************************************************************
** Function name: drawFastVLine
** Description: draw a vertical line
***************************************************************************************/
void TFT_eSPI::drawFastVLine(int32_t x, int32_t y, int32_t h, uint32_t color)
{
if (_vpOoB) return;
x+= _xDatum;
y+= _yDatum;
// Clipping
if ((x < _vpX) || (x >= _vpW) || (y >= _vpH)) return;
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 (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;
begin_tft_write();
setWindow(x, y, x + w - 1, y + h - 1);
pushBlock(color, w * h);
end_tft_write();
}
/***************************************************************************************
** Function name: color565
** Description: convert three 8 bit RGB levels to a 16 bit colour value
***************************************************************************************/
uint16_t TFT_eSPI::color565(uint8_t r, uint8_t g, uint8_t b)
{
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
}
/***************************************************************************************
** Function name: color16to8
** Description: convert 16 bit colour to an 8 bit 332 RGB colour value
***************************************************************************************/
uint8_t TFT_eSPI::color16to8(uint16_t c)
{
return ((c & 0xE000)>>8) | ((c & 0x0700)>>6) | ((c & 0x0018)>>3);
}
/***************************************************************************************
** Function name: color8to16
** Description: convert 8 bit colour to a 16 bit 565 colour value
***************************************************************************************/
uint16_t TFT_eSPI::color8to16(uint8_t color)
{
uint8_t blue[] = {0, 11, 21, 31}; // blue 2 to 5 bit colour lookup table
uint16_t color16 = 0;
// =====Green===== ===============Red==============
color16 = (color & 0x1C)<<6 | (color & 0xC0)<<5 | (color & 0xE0)<<8;
// =====Green===== =======Blue======
color16 |= (color & 0x1C)<<3 | blue[color & 0x03];
return color16;
}
/***************************************************************************************
** Function name: color16to24
** Description: convert 16 bit colour to a 24 bit 888 colour value
***************************************************************************************/
uint32_t TFT_eSPI::color16to24(uint16_t color565)
{
uint8_t r = (color565 >> 8) & 0xF8; r |= (r >> 5);
uint8_t g = (color565 >> 3) & 0xFC; g |= (g >> 6);
uint8_t b = (color565 << 3) & 0xF8; b |= (b >> 5);
return (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;
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;
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));
}
// Shift right 1 to drop rounding bit and shift right 8 to divide by 256
uint16_t r = (((fgR * alpha) + (bgR * (255 - alpha))) >> 9);
uint16_t g = (((fgG * alpha) + (bgG * (255 - alpha))) >> 9);
uint16_t b = (((fgB * alpha) + (bgB * (255 - alpha))) >> 9);
if (dither) {
int16_t alphaDither = (int16_t)alpha - dither + random(2*dither+1); // +/-dither randomised
alpha = (uint8_t)alphaDither;
if (alphaDither < 0) alpha = 0;
if (alphaDither >255) alpha = 255;
}
// For speed use fixed point maths and rounding to permit a power of 2 division
uint16_t fgR = ((fgc >> 15) & 0x1FE) + 1;
uint16_t fgG = ((fgc >> 7) & 0x1FE) + 1;
uint16_t fgB = ((fgc << 1) & 0x1FE) + 1;
// Shift right 1 to drop rounding bit and shift right 8 to divide by 256
uint16_t r = (((fgR * alpha) + (bgR * (255 - alpha))) >> 9);
uint16_t g = (((fgG * alpha) + (bgG * (255 - alpha))) >> 9);
uint16_t b = (((fgB * alpha) + (bgB * (255 - alpha))) >> 9);
if (!uniCode) return 1;
#ifdef SMOOTH_FONT
if(fontLoaded) {
if (uniCode < 32 && utf8 != '\n') return 1;
drawGlyph(uniCode);
return 1;
}
#endif
uint16_t cwidth = 0;
uint16_t cheight = 0;
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
#ifdef LOAD_GFXFF
if(!gfxFont) {
#endif
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
#ifdef LOAD_FONT2
if (textfont == 2) {
if (uniCode > 127) return 1;
#ifdef LOAD_RLE
{
if ((textfont>2) && (textfont<9)) {
if (uniCode > 127) return 1;
// Uses the fontinfo struct array to avoid lots of 'if' or 'switch' statements
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
if (utf8 == '\n') {
cursor_y += cheight;
cursor_x = 0;
}
else {
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;
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);
}
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*size
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
int32_t w = width;
int32_t pY = y;
uint8_t line = 0;
bool clip = xd < _vpX || xd + width * textsize >= _vpW || yd < _vpY || yd + height * textsize >= _vpH;
inTransaction = lockTransaction;
end_tft_write();
}
else { // Faster drawing of characters and background using block write
begin_tft_write();
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
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
if (ts) {
tnp = np;
while (tnp--) {tft_Write_16(textcolor);}
}
else {tft_Write_16(textcolor);}
px += textsize;
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
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);
}
// 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;
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;
#ifdef SMOOTH_FONT
if(fontLoaded) {
if (textcolor!=textbgcolor) fillRect(poX, poY, cwidth, cheight, textbgcolor);
/*
// The above only works for a single text line, not if the text is going to wrap...
// So need to use code like this in a while loop to fix it:
if (textwrapX && (cursor_x + width * textsize > width())) {
cursor_y += height;
cursor_x = 0;
}
if (textwrapY && (cursor_y >= (int32_t)height())) cursor_y = 0;
cursor_x += drawChar(uniCode, cursor_x, cursor_y, textfont);
*/
while (n < len) {
uint16_t uniCode = decodeUTF8((uint8_t*)string, &n, len - n);
drawGlyph(uniCode);
}
sumX += cwidth;
//fontFile.close();
}
else
#endif
{
while (n < len) {
uint16_t uniCode = decodeUTF8((uint8_t*)string, &n, len - n);
sumX += drawChar(uniCode, poX+sumX, poY, font);
}
}
#ifndef PADDING_DEBUG
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DEBUG ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
#else
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::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
// 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
// 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
#ifdef LOAD_GFXFF
textfont = 1;
gfxFont = (GFXfont *)f;
glyph_ab = 0;
glyph_bb = 0;
uint16_t numChars = pgm_read_word(&gfxFont->last) - pgm_read_word(&gfxFont->first);
#else
/***************************************************************************************
** Function name: setFreeFont
** Descriptions: Sets the GFX free font to use
***************************************************************************************/
#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
////////////////////////////////////////////////////////////////////////////////////////
#ifdef TOUCH_CS
#include "Extensions/Touch.cpp"
#include "Extensions/Button.cpp"
#endif
#include "Extensions/Sprite.cpp"
#ifdef SMOOTH_FONT
#include "Extensions/Smooth_font.cpp"
#endif
/***************************************************************************************
** Function name: TOUCH
** Description: Define TOUCH_CS is the user setup file to enable this code
***************************************************************************************/
/***************************************************************************************
** Function name: begin_touch_read_write - was spi_begin_touch
** Description: Start transaction and select touch controller
***************************************************************************************/
// The touch controller has a low SPI clock rate
inline void TFT_eSPI::begin_touch_read_write(void){
DMA_BUSY_CHECK;
CS_H; // Just in case it has been left low
#if defined (SPI_HAS_TRANSACTION) && defined (SUPPORT_TRANSACTIONS)
if (locked) {locked = false; spi.beginTransaction(SPISettings(SPI_TOUCH_FREQUENCY, MSBFIRST, SPI_MODE0))
#else
spi.setFrequency(SPI_TOUCH_FREQUENCY);
#endif
SET_BUS_READ_MODE;
T_CS_L;
}
/***************************************************************************************
** Function name: end_touch_read_write - was spi_end_touch
** Description: End transaction and deselect touch controller
***************************************************************************************/
inline void TFT_eSPI::end_touch_read_write(void){
T_CS_H;
#if defined (SPI_HAS_TRANSACTION) && defined (SUPPORT_TRANSACTIONS)
if(!inTransaction) {if (!locked) {locked = true; spi.endTransaction();}}
#else
spi.setFrequency(SPI_FREQUENCY);
#endif
//SET_BUS_WRITE_MODE;
}
/***************************************************************************************
** Function name: Legacy - deprecated
** Description: Start/end transaction
***************************************************************************************/
void TFT_eSPI::spi_begin_touch() {begin_touch_read_write();}
void TFT_eSPI::spi_end_touch() { end_touch_read_write();}
/***************************************************************************************
** Function name: getTouchRaw
** Description: read raw touch position. Always returns true.
***************************************************************************************/
uint8_t TFT_eSPI::getTouchRaw(uint16_t *x, uint16_t *y){
uint16_t tmp;
begin_touch_read_write();
// Start YP sample request for x position, read 4 times and keep last sample
spi.transfer(0xd0); // Start new YP conversion
spi.transfer(0); // Read first 8 bits
spi.transfer(0xd0); // Read last 8 bits and start new YP conversion
spi.transfer(0); // Read first 8 bits
spi.transfer(0xd0); // Read last 8 bits and start new YP conversion
spi.transfer(0); // Read first 8 bits
spi.transfer(0xd0); // Read last 8 bits and start new YP conversion
*x = tmp;
// Start XP sample request for y position, read 4 times and keep last sample
spi.transfer(0); // Read first 8 bits
spi.transfer(0x90); // Read last 8 bits and start new XP conversion
spi.transfer(0); // Read first 8 bits
spi.transfer(0x90); // Read last 8 bits and start new XP conversion
spi.transfer(0); // Read first 8 bits
spi.transfer(0x90); // Read last 8 bits and start new XP conversion
*y = tmp;
end_touch_read_write();
return true;
}
/***************************************************************************************
** Function name: getTouchRawZ
** Description: read raw pressure on touchpad and return Z value.
***************************************************************************************/
uint16_t TFT_eSPI::getTouchRawZ(void){
begin_touch_read_write();
// Z sample request
int16_t tz = 0xFFF;
spi.transfer(0xb0); // Start new Z1 conversion
tz += spi.transfer16(0xc0) >> 3; // Read Z1 and start Z2 conversion
tz -= spi.transfer16(0x00) >> 3; // Read Z2
end_touch_read_write();
return (uint16_t)tz;
}
/***************************************************************************************
** Function name: validTouch
** Description: read validated position. Return false if not pressed.
***************************************************************************************/
#define _RAWERR 20 // Deadband error allowed in successive position samples
uint8_t TFT_eSPI::validTouch(uint16_t *x, uint16_t *y, uint16_t threshold){
uint16_t x_tmp, y_tmp, x_tmp2, y_tmp2;
// Serial.print("Z = ");Serial.println(z1);
getTouchRaw(&x_tmp,&y_tmp);
*x = x_tmp;
*y = y_tmp;
return true;
}
/***************************************************************************************
** Function name: getTouch
** Description: read callibrated position. Return false if not pressed.
***************************************************************************************/
#define Z_THRESHOLD 350 // Touch pressure threshold for validating touches
uint8_t TFT_eSPI::getTouch(uint16_t *x, uint16_t *y, uint16_t threshold){
uint16_t x_tmp, y_tmp;
uint8_t n = 5;
uint8_t valid = 0;
while (n--)
{
if (validTouch(&x_tmp, &y_tmp, threshold)) valid++;;
}
convertRawXY(&x_tmp, &y_tmp);
_pressX = x_tmp;
_pressY = y_tmp;
*x = _pressX;
*y = _pressY;
return valid;
}
/***************************************************************************************
** Function name: convertRawXY
** Description: convert raw touch x,y values to screen coordinates
***************************************************************************************/
void TFT_eSPI::convertRawXY(uint16_t *x, uint16_t *y)
{
uint16_t x_tmp = *x, y_tmp = *y, xx, yy;
if(!touchCalibration_rotate){
xx=(x_tmp-touchCalibration_x0)*_width/touchCalibration_x1;
yy=(y_tmp-touchCalibration_y0)*_height/touchCalibration_y1;
if(touchCalibration_invert_x)
xx = _width - xx;
if(touchCalibration_invert_y)
yy = _height - yy;
} else {
xx=(y_tmp-touchCalibration_x0)*_width/touchCalibration_x1;
yy=(x_tmp-touchCalibration_y0)*_height/touchCalibration_y1;
if(touchCalibration_invert_x)
xx = _width - xx;
if(touchCalibration_invert_y)
yy = _height - yy;
}
*x = xx;
*y = yy;
}
/***************************************************************************************
** Function name: calibrateTouch
** Description: generates calibration parameters for touchscreen.
***************************************************************************************/
void TFT_eSPI::calibrateTouch(uint16_t *parameters, uint32_t color_fg, uint32_t color_bg, uint8_t size){
int16_t values[] = {0,0,0,0,0,0,0,0};
uint16_t x_tmp, y_tmp;
switch (i) {
case 0: // up left
drawLine(0, 0, 0, size, color_fg);
drawLine(0, 0, size, 0, color_fg);
drawLine(0, 0, size , size, color_fg);
break;
case 1: // bot left
drawLine(0, _height-size-1, 0, _height-1, color_fg);
drawLine(0, _height-1, size, _height-1, color_fg);
drawLine(size, _height-size-1, 0, _height-1 , color_fg);
break;
case 2: // up right
drawLine(_width-size-1, 0, _width-1, 0, color_fg);
drawLine(_width-size-1, size, _width-1, 0, color_fg);
drawLine(_width-1, size, _width-1, 0, color_fg);
break;
case 3: // bot right
drawLine(_width-size-1, _height-size-1, _width-1, _height-1, color_fg);
drawLine(_width-1, _height-1-size, _width-1, _height-1, color_fg);
drawLine(_width-1-size, _height-1, _width-1, _height-1, color_fg);
break;
}
// in addition, the touch screen axis could be in the opposite direction of the TFT axis
touchCalibration_invert_x = false;
if(touchCalibration_x0 > touchCalibration_x1){
values[0]=touchCalibration_x0;
touchCalibration_x0 = touchCalibration_x1;
touchCalibration_x1 = values[0];
touchCalibration_invert_x = true;
}
touchCalibration_invert_y = false;
if(touchCalibration_y0 > touchCalibration_y1){
values[0]=touchCalibration_y0;
touchCalibration_y0 = touchCalibration_y1;
touchCalibration_y1 = values[0];
}
// pre calculate
touchCalibration_x1 -= touchCalibration_x0;
touchCalibration_y1 -= touchCalibration_y0;
if(touchCalibration_x0 == 0) touchCalibration_x0 = 1;
if(touchCalibration_x1 == 0) touchCalibration_x1 = 1;
if(touchCalibration_y0 == 0) touchCalibration_y0 = 1;
if(touchCalibration_y1 == 0) touchCalibration_y1 = 1;
if(touchCalibration_x0 == 0) touchCalibration_x0 = 1;
if(touchCalibration_x1 == 0) touchCalibration_x1 = 1;
if(touchCalibration_y0 == 0) touchCalibration_y0 = 1;
if(touchCalibration_y1 == 0) touchCalibration_y1 = 1;
#ifdef FONT_FS_AVAILABLE
/***************************************************************************************
** Function name: loadFont
** Description: loads parameters from a font vlw file
*************************************************************************************x*/
void TFT_eSPI::loadFont(String fontName, fs::FS &ffs)
{
fontFS = ffs;
loadFont(fontName, false);
}
#endif
/***************************************************************************************
** Function name: loadFont
** Description: loads parameters from a font vlw file
*************************************************************************************x*/
void TFT_eSPI::loadFont(String fontName, bool flash)
{
/*
The vlw font format does not appear to be documented anywhere, so some reverse
engineering has been applied!
Next are gCount sets of values for each glyph, each set comprises 7 int32t parameters (28 bytes):
1. Glyph Unicode stored as a 32 bit value
2. Height of bitmap bounding box
3. Width of bitmap bounding box
4. gxAdvance for cursor (setWidth in Processing)
5. dY = distance from cursor baseline to top of glyph bitmap (signed value +ve = up)
6. dX = distance from cursor to left side of glyph bitmap (signed value -ve = left)
7. padding value, typically 0
The bitmaps start next at 24 + (28 * gCount) bytes from the start of the file.
Each pixel is 1 byte, an 8 bit Alpha value which represents the transparency from
0xFF foreground colour, 0x00 background. The sketch uses a linear interpolation
between the foreground and background RGB component colours. e.g.
pixelRed = ((fgRed * alpha) + (bgRed * (255 - alpha))/255
To gain a performance advantage fixed point arithmetic is used with rounding and
division by 256 (shift right 8 bits is faster).
*/
if (fontLoaded) unloadFont();
#ifdef FONT_FS_AVAILABLE
if (fontName == "") fs_font = false;
else { fontPtr = nullptr; fs_font = true; }
if (fs_font) {
spiffs = flash; // true if font is in SPIFFS
// These next gFont values might be updated when the Metrics are fetched
gFont.maxAscent = gFont.ascent; // Determined from metrics
gFont.maxDescent = gFont.descent; // Determined from metrics
gFont.yAdvance = gFont.ascent + gFont.descent;
gFont.spaceWidth = gFont.yAdvance / 4; // Guess at space width
fontLoaded = true;
#ifdef SHOW_ASCENT_DESCENT
Serial.print("ascent = "); Serial.println(gFont.ascent);
Serial.print("descent = "); Serial.println(gFont.descent);
#endif
#ifdef FONT_FS_AVAILABLE
if (fs_font) fontFile.seek(headerPtr, fs::SeekSet);
#endif
uint16_t gNum = 0;
// Different glyph sets have different ascent values not always based on "d", so we could get
// the maximum glyph ascent by checking all characters. BUT this method can generate bad values
// for non-existant glyphs, so we will reply on processing for the value and disable this code for now...
/*
if (gdY[gNum] > gFont.maxAscent)
{
// Try to avoid UTF coding values and characters that tend to give duff values
if (((gUnicode[gNum] > 0x20) && (gUnicode[gNum] < 0x7F)) || (gUnicode[gNum] > 0xA0))
{
gFont.maxAscent = gdY[gNum];
#ifdef SHOW_ASCENT_DESCENT
Serial.print("Unicode = 0x"); Serial.print(gUnicode[gNum], HEX); Serial.print(", maxAscent = "); Serial.println
#endif
}
}
*/
// Different glyph sets have different descent values not always based on "p", so get maximum glyph descent
if (((int16_t)gHeight[gNum] - (int16_t)gdY[gNum]) > gFont.maxDescent)
{
// Avoid UTF coding values and characters that tend to give duff values
if (((gUnicode[gNum] > 0x20) && (gUnicode[gNum] < 0xA0) && (gUnicode[gNum] != 0x7F)) || (gUnicode[gNum
{
gFont.maxDescent = gHeight[gNum] - gdY[gNum];
#ifdef SHOW_ASCENT_DESCENT
#endif
}
}
gBitmap[gNum] = bitmapPtr;
gNum++;
yield();
}
if (gHeight)
{
free(gHeight);
gHeight = NULL;
}
if (gWidth)
{
free(gWidth);
gWidth = NULL;
}
if (gxAdvance)
{
free(gxAdvance);
gxAdvance = NULL;
}
if (gdY)
{
free(gdY);
gdY = NULL;
}
if (gdX)
{
free(gdX);
gdX = NULL;
}
if (gBitmap)
{
free(gBitmap);
gBitmap = NULL;
}
gFont.gArray = nullptr;
#ifdef FONT_FS_AVAILABLE
if (fs_font && fontFile) fontFile.close();
#endif
fontLoaded = false;
}
/***************************************************************************************
** Function name: readInt32
** Description: Get a 32 bit integer from the font file
*************************************************************************************x*/
uint32_t TFT_eSPI::readInt32(void)
{
uint32_t val = 0;
#ifdef FONT_FS_AVAILABLE
if (fs_font) {
val |= fontFile.read() << 24;
val |= fontFile.read() << 16;
val |= fontFile.read() << 8;
val |= fontFile.read();
}
else
#endif
{
val |= pgm_read_byte(fontPtr++) << 24;
val |= pgm_read_byte(fontPtr++) << 16;
val |= pgm_read_byte(fontPtr++) << 8;
val |= pgm_read_byte(fontPtr++);
}
return val;
}
/***************************************************************************************
** Function name: getUnicodeIndex
** Description: Get the font file index of a Unicode character
*************************************************************************************x*/
bool TFT_eSPI::getUnicodeIndex(uint16_t unicode, uint16_t *index)
{
for (uint16_t i = 0; i < gFont.gCount; i++)
{
if (gUnicode[i] == unicode)
{
*index = i;
return true;
}
}
return false;
}
/***************************************************************************************
** Function name: drawGlyph
** Description: Write a character to the TFT cursor position
*************************************************************************************x*/
// Expects file to be open
void TFT_eSPI::drawGlyph(uint16_t code)
{
uint16_t fg = textcolor;
uint16_t bg = textbgcolor;
if (code == '\n') {
cursor_x = 0;
cursor_y += gFont.yAdvance;
if (textwrapY && (cursor_y >= height())) cursor_y = 0;
return;
}
}
uint16_t gNum = 0;
bool found = getUnicodeIndex(code, &gNum);
if (found)
{
#ifdef FONT_FS_AVAILABLE
if (fs_font)
{
fontFile.seek(gBitmap[gNum], fs::SeekSet); // This is taking >30ms for a significant position shift
pbuffer = (uint8_t*)malloc(gWidth[gNum]);
}
#endif
int16_t xs = cx;
uint32_t dl = 0;
uint8_t pixel;
if (pixel)
{
if (pixel != 0xFF)
{
if (dl) {
if (dl==1) drawPixel(xs, y + cy, fg);
else drawFastHLine( xs, y + cy, dl, fg);
dl = 0;
}
if (getColor) bg = getColor(x + cx, y + cy);
drawPixel(x + cx, y + cy, alphaBlend(pixel, fg, bg));
}
else
{
if (dl==0) xs = x + cx;
dl++;
}
}
else
{
if (dl) { drawFastHLine( xs, y + cy, dl, fg); dl = 0; }
}
}
if (dl) { drawFastHLine( xs, y + cy, dl, fg); dl = 0; }
}
if (pbuffer) free(pbuffer);
cursor_x += gxAdvance[gNum];
endWrite();
}
else
{
// Not a Unicode in font so draw a rectangle and move on cursor
drawRect(cursor_x, cursor_y + gFont.maxAscent - gFont.ascent, gFont.spaceWidth, gFont.ascent, fg);
cursor_x += gFont.spaceWidth + 1;
}
}
/***************************************************************************************
** Function name: showFont
** Description: Page through all characters in font, td ms between screens
*************************************************************************************x*/
void TFT_eSPI::showFont(uint32_t td)
{
if(!fontLoaded) return;
fillScreen(textbgcolor);
cursorY += gFont.yAdvance;
if (cursorY + gFont.maxAscent + gFont.descent >= height()) {
cursorX = -gdX[i];
cursorY = 0;
delay(timeDelay);
timeDelay = td;
fillScreen(textbgcolor);
}
}
setCursor(cursorX, cursorY);
drawGlyph(gUnicode[i]);
cursorX += gxAdvance[i];
//cursorX += printToSprite( cursorX, cursorY, i );
yield();
}
delay(timeDelay);
fillScreen(textbgcolor);
//fontFile.close();
}
/***************************************************************************************
** Function name: BUTTON
** Description: Code for the GFX button UI element
***************************************************************************************/
TFT_eSPI_Button::TFT_eSPI_Button(void) {
_gfx = nullptr;
_xd = 0;
_yd = 0;
_textdatum = MC_DATUM;
_label[9] = '\0';
}
/***************************************************************************************
** Function name: initButton
** Description: Classic initButton() function: pass center & size
***************************************************************************************/
void TFT_eSPI_Button::initButton(
TFT_eSPI *gfx, int16_t x, int16_t y, uint16_t w, uint16_t h,
uint16_t outline, uint16_t fill, uint16_t textcolor,
char *label, uint8_t textsize)
{
// Tweak arguments and pass to the newer initButtonUL() function...
initButtonUL(gfx, x - (w / 2), y - (h / 2), w, h, outline, fill,
textcolor, label, textsize);
}
/***************************************************************************************
** Function name: initButtonUL
** Description: Newer function instead accepts upper-left corner & size
***************************************************************************************/
void TFT_eSPI_Button::initButtonUL(
TFT_eSPI *gfx, int16_t x1, int16_t y1, uint16_t w, uint16_t h,
uint16_t outline, uint16_t fill, uint16_t textcolor,
char *label, uint8_t textsize)
{
_x1 = x1;
_y1 = y1;
_w = w;
_h = h;
_outlinecolor = outline;
_fillcolor = fill;
_textcolor = textcolor;
_textsize = textsize;
_gfx = gfx;
strncpy(_label, label, 9);
}
/***************************************************************************************
** Function name: setLabelDatum
** Description: Adjust text datum and x, y deltas
***************************************************************************************/
void TFT_eSPI_Button::setLabelDatum(int16_t x_delta, int16_t y_delta, uint8_t datum)
{
_xd = x_delta;
_yd = y_delta;
_textdatum = datum;
}
/***************************************************************************************
** Function name: drawButton
** Description: Draw Button on screen
***************************************************************************************/
void TFT_eSPI_Button::drawButton(bool inverted, String long_name) {
uint16_t fill, outline, text;
if(!inverted) {
fill = _fillcolor;
outline = _outlinecolor;
text = _textcolor;
} else {
fill = _textcolor;
outline = _outlinecolor;
text = _fillcolor;
}
_gfx->setTextColor(text, fill);
_gfx->setTextSize(_textsize);
if (long_name == "")
_gfx->drawString(_label, _x1 + (_w/2) + _xd, _y1 + (_h/2) - 4 + _yd);
else
_gfx->drawString(long_name, _x1 + (_w/2) + _xd, _y1 + (_h/2) - 4 + _yd);
_gfx->setTextDatum(tempdatum);
_gfx->setTextPadding(tempPadding);
}
void TFT_eSPI_Button::press(bool p) {
laststate = currstate;
currstate = p;
}
/***************************************************************************************
** Function name: isPressed()
** Description: return (currstate)
***************************************************************************************/
bool TFT_eSPI_Button::isPressed() { return currstate; }
/***************************************************************************************
** Function name: justPressed()
** Description: return (currstate && !laststate)
***************************************************************************************/
bool TFT_eSPI_Button::justPressed() { return (currstate && !laststate); }
/***************************************************************************************
** Function name: justReleased()
** Description: return (currstate && !laststate)
***************************************************************************************/
bool TFT_eSPI_Button::justReleased() { return (!currstate && laststate); }
/***************************************************************************************
** Function name: TFT_eSprite
** Description: Class constructor
***************************************************************************************/
// The following class creates Sprites in RAM, graphics can then be drawn in the Sprite
// and rendered quickly onto the TFT screen. The class inherits the graphics functions
// from the TFT_eSPI class. Some functions are overridden by this class so that the
// graphics are written to the Sprite rather than the TFT.
// Coded by Bodmer, see license file in root folder
// Color bytes are swapped when writing to RAM, this introduces a small overhead but
// there is a nett performance gain by using swapped bytes.
TFT_eSprite::TFT_eSprite(TFT_eSPI *tft)
{
_tft = tft; // Pointer to tft class so we can call member functions
_iwidth = 0; // Initialise width and height to 0 (it does not exist yet)
_iheight = 0;
_bpp = 16;
_swapBytes = false; // Do not swap pushImage colour bytes by default
_created = false;
_vpOoB = true;
_colorMap = nullptr;
_psram_enable = true;
}
/***************************************************************************************
** Function name: createSprite
** Description: Create a sprite (bitmap) of defined width and height
***************************************************************************************/
// cast returned value to (uint8_t*) for 8 bit or (uint16_t*) for 16 bit colours
void* TFT_eSprite::createSprite(int16_t w, int16_t h, uint8_t frames)
{
cursor_x = 0;
cursor_y = 0;
if (_img8)
{
_created = true;
rotation = 0;
setViewport(0, 0, _dwidth, _dheight);
setPivot(_iwidth/2, _iheight/2);
return _img8_1;
}
return nullptr;
}
/***************************************************************************************
** Function name: getPointer
** Description: Returns pointer to start of sprite memory area
***************************************************************************************/
void* TFT_eSprite::getPointer(void)
{
if (!_created) return nullptr;
return _img8_1;
}
/***************************************************************************************
** Function name: created
** Description: Returns true if sprite has been created
***************************************************************************************/
bool TFT_eSprite::created(void)
{
return _created;
}
/***************************************************************************************
** Function name: ~TFT_eSprite
** Description: Class destructor
***************************************************************************************/
TFT_eSprite::~TFT_eSprite(void)
{
deleteSprite();
#ifdef SMOOTH_FONT
if(fontLoaded) unloadFont();
#endif
}
/***************************************************************************************
** Function name: callocSprite
** Description: Allocate a memory area for the Sprite and return pointer
***************************************************************************************/
void* TFT_eSprite::callocSprite(int16_t w, int16_t h, uint8_t frames)
{
// Add one extra "off screen" pixel to point out-of-bounds setWindow() coordinates
// this means push/writeColor functions do not need additional bounds checks and
// hence will run faster in normal circumstances.
uint8_t* ptr8 = nullptr;
if (_bpp == 16)
{
#if defined (ESP32) && defined (CONFIG_SPIRAM_SUPPORT)
if ( psramFound() && _psram_enable && !_tft->DMA_Enabled)
{
ptr8 = ( uint8_t*) ps_calloc(frames * w * h + frames, sizeof(uint16_t));
//Serial.println("PSRAM");
}
else
#endif
{
ptr8 = ( uint8_t*) calloc(frames * w * h + frames, sizeof(uint16_t));
//Serial.println("Normal RAM");
}
}
else if (_bpp == 8)
{
#if defined (ESP32) && defined (CONFIG_SPIRAM_SUPPORT)
if ( psramFound() && _psram_enable ) ptr8 = ( uint8_t*) ps_calloc(frames * w * h + frames, sizeof(uint8_t));
else
#endif
ptr8 = ( uint8_t*) calloc(frames * w * h + frames, sizeof(uint8_t));
}
else if (_bpp == 4)
{
w = (w+1) & 0xFFFE; // width needs to be multiple of 2, with an extra "off screen" pixel
_iwidth = w;
#if defined (ESP32) && defined (CONFIG_SPIRAM_SUPPORT)
if ( psramFound() && _psram_enable ) ptr8 = ( uint8_t*) ps_calloc(((frames * w * h) >> 1) + frames, sizeof(uin
else
#endif
ptr8 = ( uint8_t*) calloc(((frames * w * h) >> 1) + frames, sizeof(uint8_t));
}
w = (w+7) & 0xFFF8; // width should be the multiple of 8 bits to be compatible with epdpaint
_iwidth = w; // _iwidth is rounded up to be multiple of 8, so might not be = _dwidth
_bitwidth = w; // _bitwidth will not be rotated whereas _iwidth may be
return ptr8;
}
/***************************************************************************************
** Function name: createPalette (from RAM array)
** Description: Set a palette for a 4-bit per pixel sprite
***************************************************************************************/
void TFT_eSprite::createPalette(uint16_t colorMap[], uint8_t colors)
{
if (_colorMap != nullptr)
{
free(_colorMap);
}
if (colorMap == nullptr)
{
// Create a color map using the default FLASH map
createPalette(default_4bit_palette);
return;
}
if ( f == 2 ) _img8 = _img8_2;
else _img8 = _img8_1;
return _img8;
}
/***************************************************************************************
** Function name: setColorDepth
** Description: Set bits per pixel for colour (1, 8 or 16)
***************************************************************************************/
void* TFT_eSprite::setColorDepth(int8_t b)
{
// Do not re-create the sprite if the colour depth does not change
if (_bpp == b) return _img8_1;
return nullptr;
}
/***************************************************************************************
** Function name: getColorDepth
** Description: Get bits per pixel for colour (1, 8 or 16)
***************************************************************************************/
int8_t TFT_eSprite::getColorDepth(void)
{
if (_created) return _bpp;
else return 0;
}
/***************************************************************************************
** Function name: setBitmapColor
** Description: Set the 1bpp foreground foreground and background colour
***************************************************************************************/
void TFT_eSprite::setBitmapColor(uint16_t c, uint16_t b)
{
if (c == b) b = ~c;
_tft->bitmap_fg = c;
_tft->bitmap_bg = b;
}
/***************************************************************************************
** Function name: setPaletteColor
** Description: Set the 4bpp palette color at the given index
***************************************************************************************/
void TFT_eSprite::setPaletteColor(uint8_t index, uint16_t color)
{
if (_colorMap == nullptr || index > 15) return; // out of bounds
_colorMap[index] = color;
}
/***************************************************************************************
** Function name: getPaletteColor
** Description: Return the palette color at 4bpp index, or 0 on error.
***************************************************************************************/
uint16_t TFT_eSprite::getPaletteColor(uint8_t index)
{
if (_colorMap == nullptr || index > 15) return 0; // out of bounds
return _colorMap[index];
}
/***************************************************************************************
** Function name: deleteSprite
** Description: Delete the sprite to free up memory (RAM)
***************************************************************************************/
void TFT_eSprite::deleteSprite(void)
{
if (_colorMap != nullptr)
{
free(_colorMap);
_colorMap = nullptr;
}
if (_created)
{
free(_img8_1);
_img8 = nullptr;
_created = false;
_vpOoB = true; // TFT_eSPI class write() uses this to check for valid sprite
}
}
/***************************************************************************************
** Function name: pushRotated - Fast fixed point integer maths version
** Description: Push rotated Sprite to TFT screen
***************************************************************************************/
#define FP_SCALE 10
bool TFT_eSprite::pushRotated(int16_t angle, uint32_t transp)
{
if ( !_created || _tft->_vpOoB) return false;
// Get the bounding box of this rotated source Sprite relative to Sprite pivot
if ( !getRotatedBounds(angle, &min_x, &min_y, &max_x, &max_y) ) return false;
if (transp != 0x00FFFFFF) {
if (_bpp == 4) tpcolor = _colorMap[transp & 0x0F];
tpcolor = tpcolor>>8 | tpcolor<<8; // Working with swapped color bytes
}
_tft->startWrite(); // Avoid transaction overhead for every tft pixel
// Scan destination bounding box and fetch transformed pixels from source Sprite
for (int32_t y = min_y; y <= max_y; y++, yt++) {
int32_t x = min_x;
uint32_t xs = (_cosra * xt - (_sinra * yt - (_xPivot << FP_SCALE)) + (1 << (FP_SCALE - 1)));
uint32_t ys = (_sinra * xt + (_cosra * yt + (_yPivot << FP_SCALE)) + (1 << (FP_SCALE - 1)));
while ((xs >= xe || ys >= ye) && x < max_x) { x++; xs += _cosra; ys += _sinra; }
if (x == max_x) continue;
uint32_t pixel_count = 0;
do {
uint32_t rp;
int32_t xp = xs >> FP_SCALE;
int32_t yp = ys >> FP_SCALE;
if (_bpp == 16) {rp = _img[xp + yp * _iwidth]; }
else { rp = readPixel(xp, yp); rp = (uint16_t)(rp>>8 | rp<<8); }
if (tpcolor == rp) {
if (pixel_count) {
// TFT window is already clipped, so this is faster than pushImage()
_tft->setWindow(x - pixel_count, y, x, y);
_tft->pushPixels(sline_buffer, pixel_count);
pixel_count = 0;
}
}
else {
sline_buffer[pixel_count++] = rp;
}
} while (++x < max_x && (xs += _cosra) < xe && (ys += _sinra) < ye);
if (pixel_count) {
// TFT window is already clipped, so this is faster than pushImage()
_tft->setWindow(x - pixel_count, y, x, y);
_tft->pushPixels(sline_buffer, pixel_count);
}
}
return true;
}
/***************************************************************************************
** Function name: pushRotated - Fast fixed point integer maths version
** Description: Push a rotated copy of the Sprite to another Sprite
***************************************************************************************/
// Not compatible with 4bpp
bool TFT_eSprite::pushRotated(TFT_eSprite *spr, int16_t angle, uint32_t transp)
{
if ( !_created || _bpp == 4) return false; // Check this Sprite is created
if ( !spr->_created || spr->_bpp == 4) return false; // Ckeck destination Sprite is created
if (transp != 0x00FFFFFF) {
if (_bpp == 4) tpcolor = _colorMap[transp & 0x0F];
tpcolor = tpcolor>>8 | tpcolor<<8; // Working with swapped color bytes
}
// Scan destination bounding box and fetch transformed pixels from source Sprite
for (int32_t y = min_y; y <= max_y; y++, yt++) {
int32_t x = min_x;
uint32_t xs = (_cosra * xt - (_sinra * yt - (_xPivot << FP_SCALE)) + (1 << (FP_SCALE - 1)));
uint32_t ys = (_sinra * xt + (_cosra * yt + (_yPivot << FP_SCALE)) + (1 << (FP_SCALE - 1)));
while ((xs >= xe || ys >= ye) && x < max_x) { x++; xs += _cosra; ys += _sinra; }
if (x == max_x) continue;
uint32_t pixel_count = 0;
do {
uint32_t rp;
int32_t xp = xs >> FP_SCALE;
int32_t yp = ys >> FP_SCALE;
if (_bpp == 16) rp = _img[xp + yp * _iwidth];
else { rp = readPixel(xp, yp); rp = (uint16_t)(rp>>8 | rp<<8); }
if (tpcolor == rp) {
if (pixel_count) {
spr->pushImage(x - pixel_count, y, pixel_count, 1, sline_buffer);
pixel_count = 0;
}
}
else {
sline_buffer[pixel_count++] = rp;
}
} while (++x < max_x && (xs += _cosra) < xe && (ys += _sinra) < ye);
if (pixel_count) spr->pushImage(x - pixel_count, y, pixel_count, 1, sline_buffer);
}
spr->setSwapBytes(oldSwapBytes);
return true;
}
/***************************************************************************************
** Function name: getRotatedBounds
** Description: Get TFT bounding box of a rotated Sprite wrt pivot
***************************************************************************************/
bool TFT_eSprite::getRotatedBounds(int16_t angle, int16_t *min_x, int16_t *min_y,
int16_t *max_x, int16_t *max_y)
{
// Get the bounding box of this rotated source Sprite relative to Sprite pivot
getRotatedBounds(angle, width(), height(), _xPivot, _yPivot, min_x, min_y, max_x, max_y);
// Move bounding box so source Sprite pivot coincides with TFT pivot
*min_x += _tft->_xPivot;
*max_x += _tft->_xPivot;
*min_y += _tft->_yPivot;
*max_y += _tft->_yPivot;
return true;
}
/***************************************************************************************
** Function name: getRotatedBounds
** Description: Get destination Sprite bounding box of a rotated Sprite wrt pivot
***************************************************************************************/
bool TFT_eSprite::getRotatedBounds(TFT_eSprite *spr, int16_t angle, int16_t *min_x, int16_t *min_y,
int16_t *max_x, int16_t *max_y)
{
// Get the bounding box of this rotated source Sprite relative to Sprite pivot
getRotatedBounds(angle, width(), height(), _xPivot, _yPivot, min_x, min_y, max_x, max_y);
// Move bounding box so source Sprite pivot coincides with destination Sprite pivot
*min_x += spr->_xPivot;
*max_x += spr->_xPivot;
*min_y += spr->_yPivot;
*max_y += spr->_yPivot;
return true;
}
/***************************************************************************************
** Function name: rotatedBounds
** Description: Get bounding box of a rotated Sprite wrt pivot
***************************************************************************************/
void TFT_eSprite::getRotatedBounds(int16_t angle, int16_t w, int16_t h, int16_t xp, int16_t yp,
int16_t *min_x, int16_t *min_y, int16_t *max_x, int16_t *max_y)
{
// Trig values for the rotation
float radAngle = -angle * 0.0174532925; // Convert degrees to radians
float sina = sin(radAngle);
float cosa = cos(radAngle);
*max_x = x0+2;
if (x1 > *max_x) *max_x = x1+2;
if (x2 > *max_x) *max_x = x2+2;
if (x3 > *max_x) *max_x = x3+2;
*min_y = y0-2;
if (y1 < *min_y) *min_y = y1-2;
if (y2 < *min_y) *min_y = y2-2;
if (y3 < *min_y) *min_y = y3-2;
*max_y = y0+2;
if (y1 > *max_y) *max_y = y1+2;
if (y2 > *max_y) *max_y = y2+2;
if (y3 > *max_y) *max_y = y3+2;
if (_bpp == 16)
{
bool oldSwapBytes = _tft->getSwapBytes();
_tft->setSwapBytes(false);
_tft->pushImage(x, y, _dwidth, _dheight, _img );
_tft->setSwapBytes(oldSwapBytes);
}
else if (_bpp == 4)
{
_tft->pushImage(x, y, _dwidth, _dheight, _img4, false, _colorMap);
}
else _tft->pushImage(x, y, _dwidth, _dheight, _img8, (bool)(_bpp == 8));
}
/***************************************************************************************
** Function name: pushSprite
** Description: Push the sprite to the TFT at x, y with transparent colour
***************************************************************************************/
void TFT_eSprite::pushSprite(int32_t x, int32_t y, uint16_t transp)
{
if (!_created) return;
if (_bpp == 16)
{
bool oldSwapBytes = _tft->getSwapBytes();
_tft->setSwapBytes(false);
_tft->pushImage(x, y, _dwidth, _dheight, _img, transp );
_tft->setSwapBytes(oldSwapBytes);
}
else if (_bpp == 8)
{
transp = (uint8_t)((transp & 0xE000)>>8 | (transp & 0x0700)>>6 | (transp & 0x0018)>>3);
_tft->pushImage(x, y, _dwidth, _dheight, _img8, (uint8_t)transp, (bool)true);
}
else if (_bpp == 4)
{
_tft->pushImage(x, y, _dwidth, _dheight, _img4, (uint8_t)(transp & 0x0F), false, _colorMap);
}
else _tft->pushImage(x, y, _dwidth, _dheight, _img8, 0, (bool)false);
}
/***************************************************************************************
** Function name: pushToSprite
** Description: Push the sprite to another sprite at x, y
***************************************************************************************/
// Note: The following sprite to sprite colour depths are currently supported:
// Source Destination
// 16bpp -> 16bpp
// 16bpp -> 8bpp
// 8bpp -> 8bpp
// 4bpp -> 4bpp (note: color translation depends on the 2 sprites pallete colors)
// 1bpp -> 1bpp (note: color translation depends on the 2 sprites bitmap colors)
return true;
}
/***************************************************************************************
** Function name: pushToSprite
** Description: Push the sprite to another sprite at x, y with transparent colour
***************************************************************************************/
// Note: The following sprite to sprite colour depths are currently supported:
// Source Destination
// 16bpp -> 16bpp
// 16bpp -> 8bpp
// 8bpp -> 8bpp
// 1bpp -> 1bpp
// Scan destination bounding box and fetch transformed pixels from source Sprite
for (int32_t ys = 0; ys < height(); ys++) {
int32_t ox = x;
uint32_t pixel_count = 0;
if (transp == rp) {
if (pixel_count) {
dspr->pushImage(ox, y, pixel_count, 1, sline_buffer, _bpp);
ox += pixel_count;
pixel_count = 0;
}
else ox++;
}
else {
sline_buffer[pixel_count++] = rp;
}
}
if (pixel_count) dspr->pushImage(ox, y, pixel_count, 1, sline_buffer);
y++;
}
dspr->setSwapBytes(oldSwapBytes);
return true;
}
/***************************************************************************************
** Function name: pushSprite
** Description: Push a cropped sprite to the TFT at tx, ty
***************************************************************************************/
bool TFT_eSprite::pushSprite(int32_t tx, int32_t ty, int32_t sx, int32_t sy, int32_t sw, int32_t sh)
{
if (!_created) return false;
if (_bpp == 16)
{
bool oldSwapBytes = _tft->getSwapBytes();
_tft->setSwapBytes(false);
_tft->setSwapBytes(oldSwapBytes);
}
else if (_bpp == 8)
{
// Check if a faster block copy to screen is possible
if ( sx == 0 && sw == _dwidth)
_tft->pushImage(tx, ty, sw, sh, _img8 + _iwidth * _ys, (bool)true );
else // Render line by line
while (sh--)
_tft->pushImage(tx, ty++, sw, 1, _img8 + _xs + _iwidth * _ys++, (bool)true );
}
else if (_bpp == 4)
{
// Check if a faster block copy to screen is possible
if ( sx == 0 && sw == _dwidth)
_tft->pushImage(tx, ty, sw, sh, _img4 + (_iwidth>>1) * _ys, false, _colorMap );
else // Render line by line
{
int32_t ds = _xs&1; // Odd x start pixel
return true;
}
/***************************************************************************************
** Function name: readPixelValue
** Description: Read the color map index of a pixel at defined coordinates
***************************************************************************************/
uint16_t TFT_eSprite::readPixelValue(int32_t x, int32_t y)
{
if (_vpOoB || !_created) return 0xFF;
x+= _xDatum;
y+= _yDatum;
// Range checking
if ((x < _vpX) || (y < _vpY) ||(x >= _vpW) || (y >= _vpH)) return 0xFF;
if (_bpp == 16)
{
// Return the pixel colour
return readPixel(x - _xDatum, y - _yDatum);
}
if (_bpp == 8)
{
// Return the pixel byte value
return _img8[x + y * _iwidth];
}
if (_bpp == 4)
{
if (x >= _dwidth) return 0xFF;
if ((x & 0x01) == 0)
return _img4[((x+y*_iwidth)>>1)] >> 4; // even index = bits 7 .. 4
else
return _img4[((x+y*_iwidth)>>1)] & 0x0F; // odd index = bits 3 .. 0.
}
if (_bpp == 1)
{
// Note: _dwidth and _dheight bounds not checked (rounded up -iwidth and _iheight used)
if (rotation == 1)
{
uint16_t tx = x;
x = _dheight - y - 1;
y = tx;
}
else if (rotation == 2)
{
x = _dwidth - x - 1;
y = _dheight - y - 1;
}
else if (rotation == 3)
{
uint16_t tx = x;
x = y;
y = _dwidth - tx - 1;
}
// Return 1 or 0
return (_img8[(x + y * _bitwidth)>>3] >> (7-(x & 0x7))) & 0x01;
}
return 0;
}
/***************************************************************************************
** Function name: readPixel
** Description: Read 565 colour of a pixel at defined coordinates
***************************************************************************************/
uint16_t TFT_eSprite::readPixel(int32_t x, int32_t y)
{
if (_vpOoB || !_created) return 0xFFFF;
x+= _xDatum;
y+= _yDatum;
// Range checking
if ((x < _vpX) || (y < _vpY) ||(x >= _vpW) || (y >= _vpH)) return 0xFFFF;
if (_bpp == 16)
{
uint16_t color = _img[x + y * _iwidth];
return (color >> 8) | (color << 8);
}
if (_bpp == 8)
{
uint16_t color = _img8[x + y * _iwidth];
if (color != 0)
{
uint8_t blue[] = {0, 11, 21, 31};
color = (color & 0xE0)<<8 | (color & 0xC0)<<5
| (color & 0x1C)<<6 | (color & 0x1C)<<3
| blue[color & 0x03];
}
return color;
}
if (_bpp == 4)
{
if (x >= _dwidth) return 0xFFFF;
uint16_t color;
if ((x & 0x01) == 0)
color = _colorMap[_img4[((x+y*_iwidth)>>1)] >> 4]; // even index = bits 7 .. 4
else
color = _colorMap[_img4[((x+y*_iwidth)>>1)] & 0x0F]; // odd index = bits 3 .. 0.
return color;
}
PI_CLIP;
if(_swapBytes)
{
while (dh--)
{
// Fast copy with a 1 byte shift
memcpy(ptrs+1, ptro, (dw<<1) - 1);
// Now correct just the even numbered bytes
for (int32_t xp = 0; xp < (dw<<1); xp+=2)
{
ptrs[xp] = ptro[xp+1];;
}
ptro += w<<1;
ptrs += _iwidth<<1;
}
}
else
{
while (dh--)
{
memcpy(ptrs, ptro, dw<<1);
ptro += w << 1;
ptrs += _iwidth << 1;
}
}
}
else if (_bpp == 8 && sbpp == 8) // Plot a 8 bpp image into a 8 bpp Sprite
{
// Pointer within original image
uint8_t *ptro = (uint8_t *)data + (dx + dy * w);
// Pointer within sprite image
uint8_t *ptrs = (uint8_t *)_img + (x + y * _iwidth);
while (dh--)
{
memcpy(ptrs, ptro, dw);
ptro += w;
ptrs += _iwidth;
}
}
else if (_bpp == 8) // Plot a 16 bpp image into a 8 bpp Sprite
{
uint16_t lastColor = 0;
uint8_t color8 = 0;
for (int32_t yp = dy; yp < dy + dh; yp++)
{
int32_t xyw = x + y * _iwidth;
int32_t dxypw = dx + yp * w;
for (int32_t xp = dx; xp < dx + dw; xp++)
{
uint16_t color = data[dxypw++];
if (color != lastColor) {
// When data source is a sprite, the bytes are already swapped
if(!_swapBytes) color8 = (uint8_t)((color & 0xE0) | (color & 0x07)<<2 | (color & 0x1800)>>11);
else color8 = (uint8_t)((color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3);
}
lastColor = color;
_img8[xyw++] = color8;
}
y++;
}
}
else if (_bpp == 4)
{
// The image is assumed to be 4 bit, where each byte corresponds to two pixels.
// much faster when aligned to a byte boundary, because the alternative is slower, requiring
// tedious bit operations.
uint8_t *ptr = (uint8_t *)data;
if ((x & 0x01) == 0 && (dx & 0x01) == 0 && (dw & 0x01) == 0)
{
x = (x >> 1) + y * sWidth;
dw = (dw >> 1);
dx = (dx >> 1) + dy * (w>>1);
while (dh--)
{
memcpy(_img4 + x, ptr + dx, dw);
dx += (w >> 1);
x += sWidth;
}
}
else // not optimized
{
for (int32_t yp = dy; yp < dy + dh; yp++)
{
int32_t ox = x;
for (int32_t xp = dx; xp < dx + dw; xp++)
{
uint32_t color;
if ((xp & 0x01) == 0)
color = (ptr[((xp+yp*w)>>1)] & 0xF0) >> 4; // even index = bits 7 .. 4
else
color = ptr[((xp-1+yp*w)>>1)] & 0x0F; // odd index = bits 3 .. 0.
drawPixel(ox, y, color);
ox++;
}
y++;
}
}
}
else // 1bpp
{
// Plot a 1bpp image into a 1bpp Sprite
uint32_t ww = (w+7)>>3; // Width of source image line in bytes
uint8_t *ptr = (uint8_t *)data;
for (int32_t yp = dy; yp < dy + dh; yp++)
{
uint32_t yw = yp * ww; // Byte starting the line containing source pixel
int32_t ox = x;
for (int32_t xp = dx; xp < dx + dw; xp++)
{
uint16_t readPixel = (ptr[(xp>>3) + yw] & (0x80 >> (xp & 0x7)) );
drawPixel(ox++, y, readPixel);
}
y++;
}
}
}
/***************************************************************************************
** Function name: pushImage
** Description: push 565 colour FLASH (PROGMEM) image into a defined area
***************************************************************************************/
void TFT_eSprite::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, const uint16_t *data)
{
#ifdef ESP32
pushImage(x, y, w, h, (uint16_t*) data);
#else
// Partitioned memory FLASH processor
if (data == nullptr || !_created) return;
PI_CLIP;
else if (_bpp == 4)
{
#ifdef TFT_eSPI_DEBUG
Serial.println("TFT_eSprite::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, const uint16_t *data) not imple
#endif
return;
}
while (dh--) {
int32_t odx = dx;
int32_t ox = x;
while (odx < dx + dw) {
uint8_t pbyte = pgm_read_byte(ptr + (odx>>3));
uint8_t mask = 0x80 >> (odx & 7);
while (mask) {
uint8_t p = pbyte & mask;
mask = mask >> 1;
drawPixel(ox++, y, p);
odx++;
}
}
ptr += bsw;
y++;
}
}
#endif // if ESP32 check
}
/***************************************************************************************
** Function name: setWindow
** Description: Set the bounds of a window in the sprite
***************************************************************************************/
// Intentionally not constrained to viewport area, does not manage 1bpp rotations
void TFT_eSprite::setWindow(int32_t x0, int32_t y0, int32_t x1, int32_t y1)
{
if (x0 > x1) swap_coord(x0, x1);
if (y0 > y1) swap_coord(y0, y1);
int32_t w = width();
int32_t h = height();
_xs = x0;
_ys = y0;
_xe = x1;
_ye = y1;
}
_xptr = _xs;
_yptr = _ys;
}
/***************************************************************************************
** Function name: pushColor
** Description: Send a new pixel to the set window
***************************************************************************************/
void TFT_eSprite::pushColor(uint32_t color)
{
if (!_created ) return;
else if (_bpp == 8)
_img8[_xptr + _yptr * _iwidth] = (uint8_t )((color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3
else if (_bpp == 4)
{
uint8_t c = (uint8_t)color & 0x0F;
if ((_xptr & 0x01) == 0) {
_img4[(_xptr + _yptr * _iwidth)>>1] = (c << 4) | (_img4[(_xptr + _yptr * _iwidth)>>1] & 0x0F); // new colo
}
else {
_img4[(_xptr + _yptr * _iwidth)>>1] = (_img4[(_xptr + _yptr * _iwidth)>>1] & 0xF0) | c; // new color is the
}
}
// Increment x
_xptr++;
}
/***************************************************************************************
** Function name: pushColor
** Description: Send a "len" new pixels to the set window
***************************************************************************************/
void TFT_eSprite::pushColor(uint32_t color, uint16_t len)
{
if (!_created ) return;
uint16_t pixelColor;
if (_bpp == 16)
pixelColor = (uint16_t) (color >> 8) | (color << 8);
else if (_bpp == 8)
pixelColor = (color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3;
while(len--) writeColor(pixelColor);
}
/***************************************************************************************
** Function name: writeColor
** Description: Write a pixel with pre-formatted colour to the set window
***************************************************************************************/
void TFT_eSprite::writeColor(uint16_t color)
{
if (!_created ) return;
else if (_bpp == 4)
{
uint8_t c = (uint8_t)color & 0x0F;
if ((_xptr & 0x01) == 0)
_img4[(_xptr + _yptr * _iwidth)>>1] = (c << 4) | (_img4[(_xptr + _yptr * _iwidth)>>1] & 0x0F); // new colo
else
_img4[(_xptr + _yptr * _iwidth)>>1] = (_img4[(_xptr + _yptr * _iwidth)>>1] & 0xF0) | c; // new color is the
}
// Increment x
_xptr++;
if (x < 0) { w += x; x = 0; }
if (y < 0) { h += y; y = 0; }
_sx = x;
_sy = y;
_sw = w;
_sh = h;
_scolor = color;
}
/***************************************************************************************
** Function name: scroll
** Description: Scroll dx,dy pixels, positive right,down, negative left,up
***************************************************************************************/
void TFT_eSprite::scroll(int16_t dx, int16_t dy)
{
if (abs(dx) >= _sw || abs(dy) >= _sh)
{
fillRect (_sx, _sy, _sw, _sh, _scolor);
return;
}
if (_bpp > 1) {
if (_vpDatum) return _xWidth;
return _dwidth;
}
if (rotation & 1) {
if (_vpDatum) return _xWidth;
return _dheight;
}
if (_bpp > 1) {
if (_vpDatum) return _yHeight;
return _dheight;
}
if (rotation & 1) {
if (_vpDatum) return _yHeight;
return _dwidth;
}
rotation = r;
if (rotation&1) {
resetViewport();
}
else {
resetViewport();
}
}
/***************************************************************************************
** Function name: getRotation
** Description: Get rotation for 1bpp sprite
***************************************************************************************/
uint8_t TFT_eSprite::getRotation(void)
{
return rotation;
}
/***************************************************************************************
** Function name: drawPixel
** Description: push a single pixel at an arbitrary position
***************************************************************************************/
void TFT_eSprite::drawPixel(int32_t x, int32_t y, uint32_t color)
{
if (!_created || _vpOoB) return;
x+= _xDatum;
y+= _yDatum;
// Range checking
if ((x < _vpX) || (y < _vpY) ||(x >= _vpW) || (y >= _vpH)) return;
if (_bpp == 16)
{
color = (color >> 8) | (color << 8);
_img[x+y*_iwidth] = (uint16_t) color;
}
else if (_bpp == 8)
{
_img8[x+y*_iwidth] = (uint8_t)((color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3);
}
else if (_bpp == 4)
{
uint8_t c = color & 0x0F;
int index = (x+y*_iwidth)>>1;;
if ((x & 0x01) == 0) {
_img4[index] = (uint8_t)((c << 4) | (_img4[index] & 0x0F));
}
else {
_img4[index] = (uint8_t)(c | (_img4[index] & 0xF0));
}
}
else // 1 bpp
{
if (rotation == 1)
{
uint16_t tx = x;
x = _dwidth - y - 1;
y = tx;
}
else if (rotation == 2)
{
x = _dwidth - x - 1;
y = _dheight - y - 1;
}
else if (rotation == 3)
{
uint16_t tx = x;
x = y;
y = _dheight - tx - 1;
}
//_xDatum and _yDatum Not added here, it is added by drawPixel & drawFastxLine
x+= _xDatum;
y+= _yDatum;
// Clipping
if ((x < _vpX) || (x >= _vpW) || (y >= _vpH)) return;
if (h < 1) return;
if (_bpp == 16)
{
color = (color >> 8) | (color << 8);
int32_t yp = x + _iwidth * y;
while (h--) {_img[yp] = (uint16_t) color; yp += _iwidth;}
}
else if (_bpp == 8)
{
color = (color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3;
while (h--) _img8[x + _iwidth * y++] = (uint8_t) color;
}
else if (_bpp == 4)
{
if ((x & 0x01) == 0)
{
uint8_t c = (uint8_t) (color & 0xF) << 4;
while (h--) {
_img4[(x + _iwidth * y)>>1] = (uint8_t) (c | (_img4[(x + _iwidth * y)>>1] & 0x0F));
y++;
}
}
else {
uint8_t c = (uint8_t)color & 0xF;
while (h--) {
_img4[(x + _iwidth * y)>>1] = (uint8_t) (c | (_img4[(x + _iwidth * y)>>1] & 0xF0)); // x is odd; new color g
y++;
}
}
}
else
{
x -= _xDatum; // Remove any offset as it will be added by drawPixel
y -= _yDatum;
while (h--)
{
drawPixel(x, y++, color);
}
}
}
/***************************************************************************************
** Function name: drawFastHLine
** Description: draw a horizontal line
***************************************************************************************/
void TFT_eSprite::drawFastHLine(int32_t x, int32_t y, int32_t w, uint32_t color)
{
if (!_created || _vpOoB) return;
x+= _xDatum;
y+= _yDatum;
// Clipping
if ((y < _vpY) || (x >= _vpW) || (y >= _vpH)) return;
if (w < 1) return;
if (_bpp == 16)
{
color = (color >> 8) | (color << 8);
while (w--) _img[_iwidth * y + x++] = (uint16_t) color;
}
else if (_bpp == 8)
{
color = (color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3;
memset(_img8+_iwidth * y + x, (uint8_t)color, w);
}
else if (_bpp == 4)
{
uint8_t c = (uint8_t)color & 0x0F;
uint8_t c2 = (c | ((c << 4) & 0xF0));
if ((x & 0x01) == 1)
{
drawPixel(x - _xDatum, y - _yDatum, color);
x++; w--;
if (w < 1)
return;
}
while (w--)
{
drawPixel(x++, y, color);
}
}
}
/***************************************************************************************
** Function name: fillRect
** Description: draw a filled rectangle
***************************************************************************************/
void TFT_eSprite::fillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color)
{
if (!_created || _vpOoB) return;
x+= _xDatum;
y+= _yDatum;
// Clipping
if ((x >= _vpW) || (y >= _vpH)) return;
int32_t yp = _iwidth * y + x;
if (_bpp == 16)
{
color = (color >> 8) | (color << 8);
uint32_t iw = w;
int32_t ys = yp;
if(h--) {while (iw--) _img[yp++] = (uint16_t) color;}
yp = ys;
while (h--)
{
yp += _iwidth;
memcpy( _img+yp, _img+ys, w<<1);
}
}
else if (_bpp == 8)
{
color = (color & 0xE000)>>8 | (color & 0x0700)>>6 | (color & 0x0018)>>3;
while (h--)
{
memset(_img8 + yp, (uint8_t)color, w);
yp += _iwidth;
}
}
else if (_bpp == 4)
{
uint8_t c1 = (uint8_t)color & 0x0F;
uint8_t c2 = c1 | ((c1 << 4) & 0xF0);
if ((x & 0x01) == 0 && (w & 0x01) == 0)
{
yp = (yp >> 1);
while (h--)
{
memset(_img4 + yp, c2, (w>>1));
yp += (_iwidth >> 1);
}
}
else if ((x & 0x01) == 0)
{
int8_t j, k;
for (j = 0; j < 8; j++) {
for (k = 0; k < 5; k++ ) {
if (column[k] & mask) {
drawPixel(x + k, y + j, color);
}
else {
drawPixel(x + k, y + j, bg);
}
}
mask <<= 1;
drawPixel(x + k, y + j, bg);
}
}
else
{
for (int8_t i = 0; i < 6; i++ ) {
uint8_t line;
if (i == 5)
line = 0x0;
else
line = pgm_read_byte(font + (c * 5) + i);
//>>>>>>>>>>>>>>>>>>>>>>>>>>>
#ifdef LOAD_GFXFF
} else { // Custom font
#endif
//>>>>>>>>>>>>>>>>>>>>>>>>>>>
#endif // LOAD_GLCD
#ifdef LOAD_GFXFF
// Filter out bad characters not present in font
if ((c >= pgm_read_word(&gfxFont->first)) && (c <= pgm_read_word(&gfxFont->last )))
//>>>>>>>>>>>>>>>>>>>>>>>>>>>
c -= pgm_read_word(&gfxFont->first);
GFXglyph *glyph = &(((GFXglyph *)pgm_read_dword(&gfxFont->glyph))[c]);
uint8_t *bitmap = (uint8_t *)pgm_read_dword(&gfxFont->bitmap);
uint32_t bo = pgm_read_word(&glyph->bitmapOffset);
uint8_t w = pgm_read_byte(&glyph->width),
h = pgm_read_byte(&glyph->height);
//xa = pgm_read_byte(&glyph->xAdvance);
int8_t xo = pgm_read_byte(&glyph->xOffset),
yo = pgm_read_byte(&glyph->yOffset);
uint8_t xx, yy, bits=0, bit=0;
int16_t xo16 = 0, yo16 = 0;
if(size > 1) {
xo16 = xo;
yo16 = yo;
}
#ifdef LOAD_GLCD
#ifdef LOAD_GFXFF
} // End classic vs custom font
#endif
#endif
}
/***************************************************************************************
** Function name: drawChar
** Description: draw a unicode glyph onto the screen
***************************************************************************************/
// TODO: Rationalise with TFT_eSPI
// Any UTF-8 decoding must be done before calling drawChar()
int16_t TFT_eSprite::drawChar(uint16_t uniCode, int32_t x, int32_t y)
{
return drawChar(uniCode, x, y, textfont);
}
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*size
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
int32_t w = width;
int32_t pY = y;
uint8_t line = 0;
bool clip = xd < _vpX || xd + width * textsize >= _vpW || yd < _vpY || yd + height * textsize >= _vpH;
#ifdef LOAD_RLE
else
#endif
#endif //FONT2
if (ts) {
tnp = np;
while (tnp--) writeColor(color);
}
else writeColor(color);
px += textsize;
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);
}
}
}
}
// End of RLE font rendering
#endif
}
#ifdef SMOOTH_FONT
/***************************************************************************************
** Function name: drawGlyph
** Description: Write a character to the sprite cursor position
***************************************************************************************/
//
void TFT_eSprite::drawGlyph(uint16_t code)
{
uint16_t fg = textcolor;
uint16_t bg = textbgcolor;
if (code == '\n') {
cursor_x = 0;
cursor_y += gFont.yAdvance;
if (textwrapY && (cursor_y >= height())) cursor_y = 0;
return;
}
}
uint16_t gNum = 0;
bool found = getUnicodeIndex(code, &gNum);
if (found)
{
if (newSprite)
{
createSprite(gWidth[gNum], gFont.yAdvance);
if(fg != bg) fillSprite(bg);
cursor_x = -gdX[gNum];
cursor_y = 0;
}
else
{
if( textwrapX && ((cursor_x + gWidth[gNum] + gdX[gNum]) > width())) {
cursor_y += gFont.yAdvance;
cursor_x = 0;
}
#ifdef FONT_FS_AVAILABLE
if (fs_font) {
fontFile.seek(gBitmap[gNum], fs::SeekSet); // This is slow for a significant position shift!
pbuffer = (uint8_t*)malloc(gWidth[gNum]);
}
#endif
int16_t xs = 0;
uint16_t dl = 0;
uint8_t pixel = 0;
int32_t cgy = cursor_y + gFont.maxAscent - gdY[gNum];
int32_t cgx = cursor_x + gdX[gNum];
if (pbuffer) free(pbuffer);
if (newSprite)
{
pushSprite(cgx, cursor_y);
deleteSprite();
}
cursor_x += gxAdvance[gNum];
}
else
{
// Not a Unicode in font so draw a rectangle and move on cursor
drawRect(cursor_x, cursor_y + gFont.maxAscent - gFont.ascent, gFont.spaceWidth, gFont.ascent, fg);
cursor_x += gFont.spaceWidth + 1;
}
}
/***************************************************************************************
** Function name: printToSprite
** Description: Write a string to the sprite cursor position
***************************************************************************************/
void TFT_eSprite::printToSprite(String string)
{
if(!fontLoaded) return;
printToSprite((char*)string.c_str(), string.length());
}
/***************************************************************************************
** Function name: printToSprite
** Description: Write a string to the sprite cursor position
***************************************************************************************/
void TFT_eSprite::printToSprite(char *cbuffer, uint16_t len) //String string)
{
if(!fontLoaded) return;
uint16_t n = 0;
bool newSprite = !_created;
if (newSprite)
{
int16_t sWidth = 1;
uint16_t index = 0;
createSprite(sWidth, gFont.yAdvance);
n = 0;
if (newSprite)
{ // The sprite had to be created so place at TFT cursor
pushSprite(_tft->cursor_x, _tft->cursor_y);
deleteSprite();
}
}
/***************************************************************************************
** Function name: printToSprite
** Description: Print character in a Sprite, create sprite if needed
***************************************************************************************/
int16_t TFT_eSprite::printToSprite(int16_t x, int16_t y, uint16_t index)
{
bool newSprite = !_created;
int16_t sWidth = gWidth[index];
if (newSprite)
{
createSprite(sWidth, gFont.yAdvance);
drawGlyph(gUnicode[index]);
else drawGlyph(gUnicode[index]);
return gxAdvance[index];
}
#endif
/***************************************************************************************
** Function name: CONNECTIONS
** Description: Diagrams
***************************************************************************************/
If you use PWM to GPIO 32, you need put this lines in
SETUP of sketch:
ledcSetup(0, 5000, 8);
#ifdef TFT_BL
ledcAttachPin(TFT_BL, 0);
#else
ledcAttachPin(32, 0);
#endif
ledcWrite(0, 255); // Start @ initial Brightnes
/***************************************************************************************
** Function name: SETUP FILES
** Description: Setup Files
***************************************************************************************/
#define ILI9488_DRIVER // WARNING: Do not connect ILI9488 display SDO to MISO if other devices share the S
#define TFT_BL 32 // LED back-light control pin
#define TFT_BACKLIGHT_ON HIGH // Level to turn ON back-light (HIGH or LOW)
#define TFT_MISO 19
#define TFT_MOSI 23
#define TFT_SCLK 18
#define TFT_CS 15 // Chip select control pin
#define TFT_DC 2 // Data Command control pin
#define TFT_RST 4 // Reset pin (could connect to RST pin)
#define TOUCH_CS 21 // Chip select pin (T_CS) of touch screen
#define LOAD_GLCD // Font 1. Original Adafruit 8 pixel font needs ~1820 bytes in FLASH
#define LOAD_FONT2 // Font 2. Small 16 pixel high font, needs ~3534 bytes in FLASH, 96 characters
#define LOAD_FONT4 // Font 4. Medium 26 pixel high font, needs ~5848 bytes in FLASH, 96 characters
#define LOAD_FONT6 // Font 6. Large 48 pixel font, needs ~2666 bytes in FLASH, only characters 1234567890:-.a
#define LOAD_FONT7 // Font 7. 7 segment 48 pixel font, needs ~2438 bytes in FLASH, only characters 123456789
#define LOAD_FONT8 // Font 8. Large 75 pixel font needs ~3256 bytes in FLASH, only characters 1234567890:-.
#define LOAD_GFXFF // FreeFonts. Include access to the 48 Adafruit_GFX free fonts FF1 to FF48 and custom fonts
#define SMOOTH_FONT
// Define the SPI clock frequency, this affects the graphics rendering speed. Too
// fast and the TFT driver will not keep up and display corruption appears.
// With an ILI9341 display 40MHz works OK, 80MHz sometimes fails
// With a ST7735 display more than 27MHz may not work (spurious pixels and lines)
// With an ILI9163 display 27 MHz works OK.
// The XPT2046 requires a lower SPI clock rate of 2.5MHz so we define that here:
#define SPI_TOUCH_FREQUENCY 2500000
// The ESP32 has 2 free SPI ports i.e. VSPI and HSPI, the VSPI is the default.
// If the VSPI port is in use and pins are not accessible (e.g. TTGO T-Beam)
// then uncomment the following line:
//#define USE_HSPI_PORT
/***************************************************************************************
** Function name: User_Setup_Select.h
** Description: Setup Files
***************************************************************************************/
// Only ONE line below should be uncommented. Add extra lines and files as needed.
/////////////////////////////////////////////////////////////////////////////////////
// //
// DON'T TINKER WITH ANY OF THE FOLLOWING LINES, THESE ADD THE TFT DRIVERS //
// AND ESP8266 PIN DEFINITONS, THEY ARE HERE FOR BODMER'S CONVENIENCE! //
// //
/////////////////////////////////////////////////////////////////////////////////////
// Identical looking TFT displays may have a different colour ordering in the 16 bit colour
#define TFT_BGR 0 // Colour order Blue-Green-Red
#define TFT_RGB 1 // Colour order Red-Green-Blue
#define ILI9488_DRIVER
//#define TFT_INVERSION_OFF
#define TFT_MISO 19 // (leave TFT SDO disconnected if other SPI devices share MISO)
#define TFT_MOSI 23
#define TFT_SCLK 18
#define TFT_CS 15 // Chip select control pin
#define TFT_DC 2 // Data Command control pin
#define TFT_RST 4 // Reset pin (could connect to RST pin)
#define TOUCH_CS 21
#define LOAD_GLCD // Font 1. Original Adafruit 8 pixel font needs ~1820 bytes in FLASH
#define LOAD_FONT2 // Font 2. Small 16 pixel high font, needs ~3534 bytes in FLASH, 96 characters
#define LOAD_FONT4 // Font 4. Medium 26 pixel high font, needs ~5848 bytes in FLASH, 96 characters
#define LOAD_FONT6 // Font 6. Large 48 pixel font, needs ~2666 bytes in FLASH, only characters 1234567890:-.a
#define LOAD_FONT7 // Font 7. 7 segment 48 pixel font, needs ~2438 bytes in FLASH, only characters 123456789
#define LOAD_FONT8 // Font 8. Large 75 pixel font needs ~3256 bytes in FLASH, only characters 1234567890:-.
#define LOAD_GFXFF // FreeFonts. Include access to the 48 Adafruit_GFX free fonts FF1 to FF48 and custom fonts
#define SMOOTH_FONT
// Define the SPI clock frequency, this affects the graphics rendering speed. Too
// fast and the TFT driver will not keep up and display corruption appears.
// With an ILI9341 display 40MHz works OK, 80MHz sometimes fails
// With a ST7735 display more than 27MHz may not work (spurious pixels and lines)
// With an ILI9163 display 27 MHz works OK.
// The XPT2046 requires a lower SPI clock rate of 2.5MHz so we define that here:
#define SPI_TOUCH_FREQUENCY 2500000
// The ESP32 has 2 free SPI ports i.e. VSPI and HSPI, the VSPI is the default.
// If the VSPI port is in use and pins are not accessible (e.g. TTGO T-Beam)
// then uncomment the following line:
//#define USE_HSPI_PORT