Revision control

++ // Copyright (C) 2023 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++
++ #include <QtCore/qdebug.h>
++ #include <QtCore/qfileinfo.h>
++ #include "qheightmapsurfacedataproxy_p.h"
++ #include "qsurface3dseries_p.h"
++ #include "qgraphs3dlogging_p.h"
++
++ #include <qtgraphs_tracepoints_p.h>
++
++ QT_BEGIN_NAMESPACE
++
++ Q_TRACE_PREFIX(qtgraphs,
++ "QT_BEGIN_NAMESPACE" \
++ "class QHeightMapSurfaceDataProxy;" \
++ "QT_END_NAMESPACE"
++ )
++
++ Q_TRACE_POINT(qtgraphs, QGraphs3DHeightMapSurfaceDataProxHandlePendingResolve_entry, bool is16Bit);
++ Q_TRACE_POINT(qtgraphs, QGraphs3DHeightMapSurfaceDataProxHandlePendingResolve_exit);
++
++ // Default ranges correspond value axis defaults
++ const float defaultMinValue = 0.0f;
++ const float defaultMaxValue = 10.0f;
++
++ /*!
++ * \class QHeightMapSurfaceDataProxy
++ * \inmodule QtGraphs
++ * \ingroup graphs_3D
++ * \brief Base proxy class for Q3DSurfaceWidgetItem.
++ *
++ * QHeightMapSurfaceDataProxy takes care of the processing of height map data
++ * related to surfaces. It provides the visualization of a height map as a
++ * surface plot.
++ *
++ * Since height maps do not contain values for X or Z axes, those values need to
++ * be given separately using the minXValue, maxXValue, minZValue, and maxZValue
++ * properties. The X-value corresponds to image horizontal direction and the Z-value
++ * to the vertical. Setting any of these properties triggers an asynchronous
++ * re-resolution of any existing height map.
++ *
++ * \sa QSurfaceDataProxy, {Qt Graphs Data Handling with 3D}
++ */
++
++ /*!
++ * \qmltype HeightMapSurfaceDataProxy
++ * \inqmlmodule QtGraphs
++ * \ingroup graphs_qml_3D
++ * \nativetype QHeightMapSurfaceDataProxy
++ * \inherits SurfaceDataProxy
++ * \brief Base proxy type for Surface3D.
++ *
++ * QHeightMapSurfaceDataProxy takes care of the processing of height map data
++ * related to surfaces. It provides the visualization of a height map as a
++ * surface plot.
++ *
++ * For more complete description, see QHeightMapSurfaceDataProxy.
++ *
++ * \sa {Qt Graphs Data Handling with 3D}
++ */
++
++ /*!
++ * \qmlproperty string HeightMapSurfaceDataProxy::heightMapFile
++ *
++ * A file with a height map image to be visualized. Setting this property
++ * replaces current data with height map data.
++ *
++ * There are several formats the image file can be given in, but if it is not in
++ * a directly usable format, a conversion is made.
++ *
++ * \note If the result seems wrong, the automatic conversion failed
++ * and you should try converting the image yourself before setting it. Preferred
++ * format is QImage::Format_RGB32 in grayscale.
++ *
++ * The height of the image is read from the red component of the pixels if the
++ * image is in grayscale. Otherwise, it is an average calculated from the red, green,
++ * and blue components of the pixels. Using grayscale images may improve data
++ * conversion speed for large images.
++ *
++ * Since height maps do not contain values for X or Z axes, these values need to
++ * be given separately using the minXValue, maxXValue, minZValue, and maxZValue
++ * properties. The X-value corresponds to the image's horizontal direction,
++ * and the Z-value to the vertical. Setting any of these properties triggers
++ * an asynchronous re-resolution of any existing height map.
++ *
++ * Not recommended formats: all mono formats (for example QImage::Format_Mono).
++ */
++
++ /*!
++ * \qmlproperty real HeightMapSurfaceDataProxy::minXValue
++ *
++ * The minimum X value for the generated surface points. Defaults to \c{0.0}.
++ * When setting this property the corresponding maximum value is adjusted if
++ * necessary, to ensure that the range remains valid.
++ */
++
++ /*!
++ * \qmlproperty real HeightMapSurfaceDataProxy::maxXValue
++ *
++ * The maximum X value for the generated surface points. Defaults to \c{10.0}.
++ * When setting this property the corresponding minimum value is adjusted if
++ * necessary, to ensure that the range remains valid.
++ */
++
++ /*!
++ * \qmlproperty real HeightMapSurfaceDataProxy::minZValue
++ *
++ * The minimum Z value for the generated surface points. Defaults to \c{0.0}.
++ * When setting this property the corresponding maximum value is adjusted if
++ * necessary, to ensure that the range remains valid.
++ */
++
++ /*!
++ * \qmlproperty real HeightMapSurfaceDataProxy::maxZValue
++ *
++ * The maximum Z value for the generated surface points. Defaults to \c{10.0}.
++ * When setting this property the corresponding minimum value is adjusted if
++ * necessary, to ensure that the range remains valid.
++ */
++
++ /*!
++ * \qmlproperty real HeightMapSurfaceDataProxy::minYValue
++ *
++ * The minimum Y value for the generated surface points. Defaults to \c{0.0}.
++ * When setting this property the corresponding maximum value is adjusted if
++ * necessary, to ensure that the range remains valid.
++ */
++
++ /*!
++ * \qmlproperty real HeightMapSurfaceDataProxy::maxYValue
++ *
++ * The maximum Y value for the generated surface points. Defaults to \c{10.0}.
++ * When setting this property the corresponding minimum value is adjusted if
++ * necessary, to ensure that the range remains valid.
++ */
++
++ /*!
++ * \qmlproperty real HeightMapSurfaceDataProxy::autoScaleY
++ *
++ * Scale height values to Y-axis. Defaults to \c{false}. When this property is
++ * set to \c{true}, the height values are scaled to fit on the Y-axis between
++ * \c{minYValue} and \c{maxYValue}.
++ */
++
++ /*!
++ \qmlsignal HeightMapSurfaceDataProxy::heightMapFileChanged(string filename)
++
++ This signal is emitted when heightMapFile changes to \a filename.
++ */
++
++ /*!
++ \qmlsignal HeightMapSurfaceDataProxy::minXValueChanged(real value)
++
++ This signal is emitted when minXValue changes to \a value.
++ */
++
++ /*!
++ \qmlsignal HeightMapSurfaceDataProxy::maxXValueChanged(real value)
++
++ This signal is emitted when maxXValue changes to \a value.
++ */
++
++ /*!
++ \qmlsignal HeightMapSurfaceDataProxy::minZValueChanged(real value)
++
++ This signal is emitted when minZValue changes to \a value.
++ */
++
++ /*!
++ \qmlsignal HeightMapSurfaceDataProxy::maxZValueChanged(real value)
++
++ This signal is emitted when maxZValue changes to \a value.
++ */
++
++ /*!
++ \qmlsignal HeightMapSurfaceDataProxy::minYValueChanged(real value)
++
++ This signal is emitted when minYValue changes to \a value.
++ */
++
++ /*!
++ \qmlsignal HeightMapSurfaceDataProxy::maxYValueChanged(real value)
++
++ This signal is emitted when maxYValue changes to \a value.
++ */
++
++ /*!
++ \qmlsignal HeightMapSurfaceDataProxy::autoScaleYChanged(bool enabled)
++
++ This signal is emitted when autoScaleY changes to \a enabled.
++ */
++
++ /*!
++ * Constructs QHeightMapSurfaceDataProxy with the given \a parent.
++ */
++ QHeightMapSurfaceDataProxy::QHeightMapSurfaceDataProxy(QObject *parent)
++ : QSurfaceDataProxy(*(new QHeightMapSurfaceDataProxyPrivate()), parent)
++ {
++ Q_D(QHeightMapSurfaceDataProxy);
++ QObject::connect(&d->m_resolveTimer,
++ &QTimer::timeout,
++ this,
++ &QHeightMapSurfaceDataProxy::handlePendingResolve);
++ }
++
++ /*!
++ * Constructs QHeightMapSurfaceDataProxy with the given \a image and \a parent.
++ * Height map is set by calling setHeightMap() with \a image.
++ *
++ * \sa heightMap
++ */
++ QHeightMapSurfaceDataProxy::QHeightMapSurfaceDataProxy(const QImage &image, QObject *parent)
++ : QSurfaceDataProxy(*(new QHeightMapSurfaceDataProxyPrivate()), parent)
++ {
++ Q_D(QHeightMapSurfaceDataProxy);
++ QObject::connect(&d->m_resolveTimer,
++ &QTimer::timeout,
++ this,
++ &QHeightMapSurfaceDataProxy::handlePendingResolve);
++ setHeightMap(image);
++ }
++
++ /*!
++ * Constructs QHeightMapSurfaceDataProxy from the given image \a filename and \a
++ * parent. Height map is set by calling setHeightMapFile() with \a filename.
++ *
++ * \sa heightMapFile
++ */
++ QHeightMapSurfaceDataProxy::QHeightMapSurfaceDataProxy(const QString &filename, QObject *parent)
++ : QSurfaceDataProxy(*(new QHeightMapSurfaceDataProxyPrivate()), parent)
++ {
++ Q_D(QHeightMapSurfaceDataProxy);
++ QObject::connect(&d->m_resolveTimer,
++ &QTimer::timeout,
++ this,
++ &QHeightMapSurfaceDataProxy::handlePendingResolve);
++ setHeightMapFile(filename);
++ }
++
++ /*!
++ * \internal
++ */
++ QHeightMapSurfaceDataProxy::QHeightMapSurfaceDataProxy(QHeightMapSurfaceDataProxyPrivate &d,
++ QObject *parent)
++ : QSurfaceDataProxy(d, parent)
++ {}
++
++ /*!
++ * Destroys QHeightMapSurfaceDataProxy.
++ */
++ QHeightMapSurfaceDataProxy::~QHeightMapSurfaceDataProxy() {}
++
++ /*!
++ * \property QHeightMapSurfaceDataProxy::heightMap
++ *
++ * \brief The height map image to be visualized.
++ */
++
++ /*!
++ * Replaces current data with the height map data specified by \a image.
++ *
++ * There are several formats the \a image can be given in, but if it is not in a
++ * directly usable format, a conversion is made.
++ *
++ * \note If the result seems wrong, the automatic conversion failed
++ * and you should try converting the \a image yourself before setting it.
++ * Preferred format is QImage::Format_RGB32 in grayscale.
++ *
++ * The height of the \a image is read from the red component of the pixels if
++ * the \a image is in grayscale. Otherwise it is an average calculated from the red,
++ * green, and blue components of the pixels. Using grayscale images may improve
++ * data conversion speed for large images.
++ *
++ * Not recommended formats: all mono formats (for example QImage::Format_Mono).
++ *
++ * The height map is resolved asynchronously. QSurfaceDataProxy::arrayReset() is
++ * emitted when the data has been resolved.
++ */
++ void QHeightMapSurfaceDataProxy::setHeightMap(const QImage &image)
++ {
++ Q_D(QHeightMapSurfaceDataProxy);
++ d->m_heightMap = image;
++
++ // We do resolving asynchronously to make qml onArrayReset handlers actually
++ // get the initial reset
++ if (!d->m_resolveTimer.isActive())
++ d->m_resolveTimer.start(0);
++ }
++
++ QImage QHeightMapSurfaceDataProxy::heightMap() const
++ {
++ Q_D(const QHeightMapSurfaceDataProxy);
++ return d->m_heightMap;
++ }
++
++ /*!
++ * \property QHeightMapSurfaceDataProxy::heightMapFile
++ *
++ * \brief The name of the file with a height map image to be visualized.
++ */
++
++ /*!
++ * Replaces current data with height map data from the file specified by
++ * \a filename.
++ *
++ * \sa heightMap
++ */
++ void QHeightMapSurfaceDataProxy::setHeightMapFile(const QString &filename)
++ {
++ Q_D(QHeightMapSurfaceDataProxy);
++ QFileInfo validfile(filename);
++ // Check if the filename is empty, in which case we should clear the height map,
++ // or if not, it's an actual file that can be found
++ if (!filename.isEmpty() && (!validfile.exists() || !validfile.isFile())) {
++ qCWarning(lcProperties3D, "%s height map file %s does not exist.",
++ qUtf8Printable(QLatin1String(__func__)), qUtf8Printable(filename));
++ return;
++ }
++ if (d->m_heightMapFile == filename) {
++ qCDebug(lcProperties3D, "%s value is already set to: %s",
++ qUtf8Printable(QLatin1String(__FUNCTION__)), qUtf8Printable(filename));
++ return;
++ }
++ d->m_heightMapFile = filename;
++ setHeightMap(QImage(filename));
++ emit heightMapFileChanged(filename);
++ }
++
++ QString QHeightMapSurfaceDataProxy::heightMapFile() const
++ {
++ Q_D(const QHeightMapSurfaceDataProxy);
++ return d->m_heightMapFile;
++ }
++
++ /*!
++ * A convenience function for setting all minimum (\a minX and \a minZ) and
++ * maximum
++ * (\a maxX and \a maxZ) values at the same time. The minimum values must be
++ * smaller than the corresponding maximum value. Otherwise the values get
++ * adjusted so that they are valid.
++ */
++ void QHeightMapSurfaceDataProxy::setValueRanges(float minX, float maxX, float minZ, float maxZ)
++ {
++ Q_D(QHeightMapSurfaceDataProxy);
++ d->setValueRanges(minX, maxX, minZ, maxZ);
++ }
++
++ /*!
++ * \property QHeightMapSurfaceDataProxy::minXValue
++ *
++ * \brief The minimum X value for the generated surface points.
++ *
++ * Defaults to \c{0.0}.
++ *
++ * When setting this property the corresponding maximum value is adjusted if
++ * necessary, to ensure that the range remains valid.
++ */
++ void QHeightMapSurfaceDataProxy::setMinXValue(float min)
++ {
++ Q_D(QHeightMapSurfaceDataProxy);
++ d->setMinXValue(min);
++ }
++
++ float QHeightMapSurfaceDataProxy::minXValue() const
++ {
++ Q_D(const QHeightMapSurfaceDataProxy);
++ return d->m_minXValue;
++ }
++
++ /*!
++ * \property QHeightMapSurfaceDataProxy::maxXValue
++ *
++ * \brief The maximum X value for the generated surface points.
++ *
++ * Defaults to \c{10.0}.
++ *
++ * When setting this property the corresponding minimum value is adjusted if
++ * necessary, to ensure that the range remains valid.
++ */
++ void QHeightMapSurfaceDataProxy::setMaxXValue(float max)
++ {
++ Q_D(QHeightMapSurfaceDataProxy);
++ d->setMaxXValue(max);
++ }
++
++ float QHeightMapSurfaceDataProxy::maxXValue() const
++ {
++ Q_D(const QHeightMapSurfaceDataProxy);
++ return d->m_maxXValue;
++ }
++
++ /*!
++ * \property QHeightMapSurfaceDataProxy::minZValue
++ *
++ * \brief The minimum Z value for the generated surface points.
++ *
++ * Defaults to \c{0.0}.
++ *
++ * When setting this property the corresponding maximum value is adjusted if
++ * necessary, to ensure that the range remains valid.
++ */
++ void QHeightMapSurfaceDataProxy::setMinZValue(float min)
++ {
++ Q_D(QHeightMapSurfaceDataProxy);
++ d->setMinZValue(min);
++ }
++
++ float QHeightMapSurfaceDataProxy::minZValue() const
++ {
++ Q_D(const QHeightMapSurfaceDataProxy);
++ return d->m_minZValue;
++ }
++
++ /*!
++ * \property QHeightMapSurfaceDataProxy::maxZValue
++ *
++ * \brief The maximum Z value for the generated surface points.
++ *
++ * Defaults to \c{10.0}.
++ *
++ * When setting this property the corresponding minimum value is adjusted if
++ * necessary, to ensure that the range remains valid.
++ */
++ void QHeightMapSurfaceDataProxy::setMaxZValue(float max)
++ {
++ Q_D(QHeightMapSurfaceDataProxy);
++ d->setMaxZValue(max);
++ }
++
++ float QHeightMapSurfaceDataProxy::maxZValue() const
++ {
++ Q_D(const QHeightMapSurfaceDataProxy);
++ return d->m_maxZValue;
++ }
++
++ /*!
++ * \property QHeightMapSurfaceDataProxy::minYValue
++ *
++ * \brief The minimum Y value for the generated surface points.
++ *
++ * Defaults to \c{0.0}.
++ *
++ * When setting this property the corresponding maximum value is adjusted if
++ * necessary, to ensure that the range remains valid.
++ *
++ * \sa autoScaleY
++ */
++ void QHeightMapSurfaceDataProxy::setMinYValue(float min)
++ {
++ Q_D(QHeightMapSurfaceDataProxy);
++ d->setMinYValue(min);
++ }
++
++ float QHeightMapSurfaceDataProxy::minYValue() const
++ {
++ Q_D(const QHeightMapSurfaceDataProxy);
++ return d->m_minYValue;
++ }
++
++ /*!
++ * \property QHeightMapSurfaceDataProxy::maxYValue
++ *
++ * \brief The maximum Y value for the generated surface points.
++ *
++ * Defaults to \c{10.0}.
++ *
++ * When setting this property the corresponding minimum value is adjusted if
++ * necessary, to ensure that the range remains valid.
++ *
++ * \sa autoScaleY
++ */
++ void QHeightMapSurfaceDataProxy::setMaxYValue(float max)
++ {
++ Q_D(QHeightMapSurfaceDataProxy);
++ d->setMaxYValue(max);
++ }
++
++ float QHeightMapSurfaceDataProxy::maxYValue() const
++ {
++ Q_D(const QHeightMapSurfaceDataProxy);
++ return d->m_maxYValue;
++ }
++
++ /*!
++ * \property QHeightMapSurfaceDataProxy::autoScaleY
++ *
++ * \brief Scale height values to Y-axis.
++ *
++ * Defaults to \c{false}.
++ *
++ * When this property is set to \c{true},
++ * the height values are scaled to fit on the Y-axis between minYValue and
++ * maxYValue.
++ *
++ * \sa minYValue, maxYValue
++ */
++ void QHeightMapSurfaceDataProxy::setAutoScaleY(bool enabled)
++ {
++ Q_D(QHeightMapSurfaceDataProxy);
++ d->setAutoScaleY(enabled);
++ }
++
++ bool QHeightMapSurfaceDataProxy::autoScaleY() const
++ {
++ Q_D(const QHeightMapSurfaceDataProxy);
++ return d->m_autoScaleY;
++ }
++
++ /*!
++ * \internal
++ */
++ void QHeightMapSurfaceDataProxy::handlePendingResolve()
++ {
++ Q_D(QHeightMapSurfaceDataProxy);
++ d->handlePendingResolve();
++ }
++
++ // QHeightMapSurfaceDataProxyPrivate
++
++ QHeightMapSurfaceDataProxyPrivate::QHeightMapSurfaceDataProxyPrivate()
++ : m_minXValue(defaultMinValue)
++ , m_maxXValue(defaultMaxValue)
++ , m_minZValue(defaultMinValue)
++ , m_maxZValue(defaultMaxValue)
++ , m_minYValue(defaultMinValue)
++ , m_maxYValue(defaultMaxValue)
++ , m_autoScaleY(false)
++ {
++ m_resolveTimer.setSingleShot(true);
++ }
++
++ QHeightMapSurfaceDataProxyPrivate::~QHeightMapSurfaceDataProxyPrivate() {}
++
++ void QHeightMapSurfaceDataProxyPrivate::setValueRanges(float minX,
++ float maxX,
++ float minZ,
++ float maxZ)
++ {
++ Q_Q(QHeightMapSurfaceDataProxy);
++ bool minXChanged = false;
++ bool maxXChanged = false;
++ bool minZChanged = false;
++ bool maxZChanged = false;
++ if (m_minXValue != minX) {
++ m_minXValue = minX;
++ minXChanged = true;
++ }
++ if (m_minZValue != minZ) {
++ m_minZValue = minZ;
++ minZChanged = true;
++ }
++ if (m_maxXValue != maxX || minX >= maxX) {
++ if (minX >= maxX) {
++ m_maxXValue = minX + 1.0f;
++ qCWarning(lcProperties3D, "%s tried to set invalid range for X value range."
++ " Range automatically adjusted to a valid one: %f - %f --> %f - %f",
++ qUtf8Printable(QLatin1String(__FUNCTION__)),
++ minX,
++ maxX,
++ m_minXValue,
++ m_maxXValue);
++ } else {
++ m_maxXValue = maxX;
++ }
++ maxXChanged = true;
++ }
++ if (m_maxZValue != maxZ || minZ >= maxZ) {
++ if (minZ >= maxZ) {
++ m_maxZValue = minZ + 1.0f;
++ qCWarning(lcProperties3D, "%s tried to set invalid range for Z value range."
++ " Range automatically adjusted to a valid one: %f - %f --> %f - %f",
++ qUtf8Printable(QLatin1String(__FUNCTION__)),
++ minZ,
++ maxZ,
++ m_minZValue,
++ m_maxZValue);
++ } else {
++ m_maxZValue = maxZ;
++ }
++ maxZChanged = true;
++ }
++
++ if (minXChanged)
++ emit q->minXValueChanged(m_minXValue);
++ if (minZChanged)
++ emit q->minZValueChanged(m_minZValue);
++ if (maxXChanged)
++ emit q->maxXValueChanged(m_maxXValue);
++ if (maxZChanged)
++ emit q->maxZValueChanged(m_maxZValue);
++
++ if ((minXChanged || minZChanged || maxXChanged || maxZChanged) && !m_resolveTimer.isActive())
++ m_resolveTimer.start(0);
++ }
++
++ void QHeightMapSurfaceDataProxyPrivate::setMinXValue(float min)
++ {
++ Q_Q(QHeightMapSurfaceDataProxy);
++ if (min != m_minXValue) {
++ bool maxChanged = false;
++ if (min >= m_maxXValue) {
++ float oldMax = m_maxXValue;
++ m_maxXValue = min + 1.0f;
++ qCWarning(lcProperties3D, "%s tried to set minimum X to equal or larger"
++ " than maximum X for value "
++ "range. Maximum automatically adjusted to a valid one: %f --> %f",
++ qUtf8Printable(QLatin1String(__func__)),
++ oldMax,
++ m_maxXValue);
++ maxChanged = true;
++ }
++ m_minXValue = min;
++ emit q->minXValueChanged(m_minXValue);
++ if (maxChanged)
++ emit q->maxXValueChanged(m_maxXValue);
++
++ if (!m_resolveTimer.isActive())
++ m_resolveTimer.start(0);
++ }
++ }
++
++ void QHeightMapSurfaceDataProxyPrivate::setMaxXValue(float max)
++ {
++ Q_Q(QHeightMapSurfaceDataProxy);
++ if (m_maxXValue != max) {
++ bool minChanged = false;
++ if (max <= m_minXValue) {
++ float oldMin = m_minXValue;
++ m_minXValue = max - 1.0f;
++ qCWarning(lcProperties3D, "%s tried to set maximum X to equal or smaller than minimum X"
++ " for value range. Minimum automatically adjusted to a valid one: %f --> %f",
++ qUtf8Printable(QLatin1String(__func__)),
++ oldMin,
++ m_minXValue);
++ minChanged = true;
++ }
++ m_maxXValue = max;
++ emit q->maxXValueChanged(m_maxXValue);
++ if (minChanged)
++ emit q->minXValueChanged(m_minXValue);
++
++ if (!m_resolveTimer.isActive())
++ m_resolveTimer.start(0);
++ }
++ }
++
++ void QHeightMapSurfaceDataProxyPrivate::setMinZValue(float min)
++ {
++ Q_Q(QHeightMapSurfaceDataProxy);
++ if (min != m_minZValue) {
++ bool maxChanged = false;
++ if (min >= m_maxZValue) {
++ float oldMax = m_maxZValue;
++ m_maxZValue = min + 1.0f;
++ qCWarning(lcProperties3D, "%s tried to set minimum Z to equal or larger than maximum Z"
++ " for value range. Maximum automatically adjusted to a valid one: %f --> %f",
++ qUtf8Printable(QLatin1String(__func__)),
++ oldMax,
++ m_maxZValue);
++ maxChanged = true;
++ }
++ m_minZValue = min;
++ emit q->minZValueChanged(m_minZValue);
++ if (maxChanged)
++ emit q->maxZValueChanged(m_maxZValue);
++
++ if (!m_resolveTimer.isActive())
++ m_resolveTimer.start(0);
++ }
++ }
++
++ void QHeightMapSurfaceDataProxyPrivate::setMaxZValue(float max)
++ {
++ Q_Q(QHeightMapSurfaceDataProxy);
++ if (m_maxZValue != max) {
++ bool minChanged = false;
++ if (max <= m_minZValue) {
++ float oldMin = m_minZValue;
++ m_minZValue = max - 1.0f;
++ qCWarning(lcProperties3D, "%s tried to set maximum Z to equal or smaller than minimum Z"
++ " for value range. Minimum automatically adjusted to a valid one: %f --> %f",
++ qUtf8Printable(QLatin1String(__func__)),
++ oldMin,
++ m_minZValue);
++ minChanged = true;
++ }
++ m_maxZValue = max;
++ emit q->maxZValueChanged(m_maxZValue);
++ if (minChanged)
++ emit q->minZValueChanged(m_minZValue);
++
++ if (!m_resolveTimer.isActive())
++ m_resolveTimer.start(0);
++ }
++ }
++
++ void QHeightMapSurfaceDataProxyPrivate::setMinYValue(float min)
++ {
++ Q_Q(QHeightMapSurfaceDataProxy);
++ if (m_minYValue != min) {
++ bool maxChanged = false;
++ if (min >= m_maxYValue) {
++ float oldMax = m_maxYValue;
++ m_maxYValue = min + 1.0f;
++ qCWarning(lcProperties3D, "%s tried to set minimum Y to equal or larger than maximum Y"
++ " for value range. Maximum automatically adjusted to a valid one: %f --> %f",
++ qUtf8Printable(QLatin1String(__func__)),
++ oldMax,
++ m_maxYValue);
++ maxChanged = true;
++ }
++ m_minYValue = min;
++ emit q->minYValueChanged(m_minYValue);
++ if (maxChanged)
++ emit q->maxYValueChanged(m_maxYValue);
++
++ if (!m_resolveTimer.isActive())
++ m_resolveTimer.start(0);
++ }
++ }
++
++ void QHeightMapSurfaceDataProxyPrivate::setMaxYValue(float max)
++ {
++ Q_Q(QHeightMapSurfaceDataProxy);
++ if (m_maxYValue != max) {
++ bool minChanged = false;
++ if (max <= m_minYValue) {
++ float oldMin = m_minYValue;
++ m_minYValue = max - 1.0f;
++ qCWarning(lcProperties3D, "%s tried to set maximum Y to equal or smaller than minimum Y"
++ " for value range. Minimum automatically adjusted to a valid one: %f --> %f",
++ qUtf8Printable(QLatin1String(__func__)),
++ oldMin,
++ m_minYValue);
++ minChanged = true;
++ }
++ m_maxYValue = max;
++ emit q->maxYValueChanged(m_maxYValue);
++ if (minChanged)
++ emit q->minYValueChanged(m_minYValue);
++
++ if (!m_resolveTimer.isActive())
++ m_resolveTimer.start(0);
++ }
++ }
++
++ void QHeightMapSurfaceDataProxyPrivate::setAutoScaleY(bool enabled)
++ {
++ Q_Q(QHeightMapSurfaceDataProxy);
++ if (enabled == m_autoScaleY) {
++ qCDebug(lcProperties3D) << __FUNCTION__
++ << "value is already set to:" << enabled;
++ return;
++ }
++
++ m_autoScaleY = enabled;
++ emit q->autoScaleYChanged(m_autoScaleY);
++
++ if (!m_resolveTimer.isActive())
++ m_resolveTimer.start(0);
++ }
++
++ void QHeightMapSurfaceDataProxyPrivate::handlePendingResolve()
++ {
++ Q_Q(QHeightMapSurfaceDataProxy);
++ QImage heightImage = m_heightMap;
++ int bytesInChannel = 1;
++ float yMul = 1.0f / UINT8_MAX;
++
++ bool is16bit = (heightImage.format() == QImage::Format_RGBX64
++ || heightImage.format() == QImage::Format_RGBA64
++ || heightImage.format() == QImage::Format_RGBA64_Premultiplied
++ || heightImage.format() == QImage::Format_Grayscale16);
++
++ Q_TRACE(QGraphs3DHeightMapSurfaceDataProxHandlePendingResolve_entry, is16bit);
++ // Convert to RGB32 to be sure we're reading the right bytes
++ if (is16bit) {
++ if (heightImage.format() != QImage::Format_RGBX64)
++ heightImage = heightImage.convertToFormat(QImage::Format_RGBX64);
++
++ bytesInChannel = 2;
++ yMul = 1.0f / UINT16_MAX;
++ } else if (heightImage.format() != QImage::Format_RGB32) {
++ heightImage = heightImage.convertToFormat(QImage::Format_RGB32);
++ }
++
++ uchar *bits = heightImage.bits();
++
++ int imageHeight = heightImage.height();
++ int imageWidth = heightImage.width();
++ int bitCount = imageWidth * 4 * (imageHeight - 1) * bytesInChannel;
++ int widthBits = imageWidth * 4 * bytesInChannel;
++ float height = 0;
++
++ // Do not recreate array if dimensions have not changed
++ QSurfaceDataArray dataArray = q->series()->dataArray();
++ if (imageWidth != q->columnCount() || imageHeight != dataArray.size()) {
++ dataArray.clear();
++ dataArray.reserve(imageHeight);
++ for (int i = 0; i < imageHeight; i++) {
++ QSurfaceDataRow newProxyRow(imageWidth);
++ dataArray.append(newProxyRow);
++ }
++ }
++ yMul *= m_maxYValue - m_minYValue;
++ float xMul = (m_maxXValue - m_minXValue) / float(imageWidth - 1);
++ float zMul = (m_maxZValue - m_minZValue) / float(imageHeight - 1);
++
++ // Last row and column are explicitly set to max values, as relying
++ // on multiplier can cause rounding errors, resulting in the value being
++ // slightly over the specified maximum, which in turn can lead to it not
++ // getting rendered.
++ int lastRow = imageHeight - 1;
++ int lastCol = imageWidth - 1;
++ if (heightImage.isGrayscale()) {
++ // Grayscale, it's enough to read Red byte
++ for (int i = 0; i < imageHeight; i++, bitCount -= widthBits) {
++ QSurfaceDataRow &newRow = dataArray[i];
++ float zVal;
++ if (i == lastRow)
++ zVal = m_maxZValue;
++ else
++ zVal = (float(i) * zMul) + m_minZValue;
++ int j = 0;
++ float yVal = 0;
++ uchar *pixelptr;
++ for (; j < lastCol; j++) {
++ pixelptr = (uchar *) (bits + bitCount + (j * 4 * bytesInChannel));
++ if (!m_autoScaleY)
++ yVal = *pixelptr;
++ else
++ yVal = float(*pixelptr) * yMul + m_minYValue;
++ newRow[j].setPosition(QVector3D((float(j) * xMul) + m_minXValue, yVal, zVal));
++ }
++ newRow[j].setPosition(QVector3D(m_maxXValue, yVal, zVal));
++ }
++ } else {
++ // Not grayscale, we'll need to calculate height from RGB
++ for (int i = 0; i < imageHeight; i++, bitCount -= widthBits) {
++ QSurfaceDataRow &newRow = dataArray[i];
++ float zVal;
++ if (i == lastRow)
++ zVal = m_maxZValue;
++ else
++ zVal = (float(i) * zMul) + m_minZValue;
++ int j = 0;
++ float yVal = 0;
++ for (; j < lastCol; j++) {
++ int nextpixel = j * 4 * bytesInChannel;
++ uchar *pixelptr = (uchar *) (bits + bitCount + nextpixel);
++ if (is16bit) {
++ height = (float(*((ushort *) pixelptr)) + float(*(((ushort *) pixelptr) + 1))
++ + float(*(((ushort *) pixelptr) + 2)));
++ } else {
++ height = (float(*pixelptr) + float(*(pixelptr + 1)) + float(*(pixelptr + 2)));
++ }
++ if (!m_autoScaleY)
++ yVal = height / 3.0f;
++ else
++ yVal = (height / 3.0f * yMul) + m_minYValue;
++
++ newRow[j].setPosition(QVector3D((float(j) * xMul) + m_minXValue, yVal, zVal));
++ }
++ newRow[j].setPosition(QVector3D(m_maxXValue, yVal, zVal));
++ }
++ }
++
++ Q_TRACE(QGraphs3DHeightMapSurfaceDataProxHandlePendingResolve_exit);
++ q->resetArray(dataArray);
++ emit q->heightMapChanged(m_heightMap);
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qheightmapsurfacedataproxy.cpp"