blob: e80058db20ca2beaa9a3ea9e757186a30fa67144 [file] [log] [blame]
/****************************************************************************
**
** 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 <QtGui/QPainter>
#include <QtGui/QOpenGLContext>
#include <QtGui/QOffscreenSurface>
#include <QtCore/QCoreApplication>
QT_BEGIN_NAMESPACE_DATAVISUALIZATION
#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);
// 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 QRegExp formatMatcher(QStringLiteral("^([^%]*)%([\\-\\+#\\s\\d\\.lhjztL]*)([dicuoxfegXFEG])(.*)$"));
static QRegExp precisionMatcher(QStringLiteral("\\.(\\d+)"));
Utils::ParamType retVal;
if (formatMatcher.indexIn(format, 0) != -1) {
preStr = formatMatcher.cap(1);
// Six and 'g' are defaults in Qt API
precision = 6;
if (!formatMatcher.cap(2).isEmpty()) {
if (precisionMatcher.indexIn(formatMatcher.cap(2), 0) != -1)
precision = precisionMatcher.cap(1).toInt();
}
if (formatMatcher.cap(3).isEmpty())
formatSpec = 'g';
else
formatSpec = formatMatcher.cap(3).at(0).toLatin1();
postStr = formatMatcher.cap(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<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_DATAVISUALIZATION