Fix polygon stroking with cosmetic pen

Fix a number of issues that caused polygons to not always be drawn
fully connected.

Ensures the original lastPixel is set when drawing closed polygons,
ensure we don't round away from the original starting point, and add
handling of edges that need to be rounded half a pixel sideways to line
up with endpoints.

Task-number: QTBUG-27053
Change-Id: Ib51ee5623a629996af51a0967096383f04e91e2f
Reviewed-by: Eirik Aavitsland <eirik.aavitsland@qt.io>
bb10
Allan Sandfeld Jensen 2017-07-04 17:24:04 +02:00
parent 03b4838cb5
commit d866617ea6
2 changed files with 151 additions and 13 deletions

View File

@ -304,8 +304,8 @@ void QCosmeticStroker::setup()
ymin = deviceRect.top() - 1;
ymax = deviceRect.bottom() + 2;
lastPixel.x = -1;
lastPixel.y = -1;
lastPixel.x = INT_MIN;
lastPixel.y = INT_MIN;
}
// returns true if the whole line gets clipped away
@ -325,11 +325,11 @@ bool QCosmeticStroker::clipLine(qreal &x1, qreal &y1, qreal &x2, qreal &y2)
x1 = xmax;
}
if (x2 < xmin) {
lastPixel.x = -1;
lastPixel.x = INT_MIN;
y2 += (y2 - y1)/(x2 - x1) * (xmin - x2);
x2 = xmin;
} else if (x2 > xmax) {
lastPixel.x = -1;
lastPixel.x = INT_MIN;
y2 += (y2 - y1)/(x2 - x1) * (xmax - x2);
x2 = xmax;
}
@ -346,11 +346,11 @@ bool QCosmeticStroker::clipLine(qreal &x1, qreal &y1, qreal &x2, qreal &y2)
y1 = ymax;
}
if (y2 < ymin) {
lastPixel.x = -1;
lastPixel.x = INT_MIN;
x2 += (x2 - x1)/(y2 - y1) * (ymin - y2);
y2 = ymin;
} else if (y2 > ymax) {
lastPixel.x = -1;
lastPixel.x = INT_MIN;
x2 += (x2 - x1)/(y2 - y1) * (ymax - y2);
y2 = ymax;
}
@ -358,7 +358,7 @@ bool QCosmeticStroker::clipLine(qreal &x1, qreal &y1, qreal &x2, qreal &y2)
return false;
clipped:
lastPixel.x = -1;
lastPixel.x = INT_MIN;
return true;
}
@ -374,7 +374,7 @@ void QCosmeticStroker::drawLine(const QPointF &p1, const QPointF &p2)
QPointF end = p2 * state->matrix;
patternOffset = state->lastPen.dashOffset()*64;
lastPixel.x = -1;
lastPixel.x = INT_MIN;
stroke(this, start.x(), start.y(), end.x(), end.y(), drawCaps ? CapBegin|CapEnd : 0);
@ -417,8 +417,8 @@ void QCosmeticStroker::calculateLastPoint(qreal rx1, qreal ry1, qreal rx2, qreal
// by calculating the direction and last pixel of the last segment in the contour.
// the info is then used to perform dropout control when drawing the first line segment
// of the contour
lastPixel.x = -1;
lastPixel.y = -1;
lastPixel.x = INT_MIN;
lastPixel.y = INT_MIN;
if (clipLine(rx1, ry1, rx2, ry2))
return;
@ -599,7 +599,11 @@ void QCosmeticStroker::drawPath(const QVectorPath &path)
bool closed = path.hasImplicitClose() || (points[0] == end[-2] && points[1] == end[-1]);
int caps = (!closed && drawCaps) ? CapBegin : NoCaps;
if (closed) {
QPointF p2 = QPointF(end[-2], end[-1]) * state->matrix;
QPointF p2;
if (points[0] == end[-2] && points[1] == end[-1] && path.elementCount() > 2)
p2 = QPointF(end[-4], end[-3]) * state->matrix;
else
p2 = QPointF(end[-2], end[-1]) * state->matrix;
calculateLastPoint(p2.x(), p2.y(), p.x(), p.y());
}
@ -770,6 +774,11 @@ static bool drawLine(QCosmeticStroker *stroker, qreal rx1, qreal ry1, qreal rx2,
int ys = (y2 + 32) >> 6;
int round = (xinc > 0) ? 32 : 0;
// If capAdjust made us round away from what calculateLastPoint gave us,
// round back the other way so we start and end on the right point.
if ((caps & QCosmeticStroker::CapBegin) && stroker->lastPixel.y == y + 1)
y++;
if (y != ys) {
x += ((y * (1<<6)) + round - y1) * xinc >> 6;
@ -783,7 +792,7 @@ static bool drawLine(QCosmeticStroker *stroker, qreal rx1, qreal ry1, qreal rx2,
qSwap(first, last);
bool axisAligned = qAbs(xinc) < (1 << 14);
if (stroker->lastPixel.x >= 0) {
if (stroker->lastPixel.x > INT_MIN) {
if (first.x == stroker->lastPixel.x &&
first.y == stroker->lastPixel.y) {
// remove duplicated pixel
@ -805,6 +814,14 @@ static bool drawLine(QCosmeticStroker *stroker, qreal rx1, qreal ry1, qreal rx2,
--y;
x -= xinc;
}
} else if (stroker->lastDir == dir &&
((qAbs(stroker->lastPixel.x - first.x) <= 1 &&
qAbs(stroker->lastPixel.y - first.y) > 1))) {
x += xinc >> 1;
if (swapped)
last.x = (x >> 16);
else
last.x = (x + (ys - y - 1)*xinc) >> 16;
}
}
stroker->lastDir = dir;
@ -847,6 +864,11 @@ static bool drawLine(QCosmeticStroker *stroker, qreal rx1, qreal ry1, qreal rx2,
int xs = (x2 + 32) >> 6;
int round = (yinc > 0) ? 32 : 0;
// If capAdjust made us round away from what calculateLastPoint gave us,
// round back the other way so we start and end on the right point.
if ((caps & QCosmeticStroker::CapBegin) && stroker->lastPixel.x == x + 1)
x++;
if (x != xs) {
y += ((x * (1<<6)) + round - x1) * yinc >> 6;
@ -860,7 +882,7 @@ static bool drawLine(QCosmeticStroker *stroker, qreal rx1, qreal ry1, qreal rx2,
qSwap(first, last);
bool axisAligned = qAbs(yinc) < (1 << 14);
if (stroker->lastPixel.x >= 0) {
if (stroker->lastPixel.x > INT_MIN) {
if (first.x == stroker->lastPixel.x && first.y == stroker->lastPixel.y) {
// remove duplicated pixel
if (swapped) {
@ -881,6 +903,14 @@ static bool drawLine(QCosmeticStroker *stroker, qreal rx1, qreal ry1, qreal rx2,
--x;
y -= yinc;
}
} else if (stroker->lastDir == dir &&
((qAbs(stroker->lastPixel.x - first.x) <= 1 &&
qAbs(stroker->lastPixel.y - first.y) > 1))) {
y += yinc >> 1;
if (swapped)
last.y = (y >> 16);
else
last.y = (y + (xs - x - 1)*yinc) >> 16;
}
}
stroker->lastDir = dir;

View File

@ -303,6 +303,8 @@ private slots:
void blendNullRGB32();
void toRGB64();
void fillPolygon();
private:
void fillData();
void setPenColor(QPainter& p);
@ -5191,6 +5193,112 @@ void tst_QPainter::toRGB64()
}
}
void tst_QPainter::fillPolygon()
{
QImage image(50, 50, QImage::Format_RGB32);
image.fill(Qt::white);
QPainter painter(&image);
QBrush brush(Qt::black, Qt::SolidPattern);
painter.setBrush(brush);
QPen pen(Qt::red, 0, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin);
painter.setPen(pen);
const QPoint diamondpoints[5] = {
QPoint(-15, 0),
QPoint(0, -15),
QPoint(15, 0),
QPoint(0, 15),
QPoint(-15, 0)
};
enum { Outside1, Border1, Inside, Border2, Outside2 } state;
for (int i = 0; i < 16 ; i++)
{
for (int j = 0; j < 16 ; j++)
{
image.fill(Qt::white);
painter.resetTransform();
painter.translate(25 + i/16., 25 + j/16.);
painter.drawPolygon(diamondpoints, 5);
for (int x = 0; x < 50; x++) {
state = Outside1;
for (int y = 0; y < 50; y++) {
QRgb c = image.pixel(x, y);
switch (state) {
case Outside1:
if (c == QColor(Qt::red).rgb())
state = Border1;
else
QCOMPARE(c, QColor(Qt::white).rgb());
break;
case Border1:
if (c == QColor(Qt::black).rgb())
state = Inside;
else if (c == QColor(Qt::white).rgb())
state = Outside2;
else
QCOMPARE(c, QColor(Qt::red).rgb());
break;
case Inside:
if (c == QColor(Qt::red).rgb())
state = Border2;
else
QCOMPARE(c, QColor(Qt::black).rgb());
break;
case Border2:
if (c == QColor(Qt::white).rgb())
state = Outside2;
else
QCOMPARE(c, QColor(Qt::red).rgb());
break;
case Outside2:
QCOMPARE(c, QColor(Qt::white).rgb());
}
}
}
for (int y = 0; y < 50; y++) {
state = Outside1;
for (int x = 0; x < 50; x++) {
QRgb c = image.pixel(x, y);
switch (state) {
case Outside1:
if (c == QColor(Qt::red).rgb())
state = Border1;
else
QCOMPARE(c, QColor(Qt::white).rgb());
break;
case Border1:
if (c == QColor(Qt::black).rgb())
state = Inside;
else if (c == QColor(Qt::white).rgb())
state = Outside2;
else
QCOMPARE(c, QColor(Qt::red).rgb());
break;
case Inside:
if (c == QColor(Qt::red).rgb())
state = Border2;
else
QCOMPARE(c, QColor(Qt::black).rgb());
break;
case Border2:
if (c == QColor(Qt::white).rgb())
state = Outside2;
else
QCOMPARE(c, QColor(Qt::red).rgb());
break;
case Outside2:
QCOMPARE(c, QColor(Qt::white).rgb());
}
}
}
}
}
}
QTEST_MAIN(tst_QPainter)
#include "tst_qpainter.moc"