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
53QT_BEGIN_NAMESPACE
54
55void qt_image_boxblur(QImage& image, int radius, bool quality);
56
57namespace {
58 class ShadowImageMaker
59 {
60 public:
61 virtual ~ShadowImageMaker() {}
62
63 void paintShapeAndShadow(QPainter *p, qreal offsetX, qreal offsetY, qreal blur, const QColor &color)
64 {
65 QRectF bounds = boundingRect().translated(dx: offsetX, dy: offsetY).adjusted(xp1: -2*blur, yp1: -2*blur, xp2: 2*blur, yp2: 2*blur);
66 QRect boundsAligned = bounds.toAlignedRect();
67
68 QImage shadowImage(boundsAligned.size(), QImage::Format_ARGB32_Premultiplied);
69 shadowImage.fill(pixel: 0);
70
71 QPainter shadowPainter(&shadowImage);
72 shadowPainter.setRenderHints(hints: p->renderHints());
73 shadowPainter.translate(dx: offsetX - boundsAligned.left(), dy: offsetY - boundsAligned.top());
74 paint(p: &shadowPainter);
75 shadowPainter.end();
76
77 if (blur > 0)
78 qt_image_boxblur(image&: shadowImage, radius: qMax(a: 1, b: qRound(d: blur / 2)), quality: 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(p: boundsAligned.topLeft(), image: 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:
97 FillRectShadow(const QRectF &rect, const QBrush &brush)
98 : m_rect(rect.normalized())
99 , m_brush(brush)
100 {
101 }
102
103 void paint(QPainter *p) const override { p->fillRect(m_rect, m_brush); }
104 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:
114 FillPathShadow(const QPainterPath &path, const QBrush &brush)
115 : m_path(path)
116 , m_brush(brush)
117 {
118 }
119
120 void paint(QPainter *p) const override { p->fillPath(path: m_path, brush: m_brush); }
121 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:
131 StrokePathShadow(const QPainterPath &path, const QPen &pen)
132 : m_path(path)
133 , m_pen(pen)
134 {
135 }
136
137 void paint(QPainter *p) const override { p->strokePath(path: m_path, pen: m_pen); }
138
139 QRectF boundingRect() const override
140 {
141 qreal d = qMax(a: qreal(1), b: m_pen.widthF());
142 return m_path.boundingRect().adjusted(xp1: -d, yp1: -d, xp2: d, yp2: d);
143 }
144
145 private:
146 QPainterPath m_path;
147 QPen m_pen;
148 };
149
150 class DrawImageShadow : public ShadowImageMaker
151 {
152 public:
153 DrawImageShadow(const QImage &image, const QPointF &offset)
154 : m_image(image)
155 , m_offset(offset)
156 {
157 }
158
159 void paint(QPainter *p) const override { p->drawImage(p: m_offset, image: m_image); }
160
161 QRectF boundingRect() const override { return QRectF(m_image.rect()).translated(p: m_offset); }
162
163 private:
164 QImage m_image;
165 QPointF m_offset;
166 };
167}
168
169static 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
175static 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
181static 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
187QPen 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
202void QQuickContext2DCommandBuffer::setPainterState(QPainter* p, const QQuickContext2D::State& state, const QPen& pen)
203{
204 p->setTransform(transform: 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(path: state.clipPath);
225}
226
227static 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(x: sx, y: sy, w: sw, h: sh);
249
250 if (sw != dw || sh != dh)
251 image = image.scaled(w: dw, h: 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, offsetX: state.shadowOffsetX, offsetY: state.shadowOffsetY, blur: state.shadowBlur, color: state.shadowColor);
259 } else {
260 p->drawImage(x: dx, y: dy, image);
261 }
262
263 p->endNativePainting();
264}
265
266void QQuickContext2DCommandBuffer::replay(QPainter* p, QQuickContext2D::State& state, const QVector2D &scaleFactor)
267{
268 if (!p)
269 return;
270
271 reset();
272
273 p->scale(sx: scaleFactor.x(), sy: 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(matrix: 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(r: takeRect(), c: 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, shadowRect: r, offsetX: state.shadowOffsetX, offsetY: state.shadowOffsetY, blur: state.shadowBlur, color: 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(asize: count);
373 for (uint i = 0; i < count; i++) {
374 pattern.append(t: takeReal());
375 }
376 state.lineDash = pattern;
377 QPen nPen = p->pen();
378 if (count > 0)
379 nPen.setDashPattern(pattern);
380 else
381 nPen.setStyle(Qt::SolidLine);
382 p->setPen(nPen);
383 break;
384 }
385 case QQuickContext2D::LineDashOffset:
386 {
387 state.lineDashOffset = takeReal();
388 QPen nPen = p->pen();
389 nPen.setDashOffset(state.lineDashOffset);
390 p->setPen(nPen);
391 break;
392 }
393 case QQuickContext2D::MiterLimit:
394 {
395 state.miterLimit = takeMiterLimit();
396 QPen nPen = p->pen();
397 nPen.setMiterLimit(state.miterLimit);
398 p->setPen(nPen);
399 break;
400 }
401 case QQuickContext2D::TextAlign:
402 case QQuickContext2D::TextBaseline:
403 break;
404 case QQuickContext2D::Fill:
405 {
406 QPainterPath path = takePath();
407 path.closeSubpath();
408 if (HAS_SHADOW(state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor))
409 fillShadowPath(p,path, offsetX: state.shadowOffsetX, offsetY: state.shadowOffsetY, blur: state.shadowBlur, color: state.shadowColor);
410 else
411 p->fillPath(path, brush: p->brush());
412 break;
413 }
414 case QQuickContext2D::Stroke:
415 {
416 if (HAS_SHADOW(state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor))
417 strokeShadowPath(p,path: takePath(), offsetX: state.shadowOffsetX, offsetY: state.shadowOffsetY, blur: state.shadowBlur, color: state.shadowColor);
418 else
419 p->strokePath(path: takePath(), pen: p->pen());
420 break;
421 }
422 case QQuickContext2D::Clip:
423 {
424 state.clip = takeBool();
425 state.clipPath = takePath();
426 p->setClipping(state.clip);
427 if (state.clip)
428 p->setClipPath(path: state.clipPath);
429 break;
430 }
431 case QQuickContext2D::GlobalAlpha:
432 {
433 state.globalAlpha = takeGlobalAlpha();
434 p->setOpacity(state.globalAlpha);
435 break;
436 }
437 case QQuickContext2D::GlobalCompositeOperation:
438 {
439 state.globalCompositeOperation = takeGlobalCompositeOperation();
440 p->setCompositionMode(state.globalCompositeOperation);
441 break;
442 }
443 case QQuickContext2D::DrawImage:
444 {
445 QRectF sr = takeRect();
446 QRectF dr = takeRect();
447 qt_drawImage(p, state, image: takeImage(), sr, dr, HAS_SHADOW(state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor));
448 break;
449 }
450 case QQuickContext2D::DrawPixmap:
451 {
452 QRectF sr = takeRect();
453 QRectF dr = takeRect();
454
455 QQmlRefPointer<QQuickCanvasPixmap> pix = takePixmap();
456 Q_ASSERT(!pix.isNull());
457
458 const bool hasShadow = HAS_SHADOW(state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor);
459 //TODO: generate shadow blur with shaders
460 qt_drawImage(p, state, image: pix->image(), sr, dr, shadow: hasShadow);
461 break;
462 }
463 case QQuickContext2D::GetImageData:
464 {
465 //TODO:
466 break;
467 }
468 default:
469 break;
470 }
471 }
472
473 p->end();
474}
475
476QQuickContext2DCommandBuffer::QQuickContext2DCommandBuffer()
477 : cmdIdx(0)
478 , intIdx(0)
479 , boolIdx(0)
480 , realIdx(0)
481 , rectIdx(0)
482 , colorIdx(0)
483 , matrixIdx(0)
484 , brushIdx(0)
485 , pathIdx(0)
486 , imageIdx(0)
487 , pixmapIdx(0)
488{
489 static bool registered = false;
490 if (!registered) {
491 qRegisterMetaType<QQuickContext2DCommandBuffer*>(typeName: "QQuickContext2DCommandBuffer*");
492 registered = true;
493 }
494}
495
496
497QQuickContext2DCommandBuffer::~QQuickContext2DCommandBuffer()
498{
499}
500
501void QQuickContext2DCommandBuffer::clear()
502{
503 commands.clear();
504 ints.clear();
505 bools.clear();
506 reals.clear();
507 rects.clear();
508 colors.clear();
509 matrixes.clear();
510 brushes.clear();
511 pathes.clear();
512 images.clear();
513 pixmaps.clear();
514 reset();
515}
516
517void QQuickContext2DCommandBuffer::reset()
518{
519 cmdIdx = 0;
520 intIdx = 0;
521 boolIdx = 0;
522 realIdx = 0;
523 rectIdx = 0;
524 colorIdx = 0;
525 matrixIdx = 0;
526 brushIdx = 0;
527 pathIdx = 0;
528 imageIdx = 0;
529 pixmapIdx = 0;
530}
531
532QT_END_NAMESPACE
533

source code of qtdeclarative/src/quick/items/context2d/qquickcontext2dcommandbuffer.cpp