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
45QT_BEGIN_NAMESPACE
46
47#if 0
48inline 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
63static inline uint sourceOver(uint d, uint color)
64{
65 return color + BYTE_MUL(x: d, a: qAlpha(rgb: ~color));
66}
67
68inline static int F16Dot16FixedDiv(int x, int y)
69{
70 if (qAbs(t: x) > 0x7fff)
71 return qlonglong(x) * (1<<16) / y;
72 return x * (1<<16) / y;
73}
74
75typedef void (*DrawPixel)(QCosmeticStroker *stroker, int x, int y, int coverage);
76
77namespace {
78
79struct Dasher {
80 QCosmeticStroker *stroker;
81 int *pattern;
82 int offset;
83 int dashIndex;
84 int dashOn;
85
86 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
112 bool on() const {
113 return (dashIndex + dashOn) & 1;
114 }
115 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
126struct NoDasher {
127 NoDasher(QCosmeticStroker *, bool, int, int) {}
128 bool on() const { return true; }
129 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 */
139template<DrawPixel drawPixel, class Dasher>
140static bool drawLine(QCosmeticStroker *stroker, qreal x1, qreal y1, qreal x2, qreal y2, int caps);
141template<DrawPixel drawPixel, class Dasher>
142static bool drawLineAA(QCosmeticStroker *stroker, qreal x1, qreal y1, qreal x2, qreal y2, int caps);
143
144inline 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
167inline 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(x: stroker->color, a: coverage);
175 stroker->pixels[offset] = sourceOver(d: stroker->pixels[offset], color: c);
176}
177
178inline 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(d: stroker->pixels[offset], color: stroker->color);
186}
187
188enum StrokeSelection {
189 Aliased = 0,
190 AntiAliased = 1,
191 Solid = 0,
192 Dashed = 2,
193 RegularDraw = 0,
194 FastDraw = 4
195};
196
197static 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
233void 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(size: penPattern.size()*sizeof(int));
261 reversePattern = (int *)malloc(size: penPattern.size()*sizeof(int));
262 patternSize = penPattern.size();
263
264 patternLength = 0;
265 for (int i = 0; i < patternSize; ++i) {
266 patternLength += (int)qBound(min: 1., val: penPattern.at(i) * 64, max: 65536.);
267 pattern[i] = patternLength;
268 }
269 patternLength = 0;
270 for (int i = 0; i < patternSize; ++i) {
271 patternLength += (int)qBound(min: 1., val: penPattern.at(i: patternSize - 1 - i) * 64, max: 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(pen: state->lastPen, hints: state->renderHints))
284 opacity = (int) 256*width;
285 else
286 opacity = (int) 256*width*state->txscale;
287 opacity = qBound(min: 0, val: opacity, max: 256);
288
289 drawCaps = state->lastPen.capStyle() != Qt::FlatCap;
290
291 if (strokeSelection & FastDraw) {
292 color = multiplyAlpha256(rgba64: state->penData.solidColor, alpha256: 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
312bool QCosmeticStroker::clipLine(qreal &x1, qreal &y1, qreal &x2, qreal &y2)
313{
314 if (!qIsFinite(d: x1) || !qIsFinite(d: y1) || !qIsFinite(d: x2) || !qIsFinite(d: 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
368void QCosmeticStroker::drawLine(const QPointF &p1, const QPointF &p2)
369{
370 QPointF start = p1 * state->matrix;
371 QPointF end = p2 * state->matrix;
372
373 if (start == end) {
374 drawPoints(points: &p1, num: 1);
375 return;
376 }
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
388void 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(stroker: this, x: qRound(d: p.x()), y: qRound(d: p.y()), coverage: 255);
394 ++points;
395 }
396
397 blend(current_span, spans, &state->penData);
398 current_span = 0;
399}
400
401void 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(stroker: this, x: qRound(d: p.x()), y: qRound(d: p.y()), coverage: 255);
407 ++points;
408 }
409
410 blend(current_span, spans, &state->penData);
411 current_span = 0;
412}
413
414void 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(x1&: rx1, y1&: ry1, x2&: rx2, y2&: 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(t: x2 - x1);
436 int dy = qAbs(t: y2 - y1);
437
438 if (dx < dy) {
439 // vertical
440 bool swapped = false;
441 if (y1 > y2) {
442 swapped = true;
443 qSwap(value1&: y1, value2&: y2);
444 qSwap(value1&: x1, value2&: x2);
445 }
446 int xinc = F16Dot16FixedDiv(x: x2 - x1, y: 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(t: 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(value1&: x1, value2&: x2);
476 qSwap(value1&: y1, value2&: y2);
477 }
478 int yinc = F16Dot16FixedDiv(x: y2 - y1, y: 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(t: yinc) < (1 << 14);
498 }
499 }
500// qDebug() << " moveTo: setting last pixel to x/y dir" << lastPixel.x << lastPixel.y << lastDir;
501}
502
503static 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
523void 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(t: type, end, points, closed: &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(rx1: p1.x(), ry1: p1.y(), rx2: p2.x(), ry2: 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(p1: 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(rx1: p2.x(), ry1: p2.y(), rx2: p.x(), ry2: 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
645void 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(points: p, level, caps);
664}
665
666static 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
692void 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(t: dx) + qAbs(t: dy));
698
699 if (qAbs(t: dx * (points[0].y - points[2].y) - dy * (points[0].x - points[2].x)) >= len ||
700 qAbs(t: dx * (points[0].y - points[1].y) - dy * (points[0].x - points[1].x)) >= len) {
701 splitCubic(points);
702
703 --level;
704 renderCubicSubdivision(points: points + 3, level, caps: caps & CapBegin);
705 renderCubicSubdivision(points, level, caps: 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
713static 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
720static 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 */
735template<DrawPixel drawPixel, class Dasher>
736static bool drawLine(QCosmeticStroker *stroker, qreal rx1, qreal ry1, qreal rx2, qreal ry2, int caps)
737{
738 bool didDraw = qAbs(t: rx2 - rx1) + qAbs(t: ry2 - ry1) >= 1.0;
739
740 if (stroker->clipLine(x1&: rx1, y1&: ry1, x2&: rx2, y2&: 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(t: x2 - x1);
750 int dy = qAbs(t: 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(value1&: y1, value2&: y2);
764 qSwap(value1&: x1, value2&: x2);
765 caps = swapCaps(caps);
766 dir = QCosmeticStroker::BottomToTop;
767 }
768 int xinc = F16Dot16FixedDiv(x: x2 - x1, y: 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, x1&: y1, x2&: y2, y&: x, yinc: 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(value1&: first, value2&: last);
796
797 bool axisAligned = qAbs(t: 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(t: stroker->lastPixel.x - first.x) > 1 ||
812 qAbs(t: 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(t: stroker->lastPixel.x - first.x) <= 1 &&
822 qAbs(t: 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(value1&: x1, value2&: x2);
854 qSwap(value1&: y1, value2&: y2);
855 caps = swapCaps(caps);
856 dir = QCosmeticStroker::RightToLeft;
857 }
858 int yinc = F16Dot16FixedDiv(x: y2 - y1, y: 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(value1&: first, value2&: last);
886
887 bool axisAligned = qAbs(t: 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(t: stroker->lastPixel.x - first.x) > 1 ||
901 qAbs(t: 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(t: stroker->lastPixel.x - first.x) <= 1 &&
911 qAbs(t: 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
938template<DrawPixel drawPixel, class Dasher>
939static bool drawLineAA(QCosmeticStroker *stroker, qreal rx1, qreal ry1, qreal rx2, qreal ry2, int caps)
940{
941 if (stroker->clipLine(x1&: rx1, y1&: ry1, x2&: rx2, y2&: 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(t: dx) < qAbs(t: dy)) {
953 // vertical
954
955 int xinc = F16Dot16FixedDiv(x: dx, y: dy);
956
957 bool swapped = false;
958 if (y1 > y2) {
959 qSwap(value1&: y1, value2&: y2);
960 qSwap(value1&: x1, value2&: 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, x1&: y1, x2&: y2, y&: x, yinc: 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(x: dy, y: dx);
1019
1020 bool swapped = false;
1021 if (x1 > x2) {
1022 qSwap(value1&: x1, value2&: x2);
1023 qSwap(value1&: y1, value2&: 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
1081QT_END_NAMESPACE
1082

source code of qtbase/src/gui/painting/qcosmeticstroker.cpp