/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Data Visualization module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 or (at your option) any later version ** approved by the KDE Free Qt Foundation. The licenses are as published by ** the Free Software Foundation and appearing in the file LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "utils_p.h" #include #include #include #include #include QT_BEGIN_NAMESPACE_DATAVISUALIZATION #define NUM_IN_POWER(y, x) for (;y> 1; int diffToFit = (valueStrWidth + prePadding) - testWidth; int maxSqueeze = int((valueStrWidth + prePadding) * 0.25f); if (diffToFit < maxSqueeze && maxTextureSize > GLint(testWidth)) targetWidth = testWidth; } bool sizeOk = false; int currentFontSize = textureFontSize; do { if (Utils::isOpenGLES()) { // ES2 can't handle textures with dimensions not in power of 2. Resize labels accordingly. // Add some padding before converting to power of two to avoid too tight fit labelSize = QSize(valueStrWidth + prePadding, valueStrHeight + prePadding); labelSize.setWidth(getNearestPowerOfTwo(labelSize.width())); labelSize.setHeight(getNearestPowerOfTwo(labelSize.height())); } else { if (!labelBackground) labelSize = QSize(valueStrWidth, valueStrHeight); else labelSize = QSize(valueStrWidth + paddingWidth * 2, valueStrHeight + paddingHeight * 2); } if (!maxTextureSize || (labelSize.width() <= maxTextureSize && (labelSize.width() <= targetWidth || !Utils::isOpenGLES()))) { // Make sure the label is not too wide sizeOk = true; } else if (--currentFontSize == 4) { qCritical() << "Label" << text << "is too long to be generated."; return QImage(); } else { fontRatio = (qreal)currentFontSize / (qreal)textureFontSize; // Reduce font size and try again valueFont.setPointSize(currentFontSize); QFontMetrics currentValueFM(valueFont); if (maxLabelWidth && (labelBackground || Utils::isOpenGLES())) valueStrWidth = maxLabelWidth * fontRatio; else valueStrWidth = currentValueFM.horizontalAdvance(text); valueStrHeight = currentValueFM.height(); valueStrWidth += paddingWidth / 2; } } while (!sizeOk); // Create image QImage image = QImage(labelSize, QImage::Format_ARGB32); image.fill(Qt::transparent); // Init painter QPainter painter(&image); // Paint text painter.setRenderHint(QPainter::Antialiasing, true); painter.setCompositionMode(QPainter::CompositionMode_Source); painter.setFont(valueFont); if (!labelBackground) { painter.setPen(txtColor); if (Utils::isOpenGLES()) { painter.drawText((labelSize.width() - valueStrWidth) / 2.0f, (labelSize.height() - valueStrHeight) / 2.0f, valueStrWidth, valueStrHeight, Qt::AlignCenter | Qt::AlignVCenter, text); } else { painter.drawText(0, 0, valueStrWidth, valueStrHeight, Qt::AlignCenter | Qt::AlignVCenter, text); } } else { painter.setBrush(QBrush(bgrColor)); qreal radius = 10.0 * fontRatio; if (borders) { painter.setPen(QPen(QBrush(txtColor), 5.0 * fontRatio, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin)); painter.drawRoundedRect(5, 5, labelSize.width() - 10, labelSize.height() - 10, radius, radius); } else { painter.setPen(bgrColor); painter.drawRoundedRect(0, 0, labelSize.width(), labelSize.height(), radius, radius); } painter.setPen(txtColor); painter.drawText((labelSize.width() - valueStrWidth) / 2.0f, (labelSize.height() - valueStrHeight) / 2.0f, valueStrWidth, valueStrHeight, Qt::AlignCenter | Qt::AlignVCenter, text); } return image; } QVector4D Utils::getSelection(QPoint mousepos, int height) { // This is the only one that works with OpenGL ES 2.0, so we're forced to use it // Item count will be limited to 256*256*256 GLubyte pixel[4] = {255, 255, 255, 255}; QOpenGLContext::currentContext()->functions()->glReadPixels(mousepos.x(), height - mousepos.y(), 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void *)pixel); QVector4D selectedColor(pixel[0], pixel[1], pixel[2], pixel[3]); return selectedColor; } QImage Utils::getGradientImage(QLinearGradient &gradient) { QImage image(QSize(gradientTextureWidth, gradientTextureHeight), QImage::Format_RGB32); gradient.setFinalStop(qreal(gradientTextureWidth), qreal(gradientTextureHeight)); gradient.setStart(0.0, 0.0); QPainter pmp(&image); pmp.setBrush(QBrush(gradient)); pmp.setPen(Qt::NoPen); pmp.drawRect(0, 0, int(gradientTextureWidth), int(gradientTextureHeight)); return image; } Utils::ParamType Utils::preParseFormat(const QString &format, QString &preStr, QString &postStr, int &precision, char &formatSpec) { static QRegularExpression formatMatcher(QStringLiteral("^([^%]*)%([\\-\\+#\\s\\d\\.lhjztL]*)([dicuoxfegXFEG])(.*)$")); static QRegularExpression precisionMatcher(QStringLiteral("\\.(\\d+)")); Utils::ParamType retVal; QRegularExpressionMatch formatMatch = formatMatcher.match(format, 0); if (formatMatch.hasMatch()) { preStr = formatMatch.captured(1); // Six and 'g' are defaults in Qt API precision = 6; if (!formatMatch.captured(2).isEmpty()) { QRegularExpressionMatch precisionMatch = precisionMatcher.match(formatMatch.captured(2), 0); if (precisionMatch.hasMatch()) precision = precisionMatch.captured(1).toInt(); } if (formatMatch.captured(3).isEmpty()) formatSpec = 'g'; else formatSpec = formatMatch.captured(3).at(0).toLatin1(); postStr = formatMatch.captured(4); retVal = mapFormatCharToParamType(formatSpec); } else { retVal = ParamTypeUnknown; // The out parameters are irrelevant in unknown case } return retVal; } Utils::ParamType Utils::mapFormatCharToParamType(char formatSpec) { ParamType retVal = ParamTypeUnknown; if (formatSpec == 'd' || formatSpec == 'i' || formatSpec == 'c') { retVal = ParamTypeInt; } else if (formatSpec == 'u' || formatSpec == 'o' || formatSpec == 'x'|| formatSpec == 'X') { retVal = ParamTypeUInt; } else if (formatSpec == 'f' || formatSpec == 'F' || formatSpec == 'e' || formatSpec == 'E' || formatSpec == 'g' || formatSpec == 'G') { retVal = ParamTypeReal; } return retVal; } QString Utils::formatLabelSprintf(const QByteArray &format, Utils::ParamType paramType, qreal value) { switch (paramType) { case ParamTypeInt: return QString::asprintf(format.constData(), qint64(value)); case ParamTypeUInt: return QString::asprintf(format.constData(), quint64(value)); case ParamTypeReal: return QString::asprintf(format.constData(), value); default: // Return format string to detect errors. Bars selection label logic also depends on this. return QString::fromUtf8(format); } } QString Utils::formatLabelLocalized(Utils::ParamType paramType, qreal value, const QLocale &locale, const QString &preStr, const QString &postStr, int precision, char formatSpec, const QByteArray &format) { switch (paramType) { case ParamTypeInt: case ParamTypeUInt: return preStr + locale.toString(qint64(value)) + postStr; case ParamTypeReal: return preStr + locale.toString(value, formatSpec, precision) + postStr; default: // Return format string to detect errors. Bars selection label logic also depends on this. return QString::fromUtf8(format); } } QString Utils::defaultLabelFormat() { static const QString defaultFormat(QStringLiteral("%.2f")); return defaultFormat; } float Utils::wrapValue(float value, float min, float max) { if (value > max) { value = min + (value - max); // In case single wrap fails, jump to opposite end. if (value > max) value = min; } if (value < min) { value = max + (value - min); // In case single wrap fails, jump to opposite end. if (value < min) value = max; } return value; } QQuaternion Utils::calculateRotation(const QVector3D &xyzRotations) { QQuaternion rotQuatX = QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, xyzRotations.x()); QQuaternion rotQuatY = QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, xyzRotations.y()); QQuaternion rotQuatZ = QQuaternion::fromAxisAndAngle(0.0f, 0.0f, 1.0f, xyzRotations.z()); QQuaternion totalRotation = rotQuatY * rotQuatZ * rotQuatX; return totalRotation; } bool Utils::isOpenGLES() { if (!staticsResolved) resolveStatics(); return isES; } void Utils::resolveStatics() { QOpenGLContext *ctx = QOpenGLContext::currentContext(); QOffscreenSurface *dummySurface = 0; if (!ctx) { QSurfaceFormat surfaceFormat; dummySurface = new QOffscreenSurface(); dummySurface->setFormat(surfaceFormat); dummySurface->create(); ctx = new QOpenGLContext; ctx->setFormat(surfaceFormat); ctx->create(); ctx->makeCurrent(dummySurface); } #if defined(QT_OPENGL_ES_2) isES = true; #elif (QT_VERSION < QT_VERSION_CHECK(5, 3, 0)) isES = false; #else isES = ctx->isOpenGLES(); #endif ctx->functions()->glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); #if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)) // We support only ES2 emulation with software renderer for now QString versionStr; #ifdef Q_OS_WIN const GLubyte *openGLVersion = ctx->functions()->glGetString(GL_VERSION); versionStr = QString::fromLatin1(reinterpret_cast(openGLVersion)).toLower(); #endif if (versionStr.contains(QStringLiteral("mesa"), Qt::CaseInsensitive) || QCoreApplication::testAttribute(Qt::AA_UseSoftwareOpenGL)) { qWarning("Only OpenGL ES2 emulation is available for software rendering."); isES = true; } #endif if (dummySurface) { ctx->doneCurrent(); delete ctx; delete dummySurface; } staticsResolved = true; } QT_END_NAMESPACE_DATAVISUALIZATION