#include "TFT_eSPI.h" #include #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=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); }