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