Revision control

++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++
++ #include "utils_p.h"
++
++ #include <QtGui/QPainter>
++ #include <QtGui/QOpenGLContext>
++ #include <QtGui/QOffscreenSurface>
++ #include <QtGui/qquaternion.h>
++
++ #include <QtCore/QCoreApplication>
++ #include <QtCore/QRegularExpression>
++ #include <QLocale>
++
++ QT_BEGIN_NAMESPACE
++
++ #define NUM_IN_POWER(y, x) for (;y<x;y<<=1)
++ #define MIN_POWER 2
++
++ // Some values that only need to be resolved once
++ static bool staticsResolved = false;
++ static GLint maxTextureSize = 0;
++ static bool isES = false;
++
++ GLuint Utils::getNearestPowerOfTwo(GLuint value)
++ {
++ GLuint powOfTwoValue = MIN_POWER;
++ NUM_IN_POWER(powOfTwoValue, value);
++ return powOfTwoValue;
++ }
++
++ QVector4D Utils::vectorFromColor(const QColor &color)
++ {
++ return QVector4D(color.redF(), color.greenF(), color.blueF(), color.alphaF());
++ }
++
++ QColor Utils::colorFromVector(const QVector3D &colorVector)
++ {
++ return QColor(colorVector.x() * 255.0f, colorVector.y() * 255.0f,
++ colorVector.z() * 255.0f, 255.0f);
++ }
++
++ QColor Utils::colorFromVector(const QVector4D &colorVector)
++ {
++ return QColor(colorVector.x() * 255.0f, colorVector.y() * 255.0f,
++ colorVector.z() * 255.0f, colorVector.w() * 255.0f);
++ }
++
++ QImage Utils::printTextToImage(const QFont &font, const QString &text, const QColor &bgrColor,
++ const QColor &txtColor, bool labelBackground,
++ bool borders, int maxLabelWidth)
++ {
++ if (!staticsResolved)
++ resolveStatics();
++
++ GLuint paddingWidth = 20;
++ GLuint paddingHeight = 20;
++ GLuint prePadding = 20;
++ GLint targetWidth = maxTextureSize;
++
++ // Calculate text dimensions
++ QFont valueFont = font;
++ valueFont.setPointSize(textureFontSize);
++ QFontMetrics valueFM(valueFont);
++ int valueStrWidth = valueFM.horizontalAdvance(text);
++
++ // ES2 needs to use maxLabelWidth always (when given) because of the power of 2 -issue.
++ if (maxLabelWidth && (labelBackground || Utils::isOpenGLES()))
++ valueStrWidth = maxLabelWidth;
++ int valueStrHeight = valueFM.height();
++ valueStrWidth += paddingWidth / 2; // Fix clipping problem with skewed fonts (italic or italic-style)
++ QSize labelSize;
++ qreal fontRatio = 1.0;
++
++ if (Utils::isOpenGLES()) {
++ // Test if text with slighly smaller font would fit into one step smaller texture
++ // ie. if the text is just exceeded the smaller texture boundary, it would
++ // make a label with large empty space
++ uint testWidth = getNearestPowerOfTwo(valueStrWidth + prePadding) >> 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);
++
++ return [&] {
++ // 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 QT_CONFIG(opengles2)
++ 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<const char *>(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