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 (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 = 0;
229 }
230 return stroke;
231}
232
233void QCosmeticStroker::setup()
234{
235 blend = state->penData.blend;
236 if (state->clip && state->clip->enabled && state->clip->hasRectClip && !state->clip->clipRect.isEmpty()) {
237 clip &= state->clip->clipRect;
238 blend = state->penData.unclipped_blend;
239 }
240
241 int strokeSelection = 0;
242 if (blend == state->penData.unclipped_blend
243 && state->penData.type == QSpanData::Solid
244 && (state->penData.rasterBuffer->format == QImage::Format_ARGB32_Premultiplied
245 || state->penData.rasterBuffer->format == QImage::Format_RGB32)
246 && state->compositionMode() == QPainter::CompositionMode_SourceOver)
247 strokeSelection |= FastDraw;
248
249 if (state->renderHints & QPainter::Antialiasing)
250 strokeSelection |= AntiAliased;
251
252 const QVector<qreal> &penPattern = state->lastPen.dashPattern();
253 if (penPattern.isEmpty()) {
254 Q_ASSERT(!pattern && !reversePattern);
255 pattern = 0;
256 reversePattern = 0;
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
379 stroke(this, start.x(), start.y(), end.x(), end.y(), drawCaps ? CapBegin|CapEnd : 0);
380
381 blend(current_span, spans, &state->penData);
382 current_span = 0;
383}
384
385void QCosmeticStroker::drawPoints(const QPoint *points, int num)
386{
387 const QPoint *end = points + num;
388 while (points < end) {
389 QPointF p = QPointF(*points) * state->matrix;
390 drawPixel(this, qRound(p.x()), qRound(p.y()), 255);
391 ++points;
392 }
393
394 blend(current_span, spans, &state->penData);
395 current_span = 0;
396}
397
398void QCosmeticStroker::drawPoints(const QPointF *points, int num)
399{
400 const QPointF *end = points + num;
401 while (points < end) {
402 QPointF p = (*points) * state->matrix;
403 drawPixel(this, qRound(p.x()), qRound(p.y()), 255);
404 ++points;
405 }
406
407 blend(current_span, spans, &state->penData);
408 current_span = 0;
409}
410
411void QCosmeticStroker::calculateLastPoint(qreal rx1, qreal ry1, qreal rx2, qreal ry2)
412{
413 // this is basically the same code as used in the aliased stroke method,
414 // but it only determines the direction and last point of a line
415 //
416 // This is being used to have proper dropout control for closed contours
417 // by calculating the direction and last pixel of the last segment in the contour.
418 // the info is then used to perform dropout control when drawing the first line segment
419 // of the contour
420 lastPixel.x = INT_MIN;
421 lastPixel.y = INT_MIN;
422
423 if (clipLine(rx1, ry1, rx2, ry2))
424 return;
425
426 const int half = legacyRounding ? 31 : 0;
427 int x1 = toF26Dot6(rx1) + half;
428 int y1 = toF26Dot6(ry1) + half;
429 int x2 = toF26Dot6(rx2) + half;
430 int y2 = toF26Dot6(ry2) + half;
431
432 int dx = qAbs(x2 - x1);
433 int dy = qAbs(y2 - y1);
434
435 if (dx < dy) {
436 // vertical
437 bool swapped = false;
438 if (y1 > y2) {
439 swapped = true;
440 qSwap(y1, y2);
441 qSwap(x1, x2);
442 }
443 int xinc = F16Dot16FixedDiv(x2 - x1, y2 - y1);
444 int x = x1 * (1<<10);
445
446 int y = (y1 + 32) >> 6;
447 int ys = (y2 + 32) >> 6;
448
449 int round = (xinc > 0) ? 32 : 0;
450 if (y != ys) {
451 x += ((y * (1<<6)) + round - y1) * xinc >> 6;
452
453 if (swapped) {
454 lastPixel.x = x >> 16;
455 lastPixel.y = y;
456 lastDir = QCosmeticStroker::BottomToTop;
457 } else {
458 lastPixel.x = (x + (ys - y - 1)*xinc) >> 16;
459 lastPixel.y = ys - 1;
460 lastDir = QCosmeticStroker::TopToBottom;
461 }
462 lastAxisAligned = qAbs(xinc) < (1 << 14);
463 }
464 } else {
465 // horizontal
466 if (!dx)
467 return;
468
469 bool swapped = false;
470 if (x1 > x2) {
471 swapped = true;
472 qSwap(x1, x2);
473 qSwap(y1, y2);
474 }
475 int yinc = F16Dot16FixedDiv(y2 - y1, x2 - x1);
476 int y = y1 << 10;
477
478 int x = (x1 + 32) >> 6;
479 int xs = (x2 + 32) >> 6;
480
481 int round = (yinc > 0) ? 32 : 0;
482 if (x != xs) {
483 y += ((x * (1<<6)) + round - x1) * yinc >> 6;
484
485 if (swapped) {
486 lastPixel.x = x;
487 lastPixel.y = y >> 16;
488 lastDir = QCosmeticStroker::RightToLeft;
489 } else {
490 lastPixel.x = xs - 1;
491 lastPixel.y = (y + (xs - x - 1)*yinc) >> 16;
492 lastDir = QCosmeticStroker::LeftToRight;
493 }
494 lastAxisAligned = qAbs(yinc) < (1 << 14);
495 }
496 }
497// qDebug() << " moveTo: setting last pixel to x/y dir" << lastPixel.x << lastPixel.y << lastDir;
498}
499
500static inline const QPainterPath::ElementType *subPath(const QPainterPath::ElementType *t, const QPainterPath::ElementType *end,
501 const qreal *points, bool *closed)
502{
503 const QPainterPath::ElementType *start = t;
504 ++t;
505
506 // find out if the subpath is closed
507 while (t < end) {
508 if (*t == QPainterPath::MoveToElement)
509 break;
510 ++t;
511 }
512
513 int offset = t - start - 1;
514// qDebug() << "subpath" << offset << points[0] << points[1] << points[2*offset] << points[2*offset+1];
515 *closed = (points[0] == points[2*offset] && points[1] == points[2*offset + 1]);
516
517 return t;
518}
519
520void QCosmeticStroker::drawPath(const QVectorPath &path)
521{
522// qDebug() << ">>>> drawpath" << path.convertToPainterPath()
523// << "antialiasing:" << (bool)(state->renderHints & QPainter::Antialiasing) << " implicit close:" << path.hasImplicitClose();
524 if (path.isEmpty())
525 return;
526
527 const qreal *points = path.points();
528 const QPainterPath::ElementType *type = path.elements();
529
530 if (type) {
531 const QPainterPath::ElementType *end = type + path.elementCount();
532
533 while (type < end) {
534 Q_ASSERT(type == path.elements() || *type == QPainterPath::MoveToElement);
535
536 QPointF p = QPointF(points[0], points[1]) * state->matrix;
537 patternOffset = state->lastPen.dashOffset()*64;
538 lastPixel.x = INT_MIN;
539 lastPixel.y = INT_MIN;
540
541 bool closed;
542 const QPainterPath::ElementType *e = subPath(type, end, points, &closed);
543 if (closed) {
544 const qreal *p = points + 2*(e-type);
545 QPointF p1 = QPointF(p[-4], p[-3]) * state->matrix;
546 QPointF p2 = QPointF(p[-2], p[-1]) * state->matrix;
547 calculateLastPoint(p1.x(), p1.y(), p2.x(), p2.y());
548 }
549 int caps = (!closed && drawCaps) ? CapBegin : NoCaps;
550// qDebug() << "closed =" << closed << capString(caps);
551
552 points += 2;
553 ++type;
554
555 while (type < e) {
556 QPointF p2 = QPointF(points[0], points[1]) * state->matrix;
557 switch (*type) {
558 case QPainterPath::MoveToElement:
559 Q_ASSERT(!"Logic error");
560 break;
561
562 case QPainterPath::LineToElement:
563 if (!closed && drawCaps && type == e - 1)
564 caps |= CapEnd;
565 stroke(this, p.x(), p.y(), p2.x(), p2.y(), caps);
566 p = p2;
567 points += 2;
568 ++type;
569 break;
570
571 case QPainterPath::CurveToElement: {
572 if (!closed && drawCaps && type == e - 3)
573 caps |= CapEnd;
574 QPointF p3 = QPointF(points[2], points[3]) * state->matrix;
575 QPointF p4 = QPointF(points[4], points[5]) * state->matrix;
576 renderCubic(p, p2, p3, p4, caps);
577 p = p4;
578 type += 3;
579 points += 6;
580 break;
581 }
582 case QPainterPath::CurveToDataElement:
583 Q_ASSERT(!"QPainterPath::toSubpathPolygons(), bad element type");
584 break;
585 }
586 caps = NoCaps;
587 }
588 }
589 } else { // !type, simple polygon
590 QPointF p = QPointF(points[0], points[1]) * state->matrix;
591 QPointF movedTo = p;
592 patternOffset = state->lastPen.dashOffset()*64;
593 lastPixel.x = INT_MIN;
594 lastPixel.y = INT_MIN;
595
596 const qreal *begin = points;
597 const qreal *end = points + 2*path.elementCount();
598 // handle closed path case
599 bool closed = path.hasImplicitClose() || (points[0] == end[-2] && points[1] == end[-1]);
600 int caps = (!closed && drawCaps) ? CapBegin : NoCaps;
601 if (closed) {
602 QPointF p2;
603 if (points[0] == end[-2] && points[1] == end[-1] && path.elementCount() > 2)
604 p2 = QPointF(end[-4], end[-3]) * state->matrix;
605 else
606 p2 = QPointF(end[-2], end[-1]) * state->matrix;
607 calculateLastPoint(p2.x(), p2.y(), p.x(), p.y());
608 }
609
610 bool fastPenAliased = (state->flags.fast_pen && !state->flags.antialiased);
611 points += 2;
612 while (points < end) {
613 QPointF p2 = QPointF(points[0], points[1]) * state->matrix;
614
615 if (!closed && drawCaps && points == end - 2)
616 caps |= CapEnd;
617
618 bool moveNextStart = stroke(this, p.x(), p.y(), p2.x(), p2.y(), caps);
619
620 /* fix for gaps in polylines with fastpen and aliased in a sequence
621 of points with small distances: if current point p2 has been dropped
622 out, keep last non dropped point p.
623
624 However, if the line was completely outside the devicerect, we
625 still need to update p to avoid drawing the line after this one from
626 a bad starting position.
627 */
628 if (!fastPenAliased || moveNextStart || points == begin + 2 || points == end - 2)
629 p = p2;
630 points += 2;
631 caps = NoCaps;
632 }
633 if (path.hasImplicitClose())
634 stroke(this, p.x(), p.y(), movedTo.x(), movedTo.y(), NoCaps);
635 }
636
637
638 blend(current_span, spans, &state->penData);
639 current_span = 0;
640}
641
642void QCosmeticStroker::renderCubic(const QPointF &p1, const QPointF &p2, const QPointF &p3, const QPointF &p4, int caps)
643{
644// qDebug() << ">>>> renderCubic" << p1 << p2 << p3 << p4 << capString(caps);
645 const int maxSubDivisions = 6;
646 PointF points[3*maxSubDivisions + 4];
647
648 points[3].x = p1.x();
649 points[3].y = p1.y();
650 points[2].x = p2.x();
651 points[2].y = p2.y();
652 points[1].x = p3.x();
653 points[1].y = p3.y();
654 points[0].x = p4.x();
655 points[0].y = p4.y();
656
657 PointF *p = points;
658 int level = maxSubDivisions;
659
660 renderCubicSubdivision(p, level, caps);
661}
662
663static void splitCubic(QCosmeticStroker::PointF *points)
664{
665 const qreal half = .5;
666 qreal a, b, c, d;
667
668 points[6].x = points[3].x;
669 c = points[1].x;
670 d = points[2].x;
671 points[1].x = a = ( points[0].x + c ) * half;
672 points[5].x = b = ( points[3].x + d ) * half;
673 c = ( c + d ) * half;
674 points[2].x = a = ( a + c ) * half;
675 points[4].x = b = ( b + c ) * half;
676 points[3].x = ( a + b ) * half;
677
678 points[6].y = points[3].y;
679 c = points[1].y;
680 d = points[2].y;
681 points[1].y = a = ( points[0].y + c ) * half;
682 points[5].y = b = ( points[3].y + d ) * half;
683 c = ( c + d ) * half;
684 points[2].y = a = ( a + c ) * half;
685 points[4].y = b = ( b + c ) * half;
686 points[3].y = ( a + b ) * half;
687}
688
689void QCosmeticStroker::renderCubicSubdivision(QCosmeticStroker::PointF *points, int level, int caps)
690{
691 if (level) {
692 qreal dx = points[3].x - points[0].x;
693 qreal dy = points[3].y - points[0].y;
694 qreal len = ((qreal).25) * (qAbs(dx) + qAbs(dy));
695
696 if (qAbs(dx * (points[0].y - points[2].y) - dy * (points[0].x - points[2].x)) >= len ||
697 qAbs(dx * (points[0].y - points[1].y) - dy * (points[0].x - points[1].x)) >= len) {
698 splitCubic(points);
699
700 --level;
701 renderCubicSubdivision(points + 3, level, caps & CapBegin);
702 renderCubicSubdivision(points, level, caps & CapEnd);
703 return;
704 }
705 }
706
707 stroke(this, points[3].x, points[3].y, points[0].x, points[0].y, caps);
708}
709
710static inline int swapCaps(int caps)
711{
712 return ((caps & QCosmeticStroker::CapBegin) << 1) |
713 ((caps & QCosmeticStroker::CapEnd) >> 1);
714}
715
716// adjust line by half a pixel
717static inline void capAdjust(int caps, int &x1, int &x2, int &y, int yinc)
718{
719 if (caps & QCosmeticStroker::CapBegin) {
720 x1 -= 32;
721 y -= yinc >> 1;
722 }
723 if (caps & QCosmeticStroker::CapEnd) {
724 x2 += 32;
725 }
726}
727
728/*
729 The hard part about this is dropout control and avoiding douple drawing of points when
730 the drawing shifts from horizontal to vertical or back.
731 */
732template<DrawPixel drawPixel, class Dasher>
733static bool drawLine(QCosmeticStroker *stroker, qreal rx1, qreal ry1, qreal rx2, qreal ry2, int caps)
734{
735 bool didDraw = qAbs(rx2 - rx1) + qAbs(ry2 - ry1) >= 1.0;
736
737 if (stroker->clipLine(rx1, ry1, rx2, ry2))
738 return true;
739
740 const int half = stroker->legacyRounding ? 31 : 0;
741 int x1 = toF26Dot6(rx1) + half;
742 int y1 = toF26Dot6(ry1) + half;
743 int x2 = toF26Dot6(rx2) + half;
744 int y2 = toF26Dot6(ry2) + half;
745
746 int dx = qAbs(x2 - x1);
747 int dy = qAbs(y2 - y1);
748
749 QCosmeticStroker::Point last = stroker->lastPixel;
750
751// qDebug() << "stroke" << x1/64. << y1/64. << x2/64. << y2/64.;
752
753 if (dx < dy) {
754 // vertical
755 QCosmeticStroker::Direction dir = QCosmeticStroker::TopToBottom;
756
757 bool swapped = false;
758 if (y1 > y2) {
759 swapped = true;
760 qSwap(y1, y2);
761 qSwap(x1, x2);
762 caps = swapCaps(caps);
763 dir = QCosmeticStroker::BottomToTop;
764 }
765 int xinc = F16Dot16FixedDiv(x2 - x1, y2 - y1);
766 int x = x1 * (1<<10);
767
768 if ((stroker->lastDir ^ QCosmeticStroker::VerticalMask) == dir)
769 caps |= swapped ? QCosmeticStroker::CapEnd : QCosmeticStroker::CapBegin;
770
771 capAdjust(caps, y1, y2, x, xinc);
772
773 int y = (y1 + 32) >> 6;
774 int ys = (y2 + 32) >> 6;
775 int round = (xinc > 0) ? 32 : 0;
776
777 // If capAdjust made us round away from what calculateLastPoint gave us,
778 // round back the other way so we start and end on the right point.
779 if ((caps & QCosmeticStroker::CapBegin) && stroker->lastPixel.y == y + 1)
780 y++;
781
782 if (y != ys) {
783 x += ((y * (1<<6)) + round - y1) * xinc >> 6;
784
785 // calculate first and last pixel and perform dropout control
786 QCosmeticStroker::Point first;
787 first.x = x >> 16;
788 first.y = y;
789 last.x = (x + (ys - y - 1)*xinc) >> 16;
790 last.y = ys - 1;
791 if (swapped)
792 qSwap(first, last);
793
794 bool axisAligned = qAbs(xinc) < (1 << 14);
795 if (stroker->lastPixel.x > INT_MIN) {
796 if (first.x == stroker->lastPixel.x &&
797 first.y == stroker->lastPixel.y) {
798 // remove duplicated pixel
799 if (swapped) {
800 --ys;
801 } else {
802 ++y;
803 x += xinc;
804 }
805 } else if (stroker->lastDir != dir &&
806 (((axisAligned && stroker->lastAxisAligned) &&
807 stroker->lastPixel.x != first.x && stroker->lastPixel.y != first.y) ||
808 (qAbs(stroker->lastPixel.x - first.x) > 1 ||
809 qAbs(stroker->lastPixel.y - first.y) > 1))) {
810 // have a missing pixel, insert it
811 if (swapped) {
812 ++ys;
813 } else {
814 --y;
815 x -= xinc;
816 }
817 } else if (stroker->lastDir == dir &&
818 ((qAbs(stroker->lastPixel.x - first.x) <= 1 &&
819 qAbs(stroker->lastPixel.y - first.y) > 1))) {
820 x += xinc >> 1;
821 if (swapped)
822 last.x = (x >> 16);
823 else
824 last.x = (x + (ys - y - 1)*xinc) >> 16;
825 }
826 }
827 stroker->lastDir = dir;
828 stroker->lastAxisAligned = axisAligned;
829
830 Dasher dasher(stroker, swapped, y * (1<<6), ys * (1<<6));
831
832 do {
833 if (dasher.on())
834 drawPixel(stroker, x >> 16, y, 255);
835 dasher.adjust();
836 x += xinc;
837 } while (++y < ys);
838 didDraw = true;
839 }
840 } else {
841 // horizontal
842 if (!dx)
843 return true;
844
845 QCosmeticStroker::Direction dir = QCosmeticStroker::LeftToRight;
846
847 bool swapped = false;
848 if (x1 > x2) {
849 swapped = true;
850 qSwap(x1, x2);
851 qSwap(y1, y2);
852 caps = swapCaps(caps);
853 dir = QCosmeticStroker::RightToLeft;
854 }
855 int yinc = F16Dot16FixedDiv(y2 - y1, x2 - x1);
856 int y = y1 * (1<<10);
857
858 if ((stroker->lastDir ^ QCosmeticStroker::HorizontalMask) == dir)
859 caps |= swapped ? QCosmeticStroker::CapEnd : QCosmeticStroker::CapBegin;
860
861 capAdjust(caps, x1, x2, y, yinc);
862
863 int x = (x1 + 32) >> 6;
864 int xs = (x2 + 32) >> 6;
865 int round = (yinc > 0) ? 32 : 0;
866
867 // If capAdjust made us round away from what calculateLastPoint gave us,
868 // round back the other way so we start and end on the right point.
869 if ((caps & QCosmeticStroker::CapBegin) && stroker->lastPixel.x == x + 1)
870 x++;
871
872 if (x != xs) {
873 y += ((x * (1<<6)) + round - x1) * yinc >> 6;
874
875 // calculate first and last pixel to perform dropout control
876 QCosmeticStroker::Point first;
877 first.x = x;
878 first.y = y >> 16;
879 last.x = xs - 1;
880 last.y = (y + (xs - x - 1)*yinc) >> 16;
881 if (swapped)
882 qSwap(first, last);
883
884 bool axisAligned = qAbs(yinc) < (1 << 14);
885 if (stroker->lastPixel.x > INT_MIN) {
886 if (first.x == stroker->lastPixel.x && first.y == stroker->lastPixel.y) {
887 // remove duplicated pixel
888 if (swapped) {
889 --xs;
890 } else {
891 ++x;
892 y += yinc;
893 }
894 } else if (stroker->lastDir != dir &&
895 (((axisAligned && stroker->lastAxisAligned) &&
896 stroker->lastPixel.x != first.x && stroker->lastPixel.y != first.y) ||
897 (qAbs(stroker->lastPixel.x - first.x) > 1 ||
898 qAbs(stroker->lastPixel.y - first.y) > 1))) {
899 // have a missing pixel, insert it
900 if (swapped) {
901 ++xs;
902 } else {
903 --x;
904 y -= yinc;
905 }
906 } else if (stroker->lastDir == dir &&
907 ((qAbs(stroker->lastPixel.x - first.x) <= 1 &&
908 qAbs(stroker->lastPixel.y - first.y) > 1))) {
909 y += yinc >> 1;
910 if (swapped)
911 last.y = (y >> 16);
912 else
913 last.y = (y + (xs - x - 1)*yinc) >> 16;
914 }
915 }
916 stroker->lastDir = dir;
917 stroker->lastAxisAligned = axisAligned;
918
919 Dasher dasher(stroker, swapped, x * (1<<6), xs * (1<<6));
920
921 do {
922 if (dasher.on())
923 drawPixel(stroker, x, y >> 16, 255);
924 dasher.adjust();
925 y += yinc;
926 } while (++x < xs);
927 didDraw = true;
928 }
929 }
930 stroker->lastPixel = last;
931 return didDraw;
932}
933
934
935template<DrawPixel drawPixel, class Dasher>
936static bool drawLineAA(QCosmeticStroker *stroker, qreal rx1, qreal ry1, qreal rx2, qreal ry2, int caps)
937{
938 if (stroker->clipLine(rx1, ry1, rx2, ry2))
939 return true;
940
941 int x1 = toF26Dot6(rx1);
942 int y1 = toF26Dot6(ry1);
943 int x2 = toF26Dot6(rx2);
944 int y2 = toF26Dot6(ry2);
945
946 int dx = x2 - x1;
947 int dy = y2 - y1;
948
949 if (qAbs(dx) < qAbs(dy)) {
950 // vertical
951
952 int xinc = F16Dot16FixedDiv(dx, dy);
953
954 bool swapped = false;
955 if (y1 > y2) {
956 qSwap(y1, y2);
957 qSwap(x1, x2);
958 swapped = true;
959 caps = swapCaps(caps);
960 }
961
962 int x = (x1 - 32) * (1<<10);
963 x -= ( ((y1 & 63) - 32) * xinc ) >> 6;
964
965 capAdjust(caps, y1, y2, x, xinc);
966
967 Dasher dasher(stroker, swapped, y1, y2);
968
969 int y = y1 >> 6;
970 int ys = y2 >> 6;
971
972 int alphaStart, alphaEnd;
973 if (y == ys) {
974 alphaStart = y2 - y1;
975 Q_ASSERT(alphaStart >= 0 && alphaStart < 64);
976 alphaEnd = 0;
977 } else {
978 alphaStart = 64 - (y1 & 63);
979 alphaEnd = (y2 & 63);
980 }
981// qDebug() << "vertical" << x1/64. << y1/64. << x2/64. << y2/64.;
982// qDebug() << " x=" << x << "dx=" << dx << "xi=" << (x>>16) << "xsi=" << ((x+(ys-y)*dx)>>16) << "y=" << y << "ys=" << ys;
983
984 // draw first pixel
985 if (dasher.on()) {
986 uint alpha = (quint8)(x >> 8);
987 drawPixel(stroker, x>>16, y, (255-alpha) * alphaStart >> 6);
988 drawPixel(stroker, (x>>16) + 1, y, alpha * alphaStart >> 6);
989 }
990 dasher.adjust();
991 x += xinc;
992 ++y;
993 if (y < ys) {
994 do {
995 if (dasher.on()) {
996 uint alpha = (quint8)(x >> 8);
997 drawPixel(stroker, x>>16, y, (255-alpha));
998 drawPixel(stroker, (x>>16) + 1, y, alpha);
999 }
1000 dasher.adjust();
1001 x += xinc;
1002 } while (++y < ys);
1003 }
1004 // draw last pixel
1005 if (alphaEnd && dasher.on()) {
1006 uint alpha = (quint8)(x >> 8);
1007 drawPixel(stroker, x>>16, y, (255-alpha) * alphaEnd >> 6);
1008 drawPixel(stroker, (x>>16) + 1, y, alpha * alphaEnd >> 6);
1009 }
1010 } else {
1011 // horizontal
1012 if (!dx)
1013 return true;
1014
1015 int yinc = F16Dot16FixedDiv(dy, dx);
1016
1017 bool swapped = false;
1018 if (x1 > x2) {
1019 qSwap(x1, x2);
1020 qSwap(y1, y2);
1021 swapped = true;
1022 caps = swapCaps(caps);
1023 }
1024
1025 int y = (y1 - 32) * (1<<10);
1026 y -= ( ((x1 & 63) - 32) * yinc ) >> 6;
1027
1028 capAdjust(caps, x1, x2, y, yinc);
1029
1030 Dasher dasher(stroker, swapped, x1, x2);
1031
1032 int x = x1 >> 6;
1033 int xs = x2 >> 6;
1034
1035// qDebug() << "horizontal" << x1/64. << y1/64. << x2/64. << y2/64.;
1036// qDebug() << " y=" << y << "dy=" << dy << "x=" << x << "xs=" << xs << "yi=" << (y>>16) << "ysi=" << ((y+(xs-x)*dy)>>16);
1037 int alphaStart, alphaEnd;
1038 if (x == xs) {
1039 alphaStart = x2 - x1;
1040 Q_ASSERT(alphaStart >= 0 && alphaStart < 64);
1041 alphaEnd = 0;
1042 } else {
1043 alphaStart = 64 - (x1 & 63);
1044 alphaEnd = (x2 & 63);
1045 }
1046
1047 // draw first pixel
1048 if (dasher.on()) {
1049 uint alpha = (quint8)(y >> 8);
1050 drawPixel(stroker, x, y>>16, (255-alpha) * alphaStart >> 6);
1051 drawPixel(stroker, x, (y>>16) + 1, alpha * alphaStart >> 6);
1052 }
1053 dasher.adjust();
1054 y += yinc;
1055 ++x;
1056 // draw line
1057 if (x < xs) {
1058 do {
1059 if (dasher.on()) {
1060 uint alpha = (quint8)(y >> 8);
1061 drawPixel(stroker, x, y>>16, (255-alpha));
1062 drawPixel(stroker, x, (y>>16) + 1, alpha);
1063 }
1064 dasher.adjust();
1065 y += yinc;
1066 } while (++x < xs);
1067 }
1068 // draw last pixel
1069 if (alphaEnd && dasher.on()) {
1070 uint alpha = (quint8)(y >> 8);
1071 drawPixel(stroker, x, y>>16, (255-alpha) * alphaEnd >> 6);
1072 drawPixel(stroker, x, (y>>16) + 1, alpha * alphaEnd >> 6);
1073 }
1074 }
1075 return true;
1076}
1077
1078QT_END_NAMESPACE
1079