/**************************************************************************** ** ** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of Qt Creator. ** ** 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 Digia. For licensing terms and ** conditions see http://qt.digia.com/licensing. For further information ** use the contact form at http://qt.digia.com/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ****************************************************************************/ #include "qdeclarativecontext2d_p.h" #include "qdeclarativecanvas_p.h" #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE static const double Q_PI = 3.14159265358979323846; // pi class CustomDropShadowEffect : public QGraphicsDropShadowEffect { public: void draw(QPainter *painter) { QGraphicsDropShadowEffect::draw(painter);} void drawSource(QPainter *painter) { QGraphicsDropShadowEffect::drawSource(painter);} }; // Note, this is exported but in a private header as qtopengl depends on it. // But it really should be considered private API void qt_blurImage(QPainter *p, QImage &blurImage, qreal radius, bool quality, bool alphaOnly, int transposed = 0); void qt_blurImage(QImage &blurImage, qreal radius, bool quality, int transposed = 0); #define DEGREES(t) ((t) * 180.0 / Q_PI) #define qClamp(val, min, max) qMin(qMax(val, min), max) static QList parseNumbersList(QString::const_iterator &itr) { QList points; QString temp; while ((*itr).isSpace()) ++itr; while ((*itr).isNumber() || (*itr) == QLatin1Char('-') || (*itr) == QLatin1Char('+') || (*itr) == QLatin1Char('.')) { temp.clear(); if ((*itr) == QLatin1Char('-')) temp += *itr++; else if ((*itr) == QLatin1Char('+')) temp += *itr++; while ((*itr).isDigit()) temp += *itr++; if ((*itr) == QLatin1Char('.')) temp += *itr++; while ((*itr).isDigit()) temp += *itr++; while ((*itr).isSpace()) ++itr; if ((*itr) == QLatin1Char(',')) ++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 compo; if (name.startsWith(QLatin1String("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(QLatin1String("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 if (name.startsWith(QLatin1String("hsla("))) { ++itr; ++itr; ++itr; ++itr; ++itr; compo = parseNumbersList(itr); if (compo.size() != 4) return QColor(); return QColor::fromHslF(compo[0], compo[1], compo[2], compo[3]); } else if (name.startsWith(QLatin1String("hsl("))) { ++itr; ++itr; ++itr; ++itr; ++itr; compo = parseNumbersList(itr); if (compo.size() != 3) return QColor(); return QColor::fromHslF(compo[0], compo[1], compo[2]); } else { //QRgb color; //CSSParser::parseColor(name, color); return QColor(name); } } static QPainter::CompositionMode compositeOperatorFromString(const QString &compositeOperator) { if (compositeOperator == QLatin1String("source-over")) return QPainter::CompositionMode_SourceOver; else if (compositeOperator == QLatin1String("source-out")) return QPainter::CompositionMode_SourceOut; else if (compositeOperator == QLatin1String("source-in")) return QPainter::CompositionMode_SourceIn; else if (compositeOperator == QLatin1String("source-atop")) return QPainter::CompositionMode_SourceAtop; else if (compositeOperator == QLatin1String("destination-atop")) return QPainter::CompositionMode_DestinationAtop; else if (compositeOperator == QLatin1String("destination-in")) return QPainter::CompositionMode_DestinationIn; else if (compositeOperator == QLatin1String("destination-out")) return QPainter::CompositionMode_DestinationOut; else if (compositeOperator == QLatin1String("destination-over")) return QPainter::CompositionMode_DestinationOver; else if (compositeOperator == QLatin1String("darker")) return QPainter::CompositionMode_SourceOver; else if (compositeOperator == QLatin1String("lighter")) return QPainter::CompositionMode_SourceOver; else if (compositeOperator == QLatin1String("copy")) return QPainter::CompositionMode_Source; else if (compositeOperator == QLatin1String("xor")) return QPainter::CompositionMode_Xor; return QPainter::CompositionMode_SourceOver; } static QString compositeOperatorToString(QPainter::CompositionMode op) { switch (op) { case QPainter::CompositionMode_SourceOver: return QLatin1String("source-over"); case QPainter::CompositionMode_DestinationOver: return QLatin1String("destination-over"); case QPainter::CompositionMode_Clear: return QLatin1String("clear"); case QPainter::CompositionMode_Source: return QLatin1String("source"); case QPainter::CompositionMode_Destination: return QLatin1String("destination"); case QPainter::CompositionMode_SourceIn: return QLatin1String("source-in"); case QPainter::CompositionMode_DestinationIn: return QLatin1String("destination-in"); case QPainter::CompositionMode_SourceOut: return QLatin1String("source-out"); case QPainter::CompositionMode_DestinationOut: return QLatin1String("destination-out"); case QPainter::CompositionMode_SourceAtop: return QLatin1String("source-atop"); case QPainter::CompositionMode_DestinationAtop: return QLatin1String("destination-atop"); case QPainter::CompositionMode_Xor: return QLatin1String("xor"); case QPainter::CompositionMode_Plus: return QLatin1String("plus"); case QPainter::CompositionMode_Multiply: return QLatin1String("multiply"); case QPainter::CompositionMode_Screen: return QLatin1String("screen"); case QPainter::CompositionMode_Overlay: return QLatin1String("overlay"); case QPainter::CompositionMode_Darken: return QLatin1String("darken"); case QPainter::CompositionMode_Lighten: return QLatin1String("lighten"); case QPainter::CompositionMode_ColorDodge: return QLatin1String("color-dodge"); case QPainter::CompositionMode_ColorBurn: return QLatin1String("color-burn"); case QPainter::CompositionMode_HardLight: return QLatin1String("hard-light"); case QPainter::CompositionMode_SoftLight: return QLatin1String("soft-light"); case QPainter::CompositionMode_Difference: return QLatin1String("difference"); case QPainter::CompositionMode_Exclusion: return QLatin1String("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) { CanvasGradient * gradient= qobject_cast(style.value()); if (gradient) { m_state.strokeStyle = gradient->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) { CanvasGradient * gradient= qobject_cast(style.value()); if (gradient) { m_state.fillStyle = gradient->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; } CanvasImage *Context2D::createImage(const QString &url) { return new CanvasImage(url); } CanvasGradient *Context2D::createLinearGradient(qreal x0, qreal y0, qreal x1, qreal y1) { QLinearGradient g(x0, y0, x1, y1); return new 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 new 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 QLatin1String("butt"); case Qt::SquareCap: return QLatin1String("square"); case Qt::RoundCap: return QLatin1String("round"); default: ; } return QString(); } void Context2D::setLineCap(const QString &capString) { Qt::PenCapStyle style; if (capString == QLatin1String("round")) style = Qt::RoundCap; else if (capString == QLatin1String("square")) style = Qt::SquareCap; else //if (capString == QLatin1String("butt")) style = Qt::FlatCap; m_state.lineCap = style; m_state.flags |= DirtyLineCap; } QString Context2D::lineJoin() const { switch (m_state.lineJoin) { case Qt::RoundJoin: return QLatin1String("round"); case Qt::BevelJoin: return QLatin1String("bevel"); case Qt::MiterJoin: return QLatin1String("miter"); default: ; } return QString(); } void Context2D::setLineJoin(const QString &joinString) { Qt::PenJoinStyle style; if (joinString == QLatin1String("round")) style = Qt::RoundJoin; else if (joinString == QLatin1String("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) { if (m_state.shadowOffsetX == x) return; m_state.shadowOffsetX = x; updateShadowBuffer(); if (m_painter.device() == &m_shadowbuffer && m_state.shadowBlur>0) endPainting(); m_state.flags |= DirtyShadowOffsetX; } const QList &Context2D::mouseAreas() const { return m_mouseAreas; } void Context2D::updateShadowBuffer() { if (m_shadowbuffer.isNull() || m_shadowbuffer.width() != m_width+m_state.shadowOffsetX || m_shadowbuffer.height() != m_height+m_state.shadowOffsetY) { m_shadowbuffer = QImage(m_width+m_state.shadowOffsetX, m_height+m_state.shadowOffsetY, QImage::Format_ARGB32); m_shadowbuffer.fill(Qt::transparent); } } void Context2D::setShadowOffsetY(qreal y) { if (m_state.shadowOffsetY == y) return; m_state.shadowOffsetY = y; updateShadowBuffer(); if (m_painter.device() == &m_shadowbuffer && m_state.shadowBlur>0) endPainting(); m_state.flags |= DirtyShadowOffsetY; } void Context2D::setShadowBlur(qreal b) { if (m_state.shadowBlur == b) return; m_state.shadowBlur = b; updateShadowBuffer(); if (m_painter.device() == &m_shadowbuffer && m_state.shadowBlur>0) endPainting(); m_state.flags |= DirtyShadowBlur; } void Context2D::setShadowColor(const QString &str) { m_state.shadowColor = colorFromString(str); if (m_painter.device() == &m_shadowbuffer && m_state.shadowBlur>0) endPainting(); m_state.flags |= DirtyShadowColor; } QString Context2D::textBaseline() { switch (m_state.textBaseline) { case Context2D::Alphabetic: return QLatin1String("alphabetic"); case Context2D::Hanging: return QLatin1String("hanging"); case Context2D::Bottom: return QLatin1String("bottom"); case Context2D::Top: return QLatin1String("top"); case Context2D::Middle: return QLatin1String("middle"); default: Q_ASSERT("invalid value"); return QLatin1String("start"); } } void Context2D::setTextBaseline(const QString &baseline) { if (baseline==QLatin1String("alphabetic")) { m_state.textBaseline = Context2D::Alphabetic; } else if (baseline == QLatin1String("hanging")) { m_state.textBaseline = Context2D::Hanging; } else if (baseline == QLatin1String("top")) { m_state.textBaseline = Context2D::Top; } else if (baseline == QLatin1String("bottom")) { m_state.textBaseline = Context2D::Bottom; } else if (baseline == QLatin1String("middle")) { m_state.textBaseline = Context2D::Middle; } else { m_state.textBaseline = Context2D::Alphabetic; qWarning() << (QLatin1String("Context2D: invalid baseline:") + baseline); } m_state.flags |= DirtyTextBaseline; } QString Context2D::textAlign() { switch (m_state.textAlign) { case Context2D::Left: return QLatin1String("left"); case Context2D::Right: return QLatin1String("right"); case Context2D::Center: return QLatin1String("center"); case Context2D::Start: return QLatin1String("start"); case Context2D::End: return QLatin1String("end"); default: Q_ASSERT("invalid value"); qWarning() << ("Context2D::invalid textAlign"); return QLatin1String("start"); } } void Context2D::setTextAlign(const QString &baseline) { if (baseline==QLatin1String("start")) { m_state.textAlign = Context2D::Start; } else if (baseline == QLatin1String("end")) { m_state.textAlign = Context2D::End; } else if (baseline == QLatin1String("left")) { m_state.textAlign = Context2D::Left; } else if (baseline == QLatin1String("right")) { m_state.textAlign = Context2D::Right; } else if (baseline == QLatin1String("center")) { m_state.textAlign = Context2D::Center; } else { m_state.textAlign= Context2D::Start; qWarning("Context2D: invalid text align"); } // ### alphabetic, ideographic, hanging m_state.flags |= DirtyTextBaseline; } void Context2D::setFont(const QString &fontString) { QFont font; // ### this is simplified and incomplete QStringList tokens = fontString.split(QLatin1Char(QLatin1Char(' '))); foreach (const QString &token, tokens) { if (token == QLatin1String("italic")) { font.setItalic(true); } else if (token == QLatin1String("bold")) { font.setBold(true); } else if (token.endsWith(QLatin1String("px"))) { QString number = token; number.remove(QLatin1String("px")); #ifdef Q_OS_MACX // compensating the extra antialias space with bigger fonts // this class is only used by the QML Profiler // not much harm can be inflicted by this dirty hack font.setPointSizeF(number.trimmed().toFloat()*4.0f/3.0f); #else font.setPointSizeF(number.trimmed().toFloat()); #endif } else { font.setFamily(token); } } m_state.font = font; m_state.flags |= DirtyFont; } QString Context2D::font() { return m_state.font.toString(); } 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(worldMatrix(), false); m_painter.setCompositionMode(QPainter::CompositionMode_Source); QColor fillColor = parent()->property("color").value(); m_painter.fillRect(QRectF(x, y, w, h), fillColor); m_painter.restore(); scheduleChange(); } void Context2D::fillRect(qreal x, qreal y, qreal w, qreal h) { beginPainting(); m_painter.save(); m_painter.setMatrix(worldMatrix(), false); m_painter.fillRect(QRectF(x, y, w, h), m_painter.brush()); m_painter.restore(); scheduleChange(); } int Context2D::baseLineOffset(Context2D::TextBaseLine value, const QFontMetrics &metrics) { int offset = 0; switch (value) { case Context2D::Top: break; case Context2D::Alphabetic: case Context2D::Middle: case Context2D::Hanging: offset = metrics.ascent(); break; case Context2D::Bottom: offset = metrics.height(); break; } return offset; } int Context2D::textAlignOffset(Context2D::TextAlign value, const QFontMetrics &metrics, const QString &text) { int offset = 0; if (value == Context2D::Start) value = qApp->layoutDirection() == Qt::LeftToRight ? Context2D::Left : Context2D::Right; else if (value == Context2D::End) value = qApp->layoutDirection() == Qt::LeftToRight ? Context2D::Right: Context2D::Left; switch (value) { case Context2D::Center: offset = metrics.width(text)/2; break; case Context2D::Right: offset = metrics.width(text); case Context2D::Left: default: break; } return offset; } void Context2D::fillText(const QString &text, qreal x, qreal y) { beginPainting(); m_painter.save(); m_painter.setPen(QPen(m_state.fillStyle, m_state.lineWidth)); m_painter.setMatrix(worldMatrix(), false); QFont font; font.setBold(true); m_painter.setFont(m_state.font); int yoffset = baseLineOffset(m_state.textBaseline, m_painter.fontMetrics()); int xoffset = textAlignOffset(m_state.textAlign, m_painter.fontMetrics(), text); QTextOption opt; // Adjust baseLine etc m_painter.drawText(QRectF(x-xoffset, y-yoffset, QWIDGETSIZE_MAX, m_painter.fontMetrics().height()), text, opt); m_painter.restore(); endPainting(); scheduleChange(); } void Context2D::strokeText(const QString &text, qreal x, qreal y) { beginPainting(); m_painter.save(); m_painter.setPen(QPen(m_state.fillStyle,0)); m_painter.setMatrix(worldMatrix(), false); QPainterPath textPath; QFont font = m_state.font; font.setStyleStrategy(QFont::ForceOutline); m_painter.setFont(font); const QFontMetrics &metrics = m_painter.fontMetrics(); int yoffset = baseLineOffset(m_state.textBaseline, metrics); int xoffset = textAlignOffset(m_state.textAlign, metrics, text); textPath.addText(x-xoffset, y-yoffset+metrics.ascent(), font, text); m_painter.strokePath(textPath, QPen(m_state.fillStyle, m_state.lineWidth)); m_painter.restore(); endPainting(); 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(worldMatrix(), false); m_painter.strokePath(path, m_painter.pen()); m_painter.restore(); scheduleChange(); } void Context2D::mouseArea(qreal x, qreal y, qreal w, qreal h, const QJSValue &callback, const QJSValue &data) { MouseArea a = { callback, data, QRectF(x, y, w, h), m_state.matrix }; m_mouseAreas << a; } void Context2D::beginPath() { m_path = QPainterPath(); } void Context2D::closePath() { m_path.closeSubpath(); } void Context2D::moveTo(qreal x, qreal y) { QPointF pt = worldMatrix().map(QPointF(x, y)); m_path.moveTo(pt); } void Context2D::lineTo(qreal x, qreal y) { QPointF pt = worldMatrix().map(QPointF(x, y)); m_path.lineTo(pt); } void Context2D::quadraticCurveTo(qreal cpx, qreal cpy, qreal x, qreal y) { QPointF cp = worldMatrix().map(QPointF(cpx, cpy)); QPointF xy = worldMatrix().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 = worldMatrix().map(QPointF(cp1x, cp1y)); QPointF cp2 = worldMatrix().map(QPointF(cp2x, cp2y)); QPointF end = worldMatrix().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 = worldMatrix().map(QPointF(x1, y1)); QPointF end = worldMatrix().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 = worldMatrix().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 = worldMatrix().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(worldMatrix(), false); QPainterPath tmp = worldMatrix().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), m_width(0), m_height(0), m_inPaint(false) { reset(); } void Context2D::setupPainter() { m_painter.setRenderHint(QPainter::Antialiasing, true); 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); } } void Context2D::beginPainting() { if (m_pixmap.width() != m_width || m_pixmap.height() != m_height) { if (m_painter.isActive()) m_painter.end(); m_pixmap = QPixmap(m_width, m_height); m_pixmap.fill(parent()->property("color").value()); } if (m_state.shadowBlur > 0 && m_painter.device() != &m_shadowbuffer) { if (m_painter.isActive()) m_painter.end(); updateShadowBuffer(); m_painter.begin(&m_shadowbuffer); m_painter.setViewport(m_state.shadowOffsetX, m_state.shadowOffsetY, m_shadowbuffer.width(), m_shadowbuffer.height()); m_shadowbuffer.fill(Qt::transparent); } if (!m_painter.isActive()) { m_painter.begin(&m_pixmap); 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 { setupPainter(); m_state.flags = 0; } } void Context2D::endPainting() { if (m_state.shadowBlur > 0) { QImage alphaChannel = m_shadowbuffer.alphaChannel(); qt_blurImage(alphaChannel, m_state.shadowBlur, false, 1); QRect imageRect = m_shadowbuffer.rect(); if (m_shadowColorIndexBuffer.isEmpty() || m_shadowColorBuffer != m_state.shadowColor) { m_shadowColorIndexBuffer.clear(); m_shadowColorBuffer = m_state.shadowColor; for (int i = 0; i < 256; ++i) { m_shadowColorIndexBuffer << qRgba(qRound(255 * m_state.shadowColor.redF()), qRound(255 * m_state.shadowColor.greenF()), qRound(255 * m_state.shadowColor.blueF()), i); } } alphaChannel.setColorTable(m_shadowColorIndexBuffer); if (m_painter.isActive()) m_painter.end(); m_painter.begin(&m_pixmap); // draw the blurred drop shadow... m_painter.save(); QTransform tf = m_painter.transform(); m_painter.translate(0, imageRect.height()); m_painter.rotate(-90); m_painter.drawImage(0, 0, alphaChannel); m_painter.setTransform(tf); m_painter.restore(); // draw source m_painter.drawImage(-m_state.shadowOffsetX, -m_state.shadowOffsetY, m_shadowbuffer.copy()); m_painter.end(); } } void Context2D::clear() { m_painter.fillRect(QRect(QPoint(0,0), size()), Qt::white); } void Context2D::reset() { m_stateStack.clear(); m_state.matrix = QMatrix(); m_state.clipPath = QPainterPath(); m_state.strokeStyle = Qt::black; m_state.fillStyle = Qt::black; m_state.globalAlpha = 1.0; 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.globalCompositeOperation = QPainter::CompositionMode_SourceOver; m_state.font = QFont(); m_state.textAlign = Start; m_state.textBaseline = Alphabetic; m_state.flags = AllIsFullOfDirt; m_mouseAreas.clear(); clear(); } void Context2D::drawImage(const QVariant &var, qreal sx, qreal sy, qreal sw = 0, qreal sh = 0) { CanvasImage *image = qobject_cast(var.value()); if (!image) { Canvas *canvas = qobject_cast(var.value()); if (canvas) image = canvas->toImage(); } if (image) { beginPainting(); if (sw == sh && sh == 0) m_painter.drawPixmap(QPointF(sx, sy), image->value()); else m_painter.drawPixmap(QRect(sx, sy, sw, sh), image->value()); scheduleChange(); } } void Context2D::setSize(int width, int height) { endPainting(); m_width = width; m_height = height; scheduleChange(); } void Context2D::setSize(const QSize &size) { setSize(size.width(), size.height()); } QSize Context2D::size() const { return m_pixmap.size(); } QPoint Context2D::painterTranslate() const { return m_painterTranslate; } void Context2D::setPainterTranslate(const QPoint &translate) { m_painterTranslate = translate; m_state.flags |= DirtyTransformationMatrix; } void Context2D::scheduleChange() { QMetaObject::invokeMethod(this, "onScheduleChange", Qt::QueuedConnection, Q_ARG(int, 0)); } void Context2D::onScheduleChange(int interval) { if (m_changeTimerId == -1 && !m_inPaint) m_changeTimerId = startTimer(interval); } void Context2D::timerEvent(QTimerEvent *e) { if (e->timerId() == m_changeTimerId) { killTimer(m_changeTimerId); m_changeTimerId = -1; endPainting(); emit changed(); } else { QObject::timerEvent(e); } } QMatrix Context2D::worldMatrix() const { QMatrix mat; mat.translate(m_painterTranslate.x(), m_painterTranslate.y()); mat *= m_state.matrix; return mat; } QT_END_NAMESPACE