1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "utils_p.h"
5
6#include <QtGui/QPainter>
7#include <QtGui/QOpenGLContext>
8#include <QtGui/QOffscreenSurface>
9#include <QtCore/QCoreApplication>
10#include <QtCore/QRegularExpression>
11#include <QLocale>
12
13QT_BEGIN_NAMESPACE
14
15#define NUM_IN_POWER(y, x) for (;y<x;y<<=1)
16#define MIN_POWER 2
17
18// Some values that only need to be resolved once
19static bool staticsResolved = false;
20static GLint maxTextureSize = 0;
21static bool isES = false;
22
23GLuint Utils::getNearestPowerOfTwo(GLuint value)
24{
25 GLuint powOfTwoValue = MIN_POWER;
26 NUM_IN_POWER(powOfTwoValue, value);
27 return powOfTwoValue;
28}
29
30QVector4D Utils::vectorFromColor(const QColor &color)
31{
32 return QVector4D(color.redF(), color.greenF(), color.blueF(), color.alphaF());
33}
34
35QColor Utils::colorFromVector(const QVector3D &colorVector)
36{
37 return QColor(colorVector.x() * 255.0f, colorVector.y() * 255.0f,
38 colorVector.z() * 255.0f, 255.0f);
39}
40
41QColor Utils::colorFromVector(const QVector4D &colorVector)
42{
43 return QColor(colorVector.x() * 255.0f, colorVector.y() * 255.0f,
44 colorVector.z() * 255.0f, colorVector.w() * 255.0f);
45}
46
47QImage Utils::printTextToImage(const QFont &font, const QString &text, const QColor &bgrColor,
48 const QColor &txtColor, bool labelBackground,
49 bool borders, int maxLabelWidth)
50{
51 if (!staticsResolved)
52 resolveStatics();
53
54 GLuint paddingWidth = 20;
55 GLuint paddingHeight = 20;
56 GLuint prePadding = 20;
57 GLint targetWidth = maxTextureSize;
58
59 // Calculate text dimensions
60 QFont valueFont = font;
61 valueFont.setPointSize(textureFontSize);
62 QFontMetrics valueFM(valueFont);
63 int valueStrWidth = valueFM.horizontalAdvance(text);
64
65 // ES2 needs to use maxLabelWidth always (when given) because of the power of 2 -issue.
66 if (maxLabelWidth && (labelBackground || Utils::isOpenGLES()))
67 valueStrWidth = maxLabelWidth;
68 int valueStrHeight = valueFM.height();
69 valueStrWidth += paddingWidth / 2; // Fix clipping problem with skewed fonts (italic or italic-style)
70 QSize labelSize;
71 qreal fontRatio = 1.0;
72
73 if (Utils::isOpenGLES()) {
74 // Test if text with slighly smaller font would fit into one step smaller texture
75 // ie. if the text is just exceeded the smaller texture boundary, it would
76 // make a label with large empty space
77 uint testWidth = getNearestPowerOfTwo(value: valueStrWidth + prePadding) >> 1;
78 int diffToFit = (valueStrWidth + prePadding) - testWidth;
79 int maxSqueeze = int((valueStrWidth + prePadding) * 0.25f);
80 if (diffToFit < maxSqueeze && maxTextureSize > GLint(testWidth))
81 targetWidth = testWidth;
82 }
83
84 bool sizeOk = false;
85 int currentFontSize = textureFontSize;
86 do {
87 if (Utils::isOpenGLES()) {
88 // ES2 can't handle textures with dimensions not in power of 2. Resize labels accordingly.
89 // Add some padding before converting to power of two to avoid too tight fit
90 labelSize = QSize(valueStrWidth + prePadding, valueStrHeight + prePadding);
91 labelSize.setWidth(getNearestPowerOfTwo(value: labelSize.width()));
92 labelSize.setHeight(getNearestPowerOfTwo(value: labelSize.height()));
93 } else {
94 if (!labelBackground)
95 labelSize = QSize(valueStrWidth, valueStrHeight);
96 else
97 labelSize = QSize(valueStrWidth + paddingWidth * 2, valueStrHeight + paddingHeight * 2);
98 }
99
100 if (!maxTextureSize || (labelSize.width() <= maxTextureSize
101 && (labelSize.width() <= targetWidth || !Utils::isOpenGLES()))) {
102 // Make sure the label is not too wide
103 sizeOk = true;
104 } else if (--currentFontSize == 4) {
105 qCritical() << "Label" << text << "is too long to be generated.";
106 return QImage();
107 } else {
108 fontRatio = (qreal)currentFontSize / (qreal)textureFontSize;
109 // Reduce font size and try again
110 valueFont.setPointSize(currentFontSize);
111 QFontMetrics currentValueFM(valueFont);
112 if (maxLabelWidth && (labelBackground || Utils::isOpenGLES()))
113 valueStrWidth = maxLabelWidth * fontRatio;
114 else
115 valueStrWidth = currentValueFM.horizontalAdvance(text);
116 valueStrHeight = currentValueFM.height();
117 valueStrWidth += paddingWidth / 2;
118 }
119 } while (!sizeOk);
120
121 // Create image
122 QImage image = QImage(labelSize, QImage::Format_ARGB32);
123 image.fill(color: Qt::transparent);
124
125 // Init painter
126 QPainter painter(&image);
127 // Paint text
128 painter.setRenderHint(hint: QPainter::Antialiasing, on: true);
129 painter.setCompositionMode(QPainter::CompositionMode_Source);
130 painter.setFont(valueFont);
131 if (!labelBackground) {
132 painter.setPen(txtColor);
133 if (Utils::isOpenGLES()) {
134 painter.drawText(x: (labelSize.width() - valueStrWidth) / 2.0f,
135 y: (labelSize.height() - valueStrHeight) / 2.0f,
136 w: valueStrWidth, h: valueStrHeight,
137 flags: Qt::AlignCenter | Qt::AlignVCenter,
138 str: text);
139 } else {
140 painter.drawText(x: 0, y: 0,
141 w: valueStrWidth, h: valueStrHeight,
142 flags: Qt::AlignCenter | Qt::AlignVCenter,
143 str: text);
144 }
145 } else {
146 painter.setBrush(QBrush(bgrColor));
147 qreal radius = 10.0 * fontRatio;
148 if (borders) {
149 painter.setPen(QPen(QBrush(txtColor), 5.0 * fontRatio,
150 Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin));
151 painter.drawRoundedRect(x: 5, y: 5,
152 w: labelSize.width() - 10, h: labelSize.height() - 10,
153 xRadius: radius, yRadius: radius);
154 } else {
155 painter.setPen(bgrColor);
156 painter.drawRoundedRect(x: 0, y: 0, w: labelSize.width(), h: labelSize.height(), xRadius: radius, yRadius: radius);
157 }
158 painter.setPen(txtColor);
159 painter.drawText(x: (labelSize.width() - valueStrWidth) / 2.0f,
160 y: (labelSize.height() - valueStrHeight) / 2.0f,
161 w: valueStrWidth, h: valueStrHeight,
162 flags: Qt::AlignCenter | Qt::AlignVCenter,
163 str: text);
164 }
165 return image;
166}
167
168QVector4D Utils::getSelection(QPoint mousepos, int height)
169{
170 // This is the only one that works with OpenGL ES 2.0, so we're forced to use it
171 // Item count will be limited to 256*256*256
172 GLubyte pixel[4] = {255, 255, 255, 255};
173 QOpenGLContext::currentContext()->functions()->glReadPixels(x: mousepos.x(), y: height - mousepos.y(),
174 width: 1, height: 1, GL_RGBA, GL_UNSIGNED_BYTE,
175 pixels: (void *)pixel);
176 QVector4D selectedColor(pixel[0], pixel[1], pixel[2], pixel[3]);
177 return selectedColor;
178}
179
180QImage Utils::getGradientImage(QLinearGradient &gradient)
181{
182 QImage image(QSize(gradientTextureWidth, gradientTextureHeight), QImage::Format_RGB32);
183 gradient.setFinalStop(x: qreal(gradientTextureWidth), y: qreal(gradientTextureHeight));
184 gradient.setStart(x: 0.0, y: 0.0);
185
186 QPainter pmp(&image);
187 pmp.setBrush(QBrush(gradient));
188 pmp.setPen(Qt::NoPen);
189 pmp.drawRect(x: 0, y: 0, w: int(gradientTextureWidth), h: int(gradientTextureHeight));
190 return image;
191}
192
193Utils::ParamType Utils::preParseFormat(const QString &format, QString &preStr, QString &postStr,
194 int &precision, char &formatSpec)
195{
196 static QRegularExpression formatMatcher(QStringLiteral("^([^%]*)%([\\-\\+#\\s\\d\\.lhjztL]*)([dicuoxfegXFEG])(.*)$"));
197 static QRegularExpression precisionMatcher(QStringLiteral("\\.(\\d+)"));
198
199 Utils::ParamType retVal;
200
201 QRegularExpressionMatch formatMatch = formatMatcher.match(subject: format, offset: 0);
202
203 if (formatMatch.hasMatch()) {
204 preStr = formatMatch.captured(nth: 1);
205 // Six and 'g' are defaults in Qt API
206 precision = 6;
207 if (!formatMatch.captured(nth: 2).isEmpty()) {
208 QRegularExpressionMatch precisionMatch = precisionMatcher.match(subject: formatMatch.captured(nth: 2),
209 offset: 0);
210 if (precisionMatch.hasMatch())
211 precision = precisionMatch.captured(nth: 1).toInt();
212 }
213 if (formatMatch.captured(nth: 3).isEmpty())
214 formatSpec = 'g';
215 else
216 formatSpec = formatMatch.captured(nth: 3).at(i: 0).toLatin1();
217 postStr = formatMatch.captured(nth: 4);
218 retVal = mapFormatCharToParamType(formatSpec);
219 } else {
220 retVal = ParamTypeUnknown;
221 // The out parameters are irrelevant in unknown case
222 }
223
224 return retVal;
225}
226
227Utils::ParamType Utils::mapFormatCharToParamType(char formatSpec)
228{
229 ParamType retVal = ParamTypeUnknown;
230 if (formatSpec == 'd' || formatSpec == 'i' || formatSpec == 'c') {
231 retVal = ParamTypeInt;
232 } else if (formatSpec == 'u' || formatSpec == 'o'
233 || formatSpec == 'x'|| formatSpec == 'X') {
234 retVal = ParamTypeUInt;
235 } else if (formatSpec == 'f' || formatSpec == 'F'
236 || formatSpec == 'e' || formatSpec == 'E'
237 || formatSpec == 'g' || formatSpec == 'G') {
238 retVal = ParamTypeReal;
239 }
240
241 return retVal;
242}
243
244QString Utils::formatLabelSprintf(const QByteArray &format, Utils::ParamType paramType, qreal value)
245{
246 switch (paramType) {
247 case ParamTypeInt:
248 return QString::asprintf(format: format.constData(), qint64(value));
249 case ParamTypeUInt:
250 return QString::asprintf(format: format.constData(), quint64(value));
251 case ParamTypeReal:
252 return QString::asprintf(format: format.constData(), value);
253 default:
254 // Return format string to detect errors. Bars selection label logic also depends on this.
255 return QString::fromUtf8(ba: format);
256 }
257}
258
259QString Utils::formatLabelLocalized(Utils::ParamType paramType, qreal value,
260 const QLocale &locale, const QString &preStr, const QString &postStr,
261 int precision, char formatSpec, const QByteArray &format)
262{
263 switch (paramType) {
264 case ParamTypeInt:
265 case ParamTypeUInt:
266 return preStr + locale.toString(i: qint64(value)) + postStr;
267 case ParamTypeReal:
268 return preStr + locale.toString(f: value, format: formatSpec, precision) + postStr;
269 default:
270 // Return format string to detect errors. Bars selection label logic also depends on this.
271 return QString::fromUtf8(ba: format);
272 }
273}
274
275QString Utils::defaultLabelFormat()
276{
277 static const QString defaultFormat(QStringLiteral("%.2f"));
278 return defaultFormat;
279}
280
281float Utils::wrapValue(float value, float min, float max)
282{
283 if (value > max) {
284 value = min + (value - max);
285
286 // In case single wrap fails, jump to opposite end.
287 if (value > max)
288 value = min;
289 }
290
291 if (value < min) {
292 value = max + (value - min);
293
294 // In case single wrap fails, jump to opposite end.
295 if (value < min)
296 value = max;
297 }
298
299 return value;
300}
301
302QQuaternion Utils::calculateRotation(const QVector3D &xyzRotations)
303{
304 QQuaternion rotQuatX = QQuaternion::fromAxisAndAngle(x: 1.0f, y: 0.0f, z: 0.0f, angle: xyzRotations.x());
305 QQuaternion rotQuatY = QQuaternion::fromAxisAndAngle(x: 0.0f, y: 1.0f, z: 0.0f, angle: xyzRotations.y());
306 QQuaternion rotQuatZ = QQuaternion::fromAxisAndAngle(x: 0.0f, y: 0.0f, z: 1.0f, angle: xyzRotations.z());
307 QQuaternion totalRotation = rotQuatY * rotQuatZ * rotQuatX;
308 return totalRotation;
309}
310
311bool Utils::isOpenGLES()
312{
313 if (!staticsResolved)
314 resolveStatics();
315 return isES;
316}
317
318void Utils::resolveStatics()
319{
320 QOpenGLContext *ctx = QOpenGLContext::currentContext();
321 QOffscreenSurface *dummySurface = 0;
322 if (!ctx) {
323 QSurfaceFormat surfaceFormat;
324 dummySurface = new QOffscreenSurface();
325 dummySurface->setFormat(surfaceFormat);
326 dummySurface->create();
327 ctx = new QOpenGLContext;
328 ctx->setFormat(surfaceFormat);
329 ctx->create();
330 ctx->makeCurrent(surface: dummySurface);
331 }
332
333#if QT_CONFIG(opengles2)
334 isES = true;
335#elif (QT_VERSION < QT_VERSION_CHECK(5, 3, 0))
336 isES = false;
337#else
338 isES = ctx->isOpenGLES();
339#endif
340
341 ctx->functions()->glGetIntegerv(GL_MAX_TEXTURE_SIZE, params: &maxTextureSize);
342
343#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
344 // We support only ES2 emulation with software renderer for now
345 QString versionStr;
346#ifdef Q_OS_WIN
347 const GLubyte *openGLVersion = ctx->functions()->glGetString(GL_VERSION);
348 versionStr = QString::fromLatin1(reinterpret_cast<const char *>(openGLVersion)).toLower();
349#endif
350 if (versionStr.contains(QStringLiteral("mesa"), cs: Qt::CaseInsensitive)
351 || QCoreApplication::testAttribute(attribute: Qt::AA_UseSoftwareOpenGL)) {
352 qWarning(msg: "Only OpenGL ES2 emulation is available for software rendering.");
353 isES = true;
354 }
355#endif
356
357 if (dummySurface) {
358 ctx->doneCurrent();
359 delete ctx;
360 delete dummySurface;
361 }
362
363 staticsResolved = true;
364}
365
366QT_END_NAMESPACE
367

source code of qtdatavis3d/src/datavisualization/utils/utils.cpp