2 /****************************************************************************
4 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
5 ** All rights reserved.
6 ** Contact: Nokia Corporation (qt-info@nokia.com)
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 <private/qsgvertexcolormaterial_p.h>
48 #include "qsgtexturematerial.h"
50 #include <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 switch (m_material_type) {
83 case TypeVertexGradient:
90 QSGGeometryNode *QSGDefaultRectangleNode::border()
93 m_border = new QSGGeometryNode;
94 m_border->setMaterial(&m_border_material);
95 QSGGeometry *geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 0);
96 m_border->setGeometry(geometry);
97 m_border->setFlag(QSGNode::OwnsGeometry);
102 void QSGDefaultRectangleNode::setRect(const QRectF &rect)
107 m_dirty_geometry = true;
110 void QSGDefaultRectangleNode::setColor(const QColor &color)
112 if (color == m_fill_material.color())
114 if (m_gradient_stops.isEmpty()) {
115 Q_ASSERT(m_material_type == TypeFlat);
116 m_fill_material.setColor(color);
117 setMaterial(&m_fill_material); // Indicate that the material state has changed.
121 void QSGDefaultRectangleNode::setPenColor(const QColor &color)
123 if (color == m_border_material.color())
125 m_border_material.setColor(color);
126 border()->setMaterial(&m_border_material); // Indicate that the material state has changed.
129 void QSGDefaultRectangleNode::setPenWidth(qreal width)
131 if (width == m_pen_width)
134 QSGNode *b = border();
135 if (m_pen_width <= 0 && b->parent())
137 else if (m_pen_width > 0 && !b->parent())
139 m_dirty_geometry = true;
143 void QSGDefaultRectangleNode::setGradientStops(const QGradientStops &stops)
145 if (stops.constData() == m_gradient_stops.constData())
148 m_gradient_stops = stops;
150 m_gradient_is_opaque = true;
151 for (int i = 0; i < stops.size(); ++i)
152 m_gradient_is_opaque &= stops.at(i).second.alpha() == 0xff;
154 if (stops.isEmpty()) {
155 // No gradient specified, use flat color.
156 if (m_material_type != TypeFlat) {
159 delete opaqueMaterial();
160 setOpaqueMaterial(0);
162 setMaterial(&m_fill_material);
163 m_material_type = TypeFlat;
165 setGeometry(&m_default_geometry);
166 setFlag(OwnsGeometry, false);
169 if (m_material_type == TypeFlat) {
170 QSGVertexColorMaterial *material = new QSGVertexColorMaterial;
171 setMaterial(material);
172 m_material_type = TypeVertexGradient;
173 QSGGeometry *g = new QSGGeometry(QSGGeometry::defaultAttributes_ColoredPoint2D(), 0);
175 setFlag(OwnsGeometry);
177 static_cast<QSGVertexColorMaterial *>(material())->setColorsAreOpaque(m_gradient_is_opaque);
180 m_dirty_geometry = true;
183 void QSGDefaultRectangleNode::setRadius(qreal radius)
185 if (radius == m_radius)
188 m_dirty_geometry = true;
191 void QSGDefaultRectangleNode::setAligned(bool aligned)
193 if (aligned == m_aligned)
196 m_dirty_geometry = true;
199 void QSGDefaultRectangleNode::update()
201 if (m_dirty_geometry) {
203 m_dirty_geometry = false;
209 unsigned char r, g, b, a;
212 Color4ub operator *(Color4ub c, float t) { c.a *= t; c.r *= t; c.g *= t; c.b *= t; return c; }
213 Color4ub operator +(Color4ub a, Color4ub b) { a.a += b.a; a.r += b.r; a.g += b.g; a.b += b.b; return a; }
215 static inline Color4ub colorToColor4ub(const QColor &c)
217 Color4ub color = { uchar(c.redF() * c.alphaF() * 255),
218 uchar(c.greenF() * c.alphaF() * 255),
219 uchar(c.blueF() * c.alphaF() * 255),
220 uchar(c.alphaF() * 255)
236 void QSGDefaultRectangleNode::updateGeometry()
238 qreal penWidth = m_aligned ? qreal(qRound(m_pen_width)) : m_pen_width;
240 // fast path for the simple case...
241 if ((penWidth == 0 || m_border_material.color().alpha() == 0)
243 && m_material_type == TypeFlat) {
244 QSGGeometry::updateRectGeometry(&m_default_geometry, m_rect);
248 QSGGeometry *fill = geometry();
250 // Check that the vertex type matches the material.
251 Q_ASSERT(m_material_type != TypeFlat || fill->sizeOfVertex() == sizeof(Vertex));
252 Q_ASSERT(m_material_type != TypeVertexGradient || fill->sizeOfVertex() == sizeof(ColorVertex));
254 QSGGeometry *borderGeometry = 0;
256 borderGeometry = border()->geometry();
257 Q_ASSERT(borderGeometry->sizeOfVertex() == sizeof(Vertex));
260 int fillVertexCount = 0;
262 // Preallocate arrays for a rectangle with 18 segments per corner and 3 gradient stops.
263 uchar *fillVertices = 0;
264 Vertex *borderVertices = 0;
266 Color4ub fillColor = colorToColor4ub(m_fill_material.color());
267 const QGradientStops &stops = m_gradient_stops;
272 // Radius should never exceeds half of the width or half of the height
273 qreal radius = qMin(qMin(m_rect.width() * qreal(0.5), m_rect.height() * qreal(0.5)), m_radius);
274 QRectF innerRect = m_rect;
275 innerRect.adjust(radius, radius, -radius, -radius);
276 if (m_aligned && (int(penWidth) & 1)) {
277 // Pen width is odd, so add the offset as documented.
278 innerRect.moveLeft(innerRect.left() + qreal(0.5));
279 innerRect.moveTop(innerRect.top() + qreal(0.5));
282 qreal innerRadius = radius - penWidth * qreal(0.5);
283 qreal outerRadius = radius + penWidth * qreal(0.5);
285 // Number of segments per corner, approximately one per 3 pixels.
286 int segments = qBound(3, qCeil(outerRadius * (M_PI / 6)), 18);
295 --------+ \ <- gradient line
301 int nextGradientStop = 0;
302 qreal gradientPos = (radius - innerRadius) / (innerRect.height() + 2 * radius);
303 while (nextGradientStop < stops.size() && stops.at(nextGradientStop).first <= gradientPos)
305 int lastGradientStop = stops.size() - 1;
306 qreal lastGradientPos = (innerRect.height() + radius + innerRadius) / (innerRect.height() + 2 * radius);
307 while (lastGradientStop >= nextGradientStop && stops.at(lastGradientStop).first >= lastGradientPos)
310 int borderVertexHead = 0;
311 int borderVertexTail = 0;
313 // The reason I add extra vertices where the gradient lines intersect the border is
314 // to avoid pixel sized gaps between the fill and the border caused by floating point
316 borderGeometry->allocate((segments + 1) * 2 * 4 + (lastGradientStop - nextGradientStop + 1) * 4 + 2);
317 borderVertexHead = borderVertexTail = (borderGeometry->vertexCount() >> 1) - 1;
318 borderVertices = (Vertex *)borderGeometry->vertexData();
321 fill->allocate((segments + 1) * 4 + (lastGradientStop - nextGradientStop + 1) * 2);
322 fillVertices = (uchar *)fill->vertexData();
324 qreal py = 0; // previous inner y-coordinate.
325 qreal plx = 0; // previous inner left x-coordinate.
326 qreal prx = 0; // previous inner right x-coordinate.
328 qreal angle = qreal(0.5) * M_PI / qreal(segments);
329 qreal cosStep = qFastCos(angle);
330 qreal sinStep = qFastSin(angle);
332 for (int part = 0; part < 2; ++part) {
335 for (int i = 0; i <= segments; ++i) {
336 qreal y = (part ? innerRect.bottom() : innerRect.top()) - innerRadius * c; // current inner y-coordinate.
337 qreal lx = innerRect.left() - innerRadius * s; // current inner left x-coordinate.
338 qreal rx = innerRect.right() + innerRadius * s; // current inner right x-coordinate.
339 qreal Y = (part ? innerRect.bottom() : innerRect.top()) - outerRadius * c; // current outer y-coordinate.
340 qreal lX = innerRect.left() - outerRadius * s; // current outer left x-coordinate.
341 qreal rX = innerRect.right() + outerRadius * s; // current outer right x-coordinate.
342 gradientPos = ((part ? innerRect.height() : 0) + radius - innerRadius * c) / (innerRect.height() + 2 * radius);
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);