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

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