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