Coverage Report

Created: 2026-01-25 07:18

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