1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtQml module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qquickcontext2dcommandbuffer_p.h"
43 #include "qquickcanvasitem_p.h"
45 #include <QtCore/QMutex>
46 #include <QtQuick/qsgtexture.h>
47 #include <QtGui/QOpenGLContext>
48 #include <QtGui/QPaintEngine>
49 #include <QtGui/private/qopenglpaintengine_p.h>
51 #define HAS_SHADOW(offsetX, offsetY, blur, color) (color.isValid() && color.alpha() && (blur || offsetX || offsetY))
55 void qt_image_boxblur(QImage& image, int radius, bool quality);
57 static QImage makeShadowImage(const QImage& image, qreal offsetX, qreal offsetY, qreal blur, const QColor& color)
59 QImage shadowImg(image.width() + blur + qAbs(offsetX),
60 image.height() + blur + qAbs(offsetY),
61 QImage::Format_ARGB32_Premultiplied);
63 QPainter tmpPainter(&shadowImg);
64 tmpPainter.setCompositionMode(QPainter::CompositionMode_Source);
65 qreal shadowX = offsetX > 0? offsetX : 0;
66 qreal shadowY = offsetY > 0? offsetY : 0;
68 tmpPainter.drawImage(shadowX, shadowY, image);
72 qt_image_boxblur(shadowImg, blur/2, true);
74 // blacken the image with shadow color...
75 tmpPainter.begin(&shadowImg);
76 tmpPainter.setCompositionMode(QPainter::CompositionMode_SourceIn);
77 tmpPainter.fillRect(shadowImg.rect(), color);
82 static void fillRectShadow(QPainter* p, QRectF shadowRect, qreal offsetX, qreal offsetY, qreal blur, const QColor& color)
84 QRectF r = shadowRect;
87 QImage shadowImage(r.size().width() + 1, r.size().height() + 1, QImage::Format_ARGB32_Premultiplied);
89 tp.begin(&shadowImage);
90 tp.fillRect(r, p->brush());
92 shadowImage = makeShadowImage(shadowImage, offsetX, offsetY, blur, color);
94 qreal dx = shadowRect.left() + (offsetX < 0? offsetX:0);
95 qreal dy = shadowRect.top() + (offsetY < 0? offsetY:0);
97 p->drawImage(dx, dy, shadowImage);
98 p->fillRect(shadowRect, p->brush());
101 static void fillShadowPath(QPainter* p, const QPainterPath& path, qreal offsetX, qreal offsetY, qreal blur, const QColor& color)
103 QRectF r = path.boundingRect();
104 QImage img(r.size().width() + r.left() + 1,
105 r.size().height() + r.top() + 1,
106 QImage::Format_ARGB32_Premultiplied);
109 tp.fillPath(path.translated(0, 0), p->brush());
112 QImage shadowImage = makeShadowImage(img, offsetX, offsetY, blur, color);
113 qreal dx = r.left() + (offsetX < 0? offsetX:0);
114 qreal dy = r.top() + (offsetY < 0? offsetY:0);
116 p->drawImage(dx, dy, shadowImage);
117 p->fillPath(path, p->brush());
120 static void strokeShadowPath(QPainter* p, const QPainterPath& path, qreal offsetX, qreal offsetY, qreal blur, const QColor& color)
122 QRectF r = path.boundingRect();
123 QImage img(r.size().width() + r.left() + 1,
124 r.size().height() + r.top() + 1,
125 QImage::Format_ARGB32_Premultiplied);
128 tp.strokePath(path, p->pen());
131 QImage shadowImage = makeShadowImage(img, offsetX, offsetY, blur, color);
132 qreal dx = r.left() + (offsetX < 0? offsetX:0);
133 qreal dy = r.top() + (offsetY < 0? offsetY:0);
134 p->drawImage(dx, dy, shadowImage);
135 p->strokePath(path, p->pen());
137 static inline void drawRepeatPattern(QPainter* p, const QImage& image, const QRectF& rect, const bool repeatX, const bool repeatY)
139 // Patterns must be painted so that the top left of the first image is anchored at
140 // the origin of the coordinate space
141 if (!image.isNull()) {
142 int w = image.width();
143 int h = image.height();
145 QRect r(static_cast<int>(rect.x()), static_cast<int>(rect.y()), static_cast<int>(rect.width()), static_cast<int>(rect.height()));
147 // startX, startY is the coordinate of the first image we need to put on the left-top of the rect
148 if (repeatX && repeatY) {
150 // startX, startY is at the left top side of the left-top of the rect
151 startX = r.x() >=0 ? r.x() - (r.x() % w) : r.x() - (w - qAbs(r.x()) % w);
152 startY = r.y() >=0 ? r.y() - (r.y() % h) : r.y() - (h - qAbs(r.y()) % h);
154 if (!repeatX && !repeatY) {
156 // only draw the image once at orgin once, check if need to draw
157 QRect imageRect(0, 0, w, h);
158 if (imageRect.intersects(r)) {
163 } else if (repeatX && !repeatY) {
165 // startY is fixed, but startX change based on the left-top of the rect
166 QRect imageRect(r.x(), 0, r.width(), h);
167 if (imageRect.intersects(r)) {
168 startX = r.x() >=0 ? r.x() - (r.x() % w) : r.x() - (w - qAbs(r.x()) % w);
174 // startX is fixed, but startY change based on the left-top of the rect
175 QRect imageRect(0, r.y(), w, r.height());
176 if (imageRect.intersects(r)) {
178 startY = r.y() >=0 ? r.y() - (r.y() % h) : r.y() - (h - qAbs(r.y()) % h);
190 QRect imageRect(x, y, w, h);
191 QRect intersectRect = imageRect.intersected(r);
192 QPoint destStart(intersectRect.x(), intersectRect.y());
193 QRect sourceRect(intersectRect.x() - imageRect.x(), intersectRect.y() - imageRect.y(), intersectRect.width(), intersectRect.height());
195 p->drawImage(destStart, image, sourceRect);
197 } while (repeatX && x < r.x() + r.width());
200 } while (repeatY && y < r.y() + r.height());
204 QPen QQuickContext2DCommandBuffer::makePen(const QQuickContext2D::State& state)
207 pen.setWidthF(state.lineWidth);
208 pen.setCapStyle(state.lineCap);
209 pen.setJoinStyle(state.lineJoin);
210 pen.setMiterLimit(state.miterLimit);
211 pen.setBrush(state.strokeStyle);
215 void QQuickContext2DCommandBuffer::setPainterState(QPainter* p, const QQuickContext2D::State& state, const QPen& pen)
217 p->setTransform(state.matrix * p->transform());
222 if (state.fillStyle != p->brush())
223 p->setBrush(state.fillStyle);
225 if (state.font != p->font())
226 p->setFont(state.font);
228 if (state.globalAlpha != p->opacity()) {
229 p->setOpacity(state.globalAlpha);
232 if (state.globalCompositeOperation != p->compositionMode())
233 p->setCompositionMode(state.globalCompositeOperation);
236 static void qt_drawImage(QPainter *p, QQuickContext2D::State& state, QImage image, const QRectF& sr, const QRectF& dr, bool shadow = false)
245 qreal sw = sr.width();
246 qreal sh = sr.height();
249 qreal dw = dr.width();
250 qreal dh = dr.height();
252 if (sw == -1 || sh == -1) {
256 if (sx != 0 || sy != 0 || sw != image.width() || sh != image.height())
257 image = image.copy(sx, sy, sw, sh);
259 if (sw != dw || sh != dh)
260 image = image.scaled(dw, dh);
263 QImage shadow = makeShadowImage(image, state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor);
264 qreal shadow_dx = dx + (state.shadowOffsetX < 0? state.shadowOffsetY:0);
265 qreal shadow_dy = dy + (state.shadowOffsetX < 0? state.shadowOffsetY:0);
266 p->drawImage(shadow_dx, shadow_dy, shadow);
268 //Strange OpenGL painting behavior here, without beginNativePainting/endNativePainting, only the first image is painted.
269 p->beginNativePainting();
270 p->drawImage(dx, dy, image);
271 p->endNativePainting();
274 void QQuickContext2DCommandBuffer::replay(QPainter* p, QQuickContext2D::State& state)
281 QTransform originMatrix = p->worldTransform();
283 QPen pen = makePen(state);
284 setPainterState(p, state, pen);
287 QQuickContext2D::PaintCommand cmd = takeNextCommand();
289 case QQuickContext2D::UpdateMatrix:
291 state.matrix = takeMatrix();
292 p->setWorldTransform(state.matrix * originMatrix);
295 case QQuickContext2D::ClearRect:
297 QPainter::CompositionMode cm = p->compositionMode();
298 qreal alpha = p->opacity();
299 p->setCompositionMode(QPainter::CompositionMode_Source);
301 p->fillRect(takeRect(), QColor(qRgba(0, 0, 0, 0)));
302 p->setCompositionMode(cm);
303 p->setOpacity(alpha);
306 case QQuickContext2D::FillRect:
308 QRectF r = takeRect();
309 if (HAS_SHADOW(state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor))
310 fillRectShadow(p, r, state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor);
312 p->fillRect(r, p->brush());
315 case QQuickContext2D::ShadowColor:
317 state.shadowColor = takeColor();
320 case QQuickContext2D::ShadowBlur:
322 state.shadowBlur = takeShadowBlur();
325 case QQuickContext2D::ShadowOffsetX:
327 state.shadowOffsetX = takeShadowOffsetX();
330 case QQuickContext2D::ShadowOffsetY:
332 state.shadowOffsetY = takeShadowOffsetY();
335 case QQuickContext2D::FillStyle:
337 state.fillStyle = takeFillStyle();
338 state.fillPatternRepeatX = takeBool();
339 state.fillPatternRepeatY = takeBool();
340 p->setBrush(state.fillStyle);
343 case QQuickContext2D::StrokeStyle:
345 state.strokeStyle = takeStrokeStyle();
346 state.strokePatternRepeatX = takeBool();
347 state.strokePatternRepeatY = takeBool();
348 QPen nPen = p->pen();
349 nPen.setBrush(state.strokeStyle);
353 case QQuickContext2D::LineWidth:
355 state.lineWidth = takeLineWidth();
356 QPen nPen = p->pen();
358 nPen.setWidthF(state.lineWidth);
362 case QQuickContext2D::LineCap:
364 state.lineCap = takeLineCap();
365 QPen nPen = p->pen();
366 nPen.setCapStyle(state.lineCap);
370 case QQuickContext2D::LineJoin:
372 state.lineJoin = takeLineJoin();
373 QPen nPen = p->pen();
374 nPen.setJoinStyle(state.lineJoin);
378 case QQuickContext2D::MiterLimit:
380 state.miterLimit = takeMiterLimit();
381 QPen nPen = p->pen();
382 nPen.setMiterLimit(state.miterLimit);
386 case QQuickContext2D::TextAlign:
387 case QQuickContext2D::TextBaseline:
389 case QQuickContext2D::Fill:
391 QPainterPath path = takePath();
393 if (HAS_SHADOW(state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor))
394 fillShadowPath(p,path, state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor);
396 p->fillPath(path, p->brush());
399 case QQuickContext2D::Stroke:
401 if (HAS_SHADOW(state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor))
402 strokeShadowPath(p,takePath(), state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor);
404 p->strokePath(takePath(), p->pen());
407 case QQuickContext2D::Clip:
409 state.clipPath = takePath();
410 p->setClipping(true);
411 p->setClipPath(state.clipPath);
414 case QQuickContext2D::GlobalAlpha:
416 state.globalAlpha = takeGlobalAlpha();
417 p->setOpacity(state.globalAlpha);
420 case QQuickContext2D::GlobalCompositeOperation:
422 state.globalCompositeOperation = takeGlobalCompositeOperation();
423 p->setCompositionMode(state.globalCompositeOperation);
426 case QQuickContext2D::DrawImage:
428 QRectF sr = takeRect();
429 QRectF dr = takeRect();
430 qt_drawImage(p, state, takeImage(), sr, dr, HAS_SHADOW(state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor));
433 case QQuickContext2D::DrawPixmap:
435 QRectF sr = takeRect();
436 QRectF dr = takeRect();
438 QQmlRefPointer<QQuickCanvasPixmap> pix = takePixmap();
439 Q_ASSERT(!pix.isNull());
441 const bool hasShadow = HAS_SHADOW(state.shadowOffsetX, state.shadowOffsetY, state.shadowBlur, state.shadowColor);
442 if (p->paintEngine()->type() != QPaintEngine::OpenGL2 || hasShadow){
443 //TODO: generate shadow blur with shaders
444 qt_drawImage(p, state, pix->image(), sr, dr, hasShadow);
445 } else if (pix->texture()){
446 QSGTexture *tex = pix->texture();
447 QSGDynamicTexture *dynamicTexture = qobject_cast<QSGDynamicTexture *>(tex);
449 dynamicTexture->updateTexture();
451 if (tex->textureId()) {
454 sr.setWidth(tex->textureSize().width());
456 sr.setHeight(tex->textureSize().height());
459 dr.setWidth(sr.width());
461 dr.setHeight(sr.height());
463 qreal srBottom = sr.bottom();
464 sr.setBottom(sr.top());
468 if (p->paintEngine()->type() == QPaintEngine::OpenGL2) {
469 QOpenGL2PaintEngineEx *engine = static_cast<QOpenGL2PaintEngineEx *>(p->paintEngine());
470 engine->drawTexture(dr, tex->textureId(), tex->textureSize(), sr);
476 case QQuickContext2D::GetImageData:
489 QQuickContext2DCommandBuffer::QQuickContext2DCommandBuffer()
502 static bool registered = false;
504 qRegisterMetaType<QQuickContext2DCommandBuffer*>("QQuickContext2DCommandBuffer*");
510 QQuickContext2DCommandBuffer::~QQuickContext2DCommandBuffer()
514 void QQuickContext2DCommandBuffer::clear()
530 void QQuickContext2DCommandBuffer::reset()