1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt Data Visualization module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 or (at your option) any later version
20** approved by the KDE Free Qt Foundation. The licenses are as published by
21** the Free Software Foundation and appearing in the file LICENSE.GPL3
22** included in the packaging of this file. Please review the following
23** information to ensure the GNU General Public License requirements will
24** be met: https://www.gnu.org/licenses/gpl-3.0.html.
25**
26** $QT_END_LICENSE$
27**
28****************************************************************************/
29
30#include "qheightmapsurfacedataproxy_p.h"
31
32QT_BEGIN_NAMESPACE_DATAVISUALIZATION
33
34// Default ranges correspond value axis defaults
35const float defaultMinValue = 0.0f;
36const float defaultMaxValue = 10.0f;
37
38/*!
39 * \class QHeightMapSurfaceDataProxy
40 * \inmodule QtDataVisualization
41 * \brief Base proxy class for Q3DSurface.
42 * \since QtDataVisualization 1.0
43 *
44 * QHeightMapSurfaceDataProxy takes care of surface related height map data handling. It provides a
45 * way to give a height map to be visualized as a surface plot.
46 *
47 * Since height maps do not contain values for X or Z axes, those values need to be given
48 * separately using minXValue, maxXValue, minZValue, and maxZValue properties. X-value corresponds
49 * to image horizontal direction and Z-value to the vertical. Setting any of these
50 * properties triggers asynchronous re-resolving of any existing height map.
51 *
52 * \sa QSurfaceDataProxy, {Qt Data Visualization Data Handling}
53 */
54
55/*!
56 * \qmltype HeightMapSurfaceDataProxy
57 * \inqmlmodule QtDataVisualization
58 * \since QtDataVisualization 1.0
59 * \ingroup datavisualization_qml
60 * \instantiates QHeightMapSurfaceDataProxy
61 * \inherits SurfaceDataProxy
62 * \brief Base proxy type for Surface3D.
63 *
64 * HeightMapSurfaceDataProxy takes care of surface related height map data handling. It provides a
65 * way to give a height map to be visualized as a surface plot.
66 *
67 * For more complete description, see QHeightMapSurfaceDataProxy.
68 *
69 * \sa {Qt Data Visualization Data Handling}
70 */
71
72/*!
73 * \qmlproperty string HeightMapSurfaceDataProxy::heightMapFile
74 *
75 * A file with a height map image to be visualized. Setting this property replaces current data
76 * with height map data.
77 *
78 * There are several formats the image file can be given in, but if it is not in a directly usable
79 * format, a conversion is made.
80 *
81 * \note If the result seems wrong, the automatic conversion failed
82 * and you should try converting the image yourself before setting it. Preferred format is
83 * QImage::Format_RGB32 in grayscale.
84 *
85 * The height of the image is read from the red component of the pixels if the image is in grayscale,
86 * otherwise it is an average calculated from red, green and blue components of the pixels. Using
87 * grayscale images may improve data conversion speed for large images.
88 *
89 * Since height maps do not contain values for X or Z axes, those values need to be given
90 * separately using minXValue, maxXValue, minZValue, and maxZValue properties. X-value corresponds
91 * to image horizontal direction and Z-value to the vertical. Setting any of these
92 * properties triggers asynchronous re-resolving of any existing height map.
93 *
94 * Not recommended formats: all mono formats (for example QImage::Format_Mono).
95 */
96
97/*!
98 * \qmlproperty real HeightMapSurfaceDataProxy::minXValue
99 *
100 * The minimum X value for the generated surface points. Defaults to \c{0.0}.
101 * When setting this property the corresponding maximum value is adjusted if necessary,
102 * to ensure that the range remains valid.
103 */
104
105/*!
106 * \qmlproperty real HeightMapSurfaceDataProxy::maxXValue
107 *
108 * The maximum X value for the generated surface points. Defaults to \c{10.0}.
109 * When setting this property the corresponding minimum value is adjusted if necessary,
110 * to ensure that the range remains valid.
111 */
112
113/*!
114 * \qmlproperty real HeightMapSurfaceDataProxy::minZValue
115 *
116 * The minimum Z value for the generated surface points. Defaults to \c{0.0}.
117 * When setting this property the corresponding maximum value is adjusted if necessary,
118 * to ensure that the range remains valid.
119 */
120
121/*!
122 * \qmlproperty real HeightMapSurfaceDataProxy::maxZValue
123 *
124 * The maximum Z value for the generated surface points. Defaults to \c{10.0}.
125 * When setting this property the corresponding minimum value is adjusted if necessary,
126 * to ensure that the range remains valid.
127 */
128
129/*!
130 * Constructs QHeightMapSurfaceDataProxy with the given \a parent.
131 */
132QHeightMapSurfaceDataProxy::QHeightMapSurfaceDataProxy(QObject *parent) :
133 QSurfaceDataProxy(new QHeightMapSurfaceDataProxyPrivate(this), parent)
134{
135}
136
137/*!
138 * Constructs QHeightMapSurfaceDataProxy with the given \a image and \a parent. Height map is set
139 * by calling setHeightMap() with \a image.
140 *
141 * \sa heightMap
142 */
143QHeightMapSurfaceDataProxy::QHeightMapSurfaceDataProxy(const QImage &image, QObject *parent) :
144 QSurfaceDataProxy(new QHeightMapSurfaceDataProxyPrivate(this), parent)
145{
146 setHeightMap(image);
147}
148
149/*!
150 * Constructs QHeightMapSurfaceDataProxy from the given image \a filename and \a parent. Height map is set
151 * by calling setHeightMapFile() with \a filename.
152 *
153 * \sa heightMapFile
154 */
155QHeightMapSurfaceDataProxy::QHeightMapSurfaceDataProxy(const QString &filename, QObject *parent) :
156 QSurfaceDataProxy(new QHeightMapSurfaceDataProxyPrivate(this), parent)
157{
158 setHeightMapFile(filename);
159}
160
161/*!
162 * \internal
163 */
164QHeightMapSurfaceDataProxy::QHeightMapSurfaceDataProxy(
165 QHeightMapSurfaceDataProxyPrivate *d, QObject *parent) :
166 QSurfaceDataProxy(d, parent)
167{
168}
169
170/*!
171 * Destroys QHeightMapSurfaceDataProxy.
172 */
173QHeightMapSurfaceDataProxy::~QHeightMapSurfaceDataProxy()
174{
175}
176
177/*!
178 * \property QHeightMapSurfaceDataProxy::heightMap
179 *
180 * \brief The height map image to be visualized.
181 */
182
183/*!
184 * Replaces current data with the height map data specified by \a image.
185 *
186 * There are several formats the \a image can be given in, but if it is not in a directly usable
187 * format, a conversion is made.
188 *
189 * \note If the result seems wrong, the automatic conversion failed
190 * and you should try converting the \a image yourself before setting it. Preferred format is
191 * QImage::Format_RGB32 in grayscale.
192 *
193 * The height of the \a image is read from the red component of the pixels if the \a image is in
194 * grayscale, otherwise it is an average calculated from red, green, and blue components of the
195 * pixels. Using grayscale images may improve data conversion speed for large images.
196 *
197 * Not recommended formats: all mono formats (for example QImage::Format_Mono).
198 *
199 * The height map is resolved asynchronously. QSurfaceDataProxy::arrayReset() is emitted when the
200 * data has been resolved.
201 */
202void QHeightMapSurfaceDataProxy::setHeightMap(const QImage &image)
203{
204 dptr()->m_heightMap = image;
205
206 // We do resolving asynchronously to make qml onArrayReset handlers actually get the initial reset
207 if (!dptr()->m_resolveTimer.isActive())
208 dptr()->m_resolveTimer.start(msec: 0);
209}
210
211QImage QHeightMapSurfaceDataProxy::heightMap() const
212{
213 return dptrc()->m_heightMap;
214}
215
216/*!
217 * \property QHeightMapSurfaceDataProxy::heightMapFile
218 *
219 * \brief The name of the file with a height map image to be visualized.
220 */
221
222/*!
223 * Replaces current data with height map data from the file specified by
224 * \a filename.
225 *
226 * \sa heightMap
227 */
228void QHeightMapSurfaceDataProxy::setHeightMapFile(const QString &filename)
229{
230 dptr()->m_heightMapFile = filename;
231 setHeightMap(QImage(filename));
232 emit heightMapFileChanged(filename);
233}
234
235QString QHeightMapSurfaceDataProxy::heightMapFile() const
236{
237 return dptrc()->m_heightMapFile;
238}
239
240/*!
241 * A convenience function for setting all minimum (\a minX and \a minZ) and maximum
242 * (\a maxX and \a maxZ) values at the same time. The minimum values must be smaller than the
243 * corresponding maximum value. Otherwise the values get adjusted so that they are valid.
244 */
245void QHeightMapSurfaceDataProxy::setValueRanges(float minX, float maxX, float minZ, float maxZ)
246{
247 dptr()->setValueRanges(minX, maxX, minZ, maxZ);
248}
249
250/*!
251 * \property QHeightMapSurfaceDataProxy::minXValue
252 *
253 * \brief The minimum X value for the generated surface points.
254 *
255 * Defaults to \c{0.0}.
256 *
257 * When setting this property the corresponding maximum value is adjusted if necessary,
258 * to ensure that the range remains valid.
259 */
260void QHeightMapSurfaceDataProxy::setMinXValue(float min)
261{
262 dptr()->setMinXValue(min);
263}
264
265float QHeightMapSurfaceDataProxy::minXValue() const
266{
267 return dptrc()->m_minXValue;
268}
269
270/*!
271 * \property QHeightMapSurfaceDataProxy::maxXValue
272 *
273 * \brief The maximum X value for the generated surface points.
274 *
275 * Defaults to \c{10.0}.
276 *
277 * When setting this property the corresponding minimum value is adjusted if necessary,
278 * to ensure that the range remains valid.
279 */
280void QHeightMapSurfaceDataProxy::setMaxXValue(float max)
281{
282 dptr()->setMaxXValue(max);
283}
284
285float QHeightMapSurfaceDataProxy::maxXValue() const
286{
287 return dptrc()->m_maxXValue;
288}
289
290/*!
291 * \property QHeightMapSurfaceDataProxy::minZValue
292 *
293 * \brief The minimum Z value for the generated surface points.
294 *
295 * Defaults to \c{0.0}.
296 *
297 * When setting this property the corresponding maximum value is adjusted if necessary,
298 * to ensure that the range remains valid.
299 */
300void QHeightMapSurfaceDataProxy::setMinZValue(float min)
301{
302 dptr()->setMinZValue(min);
303}
304
305float QHeightMapSurfaceDataProxy::minZValue() const
306{
307 return dptrc()->m_minZValue;
308}
309
310/*!
311 * \property QHeightMapSurfaceDataProxy::maxZValue
312 *
313 * \brief The maximum Z value for the generated surface points.
314 *
315 * Defaults to \c{10.0}.
316 *
317 * When setting this property the corresponding minimum value is adjusted if necessary,
318 * to ensure that the range remains valid.
319 */
320void QHeightMapSurfaceDataProxy::setMaxZValue(float max)
321{
322 dptr()->setMaxZValue(max);
323}
324
325float QHeightMapSurfaceDataProxy::maxZValue() const
326{
327 return dptrc()->m_maxZValue;
328}
329
330/*!
331 * \internal
332 */
333QHeightMapSurfaceDataProxyPrivate *QHeightMapSurfaceDataProxy::dptr()
334{
335 return static_cast<QHeightMapSurfaceDataProxyPrivate *>(d_ptr.data());
336}
337
338/*!
339 * \internal
340 */
341const QHeightMapSurfaceDataProxyPrivate *QHeightMapSurfaceDataProxy::dptrc() const
342{
343 return static_cast<const QHeightMapSurfaceDataProxyPrivate *>(d_ptr.data());
344}
345
346// QHeightMapSurfaceDataProxyPrivate
347
348QHeightMapSurfaceDataProxyPrivate::QHeightMapSurfaceDataProxyPrivate(QHeightMapSurfaceDataProxy *q)
349 : QSurfaceDataProxyPrivate(q),
350 m_minXValue(defaultMinValue),
351 m_maxXValue(defaultMaxValue),
352 m_minZValue(defaultMinValue),
353 m_maxZValue(defaultMaxValue)
354{
355 m_resolveTimer.setSingleShot(true);
356 QObject::connect(sender: &m_resolveTimer, signal: &QTimer::timeout,
357 receiver: this, slot: &QHeightMapSurfaceDataProxyPrivate::handlePendingResolve);
358}
359
360QHeightMapSurfaceDataProxyPrivate::~QHeightMapSurfaceDataProxyPrivate()
361{
362}
363
364QHeightMapSurfaceDataProxy *QHeightMapSurfaceDataProxyPrivate::qptr()
365{
366 return static_cast<QHeightMapSurfaceDataProxy *>(q_ptr);
367}
368
369void QHeightMapSurfaceDataProxyPrivate::setValueRanges(float minX, float maxX,
370 float minZ, float maxZ)
371{
372 bool minXChanged = false;
373 bool maxXChanged = false;
374 bool minZChanged = false;
375 bool maxZChanged = false;
376 if (m_minXValue != minX) {
377 m_minXValue = minX;
378 minXChanged = true;
379 }
380 if (m_minZValue != minZ) {
381 m_minZValue = minZ;
382 minZChanged = true;
383 }
384 if (m_maxXValue != maxX || minX >= maxX) {
385 if (minX >= maxX) {
386 m_maxXValue = minX + 1.0f;
387 qWarning() << "Warning: Tried to set invalid range for X value range."
388 " Range automatically adjusted to a valid one:"
389 << minX << "-" << maxX << "-->" << m_minXValue << "-" << m_maxXValue;
390 } else {
391 m_maxXValue = maxX;
392 }
393 maxXChanged = true;
394 }
395 if (m_maxZValue != maxZ || minZ >= maxZ) {
396 if (minZ >= maxZ) {
397 m_maxZValue = minZ + 1.0f;
398 qWarning() << "Warning: Tried to set invalid range for Z value range."
399 " Range automatically adjusted to a valid one:"
400 << minZ << "-" << maxZ << "-->" << m_minZValue << "-" << m_maxZValue;
401 } else {
402 m_maxZValue = maxZ;
403 }
404 maxZChanged = true;
405 }
406
407 if (minXChanged)
408 emit qptr()->minXValueChanged(value: m_minXValue);
409 if (minZChanged)
410 emit qptr()->minZValueChanged(value: m_minZValue);
411 if (maxXChanged)
412 emit qptr()->maxXValueChanged(value: m_maxXValue);
413 if (maxZChanged)
414 emit qptr()->maxZValueChanged(value: m_maxZValue);
415
416 if ((minXChanged || minZChanged || maxXChanged || maxZChanged) && !m_resolveTimer.isActive())
417 m_resolveTimer.start(msec: 0);
418}
419
420void QHeightMapSurfaceDataProxyPrivate::setMinXValue(float min)
421{
422 if (min != m_minXValue) {
423 bool maxChanged = false;
424 if (min >= m_maxXValue) {
425 float oldMax = m_maxXValue;
426 m_maxXValue = min + 1.0f;
427 qWarning() << "Warning: Tried to set minimum X to equal or larger than maximum X for"
428 " value range. Maximum automatically adjusted to a valid one:"
429 << oldMax << "-->" << m_maxXValue;
430 maxChanged = true;
431 }
432 m_minXValue = min;
433 emit qptr()->minXValueChanged(value: m_minXValue);
434 if (maxChanged)
435 emit qptr()->maxXValueChanged(value: m_maxXValue);
436
437 if (!m_resolveTimer.isActive())
438 m_resolveTimer.start(msec: 0);
439 }
440}
441
442void QHeightMapSurfaceDataProxyPrivate::setMaxXValue(float max)
443{
444 if (m_maxXValue != max) {
445 bool minChanged = false;
446 if (max <= m_minXValue) {
447 float oldMin = m_minXValue;
448 m_minXValue = max - 1.0f;
449 qWarning() << "Warning: Tried to set maximum X to equal or smaller than minimum X for"
450 " value range. Minimum automatically adjusted to a valid one:"
451 << oldMin << "-->" << m_minXValue;
452 minChanged = true;
453 }
454 m_maxXValue = max;
455 emit qptr()->maxXValueChanged(value: m_maxXValue);
456 if (minChanged)
457 emit qptr()->minXValueChanged(value: m_minXValue);
458
459 if (!m_resolveTimer.isActive())
460 m_resolveTimer.start(msec: 0);
461 }
462}
463
464void QHeightMapSurfaceDataProxyPrivate::setMinZValue(float min)
465{
466 if (min != m_minZValue) {
467 bool maxChanged = false;
468 if (min >= m_maxZValue) {
469 float oldMax = m_maxZValue;
470 m_maxZValue = min + 1.0f;
471 qWarning() << "Warning: Tried to set minimum Z to equal or larger than maximum Z for"
472 " value range. Maximum automatically adjusted to a valid one:"
473 << oldMax << "-->" << m_maxZValue;
474 maxChanged = true;
475 }
476 m_minZValue = min;
477 emit qptr()->minZValueChanged(value: m_minZValue);
478 if (maxChanged)
479 emit qptr()->maxZValueChanged(value: m_maxZValue);
480
481 if (!m_resolveTimer.isActive())
482 m_resolveTimer.start(msec: 0);
483 }
484}
485
486void QHeightMapSurfaceDataProxyPrivate::setMaxZValue(float max)
487{
488 if (m_maxZValue != max) {
489 bool minChanged = false;
490 if (max <= m_minZValue) {
491 float oldMin = m_minZValue;
492 m_minZValue = max - 1.0f;
493 qWarning() << "Warning: Tried to set maximum Z to equal or smaller than minimum Z for"
494 " value range. Minimum automatically adjusted to a valid one:"
495 << oldMin << "-->" << m_minZValue;
496 minChanged = true;
497 }
498 m_maxZValue = max;
499 emit qptr()->maxZValueChanged(value: m_maxZValue);
500 if (minChanged)
501 emit qptr()->minZValueChanged(value: m_minZValue);
502
503 if (!m_resolveTimer.isActive())
504 m_resolveTimer.start(msec: 0);
505 }
506}
507
508void QHeightMapSurfaceDataProxyPrivate::handlePendingResolve()
509{
510 QImage heightImage = m_heightMap;
511
512 // Convert to RGB32 to be sure we're reading the right bytes
513 if (heightImage.format() != QImage::Format_RGB32)
514 heightImage = heightImage.convertToFormat(f: QImage::Format_RGB32);
515
516 uchar *bits = heightImage.bits();
517
518 int imageHeight = heightImage.height();
519 int imageWidth = heightImage.width();
520 int bitCount = imageWidth * 4 * (imageHeight - 1);
521 int widthBits = imageWidth * 4;
522 float height = 0;
523
524 // Do not recreate array if dimensions have not changed
525 QSurfaceDataArray *dataArray = m_dataArray;
526 if (imageWidth != qptr()->columnCount() || imageHeight != dataArray->size()) {
527 dataArray = new QSurfaceDataArray;
528 dataArray->reserve(alloc: imageHeight);
529 for (int i = 0; i < imageHeight; i++) {
530 QSurfaceDataRow *newProxyRow = new QSurfaceDataRow(imageWidth);
531 dataArray->append(t: newProxyRow);
532 }
533 }
534
535 float xMul = (m_maxXValue - m_minXValue) / float(imageWidth - 1);
536 float zMul = (m_maxZValue - m_minZValue) / float(imageHeight - 1);
537
538 // Last row and column are explicitly set to max values, as relying
539 // on multiplier can cause rounding errors, resulting in the value being
540 // slightly over the specified maximum, which in turn can lead to it not
541 // getting rendered.
542 int lastRow = imageHeight - 1;
543 int lastCol = imageWidth - 1;
544
545 if (heightImage.isGrayscale()) {
546 // Grayscale, it's enough to read Red byte
547 for (int i = 0; i < imageHeight; i++, bitCount -= widthBits) {
548 QSurfaceDataRow &newRow = *dataArray->at(i);
549 float zVal;
550 if (i == lastRow)
551 zVal = m_maxZValue;
552 else
553 zVal = (float(i) * zMul) + m_minZValue;
554 int j = 0;
555 for (; j < lastCol; j++)
556 newRow[j].setPosition(QVector3D((float(j) * xMul) + m_minXValue,
557 float(bits[bitCount + (j * 4)]),
558 zVal));
559 newRow[j].setPosition(QVector3D(m_maxXValue,
560 float(bits[bitCount + (j * 4)]),
561 zVal));
562 }
563 } else {
564 // Not grayscale, we'll need to calculate height from RGB
565 for (int i = 0; i < imageHeight; i++, bitCount -= widthBits) {
566 QSurfaceDataRow &newRow = *dataArray->at(i);
567 float zVal;
568 if (i == lastRow)
569 zVal = m_maxZValue;
570 else
571 zVal = (float(i) * zMul) + m_minZValue;
572 int j = 0;
573 int nextpixel = 0;
574 for (; j < lastCol; j++) {
575 nextpixel = j * 4;
576 height = (float(bits[bitCount + nextpixel])
577 + float(bits[1 + bitCount + nextpixel])
578 + float(bits[2 + bitCount + nextpixel]));
579 newRow[j].setPosition(QVector3D((float(j) * xMul) + m_minXValue,
580 height / 3.0f,
581 zVal));
582 }
583 nextpixel = j * 4;
584 height = (float(bits[bitCount + nextpixel])
585 + float(bits[1 + bitCount + nextpixel])
586 + float(bits[2 + bitCount + nextpixel]));
587 newRow[j].setPosition(QVector3D(m_maxXValue,
588 height / 3.0f,
589 zVal));
590 }
591 }
592
593 qptr()->resetArray(newArray: dataArray);
594 emit qptr()->heightMapChanged(image: m_heightMap);
595}
596
597QT_END_NAMESPACE_DATAVISUALIZATION
598

source code of qtdatavis3d/src/datavisualization/data/qheightmapsurfacedataproxy.cpp