| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtQuick module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** 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. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qquickcontext2dcommandbuffer_p.h" |
| #include "qquickcanvasitem_p.h" |
| #include <qqml.h> |
| #include <QtCore/QMutex> |
| #include <QtQuick/qsgtexture.h> |
| #include <QtGui/QPaintEngine> |
| #if QT_CONFIG(opengl) |
| # include <QtGui/QOpenGLContext> |
| # include <QtGui/private/qopenglpaintengine_p.h> |
| #endif |
| |
| #define HAS_SHADOW(offsetX, offsetY, blur, color) (color.isValid() && color.alpha() && (blur || offsetX || offsetY)) |
| |
| QT_BEGIN_NAMESPACE |
| |
| void qt_image_boxblur(QImage& image, int radius, bool quality); |
| |
| namespace { |
| class ShadowImageMaker |
| { |
| public: |
| virtual ~ShadowImageMaker() {} |
| |
| void paintShapeAndShadow(QPainter *p, qreal offsetX, qreal offsetY, qreal blur, const QColor &color) |
| { |
| QRectF bounds = boundingRect().translated(offsetX, offsetY).adjusted(-2*blur, -2*blur, 2*blur, 2*blur); |
| QRect boundsAligned = bounds.toAlignedRect(); |
| |
| QImage shadowImage(boundsAligned.size(), QImage::Format_ARGB32_Premultiplied); |
| shadowImage.fill(0); |
| |
| QPainter shadowPainter(&shadowImage); |
| shadowPainter.setRenderHints(p->renderHints()); |
| shadowPainter.translate(offsetX - boundsAligned.left(), offsetY - boundsAligned.top()); |
| paint(&shadowPainter); |
| shadowPainter.end(); |
| |
| if (blur > 0) |
| qt_image_boxblur(shadowImage, qMax(1, qRound(blur / 2)), true); |
| |
| // blacken the image with shadow color... |
| shadowPainter.begin(&shadowImage); |
| shadowPainter.setCompositionMode(QPainter::CompositionMode_SourceIn); |
| shadowPainter.fillRect(shadowImage.rect(), color); |
| shadowPainter.end(); |
| |
| p->drawImage(boundsAligned.topLeft(), shadowImage); |
| paint(p); |
| } |
| |
| virtual void paint(QPainter *p) const = 0; |
| virtual QRectF boundingRect() const = 0; |
| }; |
| |
| class FillRectShadow : public ShadowImageMaker |
| { |
| public: |
| FillRectShadow(const QRectF &rect, const QBrush &brush) |
| : m_rect(rect.normalized()) |
| , m_brush(brush) |
| { |
| } |
| |
| void paint(QPainter *p) const override { p->fillRect(m_rect, m_brush); } |
| QRectF boundingRect() const override { return m_rect; } |
| |
| private: |
| QRectF m_rect; |
| QBrush m_brush; |
| }; |
| |
| class FillPathShadow : public ShadowImageMaker |
| { |
| public: |
| FillPathShadow(const QPainterPath &path, const QBrush &brush) |
| : m_path(path) |
| , m_brush(brush) |
| { |
| } |
| |
| void paint(QPainter *p) const override { p->fillPath(m_path, m_brush); } |
| QRectF boundingRect() const override { return m_path.boundingRect(); } |
| |
| private: |
| QPainterPath m_path; |
| QBrush m_brush; |
| }; |
| |
| class StrokePathShadow : public ShadowImageMaker |
| { |
| public: |
| StrokePathShadow(const QPainterPath &path, const QPen &pen) |
| : m_path(path) |
| , m_pen(pen) |
| { |
| } |
| |
| void paint(QPainter *p) const override { p->strokePath(m_path, m_pen); } |
| |
| QRectF boundingRect() const override |
| { |
| qreal d = qMax(qreal(1), m_pen.widthF()); |
| return m_path.boundingRect().adjusted(-d, -d, d, d); |
| } |
| |
| private: |
| QPainterPath m_path; |
| QPen m_pen; |
| }; |
| |
| class DrawImageShadow : public ShadowImageMaker |
| { |
| public: |
| DrawImageShadow(const QImage &image, const QPointF &offset) |
| : m_image(image) |
| , m_offset(offset) |
| { |
| } |
| |
| void paint(QPainter *p) const override { p->drawImage(m_offset, m_image); } |
| |
| QRectF boundingRect() const override { return QRectF(m_image.rect()).translated(m_offset); } |
| |
| private: |
| QImage m_image; |
| QPointF m_offset; |
| }; |
| } |
| |
| static void fillRectShadow(QPainter* p, QRectF shadowRect, qreal offsetX, qreal offsetY, qreal blur, const QColor& color) |
| { |
| FillRectShadow shadowMaker(shadowRect, p->brush()); |
| shadowMaker.paintShapeAndShadow(p, offsetX, offsetY, blur, color); |
| } |
| |
| static void fillShadowPath(QPainter* p, const QPainterPath& path, qreal offsetX, qreal offsetY, qreal blur, const QColor& color) |
| { |
| FillPathShadow shadowMaker(path, p->brush()); |
| shadowMaker.paintShapeAndShadow(p, offsetX, offsetY, blur, color); |
| } |
| |
| static void strokeShadowPath(QPainter* p, const QPainterPath& path, qreal offsetX, qreal offsetY, qreal blur, const QColor& color) |
| { |
| StrokePathShadow shadowMaker(path, p->pen()); |
| shadowMaker.paintShapeAndShadow(p, offsetX, offsetY, blur, color); |
| } |
| |
| QPen QQuickContext2DCommandBuffer::makePen(const QQuickContext2D::State& state) |
| { |
| QPen pen; |
| pen.setWidthF(state.lineWidth); |
| pen.setCapStyle(state.lineCap); |
| pen.setJoinStyle(state.lineJoin); |
| pen.setMiterLimit(state.miterLimit); |
| pen.setBrush(state.strokeStyle); |
| if (!state.lineDash.isEmpty()) { |
| pen.setDashPattern(state.lineDash); |
| } |
| pen.setDashOffset(state.lineDashOffset); |
| return pen; |
| } |
| |
| void QQuickContext2DCommandBuffer::setPainterState(QPainter* p, const QQuickContext2D::State& state, const QPen& pen) |
| { |
| p->setTransform(state.matrix * p->transform()); |
| |
| if (pen != p->pen()) |
| p->setPen(pen); |
| |
| if (state.fillStyle != p->brush()) |
| p->setBrush(state.fillStyle); |
| |
| if (state.font != p->font()) |
| p->setFont(state.font); |
| |
| if (state.globalAlpha != p->opacity()) { |
| p->setOpacity(state.globalAlpha); |
| } |
| |
| if (state.globalCompositeOperation != p->compositionMode()) |
| p->setCompositionMode(state.globalCompositeOperation); |
| |
| p->setClipping(state.clip); |
| if (state.clip) |
| p->setClipPath(state.clipPath); |
| } |
| |
| static void qt_drawImage(QPainter *p, QQuickContext2D::State& state, QImage image, const QRectF& sr, const QRectF& dr, bool shadow = false) |
| { |
| Q_ASSERT(p); |
| |
| if (image.isNull()) |
| return; |
| |
| qreal sx = sr.x(); |
| qreal sy = sr.y(); |
| qreal sw = sr.width(); |
| qreal sh = sr.height(); |
| qreal dx = dr.x(); |
| qreal dy = dr.y(); |
| qreal dw = dr.width(); |
| qreal dh = dr.height(); |
| |
| if (sw == -1 || sh == -1) { |
| sw = image.width(); |
| sh = image.height(); |
| } |
| if (sx != 0 || sy != 0 || sw != image.width() || sh != image.height()) |
| image = image.copy(sx, sy, sw, sh); |
| |
| if (sw != dw || sh != dh) |
| image = image.scaled(dw, dh); |
| |
| //Strange OpenGL painting behavior here, without beginNativePainting/endNativePainting, only the first image is painted. |
| p->beginNativePainting(); |
| |
| if (shadow) { |
| DrawImageShadow shadowMaker(image, QPointF(dx, dy)); |
| shadowMaker.paintShapeAndShadow(p, state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor); |
| } else { |
| p->drawImage(dx, dy, image); |
| } |
| |
| p->endNativePainting(); |
| } |
| |
| void QQuickContext2DCommandBuffer::replay(QPainter* p, QQuickContext2D::State& state, const QVector2D &scaleFactor) |
| { |
| if (!p) |
| return; |
| |
| reset(); |
| |
| p->scale(scaleFactor.x(), scaleFactor.y()); |
| QTransform originMatrix = p->worldTransform(); |
| |
| QPen pen = makePen(state); |
| setPainterState(p, state, pen); |
| |
| while (hasNext()) { |
| QQuickContext2D::PaintCommand cmd = takeNextCommand(); |
| switch (cmd) { |
| case QQuickContext2D::UpdateMatrix: |
| { |
| state.matrix = takeMatrix(); |
| p->setWorldTransform(state.matrix * originMatrix); |
| break; |
| } |
| case QQuickContext2D::ClearRect: |
| { |
| QPainter::CompositionMode cm = p->compositionMode(); |
| p->setCompositionMode(QPainter::CompositionMode_Clear); |
| p->fillRect(takeRect(), Qt::white); |
| p->setCompositionMode(cm); |
| break; |
| } |
| case QQuickContext2D::FillRect: |
| { |
| QRectF r = takeRect(); |
| if (HAS_SHADOW(state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor)) |
| fillRectShadow(p, r, state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor); |
| else |
| p->fillRect(r, p->brush()); |
| break; |
| } |
| case QQuickContext2D::ShadowColor: |
| { |
| state.shadowColor = takeColor(); |
| break; |
| } |
| case QQuickContext2D::ShadowBlur: |
| { |
| state.shadowBlur = takeShadowBlur(); |
| break; |
| } |
| case QQuickContext2D::ShadowOffsetX: |
| { |
| state.shadowOffsetX = takeShadowOffsetX(); |
| break; |
| } |
| case QQuickContext2D::ShadowOffsetY: |
| { |
| state.shadowOffsetY = takeShadowOffsetY(); |
| break; |
| } |
| case QQuickContext2D::FillStyle: |
| { |
| state.fillStyle = takeFillStyle(); |
| state.fillPatternRepeatX = takeBool(); |
| state.fillPatternRepeatY = takeBool(); |
| p->setBrush(state.fillStyle); |
| break; |
| } |
| case QQuickContext2D::StrokeStyle: |
| { |
| state.strokeStyle = takeStrokeStyle(); |
| state.strokePatternRepeatX = takeBool(); |
| state.strokePatternRepeatY = takeBool(); |
| QPen nPen = p->pen(); |
| nPen.setBrush(state.strokeStyle); |
| p->setPen(nPen); |
| break; |
| } |
| case QQuickContext2D::LineWidth: |
| { |
| state.lineWidth = takeLineWidth(); |
| QPen nPen = p->pen(); |
| |
| nPen.setWidthF(state.lineWidth); |
| p->setPen(nPen); |
| break; |
| } |
| case QQuickContext2D::LineCap: |
| { |
| state.lineCap = takeLineCap(); |
| QPen nPen = p->pen(); |
| nPen.setCapStyle(state.lineCap); |
| p->setPen(nPen); |
| break; |
| } |
| case QQuickContext2D::LineJoin: |
| { |
| state.lineJoin = takeLineJoin(); |
| QPen nPen = p->pen(); |
| nPen.setJoinStyle(state.lineJoin); |
| p->setPen(nPen); |
| break; |
| } |
| case QQuickContext2D::LineDash: |
| { |
| const qreal count = takeReal(); |
| QVector<qreal> pattern; |
| pattern.reserve(count); |
| for (uint i = 0; i < count; i++) { |
| pattern.append(takeReal()); |
| } |
| state.lineDash = pattern; |
| QPen nPen = p->pen(); |
| nPen.setDashPattern(pattern); |
| p->setPen(nPen); |
| break; |
| } |
| case QQuickContext2D::LineDashOffset: |
| { |
| state.lineDashOffset = takeReal(); |
| QPen nPen = p->pen(); |
| nPen.setDashOffset(state.lineDashOffset); |
| p->setPen(nPen); |
| break; |
| } |
| case QQuickContext2D::MiterLimit: |
| { |
| state.miterLimit = takeMiterLimit(); |
| QPen nPen = p->pen(); |
| nPen.setMiterLimit(state.miterLimit); |
| p->setPen(nPen); |
| break; |
| } |
| case QQuickContext2D::TextAlign: |
| case QQuickContext2D::TextBaseline: |
| break; |
| case QQuickContext2D::Fill: |
| { |
| QPainterPath path = takePath(); |
| path.closeSubpath(); |
| if (HAS_SHADOW(state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor)) |
| fillShadowPath(p,path, state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor); |
| else |
| p->fillPath(path, p->brush()); |
| break; |
| } |
| case QQuickContext2D::Stroke: |
| { |
| if (HAS_SHADOW(state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor)) |
| strokeShadowPath(p,takePath(), state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor); |
| else |
| p->strokePath(takePath(), p->pen()); |
| break; |
| } |
| case QQuickContext2D::Clip: |
| { |
| state.clip = takeBool(); |
| state.clipPath = takePath(); |
| p->setClipping(state.clip); |
| if (state.clip) |
| p->setClipPath(state.clipPath); |
| break; |
| } |
| case QQuickContext2D::GlobalAlpha: |
| { |
| state.globalAlpha = takeGlobalAlpha(); |
| p->setOpacity(state.globalAlpha); |
| break; |
| } |
| case QQuickContext2D::GlobalCompositeOperation: |
| { |
| state.globalCompositeOperation = takeGlobalCompositeOperation(); |
| p->setCompositionMode(state.globalCompositeOperation); |
| break; |
| } |
| case QQuickContext2D::DrawImage: |
| { |
| QRectF sr = takeRect(); |
| QRectF dr = takeRect(); |
| qt_drawImage(p, state, takeImage(), sr, dr, HAS_SHADOW(state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor)); |
| break; |
| } |
| case QQuickContext2D::DrawPixmap: |
| { |
| QRectF sr = takeRect(); |
| QRectF dr = takeRect(); |
| |
| QQmlRefPointer<QQuickCanvasPixmap> pix = takePixmap(); |
| Q_ASSERT(!pix.isNull()); |
| |
| const bool hasShadow = HAS_SHADOW(state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor); |
| //TODO: generate shadow blur with shaders |
| qt_drawImage(p, state, pix->image(), sr, dr, hasShadow); |
| break; |
| } |
| case QQuickContext2D::GetImageData: |
| { |
| //TODO: |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| |
| p->end(); |
| } |
| |
| QQuickContext2DCommandBuffer::QQuickContext2DCommandBuffer() |
| : cmdIdx(0) |
| , intIdx(0) |
| , boolIdx(0) |
| , realIdx(0) |
| , rectIdx(0) |
| , colorIdx(0) |
| , matrixIdx(0) |
| , brushIdx(0) |
| , pathIdx(0) |
| , imageIdx(0) |
| , pixmapIdx(0) |
| { |
| static bool registered = false; |
| if (!registered) { |
| qRegisterMetaType<QQuickContext2DCommandBuffer*>("QQuickContext2DCommandBuffer*"); |
| registered = true; |
| } |
| } |
| |
| |
| QQuickContext2DCommandBuffer::~QQuickContext2DCommandBuffer() |
| { |
| } |
| |
| void QQuickContext2DCommandBuffer::clear() |
| { |
| commands.clear(); |
| ints.clear(); |
| bools.clear(); |
| reals.clear(); |
| rects.clear(); |
| colors.clear(); |
| matrixes.clear(); |
| brushes.clear(); |
| pathes.clear(); |
| images.clear(); |
| pixmaps.clear(); |
| reset(); |
| } |
| |
| void QQuickContext2DCommandBuffer::reset() |
| { |
| cmdIdx = 0; |
| intIdx = 0; |
| boolIdx = 0; |
| realIdx = 0; |
| rectIdx = 0; |
| colorIdx = 0; |
| matrixIdx = 0; |
| brushIdx = 0; |
| pathIdx = 0; |
| imageIdx = 0; |
| pixmapIdx = 0; |
| } |
| |
| QT_END_NAMESPACE |