| /**************************************************************************** |
| ** |
| ** 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 <qmath.h> |
| |
| #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(qRadiansToDegrees(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) |
| { |
| QTransform 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) |
| { |
| QTransform 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; |
| } |
| |
| //! [3] |
| 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; |
| } |
| //! [3] |
| |
| 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; |
| } |
| |
| //! [0] |
| 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; |
| } |
| //! [0] |
| |
| 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.setTransform(QTransform(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(); |
| } |
| |
| |
| //! [1] |
| void Context2D::fillRect(qreal x, qreal y, qreal w, qreal h) |
| { |
| beginPainting(); |
| m_painter.save(); |
| m_painter.setTransform(QTransform(m_state.matrix), false); |
| m_painter.fillRect(QRectF(x, y, w, h), m_painter.brush()); |
| m_painter.restore(); |
| scheduleChange(); |
| } |
| //! [1] |
| |
| |
| 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.setTransform(QTransform(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 = qRadiansToDegrees(sar); |
| float ea = qRadiansToDegrees(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.setTransform(QTransform(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 = QTransform(); |
| 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(); |
| } |
| |
| //! [2] |
| 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); |
| } |
| } |
| //! [2] |