blob: 55ebbe907c333ffe61c5b029d1ac042974593c6d [file] [log] [blame]
/****************************************************************************
**
** 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