summaryrefslogtreecommitdiffstats
path: root/src/datavisualization/data/qheightmapsurfacedataproxy.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/datavisualization/data/qheightmapsurfacedataproxy.cpp')
-rw-r--r--src/datavisualization/data/qheightmapsurfacedataproxy.cpp489
1 files changed, 489 insertions, 0 deletions
diff --git a/src/datavisualization/data/qheightmapsurfacedataproxy.cpp b/src/datavisualization/data/qheightmapsurfacedataproxy.cpp
new file mode 100644
index 00000000..518e69eb
--- /dev/null
+++ b/src/datavisualization/data/qheightmapsurfacedataproxy.cpp
@@ -0,0 +1,489 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use contact form at http://qt.digia.com
+**
+** This file is part of the QtDataVisualization module.
+**
+** Licensees holding valid Qt Enterprise licenses may use this file in
+** accordance with the Qt Enterprise License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.digia.com
+**
+****************************************************************************/
+
+#include "qheightmapsurfacedataproxy_p.h"
+
+QT_DATAVISUALIZATION_BEGIN_NAMESPACE
+
+// Default ranges correspond value axis defaults
+const float defaultMinValue = 0.0f;
+const float defaultMaxValue = 10.0f;
+
+/*!
+ * \class QHeightMapSurfaceDataProxy
+ * \inmodule QtDataVisualization
+ * \brief Base proxy class for Q3DSurface.
+ * \since 1.0.0
+ *
+ * QHeightMapSurfaceDataProxy takes care of surface related height map data handling. It provides a
+ * way for giving the surface plot a height map to be visualized.
+ *
+ * Since height maps do not contain values for X or Z axes, those values need to be given
+ * separately using minXValue, maxXValue, minZValue, and maxZValue properties. X-value corresponds
+ * to image horizontal direction and Z-value to the vertical. Setting any of these
+ * properties triggers asynchronous re-resolving of any existing height map.
+ *
+ * \sa QSurfaceDataProxy, {Qt Data Visualization Data Handling}
+ */
+
+/*!
+ * \qmltype HeightMapSurfaceDataProxy
+ * \inqmlmodule com.digia.QtDataVisualization 1.0
+ * \since com.digia.QtDataVisualization 1.0
+ * \ingroup datavisualization_qml
+ * \instantiates QHeightMapSurfaceDataProxy
+ * \inherits SurfaceDataProxy
+ * \brief Base proxy type for Surface3D.
+ *
+ * HeightMapSurfaceDataProxy takes care of surface related height map data handling. It provides a
+ * way for giving the surface plot a height map to be visualized.
+ *
+ * For more complete description, see QHeightMapSurfaceDataProxy.
+ *
+ * \sa {Qt Data Visualization Data Handling}
+ */
+
+/*!
+ * \qmlproperty image HeightMapSurfaceDataProxy::heightMap
+ *
+ * A height map to be visualized. Setting this property replaces current data with height map data.
+ *
+ * There are several formats the 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 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 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, those values need to be given
+ * separately using minXValue, maxXValue, minZValue, and maxZValue properties. X-value corresponds
+ * to image horizontal direction and Z-value to the vertical. Setting any of these
+ * properties triggers asynchronous re-resolving of any existing height map.
+ *
+ * Not recommended formats: all mono formats (for example QImage::Format_Mono).
+ */
+
+/*!
+ * \qmlproperty string HeightMapSurfaceDataProxy::heightMapFile
+ *
+ * A file with a height map image to be visualized. Setting this property replaces current data
+ * with height map data.
+ *
+ * \sa heightMap
+ */
+
+/*!
+ * \qmlproperty qreal HeightMapSurfaceDataProxy::minXValue
+ *
+ * The minimum X value for the generated surface points.
+ * When setting this property the corresponding maximum value is adjusted if necessary,
+ * to ensure that the range remains valid.
+ */
+
+/*!
+ * \qmlproperty qreal HeightMapSurfaceDataProxy::maxXValue
+ *
+ * The maximum X value for the generated surface points.
+ * When setting this property the corresponding minimum value is adjusted if necessary,
+ * to ensure that the range remains valid.
+ */
+
+/*!
+ * \qmlproperty qreal HeightMapSurfaceDataProxy::minZValue
+ *
+ * The minimum Z value for the generated surface points.
+ * When setting this property the corresponding maximum value is adjusted if necessary,
+ * to ensure that the range remains valid.
+ */
+
+/*!
+ * \qmlproperty qreal HeightMapSurfaceDataProxy::maxZValue
+ *
+ * The maximum Z value for the generated surface points.
+ * When setting this property the corresponding minimum value is adjusted if necessary,
+ * to ensure that the range remains valid.
+ */
+
+/*!
+ * Constructs QHeightMapSurfaceDataProxy with the given \a parent.
+ */
+QHeightMapSurfaceDataProxy::QHeightMapSurfaceDataProxy(QObject *parent) :
+ QSurfaceDataProxy(new QHeightMapSurfaceDataProxyPrivate(this), parent)
+{
+}
+
+/*!
+ * Constructs QHeightMapSurfaceDataProxy with the given \a image and \a parent. Height map is set
+ * by calling setHeighMap() with \a image.
+ *
+ * \sa heightMap
+ */
+QHeightMapSurfaceDataProxy::QHeightMapSurfaceDataProxy(const QImage &image, QObject *parent) :
+ QSurfaceDataProxy(new QHeightMapSurfaceDataProxyPrivate(this), parent)
+{
+ setHeightMap(image);
+}
+
+/*!
+ * \internal
+ */
+QHeightMapSurfaceDataProxy::QHeightMapSurfaceDataProxy(
+ QHeightMapSurfaceDataProxyPrivate *d, QObject *parent) :
+ QSurfaceDataProxy(d, parent)
+{
+}
+
+/*!
+ * Destroys QHeightMapSurfaceDataProxy.
+ */
+QHeightMapSurfaceDataProxy::~QHeightMapSurfaceDataProxy()
+{
+}
+
+/*!
+ * \property QHeightMapSurfaceDataProxy::heightMap
+ *
+ * A height map to be visualized. Setting this property replaces current data with height map data.
+ *
+ * There are several formats the 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 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 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)
+{
+ dptr()->m_heightMap = image;
+
+ // We do resolving asynchronously to make qml onArrayReset handlers actually get the initial reset
+ if (!dptr()->m_resolveTimer.isActive())
+ dptr()->m_resolveTimer.start(0);
+}
+
+QImage QHeightMapSurfaceDataProxy::heightMap() const
+{
+ return dptrc()->m_heightMap;
+}
+
+/*!
+ * \property QHeightMapSurfaceDataProxy::heightMapFile
+ *
+ * A file with a height map image to be visualized. Setting this property replaces current data
+ * with height map data.
+ *
+ * \sa heightMap
+ */
+void QHeightMapSurfaceDataProxy::setHeightMapFile(const QString &filename)
+{
+ dptr()->m_heightMapFile = filename;
+ setHeightMap(QImage(filename));
+}
+
+QString QHeightMapSurfaceDataProxy::heightMapFile() const
+{
+ return dptrc()->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)
+{
+ dptr()->setValueRanges(minX, maxX, minZ, maxZ);
+}
+
+/*!
+ * \property QHeightMapSurfaceDataProxy::minXValue
+ *
+ * The minimum X value for the generated surface points.
+ * When setting this property the corresponding maximum value is adjusted if necessary,
+ * to ensure that the range remains valid.
+ */
+void QHeightMapSurfaceDataProxy::setMinXValue(float min)
+{
+ dptr()->setMinXValue(min);
+}
+
+float QHeightMapSurfaceDataProxy::minXValue() const
+{
+ return dptrc()->m_minXValue;
+}
+
+/*!
+ * \property QHeightMapSurfaceDataProxy::maxXValue
+ *
+ * The maximum X value for the generated surface points.
+ * When setting this property the corresponding minimum value is adjusted if necessary,
+ * to ensure that the range remains valid.
+ */
+void QHeightMapSurfaceDataProxy::setMaxXValue(float max)
+{
+ dptr()->setMaxXValue(max);
+}
+
+float QHeightMapSurfaceDataProxy::maxXValue() const
+{
+ return dptrc()->m_maxXValue;
+}
+
+/*!
+ * \property QHeightMapSurfaceDataProxy::minZValue
+ *
+ * The minimum Z value for the generated surface points.
+ * When setting this property the corresponding maximum value is adjusted if necessary,
+ * to ensure that the range remains valid.
+ */
+void QHeightMapSurfaceDataProxy::setMinZValue(float min)
+{
+ dptr()->setMinZValue(min);
+}
+
+float QHeightMapSurfaceDataProxy::minZValue() const
+{
+ return dptrc()->m_minZValue;
+}
+
+/*!
+ * \property QHeightMapSurfaceDataProxy::maxZValue
+ *
+ * The maximum Z value for the generated surface points.
+ * When setting this property the corresponding minimum value is adjusted if necessary,
+ * to ensure that the range remains valid.
+ */
+void QHeightMapSurfaceDataProxy::setMaxZValue(float max)
+{
+ dptr()->setMaxZValue(max);
+}
+
+float QHeightMapSurfaceDataProxy::maxZValue() const
+{
+ return dptrc()->m_maxZValue;
+}
+
+/*!
+ * \internal
+ */
+QHeightMapSurfaceDataProxyPrivate *QHeightMapSurfaceDataProxy::dptr()
+{
+ return static_cast<QHeightMapSurfaceDataProxyPrivate *>(d_ptr.data());
+}
+
+/*!
+ * \internal
+ */
+const QHeightMapSurfaceDataProxyPrivate *QHeightMapSurfaceDataProxy::dptrc() const
+{
+ return static_cast<const QHeightMapSurfaceDataProxyPrivate *>(d_ptr.data());
+}
+
+//
+// QHeightMapSurfaceDataProxyPrivate
+//
+
+QHeightMapSurfaceDataProxyPrivate::QHeightMapSurfaceDataProxyPrivate(QHeightMapSurfaceDataProxy *q)
+ : QSurfaceDataProxyPrivate(q),
+ m_minXValue(defaultMinValue),
+ m_maxXValue(defaultMaxValue),
+ m_minZValue(defaultMinValue),
+ m_maxZValue(defaultMaxValue)
+{
+ m_resolveTimer.setSingleShot(true);
+ QObject::connect(&m_resolveTimer, &QTimer::timeout,
+ this, &QHeightMapSurfaceDataProxyPrivate::handlePendingResolve);
+}
+
+QHeightMapSurfaceDataProxyPrivate::~QHeightMapSurfaceDataProxyPrivate()
+{
+}
+
+QHeightMapSurfaceDataProxy *QHeightMapSurfaceDataProxyPrivate::qptr()
+{
+ return static_cast<QHeightMapSurfaceDataProxy *>(q_ptr);
+}
+
+void QHeightMapSurfaceDataProxyPrivate::setValueRanges(float minX, float maxX, float minZ, float maxZ)
+{
+ bool changed = false;
+ if (m_minXValue != minX || m_minZValue != minZ) {
+ m_minXValue = minX;
+ m_minZValue = minZ;
+ changed = true;
+ }
+ if (m_maxXValue != maxX || minX >= maxX) {
+ if (minX >= maxX) {
+ m_maxXValue = minX + 1.0;
+ qWarning() << "Warning: Tried to set invalid range for X value range."
+ " Range automatically adjusted to a valid one:"
+ << minX << "-" << maxX << "-->" << m_minXValue << "-" << m_maxXValue;
+ } else {
+ m_maxXValue = maxX;
+ }
+ changed = true;
+ }
+ if (m_maxZValue != maxZ || minZ >= maxZ) {
+ if (minZ >= maxZ) {
+ m_maxZValue = minZ + 1.0;
+ qWarning() << "Warning: Tried to set invalid range for Z value range."
+ " Range automatically adjusted to a valid one:"
+ << minZ << "-" << maxZ << "-->" << m_minZValue << "-" << m_maxZValue;
+ } else {
+ m_maxZValue = maxZ;
+ }
+ changed = true;
+ }
+
+ if (changed && !m_resolveTimer.isActive())
+ m_resolveTimer.start(0);
+}
+
+void QHeightMapSurfaceDataProxyPrivate::setMinXValue(float min)
+{
+ if (min != m_minXValue) {
+ if (min >= m_maxXValue) {
+ qreal oldMax = m_maxXValue;
+ m_maxXValue = min + 1.0;
+ qWarning() << "Warning: Tried to set minimum X to equal or larger than maximum X for"
+ " value range. Maximum automatically adjusted to a valid one:"
+ << oldMax << "-->" << m_maxXValue;
+ }
+ m_minXValue = min;
+
+ if (!m_resolveTimer.isActive())
+ m_resolveTimer.start(0);
+ }
+}
+
+void QHeightMapSurfaceDataProxyPrivate::setMaxXValue(float max)
+{
+ if (m_maxXValue != max) {
+ if (max <= m_minXValue) {
+ qreal oldMin = m_minXValue;
+ m_minXValue = max - 1.0;
+ qWarning() << "Warning: Tried to set maximum X to equal or smaller than minimum X for"
+ " value range. Minimum automatically adjusted to a valid one:"
+ << oldMin << "-->" << m_minXValue;
+ }
+ m_maxXValue = max;
+
+ if (!m_resolveTimer.isActive())
+ m_resolveTimer.start(0);
+ }
+}
+
+void QHeightMapSurfaceDataProxyPrivate::setMinZValue(float min)
+{
+ if (min != m_minZValue) {
+ if (min >= m_maxZValue) {
+ qreal oldMax = m_maxZValue;
+ m_maxZValue = min + 1.0;
+ qWarning() << "Warning: Tried to set minimum Z to equal or larger than maximum Z for"
+ " value range. Maximum automatically adjusted to a valid one:"
+ << oldMax << "-->" << m_maxZValue;
+ }
+ m_minZValue = min;
+
+ if (!m_resolveTimer.isActive())
+ m_resolveTimer.start(0);
+ }
+}
+
+void QHeightMapSurfaceDataProxyPrivate::setMaxZValue(float max)
+{
+ if (m_maxZValue != max) {
+ if (max <= m_minZValue) {
+ qreal oldMin = m_minZValue;
+ m_minZValue = max - 1.0;
+ qWarning() << "Warning: Tried to set maximum Z to equal or smaller than minimum Z for"
+ " value range. Minimum automatically adjusted to a valid one:"
+ << oldMin << "-->" << m_minZValue;
+ }
+ m_maxZValue = max;
+
+ if (!m_resolveTimer.isActive())
+ m_resolveTimer.start(0);
+ }
+}
+
+void QHeightMapSurfaceDataProxyPrivate::handlePendingResolve()
+{
+ QImage heightImage = m_heightMap;
+ // Convert to RGB32 to be sure we're reading the right bytes
+ 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);
+ int widthBits = imageWidth * 4;
+ float height = 0;
+
+ // Do not recreate array if dimensions have not changed
+ QSurfaceDataArray *dataArray = m_dataArray;
+ if (imageWidth != qptr()->columnCount() || imageHeight != dataArray->size()) {
+ dataArray = new QSurfaceDataArray;
+ dataArray->reserve(imageHeight);
+ for (int i = 0; i < imageHeight; i++) {
+ QSurfaceDataRow *newProxyRow = new QSurfaceDataRow(imageWidth);
+ dataArray->append(newProxyRow);
+ }
+ }
+
+ float xMul = (m_maxXValue - m_minXValue) / float(imageWidth - 1);
+ float zMul = (m_maxZValue - m_minZValue) / float(imageHeight - 1);
+
+ if (heightImage.isGrayscale()) {
+ // Grayscale, it's enough to read Red byte
+ for (int i = 0; i < imageHeight; i++, bitCount -= widthBits) {
+ QSurfaceDataRow &newRow = *dataArray->at(i);
+ for (int j = 0; j < imageWidth; j++)
+ newRow[j].setPosition(QVector3D((float(j) * xMul) + m_minXValue, float(bits[bitCount + (j * 4)]),
+ (float(i) * zMul) + m_minZValue));
+ }
+ } else {
+ // Not grayscale, we'll need to calculate height from RGB
+ for (int i = 0; i < imageHeight; i++, bitCount -= widthBits) {
+ QSurfaceDataRow &newRow = *dataArray->at(i);
+ for (int j = 0; j < imageWidth; j++) {
+ int nextpixel = j * 4;
+ height = (float(bits[bitCount + nextpixel])
+ + float(bits[1 + bitCount + nextpixel])
+ + float(bits[2 + bitCount + nextpixel]));
+ newRow[j].setPosition(QVector3D((float(j) * xMul) + m_minXValue, height / 3.0f,
+ (float(i) * zMul) + m_minZValue));
+ }
+ }
+ }
+
+ qptr()->resetArray(dataArray);
+}
+
+QT_DATAVISUALIZATION_END_NAMESPACE