1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtQuick module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qquickcontext2dcommandbuffer_p.h"
41 #include "qquickcanvasitem_p.h"
42 #include <qqml.h>
43 #include <QtCore/QMutex>
44 #include <QtQuick/qsgtexture.h>
45 #include <QtGui/QPaintEngine>
46 #if QT_CONFIG(opengl)
47 # include <QtGui/QOpenGLContext>
48 # include <QtGui/private/qopenglpaintengine_p.h>
49 #endif
50 
51 #define HAS_SHADOW(offsetX, offsetY, blur, color) (color.isValid() && color.alpha() && (blur || offsetX || offsetY))
52 
53 QT_BEGIN_NAMESPACE
54 
55 void qt_image_boxblur(QImage& image, int radius, bool quality);
56 
57 namespace {
58     class ShadowImageMaker
59     {
60     public:
~ShadowImageMaker()61         virtual ~ShadowImageMaker() {}
62 
paintShapeAndShadow(QPainter * p,qreal offsetX,qreal offsetY,qreal blur,const QColor & color)63         void paintShapeAndShadow(QPainter *p, qreal offsetX, qreal offsetY, qreal blur, const QColor &color)
64         {
65             QRectF bounds = boundingRect().translated(offsetX, offsetY).adjusted(-2*blur, -2*blur, 2*blur, 2*blur);
66             QRect boundsAligned = bounds.toAlignedRect();
67 
68             QImage shadowImage(boundsAligned.size(), QImage::Format_ARGB32_Premultiplied);
69             shadowImage.fill(0);
70 
71             QPainter shadowPainter(&shadowImage);
72             shadowPainter.setRenderHints(p->renderHints());
73             shadowPainter.translate(offsetX - boundsAligned.left(), offsetY - boundsAligned.top());
74             paint(&shadowPainter);
75             shadowPainter.end();
76 
77             if (blur > 0)
78                 qt_image_boxblur(shadowImage, qMax(1, qRound(blur / 2)), true);
79 
80             // blacken the image with shadow color...
81             shadowPainter.begin(&shadowImage);
82             shadowPainter.setCompositionMode(QPainter::CompositionMode_SourceIn);
83             shadowPainter.fillRect(shadowImage.rect(), color);
84             shadowPainter.end();
85 
86             p->drawImage(boundsAligned.topLeft(), shadowImage);
87             paint(p);
88         }
89 
90         virtual void paint(QPainter *p) const = 0;
91         virtual QRectF boundingRect() const = 0;
92     };
93 
94     class FillRectShadow : public ShadowImageMaker
95     {
96     public:
FillRectShadow(const QRectF & rect,const QBrush & brush)97         FillRectShadow(const QRectF &rect, const QBrush &brush)
98             : m_rect(rect.normalized())
99             , m_brush(brush)
100         {
101         }
102 
paint(QPainter * p) const103         void paint(QPainter *p) const override { p->fillRect(m_rect, m_brush); }
boundingRect() const104         QRectF boundingRect() const override { return m_rect; }
105 
106     private:
107         QRectF m_rect;
108         QBrush m_brush;
109     };
110 
111     class FillPathShadow : public ShadowImageMaker
112     {
113     public:
FillPathShadow(const QPainterPath & path,const QBrush & brush)114         FillPathShadow(const QPainterPath &path, const QBrush &brush)
115             : m_path(path)
116             , m_brush(brush)
117         {
118         }
119 
paint(QPainter * p) const120         void paint(QPainter *p) const override { p->fillPath(m_path, m_brush); }
boundingRect() const121         QRectF boundingRect() const override { return m_path.boundingRect(); }
122 
123     private:
124         QPainterPath m_path;
125         QBrush m_brush;
126     };
127 
128     class StrokePathShadow : public ShadowImageMaker
129     {
130     public:
StrokePathShadow(const QPainterPath & path,const QPen & pen)131         StrokePathShadow(const QPainterPath &path, const QPen &pen)
132             : m_path(path)
133             , m_pen(pen)
134         {
135         }
136 
paint(QPainter * p) const137         void paint(QPainter *p) const override { p->strokePath(m_path, m_pen); }
138 
boundingRect() const139         QRectF boundingRect() const override
140         {
141             qreal d = qMax(qreal(1), m_pen.widthF());
142             return m_path.boundingRect().adjusted(-d, -d, d, d);
143         }
144 
145     private:
146         QPainterPath m_path;
147         QPen m_pen;
148     };
149 
150     class DrawImageShadow : public ShadowImageMaker
151     {
152     public:
DrawImageShadow(const QImage & image,const QPointF & offset)153         DrawImageShadow(const QImage &image, const QPointF &offset)
154             : m_image(image)
155             , m_offset(offset)
156         {
157         }
158 
paint(QPainter * p) const159         void paint(QPainter *p) const override { p->drawImage(m_offset, m_image); }
160 
boundingRect() const161         QRectF boundingRect() const override { return QRectF(m_image.rect()).translated(m_offset); }
162 
163     private:
164         QImage m_image;
165         QPointF m_offset;
166     };
167 }
168 
fillRectShadow(QPainter * p,QRectF shadowRect,qreal offsetX,qreal offsetY,qreal blur,const QColor & color)169 static void fillRectShadow(QPainter* p, QRectF shadowRect, qreal offsetX, qreal offsetY, qreal blur, const QColor& color)
170 {
171     FillRectShadow shadowMaker(shadowRect, p->brush());
172     shadowMaker.paintShapeAndShadow(p, offsetX, offsetY, blur, color);
173 }
174 
fillShadowPath(QPainter * p,const QPainterPath & path,qreal offsetX,qreal offsetY,qreal blur,const QColor & color)175 static void fillShadowPath(QPainter* p, const QPainterPath& path, qreal offsetX, qreal offsetY, qreal blur, const QColor& color)
176 {
177     FillPathShadow shadowMaker(path, p->brush());
178     shadowMaker.paintShapeAndShadow(p, offsetX, offsetY, blur, color);
179 }
180 
strokeShadowPath(QPainter * p,const QPainterPath & path,qreal offsetX,qreal offsetY,qreal blur,const QColor & color)181 static void strokeShadowPath(QPainter* p, const QPainterPath& path, qreal offsetX, qreal offsetY, qreal blur, const QColor& color)
182 {
183     StrokePathShadow shadowMaker(path, p->pen());
184     shadowMaker.paintShapeAndShadow(p, offsetX, offsetY, blur, color);
185 }
186 
makePen(const QQuickContext2D::State & state)187 QPen QQuickContext2DCommandBuffer::makePen(const QQuickContext2D::State& state)
188 {
189     QPen pen;
190     pen.setWidthF(state.lineWidth);
191     pen.setCapStyle(state.lineCap);
192     pen.setJoinStyle(state.lineJoin);
193     pen.setMiterLimit(state.miterLimit);
194     pen.setBrush(state.strokeStyle);
195     if (!state.lineDash.isEmpty()) {
196         pen.setDashPattern(state.lineDash);
197     }
198     pen.setDashOffset(state.lineDashOffset);
199     return pen;
200 }
201 
setPainterState(QPainter * p,const QQuickContext2D::State & state,const QPen & pen)202 void QQuickContext2DCommandBuffer::setPainterState(QPainter* p, const QQuickContext2D::State& state, const QPen& pen)
203 {
204    p->setTransform(state.matrix * p->transform());
205 
206    if (pen != p->pen())
207        p->setPen(pen);
208 
209    if (state.fillStyle != p->brush())
210        p->setBrush(state.fillStyle);
211 
212    if (state.font != p->font())
213        p->setFont(state.font);
214 
215    if (state.globalAlpha != p->opacity()) {
216        p->setOpacity(state.globalAlpha);
217    }
218 
219    if (state.globalCompositeOperation != p->compositionMode())
220        p->setCompositionMode(state.globalCompositeOperation);
221 
222    p->setClipping(state.clip);
223    if (state.clip)
224        p->setClipPath(state.clipPath);
225 }
226 
qt_drawImage(QPainter * p,QQuickContext2D::State & state,QImage image,const QRectF & sr,const QRectF & dr,bool shadow=false)227 static void qt_drawImage(QPainter *p, QQuickContext2D::State& state, QImage image, const QRectF& sr, const QRectF& dr, bool shadow = false)
228 {
229     Q_ASSERT(p);
230 
231     if (image.isNull())
232         return;
233 
234     qreal sx = sr.x();
235     qreal sy = sr.y();
236     qreal sw = sr.width();
237     qreal sh = sr.height();
238     qreal dx = dr.x();
239     qreal dy = dr.y();
240     qreal dw = dr.width();
241     qreal dh = dr.height();
242 
243     if (sw == -1 || sh == -1) {
244         sw = image.width();
245         sh = image.height();
246     }
247     if (sx != 0 || sy != 0 || sw != image.width() || sh != image.height())
248         image = image.copy(sx, sy, sw, sh);
249 
250     if (sw != dw || sh != dh)
251         image = image.scaled(dw, dh);
252 
253     //Strange OpenGL painting behavior here, without beginNativePainting/endNativePainting, only the first image is painted.
254     p->beginNativePainting();
255 
256     if (shadow) {
257         DrawImageShadow shadowMaker(image, QPointF(dx, dy));
258         shadowMaker.paintShapeAndShadow(p, state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor);
259     } else {
260         p->drawImage(dx, dy, image);
261     }
262 
263     p->endNativePainting();
264 }
265 
replay(QPainter * p,QQuickContext2D::State & state,const QVector2D & scaleFactor)266 void QQuickContext2DCommandBuffer::replay(QPainter* p, QQuickContext2D::State& state, const QVector2D &scaleFactor)
267 {
268     if (!p)
269         return;
270 
271     reset();
272 
273     p->scale(scaleFactor.x(), scaleFactor.y());
274     QTransform originMatrix = p->worldTransform();
275 
276     QPen pen = makePen(state);
277     setPainterState(p, state, pen);
278 
279     while (hasNext()) {
280         QQuickContext2D::PaintCommand cmd = takeNextCommand();
281         switch (cmd) {
282         case QQuickContext2D::UpdateMatrix:
283         {
284             state.matrix = takeMatrix();
285             p->setWorldTransform(state.matrix * originMatrix);
286             break;
287         }
288         case QQuickContext2D::ClearRect:
289         {
290             QPainter::CompositionMode  cm = p->compositionMode();
291             p->setCompositionMode(QPainter::CompositionMode_Clear);
292             p->fillRect(takeRect(), Qt::white);
293             p->setCompositionMode(cm);
294             break;
295         }
296         case QQuickContext2D::FillRect:
297         {
298             QRectF r = takeRect();
299             if (HAS_SHADOW(state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor))
300                 fillRectShadow(p, r, state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor);
301             else
302                 p->fillRect(r, p->brush());
303             break;
304         }
305         case QQuickContext2D::ShadowColor:
306         {
307             state.shadowColor = takeColor();
308             break;
309         }
310         case QQuickContext2D::ShadowBlur:
311         {
312             state.shadowBlur = takeShadowBlur();
313             break;
314         }
315         case QQuickContext2D::ShadowOffsetX:
316         {
317             state.shadowOffsetX = takeShadowOffsetX();
318             break;
319         }
320         case QQuickContext2D::ShadowOffsetY:
321         {
322             state.shadowOffsetY = takeShadowOffsetY();
323             break;
324         }
325         case QQuickContext2D::FillStyle:
326         {
327             state.fillStyle = takeFillStyle();
328             state.fillPatternRepeatX = takeBool();
329             state.fillPatternRepeatY = takeBool();
330             p->setBrush(state.fillStyle);
331             break;
332         }
333         case QQuickContext2D::StrokeStyle:
334         {
335             state.strokeStyle = takeStrokeStyle();
336             state.strokePatternRepeatX = takeBool();
337             state.strokePatternRepeatY = takeBool();
338             QPen nPen = p->pen();
339             nPen.setBrush(state.strokeStyle);
340             p->setPen(nPen);
341             break;
342         }
343         case QQuickContext2D::LineWidth:
344         {
345             state.lineWidth = takeLineWidth();
346             QPen nPen = p->pen();
347 
348             nPen.setWidthF(state.lineWidth);
349             p->setPen(nPen);
350             break;
351         }
352         case QQuickContext2D::LineCap:
353         {
354             state.lineCap = takeLineCap();
355             QPen nPen = p->pen();
356             nPen.setCapStyle(state.lineCap);
357             p->setPen(nPen);
358             break;
359         }
360         case QQuickContext2D::LineJoin:
361         {
362             state.lineJoin = takeLineJoin();
363             QPen nPen = p->pen();
364             nPen.setJoinStyle(state.lineJoin);
365             p->setPen(nPen);
366             break;
367         }
368         case QQuickContext2D::LineDash:
369         {
370             const qreal count = takeReal();
371             QVector<qreal> pattern;
372             pattern.reserve(count);
373             for (uint i = 0; i < count; i++) {
374                 pattern.append(takeReal());
375             }
376             state.lineDash = pattern;
377             QPen nPen = p->pen();
378             nPen.setDashPattern(pattern);
379             p->setPen(nPen);
380             break;
381         }
382         case QQuickContext2D::LineDashOffset:
383         {
384             state.lineDashOffset = takeReal();
385             QPen nPen = p->pen();
386             nPen.setDashOffset(state.lineDashOffset);
387             p->setPen(nPen);
388             break;
389         }
390         case QQuickContext2D::MiterLimit:
391         {
392             state.miterLimit = takeMiterLimit();
393             QPen nPen = p->pen();
394             nPen.setMiterLimit(state.miterLimit);
395             p->setPen(nPen);
396             break;
397         }
398         case QQuickContext2D::TextAlign:
399         case QQuickContext2D::TextBaseline:
400             break;
401         case QQuickContext2D::Fill:
402         {
403             QPainterPath path = takePath();
404             path.closeSubpath();
405             if (HAS_SHADOW(state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor))
406                 fillShadowPath(p,path, state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor);
407             else
408                 p->fillPath(path, p->brush());
409             break;
410         }
411         case QQuickContext2D::Stroke:
412         {
413             if (HAS_SHADOW(state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor))
414                 strokeShadowPath(p,takePath(), state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor);
415             else
416                 p->strokePath(takePath(), p->pen());
417             break;
418         }
419         case QQuickContext2D::Clip:
420         {
421             state.clip = takeBool();
422             state.clipPath = takePath();
423             p->setClipping(state.clip);
424             if (state.clip)
425                 p->setClipPath(state.clipPath);
426             break;
427         }
428         case QQuickContext2D::GlobalAlpha:
429         {
430             state.globalAlpha = takeGlobalAlpha();
431             p->setOpacity(state.globalAlpha);
432             break;
433         }
434         case QQuickContext2D::GlobalCompositeOperation:
435         {
436             state.globalCompositeOperation = takeGlobalCompositeOperation();
437             p->setCompositionMode(state.globalCompositeOperation);
438             break;
439         }
440         case QQuickContext2D::DrawImage:
441         {
442             QRectF sr = takeRect();
443             QRectF dr = takeRect();
444             qt_drawImage(p, state, takeImage(), sr, dr, HAS_SHADOW(state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor));
445             break;
446         }
447         case QQuickContext2D::DrawPixmap:
448         {
449             QRectF sr = takeRect();
450             QRectF dr = takeRect();
451 
452             QQmlRefPointer<QQuickCanvasPixmap> pix = takePixmap();
453             Q_ASSERT(!pix.isNull());
454 
455             const bool hasShadow = HAS_SHADOW(state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor);
456             //TODO: generate shadow blur with shaders
457             qt_drawImage(p, state, pix->image(), sr, dr, hasShadow);
458             break;
459         }
460         case QQuickContext2D::GetImageData:
461         {
462             //TODO:
463             break;
464         }
465         default:
466             break;
467         }
468     }
469 
470     p->end();
471 }
472 
QQuickContext2DCommandBuffer()473 QQuickContext2DCommandBuffer::QQuickContext2DCommandBuffer()
474     : cmdIdx(0)
475     , intIdx(0)
476     , boolIdx(0)
477     , realIdx(0)
478     , rectIdx(0)
479     , colorIdx(0)
480     , matrixIdx(0)
481     , brushIdx(0)
482     , pathIdx(0)
483     , imageIdx(0)
484     , pixmapIdx(0)
485 {
486     static bool registered = false;
487     if (!registered) {
488         qRegisterMetaType<QQuickContext2DCommandBuffer*>("QQuickContext2DCommandBuffer*");
489         registered = true;
490     }
491 }
492 
493 
~QQuickContext2DCommandBuffer()494 QQuickContext2DCommandBuffer::~QQuickContext2DCommandBuffer()
495 {
496 }
497 
clear()498 void QQuickContext2DCommandBuffer::clear()
499 {
500     commands.clear();
501     ints.clear();
502     bools.clear();
503     reals.clear();
504     rects.clear();
505     colors.clear();
506     matrixes.clear();
507     brushes.clear();
508     pathes.clear();
509     images.clear();
510     pixmaps.clear();
511     reset();
512 }
513 
reset()514 void QQuickContext2DCommandBuffer::reset()
515 {
516     cmdIdx = 0;
517     intIdx = 0;
518     boolIdx = 0;
519     realIdx = 0;
520     rectIdx = 0;
521     colorIdx = 0;
522     matrixIdx = 0;
523     brushIdx = 0;
524     pathIdx = 0;
525     imageIdx = 0;
526     pixmapIdx = 0;
527 }
528 
529 QT_END_NAMESPACE
530