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

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