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