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