From 13d217dc89355d5a1ded0dc77e1c77f3fdc315f7 Mon Sep 17 00:00:00 2001 From: Bodmer Date: Tue, 6 Oct 2020 00:51:41 +0100 Subject: [PATCH] Add viewport feature 2 new example sketches added for viewport demonstration --- Extensions/Sprite.cpp | 6 +- TFT_eSPI.cpp | 292 +++++++++++--- TFT_eSPI.h | 15 +- User_Setups/Setup50_SSD1963_Parallel.h | 5 +- .../320 x 240/TFT_Starfield/TFT_Starfield.ino | 2 +- examples/Viewport_Demo/Viewport_Demo.ino | 142 +++++++ examples/Viewport_Demo/Viewport_commands.ino | 33 ++ .../Viewport_graphicstest.ino | 381 ++++++++++++++++++ keywords.txt | 5 + library.json | 2 +- library.properties | 2 +- 11 files changed, 813 insertions(+), 72 deletions(-) create mode 100644 examples/Viewport_Demo/Viewport_Demo.ino create mode 100644 examples/Viewport_Demo/Viewport_commands.ino create mode 100644 examples/Viewport_graphicstest/Viewport_graphicstest.ino diff --git a/Extensions/Sprite.cpp b/Extensions/Sprite.cpp index 2c8a88b..c89acf5 100644 --- a/Extensions/Sprite.cpp +++ b/Extensions/Sprite.cpp @@ -843,9 +843,9 @@ bool TFT_eSprite::pushSprite(int32_t tx, int32_t ty, int32_t sx, int32_t sy, int _tft->pushImage(tx, ty, sw, sh, _img4 + (_iwidth>>1) * _ys, false, _colorMap ); else // Render line by line { - uint32_t ds = _xs&1; // Odd x start pixel + int32_t ds = _xs&1; // Odd x start pixel - uint32_t de = 0; // Odd x end pixel + int32_t de = 0; // Odd x end pixel if ((sw > ds) && (_xe&1)) de = 1; uint32_t dm = 0; // Midsection pixel count @@ -877,7 +877,7 @@ bool TFT_eSprite::pushSprite(int32_t tx, int32_t ty, int32_t sx, int32_t sy, int _tft->setWindow(tx, ty, tx+sw-1, ty+sh-1); while (sh--) { - for (uint32_t dx = _xs; dx < _xs + sw; dx++) _tft->pushColor(readPixel(dx, _ys)); + for (int32_t dx = _xs; dx < _xs + sw; dx++) _tft->pushColor(readPixel(dx, _ys)); ty++; _ys++; } diff --git a/TFT_eSPI.cpp b/TFT_eSPI.cpp index 59d6276..7521fed 100644 --- a/TFT_eSPI.cpp +++ b/TFT_eSPI.cpp @@ -85,6 +85,132 @@ inline void TFT_eSPI::begin_tft_read(void){ SET_BUS_READ_MODE; } +/*************************************************************************************** +** Function name: setViewport +** Description: Set the clipping region for the TFT screen +***************************************************************************************/ +void TFT_eSPI::setViewport(int32_t x, int32_t y, int32_t w, int32_t h, bool vpDatum) +{ + // Set to whole screen in case of error + _xDatum = 0; + _yDatum = 0; + _vpX = 0; + _vpY = 0; + _vpW = _width; + _vpH = _height; + + _vpDatum = false; + + + // Check if out of bounds + if ((x >= _width) || (y >= _height)) return; + + // Clip + if (x < 0) { x = 0; } + if (y < 0) { y = 0; } + + if ((x + w) > _width ) w = _width - x; + if ((y + h) > _height) h = _height - y; + + // Check if out of bounds + if (w < 1 || h < 1) return; + + if (vpDatum) + { + _xDatum = x; + _yDatum = y; + _vpX = 0; + _vpY = 0; + _vpW = w; + _vpH = h; + } + else + { + _xDatum = 0; + _yDatum = 0; + _vpX = x; + _vpY = y; + _vpW = x + w; + _vpH = y + h; + } + + _vpDatum = vpDatum; +} + +/*************************************************************************************** +** Function name: resetViewport +** Description: Reset viewport to whle TFT screen, datum at 0,0 +***************************************************************************************/ +void TFT_eSPI::resetViewport(void) +{ + // Set to whole screen + _xDatum = 0; + _yDatum = 0; + _vpX = 0; + _vpY = 0; + _vpW = _width; + _vpH = _height; + + _vpDatum = false; +} + +/*************************************************************************************** +** Function name: getViewportWidth +** Description: Get width of the viewport +***************************************************************************************/ +int32_t TFT_eSPI::getViewportWidth(void) +{ + return _vpW - _vpX; +} + +/*************************************************************************************** +** Function name: getViewportHeight +** Description: Get height of the viewport +***************************************************************************************/ +int32_t TFT_eSPI::getViewportHeight(void) +{ + return _vpH - _vpY; +} + +/*************************************************************************************** +** Function name: frameViewport +** Description: Draw a frame inside or outside the viewport of width w +***************************************************************************************/ +void TFT_eSPI::frameViewport(uint16_t color, int32_t w) +{ + // If w is positive the frame is drawn inside the viewport + // a large positive width will clear the screen inside the viewport + if (w>0) + { + fillRect(_vpX, _vpY, _vpW - _vpX, w, color); + fillRect(_vpX, _vpY + w, w, _vpH - _vpY - w - w, color); + fillRect(_vpW - w, _vpY + w, w, _vpH - _vpY - w - w, color); + fillRect(_vpX, _vpH - w, _vpW - _vpX, w, color); + } + else + // If w is negative the frame is drawn outside the viewport + // a large negative width will clear the screen outside the viewport + { + w = -w; + int32_t _xTemp = _vpX; _vpX = 0; + int32_t _yTemp = _vpY; _vpY = 0; + int32_t _wTemp = _vpW; _vpW = _width; + int32_t _hTemp = _vpH; _vpH = _height; + bool _dTemp = _vpDatum; _vpDatum = false; + + fillRect(_xDatum + _xTemp - w, _yDatum + _yTemp - w, _wTemp - _xTemp + w + w, w, color); + fillRect(_xDatum + _xTemp - w, _yDatum + _yTemp, w, _hTemp -_yTemp, color); + fillRect(_xDatum + _wTemp, _yDatum + _yTemp, w, _hTemp - _yTemp, color); + fillRect(_xDatum + _xTemp - w, _yDatum + _hTemp, _wTemp - _xTemp + w + w, w, color); + + _vpX = _xTemp; + _vpY = _yTemp; + _vpW = _wTemp; + _vpH = _hTemp; + _vpDatum = _dTemp; + } +} + /*************************************************************************************** ** Function name: end_tft_read (was called spi_end_read) ** Description: End transaction for reads and deselect TFT @@ -181,6 +307,15 @@ TFT_eSPI::TFT_eSPI(int16_t w, int16_t h) _init_width = _width = w; // Set by specific xxxxx_Defines.h file or by users sketch _init_height = _height = h; // Set by specific xxxxx_Defines.h file or by users sketch + + // Set display clip window to whole screen + _xDatum = 0; + _yDatum = 0; + _vpX = 0; + _vpY = 0; + _vpW = _width; + _vpH = _height; + rotation = 0; cursor_y = cursor_x = 0; textfont = 1; @@ -490,6 +625,18 @@ void TFT_eSPI::setRotation(uint8_t m) addr_row = 0xFFFF; addr_col = 0xFFFF; + + if (!_vpDatum) + { + // Reset viewport to whole screen + _xDatum = 0; + _yDatum = 0; + _vpX = 0; + _vpY = 0; + _vpW = _width; + _vpH = _height; + } + } @@ -903,18 +1050,18 @@ void TFT_eSPI::pushRect(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t *da void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t *data) { - if ((x >= _width) || (y >= _height)) return; + if ((x >= _vpW) || (y >= _vpH)) return; int32_t dx = 0; int32_t dy = 0; int32_t dw = w; int32_t dh = h; - if (x < 0) { dw += x; dx = -x; x = 0; } - if (y < 0) { dh += y; dy = -y; y = 0; } + if (x < _vpX) { dx = _vpX - x; dw -= dx; x = _vpX; } + if (y < _vpY) { dy = _vpY - y; dh -= dy; y = _vpY; } - if ((x + dw) > _width ) dw = _width - x; - if ((y + dh) > _height) dh = _height - y; + if ((x + dw) > _vpW ) dw = _vpW - x; + if ((y + dh) > _vpH ) dh = _vpH - y; if (dw < 1 || dh < 1) return; @@ -947,18 +1094,18 @@ void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t *d void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t *data, uint16_t transp) { - if ((x >= _width) || (y >= _height)) return; + if ((x >= _vpW) || (y >= _vpH)) return; int32_t dx = 0; int32_t dy = 0; int32_t dw = w; int32_t dh = h; - if (x < 0) { dw += x; dx = -x; x = 0; } - if (y < 0) { dh += y; dy = -y; y = 0; } + if (x < _vpX) { dx = _vpX - x; dw -= dx; x = _vpX; } + if (y < _vpY) { dy = _vpY - y; dh -= dy; y = _vpY; } - if ((x + dw) > _width ) dw = _width - x; - if ((y + dh) > _height) dh = _height - y; + if ((x + dw) > _vpW ) dw = _vpW - x; + if ((y + dh) > _vpH ) dh = _vpH - y; if (dw < 1 || dh < 1) return; @@ -1020,18 +1167,18 @@ void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t *d void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, const uint16_t *data) { // Requires 32 bit aligned access, so use PROGMEM 16 bit word functions - if ((x >= _width) || (y >= _height)) return; + if ((x >= _vpW) || (y >= _vpH)) return; int32_t dx = 0; int32_t dy = 0; int32_t dw = w; int32_t dh = h; - if (x < 0) { dw += x; dx = -x; x = 0; } - if (y < 0) { dh += y; dy = -y; y = 0; } + if (x < _vpX) { dx = _vpX - x; dw -= dx; x = _vpX; } + if (y < _vpY) { dy = _vpY - y; dh -= dy; y = _vpY; } - if ((x + dw) > _width ) dw = _width - x; - if ((y + dh) > _height) dh = _height - y; + if ((x + dw) > _vpW ) dw = _vpW - x; + if ((y + dh) > _vpH ) dh = _vpH - y; if (dw < 1 || dh < 1) return; @@ -1064,18 +1211,18 @@ void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, const uint1 void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, const uint16_t *data, uint16_t transp) { // Requires 32 bit aligned access, so use PROGMEM 16 bit word functions - if ((x >= _width) || (y >= (int32_t)_height)) return; + if ((x >= _vpW) || (y >= _vpH)) return; int32_t dx = 0; int32_t dy = 0; int32_t dw = w; int32_t dh = h; - if (x < 0) { dw += x; dx = -x; x = 0; } - if (y < 0) { dh += y; dy = -y; y = 0; } + if (x < _vpX) { dx = _vpX - x; dw -= dx; x = _vpX; } + if (y < _vpY) { dy = _vpY - y; dh -= dy; y = _vpY; } - if ((x + dw) > _width ) dw = _width - x; - if ((y + dh) > _height) dh = _height - y; + if ((x + dw) > _vpW ) dw = _vpW - x; + if ((y + dh) > _vpH ) dh = _vpH - y; if (dw < 1 || dh < 1) return; @@ -1134,18 +1281,18 @@ void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, const uint1 void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t *data, bool bpp8, uint16_t *cmap) { - if ((x >= _width) || (y >= (int32_t)_height)) return; + if ((x >= _vpW) || (y >= _vpH)) return; int32_t dx = 0; int32_t dy = 0; int32_t dw = w; int32_t dh = h; - if (x < 0) { dw += x; dx = -x; x = 0; } - if (y < 0) { dh += y; dy = -y; y = 0; } + if (x < _vpX) { dx = _vpX - x; dw -= dx; x = _vpX; } + if (y < _vpY) { dy = _vpY - y; dh -= dy; y = _vpY; } - if ((x + dw) > _width ) dw = _width - x; - if ((y + dh) > _height) dh = _height - y; + if ((x + dw) > _vpW ) dw = _vpW - x; + if ((y + dh) > _vpH ) dh = _vpH - y; if (dw < 1 || dh < 1) return; @@ -1290,18 +1437,18 @@ void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t *da ***************************************************************************************/ void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t *data, uint8_t transp, bool bpp8, uint16_t *cmap) { - if ((x >= _width) || (y >= _height)) return; + if ((x >= _vpW) || (y >= _vpH)) return; int32_t dx = 0; int32_t dy = 0; int32_t dw = w; int32_t dh = h; - if (x < 0) { dw += x; dx = -x; x = 0; } - if (y < 0) { dh += y; dy = -y; y = 0; } + if (x < _vpX) { dx = _vpX - x; dw -= dx; x = _vpX; } + if (y < _vpY) { dy = _vpY - y; dh -= dy; y = _vpY; } - if ((x + dw) > _width ) dw = _width - x; - if ((y + dh) > _height) dh = _height - y; + if ((x + dw) > _vpW ) dw = _vpW - x; + if ((y + dh) > _vpH ) dh = _vpH - y; if (dw < 1 || dh < 1) return; @@ -2329,7 +2476,8 @@ uint8_t TFT_eSPI::getTextDatum(void) // Return the size of the display (per current rotation) int16_t TFT_eSPI::width(void) { - return _width; + if (_vpDatum) return _vpW - _vpX; + else return _width; } @@ -2339,7 +2487,8 @@ int16_t TFT_eSPI::width(void) ***************************************************************************************/ int16_t TFT_eSPI::height(void) { - return _height; + if (_vpDatum) return _vpH - _vpY; + else return _height; } @@ -2479,10 +2628,10 @@ int16_t TFT_eSPI::fontHeight(void) ***************************************************************************************/ void TFT_eSPI::drawChar(int32_t x, int32_t y, uint16_t c, uint32_t color, uint32_t bg, uint8_t size) { - if ((x >= _width) || // Clip right - (y >= _height) || // Clip bottom - ((x + 6 * size - 1) < 0) || // Clip left - ((y + 8 * size - 1) < 0)) // Clip top + if ((x >= _vpW) || // Clip right + (y >= _vpH) || // Clip bottom + ((x + 6 * size - 1) < _vpX) || // Clip left + ((y + 8 * size - 1) < _vpY)) // Clip top return; if (c < 32) return; @@ -2495,7 +2644,7 @@ void TFT_eSPI::drawChar(int32_t x, int32_t y, uint16_t c, uint32_t color, uint32 bool fillbg = (bg != color); - if ((size==1) && fillbg) { + if ((size==1) && fillbg && x >= _vpX && (x + 6 * size - 1) < _vpW) { uint8_t column[6]; uint8_t mask = 0x1; begin_tft_write(); @@ -2526,13 +2675,13 @@ void TFT_eSPI::drawChar(int32_t x, int32_t y, uint16_t c, uint32_t color, uint32 else line = pgm_read_byte(font + (c * 5) + i); - if (size == 1) { // default size + if (size == 1 && !fillbg) { // default size for (int8_t j = 0; j < 8; j++) { if (line & 0x1) drawPixel(x + i, y + j, color); line >>= 1; } } - else { // big size + else { // big size or clipped for (int8_t j = 0; j < 8; j++) { if (line & 0x1) fillRect(x + (i * size), y + (j * size), size, size, color); else if (fillbg) fillRect(x + i * size, y + j * size, size, size, bg); @@ -2640,6 +2789,14 @@ void TFT_eSPI::setWindow(int32_t x0, int32_t y0, int32_t x1, int32_t y1) { //begin_tft_write(); // Must be called before setWindow + if( _vpDatum) + { + x0+= _xDatum; + x1+= _xDatum; + y0+= _yDatum; + y1+= _yDatum; + } + #if defined (SSD1963_DRIVER) if ((rotation & 0x1) == 0) { swap_coord(x0, y0); swap_coord(x1, y1); } #endif @@ -2714,7 +2871,13 @@ void TFT_eSPI::readAddrWindow(int32_t xs, int32_t ys, int32_t w, int32_t h) void TFT_eSPI::drawPixel(int32_t x, int32_t y, uint32_t color) { // Range checking - if ((x < 0) || (y < 0) ||(x >= _width) || (y >= _height)) return; + if ((x < _vpX) || (y < _vpY) ||(x >= _vpW) || (y >= _vpH)) return; + + if( _vpDatum) + { + x+= _xDatum; + y+= _yDatum; + } #ifdef CGRAM_OFFSET x+=colstart; @@ -2915,11 +3078,11 @@ void TFT_eSPI::drawLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint32_t void TFT_eSPI::drawFastVLine(int32_t x, int32_t y, int32_t h, uint32_t color) { // Clipping - if ((x < 0) || (x >= _width) || (y >= _height)) return; + if ((x < _vpX) || (x >= _vpW) || (y >= _vpH)) return; - if (y < 0) { h += y; y = 0; } + if (y < _vpY) { h += y - _vpY; y = _vpY; } - if ((y + h) > _height) h = _height - y; + if ((y + h) > _vpH) h = _vpH - y; if (h < 1) return; @@ -2940,11 +3103,11 @@ void TFT_eSPI::drawFastVLine(int32_t x, int32_t y, int32_t h, uint32_t color) void TFT_eSPI::drawFastHLine(int32_t x, int32_t y, int32_t w, uint32_t color) { // Clipping - if ((y < 0) || (x >= _width) || (y >= _height)) return; + if ((y < _vpY) || (x >= _vpW) || (y >= _vpH)) return; - if (x < 0) { w += x; x = 0; } + if (x < _vpX) { w += x - _vpX; x = _vpX; } - if ((x + w) > _width) w = _width - x; + if ((x + w) > _vpW) w = _vpW - x; if (w < 1) return; @@ -2965,13 +3128,13 @@ void TFT_eSPI::drawFastHLine(int32_t x, int32_t y, int32_t w, uint32_t color) void TFT_eSPI::fillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color) { // Clipping - if ((x >= _width) || (y >= _height)) return; + if ((x >= _vpW) || (y >= _vpH)) return; - if (x < 0) { w += x; x = 0; } - if (y < 0) { h += y; y = 0; } + if (x < _vpX) { w += x - _vpX; x = _vpX; } + if (y < _vpY) { h += y - _vpY; y = _vpY; } - if ((x + w) > _width) w = _width - x; - if ((y + h) > _height) h = _height - y; + if ((x + w) > _vpW) w = _vpW - x; + if ((y + h) > _vpH) h = _vpH - y; if ((w < 1) || (h < 1)) return; @@ -3359,11 +3522,11 @@ size_t TFT_eSPI::write(uint8_t utf8) cursor_x = 0; } else { - if (textwrapX && (cursor_x + width * textsize > _width)) { + if (textwrapX && (cursor_x + width * textsize > this->width())) { cursor_y += height; cursor_x = 0; } - if (textwrapY && (cursor_y >= (int32_t)_height)) cursor_y = 0; + if (textwrapY && (cursor_y >= (int32_t)this->height())) cursor_y = 0; cursor_x += drawChar(uniCode, cursor_x, cursor_y, textfont); } @@ -3386,13 +3549,13 @@ size_t TFT_eSPI::write(uint8_t utf8) h = pgm_read_byte(&glyph->height); if((w > 0) && (h > 0)) { // Is there an associated bitmap? int16_t xo = (int8_t)pgm_read_byte(&glyph->xOffset); - if(textwrapX && ((cursor_x + textsize * (xo + w)) > _width)) { + if(textwrapX && ((cursor_x + textsize * (xo + w)) > this->width())) { // Drawing character would go off right edge; wrap to new line cursor_x = 0; cursor_y += (int16_t)textsize * (uint8_t)pgm_read_byte(&gfxFont->yAdvance); } - if (textwrapY && (cursor_y >= (int32_t)_height)) cursor_y = 0; + if (textwrapY && (cursor_y >= (int32_t)this->height())) cursor_y = 0; drawChar(cursor_x, cursor_y, uniCode, textcolor, textbgcolor, textsize); } cursor_x += pgm_read_byte(&glyph->xAdvance) * (int16_t)textsize; @@ -3482,6 +3645,8 @@ int16_t TFT_eSPI::drawChar(uint16_t uniCode, int32_t x, int32_t y, uint8_t font) } #endif + if (x + width * textsize < (int16_t)_vpX || x >= _vpW) return width * textsize ; + int32_t w = width; int32_t pX = 0; int32_t pY = y; @@ -3491,9 +3656,8 @@ int16_t TFT_eSPI::drawChar(uint16_t uniCode, int32_t x, int32_t y, uint8_t font) if (font == 2) { w = w + 6; // Should be + 7 but we need to compensate for width increment w = w / 8; - if (x + width * textsize >= (int16_t)_width) return width * textsize ; - if (textcolor == textbgcolor || textsize != 1) { + if (textcolor == textbgcolor || textsize != 1 || x < _vpX || x + width * textsize >= _vpW) { //begin_tft_write(); // Sprite class can use this function, avoiding begin_tft_write() inTransaction = true; @@ -3570,7 +3734,7 @@ int16_t TFT_eSPI::drawChar(uint16_t uniCode, int32_t x, int32_t y, uint8_t font) inTransaction = true; w *= height; // Now w is total number of pixels in the character - if (textcolor == textbgcolor) { + if (textcolor == textbgcolor && x >= _vpX && x + width * textsize <= _vpW) { int32_t px = 0, py = pY; // To hold character block start and end column and row values int32_t pc = 0; // Pixel count uint8_t np = textsize * textsize; // Number of pixels in a drawn pixel @@ -3619,7 +3783,7 @@ int16_t TFT_eSPI::drawChar(uint16_t uniCode, int32_t x, int32_t y, uint8_t font) else { // Text colour != background and textsize = 1 and character is within screen area // so use faster drawing of characters and background using block write - if ((textsize == 1) && (x >= 0) && (x + width <= _width) && (y >= 0) && (y + height <= _height)) + if (textsize == 1 && x >= _vpX && x + width <= _vpW) { setWindow(x, y, x + width - 1, y + height - 1); @@ -3644,12 +3808,12 @@ int16_t TFT_eSPI::drawChar(uint16_t uniCode, int32_t x, int32_t y, uint8_t font) int32_t pc = 0; // Pixel count int32_t pl = 0; // Pixel line length uint16_t pcol = 0; // Pixel color - + bool pf = true; // Flag for plotting while (pc < w) { line = pgm_read_byte((uint8_t *)flash_address); flash_address++; - if (line & 0x80) { pcol = textcolor; line &= 0x7F; } - else pcol = textbgcolor; + if (line & 0x80) { pcol = textcolor; line &= 0x7F; pf = true;} + else { pcol = textbgcolor; if (textcolor == textbgcolor) pf = false;} line++; px = pc % width; tx = x + textsize * px; @@ -3661,7 +3825,7 @@ int16_t TFT_eSPI::drawChar(uint16_t uniCode, int32_t x, int32_t y, uint8_t font) while (line--) { pl++; if ((px+pl) >= width) { - fillRect(tx, ty, pl * textsize, textsize, pcol); + if (pf) fillRect(tx, ty, pl * textsize, textsize, pcol); pl = 0; px = 0; tx = x; @@ -3669,7 +3833,7 @@ int16_t TFT_eSPI::drawChar(uint16_t uniCode, int32_t x, int32_t y, uint8_t font) ty += textsize; } } - if (pl) fillRect(tx, ty, pl * textsize, textsize, pcol); + if (pl && pf) fillRect(tx, ty, pl * textsize, textsize, pcol); } } } diff --git a/TFT_eSPI.h b/TFT_eSPI.h index 07ceabe..358fc89 100644 --- a/TFT_eSPI.h +++ b/TFT_eSPI.h @@ -16,7 +16,7 @@ #ifndef _TFT_eSPIH_ #define _TFT_eSPIH_ -#define TFT_ESPI_VERSION "2.2.23" +#define TFT_ESPI_VERSION "2.3.0" /*************************************************************************************** ** Section 1: Load required header files @@ -390,6 +390,13 @@ class TFT_eSPI : public Print { void setAddrWindow(int32_t xs, int32_t ys, int32_t w, int32_t h), // Note: start coordinates + width and height setWindow(int32_t xs, int32_t ys, int32_t xe, int32_t ye); // Note: start + end coordinates + // Viewport commands, see "Viewport_Demo" sketch + void setViewport(int32_t x, int32_t y, int32_t w, int32_t h, bool vpDatum = true); + int32_t getViewportWidth(void); + int32_t getViewportHeight(void); + void frameViewport(uint16_t color, int32_t w); + void resetViewport(void); + // Push (aka write pixel) colours to the TFT (use setAddrWindow() first) void pushColor(uint16_t color), pushColor(uint16_t color, uint32_t len), // Deprecated, use pushBlock() @@ -728,6 +735,12 @@ class TFT_eSPI : public Print { int32_t _width, _height; // Display w/h as modified by current rotation int32_t addr_row, addr_col; // Window position - used to minimise window commands + // Viewport variables + int32_t _vpX, _vpY, _vpW, _vpH; + int32_t _xDatum; + int32_t _yDatum; + bool _vpDatum; + uint32_t fontsloaded; // Bit field of fonts loaded uint8_t glyph_ab, // Smooth font glyph delta Y (height) above baseline diff --git a/User_Setups/Setup50_SSD1963_Parallel.h b/User_Setups/Setup50_SSD1963_Parallel.h index 59be2d0..558356b 100644 --- a/User_Setups/Setup50_SSD1963_Parallel.h +++ b/User_Setups/Setup50_SSD1963_Parallel.h @@ -20,7 +20,10 @@ //#define SSD1963_480_DRIVER // 272 x 480 display //#define SSD1963_800_DRIVER // 480 x 800 display //#define SSD1963_800ALT_DRIVER // Alternative 480 x 800 display -#define SSD1963_800BD_DRIVER // 480 x 800 displau sourced from https://www.buydisplay.com/7-tft-screen-touch-lcd-display-module-w-ssd1963-controller-board-mcu +#define SSD1963_800BD_DRIVER // 480 x 800 display sourced from https://www.buydisplay.com/7-tft-screen-touch-lcd-display-module-w-ssd1963-controller-board-mcu + +//#define TFT_RGB_ORDER TFT_RGB // Colour order Red-Green-Blue +#define TFT_RGB_ORDER TFT_BGR // Colour order Blue-Green-Red // ################################################################################## // diff --git a/examples/320 x 240/TFT_Starfield/TFT_Starfield.ino b/examples/320 x 240/TFT_Starfield/TFT_Starfield.ino index ccc4b44..1e72197 100644 --- a/examples/320 x 240/TFT_Starfield/TFT_Starfield.ino +++ b/examples/320 x 240/TFT_Starfield/TFT_Starfield.ino @@ -20,7 +20,7 @@ uint8_t __attribute__((always_inline)) rng() zx++; za = (za^zc^zx); zb = (zb+za); - zc = (zc+(zb>>1)^za); + zc = ((zc+(zb>>1))^za); return zc; } diff --git a/examples/Viewport_Demo/Viewport_Demo.ino b/examples/Viewport_Demo/Viewport_Demo.ino new file mode 100644 index 0000000..9a25db3 --- /dev/null +++ b/examples/Viewport_Demo/Viewport_Demo.ino @@ -0,0 +1,142 @@ +// Viewport Demo + +// See viewport_commands tab + +// This example uses the viewport commands to create a "virtual TFT" within the +// normal TFT display area. This allows a sketch written for a smaller screen to +// be run in a viewport window. By default, the graphics 0,0 datum is set to the +// top left corner of the viewport, but optionally the datum can be kept at the +// corner of the TFT. + +// Viewports have a number of potential uses: +// - create a "virtual" TFT screen smaller than the actual TFT screen +// - render GUI items (menus etc) in a viewport, erase GUI item by redrawing whole screen, +// this will be fast because only the viewport will be refreshed (e.g. clearing menu) +// - limit screen refresh to a particular area, e.g. changing numbers, icons or graph plotting +// - showing a small portion of a larger image or sprite, this allows panning and scrolling + +// A viewport can have the coordinate datum (position 0,0) either at the top left corner of +// the viewport or at the normal top left corner of the TFT. +// Putting the coordinate datum at the viewport corner means that functions that draw graphics +// in a fixed position can be relocated anywhere on the screen. (see plotShapes() below). This +// makes it easier to reposition groupd of graphical ojects (for example GUI buttons) that have +// fixed relative positions. + +// The viewport position x, and y coordinates and viewport bounds must be constrained by the +// user sketch to be within the screen boundaries. + +#include +#include + +TFT_eSPI tft = TFT_eSPI(); + +void setup() { + Serial.begin(115200); + + tft.init(); + tft.setRotation(1); + tft.fillScreen(TFT_BLACK); + + +/* + tft.setViewport(VP_X, VP_Y, VP_W, VP_H); // By default the 0,0 coordinate datum is + // moved to top left corner of viewport + // Note: tft.width() and tft.height() now return viewport size + // Other command options: + //tft.setViewport(VP_X, VP_Y, VP_W, VP_H, true); // Explicitly set datum to viewport corner + + //tft.setViewport(VP_X, VP_Y, VP_W, VP_H, false); // Create viewport but datum stays at TFT corner + // Note: tft.width() and tft.height() now return TFT size + + w = tft.getViewportWidth(); // Always returns width of viewport + h = tft.getViewportHeight(); // Always returns height of viewport + + tft.frameViewport(TFT_GREEN, -2); // Draw a rectangle of width 2 outside (negative width) viewport + tft.frameViewport(TFT_RED, 10); // Draw a rectangle of width 10 inside (positive width) viewport + + // tft.resetViewport(); // Command to reset viewport to "normal" full TFT screen + // Graphics will not be drawn to the TFT outside a viewport until + // this command is used! + + // tft.setRotation(2); // Using setRotation rotates the whole TFT screen it does not just + // rotate the viewport (this is a possible future enhancement). + // Redraw all graphics after a rotation since some TFT's do not + // re-map the TFT graphics RAM to the screen pixels as expected. + delay(1000); + */ +} + +void loop() +{ + // Normal Screen + drawX(); + + delay(2000); + + // Viewport screen + tft.setViewport(10, 10, 140, 100); + tft.frameViewport(TFT_NAVY, -2); + tft.fillScreen(TFT_BLACK); + drawX(); + tft.resetViewport(); + + delay(2000); + + //Normal screen + tft.fillScreen(TFT_BLACK); + drawX(); + + delay(2000); + + tft.fillScreen(TFT_BLACK); + + // Viewport as a clipping window (false parameter means coordinate datum stays at TFT top left) + tft.setViewport(10, 10, tft.width()/2 - 10, tft.height() - 20, false); + //tft.frameViewport(TFT_NAVY, 2); // Add 2 pixel border inside viewport + //tft.frameViewport(TFT_NAVY, -2); // Add 2 pixel border outside viewport + drawX(); + + delay(2000); + + while(1) + { + tft.resetViewport(); // Reset viewport so width() and height() return TFT size + + uint16_t w = 40; + uint16_t h = 40; + uint16_t x = random(tft.width() - w); + uint16_t y = random(tft.height() - h); + + tft.setViewport(x, y, w, h); + + plotBox(); + } +} + +void drawX(void) +{ + tft.fillScreen(tft.color565(25,25,25)); // Grey + + // Draw circle + tft.drawCircle(tft.width()/2, tft.height()/2, tft.width()/4, TFT_RED); + + // Draw diagonal lines + tft.drawLine(0 , 0, tft.width()-1, tft.height()-1, TFT_GREEN); + tft.drawLine(0 , tft.height()-1, tft.width()-1, 0, TFT_BLUE); + + tft.setTextDatum(MC_DATUM); + tft.setTextColor(TFT_WHITE, tft.color565(25,25,25)); + tft.drawString("Hello World!", tft.width()/2, tft.height()/2, 4); // Font 4 +} + +void plotBox(void) +{ + // These are always plotted at a fixed position but they can + // be plotted into a viewport anywhere on the screen because + // a viewport can move the screen datum + tft.fillScreen(TFT_BLACK); // When a viewport is set, this just fills the viewport + tft.drawRect(0,0, 40,40, TFT_BLUE); + tft.setTextDatum(MC_DATUM); + tft.setTextColor(TFT_WHITE); + tft.drawNumber( random(100), 20, 23, 4); +} diff --git a/examples/Viewport_Demo/Viewport_commands.ino b/examples/Viewport_Demo/Viewport_commands.ino new file mode 100644 index 0000000..a70cbd8 --- /dev/null +++ b/examples/Viewport_Demo/Viewport_commands.ino @@ -0,0 +1,33 @@ +/* + + // Create a viewport at TFT screen coordinated X,y of width W and height H + tft.setViewport(X, Y, W, H); // By default the 0,0 coordinate datum is moved to top left + // corner of viewport + // Note: tft.width() and tft.height() now return viewport size! + // The above command is identical to: + tft.setViewport(VP_X, VP_Y, VP_W, VP_H, true); // true parameter is optional + + // To create a viewport that keeps the coordinate datum at top left of TFT, use false parameter + tft.setViewport(VP_X, VP_Y, VP_W, VP_H, false); // Note: tft.width() and tft.height() return TFT size! + + // To get width of viewport + uint16_t w = tft.getViewportWidth(); // Always returns width of viewport + + // To get height of viewport + uint16_t h = tft.getViewportHeight(); // Always returns height of viewport + + // To draw a rectangular frame outside viewport of width W (when W is negative) + tft.frameViewport(TFT_RED, -W); // Note setting the width to a large negative value will clear the screen + // outside the viewport + + // To draw a rectangular frame inside viewport of width W (when W is positive) + tft.frameViewport(TFT_RED, W); // Note setting the width to a large positive value will clear the screen + // inside the viewport + + // To reset the viewport to the normal TFT full screen + tft.resetViewport(); // Note: Graphics will NOT be drawn to the TFT outside a viewport until + // this command is used! ( The exception is using the frameViewport command + // detailed above with a negative width.) + + +*/ diff --git a/examples/Viewport_graphicstest/Viewport_graphicstest.ino b/examples/Viewport_graphicstest/Viewport_graphicstest.ino new file mode 100644 index 0000000..3816397 --- /dev/null +++ b/examples/Viewport_graphicstest/Viewport_graphicstest.ino @@ -0,0 +1,381 @@ +/* + This sketch demonstrates the Adafruit graphicstest sketch running in a + viewport (aka window) within the TFT screen area. To do this line 37 has + been added. Line 38 outlines the viewport. + + This sketch uses the GLCD font (font 1) only. + + Make sure all the display driver and pin comnenctions are correct by + editting the User_Setup.h file in the TFT_eSPI library folder. + + ######################################################################### + ###### DON'T FORGET TO UPDATE THE User_Setup.h FILE IN THE LIBRARY ###### + ######################################################################### +*/ + + +#include "SPI.h" +#include "TFT_eSPI.h" + +TFT_eSPI tft = TFT_eSPI(); + +unsigned long total = 0; +unsigned long tn = 0; +void setup() { + Serial.begin(9600); + while (!Serial); + + Serial.println(""); Serial.println(""); + Serial.println("TFT_eSPI library test!"); + + tft.init(); + + tn = micros(); + tft.fillScreen(TFT_BLACK); + + // Create a viewport 220 x 300 pixels + tft.setViewport(10,10,220,300); + tft.frameViewport(TFT_RED, -1); // 1 pixel wide frame around viewport + + yield(); Serial.println(F("Benchmark Time (microseconds)")); + + yield(); Serial.print(F("Screen fill ")); + yield(); Serial.println(testFillScreen()); + //total+=testFillScreen(); + //delay(500); + + yield(); Serial.print(F("Text ")); + yield(); Serial.println(testText()); + //total+=testText(); + //delay(3000); + + yield(); Serial.print(F("Lines ")); + yield(); Serial.println(testLines(TFT_CYAN)); + //total+=testLines(TFT_CYAN); + //delay(500); + + yield(); Serial.print(F("Horiz/Vert Lines ")); + yield(); Serial.println(testFastLines(TFT_RED, TFT_BLUE)); + //total+=testFastLines(TFT_RED, TFT_BLUE); + //delay(500); + + yield(); Serial.print(F("Rectangles (outline) ")); + yield(); Serial.println(testRects(TFT_GREEN)); + //total+=testRects(TFT_GREEN); + //delay(500); + + yield(); Serial.print(F("Rectangles (filled) ")); + yield(); Serial.println(testFilledRects(TFT_YELLOW, TFT_MAGENTA)); + //total+=testFilledRects(TFT_YELLOW, TFT_MAGENTA); + //delay(500); + + yield(); Serial.print(F("Circles (filled) ")); + yield(); Serial.println(testFilledCircles(10, TFT_MAGENTA)); + //total+= testFilledCircles(10, TFT_MAGENTA); + + yield(); Serial.print(F("Circles (outline) ")); + yield(); Serial.println(testCircles(10, TFT_WHITE)); + //total+=testCircles(10, TFT_WHITE); + //delay(500); + + yield(); Serial.print(F("Triangles (outline) ")); + yield(); Serial.println(testTriangles()); + //total+=testTriangles(); + //delay(500); + + yield(); Serial.print(F("Triangles (filled) ")); + yield(); Serial.println(testFilledTriangles()); + //total += testFilledTriangles(); + //delay(500); + + yield(); Serial.print(F("Rounded rects (outline) ")); + yield(); Serial.println(testRoundRects()); + //total+=testRoundRects(); + //delay(500); + + yield(); Serial.print(F("Rounded rects (filled) ")); + yield(); Serial.println(testFilledRoundRects()); + //total+=testFilledRoundRects(); + //delay(500); + + yield(); Serial.println(F("Done!")); yield(); + //Serial.print(F("Total = ")); Serial.println(total); + + //yield();Serial.println(millis()-tn); +} + +void loop(void) { + for (uint8_t rotation = 0; rotation < 4; rotation++) { + tft.setRotation(rotation); + tft.resetViewport(); // reset viewport to whole screen + tft.fillScreen(TFT_BLACK); // so it can be cleared + + // Create a viewport 220 x 300 pixels + tft.setViewport(10,10,220,300); + tft.frameViewport(TFT_RED, -1); // 1 pixel wide frame around viewport + + testText(); + delay(2000); + } +} + + +unsigned long testFillScreen() { + unsigned long start = micros(); + tft.fillScreen(TFT_BLACK); + tft.fillScreen(TFT_RED); + tft.fillScreen(TFT_GREEN); + tft.fillScreen(TFT_BLUE); + tft.fillScreen(TFT_BLACK); + return micros() - start; +} + +unsigned long testText() { + tft.fillScreen(TFT_BLACK); + unsigned long start = micros(); + tft.setCursor(0, 0); + tft.setTextColor(TFT_WHITE); tft.setTextSize(1); + tft.println("Hello World!"); + tft.setTextColor(TFT_YELLOW); tft.setTextSize(2); + tft.println(1234.56); + tft.setTextColor(TFT_RED); tft.setTextSize(3); + tft.println(0xDEADBEEF, HEX); + tft.println(); + tft.setTextColor(TFT_GREEN); + tft.setTextSize(5); + tft.println("Groop"); + tft.setTextSize(2); + tft.println("I implore thee,"); + //tft.setTextColor(TFT_GREEN,TFT_BLACK); + tft.setTextSize(1); + tft.println("my foonting turlingdromes."); + tft.println("And hooptiously drangle me"); + tft.println("with crinkly bindlewurdles,"); + tft.println("Or I will rend thee"); + tft.println("in the gobberwarts"); + tft.println("with my blurglecruncheon,"); + tft.println("see if I don't!"); + return micros() - start; +} + +unsigned long testLines(uint16_t color) { + unsigned long start, t; + int x1, y1, x2, y2, + w = tft.width(), + h = tft.height(); + + tft.fillScreen(TFT_BLACK); + + x1 = y1 = 0; + y2 = h - 1; + start = micros(); + for (x2 = 0; x2 < w; x2 += 6) tft.drawLine(x1, y1, x2, y2, color); + x2 = w - 1; + for (y2 = 0; y2 < h; y2 += 6) tft.drawLine(x1, y1, x2, y2, color); + t = micros() - start; // fillScreen doesn't count against timing + + tft.fillScreen(TFT_BLACK); + + x1 = w - 1; + y1 = 0; + y2 = h - 1; + start = micros(); + for (x2 = 0; x2 < w; x2 += 6) tft.drawLine(x1, y1, x2, y2, color); + x2 = 0; + for (y2 = 0; y2 < h; y2 += 6) tft.drawLine(x1, y1, x2, y2, color); + t += micros() - start; + + tft.fillScreen(TFT_BLACK); + + x1 = 0; + y1 = h - 1; + y2 = 0; + start = micros(); + for (x2 = 0; x2 < w; x2 += 6) tft.drawLine(x1, y1, x2, y2, color); + x2 = w - 1; + for (y2 = 0; y2 < h; y2 += 6) tft.drawLine(x1, y1, x2, y2, color); + t += micros() - start; + + tft.fillScreen(TFT_BLACK); + + x1 = w - 1; + y1 = h - 1; + y2 = 0; + start = micros(); + for (x2 = 0; x2 < w; x2 += 6) tft.drawLine(x1, y1, x2, y2, color); + x2 = 0; + for (y2 = 0; y2 < h; y2 += 6) tft.drawLine(x1, y1, x2, y2, color); + + return micros() - start; +} + +unsigned long testFastLines(uint16_t color1, uint16_t color2) { + unsigned long start; + int x, y, w = tft.width(), h = tft.height(); + + tft.fillScreen(TFT_BLACK); + start = micros(); + for (y = 0; y < h; y += 5) tft.drawFastHLine(0, y, w, color1); + for (x = 0; x < w; x += 5) tft.drawFastVLine(x, 0, h, color2); + + return micros() - start; +} + +unsigned long testRects(uint16_t color) { + unsigned long start; + int n, i, i2, + cx = tft.width() / 2, + cy = tft.height() / 2; + + tft.fillScreen(TFT_BLACK); + n = min(tft.width(), tft.height()); + start = micros(); + for (i = 2; i < n; i += 6) { + i2 = i / 2; + tft.drawRect(cx - i2, cy - i2, i, i, color); + } + + return micros() - start; +} + +unsigned long testFilledRects(uint16_t color1, uint16_t color2) { + unsigned long start, t = 0; + int n, i, i2, + cx = tft.width() / 2 - 1, + cy = tft.height() / 2 - 1; + + tft.fillScreen(TFT_BLACK); + n = min(tft.width(), tft.height()); + for (i = n - 1; i > 0; i -= 6) { + i2 = i / 2; + start = micros(); + tft.fillRect(cx - i2, cy - i2, i, i, color1); + t += micros() - start; + // Outlines are not included in timing results + tft.drawRect(cx - i2, cy - i2, i, i, color2); + } + + return t; +} + +unsigned long testFilledCircles(uint8_t radius, uint16_t color) { + unsigned long start; + int x, y, w = tft.width(), h = tft.height(), r2 = radius * 2; + + tft.fillScreen(TFT_BLACK); + start = micros(); + for (x = radius; x < w; x += r2) { + for (y = radius; y < h; y += r2) { + tft.fillCircle(x, y, radius, color); + } + } + + return micros() - start; +} + +unsigned long testCircles(uint8_t radius, uint16_t color) { + unsigned long start; + int x, y, r2 = radius * 2, + w = tft.width() + radius, + h = tft.height() + radius; + + // Screen is not cleared for this one -- this is + // intentional and does not affect the reported time. + start = micros(); + for (x = 0; x < w; x += r2) { + for (y = 0; y < h; y += r2) { + tft.drawCircle(x, y, radius, color); + } + } + + return micros() - start; +} + +unsigned long testTriangles() { + unsigned long start; + int n, i, cx = tft.width() / 2 - 1, + cy = tft.height() / 2 - 1; + + tft.fillScreen(TFT_BLACK); + n = min(cx, cy); + start = micros(); + for (i = 0; i < n; i += 5) { + tft.drawTriangle( + cx , cy - i, // peak + cx - i, cy + i, // bottom left + cx + i, cy + i, // bottom right + tft.color565(0, 0, i)); + } + + return micros() - start; +} + +unsigned long testFilledTriangles() { + unsigned long start, t = 0; + int i, cx = tft.width() / 2 - 1, + cy = tft.height() / 2 - 1; + + tft.fillScreen(TFT_BLACK); + start = micros(); + for (i = min(cx, cy); i > 10; i -= 5) { + start = micros(); + tft.fillTriangle(cx, cy - i, cx - i, cy + i, cx + i, cy + i, + tft.color565(0, i, i)); + t += micros() - start; + tft.drawTriangle(cx, cy - i, cx - i, cy + i, cx + i, cy + i, + tft.color565(i, i, 0)); + } + + return t; +} + +unsigned long testRoundRects() { + unsigned long start; + int w, i, i2, + cx = tft.width() / 2 - 1, + cy = tft.height() / 2 - 1; + + tft.fillScreen(TFT_BLACK); + w = min(tft.width(), tft.height()); + start = micros(); + for (i = 0; i < w; i += 6) { + i2 = i / 2; + tft.drawRoundRect(cx - i2, cy - i2, i, i, i / 8, tft.color565(i, 0, 0)); + } + + return micros() - start; +} + +unsigned long testFilledRoundRects() { + unsigned long start; + int i, i2, + cx = tft.width() / 2 - 1, + cy = tft.height() / 2 - 1; + + tft.fillScreen(TFT_BLACK); + start = micros(); + for (i = min(tft.width(), tft.height()); i > 20; i -= 6) { + i2 = i / 2; + tft.fillRoundRect(cx - i2, cy - i2, i, i, i / 8, tft.color565(0, i, 0)); + } + + return micros() - start; +} + +/*************************************************** + Original Adafruit text: + + This is an example sketch for the Adafruit 2.2" SPI display. + This library works with the Adafruit 2.2" TFT Breakout w/SD card + ----> http://www.adafruit.com/products/1480 + + Check out the links above for our tutorials and wiring diagrams + These displays use SPI to communicate, 4 or 5 pins are required to + interface (RST is optional) + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + Written by Limor Fried/Ladyada for Adafruit Industries. + MIT license, all text above must be included in any redistribution + ****************************************************/ diff --git a/keywords.txt b/keywords.txt index 41b73b6..64ad998 100644 --- a/keywords.txt +++ b/keywords.txt @@ -17,6 +17,11 @@ getRotation KEYWORD2 invertDisplay KEYWORD2 setAddrWindow KEYWORD2 setWindow KEYWORD2 +setViewport KEYWORD2 +resetViewport KEYWORD2 +getViewportWidth KEYWORD2 +getViewportHeight KEYWORD2 +frameViewport KEYWORD2 pushColor KEYWORD2 pushColors KEYWORD2 pushBlock KEYWORD2 diff --git a/library.json b/library.json index 07bd791..f69a014 100644 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "TFT_eSPI", - "version": "2.2.23", + "version": "2.3.0", "keywords": "Arduino, tft, ePaper, display, STM32, ESP8266, NodeMCU, ESP32, M5Stack, ILI9341, ST7735, ILI9163, S6D02A1, ILI9486, ST7789, RM68140", "description": "A TFT and ePaper SPI graphics library with optimisation for ESP8266, ESP32 and STM32", "repository": diff --git a/library.properties b/library.properties index caef235..749db4f 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=TFT_eSPI -version=2.2.23 +version=2.3.0 author=Bodmer maintainer=Bodmer sentence=TFT graphics library for Arduino processors with performance optimisation for STM32, ESP8266 and ESP32