Commit d819c2e8 authored by Bodmer's avatar Bodmer

Add smooth graphics examples for new functions

parent b6db90ad
......@@ -3697,11 +3697,11 @@ void TFT_eSPI::fillSmoothCircle(int32_t x, int32_t y, int32_t r, uint32_t color,
{
float deltaX = r - cx;
float deltaY = r - cy;
float weight = r - sqrtf(deltaX * deltaX + deltaY * deltaY);
if (weight > 1.0) continue;
float alphaf = r - sqrtf(deltaX * deltaX + deltaY * deltaY);
if (alphaf > 1.0) continue;
xs = cx;
if (weight < LoAlphaTheshold) continue;
uint8_t alpha = weight * 255;
if (alphaf < LoAlphaTheshold) continue;
uint8_t alpha = alphaf * 255;
drawPixel(x + cx - r, y + cy - r, color, alpha, bg_color);
drawPixel(x - cx + r, y + cy - r, color, alpha, bg_color);
drawPixel(x - cx + r, y - cy + r, color, alpha, bg_color);
......@@ -3779,7 +3779,6 @@ void TFT_eSPI::drawWideLine(float ax, float ay, float bx, float by, float wd, ui
** Function name: drawWedgeLine
** Description: draw an anti-aliased line with different width radiused ends
***************************************************************************************/
// For sprites and TFT screens where the background pixel colour is fixed
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 ( (abs(ax - bx) < 0.01f) && (abs(ay - by) < 0.01f) ) bx += 0.01f; // Avoid divide by zero
......@@ -3792,47 +3791,73 @@ void TFT_eSPI::drawWedgeLine(float ax, float ay, float bx, float by, float ar, f
if (!clipWindow(&x0, &y0, &x1, &y1)) return;
// Establish slope direction
int32_t xs = x0, yp = y1, yinc = -1;
if (((ax-ar)>(bx-br) && (ay>by)) || ((ax-ar)<(bx-br) && ay<by)) { yp = y0; yinc = 1; }
// Establish x start and y start
int32_t ys = ay;
if ((ax-ar)>(bx-br)) ys = by;
br = ar - br; // Radius delta
float rdt = ar - br; // Radius delta
float alpha = 1.0f;
ar += 0.5;
uint32_t ri = (uint32_t)(ar);
uint16_t bg = bg_color;
float xpax, ypay, bax = bx - ax, bay = by - ay;
begin_nin_write();
inTransaction = true;
// Scan bounding box, calculate pixel intensity from distance to line
for (int32_t y = y0; y <= y1; y++) {
int32_t xs = x0;
// Scan bounding box from ys down, calculate pixel intensity from distance to line
for (int32_t yp = ys; yp <= y1; yp++) {
bool swin = true; // Flag to start new window area
bool endX = false; // Flag to skip pixels
ypay = yp - ay;
for (int32_t xp = xs; xp <= x1; xp++) {
if (endX) if (alpha <= LoAlphaTheshold) break; // Skip right side
xpax = xp - ax;
alpha = ar - wedgeLineDistance(xpax, ypay, bax, bay, rdt);
if (alpha <= LoAlphaTheshold ) continue;
// Track edge to minimise calculations
if (!endX) { endX = true; xs = xp; }
if (alpha > HiAlphaTheshold) {
if (swin) { setWindow(xp, yp, width()-1, yp); swin = false; }
pushColor(fg_color);
continue;
}
//Blend color with background and plot
if (bg_color == 0x00FFFFFF) {
bg = readPixel(xp, yp); swin = true;
}
if (swin) { setWindow(xp, yp, width()-1, yp); swin = false; }
pushColor(alphaBlend((uint8_t)(alpha * PixelAlphaGain), fg_color, bg));
}
}
// Reset x start to left side of box
xs = x0;
// Scan bounding box from ys-1 up, calculate pixel intensity from distance to line
for (int32_t yp = ys-1; yp >= y0; yp--) {
bool swin = true; // Flag to start new window area
bool endX = false; // Flag to skip pixels
ypay = yp - ay;
for (int32_t xp = xs; xp <= x1; xp++) {
if (endX) if (alpha <= LoAlphaTheshold) break; // Skip right side of drawn line
xpax = xp - ax;
alpha = ar - wedgeLineDistance(xpax, ypay, bax, bay, br);
alpha = ar - wedgeLineDistance(xpax, ypay, bax, bay, rdt);
if (alpha <= LoAlphaTheshold ) continue;
// Track left line segment boundary
if (!endX) { endX = true; if ((y > (y0+ri)) && (xp > xs)) xs = xp; }
// Track line boundary
if (!endX) { endX = true; xs = xp; }
if (alpha > HiAlphaTheshold) {
//drawPixel(xp, yp, fg_color);
if (swin) { setWindow(xp, yp, width()-1, yp); swin = false; }
pushColor(fg_color); // 778960 .v. 1337554
pushColor(fg_color);
continue;
}
//Blend color with background and plot
if (bg_color == 0x00FFFFFF) {
bg = readPixel(xp, yp); swin = true;
}
//drawPixel(xp, yp, alphaBlend((uint8_t)(alpha * PixelAlphaGain), fg_color, bg));
if (swin) { setWindow(xp, yp, width()-1, yp); swin = false; }
pushColor(alphaBlend((uint8_t)(alpha * PixelAlphaGain), fg_color, bg));
}
yp+=yinc;
}
inTransaction = lockTransaction;
......
......@@ -9,14 +9,14 @@
The built-in fonts 4, 6, 7 and 8 are Run Length
Encoded (RLE) to reduce the FLASH footprint.
Last review/edit by Bodmer: 05/01/22
Last review/edit by Bodmer: 04/02/22
****************************************************/
// Stop fonts etc being loaded multiple times
#ifndef _TFT_eSPIH_
#define _TFT_eSPIH_
#define TFT_ESPI_VERSION "2.4.33"
#define TFT_ESPI_VERSION "2.4.34"
// Bit level feature flags
// Bit 0 set: viewport capability
......
// Sketch to draw an analogue clock on the screen
// This uses anti-aliased drawing functions that are built into TFT_eSPI
// Anti-aliased lines can be drawn with sub-pixel resolution and permit lines to be
// drawn with less jaggedness.
// Based on a sketch by DavyLandman:
// https://github.com/Bodmer/TFT_eSPI/issues/905
#define WIFI_SSID "Your_SSID"
#define WIFI_PASSWORD "Your_Password"
#include <Arduino.h>
#include <TFT_eSPI.h> // Master copy here: https://github.com/Bodmer/TFT_eSPI
#include <SPI.h>
#include "NotoSansBold15.h"
TFT_eSPI tft = TFT_eSPI(); // Invoke library, pins defined in User_Setup.h
TFT_eSprite face = TFT_eSprite(&tft);
#define CLOCK_X_POS 10
#define CLOCK_Y_POS 10
#define CLOCK_FG TFT_SKYBLUE
#define CLOCK_BG TFT_NAVY
#define SECCOND_FG TFT_RED
#define LABEL_FG TFT_GOLD
#define CLOCK_R 127.0f / 2.0f // Clock face radius (float type)
#define H_HAND_LENGTH CLOCK_R/2.0f
#define M_HAND_LENGTH CLOCK_R/1.4f
#define S_HAND_LENGTH CLOCK_R/1.3f
#define FACE_W CLOCK_R * 2 + 1
#define FACE_H CLOCK_R * 2 + 1
// Calculate 1 second increment angles. Hours and minute hand angles
// change every second so we see smooth sub-pixel movement
#define SECOND_ANGLE 360.0 / 60.0
#define MINUTE_ANGLE SECOND_ANGLE / 60.0
#define HOUR_ANGLE MINUTE_ANGLE / 12.0
// Sprite width and height
#define FACE_W CLOCK_R * 2 + 1
#define FACE_H CLOCK_R * 2 + 1
// Time h:m:s
uint8_t h = 0, m = 0, s = 0;
float time_secs = h * 3600 + m * 60 + s;
// Load header after time_secs global variable has been created so it is in scope
#include "NTP_Time.h" // Attached to this sketch, see that tab for library needs
// Time for next tick
uint32_t targetTime = 0;
// =========================================================================
// Setup
// =========================================================================
void setup() {
Serial.begin(115200);
Serial.println("Booting...");
// Initialise the screen
tft.init();
// Ideally set orientation for good viewing angle range because
// the anti-aliasing effectiveness varies with screen viewing angle
// Usually this is when screen ribbon connector is at the bottom
tft.setRotation(0);
tft.fillScreen(TFT_BLACK);
// Create the clock face sprite
//face.setColorDepth(8); // 8 bit will work, but reduces effectiveness of anti-aliasing
face.createSprite(FACE_W, FACE_H);
// Only 1 font used in the sprite, so can remain loaded
face.loadFont(NotoSansBold15);
// Draw the whole clock - NTP time not available yet
renderFace(time_secs);
targetTime = millis() + 100;
}
// =========================================================================
// Loop
// =========================================================================
void loop() {
// Update time periodically
if (targetTime < millis()) {
// Update next tick time in 100 milliseconds for smooth movement
targetTime = millis() + 100;
// Increment time by 100 milliseconds
time_secs += 0.100;
// Midnight roll-over
if (time_secs >= (60 * 60 * 24)) time_secs = 0;
// All graphics are drawn in sprite to stop flicker
renderFace(time_secs);
// Request time from NTP server and synchronise the local clock
// (clock may pause since this may take >100ms)
syncTime();
}
}
// =========================================================================
// Draw the clock face in the sprite
// =========================================================================
static void renderFace(float t) {
float h_angle = t * HOUR_ANGLE;
float m_angle = t * MINUTE_ANGLE;
float s_angle = t * SECOND_ANGLE;
// The face is completely redrawn - this can be done quickly
face.fillSprite(TFT_BLACK);
// Draw the face circle
face.fillSmoothCircle( CLOCK_R, CLOCK_R, CLOCK_R, CLOCK_BG );
// Set text datum to middle centre and the colour
face.setTextDatum(MC_DATUM);
// The background colour will be read during the character rendering
face.setTextColor(CLOCK_FG);
// Text offset adjustment
constexpr uint32_t dialOffset = CLOCK_R - 10;
float xp = 0.0, yp = 0.0; // Use float pixel position for smooth AA motion
// Draw digits around clock perimeter
for (uint32_t h = 1; h <= 12; h++) {
getCoord(CLOCK_R, CLOCK_R, &xp, &yp, dialOffset, h * 360.0 / 12);
face.drawNumber(h, xp, 2 + yp);
}
// Add text (could be digital time...)
face.setTextColor(LABEL_FG);
face.drawString("TFT_eSPI", CLOCK_R, CLOCK_R * 0.75);
// Draw minute hand
getCoord(CLOCK_R, CLOCK_R, &xp, &yp, M_HAND_LENGTH, m_angle);
face.drawWideLine(CLOCK_R, CLOCK_R, xp, yp, 6.0f, CLOCK_FG);
face.drawWideLine(CLOCK_R, CLOCK_R, xp, yp, 2.0f, CLOCK_BG);
// Draw hour hand
getCoord(CLOCK_R, CLOCK_R, &xp, &yp, H_HAND_LENGTH, h_angle);
face.drawWideLine(CLOCK_R, CLOCK_R, xp, yp, 6.0f, CLOCK_FG);
face.drawWideLine(CLOCK_R, CLOCK_R, xp, yp, 2.0f, CLOCK_BG);
// Draw the central pivot circle
face.fillSmoothCircle(CLOCK_R, CLOCK_R, 4, CLOCK_FG);
// Draw cecond hand
getCoord(CLOCK_R, CLOCK_R, &xp, &yp, S_HAND_LENGTH, s_angle);
face.drawWedgeLine(CLOCK_R, CLOCK_R, xp, yp, 2.5, 1.0, SECCOND_FG);
face.pushSprite(5, 5, TFT_TRANSPARENT);
}
// =========================================================================
// Get coordinates of end of a line, pivot at x,y, length r, angle a
// =========================================================================
// Coordinates are returned to caller via the xp and yp pointers
#define DEG2RAD 0.0174532925
void getCoord(int16_t x, int16_t y, float *xp, float *yp, int16_t r, float a)
{
float sx1 = cos( (a - 90) * DEG2RAD);
float sy1 = sin( (a - 90) * DEG2RAD);
*xp = sx1 * r + x;
*yp = sy1 * r + y;
}
This diff is collapsed.
This diff is collapsed.
// Sketch to demonstrate smooth (anti-aliased) graphics funtions:
// Smooth graphics result in less pixel resolution jaggedness.
#include <TFT_eSPI.h> // Master copy here: https://github.com/Bodmer/TFT_eSPI
TFT_eSPI tft = TFT_eSPI(); // Invoke library, pins defined in User_Setup.h
TFT_eSprite spr = TFT_eSprite(&tft);
// =========================================================================
// Setup
// =========================================================================
void setup() {
Serial.begin(115200);
Serial.println("Booting...");
// Initialise the screen
tft.init();
// Ideally set orientation for good viewing angle range because
// the anti-aliasing effectiveness varies with screen viewing angle
tft.setRotation(0);
tft.fillScreen(TFT_BLACK);
// Small sprite for spot demo
spr.createSprite(23, 23);
}
// =========================================================================
// Loop
// =========================================================================
void loop() {
// drawSpot is for small anti-aliased circles, coordinates and radius are
// floating point to allow sub-pixel positioning (large circles will
// be slow to draw). Use fillSmoothCircle() for large circles.
// In this case black is the backgorund colour for the anti-aliasing
float x = 10.5;
float y = 10.5;
float r = 8.6;
tft.drawSpot(x, y, r, TFT_WHITE, TFT_BLACK);
// Fill sprite with a colour
spr.fillSprite(TFT_RED);
// Draw spot in sprite, the backgorund colour is ommitted so function
// reads background colour for aliasing. (To use this method with direct write
// to TFT (tft.drawSpot...) requires the capability to read data from the TFT!)
spr.drawSpot(x, y, r, TFT_WHITE);
spr.pushSprite(21, 0);
// Draw a segmented ring meter type display
// Centre of screen
int cx = tft.width() / 2;
int cy = tft.height() / 2;
// Inner and outer radius of ring
float r1 = min(cx, cy) - 40.0;
float r2 = min(cx, cy) - 10.0;
// Inner and outer line width
int w1 = r1 / 25;
int w2 = r2 / 20;
// The following will be updated by the getCoord function
float px1 = 0.0;
float py1 = 0.0;
float px2 = 0.0;
float py2 = 0.0;
// Wedge line function, an anti-aliased wide line between 2 points, with different
// line widths at the two ends. Background colour is black.
for (int angle = -130; angle <= 130; angle += 10) {
getCoord(cx, cy, &px1, &py1, &px2, &py2, r1, r2, angle);
uint16_t colour = rainbow(map(angle, -130, 130, 0, 127));
if (angle > 45) colour = TFT_DARKGREY;
tft.drawWedgeLine(px1, py1, px2, py2, w1, w2, colour, TFT_BLACK);
}
// Smooth dark red filled circle
tft.fillSmoothCircle(cx, cy, r1 - 8, TFT_MAROON, TFT_BLACK);
// Draw a white dial pointer using wedge line function
getCoord(cx, cy, &px1, &py1, &px2, &py2, 0, r1 - 10, 45);
// Magenta wedge line pointer on red background
// Line tapers from radius 5 to zero
tft.drawWedgeLine(cx, cy, px2, py2, 5, 0, TFT_WHITE, TFT_MAROON);
delay(5000);
// Test wideLine function
tft.fillScreen(TFT_BLACK);
// Line width
int wd = 5;
// Screen limits
int w = tft.width() - wd;
int h = tft.height() - wd;
// Line end coords
int x1 = w - 1;
int x2 = w - 1;
int y1 = h - 1;
int y2 = wd;
for (x2 = wd; x2 < w; x2 += wd * 3) tft.drawWideLine(x1, y1, x2, y2, wd, TFT_WHITE, TFT_BLACK);
x2 = wd;
for (y2 = wd; y2 < h; y2 += wd * 4) tft.drawWideLine(x1, y1, x2, y2, wd, TFT_WHITE, TFT_BLACK);
delay(5000);
// Demo filled smooth rounded rectangle
tft.fillScreen(TFT_BLACK);
x1 = 30;
y1 = 30;
w = tft.width() - 2 * x1;
h = tft.height() - 2 * y1;
int rad = 30;
tft.fillSmoothRoundRect(x1, y1, w, h, rad, TFT_CYAN, TFT_BLACK);
// Wait forever
while (1) delay(100);
}
// =========================================================================
// Get coordinates of two ends of a line from r1 to r2, pivot at x,y, angle a
// =========================================================================
// Coordinates are returned to caller via the xp and yp pointers
#define DEG2RAD 0.0174532925
void getCoord(int16_t x, int16_t y, float *xp1, float *yp1, float *xp2, float *yp2, int16_t r1, int16_t r2, float a)
{
float sx = cos( (a - 90) * DEG2RAD);
float sy = sin( (a - 90) * DEG2RAD);
*xp1 = sx * r1 + x;
*yp1 = sy * r1 + y;
*xp2 = sx * r2 + x;
*yp2 = sy * r2 + y;
}
// =========================================================================
// Return a 16 bit rainbow colour
// =========================================================================
unsigned int rainbow(byte value)
{
// Value is expected to be in range 0-127
// The value is converted to a spectrum colour from 0 = blue through to 127 = red
byte red = 0; // Red is the top 5 bits of a 16 bit colour value
byte green = 0;// Green is the middle 6 bits
byte blue = 0; // Blue is the bottom 5 bits
byte quadrant = value / 32;
if (quadrant == 0) {
blue = 31;
green = 2 * (value % 32);
red = 0;
}
if (quadrant == 1) {
blue = 31 - (value % 32);
green = 63;
red = 0;
}
if (quadrant == 2) {
blue = 0;
green = 63;
red = value % 32;
}
if (quadrant == 3) {
blue = 0;
green = 63 - 2 * (value % 32);
red = 31;
}
return (red << 11) + (green << 5) + blue;
}
{
"name": "TFT_eSPI",
"version": "2.4.33",
"version": "2.4.34",
"keywords": "Arduino, tft, ePaper, display, Pico, RP2040, STM32, ESP8266, NodeMCU, ESP32, M5Stack, ILI9341, ST7735, ILI9163, S6D02A1, ILI9481, ILI9486, ILI9488, ST7789, RM68140, SSD1351, SSD1963, ILI9225, HX8357D",
"description": "A TFT and ePaper SPI graphics library with optimisation for Raspberry Pi Pico, ESP8266, ESP32 and STM32",
"repository":
......
name=TFT_eSPI
version=2.4.33
version=2.4.34
author=Bodmer
maintainer=Bodmer
sentence=TFT graphics library for Arduino processors with performance optimisation for RP2040, STM32, ESP8266 and ESP32
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment