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

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