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 "qrasterizer_p.h" |
5 | |
6 | #include <QPoint> |
7 | #include <QRect> |
8 | |
9 | #include <private/qmath_p.h> |
10 | #include <private/qdatabuffer_p.h> |
11 | #include <private/qdrawhelper_p.h> |
12 | |
13 | #include <QtGui/qpainterpath.h> |
14 | |
15 | #include <algorithm> |
16 | |
17 | QT_BEGIN_NAMESPACE |
18 | |
19 | #if Q_PROCESSOR_WORDSIZE == 8 |
20 | typedef qint64 QScFixed; |
21 | #else |
22 | typedef int QScFixed; |
23 | #endif |
24 | #define QScFixedToFloat(i) ((i) * (1./65536.)) |
25 | #define FloatToQScFixed(i) (QScFixed)((i) * 65536.) |
26 | #define IntToQScFixed(i) ((QScFixed)(i) * (1 << 16)) |
27 | #define QScFixedToInt(i) ((i) >> 16) |
28 | #define QScFixedFactor 65536 |
29 | #define FTPosToQScFixed(i) ((QScFixed)(i) * (1 << 10)) |
30 | |
31 | #define QScFixedMultiply(x, y) (QScFixed)((qlonglong(x) * qlonglong(y)) >> 16) |
32 | #define QScFixedFastMultiply(x, y) (((x) * (y)) >> 16) |
33 | |
34 | #define SPAN_BUFFER_SIZE 256 |
35 | |
36 | #define COORD_ROUNDING 1 // 0: round up, 1: round down |
37 | #define COORD_OFFSET 32 // 26.6, 32 is half a pixel |
38 | |
39 | static inline QT_FT_Vector PointToVector(const QPointF &p) |
40 | { |
41 | QT_FT_Vector result = { .x: QT_FT_Pos(p.x() * 64), .y: QT_FT_Pos(p.y() * 64) }; |
42 | return result; |
43 | } |
44 | |
45 | class QSpanBuffer { |
46 | public: |
47 | QSpanBuffer(ProcessSpans blend, void *data, const QRect &clipRect) |
48 | : m_spanCount(0) |
49 | , m_blend(blend) |
50 | , m_data(data) |
51 | , m_clipRect(clipRect) |
52 | { |
53 | } |
54 | |
55 | ~QSpanBuffer() |
56 | { |
57 | flushSpans(); |
58 | } |
59 | |
60 | void addSpan(int x, int len, int y, int coverage) |
61 | { |
62 | if (!coverage || !len) |
63 | return; |
64 | |
65 | Q_ASSERT(coverage >= 0 && coverage <= 255); |
66 | Q_ASSERT(y >= m_clipRect.top()); |
67 | Q_ASSERT(y <= m_clipRect.bottom()); |
68 | Q_ASSERT(x >= m_clipRect.left()); |
69 | Q_ASSERT(x + int(len) - 1 <= m_clipRect.right()); |
70 | |
71 | m_spans[m_spanCount].x = x; |
72 | m_spans[m_spanCount].len = len; |
73 | m_spans[m_spanCount].y = y; |
74 | m_spans[m_spanCount].coverage = coverage; |
75 | |
76 | if (++m_spanCount == SPAN_BUFFER_SIZE) |
77 | flushSpans(); |
78 | } |
79 | |
80 | private: |
81 | void flushSpans() |
82 | { |
83 | m_blend(m_spanCount, m_spans, m_data); |
84 | m_spanCount = 0; |
85 | } |
86 | |
87 | QT_FT_Span m_spans[SPAN_BUFFER_SIZE]; |
88 | int m_spanCount; |
89 | |
90 | ProcessSpans m_blend; |
91 | void *m_data; |
92 | |
93 | QRect m_clipRect; |
94 | }; |
95 | |
96 | #define CHUNK_SIZE 64 |
97 | class QScanConverter |
98 | { |
99 | public: |
100 | QScanConverter(); |
101 | ~QScanConverter(); |
102 | |
103 | void begin(int top, int bottom, int left, int right, |
104 | Qt::FillRule fillRule, QSpanBuffer *spanBuffer); |
105 | void end(); |
106 | |
107 | void mergeCurve(const QT_FT_Vector &a, const QT_FT_Vector &b, |
108 | const QT_FT_Vector &c, const QT_FT_Vector &d); |
109 | void mergeLine(QT_FT_Vector a, QT_FT_Vector b); |
110 | |
111 | struct Line |
112 | { |
113 | QScFixed x; |
114 | QScFixed delta; |
115 | |
116 | int top, bottom; |
117 | |
118 | int winding; |
119 | }; |
120 | |
121 | private: |
122 | struct Intersection |
123 | { |
124 | int x; |
125 | int winding; |
126 | |
127 | int left, right; |
128 | }; |
129 | |
130 | inline bool clip(QScFixed &xFP, int &iTop, int &iBottom, QScFixed slopeFP, QScFixed edgeFP, int winding); |
131 | inline void mergeIntersection(Intersection *head, const Intersection &isect); |
132 | |
133 | void prepareChunk(); |
134 | |
135 | void emitNode(const Intersection *node); |
136 | void emitSpans(int chunk); |
137 | |
138 | inline void allocate(int size); |
139 | |
140 | QDataBuffer<Line> m_lines; |
141 | |
142 | int m_alloc; |
143 | int m_size; |
144 | |
145 | int m_top; |
146 | int m_bottom; |
147 | |
148 | QScFixed m_leftFP; |
149 | QScFixed m_rightFP; |
150 | |
151 | int m_fillRuleMask; |
152 | |
153 | int m_x; |
154 | int m_y; |
155 | int m_winding; |
156 | |
157 | Intersection *m_intersections; |
158 | |
159 | QSpanBuffer *m_spanBuffer; |
160 | |
161 | QDataBuffer<Line *> m_active; |
162 | |
163 | template <bool AllVertical> |
164 | void scanConvert(); |
165 | }; |
166 | |
167 | class QRasterizerPrivate |
168 | { |
169 | public: |
170 | bool antialiased; |
171 | ProcessSpans blend; |
172 | void *data; |
173 | QRect clipRect; |
174 | |
175 | QScanConverter scanConverter; |
176 | }; |
177 | |
178 | QScanConverter::QScanConverter() |
179 | : m_lines(0) |
180 | , m_alloc(0) |
181 | , m_size(0) |
182 | , m_intersections(nullptr) |
183 | , m_active(0) |
184 | { |
185 | } |
186 | |
187 | QScanConverter::~QScanConverter() |
188 | { |
189 | if (m_intersections) |
190 | free(ptr: m_intersections); |
191 | } |
192 | |
193 | void QScanConverter::begin(int top, int bottom, int left, int right, |
194 | Qt::FillRule fillRule, |
195 | QSpanBuffer *spanBuffer) |
196 | { |
197 | m_top = top; |
198 | m_bottom = bottom; |
199 | m_leftFP = IntToQScFixed(left); |
200 | m_rightFP = IntToQScFixed(right + 1); |
201 | |
202 | m_lines.reset(); |
203 | |
204 | m_fillRuleMask = fillRule == Qt::WindingFill ? ~0x0 : 0x1; |
205 | m_spanBuffer = spanBuffer; |
206 | } |
207 | |
208 | void QScanConverter::prepareChunk() |
209 | { |
210 | m_size = CHUNK_SIZE; |
211 | |
212 | allocate(CHUNK_SIZE); |
213 | memset(s: m_intersections, c: 0, CHUNK_SIZE * sizeof(Intersection)); |
214 | } |
215 | |
216 | void QScanConverter::emitNode(const Intersection *node) |
217 | { |
218 | tail_call: |
219 | if (node->left) |
220 | emitNode(node: node + node->left); |
221 | |
222 | if (m_winding & m_fillRuleMask) |
223 | m_spanBuffer->addSpan(x: m_x, len: node->x - m_x, y: m_y, coverage: 0xff); |
224 | |
225 | m_x = node->x; |
226 | m_winding += node->winding; |
227 | |
228 | if (node->right) { |
229 | node += node->right; |
230 | goto tail_call; |
231 | } |
232 | } |
233 | |
234 | void QScanConverter::emitSpans(int chunk) |
235 | { |
236 | for (int dy = 0; dy < CHUNK_SIZE; ++dy) { |
237 | m_x = 0; |
238 | m_y = chunk + dy; |
239 | m_winding = 0; |
240 | |
241 | emitNode(node: &m_intersections[dy]); |
242 | } |
243 | } |
244 | |
245 | // split control points b[0] ... b[3] into |
246 | // left (b[0] ... b[3]) and right (b[3] ... b[6]) |
247 | static void split(QT_FT_Vector *b) |
248 | { |
249 | b[6] = b[3]; |
250 | |
251 | { |
252 | const QT_FT_Pos temp = (b[1].x + b[2].x)/2; |
253 | |
254 | b[1].x = (b[0].x + b[1].x)/2; |
255 | b[5].x = (b[2].x + b[3].x)/2; |
256 | b[2].x = (b[1].x + temp)/2; |
257 | b[4].x = (b[5].x + temp)/2; |
258 | b[3].x = (b[2].x + b[4].x)/2; |
259 | } |
260 | { |
261 | const QT_FT_Pos temp = (b[1].y + b[2].y)/2; |
262 | |
263 | b[1].y = (b[0].y + b[1].y)/2; |
264 | b[5].y = (b[2].y + b[3].y)/2; |
265 | b[2].y = (b[1].y + temp)/2; |
266 | b[4].y = (b[5].y + temp)/2; |
267 | b[3].y = (b[2].y + b[4].y)/2; |
268 | } |
269 | } |
270 | |
271 | template <bool AllVertical> |
272 | void QScanConverter::scanConvert() |
273 | { |
274 | if (!m_lines.size()) { |
275 | m_active.reset(); |
276 | return; |
277 | } |
278 | constexpr auto topOrder = [](const Line &a, const Line &b) { |
279 | return a.top < b.top; |
280 | }; |
281 | constexpr auto xOrder = [](const Line *a, const Line *b) { |
282 | return a->x < b->x; |
283 | }; |
284 | |
285 | std::sort(m_lines.data(), m_lines.data() + m_lines.size(), topOrder); |
286 | int line = 0; |
287 | for (int y = m_lines.first().top; y <= m_bottom; ++y) { |
288 | for (; line < m_lines.size() && m_lines.at(i: line).top == y; ++line) { |
289 | // add node to active list |
290 | if constexpr(AllVertical) { |
291 | QScanConverter::Line *l = &m_lines.at(i: line); |
292 | m_active.resize(size: m_active.size() + 1); |
293 | int j; |
294 | for (j = m_active.size() - 2; j >= 0 && xOrder(l, m_active.at(i: j)); --j) |
295 | m_active.at(i: j+1) = m_active.at(i: j); |
296 | m_active.at(i: j+1) = l; |
297 | } else { |
298 | m_active << &m_lines.at(i: line); |
299 | } |
300 | } |
301 | |
302 | int numActive = m_active.size(); |
303 | if constexpr(!AllVertical) { |
304 | // use insertion sort instead of std::sort, as the active edge list is quite small |
305 | // and in the average case already sorted |
306 | for (int i = 1; i < numActive; ++i) { |
307 | QScanConverter::Line *l = m_active.at(i); |
308 | int j; |
309 | for (j = i-1; j >= 0 && xOrder(l, m_active.at(i: j)); --j) |
310 | m_active.at(i: j+1) = m_active.at(i: j); |
311 | m_active.at(i: j+1) = l; |
312 | } |
313 | } |
314 | |
315 | int x = 0; |
316 | int winding = 0; |
317 | for (int i = 0; i < numActive; ++i) { |
318 | QScanConverter::Line *node = m_active.at(i); |
319 | |
320 | const int current = QScFixedToInt(node->x); |
321 | if (winding & m_fillRuleMask) |
322 | m_spanBuffer->addSpan(x, len: current - x, y, coverage: 0xff); |
323 | |
324 | x = current; |
325 | winding += node->winding; |
326 | |
327 | if (node->bottom == y) { |
328 | // remove node from active list |
329 | for (int j = i; j < numActive - 1; ++j) |
330 | m_active.at(i: j) = m_active.at(i: j+1); |
331 | |
332 | m_active.resize(size: --numActive); |
333 | --i; |
334 | } else { |
335 | if constexpr(!AllVertical) |
336 | node->x += node->delta; |
337 | } |
338 | } |
339 | } |
340 | m_active.reset(); |
341 | } |
342 | |
343 | void QScanConverter::end() |
344 | { |
345 | if (m_lines.isEmpty()) |
346 | return; |
347 | |
348 | if (m_lines.size() <= 32) { |
349 | bool allVertical = true; |
350 | for (int i = 0; i < m_lines.size(); ++i) { |
351 | if (m_lines.at(i).delta) { |
352 | allVertical = false; |
353 | break; |
354 | } |
355 | } |
356 | if (allVertical) |
357 | scanConvert<true>(); |
358 | else |
359 | scanConvert<false>(); |
360 | } else { |
361 | for (int chunkTop = m_top; chunkTop <= m_bottom; chunkTop += CHUNK_SIZE) { |
362 | prepareChunk(); |
363 | |
364 | Intersection isect = { .x: 0, .winding: 0, .left: 0, .right: 0 }; |
365 | |
366 | const int chunkBottom = chunkTop + CHUNK_SIZE; |
367 | for (int i = 0; i < m_lines.size(); ++i) { |
368 | Line &line = m_lines.at(i); |
369 | |
370 | if ((line.bottom < chunkTop) || (line.top > chunkBottom)) |
371 | continue; |
372 | |
373 | const int top = qMax(a: 0, b: line.top - chunkTop); |
374 | const int bottom = qMin(CHUNK_SIZE, b: line.bottom + 1 - chunkTop); |
375 | allocate(size: m_size + bottom - top); |
376 | |
377 | isect.winding = line.winding; |
378 | |
379 | Intersection *it = m_intersections + top; |
380 | Intersection *end = m_intersections + bottom; |
381 | |
382 | if (line.delta) { |
383 | for (; it != end; ++it) { |
384 | isect.x = QScFixedToInt(line.x); |
385 | line.x += line.delta; |
386 | mergeIntersection(head: it, isect); |
387 | } |
388 | } else { |
389 | isect.x = QScFixedToInt(line.x); |
390 | for (; it != end; ++it) |
391 | mergeIntersection(head: it, isect); |
392 | } |
393 | } |
394 | |
395 | emitSpans(chunk: chunkTop); |
396 | } |
397 | } |
398 | |
399 | if (m_alloc > 1024) { |
400 | free(ptr: m_intersections); |
401 | m_alloc = 0; |
402 | m_size = 0; |
403 | m_intersections = nullptr; |
404 | } |
405 | |
406 | if (m_lines.size() > 1024) |
407 | m_lines.shrink(size: 1024); |
408 | } |
409 | |
410 | inline void QScanConverter::allocate(int size) |
411 | { |
412 | if (m_alloc < size) { |
413 | int newAlloc = qMax(a: size, b: 2 * m_alloc); |
414 | m_intersections = q_check_ptr(p: (Intersection *)realloc(ptr: m_intersections, size: newAlloc * sizeof(Intersection))); |
415 | m_alloc = newAlloc; |
416 | } |
417 | } |
418 | |
419 | inline void QScanConverter::mergeIntersection(Intersection *it, const Intersection &isect) |
420 | { |
421 | Intersection *current = it; |
422 | |
423 | while (isect.x != current->x) { |
424 | int &next = isect.x < current->x ? current->left : current->right; |
425 | if (next) |
426 | current += next; |
427 | else { |
428 | Intersection *last = m_intersections + m_size; |
429 | next = last - current; |
430 | *last = isect; |
431 | ++m_size; |
432 | return; |
433 | } |
434 | } |
435 | |
436 | current->winding += isect.winding; |
437 | } |
438 | |
439 | void QScanConverter::mergeCurve(const QT_FT_Vector &pa, const QT_FT_Vector &pb, |
440 | const QT_FT_Vector &pc, const QT_FT_Vector &pd) |
441 | { |
442 | // make room for 32 splits |
443 | QT_FT_Vector beziers[4 + 3 * 32]; |
444 | |
445 | QT_FT_Vector *b = beziers; |
446 | |
447 | b[0] = pa; |
448 | b[1] = pb; |
449 | b[2] = pc; |
450 | b[3] = pd; |
451 | |
452 | const QT_FT_Pos flatness = 16; |
453 | |
454 | while (b >= beziers) { |
455 | QT_FT_Vector delta = { .x: b[3].x - b[0].x, .y: b[3].y - b[0].y }; |
456 | QT_FT_Pos l = qAbs(t: delta.x) + qAbs(t: delta.y); |
457 | |
458 | bool belowThreshold; |
459 | if (l > 64) { |
460 | qlonglong d2 = qAbs(t: qlonglong(b[1].x-b[0].x) * qlonglong(delta.y) - |
461 | qlonglong(b[1].y-b[0].y) * qlonglong(delta.x)); |
462 | qlonglong d3 = qAbs(t: qlonglong(b[2].x-b[0].x) * qlonglong(delta.y) - |
463 | qlonglong(b[2].y-b[0].y) * qlonglong(delta.x)); |
464 | |
465 | qlonglong d = d2 + d3; |
466 | |
467 | belowThreshold = (d <= qlonglong(flatness) * qlonglong(l)); |
468 | } else { |
469 | QT_FT_Pos d = qAbs(t: b[0].x-b[1].x) + qAbs(t: b[0].y-b[1].y) + |
470 | qAbs(t: b[0].x-b[2].x) + qAbs(t: b[0].y-b[2].y); |
471 | |
472 | belowThreshold = (d <= flatness); |
473 | } |
474 | |
475 | if (belowThreshold || b == beziers + 3 * 32) { |
476 | mergeLine(a: b[0], b: b[3]); |
477 | b -= 3; |
478 | continue; |
479 | } |
480 | |
481 | split(b); |
482 | b += 3; |
483 | } |
484 | } |
485 | |
486 | inline bool QScanConverter::clip(QScFixed &xFP, int &iTop, int &iBottom, QScFixed slopeFP, QScFixed edgeFP, int winding) |
487 | { |
488 | bool right = edgeFP == m_rightFP; |
489 | |
490 | if (xFP == edgeFP) { |
491 | if ((slopeFP > 0) ^ right) |
492 | return false; |
493 | else { |
494 | Line line = { .x: edgeFP, .delta: 0, .top: iTop, .bottom: iBottom, .winding: winding }; |
495 | m_lines.add(t: line); |
496 | return true; |
497 | } |
498 | } |
499 | |
500 | QScFixed lastFP = xFP + slopeFP * (iBottom - iTop); |
501 | |
502 | if (lastFP == edgeFP) { |
503 | if ((slopeFP < 0) ^ right) |
504 | return false; |
505 | else { |
506 | Line line = { .x: edgeFP, .delta: 0, .top: iTop, .bottom: iBottom, .winding: winding }; |
507 | m_lines.add(t: line); |
508 | return true; |
509 | } |
510 | } |
511 | |
512 | // does line cross edge? |
513 | if ((lastFP < edgeFP) ^ (xFP < edgeFP)) { |
514 | QScFixed deltaY = QScFixed((edgeFP - xFP) / QScFixedToFloat(slopeFP)); |
515 | |
516 | if ((xFP < edgeFP) ^ right) { |
517 | // top segment needs to be clipped |
518 | int iHeight = QScFixedToInt(deltaY + 1); |
519 | int iMiddle = iTop + iHeight; |
520 | |
521 | Line line = { .x: edgeFP, .delta: 0, .top: iTop, .bottom: iMiddle, .winding: winding }; |
522 | m_lines.add(t: line); |
523 | |
524 | if (iMiddle != iBottom) { |
525 | xFP += slopeFP * (iHeight + 1); |
526 | iTop = iMiddle + 1; |
527 | } else |
528 | return true; |
529 | } else { |
530 | // bottom segment needs to be clipped |
531 | int iHeight = QScFixedToInt(deltaY); |
532 | int iMiddle = iTop + iHeight; |
533 | |
534 | if (iMiddle != iBottom) { |
535 | Line line = { .x: edgeFP, .delta: 0, .top: iMiddle + 1, .bottom: iBottom, .winding: winding }; |
536 | m_lines.add(t: line); |
537 | |
538 | iBottom = iMiddle; |
539 | } |
540 | } |
541 | return false; |
542 | } else if ((xFP < edgeFP) ^ right) { |
543 | Line line = { .x: edgeFP, .delta: 0, .top: iTop, .bottom: iBottom, .winding: winding }; |
544 | m_lines.add(t: line); |
545 | return true; |
546 | } |
547 | |
548 | return false; |
549 | } |
550 | |
551 | void QScanConverter::mergeLine(QT_FT_Vector a, QT_FT_Vector b) |
552 | { |
553 | int winding = 1; |
554 | |
555 | if (a.y > b.y) { |
556 | qSwap(value1&: a, value2&: b); |
557 | winding = -1; |
558 | } |
559 | |
560 | int iTop = qMax(a: m_top, b: int((a.y + 32) >> 6)); |
561 | int iBottom = qMin(a: m_bottom, b: int((b.y - 32) >> 6)); |
562 | |
563 | if (iTop <= iBottom) { |
564 | QScFixed aFP = QScFixedFactor/2 + FTPosToQScFixed(a.x); |
565 | |
566 | if (b.x == a.x) { |
567 | Line line = { .x: qBound(min: m_leftFP, val: aFP, max: m_rightFP), .delta: 0, .top: iTop, .bottom: iBottom, .winding: winding }; |
568 | m_lines.add(t: line); |
569 | } else { |
570 | const qreal slope = (b.x - a.x) / qreal(b.y - a.y); |
571 | |
572 | const QScFixed slopeFP = FloatToQScFixed(slope); |
573 | |
574 | QScFixed xFP = aFP + QScFixedMultiply(slopeFP, |
575 | IntToQScFixed(iTop) |
576 | + QScFixedFactor/2 - FTPosToQScFixed(a.y)); |
577 | |
578 | if (clip(xFP, iTop, iBottom, slopeFP, edgeFP: m_leftFP, winding)) |
579 | return; |
580 | |
581 | if (clip(xFP, iTop, iBottom, slopeFP, edgeFP: m_rightFP, winding)) |
582 | return; |
583 | |
584 | Q_ASSERT(xFP >= m_leftFP); |
585 | |
586 | Line line = { .x: xFP, .delta: slopeFP, .top: iTop, .bottom: iBottom, .winding: winding }; |
587 | m_lines.add(t: line); |
588 | } |
589 | } |
590 | } |
591 | |
592 | QRasterizer::QRasterizer() |
593 | : d(new QRasterizerPrivate) |
594 | { |
595 | } |
596 | |
597 | QRasterizer::~QRasterizer() |
598 | { |
599 | delete d; |
600 | } |
601 | |
602 | void QRasterizer::setAntialiased(bool antialiased) |
603 | { |
604 | d->antialiased = antialiased; |
605 | } |
606 | |
607 | void QRasterizer::initialize(ProcessSpans blend, void *data) |
608 | { |
609 | d->blend = blend; |
610 | d->data = data; |
611 | } |
612 | |
613 | void QRasterizer::setClipRect(const QRect &clipRect) |
614 | { |
615 | d->clipRect = clipRect; |
616 | } |
617 | |
618 | static QScFixed intersectPixelFP(int x, QScFixed top, QScFixed bottom, QScFixed leftIntersectX, QScFixed rightIntersectX, QScFixed slope, QScFixed invSlope) |
619 | { |
620 | QScFixed leftX = IntToQScFixed(x); |
621 | QScFixed rightX = IntToQScFixed(x) + QScFixedFactor; |
622 | |
623 | QScFixed leftIntersectY, rightIntersectY; |
624 | auto computeIntersectY = [&]() { |
625 | if (slope > 0) { |
626 | leftIntersectY = top + QScFixedMultiply(leftX - leftIntersectX, invSlope); |
627 | rightIntersectY = leftIntersectY + invSlope; |
628 | } else { |
629 | leftIntersectY = top + QScFixedMultiply(leftX - rightIntersectX, invSlope); |
630 | rightIntersectY = leftIntersectY + invSlope; |
631 | } |
632 | }; |
633 | |
634 | if (leftIntersectX >= leftX && rightIntersectX <= rightX) { |
635 | return QScFixedMultiply(bottom - top, leftIntersectX - leftX + ((rightIntersectX - leftIntersectX) >> 1)); |
636 | } else if (leftIntersectX >= rightX) { |
637 | return bottom - top; |
638 | } else if (leftIntersectX >= leftX) { |
639 | computeIntersectY(); |
640 | if (slope > 0) { |
641 | return (bottom - top) - QScFixedFastMultiply((rightX - leftIntersectX) >> 1, rightIntersectY - top); |
642 | } else { |
643 | return (bottom - top) - QScFixedFastMultiply((rightX - leftIntersectX) >> 1, bottom - rightIntersectY); |
644 | } |
645 | } else if (rightIntersectX <= leftX) { |
646 | return 0; |
647 | } else if (rightIntersectX <= rightX) { |
648 | computeIntersectY(); |
649 | if (slope > 0) { |
650 | return QScFixedFastMultiply((rightIntersectX - leftX) >> 1, bottom - leftIntersectY); |
651 | } else { |
652 | return QScFixedFastMultiply((rightIntersectX - leftX) >> 1, leftIntersectY - top); |
653 | } |
654 | } else { |
655 | computeIntersectY(); |
656 | if (slope > 0) { |
657 | return (bottom - rightIntersectY) + ((rightIntersectY - leftIntersectY) >> 1); |
658 | } else { |
659 | return (rightIntersectY - top) + ((leftIntersectY - rightIntersectY) >> 1); |
660 | } |
661 | } |
662 | } |
663 | |
664 | static inline bool q26Dot6Compare(qreal p1, qreal p2) |
665 | { |
666 | return int((p2 - p1) * 64.) == 0; |
667 | } |
668 | |
669 | static inline QPointF snapTo26Dot6Grid(const QPointF &p) |
670 | { |
671 | return QPointF(std::floor(x: p.x() * 64) * (1 / qreal(64)), |
672 | std::floor(x: p.y() * 64) * (1 / qreal(64))); |
673 | } |
674 | |
675 | /* |
676 | The rasterize line function relies on some div by zero which should |
677 | result in +/-inf values. However, when floating point exceptions are |
678 | enabled, this will cause crashes, so we return high numbers instead. |
679 | As the returned value is used in further arithmetic, returning |
680 | FLT_MAX/DBL_MAX will also cause values, so instead return a value |
681 | that is well outside the int-range. |
682 | */ |
683 | static inline qreal qSafeDivide(qreal x, qreal y) |
684 | { |
685 | if (y == 0) |
686 | return x > 0 ? 1e20 : -1e20; |
687 | return x / y; |
688 | } |
689 | |
690 | /* Conversion to int fails if the value is too large to fit into INT_MAX or |
691 | too small to fit into INT_MIN, so we need this slightly safer conversion |
692 | when floating point exceptions are enabled |
693 | */ |
694 | static inline QScFixed qSafeFloatToQScFixed(qreal x) |
695 | { |
696 | qreal tmp = x * QScFixedFactor; |
697 | #if Q_PROCESSOR_WORDSIZE == 8 |
698 | if (tmp > qreal(INT_MAX) * QScFixedFactor) |
699 | return QScFixed(INT_MAX) * QScFixedFactor; |
700 | else if (tmp < qreal(INT_MIN) * QScFixedFactor) |
701 | return QScFixed(INT_MIN) * QScFixedFactor; |
702 | #else |
703 | if (tmp > qreal(INT_MAX)) |
704 | return INT_MAX; |
705 | else if (tmp < qreal(INT_MIN)) |
706 | return -INT_MAX; |
707 | #endif |
708 | return QScFixed(tmp); |
709 | } |
710 | |
711 | // returns true if the whole line gets clipped away |
712 | static inline bool qClipLine(QPointF *pt1, QPointF *pt2, const QRectF &clip) |
713 | { |
714 | qreal &x1 = pt1->rx(); |
715 | qreal &y1 = pt1->ry(); |
716 | qreal &x2 = pt2->rx(); |
717 | qreal &y2 = pt2->ry(); |
718 | |
719 | if (!qIsFinite(d: x1) || !qIsFinite(d: y1) || !qIsFinite(d: x2) || !qIsFinite(d: y2)) |
720 | return true; |
721 | |
722 | const qreal xmin = clip.left(); |
723 | const qreal xmax = clip.right(); |
724 | const qreal ymin = clip.top(); |
725 | const qreal ymax = clip.bottom(); |
726 | |
727 | if (x1 < xmin) { |
728 | if (x2 <= xmin) |
729 | return true; |
730 | y1 += (y2 - y1) / (x2 - x1) * (xmin - x1); |
731 | x1 = xmin; |
732 | } else if (x1 > xmax) { |
733 | if (x2 >= xmax) |
734 | return true; |
735 | y1 += (y2 - y1) / (x2 - x1) * (xmax - x1); |
736 | x1 = xmax; |
737 | } |
738 | if (x2 < xmin) { |
739 | y2 += (y2 - y1) / (x2 - x1) * (xmin - x2); |
740 | x2 = xmin; |
741 | } else if (x2 > xmax) { |
742 | y2 += (y2 - y1) / (x2 - x1) * (xmax - x2); |
743 | x2 = xmax; |
744 | } |
745 | |
746 | if (y1 < ymin) { |
747 | if (y2 <= ymin) |
748 | return true; |
749 | x1 += (x2 - x1) / (y2 - y1) * (ymin - y1); |
750 | y1 = ymin; |
751 | } else if (y1 > ymax) { |
752 | if (y2 >= ymax) |
753 | return true; |
754 | x1 += (x2 - x1) / (y2 - y1) * (ymax - y1); |
755 | y1 = ymax; |
756 | } |
757 | if (y2 < ymin) { |
758 | x2 += (x2 - x1) / (y2 - y1) * (ymin - y2); |
759 | y2 = ymin; |
760 | } else if (y2 > ymax) { |
761 | x2 += (x2 - x1) / (y2 - y1) * (ymax - y2); |
762 | y2 = ymax; |
763 | } |
764 | |
765 | return false; |
766 | } |
767 | |
768 | void QRasterizer::rasterizeLine(const QPointF &a, const QPointF &b, qreal width, bool squareCap) |
769 | { |
770 | if (a == b || !(width > 0.0) || d->clipRect.isEmpty()) |
771 | return; |
772 | |
773 | QPointF pa = a; |
774 | QPointF pb = b; |
775 | |
776 | if (squareCap) { |
777 | QPointF delta = pb - pa; |
778 | pa -= (0.5f * width) * delta; |
779 | pb += (0.5f * width) * delta; |
780 | } |
781 | |
782 | QPointF offs = QPointF(qAbs(t: b.y() - a.y()), qAbs(t: b.x() - a.x())) * width * 0.5; |
783 | const QRectF clip(d->clipRect.topLeft() - offs, d->clipRect.bottomRight() + QPoint(1, 1) + offs); |
784 | if (qClipLine(pt1: &pa, pt2: &pb, clip)) |
785 | return; |
786 | |
787 | { |
788 | // old delta |
789 | const QPointF d0 = a - b; |
790 | const qreal w0 = d0.x() * d0.x() + d0.y() * d0.y(); |
791 | |
792 | // new delta |
793 | const QPointF d = pa - pb; |
794 | const qreal w = d.x() * d.x() + d.y() * d.y(); |
795 | |
796 | if (w == 0) |
797 | return; |
798 | |
799 | // adjust width which is given relative to |b - a| |
800 | width *= qSqrt(v: w0 / w); |
801 | } |
802 | |
803 | QSpanBuffer buffer(d->blend, d->data, d->clipRect); |
804 | |
805 | if (q26Dot6Compare(p1: pa.y(), p2: pb.y())) { |
806 | const qreal x = (pa.x() + pb.x()) * 0.5f; |
807 | const qreal dx = qAbs(t: pb.x() - pa.x()) * 0.5f; |
808 | |
809 | const qreal y = pa.y(); |
810 | const qreal dy = width * dx; |
811 | |
812 | pa = QPointF(x, y - dy); |
813 | pb = QPointF(x, y + dy); |
814 | |
815 | width = 1 / width; |
816 | } |
817 | |
818 | if (q26Dot6Compare(p1: pa.x(), p2: pb.x())) { |
819 | if (pa.y() > pb.y()) |
820 | qSwap(value1&: pa, value2&: pb); |
821 | |
822 | const qreal dy = pb.y() - pa.y(); |
823 | const qreal halfWidth = 0.5f * width * dy; |
824 | |
825 | qreal left = pa.x() - halfWidth; |
826 | qreal right = pa.x() + halfWidth; |
827 | |
828 | left = qBound(min: qreal(d->clipRect.left()), val: left, max: qreal(d->clipRect.right() + 1)); |
829 | right = qBound(min: qreal(d->clipRect.left()), val: right, max: qreal(d->clipRect.right() + 1)); |
830 | |
831 | pa.ry() = qBound(min: qreal(d->clipRect.top()), val: pa.y(), max: qreal(d->clipRect.bottom() + 1)); |
832 | pb.ry() = qBound(min: qreal(d->clipRect.top()), val: pb.y(), max: qreal(d->clipRect.bottom() + 1)); |
833 | |
834 | if (q26Dot6Compare(p1: left, p2: right) || q26Dot6Compare(p1: pa.y(), p2: pb.y())) |
835 | return; |
836 | |
837 | if (d->antialiased) { |
838 | const int iLeft = int(left); |
839 | const int iRight = int(right); |
840 | const QScFixed leftWidth = IntToQScFixed(iLeft + 1) |
841 | - qSafeFloatToQScFixed(x: left); |
842 | const QScFixed rightWidth = qSafeFloatToQScFixed(x: right) |
843 | - IntToQScFixed(iRight); |
844 | |
845 | QScFixed coverage[3]; |
846 | int x[3]; |
847 | int len[3]; |
848 | |
849 | int n = 1; |
850 | if (iLeft == iRight) { |
851 | if (leftWidth == QScFixedFactor) |
852 | coverage[0] = rightWidth * 255; |
853 | else |
854 | coverage[0] = (rightWidth + leftWidth - QScFixedFactor) * 255; |
855 | x[0] = iLeft; |
856 | len[0] = 1; |
857 | } else { |
858 | coverage[0] = leftWidth * 255; |
859 | x[0] = iLeft; |
860 | len[0] = 1; |
861 | if (leftWidth == QScFixedFactor) { |
862 | len[0] = iRight - iLeft; |
863 | } else if (iRight - iLeft > 1) { |
864 | coverage[1] = IntToQScFixed(255); |
865 | x[1] = iLeft + 1; |
866 | len[1] = iRight - iLeft - 1; |
867 | ++n; |
868 | } |
869 | if (rightWidth) { |
870 | coverage[n] = rightWidth * 255; |
871 | x[n] = iRight; |
872 | len[n] = 1; |
873 | ++n; |
874 | } |
875 | } |
876 | |
877 | const QScFixed iTopFP = IntToQScFixed(int(pa.y())); |
878 | const QScFixed iBottomFP = IntToQScFixed(int(pb.y())); |
879 | const QScFixed yPa = qSafeFloatToQScFixed(x: pa.y()); |
880 | const QScFixed yPb = qSafeFloatToQScFixed(x: pb.y()); |
881 | for (QScFixed yFP = iTopFP; yFP <= iBottomFP; yFP += QScFixedFactor) { |
882 | const QScFixed rowHeight = qMin(a: yFP + QScFixedFactor, b: yPb) |
883 | - qMax(a: yFP, b: yPa); |
884 | const int y = QScFixedToInt(yFP); |
885 | if (y > d->clipRect.bottom()) |
886 | break; |
887 | for (int i = 0; i < n; ++i) { |
888 | buffer.addSpan(x: x[i], len: len[i], y, |
889 | QScFixedToInt(QScFixedMultiply(rowHeight, coverage[i]))); |
890 | } |
891 | } |
892 | } else { // aliased |
893 | int iTop = int(pa.y() + 0.5f); |
894 | int iBottom = pb.y() < 0.5f ? -1 : int(pb.y() - 0.5f); |
895 | int iLeft = int(left + 0.5f); |
896 | int iRight = right < 0.5f ? -1 : int(right - 0.5f); |
897 | |
898 | int iWidth = iRight - iLeft + 1; |
899 | for (int y = iTop; y <= iBottom; ++y) |
900 | buffer.addSpan(x: iLeft, len: iWidth, y, coverage: 255); |
901 | } |
902 | } else { |
903 | if (pa.y() > pb.y()) |
904 | qSwap(value1&: pa, value2&: pb); |
905 | |
906 | QPointF delta = pb - pa; |
907 | delta *= 0.5f * width; |
908 | const QPointF perp(delta.y(), -delta.x()); |
909 | |
910 | QPointF top; |
911 | QPointF left; |
912 | QPointF right; |
913 | QPointF bottom; |
914 | |
915 | if (pa.x() < pb.x()) { |
916 | top = pa + perp; |
917 | left = pa - perp; |
918 | right = pb + perp; |
919 | bottom = pb - perp; |
920 | } else { |
921 | top = pa - perp; |
922 | left = pb - perp; |
923 | right = pa + perp; |
924 | bottom = pb + perp; |
925 | } |
926 | |
927 | top = snapTo26Dot6Grid(p: top); |
928 | bottom = snapTo26Dot6Grid(p: bottom); |
929 | left = snapTo26Dot6Grid(p: left); |
930 | right = snapTo26Dot6Grid(p: right); |
931 | |
932 | const qreal topBound = qBound(min: qreal(d->clipRect.top()), val: top.y(), max: qreal(d->clipRect.bottom())); |
933 | const qreal bottomBound = qBound(min: qreal(d->clipRect.top()), val: bottom.y(), max: qreal(d->clipRect.bottom())); |
934 | |
935 | const QPointF topLeftEdge = left - top; |
936 | const QPointF topRightEdge = right - top; |
937 | const QPointF bottomLeftEdge = bottom - left; |
938 | const QPointF bottomRightEdge = bottom - right; |
939 | |
940 | const qreal topLeftSlope = qSafeDivide(x: topLeftEdge.x(), y: topLeftEdge.y()); |
941 | const qreal bottomLeftSlope = qSafeDivide(x: bottomLeftEdge.x(), y: bottomLeftEdge.y()); |
942 | |
943 | const qreal topRightSlope = qSafeDivide(x: topRightEdge.x(), y: topRightEdge.y()); |
944 | const qreal bottomRightSlope = qSafeDivide(x: bottomRightEdge.x(), y: bottomRightEdge.y()); |
945 | |
946 | const QScFixed topLeftSlopeFP = qSafeFloatToQScFixed(x: topLeftSlope); |
947 | const QScFixed topRightSlopeFP = qSafeFloatToQScFixed(x: topRightSlope); |
948 | |
949 | const QScFixed bottomLeftSlopeFP = qSafeFloatToQScFixed(x: bottomLeftSlope); |
950 | const QScFixed bottomRightSlopeFP = qSafeFloatToQScFixed(x: bottomRightSlope); |
951 | |
952 | const QScFixed invTopLeftSlopeFP = qSafeFloatToQScFixed(x: qSafeDivide(x: 1, y: topLeftSlope)); |
953 | const QScFixed invTopRightSlopeFP = qSafeFloatToQScFixed(x: qSafeDivide(x: 1, y: topRightSlope)); |
954 | |
955 | const QScFixed invBottomLeftSlopeFP = qSafeFloatToQScFixed(x: qSafeDivide(x: 1, y: bottomLeftSlope)); |
956 | const QScFixed invBottomRightSlopeFP = qSafeFloatToQScFixed(x: qSafeDivide(x: 1, y: bottomRightSlope)); |
957 | |
958 | if (d->antialiased) { |
959 | const QScFixed iTopFP = IntToQScFixed(int(topBound)); |
960 | const QScFixed iLeftFP = IntToQScFixed(int(left.y())); |
961 | const QScFixed iRightFP = IntToQScFixed(int(right.y())); |
962 | const QScFixed iBottomFP = IntToQScFixed(int(bottomBound)); |
963 | |
964 | QScFixed leftIntersectAf = qSafeFloatToQScFixed(x: top.x() + (int(topBound) - top.y()) * topLeftSlope); |
965 | QScFixed rightIntersectAf = qSafeFloatToQScFixed(x: top.x() + (int(topBound) - top.y()) * topRightSlope); |
966 | QScFixed leftIntersectBf = 0; |
967 | QScFixed rightIntersectBf = 0; |
968 | |
969 | if (iLeftFP < iTopFP) |
970 | leftIntersectBf = qSafeFloatToQScFixed(x: left.x() + (int(topBound) - left.y()) * bottomLeftSlope); |
971 | |
972 | if (iRightFP < iTopFP) |
973 | rightIntersectBf = qSafeFloatToQScFixed(x: right.x() + (int(topBound) - right.y()) * bottomRightSlope); |
974 | |
975 | QScFixed rowTop, rowBottomLeft, rowBottomRight, rowTopLeft, rowTopRight, rowBottom; |
976 | QScFixed topLeftIntersectAf, topLeftIntersectBf, topRightIntersectAf, topRightIntersectBf; |
977 | QScFixed bottomLeftIntersectAf, bottomLeftIntersectBf, bottomRightIntersectAf, bottomRightIntersectBf; |
978 | |
979 | int leftMin, leftMax, rightMin, rightMax; |
980 | |
981 | const QScFixed yTopFP = qSafeFloatToQScFixed(x: top.y()); |
982 | const QScFixed yLeftFP = qSafeFloatToQScFixed(x: left.y()); |
983 | const QScFixed yRightFP = qSafeFloatToQScFixed(x: right.y()); |
984 | const QScFixed yBottomFP = qSafeFloatToQScFixed(x: bottom.y()); |
985 | |
986 | rowTop = qMax(a: iTopFP, b: yTopFP); |
987 | topLeftIntersectAf = leftIntersectAf + |
988 | QScFixedMultiply(topLeftSlopeFP, rowTop - iTopFP); |
989 | topRightIntersectAf = rightIntersectAf + |
990 | QScFixedMultiply(topRightSlopeFP, rowTop - iTopFP); |
991 | |
992 | QScFixed yFP = iTopFP; |
993 | while (yFP <= iBottomFP) { |
994 | rowBottomLeft = qMin(a: yFP + QScFixedFactor, b: yLeftFP); |
995 | rowBottomRight = qMin(a: yFP + QScFixedFactor, b: yRightFP); |
996 | rowTopLeft = qMax(a: yFP, b: yLeftFP); |
997 | rowTopRight = qMax(a: yFP, b: yRightFP); |
998 | rowBottom = qMin(a: yFP + QScFixedFactor, b: yBottomFP); |
999 | |
1000 | if (yFP == iLeftFP) { |
1001 | const int y = QScFixedToInt(yFP); |
1002 | leftIntersectBf = qSafeFloatToQScFixed(x: left.x() + (y - left.y()) * bottomLeftSlope); |
1003 | topLeftIntersectBf = leftIntersectBf + QScFixedMultiply(bottomLeftSlopeFP, rowTopLeft - yFP); |
1004 | bottomLeftIntersectAf = leftIntersectAf + QScFixedMultiply(topLeftSlopeFP, rowBottomLeft - yFP); |
1005 | } else { |
1006 | topLeftIntersectBf = leftIntersectBf; |
1007 | bottomLeftIntersectAf = leftIntersectAf + topLeftSlopeFP; |
1008 | } |
1009 | |
1010 | if (yFP == iRightFP) { |
1011 | const int y = QScFixedToInt(yFP); |
1012 | rightIntersectBf = qSafeFloatToQScFixed(x: right.x() + (y - right.y()) * bottomRightSlope); |
1013 | topRightIntersectBf = rightIntersectBf + QScFixedMultiply(bottomRightSlopeFP, rowTopRight - yFP); |
1014 | bottomRightIntersectAf = rightIntersectAf + QScFixedMultiply(topRightSlopeFP, rowBottomRight - yFP); |
1015 | } else { |
1016 | topRightIntersectBf = rightIntersectBf; |
1017 | bottomRightIntersectAf = rightIntersectAf + topRightSlopeFP; |
1018 | } |
1019 | |
1020 | if (yFP == iBottomFP) { |
1021 | bottomLeftIntersectBf = leftIntersectBf + QScFixedMultiply(bottomLeftSlopeFP, rowBottom - yFP); |
1022 | bottomRightIntersectBf = rightIntersectBf + QScFixedMultiply(bottomRightSlopeFP, rowBottom - yFP); |
1023 | } else { |
1024 | bottomLeftIntersectBf = leftIntersectBf + bottomLeftSlopeFP; |
1025 | bottomRightIntersectBf = rightIntersectBf + bottomRightSlopeFP; |
1026 | } |
1027 | |
1028 | if (yFP < iLeftFP) { |
1029 | leftMin = QScFixedToInt(bottomLeftIntersectAf); |
1030 | leftMax = QScFixedToInt(topLeftIntersectAf); |
1031 | } else if (yFP == iLeftFP) { |
1032 | leftMin = QScFixedToInt(qMax(bottomLeftIntersectAf, topLeftIntersectBf)); |
1033 | leftMax = QScFixedToInt(qMax(topLeftIntersectAf, bottomLeftIntersectBf)); |
1034 | } else { |
1035 | leftMin = QScFixedToInt(topLeftIntersectBf); |
1036 | leftMax = QScFixedToInt(bottomLeftIntersectBf); |
1037 | } |
1038 | |
1039 | leftMin = qBound(min: d->clipRect.left(), val: leftMin, max: d->clipRect.right()); |
1040 | leftMax = qBound(min: d->clipRect.left(), val: leftMax, max: d->clipRect.right()); |
1041 | |
1042 | if (yFP < iRightFP) { |
1043 | rightMin = QScFixedToInt(topRightIntersectAf); |
1044 | rightMax = QScFixedToInt(bottomRightIntersectAf); |
1045 | } else if (yFP == iRightFP) { |
1046 | rightMin = QScFixedToInt(qMin(topRightIntersectAf, bottomRightIntersectBf)); |
1047 | rightMax = QScFixedToInt(qMin(bottomRightIntersectAf, topRightIntersectBf)); |
1048 | } else { |
1049 | rightMin = QScFixedToInt(bottomRightIntersectBf); |
1050 | rightMax = QScFixedToInt(topRightIntersectBf); |
1051 | } |
1052 | |
1053 | rightMin = qBound(min: d->clipRect.left(), val: rightMin, max: d->clipRect.right()); |
1054 | rightMax = qBound(min: d->clipRect.left(), val: rightMax, max: d->clipRect.right()); |
1055 | |
1056 | if (leftMax > rightMax) |
1057 | leftMax = rightMax; |
1058 | if (rightMin < leftMin) |
1059 | rightMin = leftMin; |
1060 | |
1061 | QScFixed rowHeight = rowBottom - rowTop; |
1062 | |
1063 | int x = leftMin; |
1064 | while (x <= leftMax) { |
1065 | QScFixed excluded = 0; |
1066 | |
1067 | if (yFP <= iLeftFP && rowBottomLeft > rowTop) |
1068 | excluded += intersectPixelFP(x, top: rowTop, bottom: rowBottomLeft, |
1069 | leftIntersectX: bottomLeftIntersectAf, rightIntersectX: topLeftIntersectAf, |
1070 | slope: topLeftSlopeFP, invSlope: invTopLeftSlopeFP); |
1071 | if (yFP >= iLeftFP && rowBottom > rowTopLeft) |
1072 | excluded += intersectPixelFP(x, top: rowTopLeft, bottom: rowBottom, |
1073 | leftIntersectX: topLeftIntersectBf, rightIntersectX: bottomLeftIntersectBf, |
1074 | slope: bottomLeftSlopeFP, invSlope: invBottomLeftSlopeFP); |
1075 | if (x >= rightMin) { |
1076 | if (yFP <= iRightFP && rowBottomRight > rowTop) |
1077 | excluded += (rowBottomRight - rowTop) - intersectPixelFP(x, top: rowTop, bottom: rowBottomRight, |
1078 | leftIntersectX: topRightIntersectAf, rightIntersectX: bottomRightIntersectAf, |
1079 | slope: topRightSlopeFP, invSlope: invTopRightSlopeFP); |
1080 | if (yFP >= iRightFP && rowBottom > rowTopRight) |
1081 | excluded += (rowBottom - rowTopRight) - intersectPixelFP(x, top: rowTopRight, bottom: rowBottom, |
1082 | leftIntersectX: bottomRightIntersectBf, rightIntersectX: topRightIntersectBf, |
1083 | slope: bottomRightSlopeFP, invSlope: invBottomRightSlopeFP); |
1084 | } |
1085 | |
1086 | Q_ASSERT(excluded >= 0 && excluded <= rowHeight); |
1087 | QScFixed coverage = rowHeight - excluded; |
1088 | buffer.addSpan(x, len: 1, QScFixedToInt(yFP), |
1089 | QScFixedToInt(255 * coverage)); |
1090 | ++x; |
1091 | } |
1092 | if (x < rightMin) { |
1093 | buffer.addSpan(x, len: rightMin - x, QScFixedToInt(yFP), |
1094 | QScFixedToInt(255 * rowHeight)); |
1095 | x = rightMin; |
1096 | } |
1097 | while (x <= rightMax) { |
1098 | QScFixed excluded = 0; |
1099 | if (yFP <= iRightFP && rowBottomRight > rowTop) |
1100 | excluded += (rowBottomRight - rowTop) - intersectPixelFP(x, top: rowTop, bottom: rowBottomRight, |
1101 | leftIntersectX: topRightIntersectAf, rightIntersectX: bottomRightIntersectAf, |
1102 | slope: topRightSlopeFP, invSlope: invTopRightSlopeFP); |
1103 | if (yFP >= iRightFP && rowBottom > rowTopRight) |
1104 | excluded += (rowBottom - rowTopRight) - intersectPixelFP(x, top: rowTopRight, bottom: rowBottom, |
1105 | leftIntersectX: bottomRightIntersectBf, rightIntersectX: topRightIntersectBf, |
1106 | slope: bottomRightSlopeFP, invSlope: invBottomRightSlopeFP); |
1107 | |
1108 | Q_ASSERT(excluded >= 0 && excluded <= rowHeight); |
1109 | QScFixed coverage = rowHeight - excluded; |
1110 | buffer.addSpan(x, len: 1, QScFixedToInt(yFP), |
1111 | QScFixedToInt(255 * coverage)); |
1112 | ++x; |
1113 | } |
1114 | |
1115 | leftIntersectAf += topLeftSlopeFP; |
1116 | leftIntersectBf += bottomLeftSlopeFP; |
1117 | rightIntersectAf += topRightSlopeFP; |
1118 | rightIntersectBf += bottomRightSlopeFP; |
1119 | topLeftIntersectAf = leftIntersectAf; |
1120 | topRightIntersectAf = rightIntersectAf; |
1121 | |
1122 | yFP += QScFixedFactor; |
1123 | rowTop = yFP; |
1124 | } |
1125 | } else { // aliased |
1126 | int iTop = int(top.y() + 0.5f); |
1127 | int iLeft = left.y() < 0.5f ? -1 : int(left.y() - 0.5f); |
1128 | int iRight = right.y() < 0.5f ? -1 : int(right.y() - 0.5f); |
1129 | int iBottom = bottom.y() < 0.5f? -1 : int(bottom.y() - 0.5f); |
1130 | int iMiddle = qMin(a: iLeft, b: iRight); |
1131 | |
1132 | QScFixed leftIntersectAf = qSafeFloatToQScFixed(x: top.x() + 0.5f + (iTop + 0.5f - top.y()) * topLeftSlope); |
1133 | QScFixed leftIntersectBf = qSafeFloatToQScFixed(x: left.x() + 0.5f + (iLeft + 1.5f - left.y()) * bottomLeftSlope); |
1134 | QScFixed rightIntersectAf = qSafeFloatToQScFixed(x: top.x() - 0.5f + (iTop + 0.5f - top.y()) * topRightSlope); |
1135 | QScFixed rightIntersectBf = qSafeFloatToQScFixed(x: right.x() - 0.5f + (iRight + 1.5f - right.y()) * bottomRightSlope); |
1136 | |
1137 | int ny; |
1138 | int y = iTop; |
1139 | #define DO_SEGMENT(next, li, ri, ls, rs) \ |
1140 | ny = qMin(next + 1, d->clipRect.top()); \ |
1141 | if (y < ny) { \ |
1142 | li += ls * (ny - y); \ |
1143 | ri += rs * (ny - y); \ |
1144 | y = ny; \ |
1145 | } \ |
1146 | if (next > d->clipRect.bottom()) \ |
1147 | next = d->clipRect.bottom(); \ |
1148 | for (; y <= next; ++y) { \ |
1149 | const int x1 = qMax(QScFixedToInt(li), d->clipRect.left()); \ |
1150 | const int x2 = qMin(QScFixedToInt(ri), d->clipRect.right()); \ |
1151 | if (x2 >= x1) \ |
1152 | buffer.addSpan(x1, x2 - x1 + 1, y, 255); \ |
1153 | li += ls; \ |
1154 | ri += rs; \ |
1155 | } |
1156 | |
1157 | DO_SEGMENT(iMiddle, leftIntersectAf, rightIntersectAf, topLeftSlopeFP, topRightSlopeFP) |
1158 | DO_SEGMENT(iRight, leftIntersectBf, rightIntersectAf, bottomLeftSlopeFP, topRightSlopeFP) |
1159 | DO_SEGMENT(iLeft, leftIntersectAf, rightIntersectBf, topLeftSlopeFP, bottomRightSlopeFP); |
1160 | DO_SEGMENT(iBottom, leftIntersectBf, rightIntersectBf, bottomLeftSlopeFP, bottomRightSlopeFP); |
1161 | #undef DO_SEGMENT |
1162 | } |
1163 | } |
1164 | } |
1165 | |
1166 | void QRasterizer::rasterize(const QT_FT_Outline *outline, Qt::FillRule fillRule) |
1167 | { |
1168 | if (outline->n_points < 3 || outline->n_contours == 0) |
1169 | return; |
1170 | |
1171 | const QT_FT_Vector *points = outline->points; |
1172 | |
1173 | QSpanBuffer buffer(d->blend, d->data, d->clipRect); |
1174 | |
1175 | // ### QT_FT_Outline already has a bounding rect which is |
1176 | // ### precomputed at this point, so we should probably just be |
1177 | // ### using that instead... |
1178 | QT_FT_Pos min_y = points[0].y, max_y = points[0].y; |
1179 | for (int i = 1; i < outline->n_points; ++i) { |
1180 | const QT_FT_Vector &p = points[i]; |
1181 | min_y = qMin(a: p.y, b: min_y); |
1182 | max_y = qMax(a: p.y, b: max_y); |
1183 | } |
1184 | |
1185 | int iTopBound = qMax(a: d->clipRect.top(), b: int((min_y + 32) >> 6)); |
1186 | int iBottomBound = qMin(a: d->clipRect.bottom(), b: int((max_y - 32) >> 6)); |
1187 | |
1188 | if (iTopBound > iBottomBound) |
1189 | return; |
1190 | |
1191 | d->scanConverter.begin(top: iTopBound, bottom: iBottomBound, left: d->clipRect.left(), right: d->clipRect.right(), fillRule, spanBuffer: &buffer); |
1192 | |
1193 | int first = 0; |
1194 | for (int i = 0; i < outline->n_contours; ++i) { |
1195 | const int last = outline->contours[i]; |
1196 | for (int j = first; j < last; ++j) { |
1197 | if (outline->tags[j+1] == QT_FT_CURVE_TAG_CUBIC) { |
1198 | Q_ASSERT(outline->tags[j+2] == QT_FT_CURVE_TAG_CUBIC); |
1199 | d->scanConverter.mergeCurve(pa: points[j], pb: points[j+1], pc: points[j+2], pd: points[j+3]); |
1200 | j += 2; |
1201 | } else { |
1202 | d->scanConverter.mergeLine(a: points[j], b: points[j+1]); |
1203 | } |
1204 | } |
1205 | |
1206 | first = last + 1; |
1207 | } |
1208 | |
1209 | d->scanConverter.end(); |
1210 | } |
1211 | |
1212 | void QRasterizer::rasterize(const QPainterPath &path, Qt::FillRule fillRule) |
1213 | { |
1214 | if (path.isEmpty()) |
1215 | return; |
1216 | |
1217 | QSpanBuffer buffer(d->blend, d->data, d->clipRect); |
1218 | |
1219 | QRectF bounds = path.controlPointRect(); |
1220 | |
1221 | int iTopBound = qMax(a: d->clipRect.top(), b: int(bounds.top() + 0.5)); |
1222 | int iBottomBound = qMin(a: d->clipRect.bottom(), b: int(bounds.bottom() - 0.5)); |
1223 | |
1224 | if (iTopBound > iBottomBound) |
1225 | return; |
1226 | |
1227 | d->scanConverter.begin(top: iTopBound, bottom: iBottomBound, left: d->clipRect.left(), right: d->clipRect.right(), fillRule, spanBuffer: &buffer); |
1228 | |
1229 | int subpathStart = 0; |
1230 | QT_FT_Vector last = { .x: 0, .y: 0 }; |
1231 | for (int i = 0; i < path.elementCount(); ++i) { |
1232 | switch (path.elementAt(i).type) { |
1233 | case QPainterPath::LineToElement: |
1234 | { |
1235 | QT_FT_Vector p1 = last; |
1236 | QT_FT_Vector p2 = PointToVector(p: path.elementAt(i)); |
1237 | d->scanConverter.mergeLine(a: p1, b: p2); |
1238 | last = p2; |
1239 | break; |
1240 | } |
1241 | case QPainterPath::MoveToElement: |
1242 | { |
1243 | if (i != 0) { |
1244 | QT_FT_Vector first = PointToVector(p: path.elementAt(i: subpathStart)); |
1245 | // close previous subpath |
1246 | if (first.x != last.x || first.y != last.y) |
1247 | d->scanConverter.mergeLine(a: last, b: first); |
1248 | } |
1249 | subpathStart = i; |
1250 | last = PointToVector(p: path.elementAt(i)); |
1251 | break; |
1252 | } |
1253 | case QPainterPath::CurveToElement: |
1254 | { |
1255 | QT_FT_Vector p1 = last; |
1256 | QT_FT_Vector p2 = PointToVector(p: path.elementAt(i)); |
1257 | QT_FT_Vector p3 = PointToVector(p: path.elementAt(i: ++i)); |
1258 | QT_FT_Vector p4 = PointToVector(p: path.elementAt(i: ++i)); |
1259 | d->scanConverter.mergeCurve(pa: p1, pb: p2, pc: p3, pd: p4); |
1260 | last = p4; |
1261 | break; |
1262 | } |
1263 | default: |
1264 | Q_ASSERT(false); |
1265 | break; |
1266 | } |
1267 | } |
1268 | |
1269 | QT_FT_Vector first = PointToVector(p: path.elementAt(i: subpathStart)); |
1270 | |
1271 | // close path |
1272 | if (first.x != last.x || first.y != last.y) |
1273 | d->scanConverter.mergeLine(a: last, b: first); |
1274 | |
1275 | d->scanConverter.end(); |
1276 | } |
1277 | |
1278 | QT_END_NAMESPACE |
1279 | |