diff --git a/TFT_eSPI.cpp b/TFT_eSPI.cpp index dbbb2a3..9d69fe8 100644 --- a/TFT_eSPI.cpp +++ b/TFT_eSPI.cpp @@ -53,6 +53,14 @@ void delay(uint32_t ms) } } +/*************************************************************************************** +** Description: Constants for anti-aliased line drawing on TFT and in Sprites +***************************************************************************************/ +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; + // Clipping macro for pushImage #define PI_CLIP \ if (_vpOoB) return; \ @@ -2567,6 +2575,211 @@ void TFT_eSPI::drawPixel(int32_t x, int32_t y, uint16_t color) end_tft_write(); } +/*************************************************************************************** +** Function name: sqrt_fraction (private function) +** 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 an 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 +// 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, + uint32_t startAngle, uint32_t endAngle, + uint32_t fg_color, uint32_t bg_color, + bool smooth) +{ + if (endAngle > 360) endAngle = 360; + if (startAngle > 360) startAngle = 360; + if (_vpOoB || startAngle == endAngle) 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) { + // Arc sweeps through 6 o'clock so draw in two parts + if (startAngle < 360) drawArc(x, y, r, ir, startAngle, 360, fg_color, bg_color, smooth); + if (endAngle == 0) return; + startAngle = 0; + } + inTransaction = true; + + int32_t xs = 0; // x start position for quadrant scan + uint8_t alpha = 0; // alpha value for blending pixels + + uint32_t r2 = r * r; // Outer arc radius^2 + if (smooth) r++; // Outer AA zone radius + uint32_t r1 = r * r; // Outer AA radius^2 + int16_t w = r - ir; // Width of arc (r - ir + 1) + uint32_t r3 = ir * ir; // Inner arc radius^2 + if (smooth) ir--; // Inner AA zone radius + uint32_t r4 = ir * ir; // Inner AA radius^2 + + // 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)(1UL<<16); + + // Update slope table, add slope for arc start + if (startAngle <= 90) { + startSlope[0] = slope; + } + else if (startAngle <= 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)(1UL<<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; + startSlope[2] = 0; + } + else if (endAngle <= 180) { + endSlope[1] = slope; + startSlope[2] = 0; + } + 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 = ~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 <= endSlope[3] && slope >= startSlope[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 = 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 = fastBlend(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 <= endSlope[3] && slope >= startSlope[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: pushColor ** Description: push a single pixel @@ -2725,14 +2938,6 @@ void TFT_eSPI::drawLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint16_t end_tft_write(); } - -/*************************************************************************************** -** Description: Constants for anti-aliased line drawing on TFT and in Sprites -***************************************************************************************/ -constexpr float PixelAlphaGain = 255.0; -constexpr float LoAlphaTheshold = 1.0/32.0; -constexpr float HiAlphaTheshold = 1.0 - LoAlphaTheshold; - /*************************************************************************************** ** Function name: drawPixel (aplha blended) ** Description: Draw a pixel blended with the screen or bg pixel colour diff --git a/TFT_eSPI.h b/TFT_eSPI.h index df002d6..77e2e62 100644 --- a/TFT_eSPI.h +++ b/TFT_eSPI.h @@ -346,6 +346,12 @@ class TFT_eSPI : public espgui::TftInterface { // 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); + + // As per "drawSmoothArc" except the ends of the arc are NOT anti-aliased, this facilitates dynamic arc length changes with + // arc segments and ensures clean segment joints. + // The sides of the arc are anti-aliased by default. If smoothArc is false sides will NOT be anti-aliased + void drawArc(int32_t x, int32_t y, int32_t r, int32_t ir, uint32_t startAngle, uint32_t endAngle, uint32_t fg_color, uint32_t bg_color, bool smoothArc = true); + // 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); @@ -559,6 +565,9 @@ class TFT_eSPI : public espgui::TftInterface { // 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); @@ -646,6 +655,24 @@ class TFT_eSPI : public espgui::TftInterface { }; // End of class TFT_eSPI +// Swap any type +template static inline void +transpose(T& a, T& b) { T t = a; a = b; b = t; } + +// Fast alphaBlend +template static inline uint16_t +fastBlend(A alpha, F fgc, B bgc) +{ + // Split out and blend 5-bit red and blue channels + uint32_t rxb = bgc & 0xF81F; + rxb += ((fgc & 0xF81F) - rxb) * (alpha >> 2) >> 6; + // Split out and blend 6-bit green channel + uint32_t xgx = bgc & 0x07E0; + xgx += ((fgc & 0x07E0) - xgx) * alpha >> 8; + // Recombine channels + return (rxb & 0xF81F) | (xgx & 0x07E0); +} + /*************************************************************************************** ** Section 10: Additional extension classes ***************************************************************************************/