1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQuick module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qsgsoftwareinternalrectanglenode_p.h"
41#include <qmath.h>
42
43#include <QtGui/QPainter>
44
45QT_BEGIN_NAMESPACE
46
47QSGSoftwareInternalRectangleNode::QSGSoftwareInternalRectangleNode()
48 : m_penWidth(0)
49 , m_radius(0)
50 , m_vertical(true)
51 , m_cornerPixmapIsDirty(true)
52 , m_devicePixelRatio(1)
53{
54 m_pen.setJoinStyle(Qt::MiterJoin);
55 m_pen.setMiterLimit(0);
56 setMaterial((QSGMaterial*)1);
57 setGeometry((QSGGeometry*)1);
58}
59
60void QSGSoftwareInternalRectangleNode::setRect(const QRectF &rect)
61{
62 QRect alignedRect = rect.toAlignedRect();
63 if (m_rect != alignedRect) {
64 m_rect = alignedRect;
65 markDirty(bits: DirtyMaterial);
66 }
67}
68
69void QSGSoftwareInternalRectangleNode::setColor(const QColor &color)
70{
71 if (m_color != color) {
72 m_color = color;
73 m_cornerPixmapIsDirty = true;
74 markDirty(bits: DirtyMaterial);
75 }
76}
77
78void QSGSoftwareInternalRectangleNode::setPenColor(const QColor &color)
79{
80 if (m_penColor != color) {
81 m_penColor = color;
82 m_cornerPixmapIsDirty = true;
83 markDirty(bits: DirtyMaterial);
84 }
85}
86
87void QSGSoftwareInternalRectangleNode::setPenWidth(qreal width)
88{
89 if (m_penWidth != width) {
90 m_penWidth = width;
91 m_cornerPixmapIsDirty = true;
92 markDirty(bits: DirtyMaterial);
93 }
94}
95
96//Move first stop by pos relative to seconds
97static QGradientStop interpolateStop(const QGradientStop &firstStop, const QGradientStop &secondStop, double newPos)
98{
99 double distance = secondStop.first - firstStop.first;
100 double distanceDelta = newPos - firstStop.first;
101 double modifierValue = distanceDelta / distance;
102 int redDelta = (secondStop.second.red() - firstStop.second.red()) * modifierValue;
103 int greenDelta = (secondStop.second.green() - firstStop.second.green()) * modifierValue;
104 int blueDelta = (secondStop.second.blue() - firstStop.second.blue()) * modifierValue;
105 int alphaDelta = (secondStop.second.alpha() - firstStop.second.alpha()) * modifierValue;
106
107 QGradientStop newStop;
108 newStop.first = newPos;
109 newStop.second = QColor(firstStop.second.red() + redDelta,
110 firstStop.second.green() + greenDelta,
111 firstStop.second.blue() + blueDelta,
112 firstStop.second.alpha() + alphaDelta);
113
114 return newStop;
115}
116
117void QSGSoftwareInternalRectangleNode::setGradientStops(const QGradientStops &stops)
118{
119 //normalize stops
120 bool needsNormalization = false;
121 for (const QGradientStop &stop : qAsConst(t: stops)) {
122 if (stop.first < 0.0 || stop.first > 1.0) {
123 needsNormalization = true;
124 break;
125 }
126 }
127
128 if (needsNormalization) {
129 QGradientStops normalizedStops;
130 if (stops.count() == 1) {
131 //If there is only one stop, then the position does not matter
132 //It is just treated as a color
133 QGradientStop stop = stops.at(i: 0);
134 stop.first = 0.0;
135 normalizedStops.append(t: stop);
136 } else {
137 //Clip stops to only the first below 0.0 and above 1.0
138 int below = -1;
139 int above = -1;
140 QVector<int> between;
141 for (int i = 0; i < stops.count(); ++i) {
142 if (stops.at(i).first < 0.0) {
143 below = i;
144 } else if (stops.at(i).first > 1.0) {
145 above = i;
146 break;
147 } else {
148 between.append(t: i);
149 }
150 }
151
152 //Interpoloate new color values for above and below
153 if (below != -1 ) {
154 //If there are more than one stops left, interpolate
155 if (below + 1 < stops.count()) {
156 normalizedStops.append(t: interpolateStop(firstStop: stops.at(i: below), secondStop: stops.at(i: below + 1), newPos: 0.0));
157 } else {
158 QGradientStop singleStop;
159 singleStop.first = 0.0;
160 singleStop.second = stops.at(i: below).second;
161 normalizedStops.append(t: singleStop);
162 }
163 }
164
165 for (int i = 0; i < between.count(); ++i)
166 normalizedStops.append(t: stops.at(i: between.at(i)));
167
168 if (above != -1) {
169 //If there stops before above, interpolate
170 if (above >= 1) {
171 normalizedStops.append(t: interpolateStop(firstStop: stops.at(i: above), secondStop: stops.at(i: above - 1), newPos: 1.0));
172 } else {
173 QGradientStop singleStop;
174 singleStop.first = 1.0;
175 singleStop.second = stops.at(i: above).second;
176 normalizedStops.append(t: singleStop);
177 }
178 }
179 }
180
181 m_stops = normalizedStops;
182
183 } else {
184 m_stops = stops;
185 }
186 m_cornerPixmapIsDirty = true;
187 markDirty(bits: DirtyMaterial);
188}
189
190void QSGSoftwareInternalRectangleNode::setGradientVertical(bool vertical)
191{
192 if (m_vertical != vertical) {
193 m_vertical = vertical;
194 m_cornerPixmapIsDirty = true;
195 markDirty(bits: DirtyMaterial);
196 }
197}
198
199void QSGSoftwareInternalRectangleNode::setRadius(qreal radius)
200{
201 if (m_radius != radius) {
202 m_radius = radius;
203 m_cornerPixmapIsDirty = true;
204 markDirty(bits: DirtyMaterial);
205 }
206}
207
208void QSGSoftwareInternalRectangleNode::setAligned(bool /*aligned*/)
209{
210}
211
212void QSGSoftwareInternalRectangleNode::update()
213{
214 if (!m_penWidth || m_penColor == Qt::transparent) {
215 m_pen = Qt::NoPen;
216 } else {
217 m_pen = QPen(m_penColor);
218 m_pen.setWidthF(m_penWidth);
219 }
220
221 if (!m_stops.isEmpty()) {
222 QLinearGradient gradient(QPoint(0,0), QPoint(m_vertical ? 0 : 1, m_vertical ? 1 : 0));
223 gradient.setStops(m_stops);
224 gradient.setCoordinateMode(QGradient::ObjectBoundingMode);
225 m_brush = QBrush(gradient);
226 } else {
227 m_brush = QBrush(m_color);
228 }
229
230 if (m_cornerPixmapIsDirty) {
231 generateCornerPixmap();
232 m_cornerPixmapIsDirty = false;
233 }
234}
235
236void QSGSoftwareInternalRectangleNode::paint(QPainter *painter)
237{
238 //We can only check for a device pixel ratio change when we know what
239 //paint device is being used.
240 if (!qFuzzyCompare(p1: painter->device()->devicePixelRatioF(), p2: m_devicePixelRatio)) {
241 m_devicePixelRatio = painter->device()->devicePixelRatioF();
242 generateCornerPixmap();
243 }
244
245 if (painter->transform().isRotating()) {
246 //Rotated rectangles lose the benefits of direct rendering, and have poor rendering
247 //quality when using only blits and fills.
248
249 if (m_radius == 0 && m_penWidth == 0) {
250 //Non-Rounded Rects without borders (fall back to drawRect)
251 //Most common case
252 painter->setPen(Qt::NoPen);
253 painter->setBrush(m_brush);
254 painter->drawRect(r: m_rect);
255 } else {
256 //Rounded Rects and Rects with Borders
257 //Avoids broken behaviors of QPainter::drawRect/roundedRect
258 QPixmap pixmap = QPixmap(qRound(d: m_rect.width() * m_devicePixelRatio), qRound(d: m_rect.height() * m_devicePixelRatio));
259 pixmap.fill(fillColor: Qt::transparent);
260 pixmap.setDevicePixelRatio(m_devicePixelRatio);
261 QPainter pixmapPainter(&pixmap);
262 paintRectangle(painter: &pixmapPainter, rect: QRect(0, 0, m_rect.width(), m_rect.height()));
263
264 QPainter::RenderHints previousRenderHints = painter->renderHints();
265 painter->setRenderHint(hint: QPainter::SmoothPixmapTransform, on: true);
266 painter->drawPixmap(r: m_rect, pm: pixmap);
267 painter->setRenderHints(hints: previousRenderHints);
268 }
269
270
271 } else {
272 //Paint directly
273 paintRectangle(painter, rect: m_rect);
274 }
275
276}
277
278bool QSGSoftwareInternalRectangleNode::isOpaque() const
279{
280 if (m_radius > 0.0f)
281 return false;
282 if (m_color.alpha() < 255)
283 return false;
284 if (m_penWidth > 0.0f && m_penColor.alpha() < 255)
285 return false;
286 if (m_stops.count() > 0) {
287 for (const QGradientStop &stop : qAsConst(t: m_stops)) {
288 if (stop.second.alpha() < 255)
289 return false;
290 }
291 }
292
293 return true;
294}
295
296QRectF QSGSoftwareInternalRectangleNode::rect() const
297{
298 //TODO: double check that this is correct.
299 return m_rect;
300}
301
302void QSGSoftwareInternalRectangleNode::paintRectangle(QPainter *painter, const QRect &rect)
303{
304 //Radius should never exceeds half of the width or half of the height
305 int radius = qFloor(v: qMin(a: qMin(a: rect.width(), b: rect.height()) * 0.5, b: m_radius));
306
307 QPainter::RenderHints previousRenderHints = painter->renderHints();
308 painter->setRenderHint(hint: QPainter::Antialiasing, on: false);
309
310 if (m_penWidth > 0) {
311 //Fill border Rects
312
313 //Borders can not be more than half the height/width of a rect
314 double borderWidth = qMin(a: m_penWidth, b: rect.width() * 0.5);
315 double borderHeight = qMin(a: m_penWidth, b: rect.height() * 0.5);
316
317
318
319 if (borderWidth > radius) {
320 //4 Rects
321 QRectF borderTopOutside(QPointF(rect.x() + radius, rect.y()),
322 QPointF(rect.x() + rect.width() - radius, rect.y() + radius));
323 QRectF borderTopInside(QPointF(rect.x() + borderWidth, rect.y() + radius),
324 QPointF(rect.x() + rect.width() - borderWidth, rect.y() + borderHeight));
325 QRectF borderBottomOutside(QPointF(rect.x() + radius, rect.y() + rect.height() - radius),
326 QPointF(rect.x() + rect.width() - radius, rect.y() + rect.height()));
327 QRectF borderBottomInside(QPointF(rect.x() + borderWidth, rect.y() + rect.height() - borderHeight),
328 QPointF(rect.x() + rect.width() - borderWidth, rect.y() + rect.height() - radius));
329
330 if (borderTopOutside.isValid())
331 painter->fillRect(borderTopOutside, color: m_penColor);
332 if (borderTopInside.isValid())
333 painter->fillRect(borderTopInside, color: m_penColor);
334 if (borderBottomOutside.isValid())
335 painter->fillRect(borderBottomOutside, color: m_penColor);
336 if (borderBottomInside.isValid())
337 painter->fillRect(borderBottomInside, color: m_penColor);
338
339 } else {
340 //2 Rects
341 QRectF borderTop(QPointF(rect.x() + radius, rect.y()),
342 QPointF(rect.x() + rect.width() - radius, rect.y() + borderHeight));
343 QRectF borderBottom(QPointF(rect.x() + radius, rect.y() + rect.height() - borderHeight),
344 QPointF(rect.x() + rect.width() - radius, rect.y() + rect.height()));
345 if (borderTop.isValid())
346 painter->fillRect(borderTop, color: m_penColor);
347 if (borderBottom.isValid())
348 painter->fillRect(borderBottom, color: m_penColor);
349 }
350 QRectF borderLeft(QPointF(rect.x(), rect.y() + radius),
351 QPointF(rect.x() + borderWidth, rect.y() + rect.height() - radius));
352 QRectF borderRight(QPointF(rect.x() + rect.width() - borderWidth, rect.y() + radius),
353 QPointF(rect.x() + rect.width(), rect.y() + rect.height() - radius));
354 if (borderLeft.isValid())
355 painter->fillRect(borderLeft, color: m_penColor);
356 if (borderRight.isValid())
357 painter->fillRect(borderRight, color: m_penColor);
358 }
359
360
361 if (radius > 0) {
362
363 if (radius * 2 >= rect.width() && radius * 2 >= rect.height()) {
364 //Blit whole pixmap for circles
365 painter->drawPixmap(targetRect: rect, pixmap: m_cornerPixmap, sourceRect: m_cornerPixmap.rect());
366 } else {
367
368 //blit 4 corners to border
369 int scaledRadius = qRound(d: radius * m_devicePixelRatio);
370 QRectF topLeftCorner(QPointF(rect.x(), rect.y()),
371 QPointF(rect.x() + radius, rect.y() + radius));
372 painter->drawPixmap(targetRect: topLeftCorner, pixmap: m_cornerPixmap, sourceRect: QRectF(0, 0, scaledRadius, scaledRadius));
373 QRectF topRightCorner(QPointF(rect.x() + rect.width() - radius, rect.y()),
374 QPointF(rect.x() + rect.width(), rect.y() + radius));
375 painter->drawPixmap(targetRect: topRightCorner, pixmap: m_cornerPixmap, sourceRect: QRectF(scaledRadius, 0, scaledRadius, scaledRadius));
376 QRectF bottomLeftCorner(QPointF(rect.x(), rect.y() + rect.height() - radius),
377 QPointF(rect.x() + radius, rect.y() + rect.height()));
378 painter->drawPixmap(targetRect: bottomLeftCorner, pixmap: m_cornerPixmap, sourceRect: QRectF(0, scaledRadius, scaledRadius, scaledRadius));
379 QRectF bottomRightCorner(QPointF(rect.x() + rect.width() - radius, rect.y() + rect.height() - radius),
380 QPointF(rect.x() + rect.width(), rect.y() + rect.height()));
381 painter->drawPixmap(targetRect: bottomRightCorner, pixmap: m_cornerPixmap, sourceRect: QRectF(scaledRadius, scaledRadius, scaledRadius, scaledRadius));
382
383 }
384
385 }
386
387 QRectF brushRect = QRectF(rect).marginsRemoved(margins: QMarginsF(m_penWidth, m_penWidth, m_penWidth, m_penWidth));
388 if (brushRect.width() < 0)
389 brushRect.setWidth(0);
390 if (brushRect.height() < 0)
391 brushRect.setHeight(0);
392 double innerRectRadius = qMax(a: 0.0, b: radius - m_penWidth);
393
394 //If not completely transparent or has a gradient
395 if (m_color.alpha() > 0 || !m_stops.empty()) {
396 if (innerRectRadius > 0) {
397 //Rounded Rect
398 if (m_stops.empty()) {
399 //Rounded Rects without gradient need 3 blits
400 QRectF centerRect(QPointF(brushRect.x() + innerRectRadius, brushRect.y()),
401 QPointF(brushRect.x() + brushRect.width() - innerRectRadius, brushRect.y() + brushRect.height()));
402 painter->fillRect(centerRect, color: m_color);
403 QRectF leftRect(QPointF(brushRect.x(), brushRect.y() + innerRectRadius),
404 QPointF(brushRect.x() + innerRectRadius, brushRect.y() + brushRect.height() - innerRectRadius));
405 painter->fillRect(leftRect, color: m_color);
406 QRectF rightRect(QPointF(brushRect.x() + brushRect.width() - innerRectRadius, brushRect.y() + innerRectRadius),
407 QPointF(brushRect.x() + brushRect.width(), brushRect.y() + brushRect.height() - innerRectRadius));
408 painter->fillRect(rightRect, color: m_color);
409 } else {
410 //Rounded Rect with gradient (slow)
411 painter->setPen(Qt::NoPen);
412 painter->setBrush(m_brush);
413 painter->drawRoundedRect(rect: brushRect, xRadius: innerRectRadius, yRadius: innerRectRadius);
414 }
415 } else {
416 //non-rounded rects only need 1 blit
417 painter->fillRect(brushRect, m_brush);
418 }
419 }
420
421 painter->setRenderHints(hints: previousRenderHints);
422}
423
424void QSGSoftwareInternalRectangleNode::generateCornerPixmap()
425{
426 //Generate new corner Pixmap
427 int radius = qFloor(v: qMin(a: qMin(a: m_rect.width(), b: m_rect.height()) * 0.5, b: m_radius));
428 const auto width = qRound(d: radius * 2 * m_devicePixelRatio);
429
430 if (m_cornerPixmap.width() != width)
431 m_cornerPixmap = QPixmap(width, width);
432
433 m_cornerPixmap.setDevicePixelRatio(m_devicePixelRatio);
434 m_cornerPixmap.fill(fillColor: Qt::transparent);
435
436 if (radius > 0) {
437 QPainter cornerPainter(&m_cornerPixmap);
438 cornerPainter.setRenderHint(hint: QPainter::Antialiasing);
439 cornerPainter.setCompositionMode(QPainter::CompositionMode_Source);
440
441 //Paint outer cicle
442 if (m_penWidth > 0) {
443 cornerPainter.setPen(Qt::NoPen);
444 cornerPainter.setBrush(m_penColor);
445 cornerPainter.drawRoundedRect(rect: QRectF(0, 0, radius * 2, radius *2), xRadius: radius, yRadius: radius);
446 }
447
448 //Paint inner circle
449 if (radius > m_penWidth) {
450 cornerPainter.setPen(Qt::NoPen);
451 if (m_stops.isEmpty())
452 cornerPainter.setBrush(m_brush);
453 else
454 cornerPainter.setBrush(Qt::transparent);
455
456 QMarginsF adjustmentMargins(m_penWidth, m_penWidth, m_penWidth, m_penWidth);
457 QRectF cornerCircleRect = QRectF(0, 0, radius * 2, radius * 2).marginsRemoved(margins: adjustmentMargins);
458 cornerPainter.drawRoundedRect(rect: cornerCircleRect, xRadius: radius, yRadius: radius);
459 }
460 cornerPainter.end();
461 }
462}
463
464QT_END_NAMESPACE
465

source code of qtdeclarative/src/quick/scenegraph/adaptations/software/qsgsoftwareinternalrectanglenode.cpp