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