Skip to content

Commit ee06f3e

Browse files
committed
Doc: Explain how Qt computes arcs and ellipses
The angle arguments of QPainterPath::arcTo(), QPainterPath::arcMoveTo() and the QPainter arc functions are eccentric angles: they parameterize the ellipse fitted into the bounding rectangle rather than measuring a direction from its center. In general the two coincide only for a square bounding rectangle. On a 400 by 100 rectangle, an angle of 45 degrees yields a point roughly 14 degrees above the horizontal as seen from the center. Separately, arcs and ellipses are approximated with cubic Bezier curves scaled by QT_PATH_KAPPA rather than evaluated trigonometrically, so the resulting points sit slightly off where qSin() and qCos() would place them. For a circle the offset stays below 0.1% of the radius. Neither property was documented, which repeatedly surprises users who build paths from trigonometry and then compare the result against Qt's. Document both in a new "Arcs and Ellipses" section of the QPainterPath overview, reference it from the affected functions, and note the approximation next to the existing stroker limitation in the QPainter overview. The eccentric angle behavior was judged intentional in QTBUG-2644 in 2009, where adding a doc hint was agreed on but never done. Fixes: QTBUG-149880 Pick-to: 6.12 6.11 6.8 Change-Id: Ib1dcb6c6fe27533ef582acc38b15195a87f71269 Reviewed-by: Eirik Aavitsland <eirik.aavitsland@qt.io>
1 parent dd55ea9 commit ee06f3e

2 files changed

Lines changed: 63 additions & 7 deletions

File tree

src/gui/painting/qpainter.cpp

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,6 +1307,14 @@ void QPainterPrivate::updateState(QPainterState *newState)
13071307
Another workaround is to convert the paths to polygons first and then draw the
13081308
polygons instead.
13091309
1310+
Qt likewise approximates arcs and ellipses with cubic Bezier curves instead
1311+
of evaluating them trigonometrically, so points on an arc are slightly off
1312+
their true positions. Related to this, the angles that \l{drawArc()},
1313+
\l{drawPie()}, and \l{drawChord()} take are eccentric angles: they measure
1314+
the direction from the center of the bounding rectangle only when that
1315+
rectangle is square. See \l{QPainterPath#Arcs and Ellipses}{Arcs and
1316+
Ellipses} for details on both.
1317+
13101318
\section1 Performance
13111319
13121320
QPainter is a rich framework that allows developers to do a great
@@ -4170,7 +4178,10 @@ void QPainter::drawEllipse(const QRect &r)
41704178
a degree, i.e. a full circle equals 5760 (16 * 360). Positive
41714179
values for the angles mean counter-clockwise while negative values
41724180
mean the clockwise direction. Zero degrees is at the 3 o'clock
4173-
position.
4181+
position. If \a rectangle is not square, the angles are eccentric
4182+
angles and do not measure the direction from the center of the
4183+
rectangle, as described in \l{QPainterPath#Arcs and Ellipses}{Arcs
4184+
and Ellipses}.
41744185
41754186
\table 100%
41764187
\row
@@ -4235,7 +4246,10 @@ void QPainter::drawArc(const QRectF &r, int a, int alen)
42354246
degree, i.e. a full circle equals 5760 (16 * 360). Positive values
42364247
for the angles mean counter-clockwise while negative values mean
42374248
the clockwise direction. Zero degrees is at the 3 o'clock
4238-
position.
4249+
position. If \a rectangle is not square, the angles are eccentric
4250+
angles and do not measure the direction from the center of the
4251+
rectangle, as described in \l{QPainterPath#Arcs and Ellipses}{Arcs
4252+
and Ellipses}.
42394253
42404254
\table 100%
42414255
\row
@@ -4306,7 +4320,10 @@ void QPainter::drawPie(const QRectF &r, int a, int alen)
43064320
degree, i.e. a full circle equals 5760 (16 * 360). Positive values
43074321
for the angles mean counter-clockwise while negative values mean
43084322
the clockwise direction. Zero degrees is at the 3 o'clock
4309-
position.
4323+
position. If \a rectangle is not square, the angles are eccentric
4324+
angles and do not measure the direction from the center of the
4325+
rectangle, as described in \l{QPainterPath#Arcs and Ellipses}{Arcs
4326+
and Ellipses}.
43104327
43114328
\table 100%
43124329
\row

