The Adafruit_GFX library draws a filled circle as a sequence of vertical lines. It is not that difficult to use their equations to calculate each line in the previous circle, find any lines in the new circle that occupy the same vertical column of the display, and only erase the pixels that fall outside the new circle. That tends to minimize the flicker except for the edges of the circle where pixels are either being erased or added.
Unfortunately the slow SPI speed of an UNO causes noticeable shearing of the image when a large circle moves rapidly.
#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"
#include <Wire.h>
// For the Adafruit shield, these are the default.
#define TFT_DC 9
#define TFT_CS 10
// Use hardware SPI (on Uno, #13, #12, #11) and the above for CS/DC
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);
struct _line {
int16_t y;
int16_t h;
};
int xPrev;
int yPrev;
void setup() {
Serial.begin(115200);
Serial.println(F("ILI9341 Test!"));
tft.begin();
tft.fillScreen(ILI9341_BLACK);
int x = 50;
int y = 50;
int r = 30;
xPrev = x;
yPrev = y;
int xIncrement = 1;
int yIncrement = 1;
while (true) {
moveCircle(x, y, r, ILI9341_WHITE, xPrev, yPrev, r, ILI9341_BLACK);
//tft.drawCircle(xPrev, yPrev, r, ILI9341_BLACK);
//tft.drawCircle(x, y, r, ILI9341_WHITE);
xPrev = x;
yPrev = y;
x += xIncrement;
y += yIncrement;
if (x >= (tft.width() - r)) {
xIncrement = -xIncrement;
}
if (x <= r) {
xIncrement = -xIncrement;
}
if (y >= (tft.height() - r)) {
yIncrement = -yIncrement;
}
if (y <= r) {
yIncrement = -yIncrement;
}
}
}
void loop(void) {
}
/**************************************************************************/
/*!
@brief check to determine if a vertical line segment is in the same
vertical column as a circle
@param x0 Center-point x coordinate
@param y0 Center-point y coordinate
@param r Radius of circle
@param xTest x coordinate of vertical line
@param line y coordinate and height of vertical line
*/
/**************************************************************************/
bool checkFillCircle(int16_t x0, int16_t y0, int16_t r,
int16_t xTest, _line &line) {
bool found = false;
// if ((xTest < x0 - r) || (xTest > x0 + r) /*|| (line.y > (y0 + r)) || ((line.y + line.h) < (y0 - r))*/){
// found = false;
// } else {
if (x0 == xTest) {
line.y = y0 - r;
line.h = 2 * r + 1;
found = true;
} else {
int16_t f = 1 - r;
int16_t ddF_x = 1;
int16_t ddF_y = -2 * r;
int16_t x = 0;
int16_t y = r;
int16_t px = x;
int16_t py = y;
while (!found && (x < y)) {
if (f >= 0) {
y--;
ddF_y += 2;
f += ddF_y;
}
x++;
ddF_x += 2;
f += ddF_x;
// These checks avoid double-drawing certain lines, important
// for the SSD1306 library which has an INVERT drawing mode.
if (x < (y + 1)) {
if (((x0 + x) == xTest) || ((x0 - x) == xTest)) {
line.y = y0 - y;
line.h = 2 * y + 1;
found = true;
}
}
if (y != py) {
if (((x0 + py) == xTest) || ((x0 - py) == xTest)) {
line.y = y0 - px;
line.h = 2 * px + 1;
found = true;
}
py = y;
}
px = x;
}
//}
}
return found;
}
/**************************************************************************/
/*!
@brief erase portion of line that lies outside a circle
@param x0 Center-point x coordinate of circle
@param y0 Center-point y coordinate of circle
@param r0 Radius of circle
@param xD x coordinate of vertical line
@param yLine y coordinate of vertical line
@param hLine length of vertical line
@param bgColor 16-bit 5-6-5 Color of vertical line
*/
/**************************************************************************/
void eraseLine(int16_t x0, int16_t y0, int16_t r0,
int16_t xD, int16_t yLine, int16_t hLine, uint16_t bgColor) {
_line fgLine = {0, 0};
_line bgLine = {yLine, hLine};
if (checkFillCircle(x0, y0, r0, xD, fgLine)) {
if (bgLine.y < fgLine.y) {
if ((bgLine.y + bgLine.h) < fgLine.y) {
tft.writeFastVLine(xD, bgLine.y, bgLine.h, bgColor);
}
else if ((bgLine.y + bgLine.h) <= (fgLine.y + fgLine.h)) {
tft.writeFastVLine(xD, bgLine.y, fgLine.y - bgLine.y, bgColor);
}
else {
tft.writeFastVLine(xD, bgLine.y, fgLine.y - bgLine.y, bgColor);
tft.writeFastVLine(xD, fgLine.y + fgLine.h, (bgLine.y + bgLine.h) - (fgLine.y + fgLine.h), bgColor);
}
}
if (bgLine.y > fgLine.y) {
if (bgLine.y > (fgLine.y + fgLine.h)) {
tft.writeFastVLine(xD, bgLine.y, bgLine.h, bgColor);
} else {
if ((bgLine.y + bgLine.h) > (fgLine.y + fgLine.h)) {
tft.writeFastVLine(xD, fgLine.y + fgLine.h, bgLine.h - (fgLine.y + fgLine.h - bgLine.y), bgColor);
}
}
}
if (bgLine.y == fgLine.y) {
if (bgLine.h > fgLine.h) {
tft.writeFastVLine(xD, bgLine.y + fgLine.h , bgLine.h - fgLine.h, bgColor);
}
}
}
else {
tft.writeFastVLine(xD, bgLine.y, bgLine.h, bgColor);
}
}
/**************************************************************************/
/*!
@brief draw a filled circle after erasing the previous circle
only the portion of the previous circle that lies outside the
new circle will be erased
@param x0 Center-point x coordinate of new circle
@param y0 Center-point y coordinate of new circle
@param r0 Radius of new circle
@param fgColor 16-bit 5-6-5 Color of new circle
@param x1 Center-point x coordinate of previous circle
@param y1 Center-point y coordinate of previous circle
@param r1 Radius of previous circle
@param bgColor 16-bit 5-6-5 Color to overwrite previous circle
*/
/**************************************************************************/
void moveCircle(int16_t x0, int16_t y0, int16_t r0, uint16_t fgColor,
int16_t x1, int16_t y1, int16_t r1, uint16_t bgColor) {
int16_t f;
int16_t ddF_x;
int16_t ddF_y;
int16_t x;
int16_t y;
int16_t px;
int16_t py;
tft.startWrite();
//erase the background circle that lies outside the foreground circle
eraseLine(x0, y0, r0, x1, y1 - r1, 2 * r1 + 1, bgColor);
f = 1 - r1;
ddF_x = 1;
ddF_y = -2 * r1;
x = 0;
y = r1;
px = x;
py = y;
while (x < y) {
if (f >= 0) {
y--;
ddF_y += 2;
f += ddF_y;
}
x++;
ddF_x += 2;
f += ddF_x;
// These checks avoid double-drawing certain lines, important
// for the SSD1306 library which has an INVERT drawing mode.
if (x < (y + 1)) {
eraseLine(x0, y0, r0, x1 + x, y1 - y, 2 * y + 1, bgColor);
eraseLine(x0, y0, r0, x1 - x, y1 - y, 2 * y + 1, bgColor);
}
if (y != py) {
eraseLine(x0, y0, r0, x1 + py, y1 - px, 2 * px + 1, bgColor);
eraseLine(x0, y0, r0, x1 - py, y1 - px, 2 * px + 1, bgColor);
py = y;
}
px = x;
}
//draw the foreground circle
tft.writeFastVLine(x0, y0 - r0, 2 * r0 + 1, fgColor);
f = 1 - r0;
ddF_x = 1;
ddF_y = -2 * r0;
x = 0;
y = r0;
px = x;
py = y;
while (x < y) {
if (f >= 0) {
y--;
ddF_y += 2;
f += ddF_y;
}
x++;
ddF_x += 2;
f += ddF_x;
// These checks avoid double-drawing certain lines, important
// for the SSD1306 library which has an INVERT drawing mode.
if (x < (y + 1)) {
tft.writeFastVLine(x0 + x, y0 - y, 2 * y + 1, fgColor);
tft.writeFastVLine(x0 - x, y0 - y, 2 * y + 1, fgColor);
}
if (y != py) {
tft.writeFastVLine(x0 + py, y0 - px, 2 * px + 1, fgColor);
tft.writeFastVLine(x0 - py, y0 - px, 2 * px + 1, fgColor);
py = y;
}
px = x;
}
tft.endWrite();
}