1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2017 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the examples of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:BSD$ |
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 | ** BSD License Usage |
18 | ** Alternatively, you may use this file under the terms of the BSD license |
19 | ** as follows: |
20 | ** |
21 | ** "Redistribution and use in source and binary forms, with or without |
22 | ** modification, are permitted provided that the following conditions are |
23 | ** met: |
24 | ** * Redistributions of source code must retain the above copyright |
25 | ** notice, this list of conditions and the following disclaimer. |
26 | ** * Redistributions in binary form must reproduce the above copyright |
27 | ** notice, this list of conditions and the following disclaimer in |
28 | ** the documentation and/or other materials provided with the |
29 | ** distribution. |
30 | ** * Neither the name of The Qt Company Ltd nor the names of its |
31 | ** contributors may be used to endorse or promote products derived |
32 | ** from this software without specific prior written permission. |
33 | ** |
34 | ** |
35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." |
46 | ** |
47 | ** $QT_END_LICENSE$ |
48 | ** |
49 | ****************************************************************************/ |
50 | |
51 | #include "context2d.h" |
52 | |
53 | #include <QVariant> |
54 | #include <qmath.h> |
55 | |
56 | #define qClamp(val, min, max) qMin(qMax(val, min), max) |
57 | static QList<qreal> parseNumbersList(QString::const_iterator &itr) |
58 | { |
59 | QList<qreal> points; |
60 | QString temp; |
61 | while ((*itr).isSpace()) |
62 | ++itr; |
63 | while ((*itr).isNumber() || |
64 | (*itr) == '-' || (*itr) == '+' || (*itr) == '.') { |
65 | temp = QString(); |
66 | |
67 | if ((*itr) == '-') |
68 | temp += *itr++; |
69 | else if ((*itr) == '+') |
70 | temp += *itr++; |
71 | while ((*itr).isDigit()) |
72 | temp += *itr++; |
73 | if ((*itr) == '.') |
74 | temp += *itr++; |
75 | while ((*itr).isDigit()) |
76 | temp += *itr++; |
77 | while ((*itr).isSpace()) |
78 | ++itr; |
79 | if ((*itr) == ',') |
80 | ++itr; |
81 | points.append(t: temp.toDouble()); |
82 | //eat spaces |
83 | while ((*itr).isSpace()) |
84 | ++itr; |
85 | } |
86 | |
87 | return points; |
88 | } |
89 | |
90 | QColor colorFromString(const QString &name) |
91 | { |
92 | QString::const_iterator itr = name.constBegin(); |
93 | QList<qreal> compo; |
94 | if (name.startsWith(s: "rgba(" )) { |
95 | ++itr; ++itr; ++itr; ++itr; ++itr; |
96 | compo = parseNumbersList(itr); |
97 | if (compo.size() != 4) { |
98 | return QColor(); |
99 | } |
100 | //alpha seems to be always between 0-1 |
101 | compo[3] *= 255; |
102 | return QColor((int)compo[0], (int)compo[1], |
103 | (int)compo[2], (int)compo[3]); |
104 | } else if (name.startsWith(s: "rgb(" )) { |
105 | ++itr; ++itr; ++itr; ++itr; |
106 | compo = parseNumbersList(itr); |
107 | if (compo.size() != 3) { |
108 | return QColor(); |
109 | } |
110 | return QColor((int)qClamp(compo[0], qreal(0), qreal(255)), |
111 | (int)qClamp(compo[1], qreal(0), qreal(255)), |
112 | (int)qClamp(compo[2], qreal(0), qreal(255))); |
113 | } else { |
114 | //QRgb color; |
115 | //CSSParser::parseColor(name, color); |
116 | return QColor(name); |
117 | } |
118 | } |
119 | |
120 | |
121 | static QPainter::CompositionMode compositeOperatorFromString(const QString &compositeOperator) |
122 | { |
123 | if ( compositeOperator == "source-over" ) { |
124 | return QPainter::CompositionMode_SourceOver; |
125 | } else if ( compositeOperator == "source-out" ) { |
126 | return QPainter::CompositionMode_SourceOut; |
127 | } else if ( compositeOperator == "source-in" ) { |
128 | return QPainter::CompositionMode_SourceIn; |
129 | } else if ( compositeOperator == "source-atop" ) { |
130 | return QPainter::CompositionMode_SourceAtop; |
131 | } else if ( compositeOperator == "destination-atop" ) { |
132 | return QPainter::CompositionMode_DestinationAtop; |
133 | } else if ( compositeOperator == "destination-in" ) { |
134 | return QPainter::CompositionMode_DestinationIn; |
135 | } else if ( compositeOperator == "destination-out" ) { |
136 | return QPainter::CompositionMode_DestinationOut; |
137 | } else if ( compositeOperator == "destination-over" ) { |
138 | return QPainter::CompositionMode_DestinationOver; |
139 | } else if ( compositeOperator == "darker" ) { |
140 | return QPainter::CompositionMode_SourceOver; |
141 | } else if ( compositeOperator == "lighter" ) { |
142 | return QPainter::CompositionMode_SourceOver; |
143 | } else if ( compositeOperator == "copy" ) { |
144 | return QPainter::CompositionMode_Source; |
145 | } else if ( compositeOperator == "xor" ) { |
146 | return QPainter::CompositionMode_Xor; |
147 | } |
148 | |
149 | return QPainter::CompositionMode_SourceOver; |
150 | } |
151 | |
152 | static QString compositeOperatorToString(QPainter::CompositionMode op) |
153 | { |
154 | switch (op) { |
155 | case QPainter::CompositionMode_SourceOver: |
156 | return "source-over" ; |
157 | case QPainter::CompositionMode_DestinationOver: |
158 | return "destination-over" ; |
159 | case QPainter::CompositionMode_Clear: |
160 | return "clear" ; |
161 | case QPainter::CompositionMode_Source: |
162 | return "source" ; |
163 | case QPainter::CompositionMode_Destination: |
164 | return "destination" ; |
165 | case QPainter::CompositionMode_SourceIn: |
166 | return "source-in" ; |
167 | case QPainter::CompositionMode_DestinationIn: |
168 | return "destination-in" ; |
169 | case QPainter::CompositionMode_SourceOut: |
170 | return "source-out" ; |
171 | case QPainter::CompositionMode_DestinationOut: |
172 | return "destination-out" ; |
173 | case QPainter::CompositionMode_SourceAtop: |
174 | return "source-atop" ; |
175 | case QPainter::CompositionMode_DestinationAtop: |
176 | return "destination-atop" ; |
177 | case QPainter::CompositionMode_Xor: |
178 | return "xor" ; |
179 | case QPainter::CompositionMode_Plus: |
180 | return "plus" ; |
181 | case QPainter::CompositionMode_Multiply: |
182 | return "multiply" ; |
183 | case QPainter::CompositionMode_Screen: |
184 | return "screen" ; |
185 | case QPainter::CompositionMode_Overlay: |
186 | return "overlay" ; |
187 | case QPainter::CompositionMode_Darken: |
188 | return "darken" ; |
189 | case QPainter::CompositionMode_Lighten: |
190 | return "lighten" ; |
191 | case QPainter::CompositionMode_ColorDodge: |
192 | return "color-dodge" ; |
193 | case QPainter::CompositionMode_ColorBurn: |
194 | return "color-burn" ; |
195 | case QPainter::CompositionMode_HardLight: |
196 | return "hard-light" ; |
197 | case QPainter::CompositionMode_SoftLight: |
198 | return "soft-light" ; |
199 | case QPainter::CompositionMode_Difference: |
200 | return "difference" ; |
201 | case QPainter::CompositionMode_Exclusion: |
202 | return "exclusion" ; |
203 | default: |
204 | break; |
205 | } |
206 | return QString(); |
207 | } |
208 | |
209 | void Context2D::save() |
210 | { |
211 | m_stateStack.push(t: m_state); |
212 | } |
213 | |
214 | |
215 | void Context2D::restore() |
216 | { |
217 | if (!m_stateStack.isEmpty()) { |
218 | m_state = m_stateStack.pop(); |
219 | m_state.flags = AllIsFullOfDirt; |
220 | } |
221 | } |
222 | |
223 | |
224 | void Context2D::scale(qreal x, qreal y) |
225 | { |
226 | m_state.matrix.scale(sx: x, sy: y); |
227 | m_state.flags |= DirtyTransformationMatrix; |
228 | } |
229 | |
230 | |
231 | void Context2D::rotate(qreal angle) |
232 | { |
233 | m_state.matrix.rotate(a: qRadiansToDegrees(radians: angle)); |
234 | m_state.flags |= DirtyTransformationMatrix; |
235 | } |
236 | |
237 | |
238 | void Context2D::translate(qreal x, qreal y) |
239 | { |
240 | m_state.matrix.translate(dx: x, dy: y); |
241 | m_state.flags |= DirtyTransformationMatrix; |
242 | } |
243 | |
244 | |
245 | void Context2D::transform(qreal m11, qreal m12, qreal m21, qreal m22, |
246 | qreal dx, qreal dy) |
247 | { |
248 | QTransform mat(m11, m12, |
249 | m21, m22, |
250 | dx, dy); |
251 | m_state.matrix *= mat; |
252 | m_state.flags |= DirtyTransformationMatrix; |
253 | } |
254 | |
255 | |
256 | void Context2D::setTransform(qreal m11, qreal m12, qreal m21, qreal m22, |
257 | qreal dx, qreal dy) |
258 | { |
259 | QTransform mat(m11, m12, |
260 | m21, m22, |
261 | dx, dy); |
262 | m_state.matrix = mat; |
263 | m_state.flags |= DirtyTransformationMatrix; |
264 | } |
265 | |
266 | |
267 | QString Context2D::globalCompositeOperation() const |
268 | { |
269 | return compositeOperatorToString(op: m_state.globalCompositeOperation); |
270 | } |
271 | |
272 | void Context2D::setGlobalCompositeOperation(const QString &op) |
273 | { |
274 | QPainter::CompositionMode mode = |
275 | compositeOperatorFromString(compositeOperator: op); |
276 | m_state.globalCompositeOperation = mode; |
277 | m_state.flags |= DirtyGlobalCompositeOperation; |
278 | } |
279 | |
280 | QVariant Context2D::strokeStyle() const |
281 | { |
282 | return m_state.strokeStyle; |
283 | } |
284 | |
285 | void Context2D::setStrokeStyle(const QVariant &style) |
286 | { |
287 | if (style.canConvert<CanvasGradient>()) { |
288 | CanvasGradient cg = qvariant_cast<CanvasGradient>(v: style); |
289 | m_state.strokeStyle = cg.value; |
290 | } else { |
291 | QColor color = colorFromString(name: style.toString()); |
292 | m_state.strokeStyle = color; |
293 | } |
294 | m_state.flags |= DirtyStrokeStyle; |
295 | } |
296 | |
297 | QVariant Context2D::fillStyle() const |
298 | { |
299 | return m_state.fillStyle; |
300 | } |
301 | |
302 | //! [3] |
303 | void Context2D::setFillStyle(const QVariant &style) |
304 | { |
305 | if (style.canConvert<CanvasGradient>()) { |
306 | CanvasGradient cg = qvariant_cast<CanvasGradient>(v: style); |
307 | m_state.fillStyle = cg.value; |
308 | } else { |
309 | QColor color = colorFromString(name: style.toString()); |
310 | m_state.fillStyle = color; |
311 | } |
312 | m_state.flags |= DirtyFillStyle; |
313 | } |
314 | //! [3] |
315 | |
316 | qreal Context2D::globalAlpha() const |
317 | { |
318 | return m_state.globalAlpha; |
319 | } |
320 | |
321 | void Context2D::setGlobalAlpha(qreal alpha) |
322 | { |
323 | m_state.globalAlpha = alpha; |
324 | m_state.flags |= DirtyGlobalAlpha; |
325 | } |
326 | |
327 | |
328 | CanvasGradient Context2D::createLinearGradient(qreal x0, qreal y0, |
329 | qreal x1, qreal y1) |
330 | { |
331 | QLinearGradient g(x0, y0, x1, y1); |
332 | return CanvasGradient(g); |
333 | } |
334 | |
335 | |
336 | CanvasGradient Context2D::createRadialGradient(qreal x0, qreal y0, |
337 | qreal r0, qreal x1, |
338 | qreal y1, qreal r1) |
339 | { |
340 | QRadialGradient g(QPointF(x1, y1), r0+r1, QPointF(x0, y0)); |
341 | return CanvasGradient(g); |
342 | } |
343 | |
344 | qreal Context2D::lineWidth() const |
345 | { |
346 | return m_state.lineWidth; |
347 | } |
348 | |
349 | void Context2D::setLineWidth(qreal w) |
350 | { |
351 | m_state.lineWidth = w; |
352 | m_state.flags |= DirtyLineWidth; |
353 | } |
354 | |
355 | //! [0] |
356 | QString Context2D::lineCap() const |
357 | { |
358 | switch (m_state.lineCap) { |
359 | case Qt::FlatCap: |
360 | return "butt" ; |
361 | case Qt::SquareCap: |
362 | return "square" ; |
363 | case Qt::RoundCap: |
364 | return "round" ; |
365 | default: ; |
366 | } |
367 | return QString(); |
368 | } |
369 | |
370 | void Context2D::setLineCap(const QString &capString) |
371 | { |
372 | Qt::PenCapStyle style; |
373 | if (capString == "round" ) |
374 | style = Qt::RoundCap; |
375 | else if (capString == "square" ) |
376 | style = Qt::SquareCap; |
377 | else //if (capString == "butt") |
378 | style = Qt::FlatCap; |
379 | m_state.lineCap = style; |
380 | m_state.flags |= DirtyLineCap; |
381 | } |
382 | //! [0] |
383 | |
384 | QString Context2D::lineJoin() const |
385 | { |
386 | switch (m_state.lineJoin) { |
387 | case Qt::RoundJoin: |
388 | return "round" ; |
389 | case Qt::BevelJoin: |
390 | return "bevel" ; |
391 | case Qt::MiterJoin: |
392 | return "miter" ; |
393 | default: ; |
394 | } |
395 | return QString(); |
396 | } |
397 | |
398 | void Context2D::setLineJoin(const QString &joinString) |
399 | { |
400 | Qt::PenJoinStyle style; |
401 | if (joinString == "round" ) |
402 | style = Qt::RoundJoin; |
403 | else if (joinString == "bevel" ) |
404 | style = Qt::BevelJoin; |
405 | else //if (joinString == "miter") |
406 | style = Qt::MiterJoin; |
407 | m_state.lineJoin = style; |
408 | m_state.flags |= DirtyLineJoin; |
409 | } |
410 | |
411 | qreal Context2D::miterLimit() const |
412 | { |
413 | return m_state.miterLimit; |
414 | } |
415 | |
416 | void Context2D::setMiterLimit(qreal m) |
417 | { |
418 | m_state.miterLimit = m; |
419 | m_state.flags |= DirtyMiterLimit; |
420 | } |
421 | |
422 | void Context2D::setShadowOffsetX(qreal x) |
423 | { |
424 | m_state.shadowOffsetX = x; |
425 | m_state.flags |= DirtyShadowOffsetX; |
426 | } |
427 | |
428 | void Context2D::setShadowOffsetY(qreal y) |
429 | { |
430 | m_state.shadowOffsetY = y; |
431 | m_state.flags |= DirtyShadowOffsetY; |
432 | } |
433 | |
434 | void Context2D::setShadowBlur(qreal b) |
435 | { |
436 | m_state.shadowBlur = b; |
437 | m_state.flags |= DirtyShadowBlur; |
438 | } |
439 | |
440 | void Context2D::setShadowColor(const QString &str) |
441 | { |
442 | m_state.shadowColor = colorFromString(name: str); |
443 | m_state.flags |= DirtyShadowColor; |
444 | } |
445 | |
446 | qreal Context2D::shadowOffsetX() const |
447 | { |
448 | return m_state.shadowOffsetX; |
449 | } |
450 | |
451 | qreal Context2D::shadowOffsetY() const |
452 | { |
453 | return m_state.shadowOffsetY; |
454 | } |
455 | |
456 | |
457 | qreal Context2D::shadowBlur() const |
458 | { |
459 | return m_state.shadowBlur; |
460 | } |
461 | |
462 | |
463 | QString Context2D::shadowColor() const |
464 | { |
465 | return m_state.shadowColor.name(); |
466 | } |
467 | |
468 | |
469 | void Context2D::clearRect(qreal x, qreal y, qreal w, qreal h) |
470 | { |
471 | beginPainting(); |
472 | m_painter.save(); |
473 | m_painter.setTransform(transform: QTransform(m_state.matrix), combine: false); |
474 | m_painter.setCompositionMode(QPainter::CompositionMode_Source); |
475 | m_painter.fillRect(QRectF(x, y, w, h), color: QColor(0, 0, 0, 0)); |
476 | m_painter.restore(); |
477 | scheduleChange(); |
478 | } |
479 | |
480 | |
481 | //! [1] |
482 | void Context2D::fillRect(qreal x, qreal y, qreal w, qreal h) |
483 | { |
484 | beginPainting(); |
485 | m_painter.save(); |
486 | m_painter.setTransform(transform: QTransform(m_state.matrix), combine: false); |
487 | m_painter.fillRect(QRectF(x, y, w, h), m_painter.brush()); |
488 | m_painter.restore(); |
489 | scheduleChange(); |
490 | } |
491 | //! [1] |
492 | |
493 | |
494 | void Context2D::strokeRect(qreal x, qreal y, qreal w, qreal h) |
495 | { |
496 | QPainterPath path; |
497 | path.addRect(x, y, w, h); |
498 | beginPainting(); |
499 | m_painter.save(); |
500 | m_painter.setTransform(transform: QTransform(m_state.matrix), combine: false); |
501 | m_painter.strokePath(path, pen: m_painter.pen()); |
502 | m_painter.restore(); |
503 | scheduleChange(); |
504 | } |
505 | |
506 | |
507 | void Context2D::beginPath() |
508 | { |
509 | m_path = QPainterPath(); |
510 | } |
511 | |
512 | |
513 | void Context2D::closePath() |
514 | { |
515 | m_path.closeSubpath(); |
516 | } |
517 | |
518 | |
519 | void Context2D::moveTo(qreal x, qreal y) |
520 | { |
521 | QPointF pt = m_state.matrix.map(p: QPointF(x, y)); |
522 | m_path.moveTo(p: pt); |
523 | } |
524 | |
525 | |
526 | void Context2D::lineTo(qreal x, qreal y) |
527 | { |
528 | QPointF pt = m_state.matrix.map(p: QPointF(x, y)); |
529 | m_path.lineTo(p: pt); |
530 | } |
531 | |
532 | |
533 | void Context2D::quadraticCurveTo(qreal cpx, qreal cpy, qreal x, qreal y) |
534 | { |
535 | QPointF cp = m_state.matrix.map(p: QPointF(cpx, cpy)); |
536 | QPointF xy = m_state.matrix.map(p: QPointF(x, y)); |
537 | m_path.quadTo(ctrlPt: cp, endPt: xy); |
538 | } |
539 | |
540 | |
541 | void Context2D::bezierCurveTo(qreal cp1x, qreal cp1y, |
542 | qreal cp2x, qreal cp2y, qreal x, qreal y) |
543 | { |
544 | QPointF cp1 = m_state.matrix.map(p: QPointF(cp1x, cp1y)); |
545 | QPointF cp2 = m_state.matrix.map(p: QPointF(cp2x, cp2y)); |
546 | QPointF end = m_state.matrix.map(p: QPointF(x, y)); |
547 | m_path.cubicTo(ctrlPt1: cp1, ctrlPt2: cp2, endPt: end); |
548 | } |
549 | |
550 | |
551 | void Context2D::arcTo(qreal x1, qreal y1, qreal x2, qreal y2, qreal radius) |
552 | { |
553 | //FIXME: this is surely busted |
554 | QPointF st = m_state.matrix.map(p: QPointF(x1, y1)); |
555 | QPointF end = m_state.matrix.map(p: QPointF(x2, y2)); |
556 | m_path.arcTo(x: st.x(), y: st.y(), |
557 | w: end.x()-st.x(), h: end.y()-st.y(), |
558 | startAngle: radius, arcLength: 90); |
559 | } |
560 | |
561 | |
562 | void Context2D::rect(qreal x, qreal y, qreal w, qreal h) |
563 | { |
564 | QPainterPath path; path.addRect(x, y, w, h); |
565 | path = m_state.matrix.map(p: path); |
566 | m_path.addPath(path); |
567 | } |
568 | |
569 | void Context2D::arc(qreal xc, qreal yc, qreal radius, |
570 | qreal sar, qreal ear, |
571 | bool anticlockwise) |
572 | { |
573 | //### HACK |
574 | // In Qt we don't switch the coordinate system for degrees |
575 | // and still use the 0,0 as bottom left for degrees so we need |
576 | // to switch |
577 | sar = -sar; |
578 | ear = -ear; |
579 | anticlockwise = !anticlockwise; |
580 | //end hack |
581 | |
582 | float sa = qRadiansToDegrees(radians: sar); |
583 | float ea = qRadiansToDegrees(radians: ear); |
584 | |
585 | double span = 0; |
586 | |
587 | double xs = xc - radius; |
588 | double ys = yc - radius; |
589 | double width = radius*2; |
590 | double height = radius*2; |
591 | |
592 | if (!anticlockwise && (ea < sa)) { |
593 | span += 360; |
594 | } else if (anticlockwise && (sa < ea)) { |
595 | span -= 360; |
596 | } |
597 | |
598 | //### this is also due to switched coordinate system |
599 | // we would end up with a 0 span instead of 360 |
600 | if (!(qFuzzyCompare(p1: span + (ea - sa) + 1, p2: 1) && |
601 | qFuzzyCompare(p1: qAbs(t: span), p2: 360))) { |
602 | span += ea - sa; |
603 | } |
604 | |
605 | QPainterPath path; |
606 | path.moveTo(p: QPointF(xc + radius * cos(x: sar), |
607 | yc - radius * sin(x: sar))); |
608 | |
609 | path.arcTo(x: xs, y: ys, w: width, h: height, startAngle: sa, arcLength: span); |
610 | path = m_state.matrix.map(p: path); |
611 | m_path.addPath(path); |
612 | } |
613 | |
614 | |
615 | void Context2D::fill() |
616 | { |
617 | beginPainting(); |
618 | m_painter.fillPath(path: m_path, brush: m_painter.brush()); |
619 | scheduleChange(); |
620 | } |
621 | |
622 | |
623 | void Context2D::stroke() |
624 | { |
625 | beginPainting(); |
626 | m_painter.save(); |
627 | m_painter.setTransform(transform: QTransform(m_state.matrix), combine: false); |
628 | QPainterPath tmp = m_state.matrix.inverted().map(p: m_path); |
629 | m_painter.strokePath(path: tmp, pen: m_painter.pen()); |
630 | m_painter.restore(); |
631 | scheduleChange(); |
632 | } |
633 | |
634 | |
635 | void Context2D::clip() |
636 | { |
637 | m_state.clipPath = m_path; |
638 | m_state.flags |= DirtyClippingRegion; |
639 | } |
640 | |
641 | |
642 | bool Context2D::isPointInPath(qreal x, qreal y) const |
643 | { |
644 | return m_path.contains(pt: QPointF(x, y)); |
645 | } |
646 | |
647 | |
648 | ImageData Context2D::getImageData(qreal sx, qreal sy, qreal sw, qreal sh) |
649 | { |
650 | Q_UNUSED(sx); |
651 | Q_UNUSED(sy); |
652 | Q_UNUSED(sw); |
653 | Q_UNUSED(sh); |
654 | return ImageData(); |
655 | } |
656 | |
657 | |
658 | void Context2D::putImageData(ImageData image, qreal dx, qreal dy) |
659 | { |
660 | Q_UNUSED(image); |
661 | Q_UNUSED(dx); |
662 | Q_UNUSED(dy); |
663 | } |
664 | |
665 | Context2D::Context2D(QObject *parent) |
666 | : QObject(parent), m_changeTimerId(-1) |
667 | { |
668 | reset(); |
669 | } |
670 | |
671 | const QImage &Context2D::endPainting() |
672 | { |
673 | if (m_painter.isActive()) |
674 | m_painter.end(); |
675 | return m_image; |
676 | } |
677 | |
678 | void Context2D::beginPainting() |
679 | { |
680 | if (!m_painter.isActive()) { |
681 | m_painter.begin(&m_image); |
682 | m_painter.setRenderHint(hint: QPainter::Antialiasing); |
683 | if (!m_state.clipPath.isEmpty()) |
684 | m_painter.setClipPath(path: m_state.clipPath); |
685 | m_painter.setBrush(m_state.fillStyle); |
686 | m_painter.setOpacity(m_state.globalAlpha); |
687 | QPen pen; |
688 | pen.setBrush(m_state.strokeStyle); |
689 | if (pen.style() == Qt::NoPen) |
690 | pen.setStyle(Qt::SolidLine); |
691 | pen.setCapStyle(m_state.lineCap); |
692 | pen.setJoinStyle(m_state.lineJoin); |
693 | pen.setWidthF(m_state.lineWidth); |
694 | pen.setMiterLimit(m_state.miterLimit); |
695 | m_painter.setPen(pen); |
696 | } else { |
697 | if ((m_state.flags & DirtyClippingRegion) && !m_state.clipPath.isEmpty()) |
698 | m_painter.setClipPath(path: m_state.clipPath); |
699 | if (m_state.flags & DirtyFillStyle) |
700 | m_painter.setBrush(m_state.fillStyle); |
701 | if (m_state.flags & DirtyGlobalAlpha) |
702 | m_painter.setOpacity(m_state.globalAlpha); |
703 | if (m_state.flags & DirtyGlobalCompositeOperation) |
704 | m_painter.setCompositionMode(m_state.globalCompositeOperation); |
705 | if (m_state.flags & MDirtyPen) { |
706 | QPen pen = m_painter.pen(); |
707 | if (m_state.flags & DirtyStrokeStyle) |
708 | pen.setBrush(m_state.strokeStyle); |
709 | if (m_state.flags & DirtyLineWidth) |
710 | pen.setWidthF(m_state.lineWidth); |
711 | if (m_state.flags & DirtyLineCap) |
712 | pen.setCapStyle(m_state.lineCap); |
713 | if (m_state.flags & DirtyLineJoin) |
714 | pen.setJoinStyle(m_state.lineJoin); |
715 | if (m_state.flags & DirtyMiterLimit) |
716 | pen.setMiterLimit(m_state.miterLimit); |
717 | m_painter.setPen(pen); |
718 | } |
719 | m_state.flags = 0; |
720 | } |
721 | } |
722 | |
723 | void Context2D::clear() |
724 | { |
725 | endPainting(); |
726 | m_image.fill(pixel: qRgba(r: 0,g: 0,b: 0,a: 0)); |
727 | scheduleChange(); |
728 | } |
729 | |
730 | void Context2D::reset() |
731 | { |
732 | m_stateStack.clear(); |
733 | m_state.matrix = QTransform(); |
734 | m_state.clipPath = QPainterPath(); |
735 | m_state.globalAlpha = 1.0; |
736 | m_state.globalCompositeOperation = QPainter::CompositionMode_SourceOver; |
737 | m_state.strokeStyle = Qt::black; |
738 | m_state.fillStyle = Qt::black; |
739 | m_state.lineWidth = 1; |
740 | m_state.lineCap = Qt::FlatCap; |
741 | m_state.lineJoin = Qt::MiterJoin; |
742 | m_state.miterLimit = 10; |
743 | m_state.shadowOffsetX = 0; |
744 | m_state.shadowOffsetY = 0; |
745 | m_state.shadowBlur = 0; |
746 | m_state.shadowColor = qRgba(r: 0, g: 0, b: 0, a: 0); |
747 | m_state.flags = AllIsFullOfDirt; |
748 | clear(); |
749 | } |
750 | |
751 | void Context2D::setSize(int width, int height) |
752 | { |
753 | endPainting(); |
754 | QImage newi(width, height, QImage::Format_ARGB32_Premultiplied); |
755 | newi.fill(pixel: qRgba(r: 0,g: 0,b: 0,a: 0)); |
756 | QPainter p(&newi); |
757 | p.drawImage(x: 0, y: 0, image: m_image); |
758 | p.end(); |
759 | m_image = newi; |
760 | scheduleChange(); |
761 | } |
762 | |
763 | void Context2D::setSize(const QSize &size) |
764 | { |
765 | setSize(width: size.width(), height: size.height()); |
766 | } |
767 | |
768 | QSize Context2D::size() const |
769 | { |
770 | return m_image.size(); |
771 | } |
772 | |
773 | void Context2D::drawImage(DomImage *image, qreal dx, qreal dy) |
774 | { |
775 | if (!image) |
776 | return; |
777 | if (dx < 0) { |
778 | qreal sx = qAbs(t: dx); |
779 | qreal sy = qAbs(t: dy); |
780 | qreal sw = image->width() - sx; |
781 | qreal sh = image->height() - sy; |
782 | |
783 | drawImage(image, sx, sy, sw, sh, dx: 0, dy: 0, dw: sw, dh: sh); |
784 | } else { |
785 | beginPainting(); |
786 | m_painter.drawImage(p: QPointF(dx, dy), image: image->image()); |
787 | scheduleChange(); |
788 | } |
789 | } |
790 | |
791 | void Context2D::drawImage(DomImage *image, qreal dx, qreal dy, |
792 | qreal dw, qreal dh) |
793 | { |
794 | if (!image) |
795 | return; |
796 | beginPainting(); |
797 | m_painter.drawImage(r: QRectF(dx, dy, dw, dh).toRect(), image: image->image()); |
798 | scheduleChange(); |
799 | } |
800 | |
801 | void Context2D::drawImage(DomImage *image, qreal sx, qreal sy, |
802 | qreal sw, qreal sh, qreal dx, qreal dy, |
803 | qreal dw, qreal dh) |
804 | { |
805 | if (!image) |
806 | return; |
807 | beginPainting(); |
808 | m_painter.drawImage(targetRect: QRectF(dx, dy, dw, dh), image: image->image(), |
809 | sourceRect: QRectF(sx, sy, sw, sh)); |
810 | scheduleChange(); |
811 | } |
812 | |
813 | //! [2] |
814 | void Context2D::scheduleChange() |
815 | { |
816 | if (m_changeTimerId == -1) |
817 | m_changeTimerId = startTimer(interval: 0); |
818 | } |
819 | |
820 | void Context2D::timerEvent(QTimerEvent *e) |
821 | { |
822 | if (e->timerId() == m_changeTimerId) { |
823 | killTimer(id: m_changeTimerId); |
824 | m_changeTimerId = -1; |
825 | emit changed(image: endPainting()); |
826 | } else { |
827 | QObject::timerEvent(event: e); |
828 | } |
829 | } |
830 | //! [2] |
831 | |