src/gui/painting/qpainterpath.cpp

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,35 @@ static void qt_debug_path(const QPainterPath &path)
225225
using the fillRule() function, and altered using the setFillRule()
226226
function.
227227
228+
\section1 Arcs and Ellipses
229+
230+
The angle arguments of \l{arcTo()}, \l{arcMoveTo()},
231+
\l{QPainter::drawArc()}, \l{QPainter::drawPie()}, and
232+
\l{QPainter::drawChord()} are \e{eccentric} angles. An eccentric
233+
angle parameterizes the ellipse that fits into the bounding
234+
rectangle; it does not measure the direction from the center of that
235+
rectangle to the resulting point. In general the two coincide only
236+
when the bounding rectangle is square. On a 400 by 100 rectangle, for
237+
example, an angle of 45 degrees yields a point that lies roughly 14
238+
degrees above the horizontal as seen from the center.
239+
240+
To place a point at a given direction from the center instead,
241+
compute that point yourself and pass it to \l{moveTo()} or
242+
\l{lineTo()}. Alternatively, keep using these functions and convert
243+
the direction: on a bounding rectangle of the given \c width and
244+
\c height, the direction \c theta, in radians, corresponds to the
245+
eccentric angle
246+
\c{qRadiansToDegrees(qAtan2(width * qSin(theta), height * qCos(theta)))}.
247+
248+
Because a painter path stores only lines and Bezier segments, Qt
249+
approximates arcs and ellipses with cubic Bezier curves instead of
250+
evaluating them trigonometrically. For a circle, the approximation
251+
places points less than 0.1% of the radius away from their true
252+
positions. Code that compares a point from \l{arcMoveTo()} against
253+
one computed with \l{<QtMath>::}{qSin()} and \l{<QtMath>::}{qCos()}
254+
must therefore allow for a tolerance instead of testing for
255+
equality.
256+
228257
\section1 QPainterPath Information
229258
230259
The QPainterPath class provides a collection of functions that
@@ -895,7 +924,10 @@ void QPainterPath::quadTo(const QPointF &c, const QPointF &e)
895924
counter-clockwise.
896925
897926
Angles are specified in degrees. Clockwise arcs can be specified
898-
using negative angles.
927+
using negative angles. If \a rectangle is not square, the angles are
928+
eccentric angles and do not measure the direction from the center of
929+
the rectangle, as described in \l{QPainterPath#Arcs and
930+
Ellipses}{Arcs and Ellipses}.
899931
900932
Note that this function connects the starting point of the arc to
901933
the current position if they are not already connected. After the
@@ -967,9 +999,13 @@ void QPainterPath::arcTo(const QRectF &rect, qreal startAngle, qreal sweepLength
967999
rectangle at \a angle.
9681000
9691001
Angles are specified in degrees. Clockwise arcs can be specified
970-
using negative angles.
1002+
using negative angles. If \a rectangle is not square, \a angle is an
1003+
eccentric angle and does not measure the direction from the center of
1004+
the rectangle, as described in \l{QPainterPath#Arcs and
1005+
Ellipses}{Arcs and Ellipses}.
9711006
972-
\sa moveTo(), arcTo()
1007+
\sa moveTo(), arcTo(), {QPainterPath#Arcs and Ellipses}{Arcs and
1008+
Ellipses}
9731009
*/
9741010

9751011
void QPainterPath::arcMoveTo(const QRectF &rect, qreal angle)
@@ -1102,7 +1138,10 @@ void QPainterPath::addPolygon(const QPolygonF &polygon)
11021138
and adds it to the painter path as a closed subpath.
11031139
11041140
The ellipse is composed of a clockwise curve, starting and
1105-
finishing at zero degrees (the 3 o'clock position).
1141+
finishing at zero degrees (the 3 o'clock position). The curve is a
1142+
cubic Bezier approximation of the ellipse, not an exact
1143+
representation of it, as described in \l{QPainterPath#Arcs and
1144+
Ellipses}{Arcs and Ellipses}.
11061145
11071146
\table 100%
11081147
\row

0 commit comments

Comments
 (0)