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