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

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