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

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