From 9275865b29850b71d95ba5d10ce38954aa5d6e2d Mon Sep 17 00:00:00 2001 From: Jens Bache-Wiig Date: Tue, 17 Aug 2010 14:53:16 +0200 Subject: Initial version --- src/canvas.cpp | 65 +++++ src/canvas.h | 68 +++++ src/canvasplugin.cpp | 53 ++++ src/canvasplugin.h | 47 +++ src/context2d.cpp | 794 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/context2d.h | 283 ++++++++++++++++++ src/src.pro | 18 ++ 7 files changed, 1328 insertions(+) create mode 100644 src/canvas.cpp create mode 100644 src/canvas.h create mode 100644 src/canvasplugin.cpp create mode 100644 src/canvasplugin.h create mode 100644 src/context2d.cpp create mode 100644 src/context2d.h create mode 100644 src/src.pro (limited to 'src') diff --git a/src/canvas.cpp b/src/canvas.cpp new file mode 100644 index 0000000..a657948 --- /dev/null +++ b/src/canvas.cpp @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the examples of the Qt Toolkit. +** +** 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 Nokia Corporation and its Subsidiary(-ies) 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 "canvas.h" + +#include + +Canvas::Canvas(QDeclarativeItem *parent) + : QDeclarativeItem(parent) +{ + setFlag(QGraphicsItem::ItemHasNoContents, false); + setCacheMode(QGraphicsItem::ItemCoordinateCache); + setSmooth(true); +} + +void Canvas::updateCanvas() +{ + update(); +} + +void Canvas::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) +{ + painter->save(); + painter->setRenderHint(QPainter::Antialiasing, smooth()); + Context2D context(this, painter); + emit paint(&context); + painter->restore(); +} + diff --git a/src/canvas.h b/src/canvas.h new file mode 100644 index 0000000..c20f29f --- /dev/null +++ b/src/canvas.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the examples of the Qt Toolkit. +** +** 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 Nokia Corporation and its Subsidiary(-ies) 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$ +** +****************************************************************************/ + +#ifndef DRAWABLEITEM_H +#define DRAWABLEITEM_H + +#include +#include "context2d.h" + +class Context2D; + +class Canvas : public QDeclarativeItem +{ + Q_OBJECT + +public: + void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *); + + Canvas(QDeclarativeItem *parent = 0); + +public slots: + void updateCanvas(); + +signals: + void paint(Context2D *ctx); + +private: + QPixmap m_cache; +}; + +#endif + diff --git a/src/canvasplugin.cpp b/src/canvasplugin.cpp new file mode 100644 index 0000000..8a67d3d --- /dev/null +++ b/src/canvasplugin.cpp @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the examples of the Qt Toolkit. +** +** 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 Nokia Corporation and its Subsidiary(-ies) 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 +#include "canvasplugin.h" +#include "canvas.h" + +void CanvasPlugin::registerTypes(const char *uri) +{ + qDebug("test %s", uri); + qmlRegisterType(uri, 1, 0, "Canvas"); + qmlRegisterType(uri, 1, 0, "Context2D"); + qmlRegisterUncreatableType(uri, 1, 0, "CanvasImage", QString()); + qmlRegisterUncreatableType(uri, 1, 0, "Gradient", QString()); +} + +Q_EXPORT_PLUGIN2(canvasplugin, CanvasPlugin); diff --git a/src/canvasplugin.h b/src/canvasplugin.h new file mode 100644 index 0000000..b6edbba --- /dev/null +++ b/src/canvasplugin.h @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the examples of the Qt Toolkit. +** +** 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 Nokia Corporation and its Subsidiary(-ies) 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 + + class CanvasPlugin : public QDeclarativeExtensionPlugin + { + Q_OBJECT + public: + void registerTypes(const char *uri); + }; diff --git a/src/context2d.cpp b/src/context2d.cpp new file mode 100644 index 0000000..05713a2 --- /dev/null +++ b/src/context2d.cpp @@ -0,0 +1,794 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the examples of the Qt Toolkit. +** +** 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 Nokia Corporation and its Subsidiary(-ies) 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 +#include +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 parseNumbersList(QString::const_iterator &itr) +{ + QList 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 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) +{ + 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 "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, QPainter *painter) + : QObject(parent), m_changeTimerId(-1), m_painter(painter) +{ + 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() +{ + return; + 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(const QVariant &var, qreal sx, qreal sy, + qreal sw = 0, qreal sh = 0) +{ + CanvasImage *image = qobject_cast(var.value()); + if (!image) + return; + beginPainting(); + if (sw == sh) + m_painter->drawImage(QPointF(sx, sy), image->value()); + else + m_painter->drawImage(QRectF(sx, sy, sw, sh), image->value()); + 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); + } +} +//! [2] diff --git a/src/context2d.h b/src/context2d.h new file mode 100644 index 0000000..ed76cd6 --- /dev/null +++ b/src/context2d.h @@ -0,0 +1,283 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the examples of the Qt Toolkit. +** +** 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 Nokia Corporation and its Subsidiary(-ies) 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$ +** +****************************************************************************/ + +#ifndef CONTEXT2D_H +#define CONTEXT2D_H + +#include +#include +#include +#include +#include +#include +#include +#include + +QColor colorFromString(const QString &name); + +class CanvasGradient : public QObject +{ + Q_OBJECT +public: + CanvasGradient(const QGradient &gradient) : m_gradient(gradient) {} + +public slots: + QGradient value() { return m_gradient; } + void addColorStop(float pos, const QString &color) { m_gradient.setColorAt(pos, colorFromString(color));} + +public: + QGradient m_gradient; +}; + +Q_DECLARE_METATYPE(CanvasGradient*) + + +class CanvasImage: public QObject +{ + Q_OBJECT + Q_PROPERTY(QString src READ src WRITE setSrc NOTIFY sourceChanged) + Q_PROPERTY(int width READ width) + Q_PROPERTY(int height READ height) + +public: + CanvasImage() {} + CanvasImage(const QString &url) : m_image(url), m_src(url) {} + +public slots: + int width() { return m_image.width(); } + int height() { return m_image.height(); } + QImage value() { return m_image; } + QString src() { return m_src; } + void setSrc(const QString &src) { m_src = src; m_image.load(src); emit sourceChanged();} +signals: + void sourceChanged(); + +private: + QImage m_image; + QString m_src; +}; + +Q_DECLARE_METATYPE(CanvasImage*) + + +class ImageData { +}; + +class QContext2DCanvas; + +class Context2D : public QObject +{ + Q_OBJECT + // compositing + Q_PROPERTY(qreal globalAlpha READ globalAlpha WRITE setGlobalAlpha) + Q_PROPERTY(QString globalCompositeOperation READ globalCompositeOperation WRITE setGlobalCompositeOperation) + Q_PROPERTY(QVariant strokeStyle READ strokeStyle WRITE setStrokeStyle) + Q_PROPERTY(QVariant fillStyle READ fillStyle WRITE setFillStyle) + // line caps/joins + Q_PROPERTY(qreal lineWidth READ lineWidth WRITE setLineWidth) + Q_PROPERTY(QString lineCap READ lineCap WRITE setLineCap) + Q_PROPERTY(QString lineJoin READ lineJoin WRITE setLineJoin) + Q_PROPERTY(qreal miterLimit READ miterLimit WRITE setMiterLimit) + // shadows + Q_PROPERTY(qreal shadowOffsetX READ shadowOffsetX WRITE setShadowOffsetX) + Q_PROPERTY(qreal shadowOffsetY READ shadowOffsetY WRITE setShadowOffsetY) + Q_PROPERTY(qreal shadowBlur READ shadowBlur WRITE setShadowBlur) + Q_PROPERTY(QString shadowColor READ shadowColor WRITE setShadowColor) + +public: + Context2D(QObject *parent = 0, QPainter *painter = 0); + void setSize(int width, int height); + void setSize(const QSize &size); + QSize size() const; + + void clear(); + void reset(); + + // compositing + qreal globalAlpha() const; // (default 1.0) + QString globalCompositeOperation() const; // (default over) + QVariant strokeStyle() const; // (default black) + QVariant fillStyle() const; // (default black) + + void setGlobalAlpha(qreal alpha); + void setGlobalCompositeOperation(const QString &op); + void setStrokeStyle(const QVariant &style); + void setFillStyle(const QVariant &style); + + // line caps/joins + qreal lineWidth() const; // (default 1) + QString lineCap() const; // "butt", "round", "square" (default "butt") + QString lineJoin() const; // "round", "bevel", "miter" (default "miter") + qreal miterLimit() const; // (default 10) + + void setLineWidth(qreal w); + void setLineCap(const QString &s); + void setLineJoin(const QString &s); + void setMiterLimit(qreal m); + + // shadows + qreal shadowOffsetX() const; // (default 0) + qreal shadowOffsetY() const; // (default 0) + qreal shadowBlur() const; // (default 0) + QString shadowColor() const; // (default black) + + void setShadowOffsetX(qreal x); + void setShadowOffsetY(qreal y); + void setShadowBlur(qreal b); + void setShadowColor(const QString &str); + + //! [1] +public slots: + void save(); // push state on state stack + void restore(); // pop state stack and restore state + + void scale(qreal x, qreal y); + void rotate(qreal angle); + void translate(qreal x, qreal y); + void transform(qreal m11, qreal m12, qreal m21, qreal m22, + qreal dx, qreal dy); + void setTransform(qreal m11, qreal m12, qreal m21, qreal m22, + qreal dx, qreal dy); + + CanvasGradient *createLinearGradient(qreal x0, qreal y0, + qreal x1, qreal y1); + CanvasGradient *createRadialGradient(qreal x0, qreal y0, + qreal r0, qreal x1, + qreal y1, qreal r1); + + // rects + void clearRect(qreal x, qreal y, qreal w, qreal h); + void fillRect(qreal x, qreal y, qreal w, qreal h); + void strokeRect(qreal x, qreal y, qreal w, qreal h); + + // path API + void beginPath(); + void closePath(); + void moveTo(qreal x, qreal y); + void lineTo(qreal x, qreal y); + void quadraticCurveTo(qreal cpx, qreal cpy, qreal x, qreal y); + void bezierCurveTo(qreal cp1x, qreal cp1y, + qreal cp2x, qreal cp2y, qreal x, qreal y); + void arcTo(qreal x1, qreal y1, qreal x2, qreal y2, qreal radius); + void rect(qreal x, qreal y, qreal w, qreal h); + void arc(qreal x, qreal y, qreal radius, + qreal startAngle, qreal endAngle, + bool anticlockwise); + void fill(); + void stroke(); + void clip(); + bool isPointInPath(qreal x, qreal y) const; + + CanvasImage *createImage(const QString &url); + + // drawing images (no overloads due to QTBUG-11604) + void drawImage(const QVariant &var, qreal dx, qreal dy, qreal dw, qreal dh); + + // pixel manipulation + ImageData getImageData(qreal sx, qreal sy, qreal sw, qreal sh); + void putImageData(ImageData image, qreal dx, qreal dy); + +signals: + void changed(const QImage &image); + +protected: + void timerEvent(QTimerEvent *e); + +private: + void beginPainting(); + const QImage &endPainting(); + void scheduleChange(); + + int m_changeTimerId; + QImage m_image; + QPainter *m_painter; + QPainterPath m_path; + + enum DirtyFlag { + DirtyTransformationMatrix = 0x00001, + DirtyClippingRegion = 0x00002, + DirtyStrokeStyle = 0x00004, + DirtyFillStyle = 0x00008, + DirtyGlobalAlpha = 0x00010, + DirtyLineWidth = 0x00020, + DirtyLineCap = 0x00040, + DirtyLineJoin = 0x00080, + DirtyMiterLimit = 0x00100, + MDirtyPen = DirtyStrokeStyle + | DirtyLineWidth + | DirtyLineCap + | DirtyLineJoin + | DirtyMiterLimit, + DirtyShadowOffsetX = 0x00200, + DirtyShadowOffsetY = 0x00400, + DirtyShadowBlur = 0x00800, + DirtyShadowColor = 0x01000, + DirtyGlobalCompositeOperation = 0x2000, + DirtyFont = 0x04000, + DirtyTextAlign = 0x08000, + DirtyTextBaseline = 0x10000, + AllIsFullOfDirt = 0xfffff + }; + + struct State { + State() : flags(0) {} + QMatrix matrix; + QPainterPath clipPath; + QBrush strokeStyle; + QBrush fillStyle; + qreal globalAlpha; + qreal lineWidth; + Qt::PenCapStyle lineCap; + Qt::PenJoinStyle lineJoin; + qreal miterLimit; + qreal shadowOffsetX; + qreal shadowOffsetY; + qreal shadowBlur; + QColor shadowColor; + QPainter::CompositionMode globalCompositeOperation; + QFont font; + int textAlign; + int textBaseline; + int flags; + }; + State m_state; + QStack m_stateStack; +}; + +#endif diff --git a/src/src.pro b/src/src.pro new file mode 100644 index 0000000..74f902f --- /dev/null +++ b/src/src.pro @@ -0,0 +1,18 @@ + TEMPLATE = lib + CONFIG += qt plugin + QT += declarative + TARGET = canvasplugin + + DESTDIR = ..\\Canvas + OBJECTS_DIR = tmp + MOC_DIR = tmp + + HEADERS += context2d.h \ + canvas.h \ + canvasplugin.h + + SOURCES += context2d.cpp \ + canvas.cpp \ + canvasplugin.cpp + + -- cgit v1.2.3