You've already forked TEF6686_ESP32
2740 lines
72 KiB
C++
2740 lines
72 KiB
C++
#include "TFT_eSPI.h"
|
|
#include <Arduino.h>
|
|
#include "soc/dport_access.h"
|
|
#include "soc/dport_reg.h"
|
|
|
|
#ifndef VSPI
|
|
#define VSPI 3
|
|
#endif
|
|
volatile uint32_t* _spi_cmd = (volatile uint32_t*)(SPI_CMD_REG(VSPI));
|
|
volatile uint32_t* _spi_ctrl = (volatile uint32_t*)(SPI_CTRL_REG(VSPI));
|
|
volatile uint32_t* _spi_user = (volatile uint32_t*)(SPI_USER_REG(VSPI));
|
|
volatile uint32_t* _spi_mosi_dlen = (volatile uint32_t*)(SPI_MOSI_DLEN_REG(VSPI));
|
|
volatile uint32_t* _spi_miso_dlen = (volatile uint32_t*)(SPI_MISO_DLEN_REG(VSPI));
|
|
volatile uint32_t* _spi_w = (volatile uint32_t*)(SPI_W0_REG(VSPI));
|
|
volatile uint32_t* _spi_clock = (volatile uint32_t*)(SPI_CLOCK_REG(VSPI));
|
|
|
|
uint8_t spi_transfer(uint8_t val) {
|
|
SPI_BUSY_CHECK;
|
|
*_spi_miso_dlen = 7;
|
|
tft_Write_8(val);
|
|
return *_spi_w & 0xff;
|
|
}
|
|
|
|
#define MSB_16_SET(var) { (var) = (((var) & 0xFF00) >> 8) | (((var) & 0xFF) << 8); }
|
|
uint16_t transfer16(uint16_t val) {
|
|
SPI_BUSY_CHECK;
|
|
if(!(*_spi_ctrl & SPI_WR_BIT_ORDER)) MSB_16_SET(val);
|
|
|
|
*_spi_miso_dlen = 15;
|
|
tft_Write_16S(val);
|
|
uint16_t out = *_spi_w & 0xffff;
|
|
if(!(*_spi_ctrl & SPI_RD_BIT_ORDER)) MSB_16_SET(out);
|
|
return out;
|
|
}
|
|
|
|
void TFT_eSPI::pushBlock(uint16_t color, uint32_t len) {
|
|
volatile uint32_t* spi_w = _spi_w;
|
|
|
|
uint32_t color32 = (color <<8 | color >> 8) << 16 | (color << 8 | color >> 8); // 16 bit number, but endianess is swapped and also repeated twice
|
|
|
|
uint32_t rem = len & 0x1F; // first 5 bits
|
|
len -= rem; // len now is a multiple of 32
|
|
|
|
SPI_BUSY_CHECK;
|
|
|
|
uint32_t i = 0;
|
|
if (rem) { // len was not multiple of 32
|
|
for (i = 0; i < rem; i += 2) *spi_w++ = color32;
|
|
*_spi_mosi_dlen = (rem << 4) - 1; // << 4 = * 16
|
|
*_spi_cmd = SPI_USR;
|
|
|
|
if (!len) return; // if this was all, then return
|
|
|
|
i >>= 1; // >> 1 = / 2
|
|
}
|
|
|
|
while (i++ < 16) *spi_w++ = color32; // if we had rem, then rem would fill the register it needs, i remains thus we can fill out every register as needed
|
|
|
|
if(rem) SPI_BUSY_CHECK; // wait for rem's thing to complete
|
|
|
|
*_spi_mosi_dlen = 511; // (32*16)-1
|
|
|
|
while(len) {
|
|
*_spi_cmd = SPI_USR;
|
|
SPI_BUSY_CHECK;
|
|
// Since the write registers aren't cleared, we can run this how many times we want and it does the same thing as it did
|
|
len -= 32;
|
|
}
|
|
}
|
|
|
|
void TFT_eSPI::pushSwapBytePixels(const void* data_in, uint32_t len) {
|
|
uint8_t* data = (uint8_t*)data_in;
|
|
uint32_t color[16];
|
|
|
|
SPI_BUSY_CHECK;
|
|
if (len > 31) {
|
|
*_spi_mosi_dlen = 511;
|
|
while(len>31) {
|
|
uint32_t i = 0;
|
|
while(i<16) {
|
|
color[i++] = DAT8TO32(data);
|
|
data += 4;
|
|
}
|
|
memcpy((void*)_spi_w, color, sizeof(uint32_t)*16);
|
|
*_spi_cmd = SPI_USR;
|
|
len -= 32;
|
|
SPI_BUSY_CHECK;
|
|
}
|
|
}
|
|
|
|
if (len > 15) {
|
|
uint32_t i = 0;
|
|
while(i<8) {
|
|
color[i++] = DAT8TO32(data);
|
|
data += 4;
|
|
}
|
|
*_spi_mosi_dlen = 255;
|
|
memcpy((void*)_spi_w, color, sizeof(uint32_t)*8);
|
|
*_spi_cmd = SPI_USR;
|
|
len -= 16;
|
|
SPI_BUSY_CHECK;
|
|
}
|
|
|
|
if (len) {
|
|
*_spi_mosi_dlen = (len << 4) - 1;
|
|
for(uint8_t i=0; i <= (len>>1); i++) {
|
|
_spi_w[i] = DAT8TO32(data);
|
|
data += 4;
|
|
}
|
|
*_spi_cmd = SPI_USR;
|
|
SPI_BUSY_CHECK;
|
|
}
|
|
}
|
|
|
|
void TFT_eSPI::pushPixels(const void* data_in, uint32_t len) {
|
|
if(_swapBytes) {
|
|
pushSwapBytePixels(data_in, len);
|
|
return;
|
|
}
|
|
|
|
uint32_t *data = (uint32_t*)data_in;
|
|
|
|
SPI_BUSY_CHECK;
|
|
if(len >= 32) {
|
|
*_spi_mosi_dlen = 511; // (32*16)-1
|
|
while(len >= 32) {
|
|
for(uint8_t i = 0; i < 16; i++) _spi_w[i] = *data++;
|
|
*_spi_cmd = SPI_USR;
|
|
len -= 32;
|
|
SPI_BUSY_CHECK;
|
|
}
|
|
}
|
|
|
|
if (len) {
|
|
*_spi_mosi_dlen = (len << 4) - 1; // << 4 = * 16
|
|
for (uint8_t i = 0; i <= (len>>1); i++) _spi_w[i] = *data++;
|
|
*_spi_cmd = SPI_USR;
|
|
SPI_BUSY_CHECK;
|
|
}
|
|
}
|
|
|
|
#define PI_CLIP \
|
|
if (_vpOoB) return; \
|
|
x+= _xDatum; \
|
|
y+= _yDatum; \
|
|
\
|
|
if ((x >= _vpW) || (y >= _vpH)) return; \
|
|
\
|
|
int32_t dx = 0; \
|
|
int32_t dy = 0; \
|
|
int32_t dw = w; \
|
|
int32_t dh = h; \
|
|
\
|
|
if (x < _vpX) { dx = _vpX - x; dw -= dx; x = _vpX; } \
|
|
if (y < _vpY) { dy = _vpY - y; dh -= dy; y = _vpY; } \
|
|
\
|
|
if ((x + dw) > _vpW ) dw = _vpW - x; \
|
|
if ((y + dh) > _vpH ) dh = _vpH - y; \
|
|
\
|
|
if (dw < 1 || dh < 1) return;
|
|
|
|
|
|
inline void TFT_eSPI::begin_tft_write() {
|
|
SPI_SET_CLOCK_FREQ(spi_write_speed * MHZ);
|
|
CS_L;
|
|
SET_BUS_WRITE_MODE;
|
|
}
|
|
|
|
void TFT_eSPI::begin_nin_write() {
|
|
SPI_SET_CLOCK_FREQ(spi_write_speed * MHZ);
|
|
CS_L;
|
|
SET_BUS_WRITE_MODE;
|
|
}
|
|
|
|
inline void TFT_eSPI::end_tft_write() {
|
|
SPI_BUSY_CHECK; // Check send complete and clean out unused rx data
|
|
CS_H;
|
|
SET_BUS_READ_MODE; // In case bus has been configured for tx only
|
|
}
|
|
|
|
inline void TFT_eSPI::end_nin_write() {
|
|
SPI_BUSY_CHECK; // Check send complete and clean out unused rx data
|
|
CS_H;
|
|
SET_BUS_READ_MODE; // In case SPI has been configured for tx only
|
|
}
|
|
|
|
void TFT_eSPI::begin_tft_read() {
|
|
SPI_SET_CLOCK_FREQ(SPI_READ_FREQUENCY);
|
|
CS_L;
|
|
SET_BUS_READ_MODE;
|
|
}
|
|
|
|
void TFT_eSPI::end_tft_read() {
|
|
CS_H;
|
|
SET_BUS_WRITE_MODE;
|
|
}
|
|
|
|
void TFT_eSPI::setViewport(int32_t x, int32_t y, int32_t w, int32_t h, bool vpDatum) {
|
|
_xDatum = x;
|
|
_yDatum = y;
|
|
_xWidth = w;
|
|
_yHeight = h;
|
|
|
|
_vpDatum = false;
|
|
_vpOoB = false;
|
|
_vpX = 0;
|
|
_vpY = 0;
|
|
_vpW = width();
|
|
_vpH = height();
|
|
|
|
if (x<0) { w += x; x = 0; }
|
|
if (y<0) { h += y; y = 0; }
|
|
if ((x + w) > width() ) { w = width() - x; }
|
|
if ((y + h) > height() ) { h = height() - y; }
|
|
|
|
if (w < 1 || h < 1) {
|
|
_xDatum = 0;
|
|
_yDatum = 0;
|
|
_xWidth = width();
|
|
_yHeight = height();
|
|
_vpOoB = true;
|
|
return;
|
|
}
|
|
|
|
if (!vpDatum) {
|
|
_xDatum = 0;
|
|
_yDatum = 0;
|
|
_xWidth = width();
|
|
_yHeight = height();
|
|
}
|
|
|
|
_vpX = x;
|
|
_vpY = y;
|
|
_vpW = x + w;
|
|
_vpH = y + h;
|
|
_vpDatum = vpDatum;
|
|
}
|
|
|
|
void TFT_eSPI::setSPISpeed(uint8_t speed) {
|
|
if(speed > 0 && speed < 80) spi_write_speed = speed;
|
|
}
|
|
|
|
void TFT_eSPI::resetViewport() {
|
|
_vpDatum = _vpOoB = false;
|
|
_xDatum = _yDatum = _vpX = _vpY = 0;
|
|
_vpW = _xWidth = width();
|
|
_vpH = _yHeight = height();
|
|
}
|
|
|
|
TFT_eSPI::TFT_eSPI(int16_t w, int16_t h) {
|
|
_init_width = _width = w;
|
|
_init_height = _height = h;
|
|
|
|
resetViewport();
|
|
|
|
rotation = 0;
|
|
cursor_y = cursor_x = last_cursor_x = bg_cursor_x = 0;
|
|
textfont = 0;
|
|
textcolor = bitmap_fg = 0xFFFF;
|
|
textbgcolor = bitmap_bg = 0x0000;
|
|
|
|
_fillbg = textwrapY = textwrapX = false;
|
|
textdatum = TL_DATUM;
|
|
|
|
_swapBytes = true;
|
|
|
|
booted = false;
|
|
|
|
addr_row = 0xFFFF;
|
|
addr_col = 0xFFFF;
|
|
}
|
|
|
|
void TFT_eSPI::initBus() {
|
|
#ifdef TFT_CS
|
|
if (TFT_CS >= 0) {
|
|
pinMode(TFT_CS, OUTPUT);
|
|
gpio_set_level((gpio_num_t)TFT_CS, 1);
|
|
}
|
|
#endif
|
|
|
|
if (TOUCH_CS >= 0) {
|
|
pinMode(TOUCH_CS, OUTPUT);
|
|
gpio_set_level((gpio_num_t)TOUCH_CS, 1);
|
|
}
|
|
|
|
#ifdef TFT_DC
|
|
if (TFT_DC >= 0) {
|
|
pinMode(TFT_DC, OUTPUT);
|
|
gpio_set_level((gpio_num_t)TFT_DC, 1);
|
|
}
|
|
#endif
|
|
|
|
if (TFT_RST >= 0) {
|
|
pinMode(TFT_RST, OUTPUT);
|
|
gpio_set_level((gpio_num_t)TFT_RST, 1);
|
|
}
|
|
}
|
|
|
|
void TFT_eSPI::init() {
|
|
if(!booted) {
|
|
initBus();
|
|
|
|
DPORT_SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_SPI3_CLK_EN);
|
|
DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI3_RST);
|
|
SET_BUS_READ_MODE;
|
|
*_spi_ctrl = 0;
|
|
memset((void*)_spi_w, 0, sizeof(uint32_t)*16);
|
|
SPI_SET_CLOCK_FREQ(SPI_FREQUENCY);
|
|
|
|
pinMode(TFT_SCLK, OUTPUT);
|
|
pinMode(TFT_MOSI, OUTPUT);
|
|
pinMode(TFT_MISO, INPUT);
|
|
pinMatrixOutAttach(TFT_SCLK, VSPICLK_OUT_IDX, false, false);
|
|
pinMatrixInAttach(TFT_MISO, VSPID_IN_IDX, false);
|
|
pinMatrixOutAttach(TFT_MOSI, VSPID_IN_IDX, false, false);
|
|
SET_PERI_REG_MASK(SPI_DMA_CONF_REG(VSPI), SPI_DMA_RX_EN | SPI_DMA_TX_EN);
|
|
|
|
#if defined (TFT_CS)
|
|
// Set to output once again in case MISO is used for CS
|
|
if (TFT_CS >= 0) {
|
|
pinMode(TFT_CS, OUTPUT);
|
|
gpio_set_level((gpio_num_t)TFT_CS, 1);
|
|
}
|
|
#endif
|
|
|
|
|
|
// Set to output once again in case MISO is used for DC
|
|
#if defined (TFT_DC)
|
|
if (TFT_DC >= 0) {
|
|
pinMode(TFT_DC, OUTPUT);
|
|
gpio_set_level((gpio_num_t)TFT_DC, 1);
|
|
}
|
|
#endif
|
|
|
|
booted = true;
|
|
end_tft_write();
|
|
}
|
|
|
|
// Toggle RST low to reset
|
|
if (TFT_RST >= 0) {
|
|
pinMode(TFT_RST, OUTPUT);
|
|
writecommand(0x00); // Put SPI bus in known state for TFT with CS tied low
|
|
gpio_set_level((gpio_num_t)TFT_RST, 1);
|
|
delay(3);
|
|
gpio_set_level((gpio_num_t)TFT_RST, 0);
|
|
delay(12);
|
|
gpio_set_level((gpio_num_t)TFT_RST, 1);
|
|
}
|
|
|
|
delay(34); // Wait for reset to complete
|
|
|
|
begin_tft_write();
|
|
|
|
#include "ILI9341_Init.h"
|
|
|
|
end_tft_write();
|
|
|
|
setRotation(rotation);
|
|
}
|
|
|
|
void TFT_eSPI::setRotation(uint8_t m) {
|
|
begin_tft_write();
|
|
|
|
#include "ILI9341_Rotation.h"
|
|
|
|
delayMicroseconds(9);
|
|
|
|
end_tft_write();
|
|
|
|
addr_row = 0xFFFF;
|
|
addr_col = 0xFFFF;
|
|
|
|
resetViewport();
|
|
}
|
|
void TFT_eSPI::writecommand(uint8_t c) {
|
|
begin_tft_write();
|
|
|
|
DC_C;
|
|
tft_Write_8(c);
|
|
DC_D;
|
|
|
|
end_tft_write();
|
|
}
|
|
|
|
void TFT_eSPI::writedata(uint8_t d) {
|
|
begin_tft_write();
|
|
|
|
DC_D;
|
|
tft_Write_8(d);
|
|
CS_L;
|
|
|
|
end_tft_write();
|
|
}
|
|
|
|
void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t *data) {
|
|
PI_CLIP;
|
|
|
|
begin_tft_write();
|
|
|
|
setWindow(x, y, x + dw - 1, y + dh - 1);
|
|
|
|
data += dx + dy * w;
|
|
|
|
// Check if whole image can be pushed
|
|
if (dw == w) pushPixels(data, dw * dh);
|
|
else {
|
|
// Push line segments to crop image
|
|
while (dh--) {
|
|
pushPixels(data, dw);
|
|
data += w;
|
|
}
|
|
}
|
|
|
|
end_tft_write();
|
|
}
|
|
|
|
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();
|
|
|
|
data += dx + dy * w;
|
|
|
|
|
|
uint16_t lineBuf[dw]; // Use buffer to minimise setWindow call count
|
|
|
|
// The little endian transp color must be byte swapped if the image is big endian
|
|
if (!_swapBytes) transp = transp >> 8 | transp << 8;
|
|
|
|
while (dh--)
|
|
{
|
|
int32_t len = dw;
|
|
uint16_t* ptr = data;
|
|
int32_t px = x, sx = x;
|
|
bool move = true;
|
|
uint16_t np = 0;
|
|
|
|
while (len--) {
|
|
if (transp != *ptr) {
|
|
if (move) { move = false; sx = px; }
|
|
lineBuf[np] = *ptr;
|
|
np++;
|
|
} else {
|
|
move = true;
|
|
if (np) {
|
|
setWindow(sx, y, sx + np - 1, y);
|
|
pushPixels((uint16_t*)lineBuf, np);
|
|
np = 0;
|
|
}
|
|
}
|
|
px++;
|
|
ptr++;
|
|
}
|
|
if (np) { setWindow(sx, y, sx + np - 1, y); pushPixels((uint16_t*)lineBuf, np); }
|
|
|
|
y++;
|
|
data += w;
|
|
}
|
|
|
|
end_tft_write();
|
|
}
|
|
|
|
void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, const uint16_t *data) {
|
|
PI_CLIP;
|
|
|
|
begin_tft_write();
|
|
|
|
data += dx + dy * w;
|
|
|
|
uint16_t buffer[dw];
|
|
|
|
setWindow(x, y, x + dw - 1, y + dh - 1);
|
|
|
|
// Fill and send line buffers to TFT
|
|
for (int32_t i = 0; i < dh; i++) {
|
|
for (int32_t j = 0; j < dw; j++) buffer[j] = pgm_read_word(&data[i * w + j]);
|
|
pushPixels(buffer, dw);
|
|
}
|
|
|
|
end_tft_write();
|
|
}
|
|
|
|
void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, const uint16_t *data, uint16_t transp) {
|
|
PI_CLIP;
|
|
|
|
begin_tft_write();
|
|
|
|
data += dx + dy * w;
|
|
|
|
uint16_t lineBuf[dw];
|
|
|
|
if (!_swapBytes) transp = transp >> 8 | transp << 8;
|
|
|
|
while (dh--) {
|
|
int32_t len = dw;
|
|
uint16_t* ptr = (uint16_t*)data;
|
|
int32_t px = x, sx = x;
|
|
bool move = true;
|
|
|
|
uint16_t np = 0;
|
|
|
|
while (len--) {
|
|
uint16_t color = pgm_read_word(ptr);
|
|
if (transp != color) {
|
|
if (move) { move = false; sx = px; }
|
|
lineBuf[np] = color;
|
|
np++;
|
|
}
|
|
else {
|
|
move = true;
|
|
if (np) {
|
|
setWindow(sx, y, sx + np - 1, y);
|
|
pushPixels(lineBuf, np);
|
|
np = 0;
|
|
}
|
|
}
|
|
px++;
|
|
ptr++;
|
|
}
|
|
if (np) { setWindow(sx, y, sx + np - 1, y); pushPixels(lineBuf, np); }
|
|
|
|
y++;
|
|
data += w;
|
|
}
|
|
|
|
end_tft_write();
|
|
}
|
|
|
|
void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, const uint8_t *data, bool bpp8, uint16_t *cmap) {
|
|
PI_CLIP;
|
|
|
|
begin_tft_write();
|
|
bool swap = _swapBytes;
|
|
|
|
setWindow(x, y, x + dw - 1, y + dh - 1); // Sets CS low and sent RAMWR
|
|
|
|
// Line buffer makes plotting faster
|
|
uint16_t lineBuf[dw];
|
|
|
|
if (bpp8) {
|
|
_swapBytes = false;
|
|
|
|
uint8_t blue[] = {0, 11, 21, 31}; // blue 2 to 5-bit colour lookup table
|
|
|
|
_lastColor = -1; // Set to illegal value
|
|
|
|
// Used to store last shifted colour
|
|
uint8_t msbColor = 0;
|
|
uint8_t lsbColor = 0;
|
|
|
|
data += dx + dy * w;
|
|
while (dh--) {
|
|
uint32_t len = dw;
|
|
uint8_t* ptr = (uint8_t*)data;
|
|
uint8_t* linePtr = (uint8_t*)lineBuf;
|
|
|
|
while(len--) {
|
|
uint32_t color = pgm_read_byte(ptr++);
|
|
|
|
// Shifts are slow so check if colour has changed first
|
|
if (color != _lastColor) {
|
|
// =====Green===== ===============Red==============
|
|
msbColor = (color & 0x1C)>>2 | (color & 0xC0)>>3 | (color & 0xE0);
|
|
// =====Green===== =======Blue======
|
|
lsbColor = (color & 0x1C)<<3 | blue[color & 0x03];
|
|
_lastColor = color;
|
|
}
|
|
|
|
*linePtr++ = msbColor;
|
|
*linePtr++ = lsbColor;
|
|
}
|
|
|
|
pushPixels(lineBuf, dw);
|
|
|
|
data += w;
|
|
}
|
|
_swapBytes = swap; // Restore old value
|
|
} else if (cmap != nullptr) {
|
|
_swapBytes = true;
|
|
|
|
w = (w+1) & 0xFFFE; // if this is a sprite, w will already be even; this does no harm.
|
|
bool splitFirst = (dx & 0x01) != 0; // split first means we have to push a single px from the left of the sprite / image
|
|
|
|
if (splitFirst) data += ((dx - 1 + dy * w) >> 1);
|
|
else data += ((dx + dy * w) >> 1);
|
|
while (dh--) {
|
|
uint32_t len = dw;
|
|
uint8_t * ptr = (uint8_t*)data;
|
|
uint16_t *linePtr = lineBuf;
|
|
uint8_t colors; // two colors in one byte
|
|
uint16_t index;
|
|
|
|
if (splitFirst) {
|
|
colors = pgm_read_byte(ptr);
|
|
index = (colors & 0x0F);
|
|
*linePtr++ = cmap[index];
|
|
len--;
|
|
ptr++;
|
|
}
|
|
|
|
while (len--) {
|
|
colors = pgm_read_byte(ptr);
|
|
index = ((colors & 0xF0) >> 4) & 0x0F;
|
|
*linePtr++ = cmap[index];
|
|
|
|
if (len--) {
|
|
index = colors & 0x0F;
|
|
*linePtr++ = cmap[index];
|
|
} else break;
|
|
|
|
ptr++;
|
|
}
|
|
|
|
pushPixels(lineBuf, dw);
|
|
data += (w >> 1);
|
|
}
|
|
_swapBytes = swap; // Restore old value
|
|
} else {
|
|
_swapBytes = false;
|
|
uint8_t * ptr = (uint8_t*)data;
|
|
uint32_t ww = (w+7)>>3; // Width of source image line in bytes
|
|
for (int32_t yp = dy; yp < dy + dh; yp++) {
|
|
uint8_t* linePtr = (uint8_t*)lineBuf;
|
|
for (int32_t xp = dx; xp < dx + dw; xp++) {
|
|
uint16_t col = (pgm_read_byte(ptr + (xp>>3)) & (0x80 >> (xp & 0x7)) );
|
|
if (col) {*linePtr++ = bitmap_fg>>8; *linePtr++ = (uint8_t) bitmap_fg;}
|
|
else {*linePtr++ = bitmap_bg>>8; *linePtr++ = (uint8_t) bitmap_bg;}
|
|
}
|
|
ptr += ww;
|
|
pushPixels(lineBuf, dw);
|
|
}
|
|
}
|
|
|
|
_swapBytes = swap; // Restore old value
|
|
end_tft_write();
|
|
}
|
|
|
|
void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t *data, bool bpp8, uint16_t *cmap) {
|
|
PI_CLIP;
|
|
|
|
begin_tft_write();
|
|
bool swap = _swapBytes;
|
|
|
|
setWindow(x, y, x + dw - 1, y + dh - 1); // Sets CS low and sent RAMWR
|
|
|
|
uint16_t lineBuf[dw];
|
|
|
|
if (bpp8) {
|
|
_swapBytes = false;
|
|
|
|
uint8_t blue[] = {0, 11, 21, 31}; // blue 2 to 5-bit colour lookup table
|
|
|
|
_lastColor = -1; // Set to illegal value
|
|
|
|
// Used to store last shifted colour
|
|
uint8_t msbColor = 0;
|
|
uint8_t lsbColor = 0;
|
|
|
|
data += dx + dy * w;
|
|
while (dh--) {
|
|
uint32_t len = dw;
|
|
uint8_t* ptr = data;
|
|
uint8_t* linePtr = (uint8_t*)lineBuf;
|
|
|
|
while(len--) {
|
|
uint32_t color = *ptr++;
|
|
|
|
// Shifts are slow so check if colour has changed first
|
|
if (color != _lastColor) {
|
|
// =====Green===== ===============Red==============
|
|
msbColor = (color & 0x1C)>>2 | (color & 0xC0)>>3 | (color & 0xE0);
|
|
// =====Green===== =======Blue======
|
|
lsbColor = (color & 0x1C)<<3 | blue[color & 0x03];
|
|
_lastColor = color;
|
|
}
|
|
|
|
*linePtr++ = msbColor;
|
|
*linePtr++ = lsbColor;
|
|
}
|
|
|
|
pushPixels(lineBuf, dw);
|
|
|
|
data += w;
|
|
}
|
|
_swapBytes = swap; // Restore old value
|
|
} else if (cmap != nullptr) {
|
|
_swapBytes = true;
|
|
|
|
w = (w+1) & 0xFFFE; // if this is a sprite, w will already be even; this does no harm.
|
|
bool splitFirst = (dx & 0x01) != 0; // split first means we have to push a single px from the left of the sprite / image
|
|
|
|
if (splitFirst) data += ((dx - 1 + dy * w) >> 1);
|
|
else data += ((dx + dy * w) >> 1);
|
|
|
|
while (dh--) {
|
|
uint32_t len = dw;
|
|
uint8_t * ptr = data;
|
|
uint16_t *linePtr = lineBuf;
|
|
uint8_t colors; // two colors in one byte
|
|
uint16_t index;
|
|
|
|
if (splitFirst) {
|
|
colors = *ptr;
|
|
index = (colors & 0x0F);
|
|
*linePtr++ = cmap[index];
|
|
len--;
|
|
ptr++;
|
|
}
|
|
|
|
while (len--)
|
|
{
|
|
colors = *ptr;
|
|
index = ((colors & 0xF0) >> 4) & 0x0F;
|
|
*linePtr++ = cmap[index];
|
|
|
|
if (len--) {
|
|
index = colors & 0x0F;
|
|
*linePtr++ = cmap[index];
|
|
} else break;
|
|
|
|
ptr++;
|
|
}
|
|
|
|
pushPixels(lineBuf, dw);
|
|
data += (w >> 1);
|
|
}
|
|
_swapBytes = swap; // Restore old value
|
|
} else {
|
|
_swapBytes = false;
|
|
|
|
uint32_t ww = (w+7)>>3; // Width of source image line in bytes
|
|
for (int32_t yp = dy; yp < dy + dh; yp++)
|
|
{
|
|
uint8_t* linePtr = (uint8_t*)lineBuf;
|
|
for (int32_t xp = dx; xp < dx + dw; xp++)
|
|
{
|
|
uint16_t col = (data[(xp>>3)] & (0x80 >> (xp & 0x7)) );
|
|
if (col) {*linePtr++ = bitmap_fg>>8; *linePtr++ = (uint8_t) bitmap_fg;}
|
|
else {*linePtr++ = bitmap_bg>>8; *linePtr++ = (uint8_t) bitmap_bg;}
|
|
}
|
|
data += ww;
|
|
pushPixels(lineBuf, dw);
|
|
}
|
|
}
|
|
|
|
_swapBytes = swap; // Restore old value
|
|
end_tft_write();
|
|
}
|
|
|
|
void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t *data, uint8_t transp, bool bpp8, uint16_t *cmap) {
|
|
PI_CLIP;
|
|
|
|
begin_tft_write();
|
|
bool swap = _swapBytes;
|
|
|
|
|
|
// Line buffer makes plotting faster
|
|
uint16_t lineBuf[dw];
|
|
|
|
if (bpp8) { // 8 bits per pixel
|
|
_swapBytes = false;
|
|
|
|
data += dx + dy * w;
|
|
|
|
uint8_t blue[] = {0, 11, 21, 31}; // blue 2 to 5-bit colour lookup table
|
|
|
|
_lastColor = -1; // Set to illegal value
|
|
|
|
// Used to store last shifted colour
|
|
uint8_t msbColor = 0;
|
|
uint8_t lsbColor = 0;
|
|
|
|
while (dh--) {
|
|
int32_t len = dw;
|
|
uint8_t* ptr = data;
|
|
uint8_t* linePtr = (uint8_t*)lineBuf;
|
|
|
|
int32_t px = x, sx = x;
|
|
bool move = true;
|
|
uint16_t np = 0;
|
|
|
|
while (len--) {
|
|
if (transp != *ptr) {
|
|
if (move) { move = false; sx = px; }
|
|
uint8_t color = *ptr;
|
|
|
|
// Shifts are slow so check if colour has changed first
|
|
if (color != _lastColor) {
|
|
// =====Green===== ===============Red==============
|
|
msbColor = (color & 0x1C)>>2 | (color & 0xC0)>>3 | (color & 0xE0);
|
|
// =====Green===== =======Blue======
|
|
lsbColor = (color & 0x1C)<<3 | blue[color & 0x03];
|
|
_lastColor = color;
|
|
}
|
|
*linePtr++ = msbColor;
|
|
*linePtr++ = lsbColor;
|
|
np++;
|
|
}
|
|
else {
|
|
move = true;
|
|
if (np) {
|
|
setWindow(sx, y, sx + np - 1, y);
|
|
pushPixels(lineBuf, np);
|
|
linePtr = (uint8_t*)lineBuf;
|
|
np = 0;
|
|
}
|
|
}
|
|
px++;
|
|
ptr++;
|
|
}
|
|
|
|
if (np) { setWindow(sx, y, sx + np - 1, y); pushPixels(lineBuf, np); }
|
|
y++;
|
|
data += w;
|
|
}
|
|
} else if (cmap != nullptr) {
|
|
_swapBytes = true;
|
|
|
|
w = (w+1) & 0xFFFE; // here we try to recreate iwidth from dwidth.
|
|
bool splitFirst = ((dx & 0x01) != 0);
|
|
if (splitFirst) data += ((dx - 1 + dy * w) >> 1);
|
|
else data += ((dx + dy * w) >> 1);
|
|
|
|
while (dh--) {
|
|
uint32_t len = dw;
|
|
uint8_t * ptr = data;
|
|
|
|
int32_t px = x, sx = x;
|
|
bool move = true;
|
|
uint16_t np = 0;
|
|
|
|
uint8_t index; // index into cmap.
|
|
|
|
if (splitFirst) {
|
|
index = (*ptr & 0x0F); // odd = bits 3 .. 0
|
|
if (index != transp) {
|
|
move = false; sx = px;
|
|
lineBuf[np] = cmap[index];
|
|
np++;
|
|
}
|
|
px++; ptr++;
|
|
len--;
|
|
}
|
|
|
|
while (len--) {
|
|
uint8_t color = *ptr;
|
|
|
|
// find the actual color you care about. There will be two pixels here!
|
|
// but we may only want one at the end of the row
|
|
uint16_t index = ((color & 0xF0) >> 4) & 0x0F; // high bits are the even numbers
|
|
if (index != transp) {
|
|
if (move) {
|
|
move = false; sx = px;
|
|
}
|
|
lineBuf[np] = cmap[index];
|
|
np++; // added a pixel
|
|
}
|
|
else {
|
|
move = true;
|
|
if (np) {
|
|
setWindow(sx, y, sx + np - 1, y);
|
|
pushPixels(lineBuf, np);
|
|
np = 0;
|
|
}
|
|
}
|
|
px++;
|
|
|
|
if (len--) {
|
|
index = color & 0x0F; // the odd number is 3 .. 0
|
|
if (index != transp) {
|
|
if (move) {
|
|
move = false; sx = px;
|
|
}
|
|
lineBuf[np] = cmap[index];
|
|
np++;
|
|
} else {
|
|
move = true;
|
|
if (np) {
|
|
setWindow(sx, y, sx + np - 1, y);
|
|
pushPixels(lineBuf, np);
|
|
np = 0;
|
|
}
|
|
}
|
|
px++;
|
|
} else break;
|
|
ptr++; // we only increment ptr once in the loop (deliberate)
|
|
}
|
|
|
|
if (np) {
|
|
setWindow(sx, y, sx + np - 1, y);
|
|
pushPixels(lineBuf, np);
|
|
np = 0;
|
|
}
|
|
data += (w>>1);
|
|
y++;
|
|
}
|
|
} else { // 1 bit per pixel
|
|
_swapBytes = false;
|
|
|
|
uint32_t ww = (w+7)>>3; // Width of source image line in bytes
|
|
uint16_t np = 0;
|
|
|
|
for (int32_t yp = dy; yp < dy + dh; yp++) {
|
|
int32_t px = x, sx = x;
|
|
bool move = true;
|
|
for (int32_t xp = dx; xp < dx + dw; xp++) {
|
|
if (data[(xp>>3)] & (0x80 >> (xp & 0x7))) {
|
|
if (move) {
|
|
move = false;
|
|
sx = px;
|
|
}
|
|
np++;
|
|
}
|
|
else {
|
|
move = true;
|
|
if (np) {
|
|
setWindow(sx, y, sx + np - 1, y);
|
|
pushBlock(bitmap_fg, np);
|
|
np = 0;
|
|
}
|
|
}
|
|
px++;
|
|
}
|
|
if (np) { setWindow(sx, y, sx + np - 1, y); pushBlock(bitmap_fg, np); np = 0; }
|
|
y++;
|
|
data += ww;
|
|
}
|
|
}
|
|
_swapBytes = swap; // Restore old value
|
|
end_tft_write();
|
|
}
|
|
|
|
void TFT_eSPI::setSwapBytes(bool swap) {
|
|
_swapBytes = swap;
|
|
}
|
|
|
|
bool TFT_eSPI::getSwapBytes() {
|
|
return _swapBytes;
|
|
}
|
|
|
|
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;
|
|
|
|
do {
|
|
while (f < 0) {
|
|
++xe;
|
|
f += (ddF_x += 2);
|
|
}
|
|
f += (ddF_y += 2);
|
|
|
|
if (xe-xs==1) {
|
|
if (cornername & 0x1) { // left top
|
|
drawPixel(x0 - xe, y0 - rr, color);
|
|
drawPixel(x0 - rr, y0 - xe, color);
|
|
}
|
|
if (cornername & 0x2) { // right top
|
|
drawPixel(x0 + rr , y0 - xe, color);
|
|
drawPixel(x0 + xs + 1, y0 - rr, color);
|
|
}
|
|
if (cornername & 0x4) { // right bottom
|
|
drawPixel(x0 + xs + 1, y0 + rr , color);
|
|
drawPixel(x0 + rr, y0 + xs + 1, color);
|
|
}
|
|
if (cornername & 0x8) { // left bottom
|
|
drawPixel(x0 - rr, y0 + xs + 1, color);
|
|
drawPixel(x0 - xe, y0 + rr , color);
|
|
}
|
|
}
|
|
else {
|
|
len = xe - xs++;
|
|
if (cornername & 0x1) { // left top
|
|
drawFastHLine(x0 - xe, y0 - rr, len, color);
|
|
drawFastVLine(x0 - rr, y0 - xe, len, color);
|
|
}
|
|
if (cornername & 0x2) { // right top
|
|
drawFastVLine(x0 + rr, y0 - xe, len, color);
|
|
drawFastHLine(x0 + xs, y0 - rr, len, color);
|
|
}
|
|
if (cornername & 0x4) { // right bottom
|
|
drawFastHLine(x0 + xs, y0 + rr, len, color);
|
|
drawFastVLine(x0 + rr, y0 + xs, len, color);
|
|
}
|
|
if (cornername & 0x8) { // left bottom
|
|
drawFastVLine(x0 - rr, y0 + xs, len, color);
|
|
drawFastHLine(x0 - xe, y0 + rr, len, color);
|
|
}
|
|
}
|
|
xs = xe;
|
|
} while (xe < rr--);
|
|
|
|
end_tft_write(); // Does nothing if Sprite class uses this function
|
|
}
|
|
|
|
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);
|
|
|
|
drawFastHLine(x0 - r, y0, dy+1, color);
|
|
|
|
while(x<r){
|
|
|
|
if(p>=0) {
|
|
drawFastHLine(x0 - x, y0 + r, dx, color);
|
|
drawFastHLine(x0 - x, y0 - r, dx, color);
|
|
dy-=2;
|
|
p-=dy;
|
|
r--;
|
|
}
|
|
|
|
dx+=2;
|
|
p+=dx;
|
|
x++;
|
|
|
|
drawFastHLine(x0 - r, y0 + x, dy+1, color);
|
|
drawFastHLine(x0 - r, y0 - x, dy+1, color);
|
|
}
|
|
|
|
end_tft_write(); // Does nothing if Sprite class uses this function
|
|
}
|
|
|
|
void TFT_eSPI::fillCircleHelper(int32_t x0, int32_t y0, int32_t r, uint8_t cornername, int32_t delta, uint32_t color) {
|
|
int32_t f = 1 - r;
|
|
int32_t ddF_x = 1;
|
|
int32_t ddF_y = -r - r;
|
|
int32_t y = 0;
|
|
|
|
delta++;
|
|
|
|
while (y < r) {
|
|
if (f >= 0) {
|
|
if (cornername & 0x1) drawFastHLine(x0 - y, y0 + r, y + y + delta, color);
|
|
if (cornername & 0x2) drawFastHLine(x0 - y, y0 - r, y + y + delta, color);
|
|
r--;
|
|
ddF_y += 2;
|
|
f += ddF_y;
|
|
}
|
|
|
|
y++;
|
|
ddF_x += 2;
|
|
f += ddF_x;
|
|
|
|
if (cornername & 0x1) drawFastHLine(x0 - r, y0 + y, r + r + delta, color);
|
|
if (cornername & 0x2) drawFastHLine(x0 - r, y0 - y, r + r + delta, color);
|
|
}
|
|
}
|
|
|
|
void TFT_eSPI::fillScreen(uint32_t color) {
|
|
fillRect(0, 0, _width, _height, color);
|
|
}
|
|
|
|
void TFT_eSPI::drawRect(int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color) {
|
|
|
|
drawFastHLine(x, y, w, color);
|
|
drawFastHLine(x, y + h - 1, w, color);
|
|
drawFastVLine(x, y+1, h-2, color);
|
|
drawFastVLine(x + w - 1, y+1, h-2, color);
|
|
|
|
end_tft_write(); // Does nothing if Sprite class uses this function
|
|
}
|
|
|
|
void TFT_eSPI::drawRoundRect(int32_t x, int32_t y, int32_t w, int32_t h, int32_t r, uint32_t color) {
|
|
// 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);
|
|
|
|
end_tft_write(); // Does nothing if Sprite class uses this function
|
|
}
|
|
|
|
void TFT_eSPI::fillRoundRect(int32_t x, int32_t y, int32_t w, int32_t h, int32_t r, uint32_t color) {
|
|
// smarter version
|
|
fillRect(x, y + r, w, h - r - r, color);
|
|
|
|
// draw four corners
|
|
fillCircleHelper(x + r, y + h - r - 1, r, 1, w - r - r - 1, color);
|
|
fillCircleHelper(x + r , y + r, r, 2, w - r - r - 1, color);
|
|
|
|
end_tft_write(); // Does nothing if Sprite class uses this function
|
|
}
|
|
|
|
void TFT_eSPI::fillTriangle(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint32_t color) {
|
|
int32_t a, b, y, last;
|
|
|
|
// Sort coordinates by Y order (y2 >= y1 >= y0)
|
|
if (y0 > y1) {
|
|
transpose(y0, y1); transpose(x0, x1);
|
|
}
|
|
if (y1 > y2) {
|
|
transpose(y2, y1); transpose(x2, x1);
|
|
}
|
|
if (y0 > y1) {
|
|
transpose(y0, y1); transpose(x0, x1);
|
|
}
|
|
|
|
if (y0 == y2) { // Handle awkward all-on-same-line case as its own thing
|
|
a = b = x0;
|
|
if (x1 < a) a = x1;
|
|
else if (x1 > b) b = x1;
|
|
if (x2 < a) a = x2;
|
|
else if (x2 > b) b = x2;
|
|
drawFastHLine(a, y0, b - a + 1, color);
|
|
return;
|
|
}
|
|
|
|
int32_t
|
|
dx01 = x1 - x0,
|
|
dy01 = y1 - y0,
|
|
dx02 = x2 - x0,
|
|
dy02 = y2 - y0,
|
|
dx12 = x2 - x1,
|
|
dy12 = y2 - y1,
|
|
sa = 0,
|
|
sb = 0;
|
|
|
|
// For upper part of triangle, find scanline crossings for segments
|
|
// 0-1 and 0-2. If y1=y2 (flat-bottomed triangle), the scanline y1
|
|
// is included here (and second loop will be skipped, avoiding a /0
|
|
// error there), otherwise scanline y1 is skipped here and handled
|
|
// in the second loop...which also avoids a /0 error here if y0=y1
|
|
// (flat-topped triangle).
|
|
if (y1 == y2) last = y1; // Include y1 scanline
|
|
else last = y1 - 1; // Skip it
|
|
|
|
for (y = y0; y <= last; y++) {
|
|
a = x0 + sa / dy01;
|
|
b = x0 + sb / dy02;
|
|
sa += dx01;
|
|
sb += dx02;
|
|
|
|
if (a > b) transpose(a, b);
|
|
drawFastHLine(a, y, b - a + 1, color);
|
|
}
|
|
|
|
// For lower part of triangle, find scanline crossings for segments
|
|
// 0-2 and 1-2. This loop is skipped if y1=y2.
|
|
sa = dx12 * (y - y1);
|
|
sb = dx02 * (y - y0);
|
|
for (; y <= y2; y++) {
|
|
a = x1 + sa / dy12;
|
|
b = x0 + sb / dy02;
|
|
sa += dx12;
|
|
sb += dx02;
|
|
|
|
if (a > b) transpose(a, b);
|
|
drawFastHLine(a, y, b - a + 1, color);
|
|
}
|
|
|
|
end_tft_write(); // Does nothing if Sprite class uses this function
|
|
}
|
|
|
|
void TFT_eSPI::drawBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h, uint16_t color) {
|
|
int32_t i, j, byteWidth = (w + 7) / 8;
|
|
|
|
for (j = 0; j < h; j++) {
|
|
for (i = 0; i < w; i++ ) {
|
|
if (pgm_read_byte(bitmap + j * byteWidth + i / 8) & (128 >> (i & 7))) drawPixel(x + i, y + j, color);
|
|
}
|
|
}
|
|
|
|
end_tft_write(); // Does nothing if Sprite class uses this function
|
|
}
|
|
|
|
void TFT_eSPI::drawBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h, uint16_t fgcolor, uint16_t bgcolor) {
|
|
int32_t i, j, byteWidth = (w + 7) / 8;
|
|
|
|
for (j = 0; j < h; j++) {
|
|
for (i = 0; i < w; i++ ) {
|
|
if (pgm_read_byte(bitmap + j * byteWidth + i / 8) & (128 >> (i & 7))) drawPixel(x + i, y + j, fgcolor);
|
|
else drawPixel(x + i, y + j, bgcolor);
|
|
}
|
|
}
|
|
|
|
end_tft_write(); // Does nothing if Sprite class uses this function
|
|
}
|
|
|
|
void TFT_eSPI::setCursor(int16_t x, int16_t y) {
|
|
cursor_x = x;
|
|
cursor_y = y;
|
|
}
|
|
|
|
void TFT_eSPI::setCursor(int16_t x, int16_t y, uint8_t font) {
|
|
setTextFont(font);
|
|
cursor_x = x;
|
|
cursor_y = y;
|
|
}
|
|
|
|
void TFT_eSPI::setTextColor(uint16_t c) {
|
|
textcolor = textbgcolor = c;
|
|
}
|
|
|
|
void TFT_eSPI::setTextColor(uint16_t c, uint16_t b, bool bgfill) {
|
|
textcolor = c;
|
|
textbgcolor = b;
|
|
_fillbg = bgfill;
|
|
}
|
|
|
|
void TFT_eSPI::setTextDatum(uint8_t d) {
|
|
textdatum = d;
|
|
}
|
|
|
|
int16_t TFT_eSPI::width() {
|
|
if (_vpDatum) return _xWidth;
|
|
return _width;
|
|
}
|
|
|
|
int16_t TFT_eSPI::height() {
|
|
if (_vpDatum) return _yHeight;
|
|
return _height;
|
|
}
|
|
|
|
int16_t TFT_eSPI::textWidth(const String& string) {
|
|
int16_t len = string.length() + 2;
|
|
char buffer[len];
|
|
string.toCharArray(buffer, len);
|
|
return textWidth(buffer, textfont);
|
|
}
|
|
|
|
int16_t TFT_eSPI::textWidth(const String& string, uint8_t font) {
|
|
int16_t len = string.length() + 2;
|
|
char buffer[len];
|
|
string.toCharArray(buffer, len);
|
|
return textWidth(buffer, font);
|
|
}
|
|
|
|
int16_t TFT_eSPI::textWidth(const char *string) {
|
|
return textWidth(string, textfont);
|
|
}
|
|
|
|
int16_t TFT_eSPI::textWidth(const char *string, uint8_t font) {
|
|
int32_t str_width = 0;
|
|
uint16_t uniCode = 0;
|
|
|
|
while (*string) {
|
|
uniCode = decodeUTF8(*string++);
|
|
if (uniCode) {
|
|
if (uniCode == 0x20) str_width += gFonts[font].spaceWidth;
|
|
else {
|
|
uint16_t gNum = 0;
|
|
bool found = getUnicodeIndex(uniCode, &gNum, font);
|
|
if (found) {
|
|
if(str_width == 0 && gdX[font][gNum] < 0) str_width -= gdX[font][gNum];
|
|
if (*string) str_width += gxAdvance[font][gNum];
|
|
else str_width += (gdX[font][gNum] + gWidth[font][gNum]);
|
|
} else str_width += gFonts[font].spaceWidth + 1;
|
|
}
|
|
}
|
|
}
|
|
return str_width;
|
|
}
|
|
|
|
int16_t TFT_eSPI::fontHeight(uint8_t font) {
|
|
if (font > 7) return 0;
|
|
return gFonts[font].yAdvance;
|
|
}
|
|
|
|
void TFT_eSPI::setAddrWindow(int32_t x0, int32_t y0, int32_t w, int32_t h) {
|
|
begin_tft_write();
|
|
setWindow(x0, y0, x0 + w - 1, y0 + h - 1);
|
|
end_tft_write();
|
|
}
|
|
|
|
// 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) {
|
|
addr_row = 0xFFFF;
|
|
addr_col = 0xFFFF;
|
|
|
|
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;
|
|
}
|
|
|
|
void TFT_eSPI::readAddrWindow(int32_t xs, int32_t ys, int32_t w, int32_t h) {
|
|
int32_t xe = xs + w - 1;
|
|
int32_t ye = ys + h - 1;
|
|
|
|
addr_col = 0xFFFF;
|
|
addr_row = 0xFFFF;
|
|
|
|
// Column addr set
|
|
DC_C; tft_Write_8(TFT_CASET);
|
|
DC_D; tft_Write_32C(xs, xe);
|
|
|
|
// Row addr set
|
|
DC_C; tft_Write_8(TFT_PASET);
|
|
DC_D; tft_Write_32C(ys, ye);
|
|
|
|
// Read CGRAM command
|
|
DC_C; tft_Write_8(TFT_RAMRD);
|
|
|
|
DC_D;
|
|
}
|
|
|
|
void TFT_eSPI::drawPixel(int32_t x, int32_t y, uint32_t color) {
|
|
if (_vpOoB) return;
|
|
|
|
x+= _xDatum;
|
|
y+= _yDatum;
|
|
|
|
// Range checking
|
|
if ((x < _vpX) || (y < _vpY) ||(x >= _vpW) || (y >= _vpH)) return;
|
|
|
|
begin_tft_write();
|
|
SPI_BUSY_CHECK;
|
|
|
|
// No need to send x if it has not changed (speeds things up)
|
|
if (addr_col != x) {
|
|
DC_C; tft_Write_8(TFT_CASET);
|
|
DC_D; tft_Write_32D(x);
|
|
addr_col = x;
|
|
}
|
|
|
|
// No need to send y if it has not changed (speeds things up)
|
|
if (addr_row != y) {
|
|
DC_C; tft_Write_8(TFT_PASET);
|
|
DC_D; tft_Write_32D(y);
|
|
addr_row = y;
|
|
}
|
|
|
|
DC_C; tft_Write_8(TFT_RAMWR);
|
|
|
|
DC_D; tft_Write_16N(color);
|
|
|
|
end_tft_write();
|
|
}
|
|
|
|
void TFT_eSPI::pushColor(uint16_t color) {
|
|
begin_tft_write();
|
|
SPI_BUSY_CHECK;
|
|
tft_Write_16N(color);
|
|
end_tft_write();
|
|
}
|
|
|
|
void TFT_eSPI::pushColor(uint16_t color, uint32_t len) {
|
|
begin_tft_write();
|
|
|
|
pushBlock(color, len);
|
|
|
|
end_tft_write();
|
|
}
|
|
|
|
void TFT_eSPI::drawLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint32_t color) {
|
|
if (_vpOoB) return;
|
|
|
|
//begin_tft_write(); // Sprite class can use this function, avoiding begin_tft_write()
|
|
|
|
bool steep = abs(y1 - y0) > abs(x1 - x0);
|
|
if (steep) {
|
|
transpose(x0, y0);
|
|
transpose(x1, y1);
|
|
}
|
|
|
|
if (x0 > x1) {
|
|
transpose(x0, x1);
|
|
transpose(y0, y1);
|
|
}
|
|
|
|
int32_t dx = x1 - x0, dy = abs(y1 - y0);;
|
|
|
|
int32_t err = dx >> 1, ystep = -1, xs = x0, dlen = 0;
|
|
|
|
if (y0 < y1) ystep = 1;
|
|
|
|
// Split into steep and not steep for FastH/V separation
|
|
if (steep) {
|
|
for (; x0 <= x1; x0++) {
|
|
dlen++;
|
|
err -= dy;
|
|
if (err < 0) {
|
|
if (dlen == 1) drawPixel(y0, xs, color);
|
|
else drawFastVLine(y0, xs, dlen, color);
|
|
dlen = 0;
|
|
y0 += ystep; xs = x0 + 1;
|
|
err += dx;
|
|
}
|
|
}
|
|
if (dlen) drawFastVLine(y0, xs, dlen, color);
|
|
} else {
|
|
for (; x0 <= x1; x0++) {
|
|
dlen++;
|
|
err -= dy;
|
|
if (err < 0) {
|
|
if (dlen == 1) drawPixel(xs, y0, color);
|
|
else drawFastHLine(xs, y0, dlen, color);
|
|
dlen = 0;
|
|
y0 += ystep; xs = x0 + 1;
|
|
err += dx;
|
|
}
|
|
}
|
|
if (dlen) drawFastHLine(xs, y0, dlen, color);
|
|
}
|
|
|
|
end_tft_write();
|
|
}
|
|
|
|
uint16_t TFT_eSPI::drawPixel(int32_t x, int32_t y, uint32_t color, uint8_t alpha, uint32_t bg_color) {
|
|
if (bg_color == 0x00FFFFFF) bg_color = 0;
|
|
color = fastBlend(alpha, color, bg_color);
|
|
drawPixel(x, y, color);
|
|
return color;
|
|
}
|
|
|
|
void TFT_eSPI::drawFastVLine(int32_t x, int32_t y, int32_t h, uint32_t color) {
|
|
if (_vpOoB) return;
|
|
|
|
x+= _xDatum;
|
|
y+= _yDatum;
|
|
|
|
// Clipping
|
|
if ((x < _vpX) || (x >= _vpW) || (y >= _vpH)) return;
|
|
|
|
if (y < _vpY) { h += y - _vpY; y = _vpY; }
|
|
|
|
if ((y + h) > _vpH) h = _vpH - y;
|
|
|
|
if (h < 1) return;
|
|
|
|
begin_tft_write();
|
|
|
|
setWindow(x, y, x, y + h - 1);
|
|
|
|
pushBlock(color, h);
|
|
|
|
end_tft_write();
|
|
}
|
|
|
|
void TFT_eSPI::drawFastHLine(int32_t x, int32_t y, int32_t w, uint32_t color) {
|
|
if (_vpOoB) return;
|
|
|
|
x+= _xDatum;
|
|
y+= _yDatum;
|
|
|
|
// Clipping
|
|
if ((y < _vpY) || (x >= _vpW) || (y >= _vpH)) return;
|
|
|
|
if (x < _vpX) { w += x - _vpX; x = _vpX; }
|
|
|
|
if ((x + w) > _vpW) w = _vpW - x;
|
|
|
|
if (w < 1) return;
|
|
|
|
begin_tft_write();
|
|
|
|
setWindow(x, y, x + w - 1, y);
|
|
|
|
pushBlock(color, w);
|
|
|
|
end_tft_write();
|
|
}
|
|
|
|
void TFT_eSPI::fillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color) {
|
|
if (_vpOoB) return;
|
|
|
|
x+= _xDatum;
|
|
y+= _yDatum;
|
|
|
|
// Clipping
|
|
if ((x >= _vpW) || (y >= _vpH)) return;
|
|
|
|
if (x < _vpX) { w += x - _vpX; x = _vpX; }
|
|
if (y < _vpY) { h += y - _vpY; y = _vpY; }
|
|
|
|
if ((x + w) > _vpW) w = _vpW - x;
|
|
if ((y + h) > _vpH) h = _vpH - y;
|
|
|
|
if ((w < 1) || (h < 1)) return;
|
|
|
|
begin_tft_write();
|
|
|
|
setWindow(x, y, x + w - 1, y + h - 1);
|
|
|
|
pushBlock(color, w * h);
|
|
|
|
end_tft_write();
|
|
}
|
|
|
|
void TFT_eSPI::invertDisplay(bool i) {
|
|
begin_tft_write();
|
|
writecommand(TFT_INVOFF | i); writecommand(TFT_INVOFF | i);
|
|
end_tft_write();
|
|
}
|
|
|
|
uint16_t TFT_eSPI::decodeUTF8(uint8_t c) {
|
|
if ((c & 0x80) == 0x00) {
|
|
decoderState = 0;
|
|
return c;
|
|
}
|
|
|
|
if (decoderState == 0) {
|
|
if ((c & 0xE0) == 0xC0) {
|
|
decoderBuffer = ((c & 0x1F)<<6);
|
|
decoderState = 1;
|
|
return 0;
|
|
}
|
|
if ((c & 0xF0) == 0xE0) {
|
|
decoderBuffer = ((c & 0x0F)<<12);
|
|
decoderState = 2;
|
|
return 0;
|
|
}
|
|
} else {
|
|
if (decoderState == 2) {
|
|
decoderBuffer |= ((c & 0x3F)<<6);
|
|
decoderState--;
|
|
return 0;
|
|
} else {
|
|
decoderBuffer |= (c & 0x3F);
|
|
decoderState = 0;
|
|
return decoderBuffer;
|
|
}
|
|
}
|
|
|
|
decoderState = 0;
|
|
|
|
return c; // fall-back to extended ASCII
|
|
}
|
|
|
|
uint16_t TFT_eSPI::decodeUTF8(uint8_t *buf, uint16_t *index, uint16_t remaining) {
|
|
uint16_t c = buf[(*index)++];
|
|
|
|
if ((c & 0x80) == 0x00) return c;
|
|
|
|
if (((c & 0xE0) == 0xC0) && (remaining > 1)) return ((c & 0x1F)<<6) | (buf[(*index)++]&0x3F);
|
|
|
|
if (((c & 0xF0) == 0xE0) && (remaining > 2)) {
|
|
c = ((c & 0x0F)<<12) | ((buf[(*index)++]&0x3F)<<6);
|
|
return c | ((buf[(*index)++]&0x3F));
|
|
}
|
|
|
|
return c; // fall-back to extended ASCII
|
|
}
|
|
|
|
uint16_t TFT_eSPI::alphaBlend(uint8_t alpha, uint16_t fgc, uint16_t bgc) {
|
|
// Split out and blend 5-bit red and blue channels
|
|
uint32_t rxb = bgc & 0xF81F;
|
|
rxb += ((fgc & 0xF81F) - rxb) * (alpha >> 2) >> 6;
|
|
// Split out and blend 6-bit green channel
|
|
uint32_t xgx = bgc & 0x07E0;
|
|
xgx += ((fgc & 0x07E0) - xgx) * alpha >> 8;
|
|
// Recombine channels
|
|
return (rxb & 0xF81F) | (xgx & 0x07E0);
|
|
}
|
|
|
|
uint16_t TFT_eSPI::alphaBlend(uint8_t alpha, uint16_t fgc, uint16_t bgc, uint8_t dither) {
|
|
if (dither) {
|
|
int16_t alphaDither = (int16_t)alpha - dither + random(2*dither+1); // +/-4 randomised
|
|
alpha = (uint8_t)alphaDither;
|
|
if (alphaDither < 0) alpha = 0;
|
|
if (alphaDither > 255) alpha = 255;
|
|
}
|
|
|
|
return alphaBlend(alpha, fgc, bgc);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
int16_t TFT_eSPI::drawString(const char *string, int32_t poX, int32_t poY) {
|
|
return drawString(string, poX, poY, textfont);
|
|
}
|
|
|
|
int16_t TFT_eSPI::drawString(const char *string, int32_t poX, int32_t poY, uint8_t font) {
|
|
if (font > 7) return 0;
|
|
|
|
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
|
|
|
|
baseline = gFonts[font].maxAscent;
|
|
uint16_t cheight = fontHeight(font);
|
|
|
|
if (textdatum) {
|
|
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;
|
|
break;
|
|
case MC_DATUM:
|
|
poX -= cwidth/2;
|
|
poY -= cheight/2;
|
|
padding += 1;
|
|
break;
|
|
case MR_DATUM:
|
|
poX -= cwidth;
|
|
poY -= cheight/2;
|
|
padding += 2;
|
|
break;
|
|
case BL_DATUM:
|
|
poY -= cheight;
|
|
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;
|
|
break;
|
|
case C_BASELINE:
|
|
poX -= cwidth/2;
|
|
poY -= baseline;
|
|
padding += 1;
|
|
break;
|
|
case R_BASELINE:
|
|
poX -= cwidth;
|
|
poY -= baseline;
|
|
padding += 2;
|
|
break;
|
|
}
|
|
}
|
|
|
|
uint16_t len = strlen(string);
|
|
uint16_t n = 0;
|
|
|
|
setCursor(poX, poY);
|
|
|
|
// If padding is requested then fill the text background
|
|
while (n < len) {
|
|
uint16_t uniCode = decodeUTF8((uint8_t*)string, &n, len - n);
|
|
drawGlyph(uniCode, font);
|
|
}
|
|
sumX += cwidth;
|
|
|
|
return sumX;
|
|
}
|
|
|
|
void TFT_eSPI::setTextFont(uint8_t f) {
|
|
textfont = (f > FONT_COUNT) ? 1 : f; // Don't allow font > 7
|
|
}
|
|
|
|
void TFT_eSPI::loadFont(const uint8_t array[], uint8_t font) {
|
|
if (array == nullptr) return;
|
|
fontPtr = (uint8_t*)array;
|
|
unloadFont(font);
|
|
|
|
gFonts[font].gArray = (const uint8_t*)fontPtr;
|
|
gFonts[font].gCount = (uint16_t)readInt32(); // glyph count in file
|
|
readInt32(); // vlw encoder version - discard
|
|
gFonts[font].yAdvance = (uint16_t)readInt32(); // Font size in points, not pixels
|
|
readInt32();
|
|
gFonts[font].ascent = (uint16_t)readInt32(); // top of "d"
|
|
gFonts[font].descent = (uint16_t)readInt32(); // bottom of "p"
|
|
|
|
gFonts[font].maxAscent = gFonts[font].ascent;
|
|
gFonts[font].maxDescent = gFonts[font].descent;
|
|
gFonts[font].yAdvance = gFonts[font].ascent + gFonts[font].descent;
|
|
gFonts[font].spaceWidth = gFonts[font].yAdvance / 4;
|
|
|
|
loadMetrics(font);
|
|
fontOwned[font] = true;
|
|
}
|
|
|
|
void TFT_eSPI::loadMetrics(uint8_t font) {
|
|
uint32_t headerPtr = 24;
|
|
uint32_t bitmapPtr = headerPtr + gFonts[font].gCount * 28;
|
|
|
|
gUnicode[font] = (uint16_t*)malloc( gFonts[font].gCount * 2); // Unicode 16-bit Basic Multilingual Plane (0-FFFF)
|
|
gHeight[font] = (uint8_t*)malloc( gFonts[font].gCount ); // Height of glyph
|
|
gWidth[font] = (uint8_t*)malloc( gFonts[font].gCount ); // Width of glyph
|
|
gxAdvance[font] = (uint8_t*)malloc( gFonts[font].gCount ); // xAdvance - to move x cursor
|
|
gdY[font] = (int16_t*)malloc( gFonts[font].gCount * 2); // offset from bitmap top edge from lowest point in any character
|
|
gdX[font] = (int8_t*)malloc( gFonts[font].gCount ); // offset for bitmap left edge relative to cursor X
|
|
gBitmap[font] = (uint32_t*)malloc( gFonts[font].gCount * 4); // seek pointer to glyph bitmap in the file
|
|
|
|
uint16_t gNum = 0;
|
|
|
|
while (gNum < gFonts[font].gCount) {
|
|
gUnicode[font][gNum] = (uint16_t)readInt32(); // Unicode code point value
|
|
gHeight[font][gNum] = (uint8_t)readInt32(); // Height of glyph
|
|
gWidth[font][gNum] = (uint8_t)readInt32(); // Width of glyph
|
|
gxAdvance[font][gNum] = (uint8_t)readInt32(); // xAdvance - to move x cursor
|
|
gdY[font][gNum] = (int16_t)readInt32(); // y delta from baseline
|
|
gdX[font][gNum] = (int8_t)readInt32(); // x delta from cursor
|
|
readInt32(); // ignored
|
|
|
|
// Different glyph sets have different descent values not always based on "p", so get maximum glyph descent
|
|
if (((int16_t)gHeight[font][gNum] - (int16_t)gdY[font][gNum]) > gFonts[font].maxDescent) {
|
|
// Avoid UTF coding values and characters that tend to give duff values
|
|
if (((gUnicode[font][gNum] > 0x20) && (gUnicode[font][gNum] < 0xA0) && (gUnicode[font][gNum] != 0x7F)) || (gUnicode[font][gNum] > 0xFF)) gFonts[font].maxDescent = gHeight[font][gNum] - gdY[font][gNum];
|
|
}
|
|
|
|
gBitmap[font][gNum] = bitmapPtr;
|
|
|
|
bitmapPtr += gWidth[font][gNum] * gHeight[font][gNum];
|
|
|
|
gNum++;
|
|
delay(0);
|
|
}
|
|
|
|
gFonts[font].yAdvance = gFonts[font].maxAscent + gFonts[font].maxDescent;
|
|
|
|
gFonts[font].spaceWidth = (gFonts[font].ascent + gFonts[font].descent) * 2/7; // Guess at space width
|
|
}
|
|
|
|
void TFT_eSPI::unloadFont(uint8_t font) {
|
|
if (!fontOwned[font]) {
|
|
gUnicode[font] = NULL;
|
|
gHeight[font] = NULL;
|
|
gWidth[font] = NULL;
|
|
gxAdvance[font] = NULL;
|
|
gdY[font] = NULL;
|
|
gdX[font] = NULL;
|
|
gBitmap[font] = NULL;
|
|
gFonts[font].gArray = nullptr;
|
|
return;
|
|
}
|
|
|
|
if (gUnicode[font]) {
|
|
free(gUnicode[font]);
|
|
gUnicode[font] = NULL;
|
|
}
|
|
|
|
if (gHeight[font]) {
|
|
free(gHeight[font]);
|
|
gHeight[font] = NULL;
|
|
}
|
|
|
|
if (gWidth[font]) {
|
|
free(gWidth[font]);
|
|
gWidth[font] = NULL;
|
|
}
|
|
|
|
if (gxAdvance[font]) {
|
|
free(gxAdvance[font]);
|
|
gxAdvance[font] = NULL;
|
|
}
|
|
|
|
if (gdY[font]) {
|
|
free(gdY[font]);
|
|
gdY[font] = NULL;
|
|
}
|
|
|
|
if (gdX[font]) {
|
|
free(gdX[font]);
|
|
gdX[font] = NULL;
|
|
}
|
|
|
|
if (gBitmap[font]) {
|
|
free(gBitmap[font]);
|
|
gBitmap[font] = NULL;
|
|
}
|
|
|
|
gFonts[font].gArray = nullptr;
|
|
}
|
|
|
|
uint32_t TFT_eSPI::readInt32() {
|
|
uint32_t val = (uint32_t)pgm_read_byte(fontPtr++) << 24;
|
|
val |= (uint32_t)pgm_read_byte(fontPtr++) << 16;
|
|
val |= (uint32_t)pgm_read_byte(fontPtr++) << 8;
|
|
val |= (uint32_t)pgm_read_byte(fontPtr++);
|
|
|
|
return val;
|
|
}
|
|
|
|
bool TFT_eSPI::getUnicodeIndex(uint16_t unicode, uint16_t *index, uint16_t font)
|
|
{
|
|
for (uint16_t i = 0; i < gFonts[font].gCount; i++) {
|
|
if (gUnicode[font][i] == unicode) {
|
|
*index = i;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void TFT_eSPI::drawGlyph(uint16_t code, uint16_t font) {
|
|
if (last_cursor_x != cursor_x) {
|
|
bg_cursor_x = cursor_x;
|
|
last_cursor_x = cursor_x;
|
|
}
|
|
|
|
if (code < 0x21) {
|
|
if (code == 0x20) {
|
|
if (_fillbg) fillRect(bg_cursor_x, cursor_y, (cursor_x + gFonts[font].spaceWidth) - bg_cursor_x, gFonts[font].yAdvance, textbgcolor);
|
|
cursor_x += gFonts[font].spaceWidth;
|
|
bg_cursor_x = cursor_x;
|
|
last_cursor_x = cursor_x;
|
|
return;
|
|
}
|
|
|
|
if (code == '\n') {
|
|
cursor_x = 0;
|
|
bg_cursor_x = 0;
|
|
last_cursor_x = 0;
|
|
cursor_y += gFonts[font].yAdvance;
|
|
if (textwrapY && (cursor_y >= height())) cursor_y = 0;
|
|
return;
|
|
}
|
|
}
|
|
|
|
uint16_t gNum = 0;
|
|
bool found = getUnicodeIndex(code, &gNum, font);
|
|
|
|
if (found) {
|
|
if (textwrapX && (cursor_x + gWidth[font][gNum] + gdX[font][gNum] > width())) {
|
|
cursor_y += gFonts[font].yAdvance;
|
|
cursor_x = 0;
|
|
bg_cursor_x = 0;
|
|
}
|
|
if (textwrapY && ((cursor_y + gFonts[font].yAdvance) >= height())) cursor_y = 0;
|
|
if (cursor_x == 0) cursor_x -= gdX[font][gNum];
|
|
|
|
const uint8_t* gPtr = (const uint8_t*) gFonts[font].gArray;
|
|
|
|
int16_t cy = cursor_y + gFonts[font].maxAscent - gdY[font][gNum];
|
|
int16_t cx = cursor_x + gdX[font][gNum];
|
|
|
|
int16_t fxs = cx;
|
|
uint32_t fl = 0;
|
|
int16_t bxs = cx;
|
|
uint32_t bl = 0;
|
|
int16_t bx = 0;
|
|
uint8_t pixel;
|
|
|
|
begin_tft_write();
|
|
|
|
int16_t fillwidth = 0;
|
|
int16_t fillheight = 0;
|
|
|
|
// Fill area above glyph
|
|
if (_fillbg) {
|
|
fillwidth = (cursor_x + gxAdvance[font][gNum]) - bg_cursor_x;
|
|
if (fillwidth > 0) {
|
|
fillheight = gFonts[font].maxAscent - gdY[font][gNum];
|
|
// Could be negative
|
|
if (fillheight > 0) fillRect(bg_cursor_x, cursor_y, fillwidth, fillheight, textbgcolor);
|
|
} else fillwidth = 0;
|
|
|
|
// Fill any area to left of glyph
|
|
if (bg_cursor_x < cx) fillRect(bg_cursor_x, cy, cx - bg_cursor_x, gHeight[font][gNum], textbgcolor);
|
|
// Set x position in glyph area where background starts
|
|
if (bg_cursor_x > cx) bx = bg_cursor_x - cx;
|
|
// Fill any area to right of glyph
|
|
if (cx + gWidth[gNum] < cursor_x + gxAdvance[gNum]) fillRect(cx + gWidth[font][gNum], cy, (cursor_x + gxAdvance[gNum]) - (cx + gWidth[gNum]), gHeight[font][gNum], textbgcolor);
|
|
}
|
|
|
|
for(int32_t y = 0; y < gHeight[font][gNum]; y++) {
|
|
for(int32_t x = 0; x < gWidth[font][gNum]; x++) {
|
|
pixel = pgm_read_byte(gPtr + gBitmap[font][gNum] + x + gWidth[font][gNum] * y);
|
|
if (pixel) {
|
|
if (bl) { drawFastHLine( bxs, y + cy, bl, textbgcolor); bl = 0; }
|
|
if (pixel != 0xFF) {
|
|
if (fl) {
|
|
if (fl==1) drawPixel(fxs, y + cy, textcolor);
|
|
else drawFastHLine( fxs, y + cy, fl, textcolor);
|
|
fl = 0;
|
|
}
|
|
drawPixel(x + cx, y + cy, alphaBlend(pixel, textcolor, textbgcolor));
|
|
} else {
|
|
if (fl==0) fxs = x + cx;
|
|
fl++;
|
|
}
|
|
} else {
|
|
if (fl) { drawFastHLine( fxs, y + cy, fl, textcolor); fl = 0; }
|
|
if (_fillbg) {
|
|
if (x >= bx) {
|
|
if (bl==0) bxs = x + cx;
|
|
bl++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (fl) { drawFastHLine( fxs, y + cy, fl, textcolor); fl = 0; }
|
|
if (bl) { drawFastHLine( bxs, y + cy, bl, textcolor); bl = 0; }
|
|
}
|
|
|
|
// Fill area below glyph
|
|
if (fillwidth > 0) {
|
|
fillheight = (cursor_y + gFonts[font].yAdvance) - (cy + gHeight[font][gNum]);
|
|
if (fillheight > 0) fillRect(bg_cursor_x, cy + gHeight[font][gNum], fillwidth, fillheight, textbgcolor);
|
|
}
|
|
|
|
cursor_x += gxAdvance[font][gNum];
|
|
end_tft_write();
|
|
} else {
|
|
// Point code not in font so draw a rectangle and move on cursor
|
|
drawRect(cursor_x, cursor_y + gFonts[font].maxAscent - gFonts[font].ascent, gFonts[font].spaceWidth, gFonts[font].ascent, textcolor);
|
|
cursor_x += gFonts[font].spaceWidth + 1;
|
|
}
|
|
bg_cursor_x = cursor_x;
|
|
last_cursor_x = cursor_x;
|
|
}
|
|
|
|
#ifndef Z_THRESHOLD
|
|
#define Z_THRESHOLD 350 // Touch pressure threshold for validating touches
|
|
#endif
|
|
|
|
inline void TFT_eSPI::begin_touch_read_write() {
|
|
CS_H; // Just in case it has been left low
|
|
SPI_SET_CLOCK_FREQ(SPI_TOUCH_FREQUENCY);
|
|
SET_BUS_READ_MODE;
|
|
gpio_set_level((gpio_num_t)TOUCH_CS, 0);
|
|
}
|
|
|
|
inline void TFT_eSPI::end_touch_read_write() {
|
|
gpio_set_level((gpio_num_t)TOUCH_CS, 1);
|
|
}
|
|
|
|
uint8_t TFT_eSPI::getTouchRaw(uint16_t *x, uint16_t *y) {
|
|
uint16_t tmp;
|
|
|
|
begin_touch_read_write();
|
|
|
|
spi_transfer(0xd0);
|
|
spi_transfer(0);
|
|
spi_transfer(0xd0);
|
|
spi_transfer(0);
|
|
spi_transfer(0xd0);
|
|
spi_transfer(0);
|
|
spi_transfer(0xd0);
|
|
|
|
tmp = spi_transfer(0) << 5; // Read first 8 bits
|
|
tmp |= 0x1f & (spi_transfer(0x90)>>3); // Read last 8 bits and start new XP 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
|
|
|
|
tmp = spi_transfer(0) << 5; // Read first 8 bits
|
|
tmp |= 0x1f & (spi_transfer(0)>>3); // Read last 8 bits
|
|
|
|
*y = tmp;
|
|
|
|
end_touch_read_write();
|
|
|
|
return true;
|
|
}
|
|
|
|
uint16_t TFT_eSPI::getTouchRawZ() {
|
|
begin_touch_read_write();
|
|
|
|
// Z sample request
|
|
int16_t tz = 0xFFF;
|
|
spi_transfer(0xb0); // Start new Z1 conversion
|
|
tz += transfer16(0xc0) >> 3; // Read Z1 and start Z2 conversion
|
|
tz -= transfer16(0x00) >> 3; // Read Z2
|
|
|
|
end_touch_read_write();
|
|
|
|
if (tz == 4095) tz = 0;
|
|
|
|
return (uint16_t)tz;
|
|
}
|
|
|
|
#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;
|
|
|
|
// Wait until pressure stops increasing to debounce pressure
|
|
uint16_t z1 = 1;
|
|
uint16_t z2 = 0;
|
|
while (z1 > z2) {
|
|
z2 = z1;
|
|
z1 = getTouchRawZ();
|
|
delay(1);
|
|
}
|
|
|
|
if (z1 <= threshold) return false;
|
|
|
|
getTouchRaw(&x_tmp,&y_tmp);
|
|
|
|
delay(1); // Small delay to the next sample
|
|
if (getTouchRawZ() <= threshold) return false;
|
|
|
|
delay(2); // Small delay to the next sample
|
|
getTouchRaw(&x_tmp2,&y_tmp2);
|
|
|
|
if (abs(x_tmp - x_tmp2) > _RAWERR) return false;
|
|
if (abs(y_tmp - y_tmp2) > _RAWERR) return false;
|
|
|
|
*x = x_tmp;
|
|
*y = y_tmp;
|
|
|
|
return true;
|
|
}
|
|
|
|
uint8_t TFT_eSPI::getTouch(uint16_t *x, uint16_t *y, uint16_t threshold) {
|
|
uint16_t x_tmp, y_tmp;
|
|
|
|
if (threshold<20) threshold = 20;
|
|
if (_pressTime > millis()) threshold=20;
|
|
|
|
uint8_t n = 5;
|
|
uint8_t valid = 0;
|
|
while (n--) {
|
|
if (validTouch(&x_tmp, &y_tmp, threshold)) valid++;
|
|
}
|
|
|
|
if (valid<1) { _pressTime = 0; return false; }
|
|
|
|
_pressTime = millis() + 50;
|
|
|
|
convertRawXY(&x_tmp, &y_tmp);
|
|
|
|
if (x_tmp >= _width || y_tmp >= _height) return false;
|
|
|
|
*x = x_tmp;
|
|
*y = y_tmp;
|
|
return valid;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
|
|
for(uint8_t i = 0; i < 4; i++){
|
|
fillRect(0, 0, size+1, size+1, color_bg);
|
|
fillRect(0, _height-size-1, size+1, size+1, color_bg);
|
|
fillRect(_width-size-1, 0, size+1, size+1, color_bg);
|
|
fillRect(_width-size-1, _height-size-1, size+1, size+1, color_bg);
|
|
|
|
if (i == 5) break; // used to clear the arrows
|
|
|
|
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;
|
|
}
|
|
|
|
// user has to get the chance to release
|
|
if(i>0) delay(1000);
|
|
|
|
for(uint8_t j= 0; j<8; j++){
|
|
// Use a lower detect threshold as corners tend to be less sensitive
|
|
while(!validTouch(&x_tmp, &y_tmp, Z_THRESHOLD/2));
|
|
values[i*2 ] += x_tmp;
|
|
values[i*2+1] += y_tmp;
|
|
}
|
|
values[i*2 ] /= 8;
|
|
values[i*2+1] /= 8;
|
|
}
|
|
|
|
|
|
// from case 0 to case 1, the y value changed.
|
|
// If the measured delta of the touch x axis is bigger than the delta of the y axis, the touch and TFT axes are switched.
|
|
touchCalibration_rotate = false;
|
|
if(abs(values[0]-values[2]) > abs(values[1]-values[3])){
|
|
touchCalibration_rotate = true;
|
|
touchCalibration_x0 = (values[1] + values[3])/2; // calc min x
|
|
touchCalibration_x1 = (values[5] + values[7])/2; // calc max x
|
|
touchCalibration_y0 = (values[0] + values[4])/2; // calc min y
|
|
touchCalibration_y1 = (values[2] + values[6])/2; // calc max y
|
|
} else {
|
|
touchCalibration_x0 = (values[0] + values[2])/2; // calc min x
|
|
touchCalibration_x1 = (values[4] + values[6])/2; // calc max x
|
|
touchCalibration_y0 = (values[1] + values[5])/2; // calc min y
|
|
touchCalibration_y1 = (values[3] + values[7])/2; // calc max y
|
|
}
|
|
|
|
// 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];
|
|
touchCalibration_invert_y = true;
|
|
}
|
|
|
|
// 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;
|
|
|
|
// export parameters, if pointer valid
|
|
if(parameters != NULL){
|
|
parameters[0] = touchCalibration_x0;
|
|
parameters[1] = touchCalibration_x1;
|
|
parameters[2] = touchCalibration_y0;
|
|
parameters[3] = touchCalibration_y1;
|
|
parameters[4] = touchCalibration_rotate | (touchCalibration_invert_x <<1) | (touchCalibration_invert_y <<2);
|
|
}
|
|
}
|
|
|
|
|
|
void TFT_eSPI::setTouch(uint16_t *parameters){
|
|
touchCalibration_x0 = parameters[0];
|
|
touchCalibration_x1 = parameters[1];
|
|
touchCalibration_y0 = parameters[2];
|
|
touchCalibration_y1 = parameters[3];
|
|
|
|
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;
|
|
|
|
touchCalibration_rotate = parameters[4] & 0x01;
|
|
touchCalibration_invert_x = parameters[4] & 0x02;
|
|
touchCalibration_invert_y = parameters[4] & 0x04;
|
|
}
|
|
|
|
TFT_eSprite::TFT_eSprite(TFT_eSPI *tft) {
|
|
_tft = tft;
|
|
|
|
_iwidth = 0; // Initialise width and height to 0 (it does not exist yet)
|
|
_iheight = 0;
|
|
_swapBytes = true;
|
|
|
|
_created = false;
|
|
_vpOoB = true;
|
|
|
|
_xs = 0; // window bounds for pushColor
|
|
_ys = 0;
|
|
_xe = 0;
|
|
_ye = 0;
|
|
|
|
_xptr = 0; // pushColor coordinate
|
|
_yptr = 0;
|
|
}
|
|
|
|
void* TFT_eSprite::createSprite(int16_t w, int16_t h) {
|
|
if ( _created ) return _img;
|
|
|
|
if ( w < 1 || h < 1 ) return nullptr;
|
|
|
|
_iwidth = _dwidth = w;
|
|
_iheight = _dheight = h;
|
|
|
|
cursor_x = 0;
|
|
cursor_y = 0;
|
|
|
|
_sx = 0;
|
|
_sy = 0;
|
|
_sw = w;
|
|
_sh = h;
|
|
_scolor = TFT_BLACK;
|
|
|
|
_img = (uint16_t*)callocSprite(w, h);
|
|
|
|
if (_img) {
|
|
_created = true;
|
|
|
|
rotation = 0;
|
|
setViewport(0, 0, _dwidth, _dheight);
|
|
return _img;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void* TFT_eSprite::getPointer() {
|
|
if (!_created) return nullptr;
|
|
return _img;
|
|
}
|
|
|
|
TFT_eSprite::~TFT_eSprite() {
|
|
deleteSprite();
|
|
for(int i = 0; i < FONT_COUNT; i++) unloadFont(i);
|
|
}
|
|
|
|
void* TFT_eSprite::callocSprite(int16_t w, int16_t h) {
|
|
// 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;
|
|
|
|
ptr8 = (uint8_t*)calloc(w * h + 1, sizeof(uint16_t));
|
|
|
|
return ptr8;
|
|
}
|
|
|
|
void TFT_eSprite::deleteSprite() {
|
|
if (_created) {
|
|
free(_img);
|
|
_img = nullptr;
|
|
_created = false;
|
|
_vpOoB = true;
|
|
}
|
|
}
|
|
|
|
void TFT_eSprite::pushSprite(int32_t x, int32_t y) {
|
|
if (!_created) return;
|
|
|
|
bool oldSwapBytes = _tft->getSwapBytes();
|
|
_tft->setSwapBytes(false);
|
|
_tft->pushImage(x, y, _dwidth, _dheight, _img );
|
|
_tft->setSwapBytes(oldSwapBytes);
|
|
}
|
|
|
|
void TFT_eSprite::pushSprite(int32_t x, int32_t y, uint16_t transp) {
|
|
if (!_created) return;
|
|
|
|
bool oldSwapBytes = _tft->getSwapBytes();
|
|
_tft->setSwapBytes(false);
|
|
_tft->pushImage(x, y, _dwidth, _dheight, _img, transp );
|
|
_tft->setSwapBytes(oldSwapBytes);
|
|
}
|
|
|
|
bool TFT_eSprite::pushToSprite(TFT_eSprite *dspr, int32_t x, int32_t y) {
|
|
if (!_created) return false;
|
|
if (!dspr->created()) return false;
|
|
|
|
bool oldSwapBytes = dspr->getSwapBytes();
|
|
dspr->setSwapBytes(false);
|
|
dspr->pushImage(x, y, _dwidth, _dheight, _img);
|
|
dspr->setSwapBytes(oldSwapBytes);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool TFT_eSprite::pushToSprite(TFT_eSprite *dspr, int32_t x, int32_t y, uint16_t transp) {
|
|
if ( !_created || !dspr->_created) return false; // Check Sprites exist
|
|
|
|
bool oldSwapBytes = dspr->getSwapBytes();
|
|
uint16_t sline_buffer[width()];
|
|
|
|
transp = transp>>8 | transp<<8;
|
|
|
|
// 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;
|
|
|
|
for (int32_t xs = 0; xs < width(); xs++) {
|
|
uint16_t rp = _img[xs + ys * width()];
|
|
|
|
if (transp == rp) {
|
|
if (pixel_count) {
|
|
dspr->pushImage(ox, y, pixel_count, 1, sline_buffer);
|
|
ox += pixel_count;
|
|
pixel_count = 0;
|
|
}
|
|
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;
|
|
}
|
|
|
|
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;
|
|
|
|
// Perform window boundary checks and crop if needed
|
|
setWindow(sx, sy, sx + sw - 1, sy + sh - 1);
|
|
|
|
// Calculate new sprite window bounding box width and height
|
|
sw = _xe - _xs + 1;
|
|
sh = _ye - _ys + 1;
|
|
|
|
if (_ys >= _iheight) return false;
|
|
|
|
bool oldSwapBytes = _tft->getSwapBytes();
|
|
_tft->setSwapBytes(false);
|
|
|
|
// Check if a faster block copy to screen is possible
|
|
if ( sx == 0 && sw == _dwidth) _tft->pushImage(tx, ty, sw, sh, _img + _iwidth * _ys );
|
|
else {
|
|
while (sh--) _tft->pushImage(tx, ty++, sw, 1, _img + _xs + _iwidth * _ys++ );
|
|
}
|
|
|
|
_tft->setSwapBytes(oldSwapBytes);
|
|
|
|
return true;
|
|
}
|
|
|
|
void TFT_eSprite::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t *data, uint8_t sbpp) {
|
|
if (data == nullptr || !_created) return;
|
|
|
|
PI_CLIP;
|
|
|
|
// Pointer within original image
|
|
uint8_t *ptro = (uint8_t *)data + ((dx + dy * w) << 1);
|
|
// Pointer within sprite image
|
|
uint8_t *ptrs = (uint8_t *)_img + ((x + y * _iwidth) << 1);
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
void TFT_eSprite::setWindow(int32_t x0, int32_t y0, int32_t x1, int32_t y1) {
|
|
if (x0 > x1) transpose(x0, x1);
|
|
if (y0 > y1) transpose(y0, y1);
|
|
|
|
int32_t w = width();
|
|
int32_t h = height();
|
|
|
|
if ((x0 >= w) || (x1 < 0) || (y0 >= h) || (y1 < 0)) { // Point to that extra "off screen" pixel
|
|
_xs = 0;
|
|
_ys = _dheight;
|
|
_xe = 0;
|
|
_ye = _dheight;
|
|
} else {
|
|
if (x0 < 0) x0 = 0;
|
|
if (x1 >= w) x1 = w - 1;
|
|
if (y0 < 0) y0 = 0;
|
|
if (y1 >= h) y1 = h - 1;
|
|
|
|
_xs = x0;
|
|
_ys = y0;
|
|
_xe = x1;
|
|
_ye = y1;
|
|
}
|
|
|
|
_xptr = _xs;
|
|
_yptr = _ys;
|
|
}
|
|
|
|
void TFT_eSprite::pushColor(uint16_t color) {
|
|
if (!_created ) return;
|
|
|
|
// Write the colour to RAM in set window
|
|
_img[_xptr + _yptr * _iwidth] = (uint16_t) (color >> 8) | (color << 8);
|
|
|
|
_xptr++;
|
|
|
|
// Wrap on x and y to start, increment y if needed
|
|
if (_xptr > _xe) {
|
|
_xptr = _xs;
|
|
_yptr++;
|
|
if (_yptr > _ye) _yptr = _ys;
|
|
}
|
|
}
|
|
|
|
void TFT_eSprite::pushColor(uint16_t color, uint32_t len) {
|
|
if (!_created ) return;
|
|
|
|
uint16_t pixelColor;
|
|
pixelColor = (uint16_t) (color >> 8) | (color << 8);
|
|
|
|
while(len--) writeColor(pixelColor);
|
|
}
|
|
|
|
void TFT_eSprite::writeColor(uint16_t color) {
|
|
if (!_created ) return;
|
|
|
|
_img[_xptr + _yptr * _iwidth] = color;
|
|
|
|
_xptr++;
|
|
|
|
if(_xptr > _xe) {
|
|
_xptr = _xs;
|
|
_yptr++;
|
|
if (_yptr > _ye) _yptr = _ys;
|
|
}
|
|
}
|
|
|
|
void TFT_eSprite::fillSprite(uint32_t color) {
|
|
if (!_created || _vpOoB) return;
|
|
|
|
if(_xDatum == 0 && _yDatum == 0 && _xWidth == width()) {
|
|
if ( (uint8_t)color == (uint8_t)(color>>8) ) memset(_img, (uint8_t)color, _iwidth * _yHeight * 2);
|
|
else fillRect(_vpX, _vpY, _xWidth, _yHeight, color);
|
|
}
|
|
else fillRect(_vpX - _xDatum, _vpY - _yDatum, _xWidth, _yHeight, color);
|
|
}
|
|
|
|
int16_t TFT_eSprite::width() {
|
|
if (!_created ) return 0;
|
|
|
|
if (_vpDatum) return _xWidth;
|
|
return _dwidth;
|
|
}
|
|
|
|
int16_t TFT_eSprite::height() {
|
|
if (!_created ) return 0;
|
|
|
|
if (_vpDatum) return _yHeight;
|
|
return _dheight;
|
|
}
|
|
|
|
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;
|
|
|
|
color = (color >> 8) | (color << 8);
|
|
_img[x+y*_iwidth] = (uint16_t)color;
|
|
}
|
|
|
|
void TFT_eSprite::drawLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint32_t color) {
|
|
if (!_created || _vpOoB) return;
|
|
|
|
bool steep = abs(y1 - y0) > abs(x1 - x0);
|
|
if (steep) {
|
|
transpose(x0, y0);
|
|
transpose(x1, y1);
|
|
}
|
|
|
|
if (x0 > x1) {
|
|
transpose(x0, x1);
|
|
transpose(y0, y1);
|
|
}
|
|
|
|
int32_t dx = x1 - x0, dy = abs(y1 - y0);
|
|
int32_t err = dx >> 1, ystep = -1, xs = x0, dlen = 0;
|
|
|
|
if (y0 < y1) ystep = 1;
|
|
|
|
// Split into steep and not steep for FastH/V separation
|
|
if (steep) {
|
|
for(;x0 <= x1; x0++) {
|
|
dlen++;
|
|
err -= dy;
|
|
if (err < 0) {
|
|
err += dx;
|
|
if (dlen == 1) drawPixel(y0, xs, color);
|
|
else drawFastVLine(y0, xs, dlen, color);
|
|
dlen = 0; y0 += ystep; xs = x0 + 1;
|
|
}
|
|
}
|
|
if (dlen) drawFastVLine(y0, xs, dlen, color);
|
|
} else {
|
|
for(;x0 <= x1; x0++) {
|
|
dlen++;
|
|
err -= dy;
|
|
if (err < 0) {
|
|
err += dx;
|
|
if (dlen == 1) drawPixel(xs, y0, color);
|
|
else drawFastHLine(xs, y0, dlen, color);
|
|
dlen = 0; y0 += ystep; xs = x0 + 1;
|
|
}
|
|
}
|
|
if (dlen) drawFastHLine(xs, y0, dlen, color);
|
|
}
|
|
}
|
|
|
|
void TFT_eSprite::drawFastVLine(int32_t x, int32_t y, int32_t h, uint32_t color) {
|
|
if (!_created || _vpOoB) return;
|
|
|
|
x+= _xDatum;
|
|
y+= _yDatum;
|
|
|
|
// Clipping
|
|
if ((x < _vpX) || (x >= _vpW) || (y >= _vpH)) return;
|
|
if (y < _vpY) { h += y - _vpY; y = _vpY; }
|
|
if ((y + h) > _vpH) h = _vpH - y;
|
|
if (h < 1) return;
|
|
|
|
color = (color >> 8) | (color << 8);
|
|
int32_t yp = x + _iwidth * y;
|
|
while (h--) {_img[yp] = (uint16_t) color; yp += _iwidth;}
|
|
}
|
|
|
|
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 (x < _vpX) { w += x - _vpX; x = _vpX; }
|
|
if ((x + w) > _vpW) w = _vpW - x;
|
|
if (w < 1) return;
|
|
|
|
color = (color >> 8) | (color << 8);
|
|
while (w--) _img[_iwidth * y + x++] = (uint16_t) color;
|
|
}
|
|
|
|
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;
|
|
|
|
if (x < _vpX) { w += x - _vpX; x = _vpX; }
|
|
if (y < _vpY) { h += y - _vpY; y = _vpY; }
|
|
|
|
if ((x + w) > _vpW) w = _vpW - x;
|
|
if ((y + h) > _vpH) h = _vpH - y;
|
|
|
|
if ((w < 1) || (h < 1)) return;
|
|
|
|
int32_t yp = _iwidth * y + x;
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
void TFT_eSprite::drawGlyph(uint16_t code, uint16_t font) {
|
|
// Check if cursor has moved
|
|
if (last_cursor_x != cursor_x) {
|
|
bg_cursor_x = cursor_x;
|
|
last_cursor_x = cursor_x;
|
|
}
|
|
|
|
if (code < 0x21) {
|
|
if (code == ' ') {
|
|
if (_fillbg) fillRect(bg_cursor_x, cursor_y, (cursor_x + gFonts[font].spaceWidth) - bg_cursor_x, gFonts[font].yAdvance, textbgcolor);
|
|
cursor_x += gFonts[font].spaceWidth;
|
|
bg_cursor_x = cursor_x;
|
|
last_cursor_x = cursor_x;
|
|
return;
|
|
} else if (code == '\n') {
|
|
cursor_x = 0;
|
|
bg_cursor_x = 0;
|
|
last_cursor_x = 0;
|
|
cursor_y += gFonts[font].yAdvance;
|
|
if (textwrapY && (cursor_y >= height())) cursor_y = 0;
|
|
return;
|
|
}
|
|
}
|
|
|
|
uint16_t gNum = 0;
|
|
bool found = getUnicodeIndex(code, &gNum, font);
|
|
|
|
if (found) {
|
|
bool newSprite = !_created;
|
|
if(newSprite) {
|
|
createSprite(gWidth[font][gNum], gFonts[font].yAdvance);
|
|
if(textcolor != textbgcolor) fillSprite(textbgcolor);
|
|
cursor_x = -gdX[font][gNum];
|
|
bg_cursor_x = cursor_x;
|
|
last_cursor_x = cursor_x;
|
|
cursor_y = 0;
|
|
} else {
|
|
if(textwrapX && ((cursor_x + gWidth[font][gNum] + gdX[font][gNum]) > width())) {
|
|
cursor_y += gFonts[font].yAdvance;
|
|
cursor_x = 0;
|
|
bg_cursor_x = 0;
|
|
last_cursor_x = 0;
|
|
}
|
|
|
|
if(textwrapY && ((cursor_y + gFonts[font].yAdvance) > height())) cursor_y = 0;
|
|
if(cursor_x == 0) cursor_x -= gdX[font][gNum];
|
|
}
|
|
|
|
const uint8_t* gPtr = (const uint8_t*)gFonts[font].gArray;
|
|
|
|
int16_t cy = cursor_y + gFonts[font].maxAscent - gdY[font][gNum];
|
|
int16_t cx = cursor_x + gdX[font][gNum];
|
|
|
|
int16_t fxs = cx;
|
|
uint32_t fl = 0;
|
|
int16_t bxs = cx;
|
|
uint32_t bl = 0;
|
|
int16_t bx = 0;
|
|
uint8_t pixel = 0;
|
|
|
|
int16_t fillwidth = 0;
|
|
int16_t fillheight = 0;
|
|
|
|
// Fill area above glyph
|
|
if (_fillbg) {
|
|
fillwidth = (cursor_x + gxAdvance[font][gNum]) - bg_cursor_x;
|
|
if (fillwidth > 0) {
|
|
fillheight = gFonts[font].maxAscent - gdY[font][gNum];
|
|
if (fillheight > 0) fillRect(bg_cursor_x, cursor_y, fillwidth, fillheight, textbgcolor);
|
|
} else fillwidth = 0;
|
|
|
|
// Fill any area to left of glyph
|
|
if (bg_cursor_x < cx) fillRect(bg_cursor_x, cy, cx - bg_cursor_x, gHeight[font][gNum], textbgcolor);
|
|
// Set x position in glyph area where background starts
|
|
if (bg_cursor_x > cx) bx = bg_cursor_x - cx;
|
|
// Fill any area to right of glyph
|
|
if (cx + gWidth[gNum] < cursor_x + gxAdvance[gNum]) fillRect(cx + gWidth[font][gNum], cy, (cursor_x + gxAdvance[gNum]) - (cx + gWidth[gNum]), gHeight[font][gNum], textbgcolor);
|
|
}
|
|
|
|
for (int32_t y = 0; y < gHeight[font][gNum]; y++) {
|
|
for (int32_t x = 0; x < gWidth[font][gNum]; x++) {
|
|
pixel = pgm_read_byte(gPtr + gBitmap[font][gNum] + x + gWidth[font][gNum] * y);
|
|
|
|
if (pixel) {
|
|
if (bl) { drawFastHLine( bxs, y + cy, bl, textbgcolor); bl = 0; }
|
|
if (pixel != 0xFF) {
|
|
if (fl) {
|
|
if (fl==1) drawPixel(fxs, y + cy, textcolor);
|
|
else drawFastHLine( fxs, y + cy, fl, textcolor);
|
|
fl = 0;
|
|
}
|
|
drawPixel(x + cx, y + cy, alphaBlend(pixel, textcolor, textbgcolor));
|
|
} else {
|
|
if (fl==0) fxs = x + cx;
|
|
fl++;
|
|
}
|
|
} else {
|
|
if (fl) { drawFastHLine( fxs, y + cy, fl, textcolor); fl = 0; }
|
|
if (_fillbg) {
|
|
if (x >= bx) {
|
|
if (bl==0) bxs = x + cx;
|
|
bl++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (fl) { drawFastHLine( fxs, y + cy, fl, textcolor); fl = 0; }
|
|
if (bl) { drawFastHLine( bxs, y + cy, bl, textbgcolor); bl = 0; }
|
|
}
|
|
|
|
// Fill area below glyph
|
|
if (fillwidth > 0) {
|
|
fillheight = (cursor_y + gFonts[font].yAdvance) - (cy + gHeight[font][gNum]);
|
|
if (fillheight > 0) fillRect(bg_cursor_x, cy + gHeight[font][gNum], fillwidth, fillheight, textbgcolor);
|
|
}
|
|
|
|
cursor_x += gxAdvance[font][gNum];
|
|
|
|
if (newSprite) {
|
|
pushSprite(cx, cursor_y);
|
|
deleteSprite();
|
|
}
|
|
} else {
|
|
drawRect(cursor_x, cursor_y + gFonts[font].maxAscent - gFonts[font].ascent, gFonts[font].spaceWidth, gFonts[font].ascent, textcolor);
|
|
cursor_x += gFonts[font].spaceWidth + 1;
|
|
}
|
|
bg_cursor_x = cursor_x;
|
|
last_cursor_x = cursor_x;
|
|
}
|
|
|
|
void TFT_eSprite::copyFontFromTFT(uint8_t source, uint8_t destination) {
|
|
unloadFont(destination); // Make sure there is nothing there
|
|
|
|
gUnicode[destination] = _tft->gUnicode[source];
|
|
gHeight[destination] = _tft->gHeight[source];
|
|
gWidth[destination] = _tft->gWidth[source];
|
|
gxAdvance[destination] = _tft->gxAdvance[source];
|
|
gdY[destination] = _tft->gdY[source];
|
|
gdX[destination] = _tft->gdX[source];
|
|
gBitmap[destination] = _tft->gBitmap[source];
|
|
|
|
memcpy(&gFonts[destination], &_tft->gFonts[source], sizeof(fontMetrics));
|
|
|
|
fontOwned[destination] = false;
|
|
}
|
|
|
|
void TFT_eSprite::copyAllFontsFromTFT() {
|
|
for(uint8_t i = 0; i < FONT_COUNT; i++) copyFontFromTFT(i, i);
|
|
} |