Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Toggle main menu visibility
Loading...
Searching...
No Matches
qcolrpaintgraphrenderer.cpp
Go to the documentation of this file.
1
// Copyright (C) 2025 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
"qcolrpaintgraphrenderer_p.h"
5
6
7
QT_BEGIN_NAMESPACE
8
9
QColrPaintGraphRenderer::~QColrPaintGraphRenderer()
10
{
11
Q_ASSERT(m_oldPaths.isEmpty());
12
Q_ASSERT(m_oldTransforms.isEmpty());
13
14
delete
m_painter;
15
}
16
17
void
QColrPaintGraphRenderer::save()
18
{
19
qCDebug(lcColrv1).noquote().nospace()
20
<< QByteArray().fill(
' '
, m_oldPaths.size() * 2)
21
<<
"[enter scope]"
;
22
23
m_oldPaths.append(m_currentPath);
24
m_oldTransforms.append(m_currentTransform);
25
26
if
(m_painter !=
nullptr
)
27
m_painter->save();
28
}
29
30
void
QColrPaintGraphRenderer::restore()
31
{
32
m_currentPath = m_oldPaths.takeLast();
33
m_currentTransform = m_oldTransforms.takeLast();
34
35
qCDebug(lcColrv1).noquote().nospace()
36
<< QByteArray().fill(
' '
, m_oldPaths.size() * 2)
37
<<
"[exit scope]"
;
38
39
if
(m_painter !=
nullptr
)
40
m_painter->restore();
41
}
42
43
void
QColrPaintGraphRenderer::setClip(QRect rect)
44
{
45
qCDebug(lcColrv1).noquote().nospace()
46
<< QByteArray().fill(
' '
, m_oldPaths.size() * 2)
47
<<
"[set clip: "
<< rect
48
<<
"]"
;
49
if
(!isRendering())
50
m_boundingRect = m_boundingRect.united(rect);
51
if
(m_painter !=
nullptr
)
52
m_painter->setClipRect(rect);
53
}
54
55
void
QColrPaintGraphRenderer::prependTransform(
const
QTransform &transform)
56
{
57
qCDebug(lcColrv1).noquote().nospace()
58
<< QByteArray().fill(
' '
, m_oldPaths.size() * 2)
59
<<
"[prepend transform: "
<< transform
60
<<
"]"
;
61
m_currentTransform = transform * m_currentTransform;
62
}
63
64
void
QColrPaintGraphRenderer::appendPath(
const
QPainterPath &path)
65
{
66
qCDebug(lcColrv1).noquote().nospace()
67
<< QByteArray().fill(
' '
, m_oldPaths.size() * 2)
68
<<
"[append path: "
<< path.controlPointRect()
69
<<
"]"
;
70
71
QPainterPath transformedPath(m_currentTransform.map(path));
72
m_currentPath = m_currentPath.united(transformedPath);
73
if
(!isRendering())
74
m_boundingRect = m_boundingRect.united(transformedPath.boundingRect());
75
}
76
77
void
QColrPaintGraphRenderer::setPath(
const
QPainterPath &path)
78
{
79
qCDebug(lcColrv1).noquote().nospace()
80
<< QByteArray().fill(
' '
, m_oldPaths.size() * 2)
81
<<
"[set path]"
;
82
83
m_currentPath.clear();
84
appendPath(path);
85
}
86
87
void
QColrPaintGraphRenderer::setSolidColor(QColor color)
88
{
89
qCDebug(lcColrv1).noquote().nospace()
90
<< QByteArray().fill(
' '
, m_oldPaths.size() * 2)
91
<<
"[solid "
<< color
92
<<
"]"
;
93
94
if
(m_painter !=
nullptr
)
95
m_painter->setBrush(color);
96
}
97
98
void
QColrPaintGraphRenderer::setLinearGradient(QPointF p0, QPointF p1, QPointF p2,
99
QGradient::Spread spread,
100
const
QGradientStops &gradientStops)
101
{
102
if
(m_painter !=
nullptr
) {
103
qCDebug(lcColrv1).noquote().nospace()
104
<< QByteArray().fill(
' '
, m_oldPaths.size() * 2)
105
<<
"[linear gradient "
<< p0 <<
", "
<< p1 <<
", "
<< p2
106
<<
", spread: "
<< spread
107
<<
", stop count: "
<< gradientStops.size()
108
<<
"]"
;
109
110
// Calculate new start and end point for single vector gradient preferred by Qt
111
// Find vector perpendicular to p0p2 and project p0p1 onto this to find p3 (final
112
// stop)
113
// https://learn.microsoft.com/en-us/typography/opentype/spec/colr#linear-gradients
114
QVector2D p0p2(p2 - p0);
115
if
(qFuzzyIsNull(p0p2.lengthSquared())) {
116
qCWarning(lcColrv1) <<
"Malformed linear gradient in COLRv1 graph. Points:"
117
<< p0
118
<< p1
119
<< p2;
120
return
;
121
}
122
123
// v is perpendicular to p0p2
124
QVector2D v(p0p2.y(), -p0p2.x());
125
126
// u is the vector from p0 to p1
127
QVector2D u(p1 - p0);
128
if
(qFuzzyIsNull(u.lengthSquared())) {
129
qCWarning(lcColrv1) <<
"Malformed linear gradient in COLRv1 graph. Points:"
130
<< p0
131
<< p1
132
<< p2;
133
return
;
134
}
135
136
// We find the projected point p3
137
QPointF p3((QVector2D(p0) + v * QVector2D::dotProduct(u, v) / v.lengthSquared()).toPointF());
138
139
p0 = m_currentTransform.map(p0);
140
p3 = m_currentTransform.map(p1);
141
142
QLinearGradient linearGradient(p0, p3);
143
linearGradient.setSpread(spread);
144
linearGradient.setStops(gradientStops);
145
linearGradient.setCoordinateMode(QGradient::LogicalMode);
146
147
m_painter->setBrush(linearGradient);
148
}
149
}
150
151
void
QColrPaintGraphRenderer::setConicalGradient(QPointF center,
152
qreal startAngle, qreal endAngle,
153
QGradient::Spread spread,
154
const
QGradientStops &gradientStops)
155
{
156
if
(m_painter !=
nullptr
) {
157
qCDebug(lcColrv1).noquote().nospace()
158
<< QByteArray().fill(
' '
, m_oldPaths.size() * 2)
159
<<
"[conical gradient "
<< center
160
<<
", startAngle="
<< startAngle
161
<<
", endAngle="
<< endAngle
162
<<
", spread: "
<< spread
163
<<
", stop count: "
<< gradientStops.size()
164
<<
"]"
;
165
166
QConicalGradient conicalGradient(center, startAngle);
167
conicalGradient.setSpread(spread);
168
169
// Adapt stops to actual span since Qt always assumes end angle of 360
170
// Note: This does not give accurate results for the colors outside the angle span.
171
// To do this correctly, we would have to insert stops at 0°, 360° and if the spread
172
// is reflect/repeat, also throughout the uncovered area to get the correct
173
// rendering. It might however be easier to support this in QConicalGradient itself.
174
// For now, this is left only semi-supported, as sweep gradients are currently rare.
175
const
qreal multiplier = qFuzzyCompare(endAngle, startAngle)
176
? 1.0
177
: (endAngle - startAngle) / 360.0;
178
179
QGradientStops adaptedStops;
180
adaptedStops.reserve(gradientStops.size());
181
182
for
(
const
QGradientStop &gradientStop : gradientStops)
183
adaptedStops.append({gradientStop.first * multiplier, gradientStop.second});
184
185
conicalGradient.setStops(adaptedStops);
186
conicalGradient.setCoordinateMode(QGradient::LogicalMode);
187
188
m_painter->setBrush(conicalGradient);
189
}
190
}
191
192
void
QColrPaintGraphRenderer::setRadialGradient(QPointF c0, qreal r0,
193
QPointF c1, qreal r1,
194
QGradient::Spread spread,
195
const
QGradientStops &gradientStops)
196
{
197
if
(m_painter !=
nullptr
) {
198
qCDebug(lcColrv1).noquote().nospace()
199
<< QByteArray().fill(
' '
, m_oldPaths.size() * 2)
200
<<
"[radial gradient "
<< c0
201
<<
", rad="
<< r0
202
<<
", "
<< c1
203
<<
", rad="
<< r1
204
<<
", spread: "
<< spread
205
<<
", stop count: "
<< gradientStops.size()
206
<<
"]"
;
207
208
QPointF c0e(c0 + QPointF(r0, 0.0));
209
QPointF c1e(c1 + QPointF(r1, 0.0));
210
211
c0 = m_currentTransform.map(c0);
212
c0e = m_currentTransform.map(c0e);
213
c1 = m_currentTransform.map(c1);
214
c1e = m_currentTransform.map(c1e);
215
216
const
QVector2D d0(c0e - c0);
217
const
QVector2D d1(c1e - c1);
218
219
QRadialGradient gradient(c1, d1.length(), c0, d0.length());
220
gradient.setCoordinateMode(QGradient::LogicalMode);
221
gradient.setSpread(spread);
222
gradient.setStops(gradientStops);
223
m_painter->setBrush(gradient);
224
}
225
}
226
227
void
QColrPaintGraphRenderer::drawCurrentPath()
228
{
229
qCDebug(lcColrv1).noquote().nospace()
230
<< QByteArray().fill(
' '
, m_oldPaths.size() * 2)
231
<<
"[draw path "
<< m_currentPath.controlPointRect() << m_currentTransform
232
<<
"]"
;
233
234
if
(m_painter !=
nullptr
)
235
m_painter->drawPath(m_currentPath);
236
}
237
238
void
QColrPaintGraphRenderer::beginCalculateBoundingBox()
239
{
240
qCDebug(lcColrv1).noquote().nospace()
241
<< QByteArray().fill(
' '
, m_oldPaths.size() * 2)
242
<<
"[begin calculate bounding box]"
;
243
244
Q_ASSERT(m_painter ==
nullptr
);
245
m_boundingRect = QRect{};
246
}
247
248
void
QColrPaintGraphRenderer::drawImage(
const
QImage &image)
249
{
250
qCDebug(lcColrv1).noquote().nospace()
251
<< QByteArray().fill(
' '
, m_oldPaths.size() * 2)
252
<<
"[draw image: "
<< image.size() <<
"]"
;
253
254
if
(m_painter !=
nullptr
) {
255
m_painter->setWorldMatrixEnabled(
false
);
256
m_painter->drawImage(QPoint(0, 0), image);
257
m_painter->setWorldMatrixEnabled(
true
);
258
}
259
}
260
261
void
QColrPaintGraphRenderer::setCompositionMode(QPainter::CompositionMode mode)
262
{
263
qCDebug(lcColrv1).noquote().nospace()
264
<< QByteArray().fill(
' '
, m_oldPaths.size() * 2)
265
<<
"[set composition mode: "
<< mode <<
"]"
;
266
267
if
(m_painter !=
nullptr
)
268
m_painter->setCompositionMode(mode);
269
}
270
271
void
QColrPaintGraphRenderer::beginRender(qreal pixelSizeScale,
const
QTransform &transform)
272
{
273
qCDebug(lcColrv1).noquote().nospace()
274
<< QByteArray().fill(
' '
, m_oldPaths.size() * 2)
275
<<
"[begin render scale: "
<< pixelSizeScale
276
<<
", transform: "
<< transform
277
<<
"]"
;
278
279
if
(m_boundingRect.isEmpty())
280
return
;
281
282
QRect alignedRect(m_boundingRect.toAlignedRect());
283
284
m_image = QImage(alignedRect.size(), QImage::Format_ARGB32_Premultiplied);
285
m_image.fill(Qt::transparent);
286
287
Q_ASSERT(m_painter ==
nullptr
);
288
m_painter =
new
QPainter;
289
m_painter->begin(&m_image);
290
m_painter->setRenderHint(QPainter::Antialiasing);
291
m_painter->setPen(Qt::NoPen);
292
m_painter->setBrush(Qt::NoBrush);
293
294
m_painter->translate(-alignedRect.topLeft());
295
296
// Scale from normalized coordinates
297
m_painter->scale(pixelSizeScale, pixelSizeScale);
298
m_painter->setWorldTransform(transform,
true
);
299
}
300
301
QImage QColrPaintGraphRenderer::endRender()
302
{
303
qCDebug(lcColrv1).noquote().nospace()
304
<< QByteArray().fill(
' '
, m_oldPaths.size() * 2)
305
<<
"[end image size: "
<< m_image.size()
306
<<
"]"
;
307
308
Q_ASSERT(m_painter !=
nullptr
);
309
m_painter->end();
310
delete
m_painter;
311
m_painter =
nullptr
;
312
313
return
m_image;
314
}
315
316
QT_END_NAMESPACE
QT_BEGIN_NAMESPACE
Combined button and popup list for selecting options.
Definition
qsequentialanimationgroup.cpp:47
qtbase
src
gui
text
qcolrpaintgraphrenderer.cpp
Generated on
for Qt by
1.18.0