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 | |
13 | QT_BEGIN_NAMESPACE |
14 | |
15 | void qt_image_boxblur(QImage& image, int radius, bool quality); |
16 | |
17 | namespace { |
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 | |
129 | static 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 | |
135 | static 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 | |
141 | static 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 | |
147 | QPen 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 | |
162 | void 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 | |
187 | static 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 | |
226 | void 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 | |
436 | QQuickContext2DCommandBuffer::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 | |
457 | QQuickContext2DCommandBuffer::~QQuickContext2DCommandBuffer() |
458 | { |
459 | } |
460 | |
461 | void 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 | |
477 | void 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 | |
492 | QT_END_NAMESPACE |
493 | |