From 82d232adfc367cc54eaeffac9eb306596a5bb943 Mon Sep 17 00:00:00 2001 From: Bodmer Date: Thu, 12 Jan 2023 22:39:03 +0000 Subject: [PATCH] Add smooth arc drawing function Update ESP8266 architecture reference Add pushMaskedImage() to render 16bpp images with a 1bpp mask (used for transparent PNG images plus with sprites) New functions added using drawArc: drawSmoothArc drawSmoothCircle drawSmoothRoundRect New sqrt_fraction() added to improve smooth graphics performance on processors without a FPU (e.g. RP2040) Faster alphaBlend() function added which retains 6bpp for green Rename swap_coord() to transpose() --- Extensions/Sprite.cpp | 12 +- Processors/TFT_eSPI_ESP8266.h | 2 +- Processors/TFT_eSPI_RP2040.c | 2 +- Processors/TFT_eSPI_RP2040.h | 42 +- TFT_eSPI.cpp | 650 ++++++++++++++++-- TFT_eSPI.h | 114 ++- .../Anti-aliased_Clock/NTP_Time.h | 2 +- .../Read_User_Setup/Read_User_Setup.ino | 12 +- keywords.txt | 12 +- 9 files changed, 723 insertions(+), 125 deletions(-) diff --git a/Extensions/Sprite.cpp b/Extensions/Sprite.cpp index e3db710..80dadfe 100644 --- a/Extensions/Sprite.cpp +++ b/Extensions/Sprite.cpp @@ -1257,8 +1257,8 @@ void TFT_eSprite::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, const u // Intentionally not constrained to viewport area, does not manage 1bpp rotations void TFT_eSprite::setWindow(int32_t x0, int32_t y0, int32_t x1, int32_t y1) { - if (x0 > x1) swap_coord(x0, x1); - if (y0 > y1) swap_coord(y0, y1); + if (x0 > x1) transpose(x0, x1); + if (y0 > y1) transpose(y0, y1); int32_t w = width(); int32_t h = height(); @@ -1700,13 +1700,13 @@ void TFT_eSprite::drawLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint3 bool steep = abs(y1 - y0) > abs(x1 - x0); if (steep) { - swap_coord(x0, y0); - swap_coord(x1, y1); + transpose(x0, y0); + transpose(x1, y1); } if (x0 > x1) { - swap_coord(x0, x1); - swap_coord(y0, y1); + transpose(x0, x1); + transpose(y0, y1); } int32_t dx = x1 - x0, dy = abs(y1 - y0);; diff --git a/Processors/TFT_eSPI_ESP8266.h b/Processors/TFT_eSPI_ESP8266.h index 24d8fff..c05d136 100644 --- a/Processors/TFT_eSPI_ESP8266.h +++ b/Processors/TFT_eSPI_ESP8266.h @@ -19,7 +19,7 @@ #define DMA_BUSY_CHECK // DMA not available, leave blank // Initialise processor specific SPI functions, used by init() -#if (!defined (SUPPORT_TRANSACTIONS) && defined (ESP8266)) +#if (!defined (SUPPORT_TRANSACTIONS) && defined (ARDUINO_ARCH_ESP8266)) #define INIT_TFT_DATA_BUS \ spi.setBitOrder(MSBFIRST); \ spi.setDataMode(TFT_SPI_MODE); \ diff --git a/Processors/TFT_eSPI_RP2040.c b/Processors/TFT_eSPI_RP2040.c index 4e3ab4f..a902571 100644 --- a/Processors/TFT_eSPI_RP2040.c +++ b/Processors/TFT_eSPI_RP2040.c @@ -577,7 +577,7 @@ void TFT_eSPI::pushPixels(const void* data_in, uint32_t len){ //////////////////////////////////////////////////////////////////////////////////////// -#ifdef RP2040_DMA // DMA functions for 16 bit SPI and 8 bit parallel displays +#ifdef RP2040_DMA // DMA functions for 16 bit SPI and 8/16 bit parallel displays //////////////////////////////////////////////////////////////////////////////////////// /* These are created in header file: diff --git a/Processors/TFT_eSPI_RP2040.h b/Processors/TFT_eSPI_RP2040.h index c4a8f9f..c0e0086 100644 --- a/Processors/TFT_eSPI_RP2040.h +++ b/Processors/TFT_eSPI_RP2040.h @@ -65,9 +65,20 @@ #define DMA_BUSY_CHECK #endif +// Handle high performance MHS RPi display type +#if defined (MHS_DISPLAY_TYPE) && !defined (RPI_DISPLAY_TYPE) + #define RPI_DISPLAY_TYPE +#endif + #if !defined (RP2040_PIO_INTERFACE) // SPI - // Initialise processor specific SPI functions, used by init() - #define INIT_TFT_DATA_BUS // Not used + + #if defined (MHS_DISPLAY_TYPE) // High speed RPi TFT type always needs 16 bit transfers + // This swaps to 16 bit mode, used for commands so wait avoids clash with DC timing + #define INIT_TFT_DATA_BUS hw_write_masked(&spi_get_hw(SPI_X)->cr0, (16 - 1) << SPI_SSPCR0_DSS_LSB, SPI_SSPCR0_DSS_BITS) + #else + // Initialise processor specific SPI functions, used by init() + #define INIT_TFT_DATA_BUS // Not used + #endif // Wait for tx to end, flush rx FIFO, clear rx overrun #define SPI_BUSY_CHECK while (spi_get_hw(SPI_X)->sr & SPI_SSPSR_BSY_BITS) {}; \ @@ -141,7 +152,7 @@ #if !defined (RP2040_PIO_INTERFACE)// SPI //#define DC_C sio_hw->gpio_clr = (1ul << TFT_DC) //#define DC_D sio_hw->gpio_set = (1ul << TFT_DC) - #if defined (RPI_DISPLAY_TYPE) + #if defined (RPI_DISPLAY_TYPE) && !defined (MHS_DISPLAY_TYPE) #define DC_C digitalWrite(TFT_DC, LOW); #define DC_D digitalWrite(TFT_DC, HIGH); #else @@ -167,7 +178,7 @@ #define CS_H // No macro allocated so it generates no code #else #if !defined (RP2040_PIO_INTERFACE) // SPI - #if defined (RPI_DISPLAY_TYPE) + #if defined (RPI_DISPLAY_TYPE) && !defined (MHS_DISPLAY_TYPE) #define CS_L digitalWrite(TFT_CS, LOW); #define CS_H digitalWrite(TFT_CS, HIGH); #else @@ -287,7 +298,28 @@ // Macros to write commands/pixel colour data to other displays //////////////////////////////////////////////////////////////////////////////////////// #else - #if defined (RPI_DISPLAY_TYPE) // RPi TFT type always needs 16 bit transfers + #if defined (MHS_DISPLAY_TYPE) // High speed RPi TFT type always needs 16 bit transfers + // This swaps to 16 bit mode, used for commands so wait avoids clash with DC timing + #define tft_Write_8(C) while (spi_get_hw(SPI_X)->sr & SPI_SSPSR_BSY_BITS) {}; \ + hw_write_masked(&spi_get_hw(SPI_X)->cr0, (16 - 1) << SPI_SSPCR0_DSS_LSB, SPI_SSPCR0_DSS_BITS); \ + spi_get_hw(SPI_X)->dr = (uint32_t)((C) | ((C)<<8)); \ + while (spi_get_hw(SPI_X)->sr & SPI_SSPSR_BSY_BITS) {}; \ + + // Note: the following macros do not wait for the end of transmission + + #define tft_Write_16(C) while (!spi_is_writable(SPI_X)){}; spi_get_hw(SPI_X)->dr = (uint32_t)(C) + + #define tft_Write_16N(C) while (!spi_is_writable(SPI_X)){}; spi_get_hw(SPI_X)->dr = (uint32_t)(C) + + #define tft_Write_16S(C) while (!spi_is_writable(SPI_X)){}; spi_get_hw(SPI_X)->dr = (uint32_t)(C)<<8 | (C)>>8 + + #define tft_Write_32(C) spi_get_hw(SPI_X)->dr = (uint32_t)((C)>>16); spi_get_hw(SPI_X)->dr = (uint32_t)(C) + + #define tft_Write_32C(C,D) spi_get_hw(SPI_X)->dr = (uint32_t)(C); spi_get_hw(SPI_X)->dr = (uint32_t)(D) + + #define tft_Write_32D(C) spi_get_hw(SPI_X)->dr = (uint32_t)(C); spi_get_hw(SPI_X)->dr = (uint32_t)(C) + + #elif defined (RPI_DISPLAY_TYPE) // RPi TFT type always needs 16 bit transfers #define tft_Write_8(C) spi.transfer(C); spi.transfer(C) #define tft_Write_16(C) spi.transfer((uint8_t)((C)>>8));spi.transfer((uint8_t)((C)>>0)) #define tft_Write_16N(C) spi.transfer((uint8_t)((C)>>8));spi.transfer((uint8_t)((C)>>0)) diff --git a/TFT_eSPI.cpp b/TFT_eSPI.cpp index 282ed1b..c368d58 100644 --- a/TFT_eSPI.cpp +++ b/TFT_eSPI.cpp @@ -149,7 +149,6 @@ inline void TFT_eSPI::begin_tft_read(void){ SET_BUS_READ_MODE; } - /*************************************************************************************** ** Function name: end_tft_read (was called spi_end_read) ** Description: End transaction for reads and deselect TFT @@ -233,7 +232,6 @@ void TFT_eSPI::setViewport(int32_t x, int32_t y, int32_t w, int32_t h, bool vpDa //Serial.print(" _vpX=");Serial.print( _vpX);Serial.print(", _vpY=");Serial.print( _vpY); //Serial.print(", _vpW=");Serial.print(_vpW);Serial.print(", _vpH=");Serial.println(_vpH); - } /*************************************************************************************** @@ -866,6 +864,48 @@ void TFT_eSPI::setRotation(uint8_t m) } +/*************************************************************************************** +** Function name: getRotation +** Description: Return the rotation value (as used by setRotation()) +***************************************************************************************/ +uint8_t TFT_eSPI::getRotation(void) +{ + return rotation; +} + + +/*************************************************************************************** +** Function name: setOrigin +** Description: Set graphics origin to position x,y wrt to top left corner +***************************************************************************************/ +//Note: setRotation, setViewport and resetViewport will revert origin to top left +void TFT_eSPI::setOrigin(int32_t x, int32_t y) +{ + _xDatum = x; + _yDatum = y; +} + + +/*************************************************************************************** +** Function name: getOriginX +** Description: Set graphics origin to position x +***************************************************************************************/ +int32_t TFT_eSPI::getOriginX(void) +{ + return _xDatum; +} + + +/*************************************************************************************** +** Function name: getOriginY +** Description: Set graphics origin to position y +***************************************************************************************/ +int32_t TFT_eSPI::getOriginY(void) +{ + return _yDatum; +} + + /*************************************************************************************** ** Function name: commandList, used for FLASH based lists only (e.g. ST7735) ** Description: Get initialisation commands from FLASH and send to TFT @@ -1998,6 +2038,91 @@ void TFT_eSPI::pushImage(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t *da end_tft_write(); } +/*************************************************************************************** +** Function name: pushMaskedImage +** Description: Render a 16 bit colour image with a 1bpp mask +***************************************************************************************/ +// Can be used with a 16bpp sprite and a 1bpp sprite for the mask +void TFT_eSPI::pushMaskedImage(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t *img, uint8_t *mask) +{ + if (_vpOoB || w < 1 || h < 1) return; + + // To simplify mask handling the window clipping is done by the pushImage function + // Each mask image line assumed to be padded to and integer number of bytes & padding bits are 0 + + begin_tft_write(); + inTransaction = true; + + uint8_t *mptr = mask; + uint8_t *eptr = mask + ((w + 7) >> 3); + uint16_t *iptr = img; + uint32_t setCount = 0; + + // For each line in the image + while (h--) { + uint32_t xp = 0; + uint32_t clearCount = 0; + uint8_t mbyte= *mptr++; + uint32_t bits = 8; + // Scan through each byte of the bitmap and determine run lengths + do { + setCount = 0; + + //Get run length for clear bits to determine x offset + while ((mbyte & 0x80) == 0x00) { + // Check if remaining bits in byte are clear (reduce shifts) + if (mbyte == 0) { + clearCount += bits; // bits not always 8 here + if (mptr >= eptr) break; // end of line + mbyte = *mptr++; + bits = 8; + continue; + } + mbyte = mbyte << 1; // 0's shifted in + clearCount ++; + if (--bits) continue;; + if (mptr >= eptr) break; + mbyte = *mptr++; + bits = 8; + } + + //Get run length for set bits to determine render width + while ((mbyte & 0x80) == 0x80) { + // Check if all bits are set (reduces shifts) + if (mbyte == 0xFF) { + setCount += bits; + if (mptr >= eptr) break; + mbyte = *mptr++; + //bits = 8; // NR, bits always 8 here unless 1's shifted in + continue; + } + mbyte = mbyte << 1; //or mbyte += mbyte + 1 to shift in 1's + setCount ++; + if (--bits) continue; + if (mptr >= eptr) break; + mbyte = *mptr++; + bits = 8; + } + + // A mask boundary or mask end has been found, so render the pixel line + if (setCount) { + xp += clearCount; + clearCount = 0; + pushImage(x + xp, y, setCount, 1, iptr + xp); // pushImage handles clipping + //pushImageDMA(x + xp, y, setCount, 1, iptr + xp); + xp += setCount; + } + } while (setCount || mptr < eptr); + + y++; + iptr += w; + eptr += ((w + 7) >> 3); + } + + inTransaction = lockTransaction; + end_tft_write(); +} + /*************************************************************************************** ** Function name: setSwapBytes @@ -2510,13 +2635,13 @@ void TFT_eSPI::fillTriangle ( int32_t x0, int32_t y0, int32_t x1, int32_t y1, in // Sort coordinates by Y order (y2 >= y1 >= y0) if (y0 > y1) { - swap_coord(y0, y1); swap_coord(x0, x1); + transpose(y0, y1); transpose(x0, x1); } if (y1 > y2) { - swap_coord(y2, y1); swap_coord(x2, x1); + transpose(y2, y1); transpose(x2, x1); } if (y0 > y1) { - swap_coord(y0, y1); swap_coord(x0, x1); + transpose(y0, y1); transpose(x0, x1); } if (y0 == y2) { // Handle awkward all-on-same-line case as its own thing @@ -2557,7 +2682,7 @@ void TFT_eSPI::fillTriangle ( int32_t x0, int32_t y0, int32_t x1, int32_t y1, in sa += dx01; sb += dx02; - if (a > b) swap_coord(a, b); + if (a > b) transpose(a, b); drawFastHLine(a, y, b - a + 1, color); } @@ -2571,7 +2696,7 @@ void TFT_eSPI::fillTriangle ( int32_t x0, int32_t y0, int32_t x1, int32_t y1, in sa += dx12; sb += dx02; - if (a > b) swap_coord(a, b); + if (a > b) transpose(a, b); drawFastHLine(a, y, b - a + 1, color); } @@ -2837,15 +2962,6 @@ uint16_t TFT_eSPI::getTextPadding(void) return padX; } -/*************************************************************************************** -** Function name: getRotation -** Description: Return the rotation value (as used by setRotation()) -***************************************************************************************/ -uint8_t TFT_eSPI::getRotation(void) -{ - return rotation; -} - /*************************************************************************************** ** Function name: getTextDatum ** Description: Return the text datum value (as used by setTextDatum()) @@ -3194,7 +3310,7 @@ void TFT_eSPI::setWindow(int32_t x0, int32_t y0, int32_t x1, int32_t y1) addr_col = 0xFFFF; #if defined (ILI9225_DRIVER) - if (rotation & 0x01) { swap_coord(x0, y0); swap_coord(x1, y1); } + if (rotation & 0x01) { transpose(x0, y0); transpose(x1, y1); } SPI_BUSY_CHECK; DC_C; tft_Write_8(TFT_CASET1); DC_D; tft_Write_16(x0); @@ -3222,8 +3338,8 @@ void TFT_eSPI::setWindow(int32_t x0, int32_t y0, int32_t x1, int32_t y1) #endif #elif defined (SSD1351_DRIVER) if (rotation & 1) { - swap_coord(x0, y0); - swap_coord(x1, y1); + transpose(x0, y0); + transpose(x1, y1); } SPI_BUSY_CHECK; DC_C; tft_Write_8(TFT_CASET); @@ -3234,7 +3350,7 @@ void TFT_eSPI::setWindow(int32_t x0, int32_t y0, int32_t x1, int32_t y1) DC_D; #else #if defined (SSD1963_DRIVER) - if ((rotation & 0x1) == 0) { swap_coord(x0, y0); swap_coord(x1, y1); } + if ((rotation & 0x1) == 0) { transpose(x0, y0); transpose(x1, y1); } #endif #ifdef CGRAM_OFFSET @@ -3326,7 +3442,7 @@ void TFT_eSPI::readAddrWindow(int32_t xs, int32_t ys, int32_t w, int32_t h) addr_row = 0xFFFF; #if defined (SSD1963_DRIVER) - if ((rotation & 0x1) == 0) { swap_coord(xs, ys); swap_coord(xe, ye); } + if ((rotation & 0x1) == 0) { transpose(xs, ys); transpose(xe, ye); } #endif #ifdef CGRAM_OFFSET @@ -3420,7 +3536,7 @@ void TFT_eSPI::drawPixel(int32_t x, int32_t y, uint32_t color) begin_tft_write(); #if defined (ILI9225_DRIVER) - if (rotation & 0x01) { swap_coord(x, y); } + if (rotation & 0x01) { transpose(x, y); } SPI_BUSY_CHECK; // Set window to full screen to optimise sequential pixel rendering @@ -3455,7 +3571,7 @@ void TFT_eSPI::drawPixel(int32_t x, int32_t y, uint32_t color) #elif (defined (ARDUINO_ARCH_RP2040) || defined (ARDUINO_ARCH_MBED)) && !defined (SSD1351_DRIVER) #if defined (SSD1963_DRIVER) - if ((rotation & 0x1) == 0) { swap_coord(x, y); } + if ((rotation & 0x1) == 0) { transpose(x, y); } #endif #if !defined(RP2040_PIO_INTERFACE) @@ -3535,13 +3651,13 @@ void TFT_eSPI::drawPixel(int32_t x, int32_t y, uint32_t color) #else #if defined (SSD1963_DRIVER) - if ((rotation & 0x1) == 0) { swap_coord(x, y); } + if ((rotation & 0x1) == 0) { transpose(x, y); } #endif SPI_BUSY_CHECK; #if defined (SSD1351_DRIVER) - if (rotation & 0x1) { swap_coord(x, y); } + if (rotation & 0x1) { transpose(x, y); } // No need to send x if it has not changed (speeds things up) if (addr_col != x) { DC_C; tft_Write_8(TFT_CASET); @@ -3693,13 +3809,13 @@ void TFT_eSPI::drawLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint32_t bool steep = abs(y1 - y0) > abs(x1 - x0); if (steep) { - swap_coord(x0, y0); - swap_coord(x1, y1); + transpose(x0, y0); + transpose(x1, y1); } if (x0 > x1) { - swap_coord(x0, x1); - swap_coord(y0, y1); + transpose(x0, x1); + transpose(y0, y1); } int32_t dx = x1 - x0, dy = abs(y1 - y0);; @@ -3750,6 +3866,7 @@ void TFT_eSPI::drawLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint32_t constexpr float PixelAlphaGain = 255.0; constexpr float LoAlphaTheshold = 1.0/32.0; constexpr float HiAlphaTheshold = 1.0 - LoAlphaTheshold; +constexpr float deg2rad = 3.14159265359/180.0; /*************************************************************************************** ** Function name: drawPixel (alpha blended) @@ -3763,6 +3880,291 @@ uint16_t TFT_eSPI::drawPixel(int32_t x, int32_t y, uint32_t color, uint8_t alpha return color; } + +/*************************************************************************************** +** Function name: drawSmoothArc +** Description: Draw a smooth arc clockwise from 6 o'clock +***************************************************************************************/ +void TFT_eSPI::drawSmoothArc(int32_t x, int32_t y, int32_t r, int32_t ir, int32_t startAngle, int32_t endAngle, uint32_t fg_color, uint32_t bg_color, bool roundEnds) +// Centre at x,y +// r = arc outer radius, ir = arc inner radius. Inclusive so arc thickness = r - ir + 1 +// Angles in range 0-360 +// Arc foreground colour anti-aliased with background colour at edges +// anti-aliased roundEnd is optional, default is anti-aliased straight end +// Note: rounded ends extend the arc angle so can overlap, user sketch to manage this. +{ + inTransaction = true; + + if (endAngle != startAngle) + { + float sx = -sinf(startAngle * deg2rad); + float sy = +cosf(startAngle * deg2rad); + float ex = -sinf( endAngle * deg2rad); + float ey = +cosf( endAngle * deg2rad); + + if (roundEnds) + { // Round ends + sx = sx * (r + ir)/2.0 + x; + sy = sy * (r + ir)/2.0 + y; + drawSpot(sx, sy, (r - ir)/2.0, fg_color, bg_color); + + ex = ex * (r + ir)/2.0 + x; + ey = ey * (r + ir)/2.0 + y; + drawSpot(ex, ey, (r - ir)/2.0, fg_color, bg_color); + } + else + { // Square ends + float asx = sx * ir + x; + float asy = sy * ir + y; + float aex = sx * r + x; + float aey = sy * r + y; + drawWedgeLine(asx, asy, aex, aey, 0.3, 0.3, fg_color, bg_color); + + asx = ex * ir + x; + asy = ey * ir + y; + aex = ex * r + x; + aey = ey * r + y; + drawWedgeLine(asx, asy, aex, aey, 0.3, 0.3, fg_color, bg_color); + } + + if (endAngle > startAngle) + { + // Draw arc in single sweep + drawArc(x, y, r, ir, startAngle, endAngle, fg_color, bg_color); + } + else + { + // Arc sweeps through 6 o'clock so draw in two parts + drawArc(x, y, r, ir, startAngle, 360, fg_color, bg_color); + drawArc(x, y, r, ir, 0, endAngle, fg_color, bg_color); + } + } + else // Draw full 360 + { + drawArc(x, y, r, ir, 0, 360, fg_color, bg_color); + } + + inTransaction = lockTransaction; + end_tft_write(); +} + +/*************************************************************************************** +** Function name: sqrt_fraction +** Description: Smooth graphics support function for alpha derivation +***************************************************************************************/ +// Compute the fixed point square root of an integer and +// return the 8 MS bits of fractional part. +// Quicker than sqrt() for processors that do not have and FPU (e.g. RP2040) +inline uint8_t TFT_eSPI::sqrt_fraction(uint32_t num) { + if (num > (0x40000000)) return 0; + uint32_t bsh = 0x00004000; + uint32_t fpr = 0; + uint32_t osh = 0; + + // Auto adjust from U8:8 up to U15:16 + while (num>bsh) {bsh <<= 2; osh++;} + + do { + uint32_t bod = bsh + fpr; + if(num >= bod) + { + num -= bod; + fpr = bsh + bod; + } + num <<= 1; + } while(bsh >>= 1); + + return fpr>>osh; +} + +/*************************************************************************************** +** Function name: drawArc +** Description: Draw an arc clockwise from 6 o'clock position +***************************************************************************************/ +// Centre at x,y +// r = arc outer radius, ir = arc inner radius. Inclusive, so arc thickness = r-ir+1 +// Angles MUST be in range 0-360, end angle MUST be greater than start angle +// Arc foreground fg_color anti-aliased with background colour along sides +// smooth is optional, default is true, smooth=false means no antialiasing +// Note: Arc ends are not anti-aliased (use drawSmoothArc instead for that) +void TFT_eSPI::drawArc(int32_t x, int32_t y, int32_t r, int32_t ir, + int32_t startAngle, int32_t endAngle, + uint32_t fg_color, uint32_t bg_color, + bool smooth) +{ + if (_vpOoB) return; + if (r < ir) transpose(r, ir); // Required that r > ir + if (r <= 0 || ir < 0) return; // Invalid r, ir can be zero (circle sector) + if (endAngle < startAngle) transpose(startAngle, endAngle); + if (startAngle < 0) startAngle = 0; + if (endAngle > 360) endAngle = 360; + + inTransaction = true; + + int32_t xs = 0; // x start position for quadrant scan + uint8_t alpha = 0; // alpha value for blending pixels + + int32_t r2 = r * r; // Outer arc radius^2 + if (smooth) r++; // Outer AA zone radius + int32_t r1 = r * r; // Outer AA radius^2 + int16_t w = r - ir; // Width of arc (r - ir + 1) + int32_t r3 = ir * ir; // Inner arc radius^2 + if (smooth) ir--; // Inner AA zone radius + int32_t r4 = ir * ir; // Inner AA radius^2 + + // Float variants of adjusted inner and outer arc radii + //float irf = ir; + //float rf = r; + + // 1 | 2 + // ---¦--- Arc quadrant index + // 0 | 3 + // Fixed point U16.16 slope table for arc start/end in each quadrant + uint32_t startSlope[4] = {0, 0, 0xFFFFFFFF, 0}; + uint32_t endSlope[4] = {0, 0xFFFFFFFF, 0, 0}; + + // Ensure maximum U16.16 slope of arc ends is ~ 0x8000 0000 + constexpr float minDivisor = 1.0f/0x8000; + + // Fill in start slope table and empty quadrants + float fabscos = fabsf(cosf(startAngle * deg2rad)); + float fabssin = fabsf(sinf(startAngle * deg2rad)); + + // U16.16 slope of arc start + uint32_t slope = (fabscos/(fabssin + minDivisor)) * (float)(1<<16); + + // Update slope table, add slope for arc start + if (startAngle < 90) { + startSlope[0] = slope; + } + else if ( + < 180) { + startSlope[1] = slope; + } + else if (startAngle < 270) { + startSlope[1] = 0xFFFFFFFF; + startSlope[2] = slope; + } + else { + startSlope[1] = 0xFFFFFFFF; + startSlope[2] = 0; + startSlope[3] = slope; + } + + // Fill in end slope table and empty quadrants + fabscos = fabsf(cosf(endAngle * deg2rad)); + fabssin = fabsf(sinf(endAngle * deg2rad)); + + // U16.16 slope of arc end + slope = (uint32_t)((fabscos/(fabssin + minDivisor)) * (float)(1<<16)); + + // Work out which quadrants will need to be drawn and add slope for arc end + if (endAngle < 90) { + endSlope[0] = slope; + endSlope[1] = 0; + endSlope[2] = 0xFFFFFFFF; + } + else if (endAngle < 180) { + endSlope[1] = slope; + endSlope[2] = 0xFFFFFFFF; + } + else if (endAngle < 270) { + endSlope[2] = slope; + } + else { + endSlope[3] = slope; + } + + // Scan quadrant + for (int32_t cy = r - 1; cy > 0; cy--) + { + uint32_t len[4] = { 0, 0, 0, 0}; // Pixel run length + int32_t xst[4] = {-1, -1, -1, -1}; // Pixel run x start + uint32_t dy2 = (r - cy) * (r - cy); + + // Find and track arc zone start point + while ((r - xs) * (r - xs) + dy2 >= r1) xs++; + + for (int32_t cx = xs; cx < r; cx++) + { + // Calculate radius^2 + uint32_t hyp = (r - cx) * (r - cx) + dy2; + + // If in outer zone calculate alpha + if (hyp > r2) { + //alpha = (uint8_t)((rf - sqrtf(hyp)) * 255); + alpha = ~sqrt_fraction(hyp); // Outer AA zone + } + // If within arc fill zone, get line start and lengths for each quadrant + else if (hyp >= r3) { + // Calculate U16.16 slope + slope = ((r - cy) << 16)/(r - cx); + if (slope <= startSlope[0] && slope >= endSlope[0]) { // slope hi -> lo + xst[0] = cx; // Bottom left line end + len[0]++; + } + if (slope >= startSlope[1] && slope <= endSlope[1]) { // slope lo -> hi + xst[1] = cx; // Top left line end + len[1]++; + } + if (slope <= startSlope[2] && slope >= endSlope[2]) { // slope hi -> lo + xst[2] = cx; // Bottom right line start + len[2]++; + } + if (slope >= startSlope[3] && slope <= endSlope[3]) { // slope lo -> hi + xst[3] = cx; // Top right line start + len[3]++; + } + continue; // Next x + } + else { + if (hyp <= r4) break; // Skip inner pixels + //alpha = (uint8_t)((sqrtf(hyp) - irf) * 255.0); + alpha = sqrt_fraction(hyp); // Inner AA zone + } + + if (alpha < 16) continue; // Skip low alpha pixels + + // If background is read it must be done in each quadrant + uint16_t pcol = alphaBlend(alpha, fg_color, bg_color); + // Check if an AA pixels need to be drawn + slope = ((r - cy)<<16)/(r - cx); + if (slope <= startSlope[0] && slope >= endSlope[0]) // BL + drawPixel(x + cx - r, y - cy + r, pcol); + if (slope >= startSlope[1] && slope <= endSlope[1]) // TL + drawPixel(x + cx - r, y + cy - r, pcol); + if (slope <= startSlope[2] && slope >= endSlope[2]) // TR + drawPixel(x - cx + r, y + cy - r, pcol); + if (slope >= startSlope[3] && slope <= endSlope[3]) // BR + drawPixel(x - cx + r, y - cy + r, pcol); + } + // Add line in inner zone + if (len[0]) drawFastHLine(x + xst[0] - len[0] + 1 - r, y - cy + r, len[0], fg_color); // BL + if (len[1]) drawFastHLine(x + xst[1] - len[1] + 1 - r, y + cy - r, len[1], fg_color); // TL + if (len[2]) drawFastHLine(x - xst[2] + r, y + cy - r, len[2], fg_color); // TR + if (len[3]) drawFastHLine(x - xst[3] + r, y - cy + r, len[3], fg_color); // BR + } + + // Fill in centre lines + if (startAngle == 0 || endAngle == 360) drawFastVLine(x, y + r - w, w, fg_color); // Bottom + if (startAngle <= 90 && endAngle >= 90) drawFastHLine(x - r + 1, y, w, fg_color); // Left + if (startAngle <= 180 && endAngle >= 180) drawFastVLine(x, y - r + 1, w, fg_color); // Top + if (startAngle <= 270 && endAngle >= 270) drawFastHLine(x + r - w, y, w, fg_color); // Right + + inTransaction = lockTransaction; + end_tft_write(); +} + +/*************************************************************************************** +** Function name: drawSmoothCircle +** Description: Draw a smooth circle +***************************************************************************************/ +// To have effective anti-aliasing the circle will be 3 pixels thick +void TFT_eSPI::drawSmoothCircle(int32_t x, int32_t y, int32_t r, uint32_t fg_color, uint32_t bg_color) +{ + drawSmoothRoundRect(x-r, y-r, r, r-1, 0, 0, fg_color, bg_color); +} + /*************************************************************************************** ** Function name: fillSmoothCircle ** Description: Draw a filled anti-aliased circle @@ -3789,12 +4191,19 @@ void TFT_eSPI::fillSmoothCircle(int32_t x, int32_t y, int32_t r, uint32_t color, int32_t hyp2 = (r - cx) * (r - cx) + dy2; if (hyp2 <= r1) break; if (hyp2 >= r2) continue; +//* + uint8_t alpha = ~sqrt_fraction(hyp2); + if (alpha > 246) break; + xs = cx; + if (alpha < 9) continue; + //*/ +/* float alphaf = (float)r - sqrtf(hyp2); if (alphaf > HiAlphaTheshold) break; xs = cx; if (alphaf < LoAlphaTheshold) continue; uint8_t alpha = alphaf * 255; - +//*/ if (bg_color == 0x00FFFFFF) { drawPixel(x + cx - r, y + cy - r, color, alpha, bg_color); drawPixel(x - cx + r, y + cy - r, color, alpha, bg_color); @@ -3816,6 +4225,119 @@ void TFT_eSPI::fillSmoothCircle(int32_t x, int32_t y, int32_t r, uint32_t color, } +/*************************************************************************************** +** Function name: drawSmoothRoundRect +** Description: Draw a rounded rectangle +***************************************************************************************/ +// x,y is top left corner of bounding box for a complete rounded rectangle +// r = arc outer corner radius, ir = arc inner radius. Arc thickness = r-ir+1 +// w and h are width and height of the bounding rectangle +// If w and h are < radius (e.g. 0,0) a circle will be drawn with centre at x+r,y+r +// Arc foreground fg_color anti-aliased with background colour at edges +// A subset of corners can be drawn by specifying a quadrants mask. A bit set in the +// mask means draw that quadrant (all are drawn if parameter missing): +// 0x1 | 0x2 +// ---¦--- Arc quadrant mask select bits (as in drawCircleHelper fn) +// 0x8 | 0x4 +void TFT_eSPI::drawSmoothRoundRect(int32_t x, int32_t y, int32_t r, int32_t ir, int32_t w, int32_t h, uint32_t fg_color, uint32_t bg_color, uint8_t quadrants) +{ + if (_vpOoB) return; + if (r < ir) transpose(r, ir); // Required that r > ir + if (r <= 0 || ir < 0) return; // Invalid + + w -= 2*r; + h -= 2*r; + + if (w < 0) w = 0; + if (h < 0) h = 0; + + inTransaction = true; + + x += r; + y += r; +/* + float alphaGain = 1.0; + if (w != 0 || h != 0) { + if (r - ir < 2) alphaGain = 1.5; // Boost brightness for thin lines + if (r - ir < 1) alphaGain = 1.7; + } +*/ + uint16_t t = r - ir + 1; + int32_t xs = 0; + int32_t cx = 0; + + int32_t r2 = r * r; // Outer arc radius^2 + r++; + int32_t r1 = r * r; // Outer AA zone radius^2 + + int32_t r3 = ir * ir; // Inner arc radius^2 + ir--; + int32_t r4 = ir * ir; // Inner AA zone radius^2 + + //float irf = ir; + //float rf = r; + uint8_t alpha = 0; + + // Scan top left quadrant x y r ir fg_color bg_color + for (int32_t cy = r - 1; cy > 0; cy--) + { + int32_t len = 0; // Pixel run length + int32_t lxst = 0; // Left side run x start + int32_t rxst = 0; // Right side run x start + int32_t dy2 = (r - cy) * (r - cy); + + // Find and track arc zone start point + while ((r - xs) * (r - xs) + dy2 >= r1) xs++; + + for (cx = xs; cx < r; cx++) + { + // Calculate radius^2 + int32_t hyp = (r - cx) * (r - cx) + dy2; + + // If in outer zone calculate alpha + if (hyp > r2) { + alpha = ~sqrt_fraction(hyp); + //alpha = (uint8_t)((rf - sqrtf(hyp)) * 255); // Outer AA zone + } + // If within arc fill zone, get line lengths for each quadrant + else if (hyp >= r3) { + rxst = cx; // Right side start + len++; // Line segment length + continue; // Next x + } + else { + if (hyp <= r4) break; // Skip inner pixels + //alpha = (uint8_t)((sqrtf(hyp) - irf) * 255); // Inner AA zone + alpha = sqrt_fraction(hyp); + } + + if (alpha < 16) continue; // Skip low alpha pixels + + // If background is read it must be done in each quadrant - TODO + uint16_t pcol = alphaBlend(alpha, fg_color, bg_color); + if (quadrants & 0x8) drawPixel(x + cx - r, y - cy + r + h, pcol); // BL + if (quadrants & 0x1) drawPixel(x + cx - r, y + cy - r, pcol); // TL + if (quadrants & 0x2) drawPixel(x - cx + r + w, y + cy - r, pcol); // TR + if (quadrants & 0x4) drawPixel(x - cx + r + w, y - cy + r + h, pcol); // BR + } + // Fill arc inner zone in each quadrant + lxst = rxst - len + 1; // Calculate line segment start for left side + if (quadrants & 0x8) drawFastHLine(x + lxst - r, y - cy + r + h, len, fg_color); // BL + if (quadrants & 0x1) drawFastHLine(x + lxst - r, y + cy - r, len, fg_color); // TL + if (quadrants & 0x2) drawFastHLine(x - rxst + r + w, y + cy - r, len, fg_color); // TR + if (quadrants & 0x4) drawFastHLine(x - rxst + r + w, y - cy + r + h, len, fg_color); // BR + } + + // Draw sides + if ((quadrants & 0xC) == 0xC) fillRect(x, y + r - t + h, w + 1, t, fg_color); // Bottom + if ((quadrants & 0x9) == 0x9) fillRect(x - r + 1, y, t, h + 1, fg_color); // Left + if ((quadrants & 0x3) == 0x3) fillRect(x, y - r + 1, w + 1, t, fg_color); // Top + if ((quadrants & 0x6) == 0x6) fillRect(x + r - t + w, y, t, h + 1, fg_color); // Right + + inTransaction = lockTransaction; + end_tft_write(); +} + /*************************************************************************************** ** Function name: fillSmoothRoundRect ** Description: Draw a filled anti-aliased rounded corner rectangle @@ -3823,20 +4345,23 @@ void TFT_eSPI::fillSmoothCircle(int32_t x, int32_t y, int32_t r, uint32_t color, void TFT_eSPI::fillSmoothRoundRect(int32_t x, int32_t y, int32_t w, int32_t h, int32_t r, uint32_t color, uint32_t bg_color) { inTransaction = true; + int32_t xs = 0; int32_t cx = 0; // Limit radius to half width or height - if (r < 0) r = 0; + if (r < 0) r = 0; if (r > w/2) r = w/2; if (r > h/2) r = h/2; y += r; h -= 2*r; fillRect(x, y, w, h, color); + h--; x += r; w -= 2*r+1; + int32_t r1 = r * r; r++; int32_t r2 = r * r; @@ -3849,12 +4374,18 @@ void TFT_eSPI::fillSmoothRoundRect(int32_t x, int32_t y, int32_t w, int32_t h, i int32_t hyp2 = (r - cx) * (r - cx) + dy2; if (hyp2 <= r1) break; if (hyp2 >= r2) continue; + + uint8_t alpha = ~sqrt_fraction(hyp2); + if (alpha > 246) break; + xs = cx; + if (alpha < 9) continue; +/* float alphaf = (float)r - sqrtf(hyp2); if (alphaf > HiAlphaTheshold) break; xs = cx; if (alphaf < LoAlphaTheshold) continue; uint8_t alpha = alphaf * 255; - +*/ drawPixel(x + cx - r, y + cy - r, color, alpha, bg_color); drawPixel(x - cx + r + w, y + cy - r, color, alpha, bg_color); drawPixel(x - cx + r + w, y - cy + r + h, color, alpha, bg_color); @@ -3871,6 +4402,7 @@ void TFT_eSPI::fillSmoothRoundRect(int32_t x, int32_t y, int32_t w, int32_t h, i ** Function name: drawSpot - maths intensive, so for small filled circles ** Description: Draw an anti-aliased filled circle at ax,ay with radius r ***************************************************************************************/ +// Coordinates are floating point to achieve sub-pixel positioning void TFT_eSPI::drawSpot(float ax, float ay, float r, uint32_t fg_color, uint32_t bg_color) { // Filled circle can be created by the wide line function with zero line length @@ -3893,7 +4425,7 @@ void TFT_eSPI::drawWideLine(float ax, float ay, float bx, float by, float wd, ui void TFT_eSPI::drawWedgeLine(float ax, float ay, float bx, float by, float ar, float br, uint32_t fg_color, uint32_t bg_color) { if ( (ar < 0.0) || (br < 0.0) )return; - if ( (abs(ax - bx) < 0.01f) && (abs(ay - by) < 0.01f) ) bx += 0.01f; // Avoid divide by zero + if ( (fabsf(ax - bx) < 0.01f) && (fabsf(ay - by) < 0.01f) ) bx += 0.01f; // Avoid divide by zero // Find line bounding box int32_t x0 = (int32_t)floorf(fminf(ax-ar, bx-br)); @@ -3963,7 +4495,7 @@ void TFT_eSPI::drawWedgeLine(float ax, float ay, float bx, float by, float ar, f pushColor(fg_color); continue; } - //Blend color with background and plot + //Blend colour with background and plot if (bg_color == 0x00FFFFFF) { bg = readPixel(xp, yp); swin = true; } @@ -3976,7 +4508,7 @@ void TFT_eSPI::drawWedgeLine(float ax, float ay, float bx, float by, float ar, f end_nin_write(); } -// Calculate distance of px,py to closest part of line + /*************************************************************************************** ** Function name: lineDistance - private helper function for drawWedgeLine ** Description: returns distance of px,py to closest part of a to b wedge @@ -4375,26 +4907,13 @@ uint16_t TFT_eSPI::decodeUTF8(uint8_t *buf, uint16_t *index, uint16_t remaining) ** Function name: alphaBlend ** Description: Blend 16bit foreground and background *************************************************************************************x*/ -uint16_t TFT_eSPI::alphaBlend(uint8_t alpha, uint16_t fgc, uint16_t bgc) +inline uint16_t TFT_eSPI::alphaBlend(uint8_t alpha, uint16_t fgc, uint16_t bgc) { - // For speed use fixed point maths and rounding to permit a power of 2 division - uint16_t fgR = ((fgc >> 10) & 0x3E) + 1; - uint16_t fgG = ((fgc >> 4) & 0x7E) + 1; - uint16_t fgB = ((fgc << 1) & 0x3E) + 1; - - uint16_t bgR = ((bgc >> 10) & 0x3E) + 1; - uint16_t bgG = ((bgc >> 4) & 0x7E) + 1; - uint16_t bgB = ((bgc << 1) & 0x3E) + 1; - - // Shift right 1 to drop rounding bit and shift right 8 to divide by 256 - uint16_t r = (((fgR * alpha) + (bgR * (255 - alpha))) >> 9); - uint16_t g = (((fgG * alpha) + (bgG * (255 - alpha))) >> 9); - uint16_t b = (((fgB * alpha) + (bgB * (255 - alpha))) >> 9); - - // Combine RGB565 colours into 16 bits - //return ((r&0x18) << 11) | ((g&0x30) << 5) | ((b&0x18) << 0); // 2 bit greyscale - //return ((r&0x1E) << 11) | ((g&0x3C) << 5) | ((b&0x1E) << 0); // 4 bit greyscale - return (r << 11) | (g << 5) | (b << 0); + uint32_t rxb = bgc & 0xF81F; + rxb += ((fgc & 0xF81F) - rxb) * (alpha >> 2) >> 6; + uint32_t xgx = bgc & 0x07E0; + xgx += ((fgc & 0x07E0) - xgx) * alpha >> 8; + return (rxb & 0xF81F) | (xgx & 0x07E0); } /*************************************************************************************** @@ -4427,22 +4946,13 @@ uint32_t TFT_eSPI::alphaBlend24(uint8_t alpha, uint32_t fgc, uint32_t bgc, uint8 if (alphaDither >255) alpha = 255; } - // For speed use fixed point maths and rounding to permit a power of 2 division - uint16_t fgR = ((fgc >> 15) & 0x1FE) + 1; - uint16_t fgG = ((fgc >> 7) & 0x1FE) + 1; - uint16_t fgB = ((fgc << 1) & 0x1FE) + 1; - - uint16_t bgR = ((bgc >> 15) & 0x1FE) + 1; - uint16_t bgG = ((bgc >> 7) & 0x1FE) + 1; - uint16_t bgB = ((bgc << 1) & 0x1FE) + 1; - - // Shift right 1 to drop rounding bit and shift right 8 to divide by 256 - uint16_t r = (((fgR * alpha) + (bgR * (255 - alpha))) >> 9); - uint16_t g = (((fgG * alpha) + (bgG * (255 - alpha))) >> 9); - uint16_t b = (((fgB * alpha) + (bgB * (255 - alpha))) >> 9); - - // Combine RGB colours into 24 bits - return (r << 16) | (g << 8) | (b << 0); + uint32_t rxx = bgc & 0xFF0000; + rxx += ((fgc & 0xFF0000) - rxx) * alpha >> 8; + uint32_t xgx = bgc & 0x00FF00; + xgx += ((fgc & 0xFF0000) - xgx) * alpha >> 8; + uint32_t xxb = bgc & 0x0000FF; + xxb += ((fgc & 0xFF0000) - xxb) * alpha >> 8; + return (rxx & 0xFF0000) | (xgx & 0x00FF00) | (xxb & 0x0000FF); } /*************************************************************************************** diff --git a/TFT_eSPI.h b/TFT_eSPI.h index 8aeadd3..ea74338 100644 --- a/TFT_eSPI.h +++ b/TFT_eSPI.h @@ -16,7 +16,7 @@ #ifndef _TFT_eSPIH_ #define _TFT_eSPIH_ -#define TFT_ESPI_VERSION "2.4.79" +#define TFT_ESPI_VERSION "2.5.81b" // Bit level feature flags // Bit 0 set: viewport capability @@ -402,9 +402,6 @@ int16_t tch_spi_freq;// Touch controller read/write SPI frequency /*************************************************************************************** ** Section 8: Class member and support functions ***************************************************************************************/ -// Swap any type -template static inline void -swap_coord(T& a, T& b) { T t = a; a = b; b = t; } // Callback prototype for smooth font pixel colour read typedef uint16_t (*getColorCallback)(uint16_t x, uint16_t y); @@ -449,6 +446,12 @@ class TFT_eSPI : public Print { friend class TFT_eSprite; // Sprite class has ac void setRotation(uint8_t r); // Set the display image orientation to 0, 1, 2 or 3 uint8_t getRotation(void); // Read the current rotation + // Change the origin position from the default top left + // Note: setRotation, setViewport and resetViewport will revert origin to top left corner of screen/sprite + void setOrigin(int32_t x, int32_t y); + int32_t getOriginX(void); + int32_t getOriginY(void); + void invertDisplay(bool i); // Tell TFT to invert all displayed colours @@ -491,6 +494,7 @@ class TFT_eSPI : public Print { friend class TFT_eSprite; // Sprite class has ac void end_SDA_Read(void); // Restore MOSI to output #endif + // Graphics drawing void fillScreen(uint32_t color), drawRect(int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color), @@ -500,28 +504,6 @@ class TFT_eSPI : public Print { friend class TFT_eSprite; // Sprite class has ac void fillRectVGradient(int16_t x, int16_t y, int16_t w, int16_t h, uint32_t color1, uint32_t color2); void fillRectHGradient(int16_t x, int16_t y, int16_t w, int16_t h, uint32_t color1, uint32_t color2); - // Draw a pixel blended with the pixel colour on the TFT or sprite, return blended colour - // If bg_color is not included the background pixel colour will be read from TFT or sprite - uint16_t drawPixel(int32_t x, int32_t y, uint32_t color, uint8_t alpha, uint32_t bg_color = 0x00FFFFFF); - - // Draw a small anti-aliased filled circle at ax,ay with radius r (uses drawWideLine) - // If bg_color is not included the background pixel colour will be read from TFT or sprite - void drawSpot(float ax, float ay, float r, uint32_t fg_color, uint32_t bg_color = 0x00FFFFFF); - - // Draw an anti-aliased filled circle at x, y with radius r - // If bg_color is not included the background pixel colour will be read from TFT or sprite - void fillSmoothCircle(int32_t x, int32_t y, int32_t r, uint32_t color, uint32_t bg_color = 0x00FFFFFF); - - void fillSmoothRoundRect(int32_t x, int32_t y, int32_t w, int32_t h, int32_t radius, uint32_t color, uint32_t bg_color = 0x00FFFFFF); - - // Draw an anti-aliased wide line from ax,ay to bx,by width wd with radiused ends (radius is wd/2) - // If bg_color is not included the background pixel colour will be read from TFT or sprite - void drawWideLine(float ax, float ay, float bx, float by, float wd, uint32_t fg_color, uint32_t bg_color = 0x00FFFFFF); - - // Draw an anti-aliased wide line from ax,ay to bx,by with different width at each end aw, bw and with radiused ends - // If bg_color is not included the background pixel colour will be read from TFT or sprite - void drawWedgeLine(float ax, float ay, float bx, float by, float aw, float bw, uint32_t fg_color, uint32_t bg_color = 0x00FFFFFF); - void drawCircle(int32_t x, int32_t y, int32_t r, uint32_t color), drawCircleHelper(int32_t x, int32_t y, int32_t r, uint8_t cornername, uint32_t color), fillCircle(int32_t x, int32_t y, int32_t r, uint32_t color), @@ -534,6 +516,53 @@ class TFT_eSPI : public Print { friend class TFT_eSprite; // Sprite class has ac drawTriangle(int32_t x1,int32_t y1, int32_t x2,int32_t y2, int32_t x3,int32_t y3, uint32_t color), fillTriangle(int32_t x1,int32_t y1, int32_t x2,int32_t y2, int32_t x3,int32_t y3, uint32_t color); + + // Smooth (anti-aliased) graphics drawing + // Draw a pixel blended with the background pixel colour (bg_color) specified, return blended colour + // If the bg_color is not specified, the background pixel colour will be read from TFT or sprite + uint16_t drawPixel(int32_t x, int32_t y, uint32_t color, uint8_t alpha, uint32_t bg_color = 0x00FFFFFF); + + // Draw an anti-aliased (smooth) arc between start and end angles. Arc ends are anti-aliased. + // By default the arc is drawn with square ends unless the "roundEnds" parameter is included and set true + // Angle = 0 is at 6 o'clock position, 90 at 9 o'clock etc. The angles must be in range 0-360 or they will be clipped to these limits + // The start angle may be larger than the end angle. Arcs are always drawn clockwise from the start angle. + void drawSmoothArc(int32_t x, int32_t y, int32_t r, int32_t ir, int32_t startAngle, int32_t endAngle, uint32_t fg_color, uint32_t bg_color, bool roundEnds = false); + + // As per "drawSmoothArc" except endAngle should be greater than startAngle (angles will be swapped otherwise) + // The sides of the arc are anti-aliased by default. If smoothArc is false sides will NOT be anti-aliased + // The ends of the arc are NOT anti-aliased, this facilitates dynamic arc length changes with arc segments and ensures clean segment joints + void drawArc(int32_t x, int32_t y, int32_t r, int32_t ir, int32_t startAngle, int32_t endAngle, uint32_t fg_color, uint32_t bg_color, bool smoothArc = true); + + // Draw an anti-aliased filled circle at x, y with radius r + // Note: The thickness of line is 3 pixels to reduce the visible "braiding" effect of anti-aliasing narrow lines + // this means the inner anti-alias zone is always at r-1 and the outer zone at r+1 + void drawSmoothCircle(int32_t x, int32_t y, int32_t r, uint32_t fg_color, uint32_t bg_color); + + // Draw an anti-aliased filled circle at x, y with radius r + // If bg_color is not included the background pixel colour will be read from TFT or sprite + void fillSmoothCircle(int32_t x, int32_t y, int32_t r, uint32_t color, uint32_t bg_color = 0x00FFFFFF); + + // Draw a rounded rectangle that has a line thickness of r-ir+1 and bounding box defined by x,y and w,h + // The outer corner radius is r, inner corner radius is ir + // The inside and outside of the border are anti-aliased + void drawSmoothRoundRect(int32_t x, int32_t y, int32_t r, int32_t ir, int32_t w, int32_t h, uint32_t fg_color, uint32_t bg_color = 0x00FFFFFF, uint8_t quadrants = 0xF); + + // Draw a filled rounded rectangle , corner radius r and bounding box defined by x,y and w,h + void fillSmoothRoundRect(int32_t x, int32_t y, int32_t w, int32_t h, int32_t radius, uint32_t color, uint32_t bg_color = 0x00FFFFFF); + + // Draw a small anti-aliased filled circle at ax,ay with radius r (uses drawWideLine) + // If bg_color is not included the background pixel colour will be read from TFT or sprite + void drawSpot(float ax, float ay, float r, uint32_t fg_color, uint32_t bg_color = 0x00FFFFFF); + + // Draw an anti-aliased wide line from ax,ay to bx,by width wd with radiused ends (radius is wd/2) + // If bg_color is not included the background pixel colour will be read from TFT or sprite + void drawWideLine(float ax, float ay, float bx, float by, float wd, uint32_t fg_color, uint32_t bg_color = 0x00FFFFFF); + + // Draw an anti-aliased wide line from ax,ay to bx,by with different width at each end aw, bw and with radiused ends + // If bg_color is not included the background pixel colour will be read from TFT or sprite + void drawWedgeLine(float ax, float ay, float bx, float by, float aw, float bw, uint32_t fg_color, uint32_t bg_color = 0x00FFFFFF); + + // Image rendering // Swap the byte order for pushImage() and pushPixels() - corrects endianness void setSwapBytes(bool swap); @@ -572,11 +601,16 @@ class TFT_eSPI : public Print { friend class TFT_eSprite; // Sprite class has ac void pushImage(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t *data, uint8_t transparent, bool bpp8 = true, uint16_t *cmap = nullptr); // FLASH version void pushImage(int32_t x, int32_t y, int32_t w, int32_t h, const uint8_t *data, bool bpp8, uint16_t *cmap = nullptr); + + // Render a 16 bit colour image with a 1bpp mask + void pushMaskedImage(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t *img, uint8_t *mask); + // This next function has been used successfully to dump the TFT screen to a PC for documentation purposes // It reads a screen area and returns the 3 RGB 8 bit colour values of each pixel in the buffer // Set w and h to 1 to read 1 pixel's colour. The data buffer must be at least w * h * 3 bytes void readRectRGB(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t *data); + // Text rendering - value returned is the pixel width of the rendered text int16_t drawNumber(long intNumber, int32_t x, int32_t y, uint8_t font), // Draw integer using specified font number drawNumber(long intNumber, int32_t x, int32_t y), // Draw integer using current font @@ -598,6 +632,7 @@ class TFT_eSPI : public Print { friend class TFT_eSprite; // Sprite class has ac drawCentreString(const String& string, int32_t x, int32_t y, uint8_t font),// Deprecated, use setTextDatum() and drawString() drawRightString(const String& string, int32_t x, int32_t y, uint8_t font); // Deprecated, use setTextDatum() and drawString() + // Text rendering and font handling support funtions void setCursor(int16_t x, int16_t y), // Set cursor for tft.print() setCursor(int16_t x, int16_t y, uint8_t font); // Set cursor and font number for tft.print() @@ -638,13 +673,14 @@ class TFT_eSPI : public Print { friend class TFT_eSprite; // Sprite class has ac // Support function to UTF8 decode and draw characters piped through print stream size_t write(uint8_t); - // size_t write(const uint8_t *buf, size_t len); + // size_t write(const uint8_t *buf, size_t len); // Used by Smooth font class to fetch a pixel colour for the anti-aliasing void setCallback(getColorCallback getCol); uint16_t fontsLoaded(void); // Each bit in returned value represents a font type that is loaded - used for debug/error handling only + // Low level read/write void spiwrite(uint8_t); // legacy support only #ifndef RM68120_DRIVER @@ -678,15 +714,15 @@ class TFT_eSPI : public Print { friend class TFT_eSprite; // Sprite class has ac // Alpha blend 2 colours, see generic "alphaBlend_Test" example // alpha = 0 = 100% background colour // alpha = 255 = 100% foreground colour - uint16_t alphaBlend(uint8_t alpha, uint16_t fgc, uint16_t bgc); + inline uint16_t alphaBlend(uint8_t alpha, uint16_t fgc, uint16_t bgc); // 16 bit colour alphaBlend with alpha dither (dither reduces colour banding) uint16_t alphaBlend(uint8_t alpha, uint16_t fgc, uint16_t bgc, uint8_t dither); // 24 bit colour alphaBlend with optional alpha dither uint32_t alphaBlend24(uint8_t alpha, uint32_t fgc, uint32_t bgc, uint8_t dither = 0); - - // DMA support functions - these are currently just for SPI writes when using the ESP32 or STM32 processors - // DMA works also on RP2040 and PIO SPI, 8 bit parallel and 16 bit parallel + // Direct Memory Access (DMA) support functions + // These can be used for SPI writes when using the ESP32 (original) or STM32 processors. + // DMA also works on a RP2040 processor with PIO based SPI and parallel (8 and 16 bit) interfaces // Bear in mind DMA will only be of benefit in particular circumstances and can be tricky // to manage by noobs. The functions have however been designed to be noob friendly and // avoid a few DMA behaviour "gotchas". @@ -718,8 +754,13 @@ class TFT_eSPI : public Print { friend class TFT_eSprite; // Sprite class has ac // Push an image to the TFT using DMA, buffer is optional and grabs (double buffers) a copy of the image // Use the buffer if the image data will get over-written or destroyed while DMA is in progress - // If swapping colour bytes is defined, and the double buffer option is NOT used, then the bytes - // in the original data image will be swapped by the function before DMA is initiated. + // + // Note 1: If swapping colour bytes is defined, and the double buffer option is NOT used, then the bytes + // in the original image buffer content will be byte swapped by the function before DMA is initiated. + // + // Note 2: If part of the image will be off screen or outside of a set viewport, then the the original + // image buffer content will be altered to a correctly clipped image before DMA is initiated. + // // The function will wait for the last DMA to complete if it is called while a previous DMA is still // in progress, this simplifies the sketch and helps avoid "gotchas". void pushImageDMA(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t* data, uint16_t* buffer = nullptr); @@ -812,6 +853,9 @@ class TFT_eSPI : public Print { friend class TFT_eSprite; // Sprite class has ac // Single GPIO input/output direction control void gpioMode(uint8_t gpio, uint8_t mode); + // Smooth graphics helper + uint8_t sqrt_fraction(uint32_t num); + // Helper function: calculate distance of a point from a finite length line between two points float wedgeLineDistance(float pax, float pay, float bax, float bay, float dr); @@ -917,6 +961,10 @@ class TFT_eSPI : public Print { friend class TFT_eSprite; // Sprite class has ac }; // End of class TFT_eSPI +// Swap any type +template static inline void +transpose(T& a, T& b) { T t = a; a = b; b = t; } + /*************************************************************************************** ** Section 10: Additional extension classes ***************************************************************************************/ diff --git a/examples/Smooth Graphics/Anti-aliased_Clock/NTP_Time.h b/examples/Smooth Graphics/Anti-aliased_Clock/NTP_Time.h index a2eaf12..bb72e32 100644 --- a/examples/Smooth Graphics/Anti-aliased_Clock/NTP_Time.h +++ b/examples/Smooth Graphics/Anti-aliased_Clock/NTP_Time.h @@ -11,7 +11,7 @@ #include // Choose library to load -#ifdef ESP8266 +#ifdef ARDUINO_ARCH_ESP8266 // ESP8266 #include #elif (defined(ARDUINO_ARCH_MBED) || defined(ARDUINO_ARCH_RP2040)) && !defined(ARDUINO_RASPBERRY_PI_PICO_W) diff --git a/examples/Test and diagnostics/Read_User_Setup/Read_User_Setup.ino b/examples/Test and diagnostics/Read_User_Setup/Read_User_Setup.ino index 21b74ec..93479f6 100644 --- a/examples/Test and diagnostics/Read_User_Setup/Read_User_Setup.ino +++ b/examples/Test and diagnostics/Read_User_Setup/Read_User_Setup.ino @@ -19,7 +19,7 @@ TFT_eSPI tft = TFT_eSPI(); // Invoke library -#ifdef ESP8266 +#ifdef ARDUINO_ARCH_ESP8266 ADC_MODE(ADC_VCC); // Read the supply voltage #endif @@ -45,15 +45,15 @@ Serial.print("\n[code]\n"); Serial.print ("TFT_eSPI ver = "); Serial.println(user.version); printProcessorName(); -#if defined (ESP32) || defined (ESP8266) +#if defined (ESP32) || defined (ARDUINO_ARCH_ESP8266) if (user.esp < 0x32F000 || user.esp > 0x32FFFF) { Serial.print("Frequency = "); Serial.print(ESP.getCpuFreqMHz());Serial.println("MHz"); } #endif -#ifdef ESP8266 +#ifdef ARDUINO_ARCH_ESP8266 Serial.print("Voltage = "); Serial.print(ESP.getVcc() / 918.0); Serial.println("V"); // 918 empirically determined #endif Serial.print("Transactions = "); Serial.println((user.trans == 1) ? "Yes" : "No"); Serial.print("Interface = "); Serial.println((user.serial == 1) ? "SPI" : "Parallel"); -#ifdef ESP8266 +#ifdef ARDUINO_ARCH_ESP8266 if (user.serial == 1){ Serial.print("SPI overlap = "); Serial.println((user.overlap == 1) ? "Yes\n" : "No\n"); } #endif if (user.tft_driver != 0xE9D) // For ePaper displays the size is defined in the sketch @@ -78,7 +78,7 @@ if (user.pin_tft_mosi != -1) { Serial.print("MOSI = "); Serial.print("GPIO ") if (user.pin_tft_miso != -1) { Serial.print("MISO = "); Serial.print("GPIO "); Serial.println(getPinName(user.pin_tft_miso)); } if (user.pin_tft_clk != -1) { Serial.print("SCK = "); Serial.print("GPIO "); Serial.println(getPinName(user.pin_tft_clk)); } -#ifdef ESP8266 +#ifdef ARDUINO_ARCH_ESP8266 if (user.overlap == true) { Serial.println("Overlap selected, following pins MUST be used:"); @@ -92,7 +92,7 @@ if (user.overlap == true) } #endif String pinNameRef = "GPIO "; -#ifdef ESP8266 +#ifdef ARDUINO_ARCH_ESP8266 pinNameRef = "PIN_D"; #endif diff --git a/keywords.txt b/keywords.txt index fb47c5e..21439a9 100644 --- a/keywords.txt +++ b/keywords.txt @@ -18,6 +18,9 @@ pushColor KEYWORD2 setRotation KEYWORD2 getRotation KEYWORD2 +setOrigin KEYWORD2 +getOriginX KEYWORD2 +getOriginY KEYWORD2 invertDisplay KEYWORD2 setAddrWindow KEYWORD2 @@ -44,6 +47,8 @@ end_SDA_Read KEYWORD2 fillScreen KEYWORD2 drawRect KEYWORD2 +fillRectHGradient KEYWORD2 +fillRectVGradient KEYWORD2 drawRoundRect KEYWORD2 fillRoundRect KEYWORD2 @@ -69,6 +74,7 @@ getPivotY KEYWORD2 readRect KEYWORD2 pushRect KEYWORD2 pushImage KEYWORD2 +pushMaskedImage KEYWORD2 readRectRGB KEYWORD2 drawNumber KEYWORD2 @@ -140,10 +146,12 @@ calibrateTouch KEYWORD2 setTouch KEYWORD2 # Smooth (anti-aliased) graphics functions -fillRectHGradient KEYWORD2 -fillRectVGradient KEYWORD2 +drawSmoothCircle KEYWORD2 fillSmoothCircle KEYWORD2 +drawSmoothRoundRect KEYWORD2 fillSmoothRoundRect KEYWORD2 +drawSmoothArc KEYWORD2 +drawArc KEYWORD2 drawSpot KEYWORD2 drawWideLine KEYWORD2 drawWedgeLine KEYWORD2