1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtGui module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qcosmeticstroker_p.h"
41 #include "private/qpainterpath_p.h"
42 #include "private/qrgba64_p.h"
43 #include <qdebug.h>
44 
45 QT_BEGIN_NAMESPACE
46 
47 #if 0
48 inline QString capString(int caps)
49 {
50     QString str;
51     if (caps & QCosmeticStroker::CapBegin) {
52         str += "CapBegin ";
53     }
54     if (caps & QCosmeticStroker::CapEnd) {
55         str += "CapEnd ";
56     }
57     return str;
58 }
59 #endif
60 
61 #define toF26Dot6(x) ((int)((x)*64.))
62 
sourceOver(uint d,uint color)63 static inline uint sourceOver(uint d, uint color)
64 {
65     return color + BYTE_MUL(d, qAlpha(~color));
66 }
67 
F16Dot16FixedDiv(int x,int y)68 inline static int F16Dot16FixedDiv(int x, int y)
69 {
70     if (qAbs(x) > 0x7fff)
71         return qlonglong(x) * (1<<16) / y;
72     return x * (1<<16) / y;
73 }
74 
75 typedef void (*DrawPixel)(QCosmeticStroker *stroker, int x, int y, int coverage);
76 
77 namespace {
78 
79 struct Dasher {
80     QCosmeticStroker *stroker;
81     int *pattern;
82     int offset;
83     int dashIndex;
84     int dashOn;
85 
Dasher__anon1adfad160111::Dasher86     Dasher(QCosmeticStroker *s, bool reverse, int start, int stop)
87         : stroker(s)
88     {
89         int delta = stop - start;
90         if (reverse) {
91             pattern = stroker->reversePattern;
92             offset = stroker->patternLength - stroker->patternOffset - delta - ((start & 63) - 32);
93             dashOn = 0;
94         } else {
95             pattern = stroker->pattern;
96             offset = stroker->patternOffset - ((start & 63) - 32);
97             dashOn = 1;
98         }
99         offset %= stroker->patternLength;
100         if (offset < 0)
101             offset += stroker->patternLength;
102 
103         dashIndex = 0;
104         while (dashIndex < stroker->patternSize - 1 && offset>= pattern[dashIndex])
105             ++dashIndex;
106 
107 //        qDebug() << "   dasher" << offset/64. << reverse << dashIndex;
108         stroker->patternOffset += delta;
109         stroker->patternOffset %= stroker->patternLength;
110     }
111 
on__anon1adfad160111::Dasher112     bool on() const {
113         return (dashIndex + dashOn) & 1;
114     }
adjust__anon1adfad160111::Dasher115     void adjust() {
116         offset += 64;
117         if (offset >= pattern[dashIndex]) {
118             ++dashIndex;
119             dashIndex %= stroker->patternSize;
120         }
121         offset %= stroker->patternLength;
122 //        qDebug() << "dasher.adjust" << offset/64. << dashIndex;
123     }
124 };
125 
126 struct NoDasher {
NoDasher__anon1adfad160111::NoDasher127     NoDasher(QCosmeticStroker *, bool, int, int) {}
on__anon1adfad160111::NoDasher128     bool on() const { return true; }
adjust__anon1adfad160111::NoDasher129     void adjust(int = 0) {}
130 };
131 
132 };
133 
134 /*
135  * The return value is the result of the clipLine() call performed at the start
136  * of each of the two functions, aka "false" means completely outside the devices
137  * rect.
138  */
139 template<DrawPixel drawPixel, class Dasher>
140 static bool drawLine(QCosmeticStroker *stroker, qreal x1, qreal y1, qreal x2, qreal y2, int caps);
141 template<DrawPixel drawPixel, class Dasher>
142 static bool drawLineAA(QCosmeticStroker *stroker, qreal x1, qreal y1, qreal x2, qreal y2, int caps);
143 
drawPixel(QCosmeticStroker * stroker,int x,int y,int coverage)144 inline void drawPixel(QCosmeticStroker *stroker, int x, int y, int coverage)
145 {
146     const QRect &cl = stroker->clip;
147     if (x < cl.x() || x > cl.right() || y < cl.y() || y > cl.bottom())
148         return;
149 
150     if (stroker->current_span > 0) {
151         const int lastx = stroker->spans[stroker->current_span-1].x + stroker->spans[stroker->current_span-1].len ;
152         const int lasty = stroker->spans[stroker->current_span-1].y;
153 
154         if (stroker->current_span == QCosmeticStroker::NSPANS || y < lasty || (y == lasty && x < lastx)) {
155             stroker->blend(stroker->current_span, stroker->spans, &stroker->state->penData);
156             stroker->current_span = 0;
157         }
158     }
159 
160     stroker->spans[stroker->current_span].x = ushort(x);
161     stroker->spans[stroker->current_span].len = 1;
162     stroker->spans[stroker->current_span].y = y;
163     stroker->spans[stroker->current_span].coverage = coverage*stroker->opacity >> 8;
164     ++stroker->current_span;
165 }
166 
drawPixelARGB32(QCosmeticStroker * stroker,int x,int y,int coverage)167 inline void drawPixelARGB32(QCosmeticStroker *stroker, int x, int y, int coverage)
168 {
169     const QRect &cl = stroker->clip;
170     if (x < cl.x() || x > cl.right() || y < cl.y() || y > cl.bottom())
171         return;
172 
173     int offset = x + stroker->ppl*y;
174     uint c = BYTE_MUL(stroker->color, coverage);
175     stroker->pixels[offset] = sourceOver(stroker->pixels[offset], c);
176 }
177 
drawPixelARGB32Opaque(QCosmeticStroker * stroker,int x,int y,int)178 inline void drawPixelARGB32Opaque(QCosmeticStroker *stroker, int x, int y, int)
179 {
180     const QRect &cl = stroker->clip;
181     if (x < cl.x() || x > cl.right() || y < cl.y() || y > cl.bottom())
182         return;
183 
184     int offset = x + stroker->ppl*y;
185     stroker->pixels[offset] = sourceOver(stroker->pixels[offset], stroker->color);
186 }
187 
188 enum StrokeSelection {
189     Aliased = 0,
190     AntiAliased = 1,
191     Solid = 0,
192     Dashed = 2,
193     RegularDraw = 0,
194     FastDraw = 4
195 };
196 
strokeLine(int strokeSelection)197 static StrokeLine strokeLine(int strokeSelection)
198 {
199     StrokeLine stroke;
200 
201     switch (strokeSelection) {
202     case Aliased|Solid|RegularDraw:
203         stroke = &QT_PREPEND_NAMESPACE(drawLine)<drawPixel, NoDasher>;
204         break;
205     case Aliased|Solid|FastDraw:
206         stroke = &QT_PREPEND_NAMESPACE(drawLine)<drawPixelARGB32Opaque, NoDasher>;
207         break;
208     case Aliased|Dashed|RegularDraw:
209         stroke = &QT_PREPEND_NAMESPACE(drawLine)<drawPixel, Dasher>;
210         break;
211     case Aliased|Dashed|FastDraw:
212         stroke = &QT_PREPEND_NAMESPACE(drawLine)<drawPixelARGB32Opaque, Dasher>;
213         break;
214     case AntiAliased|Solid|RegularDraw:
215         stroke = &QT_PREPEND_NAMESPACE(drawLineAA)<drawPixel, NoDasher>;
216         break;
217     case AntiAliased|Solid|FastDraw:
218         stroke = &QT_PREPEND_NAMESPACE(drawLineAA)<drawPixelARGB32, NoDasher>;
219         break;
220     case AntiAliased|Dashed|RegularDraw:
221         stroke = &QT_PREPEND_NAMESPACE(drawLineAA)<drawPixel, Dasher>;
222         break;
223     case AntiAliased|Dashed|FastDraw:
224         stroke = &QT_PREPEND_NAMESPACE(drawLineAA)<drawPixelARGB32, Dasher>;
225         break;
226     default:
227         Q_ASSERT(false);
228         stroke = nullptr;
229     }
230     return stroke;
231 }
232 
setup()233 void QCosmeticStroker::setup()
234 {
235     blend = state->penData.blend;
236     if (state->clip && state->clip->enabled && state->clip->hasRectClip && !state->clip->clipRect.isEmpty()) {
237         clip &= state->clip->clipRect;
238         blend = state->penData.unclipped_blend;
239     }
240 
241     int strokeSelection = 0;
242     if (blend == state->penData.unclipped_blend
243         && state->penData.type == QSpanData::Solid
244         && (state->penData.rasterBuffer->format == QImage::Format_ARGB32_Premultiplied
245             || state->penData.rasterBuffer->format == QImage::Format_RGB32)
246         && state->compositionMode() == QPainter::CompositionMode_SourceOver)
247         strokeSelection |= FastDraw;
248 
249     if (state->renderHints & QPainter::Antialiasing)
250         strokeSelection |= AntiAliased;
251 
252     const QVector<qreal> &penPattern = state->lastPen.dashPattern();
253     if (penPattern.isEmpty() || penPattern.size() > 1024) {
254         Q_ASSERT(!pattern && !reversePattern);
255         pattern = nullptr;
256         reversePattern = nullptr;
257         patternLength = 0;
258         patternSize = 0;
259     } else {
260         pattern = (int *)malloc(penPattern.size()*sizeof(int));
261         reversePattern = (int *)malloc(penPattern.size()*sizeof(int));
262         patternSize = penPattern.size();
263 
264         patternLength = 0;
265         for (int i = 0; i < patternSize; ++i) {
266             patternLength += (int)qBound(1., penPattern.at(i) * 64, 65536.);
267             pattern[i] = patternLength;
268         }
269         patternLength = 0;
270         for (int i = 0; i < patternSize; ++i) {
271             patternLength += (int)qBound(1., penPattern.at(patternSize - 1 - i) * 64, 65536.);
272             reversePattern[i] = patternLength;
273         }
274         strokeSelection |= Dashed;
275 //        qDebug() << "setup: size=" << patternSize << "length=" << patternLength/64.;
276     }
277 
278     stroke = strokeLine(strokeSelection);
279 
280     qreal width = state->lastPen.widthF();
281     if (width == 0)
282         opacity = 256;
283     else if (qt_pen_is_cosmetic(state->lastPen, state->renderHints))
284         opacity = (int) 256*width;
285     else
286         opacity = (int) 256*width*state->txscale;
287     opacity = qBound(0, opacity, 256);
288 
289     drawCaps = state->lastPen.capStyle() != Qt::FlatCap;
290 
291     if (strokeSelection & FastDraw) {
292         color = multiplyAlpha256(state->penData.solidColor, opacity).toArgb32();
293         QRasterBuffer *buffer = state->penData.rasterBuffer;
294         pixels = (uint *)buffer->buffer();
295         ppl = buffer->stride<quint32>();
296     }
297 
298     // line drawing produces different results with different clips, so
299     // we need to clip consistently when painting to the same device
300 
301     // setup FP clip bounds
302     xmin = deviceRect.left() - 1;
303     xmax = deviceRect.right() + 2;
304     ymin = deviceRect.top() - 1;
305     ymax = deviceRect.bottom() + 2;
306 
307     lastPixel.x = INT_MIN;
308     lastPixel.y = INT_MIN;
309 }
310 
311 // returns true if the whole line gets clipped away
clipLine(qreal & x1,qreal & y1,qreal & x2,qreal & y2)312 bool QCosmeticStroker::clipLine(qreal &x1, qreal &y1, qreal &x2, qreal &y2)
313 {
314     if (!qIsFinite(x1) || !qIsFinite(y1) || !qIsFinite(x2) || !qIsFinite(y2))
315         return true;
316     // basic/rough clipping is done in floating point coordinates to avoid
317     // integer overflow problems.
318     if (x1 < xmin) {
319         if (x2 <= xmin)
320             goto clipped;
321         y1 += (y2 - y1)/(x2 - x1) * (xmin - x1);
322         x1 = xmin;
323     } else if (x1 > xmax) {
324         if (x2 >= xmax)
325             goto clipped;
326         y1 += (y2 - y1)/(x2 - x1) * (xmax - x1);
327         x1 = xmax;
328     }
329     if (x2 < xmin) {
330         lastPixel.x = INT_MIN;
331         y2 += (y2 - y1)/(x2 - x1) * (xmin - x2);
332         x2 = xmin;
333     } else if (x2 > xmax) {
334         lastPixel.x = INT_MIN;
335         y2 += (y2 - y1)/(x2 - x1) * (xmax - x2);
336         x2 = xmax;
337     }
338 
339     if (y1 < ymin) {
340         if (y2 <= ymin)
341             goto clipped;
342         x1 += (x2 - x1)/(y2 - y1) * (ymin - y1);
343         y1 = ymin;
344     } else if (y1 > ymax) {
345         if (y2 >= ymax)
346             goto clipped;
347         x1 += (x2 - x1)/(y2 - y1) * (ymax - y1);
348         y1 = ymax;
349     }
350     if (y2 < ymin) {
351         lastPixel.x = INT_MIN;
352         x2 += (x2 - x1)/(y2 - y1) * (ymin - y2);
353         y2 = ymin;
354     } else if (y2 > ymax) {
355         lastPixel.x = INT_MIN;
356         x2 += (x2 - x1)/(y2 - y1) * (ymax - y2);
357         y2 = ymax;
358     }
359 
360     return false;
361 
362   clipped:
363     lastPixel.x = INT_MIN;
364     return true;
365 }
366 
367 
drawLine(const QPointF & p1,const QPointF & p2)368 void QCosmeticStroker::drawLine(const QPointF &p1, const QPointF &p2)
369 {
370     if (p1 == p2) {
371         drawPoints(&p1, 1);
372         return;
373     }
374 
375     QPointF start = p1 * state->matrix;
376     QPointF end = p2 * state->matrix;
377 
378     patternOffset = state->lastPen.dashOffset()*64;
379     lastPixel.x = INT_MIN;
380     lastPixel.y = INT_MIN;
381 
382     stroke(this, start.x(), start.y(), end.x(), end.y(), drawCaps ? CapBegin|CapEnd : 0);
383 
384     blend(current_span, spans, &state->penData);
385     current_span = 0;
386 }
387 
drawPoints(const QPoint * points,int num)388 void QCosmeticStroker::drawPoints(const QPoint *points, int num)
389 {
390     const QPoint *end = points + num;
391     while (points < end) {
392         QPointF p = QPointF(*points) * state->matrix;
393         drawPixel(this, qRound(p.x()), qRound(p.y()), 255);
394         ++points;
395     }
396 
397     blend(current_span, spans, &state->penData);
398     current_span = 0;
399 }
400 
drawPoints(const QPointF * points,int num)401 void QCosmeticStroker::drawPoints(const QPointF *points, int num)
402 {
403     const QPointF *end = points + num;
404     while (points < end) {
405         QPointF p = (*points) * state->matrix;
406         drawPixel(this, qRound(p.x()), qRound(p.y()), 255);
407         ++points;
408     }
409 
410     blend(current_span, spans, &state->penData);
411     current_span = 0;
412 }
413 
calculateLastPoint(qreal rx1,qreal ry1,qreal rx2,qreal ry2)414 void QCosmeticStroker::calculateLastPoint(qreal rx1, qreal ry1, qreal rx2, qreal ry2)
415 {
416     // this is basically the same code as used in the aliased stroke method,
417     // but it only determines the direction and last point of a line
418     //
419     // This is being used to have proper dropout control for closed contours
420     // by calculating the direction and last pixel of the last segment in the contour.
421     // the info is then used to perform dropout control when drawing the first line segment
422     // of the contour
423     lastPixel.x = INT_MIN;
424     lastPixel.y = INT_MIN;
425 
426     if (clipLine(rx1, ry1, rx2, ry2))
427         return;
428 
429     const int half = legacyRounding ? 31 : 0;
430     int x1 = toF26Dot6(rx1) + half;
431     int y1 = toF26Dot6(ry1) + half;
432     int x2 = toF26Dot6(rx2) + half;
433     int y2 = toF26Dot6(ry2) + half;
434 
435     int dx = qAbs(x2 - x1);
436     int dy = qAbs(y2 - y1);
437 
438     if (dx < dy) {
439         // vertical
440         bool swapped = false;
441         if (y1 > y2) {
442             swapped = true;
443             qSwap(y1, y2);
444             qSwap(x1, x2);
445         }
446         int xinc = F16Dot16FixedDiv(x2 - x1, y2 - y1);
447         int x = x1 * (1<<10);
448 
449         int y = (y1 + 32) >> 6;
450         int ys = (y2 + 32) >> 6;
451 
452         int round = (xinc > 0) ? 32 : 0;
453         if (y != ys) {
454             x += ((y * (1<<6)) + round - y1) * xinc >> 6;
455 
456             if (swapped) {
457                 lastPixel.x = x >> 16;
458                 lastPixel.y = y;
459                 lastDir = QCosmeticStroker::BottomToTop;
460             } else {
461                 lastPixel.x = (x + (ys - y - 1)*xinc) >> 16;
462                 lastPixel.y = ys - 1;
463                 lastDir = QCosmeticStroker::TopToBottom;
464             }
465             lastAxisAligned = qAbs(xinc) < (1 << 14);
466         }
467     } else {
468         // horizontal
469         if (!dx)
470             return;
471 
472         bool swapped = false;
473         if (x1 > x2) {
474             swapped = true;
475             qSwap(x1, x2);
476             qSwap(y1, y2);
477         }
478         int yinc = F16Dot16FixedDiv(y2 - y1, x2 - x1);
479         int y = y1 * (1 << 10);
480 
481         int x = (x1 + 32) >> 6;
482         int xs = (x2 + 32) >> 6;
483 
484         int round = (yinc > 0) ? 32 : 0;
485         if (x != xs) {
486             y += ((x * (1<<6)) + round - x1) * yinc >> 6;
487 
488             if (swapped) {
489                 lastPixel.x = x;
490                 lastPixel.y = y >> 16;
491                 lastDir = QCosmeticStroker::RightToLeft;
492             } else {
493                 lastPixel.x = xs - 1;
494                 lastPixel.y = (y + (xs - x - 1)*yinc) >> 16;
495                 lastDir = QCosmeticStroker::LeftToRight;
496             }
497             lastAxisAligned = qAbs(yinc) < (1 << 14);
498         }
499     }
500 //    qDebug() << "   moveTo: setting last pixel to x/y dir" << lastPixel.x << lastPixel.y << lastDir;
501 }
502 
subPath(const QPainterPath::ElementType * t,const QPainterPath::ElementType * end,const qreal * points,bool * closed)503 static inline const QPainterPath::ElementType *subPath(const QPainterPath::ElementType *t, const QPainterPath::ElementType *end,
504                                                  const qreal *points, bool *closed)
505 {
506     const QPainterPath::ElementType *start = t;
507     ++t;
508 
509     // find out if the subpath is closed
510     while (t < end) {
511         if (*t == QPainterPath::MoveToElement)
512             break;
513         ++t;
514     }
515 
516     int offset = t - start - 1;
517 //    qDebug() << "subpath" << offset << points[0] << points[1] << points[2*offset] << points[2*offset+1];
518     *closed = (points[0] == points[2*offset] && points[1] == points[2*offset + 1]);
519 
520     return t;
521 }
522 
drawPath(const QVectorPath & path)523 void QCosmeticStroker::drawPath(const QVectorPath &path)
524 {
525 //    qDebug() << ">>>> drawpath" << path.convertToPainterPath()
526 //             << "antialiasing:" << (bool)(state->renderHints & QPainter::Antialiasing) << " implicit close:" << path.hasImplicitClose();
527     if (path.isEmpty())
528         return;
529 
530     const qreal *points = path.points();
531     const QPainterPath::ElementType *type = path.elements();
532 
533     if (type) {
534         const QPainterPath::ElementType *end = type + path.elementCount();
535 
536         while (type < end) {
537             Q_ASSERT(type == path.elements() || *type == QPainterPath::MoveToElement);
538 
539             QPointF p = QPointF(points[0], points[1]) * state->matrix;
540             patternOffset = state->lastPen.dashOffset()*64;
541             lastPixel.x = INT_MIN;
542             lastPixel.y = INT_MIN;
543 
544             bool closed;
545             const QPainterPath::ElementType *e = subPath(type, end, points, &closed);
546             if (closed) {
547                 const qreal *p = points + 2*(e-type);
548                 QPointF p1 = QPointF(p[-4], p[-3]) * state->matrix;
549                 QPointF p2 = QPointF(p[-2], p[-1]) * state->matrix;
550                 calculateLastPoint(p1.x(), p1.y(), p2.x(), p2.y());
551             }
552             int caps = (!closed && drawCaps) ? CapBegin : NoCaps;
553 //            qDebug() << "closed =" << closed << capString(caps);
554 
555             points += 2;
556             ++type;
557 
558             while (type < e) {
559                 QPointF p2 = QPointF(points[0], points[1]) * state->matrix;
560                 switch (*type) {
561                 case QPainterPath::MoveToElement:
562                     Q_ASSERT(!"Logic error");
563                     break;
564 
565                 case QPainterPath::LineToElement:
566                     if (!closed && drawCaps && type == e - 1)
567                         caps |= CapEnd;
568                     stroke(this, p.x(), p.y(), p2.x(), p2.y(), caps);
569                     p = p2;
570                     points += 2;
571                     ++type;
572                     break;
573 
574                 case QPainterPath::CurveToElement: {
575                     if (!closed && drawCaps && type == e - 3)
576                         caps |= CapEnd;
577                     QPointF p3 = QPointF(points[2], points[3]) * state->matrix;
578                     QPointF p4 = QPointF(points[4], points[5]) * state->matrix;
579                     renderCubic(p, p2, p3, p4, caps);
580                     p = p4;
581                     type += 3;
582                     points += 6;
583                     break;
584                 }
585                 case QPainterPath::CurveToDataElement:
586                     Q_ASSERT(!"QPainterPath::toSubpathPolygons(), bad element type");
587                     break;
588                 }
589                 caps = NoCaps;
590             }
591         }
592     } else { // !type, simple polygon
593         QPointF p = QPointF(points[0], points[1]) * state->matrix;
594         QPointF movedTo = p;
595         patternOffset = state->lastPen.dashOffset()*64;
596         lastPixel.x = INT_MIN;
597         lastPixel.y = INT_MIN;
598 
599         const qreal *begin = points;
600         const qreal *end = points + 2*path.elementCount();
601         // handle closed path case
602         bool closed = path.hasImplicitClose() || (points[0] == end[-2] && points[1] == end[-1]);
603         int caps = (!closed && drawCaps) ? CapBegin : NoCaps;
604         if (closed) {
605             QPointF p2;
606             if (points[0] == end[-2] && points[1] == end[-1] && path.elementCount() > 2)
607                 p2 = QPointF(end[-4], end[-3]) * state->matrix;
608             else
609                 p2 = QPointF(end[-2], end[-1]) * state->matrix;
610             calculateLastPoint(p2.x(), p2.y(), p.x(), p.y());
611         }
612 
613         bool fastPenAliased = (state->flags.fast_pen && !state->flags.antialiased);
614         points += 2;
615         while (points < end) {
616             QPointF p2 = QPointF(points[0], points[1]) * state->matrix;
617 
618             if (!closed && drawCaps && points == end - 2)
619                 caps |= CapEnd;
620 
621             bool moveNextStart = stroke(this, p.x(), p.y(), p2.x(), p2.y(), caps);
622 
623             /* fix for gaps in polylines with fastpen and aliased in a sequence
624                of points with small distances: if current point p2 has been dropped
625                out, keep last non dropped point p.
626 
627                However, if the line was completely outside the devicerect, we
628                still need to update p to avoid drawing the line after this one from
629                a bad starting position.
630             */
631             if (!fastPenAliased || moveNextStart || points == begin + 2 || points == end - 2)
632                 p = p2;
633             points += 2;
634             caps = NoCaps;
635         }
636         if (path.hasImplicitClose())
637             stroke(this, p.x(), p.y(), movedTo.x(), movedTo.y(), NoCaps);
638     }
639 
640 
641     blend(current_span, spans, &state->penData);
642     current_span = 0;
643 }
644 
renderCubic(const QPointF & p1,const QPointF & p2,const QPointF & p3,const QPointF & p4,int caps)645 void QCosmeticStroker::renderCubic(const QPointF &p1, const QPointF &p2, const QPointF &p3, const QPointF &p4, int caps)
646 {
647 //    qDebug() << ">>>> renderCubic" << p1 << p2 << p3 << p4 << capString(caps);
648     const int maxSubDivisions = 6;
649     PointF points[3*maxSubDivisions + 4];
650 
651     points[3].x = p1.x();
652     points[3].y = p1.y();
653     points[2].x = p2.x();
654     points[2].y = p2.y();
655     points[1].x = p3.x();
656     points[1].y = p3.y();
657     points[0].x = p4.x();
658     points[0].y = p4.y();
659 
660     PointF *p = points;
661     int level = maxSubDivisions;
662 
663     renderCubicSubdivision(p, level, caps);
664 }
665 
splitCubic(QCosmeticStroker::PointF * points)666 static void splitCubic(QCosmeticStroker::PointF *points)
667 {
668     const qreal half = .5;
669     qreal  a, b, c, d;
670 
671     points[6].x = points[3].x;
672     c = points[1].x;
673     d = points[2].x;
674     points[1].x = a = ( points[0].x + c ) * half;
675     points[5].x = b = ( points[3].x + d ) * half;
676     c = ( c + d ) * half;
677     points[2].x = a = ( a + c ) * half;
678     points[4].x = b = ( b + c ) * half;
679     points[3].x = ( a + b ) * half;
680 
681     points[6].y = points[3].y;
682     c = points[1].y;
683     d = points[2].y;
684     points[1].y = a = ( points[0].y + c ) * half;
685     points[5].y = b = ( points[3].y + d ) * half;
686     c = ( c + d ) * half;
687     points[2].y = a = ( a + c ) * half;
688     points[4].y = b = ( b + c ) * half;
689     points[3].y = ( a + b ) * half;
690 }
691 
renderCubicSubdivision(QCosmeticStroker::PointF * points,int level,int caps)692 void QCosmeticStroker::renderCubicSubdivision(QCosmeticStroker::PointF *points, int level, int caps)
693 {
694     if (level) {
695         qreal dx = points[3].x - points[0].x;
696         qreal dy = points[3].y - points[0].y;
697         qreal len = ((qreal).25) * (qAbs(dx) + qAbs(dy));
698 
699         if (qAbs(dx * (points[0].y - points[2].y) - dy * (points[0].x - points[2].x)) >= len ||
700             qAbs(dx * (points[0].y - points[1].y) - dy * (points[0].x - points[1].x)) >= len) {
701             splitCubic(points);
702 
703             --level;
704             renderCubicSubdivision(points + 3, level, caps & CapBegin);
705             renderCubicSubdivision(points, level, caps & CapEnd);
706             return;
707         }
708     }
709 
710     stroke(this, points[3].x, points[3].y, points[0].x, points[0].y, caps);
711 }
712 
swapCaps(int caps)713 static inline int swapCaps(int caps)
714 {
715     return ((caps & QCosmeticStroker::CapBegin) << 1) |
716            ((caps & QCosmeticStroker::CapEnd) >> 1);
717 }
718 
719 // adjust line by half a pixel
capAdjust(int caps,int & x1,int & x2,int & y,int yinc)720 static inline void capAdjust(int caps, int &x1, int &x2, int &y, int yinc)
721 {
722     if (caps & QCosmeticStroker::CapBegin) {
723         x1 -= 32;
724         y -= yinc >> 1;
725     }
726     if (caps & QCosmeticStroker::CapEnd) {
727         x2 += 32;
728     }
729 }
730 
731 /*
732   The hard part about this is dropout control and avoiding douple drawing of points when
733   the drawing shifts from horizontal to vertical or back.
734   */
735 template<DrawPixel drawPixel, class Dasher>
drawLine(QCosmeticStroker * stroker,qreal rx1,qreal ry1,qreal rx2,qreal ry2,int caps)736 static bool drawLine(QCosmeticStroker *stroker, qreal rx1, qreal ry1, qreal rx2, qreal ry2, int caps)
737 {
738     bool didDraw = qAbs(rx2 - rx1) + qAbs(ry2 - ry1) >= 1.0;
739 
740     if (stroker->clipLine(rx1, ry1, rx2, ry2))
741         return true;
742 
743     const int half = stroker->legacyRounding ? 31 : 0;
744     int x1 = toF26Dot6(rx1) + half;
745     int y1 = toF26Dot6(ry1) + half;
746     int x2 = toF26Dot6(rx2) + half;
747     int y2 = toF26Dot6(ry2) + half;
748 
749     int dx = qAbs(x2 - x1);
750     int dy = qAbs(y2 - y1);
751 
752     QCosmeticStroker::Point last = stroker->lastPixel;
753 
754 //    qDebug() << "stroke" << x1/64. << y1/64. << x2/64. << y2/64.;
755 
756     if (dx < dy) {
757         // vertical
758         QCosmeticStroker::Direction dir = QCosmeticStroker::TopToBottom;
759 
760         bool swapped = false;
761         if (y1 > y2) {
762             swapped = true;
763             qSwap(y1, y2);
764             qSwap(x1, x2);
765             caps = swapCaps(caps);
766             dir = QCosmeticStroker::BottomToTop;
767         }
768         int xinc = F16Dot16FixedDiv(x2 - x1, y2 - y1);
769         int x = x1 * (1<<10);
770 
771         if ((stroker->lastDir ^ QCosmeticStroker::VerticalMask) == dir)
772             caps |= swapped ? QCosmeticStroker::CapEnd : QCosmeticStroker::CapBegin;
773 
774         capAdjust(caps, y1, y2, x, xinc);
775 
776         int y = (y1 + 32) >> 6;
777         int ys = (y2 + 32) >> 6;
778         int round = (xinc > 0) ? 32 : 0;
779 
780         // If capAdjust made us round away from what calculateLastPoint gave us,
781         // round back the other way so we start and end on the right point.
782         if ((caps & QCosmeticStroker::CapBegin) && stroker->lastPixel.y == y + 1)
783            y++;
784 
785         if (y != ys) {
786             x += ((y * (1<<6)) + round - y1) * xinc >> 6;
787 
788             // calculate first and last pixel and perform dropout control
789             QCosmeticStroker::Point first;
790             first.x = x >> 16;
791             first.y = y;
792             last.x = (x + (ys - y - 1)*xinc) >> 16;
793             last.y = ys - 1;
794             if (swapped)
795                 qSwap(first, last);
796 
797             bool axisAligned = qAbs(xinc) < (1 << 14);
798             if (stroker->lastPixel.x > INT_MIN) {
799                 if (first.x == stroker->lastPixel.x &&
800                     first.y == stroker->lastPixel.y) {
801                     // remove duplicated pixel
802                     if (swapped) {
803                         --ys;
804                     } else {
805                         ++y;
806                         x += xinc;
807                     }
808                 } else if (stroker->lastDir != dir &&
809                            (((axisAligned && stroker->lastAxisAligned) &&
810                              stroker->lastPixel.x != first.x && stroker->lastPixel.y != first.y) ||
811                             (qAbs(stroker->lastPixel.x - first.x) > 1 ||
812                              qAbs(stroker->lastPixel.y - first.y) > 1))) {
813                     // have a missing pixel, insert it
814                     if (swapped) {
815                         ++ys;
816                     } else {
817                         --y;
818                         x -= xinc;
819                     }
820                 } else if (stroker->lastDir == dir &&
821                            ((qAbs(stroker->lastPixel.x - first.x) <= 1 &&
822                              qAbs(stroker->lastPixel.y - first.y) > 1))) {
823                     x += xinc >> 1;
824                     if (swapped)
825                         last.x = (x >> 16);
826                     else
827                         last.x = (x + (ys - y - 1)*xinc) >> 16;
828                 }
829             }
830             stroker->lastDir = dir;
831             stroker->lastAxisAligned = axisAligned;
832 
833             Dasher dasher(stroker, swapped, y * (1<<6), ys * (1<<6));
834 
835             do {
836                 if (dasher.on())
837                     drawPixel(stroker, x >> 16, y, 255);
838                 dasher.adjust();
839                 x += xinc;
840             } while (++y < ys);
841             didDraw = true;
842         }
843     } else {
844         // horizontal
845         if (!dx)
846             return true;
847 
848         QCosmeticStroker::Direction dir = QCosmeticStroker::LeftToRight;
849 
850         bool swapped = false;
851         if (x1 > x2) {
852             swapped = true;
853             qSwap(x1, x2);
854             qSwap(y1, y2);
855             caps = swapCaps(caps);
856             dir = QCosmeticStroker::RightToLeft;
857         }
858         int yinc = F16Dot16FixedDiv(y2 - y1, x2 - x1);
859         int y = y1 * (1<<10);
860 
861         if ((stroker->lastDir ^ QCosmeticStroker::HorizontalMask) == dir)
862             caps |= swapped ? QCosmeticStroker::CapEnd : QCosmeticStroker::CapBegin;
863 
864         capAdjust(caps, x1, x2, y, yinc);
865 
866         int x = (x1 + 32) >> 6;
867         int xs = (x2 + 32) >> 6;
868         int round = (yinc > 0) ? 32 : 0;
869 
870         // If capAdjust made us round away from what calculateLastPoint gave us,
871         // round back the other way so we start and end on the right point.
872         if ((caps & QCosmeticStroker::CapBegin) && stroker->lastPixel.x == x + 1)
873             x++;
874 
875         if (x != xs) {
876             y += ((x * (1<<6)) + round - x1) * yinc >> 6;
877 
878             // calculate first and last pixel to perform dropout control
879             QCosmeticStroker::Point first;
880             first.x = x;
881             first.y = y >> 16;
882             last.x = xs - 1;
883             last.y = (y + (xs - x - 1)*yinc) >> 16;
884             if (swapped)
885                 qSwap(first, last);
886 
887             bool axisAligned = qAbs(yinc) < (1 << 14);
888             if (stroker->lastPixel.x > INT_MIN) {
889                 if (first.x == stroker->lastPixel.x && first.y == stroker->lastPixel.y) {
890                     // remove duplicated pixel
891                     if (swapped) {
892                         --xs;
893                     } else {
894                         ++x;
895                         y += yinc;
896                     }
897                 } else if (stroker->lastDir != dir &&
898                            (((axisAligned && stroker->lastAxisAligned) &&
899                              stroker->lastPixel.x != first.x && stroker->lastPixel.y != first.y) ||
900                             (qAbs(stroker->lastPixel.x - first.x) > 1 ||
901                              qAbs(stroker->lastPixel.y - first.y) > 1))) {
902                     // have a missing pixel, insert it
903                     if (swapped) {
904                         ++xs;
905                     } else {
906                         --x;
907                         y -= yinc;
908                     }
909                 } else if (stroker->lastDir == dir &&
910                            ((qAbs(stroker->lastPixel.x - first.x) <= 1 &&
911                              qAbs(stroker->lastPixel.y - first.y) > 1))) {
912                     y += yinc >> 1;
913                     if (swapped)
914                         last.y = (y >> 16);
915                     else
916                         last.y = (y + (xs - x - 1)*yinc) >> 16;
917                 }
918             }
919             stroker->lastDir = dir;
920             stroker->lastAxisAligned = axisAligned;
921 
922             Dasher dasher(stroker, swapped, x * (1<<6), xs * (1<<6));
923 
924             do {
925                 if (dasher.on())
926                     drawPixel(stroker, x, y >> 16, 255);
927                 dasher.adjust();
928                 y += yinc;
929             } while (++x < xs);
930             didDraw = true;
931         }
932     }
933     stroker->lastPixel = last;
934     return didDraw;
935 }
936 
937 
938 template<DrawPixel drawPixel, class Dasher>
drawLineAA(QCosmeticStroker * stroker,qreal rx1,qreal ry1,qreal rx2,qreal ry2,int caps)939 static bool drawLineAA(QCosmeticStroker *stroker, qreal rx1, qreal ry1, qreal rx2, qreal ry2, int caps)
940 {
941     if (stroker->clipLine(rx1, ry1, rx2, ry2))
942         return true;
943 
944     int x1 = toF26Dot6(rx1);
945     int y1 = toF26Dot6(ry1);
946     int x2 = toF26Dot6(rx2);
947     int y2 = toF26Dot6(ry2);
948 
949     int dx = x2 - x1;
950     int dy = y2 - y1;
951 
952     if (qAbs(dx) < qAbs(dy)) {
953         // vertical
954 
955         int xinc = F16Dot16FixedDiv(dx, dy);
956 
957         bool swapped = false;
958         if (y1 > y2) {
959             qSwap(y1, y2);
960             qSwap(x1, x2);
961             swapped = true;
962             caps = swapCaps(caps);
963         }
964 
965         int x = (x1 - 32) * (1<<10);
966         x -= ( ((y1 & 63) - 32)  * xinc ) >> 6;
967 
968         capAdjust(caps, y1, y2, x, xinc);
969 
970         Dasher dasher(stroker, swapped, y1, y2);
971 
972         int y = y1 >> 6;
973         int ys = y2 >> 6;
974 
975         int alphaStart, alphaEnd;
976         if (y == ys) {
977             alphaStart = y2 - y1;
978             Q_ASSERT(alphaStart >= 0 && alphaStart < 64);
979             alphaEnd = 0;
980         } else {
981             alphaStart = 64 - (y1 & 63);
982             alphaEnd = (y2 & 63);
983         }
984 //        qDebug() << "vertical" << x1/64. << y1/64. << x2/64. << y2/64.;
985 //        qDebug() << "          x=" << x << "dx=" << dx << "xi=" << (x>>16) << "xsi=" << ((x+(ys-y)*dx)>>16) << "y=" << y << "ys=" << ys;
986 
987         // draw first pixel
988         if (dasher.on()) {
989             uint alpha = (quint8)(x >> 8);
990             drawPixel(stroker, x>>16, y, (255-alpha) * alphaStart >> 6);
991             drawPixel(stroker, (x>>16) + 1, y, alpha * alphaStart >> 6);
992         }
993         dasher.adjust();
994         x += xinc;
995         ++y;
996         if (y < ys) {
997             do {
998                 if (dasher.on()) {
999                     uint alpha = (quint8)(x >> 8);
1000                     drawPixel(stroker, x>>16, y, (255-alpha));
1001                     drawPixel(stroker, (x>>16) + 1, y, alpha);
1002                 }
1003                 dasher.adjust();
1004                 x += xinc;
1005             } while (++y < ys);
1006         }
1007         // draw last pixel
1008         if (alphaEnd && dasher.on()) {
1009             uint alpha = (quint8)(x >> 8);
1010             drawPixel(stroker, x>>16, y, (255-alpha) * alphaEnd >> 6);
1011             drawPixel(stroker, (x>>16) + 1, y, alpha * alphaEnd >> 6);
1012         }
1013     } else {
1014         // horizontal
1015         if (!dx)
1016             return true;
1017 
1018         int yinc = F16Dot16FixedDiv(dy, dx);
1019 
1020         bool swapped = false;
1021         if (x1 > x2) {
1022             qSwap(x1, x2);
1023             qSwap(y1, y2);
1024             swapped = true;
1025             caps = swapCaps(caps);
1026         }
1027 
1028         int y = (y1 - 32) * (1<<10);
1029         y -= ( ((x1 & 63) - 32)  * yinc ) >> 6;
1030 
1031         capAdjust(caps, x1, x2, y, yinc);
1032 
1033         Dasher dasher(stroker, swapped, x1, x2);
1034 
1035         int x = x1 >> 6;
1036         int xs = x2 >> 6;
1037 
1038 //        qDebug() << "horizontal" << x1/64. << y1/64. << x2/64. << y2/64.;
1039 //        qDebug() << "          y=" << y << "dy=" << dy << "x=" << x << "xs=" << xs << "yi=" << (y>>16) << "ysi=" << ((y+(xs-x)*dy)>>16);
1040         int alphaStart, alphaEnd;
1041         if (x == xs) {
1042             alphaStart = x2 - x1;
1043             Q_ASSERT(alphaStart >= 0 && alphaStart < 64);
1044             alphaEnd = 0;
1045         } else {
1046             alphaStart = 64 - (x1 & 63);
1047             alphaEnd = (x2 & 63);
1048         }
1049 
1050         // draw first pixel
1051         if (dasher.on()) {
1052             uint alpha = (quint8)(y >> 8);
1053             drawPixel(stroker, x, y>>16, (255-alpha) * alphaStart >> 6);
1054             drawPixel(stroker, x, (y>>16) + 1, alpha * alphaStart >> 6);
1055         }
1056         dasher.adjust();
1057         y += yinc;
1058         ++x;
1059         // draw line
1060         if (x < xs) {
1061             do {
1062                 if (dasher.on()) {
1063                     uint alpha = (quint8)(y >> 8);
1064                     drawPixel(stroker, x, y>>16, (255-alpha));
1065                     drawPixel(stroker, x, (y>>16) + 1, alpha);
1066                 }
1067                 dasher.adjust();
1068                 y += yinc;
1069             } while (++x < xs);
1070         }
1071         // draw last pixel
1072         if (alphaEnd && dasher.on()) {
1073             uint alpha = (quint8)(y >> 8);
1074             drawPixel(stroker, x, y>>16, (255-alpha) * alphaEnd >> 6);
1075             drawPixel(stroker, x, (y>>16) + 1, alpha * alphaEnd >> 6);
1076         }
1077     }
1078     return true;
1079 }
1080 
1081 QT_END_NAMESPACE
1082