Painter path stroking: fix capping of beziers ending in tight turns

For some overly tight beziers where the start or end point and the
next control point are closer than the pen width, the stroker's
shifting algorithm will produce a start/end tangent pointing in the
opposite direction from what is expected, for one of the sides. This
would break the square and round capping logic. Fix by detecting the
situation in the capping function and reversing the tangent when
necessary.

Change-Id: I48f4f017403d7b289b0483dd2b3a7ff1bbd0cf2a
Reviewed-by: Lars Knoll <lars.knoll@qt.io>
bb10
Eirik Aavitsland 2019-01-17 15:50:37 +01:00
parent e96641d881
commit 66c3a71e91
2 changed files with 41 additions and 14 deletions

View File

@ -455,12 +455,12 @@ void QStroker::joinPoints(qfixed focal_x, qfixed focal_y, const QLineF &nextLine
return;
}
#endif
QLineF prevLine(qt_fixed_to_real(m_back2X), qt_fixed_to_real(m_back2Y),
qt_fixed_to_real(m_back1X), qt_fixed_to_real(m_back1Y));
QPointF isect;
QLineF::IntersectType type = prevLine.intersect(nextLine, &isect);
if (join == FlatJoin) {
QLineF prevLine(qt_fixed_to_real(m_back2X), qt_fixed_to_real(m_back2Y),
qt_fixed_to_real(m_back1X), qt_fixed_to_real(m_back1Y));
QPointF isect;
QLineF::IntersectType type = prevLine.intersect(nextLine, &isect);
QLineF shortCut(prevLine.p2(), nextLine.p1());
qreal angle = shortCut.angleTo(prevLine);
if (type == QLineF::BoundedIntersection || (angle > 90 && !qFuzzyCompare(angle, (qreal)90))) {
@ -472,12 +472,6 @@ void QStroker::joinPoints(qfixed focal_x, qfixed focal_y, const QLineF &nextLine
qt_real_to_fixed(nextLine.y1()));
} else {
QLineF prevLine(qt_fixed_to_real(m_back2X), qt_fixed_to_real(m_back2Y),
qt_fixed_to_real(m_back1X), qt_fixed_to_real(m_back1Y));
QPointF isect;
QLineF::IntersectType type = prevLine.intersect(nextLine, &isect);
if (join == MiterJoin) {
qreal appliedMiterLimit = qt_fixed_to_real(m_strokeWidth * m_miterLimit);
@ -512,7 +506,11 @@ void QStroker::joinPoints(qfixed focal_x, qfixed focal_y, const QLineF &nextLine
qfixed offset = m_strokeWidth / 2;
QLineF l1(prevLine);
l1.translate(l1.dx(), l1.dy());
qreal dp = QPointF::dotProduct(QPointF(prevLine.dx(), prevLine.dy()), QPointF(nextLine.dx(), nextLine.dy()));
if (dp > 0) // same direction, means that prevLine is from a bezier that has been "reversed" by shifting
l1 = QLineF(prevLine.p2(), prevLine.p1());
else
l1.translate(l1.dx(), l1.dy());
l1.setLength(qt_fixed_to_real(offset));
QLineF l2(nextLine.p2(), nextLine.p1());
l2.translate(l2.dx(), l2.dy());
@ -570,7 +568,11 @@ void QStroker::joinPoints(qfixed focal_x, qfixed focal_y, const QLineF &nextLine
// first control line
QLineF l1 = prevLine;
l1.translate(l1.dx(), l1.dy());
qreal dp = QPointF::dotProduct(QPointF(prevLine.dx(), prevLine.dy()), QPointF(nextLine.dx(), nextLine.dy()));
if (dp > 0) // same direction, means that prevLine is from a bezier that has been "reversed" by shifting
l1 = QLineF(prevLine.p2(), prevLine.p1());
else
l1.translate(l1.dx(), l1.dy());
l1.setLength(QT_PATH_KAPPA * offset);
// second control line, find through normal between prevLine and focal.
@ -705,7 +707,6 @@ template <class Iterator> bool qt_stroke_side(Iterator *it,
QPointF(qt_fixed_to_real(e.x), qt_fixed_to_real(e.y)),
QPointF(qt_fixed_to_real(cp2.x), qt_fixed_to_real(cp2.y)),
QPointF(qt_fixed_to_real(ep.x), qt_fixed_to_real(ep.y)));
int count = bezier.shifted(offsetCurves,
MAX_OFFSET,
offset,

View File

@ -7,4 +7,30 @@ path_cubicTo degenerate 3427.0918499999997948 3872.1318999999994048 4729.4590867
scale 0.05 0.05
translate -2500 -3000
setPen black 800
drawPath degenerate
drawPath degenerate
resetMatrix
path_moveTo revbez 0 20
path_cubicTo revbez 0 0 120 0 120 -20
path_moveTo revbez 0 80
path_cubicTo revbez 0 100 120 100 120 120
translate 50 250
setPen blue 40 solidline flatcap
drawPath revbez
setPen red 0
drawPath revbez
translate 200 0
setPen blue 40 solidline squarecap
drawPath revbez
setPen red 0
drawPath revbez
translate 200 0
setPen blue 40 solidline roundcap
drawPath revbez
setPen red 0
drawPath revbez