context2d.cpp Example File | Qt Script

Qt 5.9 | Qt Script | Context2D Example | context2d.cpp Example File

Qt 5.9.3 Reference Documentation

# context2d.cpp Example File

script/context2d/context2d.cpp

      /****************************************************************************
      **
      ** Copyright (C) 2017 The Qt Company Ltd.
      ** Contact: https://www.qt.io/licensing/
      **
      ** This file is part of the examples of the Qt Toolkit.
      **
      ** $QT_BEGIN_LICENSE:BSD$
      ** Commercial License Usage
      ** Licensees holding valid commercial Qt licenses may use this file in
      ** accordance with the commercial license agreement provided with the
      ** Software or, alternatively, in accordance with the terms contained in
      ** a written agreement between you and The Qt Company. For licensing terms
      ** and conditions see https://www.qt.io/terms-conditions. For further
      ** information use the contact form at https://www.qt.io/contact-us.
      **
      ** BSD License Usage
      ** Alternatively, you may use this file under the terms of the BSD license
      ** as follows:
      **
      ** "Redistribution and use in source and binary forms, with or without
      ** modification, are permitted provided that the following conditions are
      ** met:
      **   * Redistributions of source code must retain the above copyright
      **     notice, this list of conditions and the following disclaimer.
      **   * Redistributions in binary form must reproduce the above copyright
      **     notice, this list of conditions and the following disclaimer in
      **     the documentation and/or other materials provided with the
      **     distribution.
      **   * Neither the name of The Qt Company Ltd nor the names of its
      **     contributors may be used to endorse or promote products derived
      **     from this software without specific prior written permission.
      **
      **
      ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
      ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
      ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
      ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
      ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
      ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
      ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
      ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
      ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
      ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
      ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
      **
      ** $QT_END_LICENSE$
      **
      ****************************************************************************/

      #include "context2d.h"

      #include <QVariant>

      #include <math.h>
      static const double Q_PI   = 3.14159265358979323846;   // pi

      #define DEGREES(t) ((t) * 180.0 / Q_PI)

      #define qClamp(val, min, max) qMin(qMax(val, min), max)
      static QList<qreal> parseNumbersList(QString::const_iterator &itr)
      {
          QList<qreal> points;
          QString temp;
          while ((*itr).isSpace())
              ++itr;
          while ((*itr).isNumber() ||
                 (*itr) == '-' || (*itr) == '+' || (*itr) == '.') {
              temp = QString();

              if ((*itr) == '-')
                  temp += *itr++;
              else if ((*itr) == '+')
                  temp += *itr++;
              while ((*itr).isDigit())
                  temp += *itr++;
              if ((*itr) == '.')
                  temp += *itr++;
              while ((*itr).isDigit())
                  temp += *itr++;
              while ((*itr).isSpace())
                  ++itr;
              if ((*itr) == ',')
                  ++itr;
              points.append(temp.toDouble());
              //eat spaces
              while ((*itr).isSpace())
                  ++itr;
          }

          return points;
      }

      QColor colorFromString(const QString &name)
      {
          QString::const_iterator itr = name.constBegin();
          QList<qreal> compo;
          if (name.startsWith("rgba(")) {
              ++itr; ++itr; ++itr; ++itr; ++itr;
              compo = parseNumbersList(itr);
              if (compo.size() != 4) {
                  return QColor();
              }
              //alpha seems to be always between 0-1
              compo[3] *= 255;
              return QColor((int)compo[0], (int)compo[1],
                            (int)compo[2], (int)compo[3]);
          } else if (name.startsWith("rgb(")) {
              ++itr; ++itr; ++itr; ++itr;
              compo = parseNumbersList(itr);
              if (compo.size() != 3) {
                  return QColor();
              }
              return QColor((int)qClamp(compo[0], qreal(0), qreal(255)),
                            (int)qClamp(compo[1], qreal(0), qreal(255)),
                            (int)qClamp(compo[2], qreal(0), qreal(255)));
          } else {
              //QRgb color;
              //CSSParser::parseColor(name, color);
              return QColor(name);
          }
      }

      static QPainter::CompositionMode compositeOperatorFromString(const QString &compositeOperator)
      {
          if ( compositeOperator == "source-over" ) {
              return QPainter::CompositionMode_SourceOver;
          } else if ( compositeOperator == "source-out" ) {
              return QPainter::CompositionMode_SourceOut;
          } else if ( compositeOperator == "source-in" ) {
              return QPainter::CompositionMode_SourceIn;
          } else if ( compositeOperator == "source-atop" ) {
              return QPainter::CompositionMode_SourceAtop;
          } else if ( compositeOperator == "destination-atop" ) {
              return QPainter::CompositionMode_DestinationAtop;
          } else if ( compositeOperator == "destination-in" ) {
              return QPainter::CompositionMode_DestinationIn;
          } else if ( compositeOperator == "destination-out" ) {
              return QPainter::CompositionMode_DestinationOut;
          } else if ( compositeOperator == "destination-over" ) {
              return QPainter::CompositionMode_DestinationOver;
          } else if ( compositeOperator == "darker" ) {
              return QPainter::CompositionMode_SourceOver;
          } else if ( compositeOperator == "lighter" ) {
              return QPainter::CompositionMode_SourceOver;
          } else if ( compositeOperator == "copy" ) {
              return QPainter::CompositionMode_Source;
          } else if ( compositeOperator == "xor" ) {
              return QPainter::CompositionMode_Xor;
          }

          return QPainter::CompositionMode_SourceOver;
      }

      static QString compositeOperatorToString(QPainter::CompositionMode op)
      {
          switch (op) {
          case QPainter::CompositionMode_SourceOver:
              return "source-over";
          case QPainter::CompositionMode_DestinationOver:
              return "destination-over";
          case QPainter::CompositionMode_Clear:
              return "clear";
          case QPainter::CompositionMode_Source:
              return "source";
          case QPainter::CompositionMode_Destination:
              return "destination";
          case QPainter::CompositionMode_SourceIn:
              return "source-in";
          case QPainter::CompositionMode_DestinationIn:
              return "destination-in";
          case QPainter::CompositionMode_SourceOut:
              return "source-out";
          case QPainter::CompositionMode_DestinationOut:
              return "destination-out";
          case QPainter::CompositionMode_SourceAtop:
              return "source-atop";
          case QPainter::CompositionMode_DestinationAtop:
              return "destination-atop";
          case QPainter::CompositionMode_Xor:
              return "xor";
          case QPainter::CompositionMode_Plus:
              return "plus";
          case QPainter::CompositionMode_Multiply:
              return "multiply";
          case QPainter::CompositionMode_Screen:
              return "screen";
          case QPainter::CompositionMode_Overlay:
              return "overlay";
          case QPainter::CompositionMode_Darken:
              return "darken";
          case QPainter::CompositionMode_Lighten:
              return "lighten";
          case QPainter::CompositionMode_ColorDodge:
              return "color-dodge";
          case QPainter::CompositionMode_ColorBurn:
              return "color-burn";
          case QPainter::CompositionMode_HardLight:
              return "hard-light";
          case QPainter::CompositionMode_SoftLight:
              return "soft-light";
          case QPainter::CompositionMode_Difference:
              return "difference";
          case QPainter::CompositionMode_Exclusion:
              return "exclusion";
          default:
              break;
          }
          return QString();
      }

      void Context2D::save()
      {
          m_stateStack.push(m_state);
      }

      void Context2D::restore()
      {
          if (!m_stateStack.isEmpty()) {
              m_state = m_stateStack.pop();
              m_state.flags = AllIsFullOfDirt;
          }
      }

      void Context2D::scale(qreal x, qreal y)
      {
          m_state.matrix.scale(x, y);
          m_state.flags |= DirtyTransformationMatrix;
      }

      void Context2D::rotate(qreal angle)
      {
          m_state.matrix.rotate(DEGREES(angle));
          m_state.flags |= DirtyTransformationMatrix;
      }

      void Context2D::translate(qreal x, qreal y)
      {
          m_state.matrix.translate(x, y);
          m_state.flags |= DirtyTransformationMatrix;
      }

      void Context2D::transform(qreal m11, qreal m12, qreal m21, qreal m22,
                                qreal dx, qreal dy)
      {
          QMatrix mat(m11, m12,
                      m21, m22,
                      dx, dy);
          m_state.matrix *= mat;
          m_state.flags |= DirtyTransformationMatrix;
      }

      void Context2D::setTransform(qreal m11, qreal m12, qreal m21, qreal m22,
                                   qreal dx, qreal dy)
      {
          QMatrix mat(m11, m12,
                      m21, m22,
                      dx, dy);
          m_state.matrix = mat;
          m_state.flags |= DirtyTransformationMatrix;
      }

      QString Context2D::globalCompositeOperation() const
      {
          return compositeOperatorToString(m_state.globalCompositeOperation);
      }

      void Context2D::setGlobalCompositeOperation(const QString &op)
      {
          QPainter::CompositionMode mode =
              compositeOperatorFromString(op);
          m_state.globalCompositeOperation = mode;
          m_state.flags |= DirtyGlobalCompositeOperation;
      }

      QVariant Context2D::strokeStyle() const
      {
          return m_state.strokeStyle;
      }

      void Context2D::setStrokeStyle(const QVariant &style)
      {
          if (style.canConvert<CanvasGradient>()) {
              CanvasGradient cg = qvariant_cast<CanvasGradient>(style);
              m_state.strokeStyle = cg.value;
          } else {
              QColor color = colorFromString(style.toString());
              m_state.strokeStyle = color;
          }
          m_state.flags |= DirtyStrokeStyle;
      }

      QVariant Context2D::fillStyle() const
      {
          return m_state.fillStyle;
      }

      void Context2D::setFillStyle(const QVariant &style)
      {
          if (style.canConvert<CanvasGradient>()) {
              CanvasGradient cg = qvariant_cast<CanvasGradient>(style);
              m_state.fillStyle = cg.value;
          } else {
              QColor color = colorFromString(style.toString());
              m_state.fillStyle = color;
          }
          m_state.flags |= DirtyFillStyle;
      }

      qreal Context2D::globalAlpha() const
      {
          return m_state.globalAlpha;
      }

      void Context2D::setGlobalAlpha(qreal alpha)
      {
          m_state.globalAlpha = alpha;
          m_state.flags |= DirtyGlobalAlpha;
      }

      CanvasGradient Context2D::createLinearGradient(qreal x0, qreal y0,
                                                     qreal x1, qreal y1)
      {
          QLinearGradient g(x0, y0, x1, y1);
          return CanvasGradient(g);
      }

      CanvasGradient Context2D::createRadialGradient(qreal x0, qreal y0,
                                                     qreal r0, qreal x1,
                                                     qreal y1, qreal r1)
      {
          QRadialGradient g(QPointF(x1, y1), r0+r1, QPointF(x0, y0));
          return CanvasGradient(g);
      }

      qreal Context2D::lineWidth() const
      {
          return m_state.lineWidth;
      }

      void Context2D::setLineWidth(qreal w)
      {
          m_state.lineWidth = w;
          m_state.flags |= DirtyLineWidth;
      }

      QString Context2D::lineCap() const
      {
          switch (m_state.lineCap) {
          case Qt::FlatCap:
              return "butt";
          case Qt::SquareCap:
              return "square";
          case Qt::RoundCap:
              return "round";
          default: ;
          }
          return QString();
      }

      void Context2D::setLineCap(const QString &capString)
      {
          Qt::PenCapStyle style;
          if (capString == "round")
              style = Qt::RoundCap;
          else if (capString == "square")
              style = Qt::SquareCap;
          else //if (capString == "butt")
              style = Qt::FlatCap;
          m_state.lineCap = style;
          m_state.flags |= DirtyLineCap;
      }

      QString Context2D::lineJoin() const
      {
          switch (m_state.lineJoin) {
          case Qt::RoundJoin:
              return "round";
          case Qt::BevelJoin:
              return "bevel";
          case Qt::MiterJoin:
              return "miter";
          default: ;
          }
          return QString();
      }

      void Context2D::setLineJoin(const QString &joinString)
      {
          Qt::PenJoinStyle style;
          if (joinString == "round")
              style = Qt::RoundJoin;
          else if (joinString == "bevel")
              style = Qt::BevelJoin;
          else //if (joinString == "miter")
              style = Qt::MiterJoin;
          m_state.lineJoin = style;
          m_state.flags |= DirtyLineJoin;
      }

      qreal Context2D::miterLimit() const
      {
          return m_state.miterLimit;
      }

      void Context2D::setMiterLimit(qreal m)
      {
          m_state.miterLimit = m;
          m_state.flags |= DirtyMiterLimit;
      }

      void Context2D::setShadowOffsetX(qreal x)
      {
          m_state.shadowOffsetX = x;
          m_state.flags |= DirtyShadowOffsetX;
      }

      void Context2D::setShadowOffsetY(qreal y)
      {
          m_state.shadowOffsetY = y;
          m_state.flags |= DirtyShadowOffsetY;
      }

      void Context2D::setShadowBlur(qreal b)
      {
          m_state.shadowBlur = b;
          m_state.flags |= DirtyShadowBlur;
      }

      void Context2D::setShadowColor(const QString &str)
      {
          m_state.shadowColor = colorFromString(str);
          m_state.flags |= DirtyShadowColor;
      }

      qreal Context2D::shadowOffsetX() const
      {
          return m_state.shadowOffsetX;
      }

      qreal Context2D::shadowOffsetY() const
      {
          return m_state.shadowOffsetY;
      }

      qreal Context2D::shadowBlur() const
      {
          return m_state.shadowBlur;
      }

      QString Context2D::shadowColor() const
      {
          return m_state.shadowColor.name();
      }

      void Context2D::clearRect(qreal x, qreal y, qreal w, qreal h)
      {
          beginPainting();
          m_painter.save();
          m_painter.setMatrix(m_state.matrix, false);
          m_painter.setCompositionMode(QPainter::CompositionMode_Source);
          m_painter.fillRect(QRectF(x, y, w, h), QColor(0, 0, 0, 0));
          m_painter.restore();
          scheduleChange();
      }

      void Context2D::fillRect(qreal x, qreal y, qreal w, qreal h)
      {
          beginPainting();
          m_painter.save();
          m_painter.setMatrix(m_state.matrix, false);
          m_painter.fillRect(QRectF(x, y, w, h), m_painter.brush());
          m_painter.restore();
          scheduleChange();
      }

      void Context2D::strokeRect(qreal x, qreal y, qreal w, qreal h)
      {
          QPainterPath path;
          path.addRect(x, y, w, h);
          beginPainting();
          m_painter.save();
          m_painter.setMatrix(m_state.matrix, false);
          m_painter.strokePath(path, m_painter.pen());
          m_painter.restore();
          scheduleChange();
      }

      void Context2D::beginPath()
      {
          m_path = QPainterPath();
      }

      void Context2D::closePath()
      {
          m_path.closeSubpath();
      }

      void Context2D::moveTo(qreal x, qreal y)
      {
          QPointF pt = m_state.matrix.map(QPointF(x, y));
          m_path.moveTo(pt);
      }

      void Context2D::lineTo(qreal x, qreal y)
      {
          QPointF pt = m_state.matrix.map(QPointF(x, y));
          m_path.lineTo(pt);
      }

      void Context2D::quadraticCurveTo(qreal cpx, qreal cpy, qreal x, qreal y)
      {
          QPointF cp = m_state.matrix.map(QPointF(cpx, cpy));
          QPointF xy = m_state.matrix.map(QPointF(x, y));
          m_path.quadTo(cp, xy);
      }

      void Context2D::bezierCurveTo(qreal cp1x, qreal cp1y,
                                    qreal cp2x, qreal cp2y, qreal x, qreal y)
      {
          QPointF cp1 = m_state.matrix.map(QPointF(cp1x, cp1y));
          QPointF cp2 = m_state.matrix.map(QPointF(cp2x, cp2y));
          QPointF end = m_state.matrix.map(QPointF(x, y));
          m_path.cubicTo(cp1, cp2, end);
      }

      void Context2D::arcTo(qreal x1, qreal y1, qreal x2, qreal y2, qreal radius)
      {
          //FIXME: this is surely busted
          QPointF st  = m_state.matrix.map(QPointF(x1, y1));
          QPointF end = m_state.matrix.map(QPointF(x2, y2));
          m_path.arcTo(st.x(), st.y(),
                       end.x()-st.x(), end.y()-st.y(),
                       radius, 90);
      }

      void Context2D::rect(qreal x, qreal y, qreal w, qreal h)
      {
          QPainterPath path; path.addRect(x, y, w, h);
          path = m_state.matrix.map(path);
          m_path.addPath(path);
      }

      void Context2D::arc(qreal xc, qreal yc, qreal radius,
                          qreal sar, qreal ear,
                          bool anticlockwise)
      {
          //### HACK
          // In Qt we don't switch the coordinate system for degrees
          // and still use the 0,0 as bottom left for degrees so we need
          // to switch
          sar = -sar;
          ear = -ear;
          anticlockwise = !anticlockwise;
          //end hack

          float sa = DEGREES(sar);
          float ea = DEGREES(ear);

          double span = 0;

          double xs     = xc - radius;
          double ys     = yc - radius;
          double width  = radius*2;
          double height = radius*2;

          if (!anticlockwise && (ea < sa)) {
              span += 360;
          } else if (anticlockwise && (sa < ea)) {
              span -= 360;
          }

          //### this is also due to switched coordinate system
          // we would end up with a 0 span instead of 360
          if (!(qFuzzyCompare(span + (ea - sa) + 1, 1) &&
                qFuzzyCompare(qAbs(span), 360))) {
              span   += ea - sa;
          }

          QPainterPath path;
          path.moveTo(QPointF(xc + radius  * cos(sar),
                                      yc - radius  * sin(sar)));

          path.arcTo(xs, ys, width, height, sa, span);
          path = m_state.matrix.map(path);
          m_path.addPath(path);
      }

      void Context2D::fill()
      {
          beginPainting();
          m_painter.fillPath(m_path, m_painter.brush());
          scheduleChange();
      }

      void Context2D::stroke()
      {
          beginPainting();
          m_painter.save();
          m_painter.setMatrix(m_state.matrix, false);
          QPainterPath tmp = m_state.matrix.inverted().map(m_path);
          m_painter.strokePath(tmp, m_painter.pen());
          m_painter.restore();
          scheduleChange();
      }

      void Context2D::clip()
      {
          m_state.clipPath = m_path;
          m_state.flags |= DirtyClippingRegion;
      }

      bool Context2D::isPointInPath(qreal x, qreal y) const
      {
          return m_path.contains(QPointF(x, y));
      }

      ImageData Context2D::getImageData(qreal sx, qreal sy, qreal sw, qreal sh)
      {
          Q_UNUSED(sx);
          Q_UNUSED(sy);
          Q_UNUSED(sw);
          Q_UNUSED(sh);
          return ImageData();
      }

      void Context2D::putImageData(ImageData image, qreal dx, qreal dy)
      {
          Q_UNUSED(image);
          Q_UNUSED(dx);
          Q_UNUSED(dy);
      }

      Context2D::Context2D(QObject *parent)
          : QObject(parent), m_changeTimerId(-1)
      {
          reset();
      }

      const QImage &Context2D::endPainting()
      {
          if (m_painter.isActive())
              m_painter.end();
          return m_image;
      }

      void Context2D::beginPainting()
      {
          if (!m_painter.isActive()) {
              m_painter.begin(&m_image);
              m_painter.setRenderHint(QPainter::Antialiasing);
              if (!m_state.clipPath.isEmpty())
                  m_painter.setClipPath(m_state.clipPath);
              m_painter.setBrush(m_state.fillStyle);
              m_painter.setOpacity(m_state.globalAlpha);
              QPen pen;
              pen.setBrush(m_state.strokeStyle);
              if (pen.style() == Qt::NoPen)
                  pen.setStyle(Qt::SolidLine);
              pen.setCapStyle(m_state.lineCap);
              pen.setJoinStyle(m_state.lineJoin);
              pen.setWidthF(m_state.lineWidth);
              pen.setMiterLimit(m_state.miterLimit);
              m_painter.setPen(pen);
          } else {
              if ((m_state.flags & DirtyClippingRegion) && !m_state.clipPath.isEmpty())
                  m_painter.setClipPath(m_state.clipPath);
              if (m_state.flags & DirtyFillStyle)
                  m_painter.setBrush(m_state.fillStyle);
              if (m_state.flags & DirtyGlobalAlpha)
                  m_painter.setOpacity(m_state.globalAlpha);
              if (m_state.flags & DirtyGlobalCompositeOperation)
                  m_painter.setCompositionMode(m_state.globalCompositeOperation);
              if (m_state.flags & MDirtyPen) {
                  QPen pen = m_painter.pen();
                  if (m_state.flags & DirtyStrokeStyle)
                      pen.setBrush(m_state.strokeStyle);
                  if (m_state.flags & DirtyLineWidth)
                      pen.setWidthF(m_state.lineWidth);
                  if (m_state.flags & DirtyLineCap)
                      pen.setCapStyle(m_state.lineCap);
                  if (m_state.flags & DirtyLineJoin)
                      pen.setJoinStyle(m_state.lineJoin);
                  if (m_state.flags & DirtyMiterLimit)
                      pen.setMiterLimit(m_state.miterLimit);
                  m_painter.setPen(pen);
              }
              m_state.flags = 0;
          }
      }

      void Context2D::clear()
      {
          endPainting();
          m_image.fill(qRgba(0,0,0,0));
          scheduleChange();
      }

      void Context2D::reset()
      {
          m_stateStack.clear();
          m_state.matrix = QMatrix();
          m_state.clipPath = QPainterPath();
          m_state.globalAlpha = 1.0;
          m_state.globalCompositeOperation = QPainter::CompositionMode_SourceOver;
          m_state.strokeStyle = Qt::black;
          m_state.fillStyle = Qt::black;
          m_state.lineWidth = 1;
          m_state.lineCap = Qt::FlatCap;
          m_state.lineJoin = Qt::MiterJoin;
          m_state.miterLimit = 10;
          m_state.shadowOffsetX = 0;
          m_state.shadowOffsetY = 0;
          m_state.shadowBlur = 0;
          m_state.shadowColor = qRgba(0, 0, 0, 0);
          m_state.flags = AllIsFullOfDirt;
          clear();
      }

      void Context2D::setSize(int width, int height)
      {
          endPainting();
          QImage newi(width, height, QImage::Format_ARGB32_Premultiplied);
          newi.fill(qRgba(0,0,0,0));
          QPainter p(&newi);
          p.drawImage(0, 0, m_image);
          p.end();
          m_image = newi;
          scheduleChange();
      }

      void Context2D::setSize(const QSize &size)
      {
          setSize(size.width(), size.height());
      }

      QSize Context2D::size() const
      {
          return m_image.size();
      }

      void Context2D::drawImage(DomImage *image, qreal dx, qreal dy)
      {
          if (!image)
              return;
          if (dx < 0) {
              qreal sx = qAbs(dx);
              qreal sy = qAbs(dy);
              qreal sw = image->width() - sx;
              qreal sh = image->height() - sy;

              drawImage(image, sx, sy, sw, sh, 0, 0, sw, sh);
          } else {
              beginPainting();
              m_painter.drawImage(QPointF(dx, dy), image->image());
              scheduleChange();
          }
      }

      void Context2D::drawImage(DomImage *image, qreal dx, qreal dy,
                                qreal dw, qreal dh)
      {
          if (!image)
              return;
          beginPainting();
          m_painter.drawImage(QRectF(dx, dy, dw, dh).toRect(), image->image());
          scheduleChange();
      }

      void Context2D::drawImage(DomImage *image, qreal sx, qreal sy,
                                qreal sw, qreal sh, qreal dx, qreal dy,
                                qreal dw, qreal dh)
      {
          if (!image)
              return;
          beginPainting();
          m_painter.drawImage(QRectF(dx, dy, dw, dh), image->image(),
                              QRectF(sx, sy, sw, sh));
          scheduleChange();
      }

      void Context2D::scheduleChange()
      {
          if (m_changeTimerId == -1)
              m_changeTimerId = startTimer(0);
      }

      void Context2D::timerEvent(QTimerEvent *e)
      {
          if (e->timerId() == m_changeTimerId) {
              killTimer(m_changeTimerId);
              m_changeTimerId = -1;
              emit changed(endPainting());
          } else {
              QObject::timerEvent(e);
          }
      }