2 /****************************************************************************
4 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
5 ** All rights reserved.
6 ** Contact: http://www.qt-project.org/
8 ** This file is part of the QtDeclarative module of the Qt Toolkit.
10 ** $QT_BEGIN_LICENSE:LGPL$
11 ** GNU Lesser General Public License Usage
12 ** This file may be used under the terms of the GNU Lesser General Public
13 ** License version 2.1 as published by the Free Software Foundation and
14 ** appearing in the file LICENSE.LGPL included in the packaging of this
15 ** file. Please review the following information to ensure the GNU Lesser
16 ** General Public License version 2.1 requirements will be met:
17 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
19 ** In addition, as a special exception, Nokia gives you certain additional
20 ** rights. These rights are described in the Nokia Qt LGPL Exception
21 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
23 ** GNU General Public License Usage
24 ** Alternatively, this file may be used under the terms of the GNU General
25 ** Public License version 3.0 as published by the Free Software Foundation
26 ** and appearing in the file LICENSE.GPL included in the packaging of this
27 ** file. Please review the following information to ensure the GNU General
28 ** Public License version 3.0 requirements will be met:
29 ** http://www.gnu.org/copyleft/gpl.html.
32 ** Alternatively, this file may be used in accordance with the terms and
33 ** conditions contained in a signed written agreement between you and Nokia.
41 ****************************************************************************/
45 #include "qsgdefaultrectanglenode_p.h"
47 #include <QtQuick/qsgvertexcolormaterial.h>
48 #include <QtQuick/qsgtexturematerial.h>
50 #include <QtQuick/private/qsgcontext_p.h>
52 #include <QtCore/qmath.h>
53 #include <QtCore/qvarlengtharray.h>
57 QSGDefaultRectangleNode::QSGDefaultRectangleNode(QSGContext *context)
62 , m_gradient_is_opaque(true)
63 , m_dirty_geometry(false)
64 , m_default_geometry(QSGGeometry::defaultAttributes_Point2D(), 4)
67 setGeometry(&m_default_geometry);
68 setMaterial(&m_fill_material);
69 m_border_material.setColor(QColor(0, 0, 0));
71 m_material_type = TypeFlat;
73 #ifdef QML_RUNTIME_TESTING
74 description = QLatin1String("rectangle");
78 QSGDefaultRectangleNode::~QSGDefaultRectangleNode()
80 if (m_material_type == TypeVertexGradient)
85 QSGGeometryNode *QSGDefaultRectangleNode::border()
88 m_border = new QSGGeometryNode;
89 m_border->setMaterial(&m_border_material);
90 QSGGeometry *geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 0);
91 m_border->setGeometry(geometry);
92 m_border->setFlag(QSGNode::OwnsGeometry);
97 void QSGDefaultRectangleNode::setRect(const QRectF &rect)
102 m_dirty_geometry = true;
105 void QSGDefaultRectangleNode::setColor(const QColor &color)
107 if (color == m_fill_material.color())
109 m_fill_material.setColor(color);
110 if (m_gradient_stops.isEmpty()) {
111 Q_ASSERT(m_material_type == TypeFlat);
112 markDirty(DirtyMaterial);
116 void QSGDefaultRectangleNode::setPenColor(const QColor &color)
118 if (color == m_border_material.color())
120 m_border_material.setColor(color);
122 m_border->markDirty(DirtyMaterial);
125 void QSGDefaultRectangleNode::setPenWidth(qreal width)
127 if (width == m_pen_width)
130 if (m_pen_width <= 0 && m_border && m_border->parent())
131 removeChildNode(m_border);
132 else if (m_pen_width > 0 && !border()->parent())
133 appendChildNode(m_border);
134 m_dirty_geometry = true;
138 void QSGDefaultRectangleNode::setGradientStops(const QGradientStops &stops)
140 if (stops.constData() == m_gradient_stops.constData())
143 m_gradient_stops = stops;
145 m_gradient_is_opaque = true;
146 for (int i = 0; i < stops.size(); ++i)
147 m_gradient_is_opaque &= stops.at(i).second.alpha() == 0xff;
149 if (stops.isEmpty()) {
150 // No gradient specified, use flat color.
151 if (m_material_type != TypeFlat) {
154 setMaterial(&m_fill_material);
155 m_material_type = TypeFlat;
157 setGeometry(&m_default_geometry);
158 setFlag(OwnsGeometry, false);
161 if (m_material_type == TypeFlat) {
162 QSGVertexColorMaterial *material = new QSGVertexColorMaterial;
163 setMaterial(material);
164 m_material_type = TypeVertexGradient;
165 QSGGeometry *g = new QSGGeometry(QSGGeometry::defaultAttributes_ColoredPoint2D(), 0);
167 setFlag(OwnsGeometry);
169 static_cast<QSGVertexColorMaterial *>(material())->setFlag(QSGMaterial::Blending, !m_gradient_is_opaque);
172 m_dirty_geometry = true;
175 void QSGDefaultRectangleNode::setRadius(qreal radius)
177 if (radius == m_radius)
180 m_dirty_geometry = true;
183 void QSGDefaultRectangleNode::setAligned(bool aligned)
185 if (aligned == m_aligned)
188 m_dirty_geometry = true;
191 void QSGDefaultRectangleNode::update()
193 if (m_dirty_geometry) {
195 m_dirty_geometry = false;
201 unsigned char r, g, b, a;
204 Color4ub operator *(Color4ub c, float t) { c.a *= t; c.r *= t; c.g *= t; c.b *= t; return c; }
205 Color4ub operator +(Color4ub a, Color4ub b) { a.a += b.a; a.r += b.r; a.g += b.g; a.b += b.b; return a; }
207 static inline Color4ub colorToColor4ub(const QColor &c)
209 Color4ub color = { uchar(c.redF() * c.alphaF() * 255),
210 uchar(c.greenF() * c.alphaF() * 255),
211 uchar(c.blueF() * c.alphaF() * 255),
212 uchar(c.alphaF() * 255)
228 void QSGDefaultRectangleNode::updateGeometry()
230 qreal penWidth = m_aligned ? qreal(qRound(m_pen_width)) : m_pen_width;
232 // fast path for the simple case...
233 if ((penWidth == 0 || m_border_material.color().alpha() == 0)
235 && m_material_type == TypeFlat) {
236 QSGGeometry::updateRectGeometry(&m_default_geometry, m_rect);
240 QSGGeometry *fill = geometry();
242 // Check that the vertex type matches the material.
243 Q_ASSERT(m_material_type != TypeFlat || fill->sizeOfVertex() == sizeof(Vertex));
244 Q_ASSERT(m_material_type != TypeVertexGradient || fill->sizeOfVertex() == sizeof(ColorVertex));
246 QSGGeometry *borderGeometry = 0;
248 borderGeometry = m_border->geometry();
249 Q_ASSERT(borderGeometry->sizeOfVertex() == sizeof(Vertex));
252 int fillVertexCount = 0;
254 // Preallocate arrays for a rectangle with 18 segments per corner and 3 gradient stops.
255 uchar *fillVertices = 0;
256 Vertex *borderVertices = 0;
258 Color4ub fillColor = colorToColor4ub(m_fill_material.color());
259 const QGradientStops &stops = m_gradient_stops;
264 // Radius should never exceeds half of the width or half of the height
265 qreal radius = qMin(qMin(m_rect.width() * qreal(0.5), m_rect.height() * qreal(0.5)), m_radius);
266 QRectF innerRect = m_rect;
267 innerRect.adjust(radius, radius, -radius, -radius);
268 if (m_aligned && (int(penWidth) & 1)) {
269 // Pen width is odd, so add the offset as documented.
270 innerRect.moveLeft(innerRect.left() + qreal(0.5));
271 innerRect.moveTop(innerRect.top() + qreal(0.5));
274 qreal innerRadius = radius - penWidth * qreal(0.5);
275 qreal outerRadius = radius + penWidth * qreal(0.5);
277 // Number of segments per corner, approximately one per 3 pixels.
278 int segments = qBound(3, qCeil(outerRadius * (M_PI / 6)), 18);
287 --------+ \ <- gradient line
293 int nextGradientStop = 0;
294 qreal gradientPos = (radius - innerRadius) / (innerRect.height() + 2 * radius);
295 while (nextGradientStop < stops.size() && stops.at(nextGradientStop).first <= gradientPos)
297 int lastGradientStop = stops.size() - 1;
298 qreal lastGradientPos = (innerRect.height() + radius + innerRadius) / (innerRect.height() + 2 * radius);
299 while (lastGradientStop >= nextGradientStop && stops.at(lastGradientStop).first >= lastGradientPos)
302 int borderVertexHead = 0;
303 int borderVertexTail = 0;
305 // The reason I add extra vertices where the gradient lines intersect the border is
306 // to avoid pixel sized gaps between the fill and the border caused by floating point
308 borderGeometry->allocate((segments + 1) * 2 * 4 + (lastGradientStop - nextGradientStop + 1) * 4 + 2);
309 borderVertexHead = borderVertexTail = (borderGeometry->vertexCount() >> 1) - 1;
310 borderVertices = (Vertex *)borderGeometry->vertexData();
313 fill->allocate((segments + 1) * 4 + (lastGradientStop - nextGradientStop + 1) * 2);
314 fillVertices = (uchar *)fill->vertexData();
316 qreal py = 0; // previous inner y-coordinate.
317 qreal plx = 0; // previous inner left x-coordinate.
318 qreal prx = 0; // previous inner right x-coordinate.
320 qreal angle = qreal(0.5) * M_PI / qreal(segments);
321 qreal cosStep = qFastCos(angle);
322 qreal sinStep = qFastSin(angle);
324 for (int part = 0; part < 2; ++part) {
327 for (int i = 0; i <= segments; ++i) {
329 if (innerRadius > 0) {
330 y = (part ? innerRect.bottom() : innerRect.top()) - innerRadius * c; // current inner y-coordinate.
331 lx = innerRect.left() - innerRadius * s; // current inner left x-coordinate.
332 rx = innerRect.right() + innerRadius * s; // current inner right x-coordinate.
333 gradientPos = ((part ? innerRect.height() : 0) + radius - innerRadius * c) / (innerRect.height() + 2 * radius);
335 y = (part ? innerRect.bottom() + innerRadius : innerRect.top() - innerRadius); // current inner y-coordinate.
336 lx = innerRect.left() - innerRadius; // current inner left x-coordinate.
337 rx = innerRect.right() + innerRadius; // current inner right x-coordinate.
338 gradientPos = ((part ? innerRect.height() + innerRadius : -innerRadius) + radius) / (innerRect.height() + 2 * radius);
340 qreal Y = (part ? innerRect.bottom() : innerRect.top()) - outerRadius * c; // current outer y-coordinate.
341 qreal lX = innerRect.left() - outerRadius * s; // current outer left x-coordinate.
342 qreal rX = innerRect.right() + outerRadius * s; // current outer right x-coordinate.
344 while (nextGradientStop <= lastGradientStop && stops.at(nextGradientStop).first <= gradientPos) {
345 // Insert vertices at gradient stops.
346 qreal gy = (innerRect.top() - radius) + stops.at(nextGradientStop).first * (innerRect.height() + 2 * radius);
347 Q_ASSERT(fillVertexCount >= 2);
348 qreal t = (gy - py) / (y - py);
349 qreal glx = plx * (1 - t) + t * lx;
350 qreal grx = prx * (1 - t) + t * rx;
353 const Vertex &first = borderVertices[borderVertexHead];
354 borderVertices[--borderVertexHead].position = QVector2D(glx, gy);
355 borderVertices[--borderVertexHead] = first;
357 const Vertex &last = borderVertices[borderVertexTail - 2];
358 borderVertices[borderVertexTail++] = last;
359 borderVertices[borderVertexTail++].position = QVector2D(grx, gy);
362 ColorVertex *vertices = (ColorVertex *)fillVertices;
364 fillColor = colorToColor4ub(stops.at(nextGradientStop).second);
365 vertices[fillVertexCount].position = QVector2D(grx, gy);
366 vertices[fillVertexCount].color = fillColor;
368 vertices[fillVertexCount].position = QVector2D(glx, gy);
369 vertices[fillVertexCount].color = fillColor;
376 borderVertices[--borderVertexHead].position = QVector2D(lx, y);
377 borderVertices[--borderVertexHead].position = QVector2D(lX, Y);
378 borderVertices[borderVertexTail++].position = QVector2D(rX, Y);
379 borderVertices[borderVertexTail++].position = QVector2D(rx, y);
382 if (stops.isEmpty()) {
383 Q_ASSERT(m_material_type == TypeFlat);
384 Vertex *vertices = (Vertex *)fillVertices;
385 vertices[fillVertexCount++].position = QVector2D(rx, y);
386 vertices[fillVertexCount++].position = QVector2D(lx, y);
388 if (nextGradientStop == 0) {
389 fillColor = colorToColor4ub(stops.at(0).second);
390 } else if (nextGradientStop == stops.size()) {
391 fillColor = colorToColor4ub(stops.last().second);
393 const QGradientStop &prev = stops.at(nextGradientStop - 1);
394 const QGradientStop &next = stops.at(nextGradientStop);
395 qreal t = (gradientPos - prev.first) / (next.first - prev.first);
396 fillColor = (colorToColor4ub(prev.second) * (1 - t) + colorToColor4ub(next.second) * t);
399 ColorVertex *vertices = (ColorVertex *)fillVertices;
400 vertices[fillVertexCount].position = QVector2D(rx, y);
401 vertices[fillVertexCount].color = fillColor;
403 vertices[fillVertexCount].position = QVector2D(lx, y);
404 vertices[fillVertexCount].color = fillColor;
413 c = c * cosStep - s * sinStep;
414 s = s * cosStep + tmp * sinStep;
420 const Vertex &first = borderVertices[borderVertexHead];
421 const Vertex &second = borderVertices[borderVertexHead + 1];
422 borderVertices[borderVertexTail++] = first;
423 borderVertices[borderVertexTail++] = second;
425 Q_ASSERT(borderVertexHead == 0 && borderVertexTail == borderGeometry->vertexCount());
427 Q_ASSERT(fillVertexCount == fill->vertexCount());
432 QRectF innerRect = m_rect;
433 QRectF outerRect = m_rect;
435 qreal halfPenWidth = 0;
437 if (m_aligned && (int(penWidth) & 1)) {
438 // Pen width is odd, so add the offset as documented.
439 innerRect.moveLeft(innerRect.left() + qreal(0.5));
440 innerRect.moveTop(innerRect.top() + qreal(0.5));
441 outerRect = innerRect;
443 halfPenWidth = penWidth * qreal(0.5);
444 innerRect.adjust(halfPenWidth, halfPenWidth, -halfPenWidth, -halfPenWidth);
445 outerRect.adjust(-halfPenWidth, -halfPenWidth, halfPenWidth, halfPenWidth);
448 int nextGradientStop = 0;
449 qreal gradientPos = halfPenWidth / m_rect.height();
450 while (nextGradientStop < stops.size() && stops.at(nextGradientStop).first <= gradientPos)
452 int lastGradientStop = stops.size() - 1;
453 qreal lastGradientPos = (m_rect.height() - halfPenWidth) / m_rect.height();
454 while (lastGradientStop >= nextGradientStop && stops.at(lastGradientStop).first >= lastGradientPos)
457 int borderVertexCount = 0;
459 borderGeometry->allocate((1 + lastGradientStop - nextGradientStop) * 4 + 10);
460 borderVertices = (Vertex *)borderGeometry->vertexData();
462 fill->allocate((3 + lastGradientStop - nextGradientStop) * 2);
463 fillVertices = (uchar *)fill->vertexData();
465 QVarLengthArray<qreal, 16> ys(3 + lastGradientStop - nextGradientStop);
468 for (int part = 0; part < 2; ++part) {
469 qreal y = (part ? innerRect.bottom() : innerRect.top());
470 gradientPos = (y - innerRect.top() + halfPenWidth) / m_rect.height();
472 while (nextGradientStop <= lastGradientStop && stops.at(nextGradientStop).first <= gradientPos) {
473 // Insert vertices at gradient stops.
474 qreal gy = (innerRect.top() - halfPenWidth) + stops.at(nextGradientStop).first * m_rect.height();
475 Q_ASSERT(fillVertexCount >= 2);
477 ColorVertex *vertices = (ColorVertex *)fillVertices;
479 fillColor = colorToColor4ub(stops.at(nextGradientStop).second);
480 vertices[fillVertexCount].position = QVector2D(innerRect.right(), gy);
481 vertices[fillVertexCount].color = fillColor;
483 vertices[fillVertexCount].position = QVector2D(innerRect.left(), gy);
484 vertices[fillVertexCount].color = fillColor;
492 if (stops.isEmpty()) {
493 Q_ASSERT(m_material_type == TypeFlat);
494 Vertex *vertices = (Vertex *)fillVertices;
495 vertices[fillVertexCount++].position = QVector2D(innerRect.right(), y);
496 vertices[fillVertexCount++].position = QVector2D(innerRect.left(), y);
498 if (nextGradientStop == 0) {
499 fillColor = colorToColor4ub(stops.at(0).second);
500 } else if (nextGradientStop == stops.size()) {
501 fillColor = colorToColor4ub(stops.last().second);
503 const QGradientStop &prev = stops.at(nextGradientStop - 1);
504 const QGradientStop &next = stops.at(nextGradientStop);
505 qreal t = (gradientPos - prev.first) / (next.first - prev.first);
506 fillColor = (colorToColor4ub(prev.second) * (1 - t) + colorToColor4ub(next.second) * t);
509 ColorVertex *vertices = (ColorVertex *)fillVertices;
510 vertices[fillVertexCount].position = QVector2D(innerRect.right(), y);
511 vertices[fillVertexCount].color = fillColor;
513 vertices[fillVertexCount].position = QVector2D(innerRect.left(), y);
514 vertices[fillVertexCount].color = fillColor;
522 borderVertices[borderVertexCount++].position = QVector2D(outerRect.right(), outerRect.top());
523 borderVertices[borderVertexCount++].position = QVector2D(innerRect.right(), ys[0]);
524 for (int i = 1; i < fillVertexCount / 2; ++i) {
525 borderVertices[borderVertexCount++].position = QVector2D(outerRect.right(), outerRect.bottom());
526 borderVertices[borderVertexCount++].position = QVector2D(innerRect.right(), ys[i]);
529 borderVertices[borderVertexCount++].position = QVector2D(outerRect.left(), outerRect.bottom());
530 borderVertices[borderVertexCount++].position = QVector2D(innerRect.left(), ys[fillVertexCount / 2 - 1]);
531 for (int i = fillVertexCount / 2 - 2; i >= 0; --i) {
532 borderVertices[borderVertexCount++].position = QVector2D(outerRect.left(), outerRect.top());
533 borderVertices[borderVertexCount++].position = QVector2D(innerRect.left(), ys[i]);
536 borderVertices[borderVertexCount++].position = QVector2D(outerRect.right(), outerRect.top());
537 borderVertices[borderVertexCount++].position = QVector2D(innerRect.right(), innerRect.top());
539 Q_ASSERT(borderVertexCount == borderGeometry->vertexCount());
541 Q_ASSERT(fillVertexCount == fill->vertexCount());
544 markDirty(DirtyGeometry);