Revision control

++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++ #include "qquickcontext2dcommandbuffer_p.h"
++ #include "qquickcanvasitem_p.h"
++ #include <qqml.h>
++ #include <QtCore/QMutex>
++ #include <QtQuick/qsgtexture.h>
++ #include <QtGui/QPaintEngine>
++
++ #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();
++ QList<qreal> pattern;
++ pattern.reserve(count);
++ for (uint i = 0; i < count; i++) {
++ pattern.append(takeReal());
++ }
++ state.lineDash = pattern;
++ QPen nPen = p->pen();
++ if (count > 0)
++ nPen.setDashPattern(pattern);
++ else
++ nPen.setStyle(Qt::SolidLine);
++ 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