-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixed several issues with drawing ellipses
I've replaced the ellipse drawing algorithms with implementations by Zingl Alois, originally written in 2012 and published under MIT license. This addresses several issues with the previously used implementations of pointsOnEllipse and ellipseRegion: * Ellipses no longer degrade when very large (near 2000 tiles in size). * No more gaps in the circle for some sizes. * Shape Fill tool can now actually draw circles of equal width and height and can draw both even and odd sized ellipses. The Shape Fill tool was adjusted such that drawing ellipses work in bounding-box mode by default. The Alt modifier now toggles to center + radius mode, which also applies when drawing rectangles. Closes #3775
- Loading branch information
Showing
6 changed files
with
115 additions
and
144 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,9 @@ | ||
/* | ||
* geometry.cpp | ||
* Copyright 2010-2011, Stefan Beller <[email protected]> | ||
* Copyright 2017, Benjamin Trotter <[email protected]> | ||
* Copyright 2020, Zingl Alois | ||
* Copyright 2017-2023, Thorbjørn Lindeijer <[email protected]> | ||
* | ||
* This file is part of Tiled. | ||
* | ||
|
@@ -26,142 +29,94 @@ namespace Tiled { | |
|
||
/** | ||
* Returns a lists of points on an ellipse. | ||
* (x0,y0) is the midpoint | ||
* (x1,y1) determines the radius. | ||
* (xm,ym) is the midpoint | ||
* (a,b) determines the radii. | ||
* | ||
* It is adapted from http://en.wikipedia.org/wiki/Midpoint_circle_algorithm | ||
* here is the original: http://homepage.smc.edu/kennedy_john/belipse.pdf | ||
* From "Bresenham Curve Rasterizing Algorithms". | ||
* | ||
* @version V20.15 april 2020 | ||
* @copyright MIT open-source license software | ||
* @url https://github.com/zingl/Bresenham | ||
* @author Zingl Alois | ||
*/ | ||
QVector<QPoint> pointsOnEllipse(int x0, int y0, int x1, int y1) | ||
QVector<QPoint> pointsOnEllipse(int xm, int ym, int a, int b) | ||
{ | ||
QVector<QPoint> ret; | ||
int x, y; | ||
int xChange, yChange; | ||
int ellipseError; | ||
int twoXSquare, twoYSquare; | ||
int stoppingX, stoppingY; | ||
int radiusX = x0 > x1 ? x0 - x1 : x1 - x0; | ||
int radiusY = y0 > y1 ? y0 - y1 : y1 - y0; | ||
|
||
if (radiusX == 0 && radiusY == 0) | ||
return ret; | ||
|
||
twoXSquare = 2 * radiusX * radiusX; | ||
twoYSquare = 2 * radiusY * radiusY; | ||
x = radiusX; | ||
y = 0; | ||
xChange = radiusY * radiusY * (1 - 2 * radiusX); | ||
yChange = radiusX * radiusX; | ||
ellipseError = 0; | ||
stoppingX = twoYSquare*radiusX; | ||
stoppingY = 0; | ||
while (stoppingX >= stoppingY) { | ||
ret += QPoint(x0 + x, y0 + y); | ||
ret += QPoint(x0 - x, y0 + y); | ||
ret += QPoint(x0 + x, y0 - y); | ||
ret += QPoint(x0 - x, y0 - y); | ||
y++; | ||
stoppingY += twoXSquare; | ||
ellipseError += yChange; | ||
yChange += twoXSquare; | ||
if ((2 * ellipseError + xChange) > 0) { | ||
x--; | ||
stoppingX -= twoYSquare; | ||
ellipseError += xChange; | ||
xChange += twoYSquare; | ||
} | ||
} | ||
x = 0; | ||
y = radiusY; | ||
xChange = radiusY * radiusY; | ||
yChange = radiusX * radiusX * (1 - 2 * radiusY); | ||
ellipseError = 0; | ||
stoppingX = 0; | ||
stoppingY = twoXSquare * radiusY; | ||
while (stoppingX <= stoppingY) { | ||
ret += QPoint(x0 + x, y0 + y); | ||
ret += QPoint(x0 - x, y0 + y); | ||
ret += QPoint(x0 + x, y0 - y); | ||
ret += QPoint(x0 - x, y0 - y); | ||
x++; | ||
stoppingX += twoYSquare; | ||
ellipseError += xChange; | ||
xChange += twoYSquare; | ||
if ((2 * ellipseError + yChange) > 0) { | ||
y--; | ||
stoppingY -= twoXSquare; | ||
ellipseError += yChange; | ||
yChange += twoXSquare; | ||
} | ||
|
||
long x = -a, y = 0; /* II. quadrant from bottom left to top right */ | ||
long e2 = b, dx = (1+2*x)*e2*e2; /* error increment */ | ||
long dy = x*x, err = dx+dy; /* error of 1.step */ | ||
|
||
do { | ||
ret += QPoint(xm-x, ym+y); /* I. Quadrant */ | ||
ret += QPoint(xm+x, ym+y); /* II. Quadrant */ | ||
ret += QPoint(xm+x, ym-y); /* III. Quadrant */ | ||
ret += QPoint(xm-x, ym-y); /* IV. Quadrant */ | ||
e2 = 2*err; | ||
if (e2 >= dx) { x++; err += dx += 2*(long)b*b; } /* x step */ | ||
if (e2 <= dy) { y++; err += dy += 2*(long)a*a; } /* y step */ | ||
} while (x <= 0); | ||
|
||
while (y++ < b) { /* too early stop for flat ellipses with a=1, */ | ||
ret += QPoint(xm, ym+y); /* -> finish tip of ellipse */ | ||
ret += QPoint(xm, ym-y); | ||
} | ||
|
||
return ret; | ||
} | ||
|
||
/** | ||
* returns an elliptical region centered at x0,y0 with radius determined by x1,y1 | ||
* Returns an elliptical region based on a rectangle given by x0,y0 (top-left) | ||
* and x1,y1 (bottom-right), inclusive. | ||
* | ||
* From "Bresenham Curve Rasterizing Algorithms", adjusted to output a filled | ||
* region instead of an outline. | ||
* | ||
* @version V20.15 april 2020 | ||
* @copyright MIT open-source license software | ||
* @url https://github.com/zingl/Bresenham | ||
* @author Zingl Alois | ||
*/ | ||
QRegion ellipseRegion(int x0, int y0, int x1, int y1) | ||
{ | ||
QRegion ret; | ||
int x, y; | ||
int xChange, yChange; | ||
int ellipseError; | ||
int twoXSquare, twoYSquare; | ||
int stoppingX, stoppingY; | ||
int radiusX = x0 > x1 ? x0 - x1 : x1 - x0; | ||
int radiusY = y0 > y1 ? y0 - y1 : y1 - y0; | ||
|
||
if (radiusX == 0 && radiusY == 0) | ||
return ret; | ||
|
||
twoXSquare = 2 * radiusX * radiusX; | ||
twoYSquare = 2 * radiusY * radiusY; | ||
x = radiusX; | ||
y = 0; | ||
xChange = radiusY * radiusY * (1 - 2 * radiusX); | ||
yChange = radiusX * radiusX; | ||
ellipseError = 0; | ||
stoppingX = twoYSquare*radiusX; | ||
stoppingY = 0; | ||
while (stoppingX >= stoppingY) { | ||
ret += QRect(-x, y, x * 2, 1); | ||
ret += QRect(-x, -y, x * 2, 1); | ||
y++; | ||
stoppingY += twoXSquare; | ||
ellipseError += yChange; | ||
yChange += twoXSquare; | ||
if ((2 * ellipseError + xChange) > 0) { | ||
x--; | ||
stoppingX -= twoYSquare; | ||
ellipseError += xChange; | ||
xChange += twoYSquare; | ||
} | ||
} | ||
x = 0; | ||
y = radiusY; | ||
xChange = radiusY * radiusY; | ||
yChange = radiusX * radiusX * (1 - 2 * radiusY); | ||
ellipseError = 0; | ||
stoppingX = 0; | ||
stoppingY = twoXSquare * radiusY; | ||
while (stoppingX <= stoppingY) { | ||
ret += QRect(-x, y, x * 2, 1); | ||
ret += QRect(-x, -y, x * 2, 1); | ||
x++; | ||
stoppingX += twoYSquare; | ||
ellipseError += xChange; | ||
xChange += twoYSquare; | ||
if ((2 * ellipseError + yChange) > 0) { | ||
y--; | ||
stoppingY -= twoXSquare; | ||
ellipseError += yChange; | ||
yChange += twoXSquare; | ||
} | ||
|
||
auto addRect = [&ret](int x0, int y0, int x1, int y1) { | ||
ret += QRect(QPoint(x0, y0), QPoint(x1, y1)); | ||
}; | ||
|
||
long a = abs(x1-x0), b = abs(y1-y0), b1 = b&1; /* diameter */ | ||
double dx = 4*(1.0-a)*b*b, dy = 4*(b1+1)*a*a; /* error increment */ | ||
double err = dx+dy+b1*a*a, e2; /* error of 1.step */ | ||
|
||
if (x0 > x1) { x0 = x1; x1 += a; } /* if called with swapped points */ | ||
if (y0 > y1) y0 = y1; /* .. exchange them */ | ||
y0 += (b+1)/2; y1 = y0-b1; /* starting pixel */ | ||
a = 8*a*a; b1 = 8*b*b; | ||
|
||
do { | ||
// (x1, y0) /* I. Quadrant */ | ||
// (x0, y0) /* II. Quadrant */ | ||
// (x0, y1) /* III. Quadrant */ | ||
// (x1, y1) /* IV. Quadrant */ | ||
|
||
addRect(x0, y0, x1, y0); /* Bottom half */ | ||
addRect(x0, y1, x1, y1); /* Top half */ | ||
|
||
e2 = 2*err; | ||
if (e2 <= dy) { y0++; y1--; err += dy += a; } /* y step */ | ||
if (e2 >= dx || 2*err > dy) { x0++; x1--; err += dx += b1; } /* x step */ | ||
} while (x0 <= x1); | ||
|
||
while (y0-y1 <= b) { /* too early stop of flat ellipses a=1 */ | ||
addRect(x0-1, y0, x1+1, y0); /* -> finish tip of ellipse */ | ||
addRect(x0-1, y1, x1+1, y1); | ||
y0++; | ||
y1--; | ||
} | ||
|
||
return ret.translated(x0, y0); | ||
} | ||
return ret; | ||
}; | ||
|
||
/** | ||
* Returns the lists of points on a line from (x0,y0) to (x1,y1). | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,8 @@ | ||
/* | ||
* geometry.h | ||
* Copyright 2010-2011, Stefan Beller <[email protected]> | ||
* Copyright 2017, Benjamin Trotter <[email protected]> | ||
* Copyright 2017-2023, Thorbjørn Lindeijer <[email protected]> | ||
* | ||
* This file is part of Tiled. | ||
* | ||
|
@@ -27,12 +29,15 @@ | |
|
||
namespace Tiled { | ||
|
||
QVector<QPoint> pointsOnEllipse(int x0, int y0, int x1, int y1); | ||
QVector<QPoint> pointsOnEllipse(int xm, int ym, int a, int b); | ||
QRegion ellipseRegion(int x0, int y0, int x1, int y1); | ||
QVector<QPoint> pointsOnLine(int x0, int y0, int x1, int y1, bool manhattan = false); | ||
|
||
inline QVector<QPoint> pointsOnEllipse(QPoint a, QPoint b) | ||
{ return pointsOnEllipse(a.x(), a.y(), b.x(), b.y()); } | ||
inline QVector<QPoint> pointsOnEllipse(QPoint center, int radiusX, int radiusY) | ||
{ return pointsOnEllipse(center.x(), center.y(), radiusX, radiusY); } | ||
|
||
inline QRegion ellipseRegion(QRect rect) | ||
{ return ellipseRegion(rect.left(), rect.top(), rect.right(), rect.bottom()); } | ||
|
||
inline QVector<QPoint> pointsOnLine(QPoint a, QPoint b, bool manhattan = false) | ||
{ return pointsOnLine(a.x(), a.y(), b.x(), b.y(), manhattan); } | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters