1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include <QtCore/qdebug.h>
5#include <QtCore/qfileinfo.h>
6#include "qheightmapsurfacedataproxy_p.h"
7#include "qsurface3dseries_p.h"
8
9QT_BEGIN_NAMESPACE
10
11// Default ranges correspond value axis defaults
12const float defaultMinValue = 0.0f;
13const float defaultMaxValue = 10.0f;
14
15/*!
16 * \class QHeightMapSurfaceDataProxy
17 * \inmodule QtGraphs
18 * \ingroup graphs_3D
19 * \brief Base proxy class for Q3DSurfaceWidgetItem.
20 *
21 * QHeightMapSurfaceDataProxy takes care of the processing of height map data
22 * related to surfaces. It provides the visualization of a height map as a
23 * surface plot.
24 *
25 * Since height maps do not contain values for X or Z axes, those values need to
26 * be given separately using the minXValue, maxXValue, minZValue, and maxZValue
27 * properties. The X-value corresponds to image horizontal direction and the Z-value
28 * to the vertical. Setting any of these properties triggers an asynchronous
29 * re-resolution of any existing height map.
30 *
31 * \sa QSurfaceDataProxy, {Qt Graphs Data Handling with 3D}
32 */
33
34/*!
35 * \qmltype HeightMapSurfaceDataProxy
36 * \inqmlmodule QtGraphs
37 * \ingroup graphs_qml_3D
38 * \nativetype QHeightMapSurfaceDataProxy
39 * \inherits SurfaceDataProxy
40 * \brief Base proxy type for Surface3D.
41 *
42 * QHeightMapSurfaceDataProxy takes care of the processing of height map data
43 * related to surfaces. It provides the visualization of a height map as a
44 * surface plot.
45 *
46 * For more complete description, see QHeightMapSurfaceDataProxy.
47 *
48 * \sa {Qt Graphs Data Handling with 3D}
49 */
50
51/*!
52 * \qmlproperty string HeightMapSurfaceDataProxy::heightMapFile
53 *
54 * A file with a height map image to be visualized. Setting this property
55 * replaces current data with height map data.
56 *
57 * There are several formats the image file can be given in, but if it is not in
58 * a directly usable format, a conversion is made.
59 *
60 * \note If the result seems wrong, the automatic conversion failed
61 * and you should try converting the image yourself before setting it. Preferred
62 * format is QImage::Format_RGB32 in grayscale.
63 *
64 * The height of the image is read from the red component of the pixels if the
65 * image is in grayscale. Otherwise, it is an average calculated from the red, green,
66 * and blue components of the pixels. Using grayscale images may improve data
67 * conversion speed for large images.
68 *
69 * Since height maps do not contain values for X or Z axes, these values need to
70 * be given separately using the minXValue, maxXValue, minZValue, and maxZValue
71 * properties. The X-value corresponds to the image's horizontal direction,
72 * and the Z-value to the vertical. Setting any of these properties triggers
73 * an asynchronous re-resolution of any existing height map.
74 *
75 * Not recommended formats: all mono formats (for example QImage::Format_Mono).
76 */
77
78/*!
79 * \qmlproperty real HeightMapSurfaceDataProxy::minXValue
80 *
81 * The minimum X value for the generated surface points. Defaults to \c{0.0}.
82 * When setting this property the corresponding maximum value is adjusted if
83 * necessary, to ensure that the range remains valid.
84 */
85
86/*!
87 * \qmlproperty real HeightMapSurfaceDataProxy::maxXValue
88 *
89 * The maximum X value for the generated surface points. Defaults to \c{10.0}.
90 * When setting this property the corresponding minimum value is adjusted if
91 * necessary, to ensure that the range remains valid.
92 */
93
94/*!
95 * \qmlproperty real HeightMapSurfaceDataProxy::minZValue
96 *
97 * The minimum Z value for the generated surface points. Defaults to \c{0.0}.
98 * When setting this property the corresponding maximum value is adjusted if
99 * necessary, to ensure that the range remains valid.
100 */
101
102/*!
103 * \qmlproperty real HeightMapSurfaceDataProxy::maxZValue
104 *
105 * The maximum Z value for the generated surface points. Defaults to \c{10.0}.
106 * When setting this property the corresponding minimum value is adjusted if
107 * necessary, to ensure that the range remains valid.
108 */
109
110/*!
111 * \qmlproperty real HeightMapSurfaceDataProxy::minYValue
112 *
113 * The minimum Y value for the generated surface points. Defaults to \c{0.0}.
114 * When setting this property the corresponding maximum value is adjusted if
115 * necessary, to ensure that the range remains valid.
116 */
117
118/*!
119 * \qmlproperty real HeightMapSurfaceDataProxy::maxYValue
120 *
121 * The maximum Y value for the generated surface points. Defaults to \c{10.0}.
122 * When setting this property the corresponding minimum value is adjusted if
123 * necessary, to ensure that the range remains valid.
124 */
125
126/*!
127 * \qmlproperty real HeightMapSurfaceDataProxy::autoScaleY
128 *
129 * Scale height values to Y-axis. Defaults to \c{false}. When this property is
130 * set to \c{true}, the height values are scaled to fit on the Y-axis between
131 * \c{minYValue} and \c{maxYValue}.
132 */
133
134/*!
135 \qmlsignal HeightMapSurfaceDataProxy::heightMapFileChanged(string filename)
136
137 This signal is emitted when heightMapFile changes to \a filename.
138*/
139
140/*!
141 \qmlsignal HeightMapSurfaceDataProxy::minXValueChanged(real value)
142
143 This signal is emitted when minXValue changes to \a value.
144*/
145
146/*!
147 \qmlsignal HeightMapSurfaceDataProxy::maxXValueChanged(real value)
148
149 This signal is emitted when maxXValue changes to \a value.
150*/
151
152/*!
153 \qmlsignal HeightMapSurfaceDataProxy::minZValueChanged(real value)
154
155 This signal is emitted when minZValue changes to \a value.
156*/
157
158/*!
159 \qmlsignal HeightMapSurfaceDataProxy::maxZValueChanged(real value)
160
161 This signal is emitted when maxZValue changes to \a value.
162*/
163
164/*!
165 \qmlsignal HeightMapSurfaceDataProxy::minYValueChanged(real value)
166
167 This signal is emitted when minYValue changes to \a value.
168*/
169
170/*!
171 \qmlsignal HeightMapSurfaceDataProxy::maxYValueChanged(real value)
172
173 This signal is emitted when maxYValue changes to \a value.
174*/
175
176/*!
177 \qmlsignal HeightMapSurfaceDataProxy::autoScaleYChanged(bool enabled)
178
179 This signal is emitted when autoScaleY changes to \a enabled.
180*/
181
182/*!
183 * Constructs QHeightMapSurfaceDataProxy with the given \a parent.
184 */
185QHeightMapSurfaceDataProxy::QHeightMapSurfaceDataProxy(QObject *parent)
186 : QSurfaceDataProxy(*(new QHeightMapSurfaceDataProxyPrivate()), parent)
187{
188 Q_D(QHeightMapSurfaceDataProxy);
189 QObject::connect(sender: &d->m_resolveTimer,
190 signal: &QTimer::timeout,
191 context: this,
192 slot: &QHeightMapSurfaceDataProxy::handlePendingResolve);
193}
194
195/*!
196 * Constructs QHeightMapSurfaceDataProxy with the given \a image and \a parent.
197 * Height map is set by calling setHeightMap() with \a image.
198 *
199 * \sa heightMap
200 */
201QHeightMapSurfaceDataProxy::QHeightMapSurfaceDataProxy(const QImage &image, QObject *parent)
202 : QSurfaceDataProxy(*(new QHeightMapSurfaceDataProxyPrivate()), parent)
203{
204 Q_D(QHeightMapSurfaceDataProxy);
205 QObject::connect(sender: &d->m_resolveTimer,
206 signal: &QTimer::timeout,
207 context: this,
208 slot: &QHeightMapSurfaceDataProxy::handlePendingResolve);
209 setHeightMap(image);
210}
211
212/*!
213 * Constructs QHeightMapSurfaceDataProxy from the given image \a filename and \a
214 * parent. Height map is set by calling setHeightMapFile() with \a filename.
215 *
216 * \sa heightMapFile
217 */
218QHeightMapSurfaceDataProxy::QHeightMapSurfaceDataProxy(const QString &filename, QObject *parent)
219 : QSurfaceDataProxy(*(new QHeightMapSurfaceDataProxyPrivate()), parent)
220{
221 Q_D(QHeightMapSurfaceDataProxy);
222 QObject::connect(sender: &d->m_resolveTimer,
223 signal: &QTimer::timeout,
224 context: this,
225 slot: &QHeightMapSurfaceDataProxy::handlePendingResolve);
226 setHeightMapFile(filename);
227}
228
229/*!
230 * \internal
231 */
232QHeightMapSurfaceDataProxy::QHeightMapSurfaceDataProxy(QHeightMapSurfaceDataProxyPrivate &d,
233 QObject *parent)
234 : QSurfaceDataProxy(d, parent)
235{}
236
237/*!
238 * Destroys QHeightMapSurfaceDataProxy.
239 */
240QHeightMapSurfaceDataProxy::~QHeightMapSurfaceDataProxy() {}
241
242/*!
243 * \property QHeightMapSurfaceDataProxy::heightMap
244 *
245 * \brief The height map image to be visualized.
246 */
247
248/*!
249 * Replaces current data with the height map data specified by \a image.
250 *
251 * There are several formats the \a image can be given in, but if it is not in a
252 * directly usable format, a conversion is made.
253 *
254 * \note If the result seems wrong, the automatic conversion failed
255 * and you should try converting the \a image yourself before setting it.
256 * Preferred format is QImage::Format_RGB32 in grayscale.
257 *
258 * The height of the \a image is read from the red component of the pixels if
259 * the \a image is in grayscale. Otherwise it is an average calculated from the red,
260 * green, and blue components of the pixels. Using grayscale images may improve
261 * data conversion speed for large images.
262 *
263 * Not recommended formats: all mono formats (for example QImage::Format_Mono).
264 *
265 * The height map is resolved asynchronously. QSurfaceDataProxy::arrayReset() is
266 * emitted when the data has been resolved.
267 */
268void QHeightMapSurfaceDataProxy::setHeightMap(const QImage &image)
269{
270 Q_D(QHeightMapSurfaceDataProxy);
271 d->m_heightMap = image;
272
273 // We do resolving asynchronously to make qml onArrayReset handlers actually
274 // get the initial reset
275 if (!d->m_resolveTimer.isActive())
276 d->m_resolveTimer.start(msec: 0);
277}
278
279QImage QHeightMapSurfaceDataProxy::heightMap() const
280{
281 Q_D(const QHeightMapSurfaceDataProxy);
282 return d->m_heightMap;
283}
284
285/*!
286 * \property QHeightMapSurfaceDataProxy::heightMapFile
287 *
288 * \brief The name of the file with a height map image to be visualized.
289 */
290
291/*!
292 * Replaces current data with height map data from the file specified by
293 * \a filename.
294 *
295 * \sa heightMap
296 */
297void QHeightMapSurfaceDataProxy::setHeightMapFile(const QString &filename)
298{
299 Q_D(QHeightMapSurfaceDataProxy);
300 QFileInfo validfile(filename);
301 // Check if the filename is empty, in which case we should clear the height map,
302 // or if not, it's an actual file that can be found
303 if (!filename.isEmpty() && (!validfile.exists() || !validfile.isFile())) {
304 qWarning(msg: "Height map file %ls does not exist.", qUtf16Printable(filename));
305 return;
306 }
307 if (d->m_heightMapFile != filename) {
308 d->m_heightMapFile = filename;
309 setHeightMap(QImage(filename));
310 emit heightMapFileChanged(filename);
311 }
312}
313
314QString QHeightMapSurfaceDataProxy::heightMapFile() const
315{
316 Q_D(const QHeightMapSurfaceDataProxy);
317 return d->m_heightMapFile;
318}
319
320/*!
321 * A convenience function for setting all minimum (\a minX and \a minZ) and
322 * maximum
323 * (\a maxX and \a maxZ) values at the same time. The minimum values must be
324 * smaller than the corresponding maximum value. Otherwise the values get
325 * adjusted so that they are valid.
326 */
327void QHeightMapSurfaceDataProxy::setValueRanges(float minX, float maxX, float minZ, float maxZ)
328{
329 Q_D(QHeightMapSurfaceDataProxy);
330 d->setValueRanges(minX, maxX, minZ, maxZ);
331}
332
333/*!
334 * \property QHeightMapSurfaceDataProxy::minXValue
335 *
336 * \brief The minimum X value for the generated surface points.
337 *
338 * Defaults to \c{0.0}.
339 *
340 * When setting this property the corresponding maximum value is adjusted if
341 * necessary, to ensure that the range remains valid.
342 */
343void QHeightMapSurfaceDataProxy::setMinXValue(float min)
344{
345 Q_D(QHeightMapSurfaceDataProxy);
346 d->setMinXValue(min);
347}
348
349float QHeightMapSurfaceDataProxy::minXValue() const
350{
351 Q_D(const QHeightMapSurfaceDataProxy);
352 return d->m_minXValue;
353}
354
355/*!
356 * \property QHeightMapSurfaceDataProxy::maxXValue
357 *
358 * \brief The maximum X value for the generated surface points.
359 *
360 * Defaults to \c{10.0}.
361 *
362 * When setting this property the corresponding minimum value is adjusted if
363 * necessary, to ensure that the range remains valid.
364 */
365void QHeightMapSurfaceDataProxy::setMaxXValue(float max)
366{
367 Q_D(QHeightMapSurfaceDataProxy);
368 d->setMaxXValue(max);
369}
370
371float QHeightMapSurfaceDataProxy::maxXValue() const
372{
373 Q_D(const QHeightMapSurfaceDataProxy);
374 return d->m_maxXValue;
375}
376
377/*!
378 * \property QHeightMapSurfaceDataProxy::minZValue
379 *
380 * \brief The minimum Z value for the generated surface points.
381 *
382 * Defaults to \c{0.0}.
383 *
384 * When setting this property the corresponding maximum value is adjusted if
385 * necessary, to ensure that the range remains valid.
386 */
387void QHeightMapSurfaceDataProxy::setMinZValue(float min)
388{
389 Q_D(QHeightMapSurfaceDataProxy);
390 d->setMinZValue(min);
391}
392
393float QHeightMapSurfaceDataProxy::minZValue() const
394{
395 Q_D(const QHeightMapSurfaceDataProxy);
396 return d->m_minZValue;
397}
398
399/*!
400 * \property QHeightMapSurfaceDataProxy::maxZValue
401 *
402 * \brief The maximum Z value for the generated surface points.
403 *
404 * Defaults to \c{10.0}.
405 *
406 * When setting this property the corresponding minimum value is adjusted if
407 * necessary, to ensure that the range remains valid.
408 */
409void QHeightMapSurfaceDataProxy::setMaxZValue(float max)
410{
411 Q_D(QHeightMapSurfaceDataProxy);
412 d->setMaxZValue(max);
413}
414
415float QHeightMapSurfaceDataProxy::maxZValue() const
416{
417 Q_D(const QHeightMapSurfaceDataProxy);
418 return d->m_maxZValue;
419}
420
421/*!
422 * \property QHeightMapSurfaceDataProxy::minYValue
423 *
424 * \brief The minimum Y value for the generated surface points.
425 *
426 * Defaults to \c{0.0}.
427 *
428 * When setting this property the corresponding maximum value is adjusted if
429 * necessary, to ensure that the range remains valid.
430 *
431 * \sa autoScaleY
432 */
433void QHeightMapSurfaceDataProxy::setMinYValue(float min)
434{
435 Q_D(QHeightMapSurfaceDataProxy);
436 d->setMinYValue(min);
437}
438
439float QHeightMapSurfaceDataProxy::minYValue() const
440{
441 Q_D(const QHeightMapSurfaceDataProxy);
442 return d->m_minYValue;
443}
444
445/*!
446 * \property QHeightMapSurfaceDataProxy::maxYValue
447 *
448 * \brief The maximum Y value for the generated surface points.
449 *
450 * Defaults to \c{10.0}.
451 *
452 * When setting this property the corresponding minimum value is adjusted if
453 * necessary, to ensure that the range remains valid.
454 *
455 * \sa autoScaleY
456 */
457void QHeightMapSurfaceDataProxy::setMaxYValue(float max)
458{
459 Q_D(QHeightMapSurfaceDataProxy);
460 d->setMaxYValue(max);
461}
462
463float QHeightMapSurfaceDataProxy::maxYValue() const
464{
465 Q_D(const QHeightMapSurfaceDataProxy);
466 return d->m_maxYValue;
467}
468
469/*!
470 * \property QHeightMapSurfaceDataProxy::autoScaleY
471 *
472 * \brief Scale height values to Y-axis.
473 *
474 * Defaults to \c{false}.
475 *
476 * When this property is set to \c{true},
477 * the height values are scaled to fit on the Y-axis between minYValue and
478 * maxYValue.
479 *
480 * \sa minYValue, maxYValue
481 */
482void QHeightMapSurfaceDataProxy::setAutoScaleY(bool enabled)
483{
484 Q_D(QHeightMapSurfaceDataProxy);
485 d->setAutoScaleY(enabled);
486}
487
488bool QHeightMapSurfaceDataProxy::autoScaleY() const
489{
490 Q_D(const QHeightMapSurfaceDataProxy);
491 return d->m_autoScaleY;
492}
493
494/*!
495 * \internal
496 */
497void QHeightMapSurfaceDataProxy::handlePendingResolve()
498{
499 Q_D(QHeightMapSurfaceDataProxy);
500 d->handlePendingResolve();
501}
502
503// QHeightMapSurfaceDataProxyPrivate
504
505QHeightMapSurfaceDataProxyPrivate::QHeightMapSurfaceDataProxyPrivate()
506 : m_minXValue(defaultMinValue)
507 , m_maxXValue(defaultMaxValue)
508 , m_minZValue(defaultMinValue)
509 , m_maxZValue(defaultMaxValue)
510 , m_minYValue(defaultMinValue)
511 , m_maxYValue(defaultMaxValue)
512 , m_autoScaleY(false)
513{
514 m_resolveTimer.setSingleShot(true);
515}
516
517QHeightMapSurfaceDataProxyPrivate::~QHeightMapSurfaceDataProxyPrivate() {}
518
519void QHeightMapSurfaceDataProxyPrivate::setValueRanges(float minX,
520 float maxX,
521 float minZ,
522 float maxZ)
523{
524 Q_Q(QHeightMapSurfaceDataProxy);
525 bool minXChanged = false;
526 bool maxXChanged = false;
527 bool minZChanged = false;
528 bool maxZChanged = false;
529 if (m_minXValue != minX) {
530 m_minXValue = minX;
531 minXChanged = true;
532 }
533 if (m_minZValue != minZ) {
534 m_minZValue = minZ;
535 minZChanged = true;
536 }
537 if (m_maxXValue != maxX || minX >= maxX) {
538 if (minX >= maxX) {
539 m_maxXValue = minX + 1.0f;
540 qWarning(msg: "Warning: Tried to set invalid range for X value range. Range automatically "
541 "adjusted to a valid one: %f - %f --> %f - %f",
542 minX,
543 maxX,
544 m_minXValue,
545 m_maxXValue);
546 } else {
547 m_maxXValue = maxX;
548 }
549 maxXChanged = true;
550 }
551 if (m_maxZValue != maxZ || minZ >= maxZ) {
552 if (minZ >= maxZ) {
553 m_maxZValue = minZ + 1.0f;
554 qWarning(msg: "Warning: Tried to set invalid range for Z value range."
555 " Range automatically adjusted to a valid one: %f - %f --> %f - %f",
556 minZ,
557 maxZ,
558 m_minZValue,
559 m_maxZValue);
560 } else {
561 m_maxZValue = maxZ;
562 }
563 maxZChanged = true;
564 }
565
566 if (minXChanged)
567 emit q->minXValueChanged(value: m_minXValue);
568 if (minZChanged)
569 emit q->minZValueChanged(value: m_minZValue);
570 if (maxXChanged)
571 emit q->maxXValueChanged(value: m_maxXValue);
572 if (maxZChanged)
573 emit q->maxZValueChanged(value: m_maxZValue);
574
575 if ((minXChanged || minZChanged || maxXChanged || maxZChanged) && !m_resolveTimer.isActive())
576 m_resolveTimer.start(msec: 0);
577}
578
579void QHeightMapSurfaceDataProxyPrivate::setMinXValue(float min)
580{
581 Q_Q(QHeightMapSurfaceDataProxy);
582 if (min != m_minXValue) {
583 bool maxChanged = false;
584 if (min >= m_maxXValue) {
585 float oldMax = m_maxXValue;
586 m_maxXValue = min + 1.0f;
587 qWarning(msg: "Warning: Tried to set minimum X to equal or larger than maximum X for value "
588 "range. Maximum automatically adjusted to a valid one: %f --> %f",
589 oldMax,
590 m_maxXValue);
591 maxChanged = true;
592 }
593 m_minXValue = min;
594 emit q->minXValueChanged(value: m_minXValue);
595 if (maxChanged)
596 emit q->maxXValueChanged(value: m_maxXValue);
597
598 if (!m_resolveTimer.isActive())
599 m_resolveTimer.start(msec: 0);
600 }
601}
602
603void QHeightMapSurfaceDataProxyPrivate::setMaxXValue(float max)
604{
605 Q_Q(QHeightMapSurfaceDataProxy);
606 if (m_maxXValue != max) {
607 bool minChanged = false;
608 if (max <= m_minXValue) {
609 float oldMin = m_minXValue;
610 m_minXValue = max - 1.0f;
611 qWarning(msg: "Warning: Tried to set maximum X to equal or smaller than minimum X for value "
612 "range. Minimum automatically adjusted to a valid one: %f --> %f",
613 oldMin,
614 m_minXValue);
615 minChanged = true;
616 }
617 m_maxXValue = max;
618 emit q->maxXValueChanged(value: m_maxXValue);
619 if (minChanged)
620 emit q->minXValueChanged(value: m_minXValue);
621
622 if (!m_resolveTimer.isActive())
623 m_resolveTimer.start(msec: 0);
624 }
625}
626
627void QHeightMapSurfaceDataProxyPrivate::setMinZValue(float min)
628{
629 Q_Q(QHeightMapSurfaceDataProxy);
630 if (min != m_minZValue) {
631 bool maxChanged = false;
632 if (min >= m_maxZValue) {
633 float oldMax = m_maxZValue;
634 m_maxZValue = min + 1.0f;
635 qWarning(msg: "Warning: Tried to set minimum Z to equal or larger than maximum Z for value "
636 "range. Maximum automatically adjusted to a valid one: %f --> %f",
637 oldMax,
638 m_maxZValue);
639 maxChanged = true;
640 }
641 m_minZValue = min;
642 emit q->minZValueChanged(value: m_minZValue);
643 if (maxChanged)
644 emit q->maxZValueChanged(value: m_maxZValue);
645
646 if (!m_resolveTimer.isActive())
647 m_resolveTimer.start(msec: 0);
648 }
649}
650
651void QHeightMapSurfaceDataProxyPrivate::setMaxZValue(float max)
652{
653 Q_Q(QHeightMapSurfaceDataProxy);
654 if (m_maxZValue != max) {
655 bool minChanged = false;
656 if (max <= m_minZValue) {
657 float oldMin = m_minZValue;
658 m_minZValue = max - 1.0f;
659 qWarning(msg: "Warning: Tried to set maximum Z to equal or smaller than minimum Z for value "
660 "range. Minimum automatically adjusted to a valid one: %f --> %f",
661 oldMin,
662 m_minZValue);
663 minChanged = true;
664 }
665 m_maxZValue = max;
666 emit q->maxZValueChanged(value: m_maxZValue);
667 if (minChanged)
668 emit q->minZValueChanged(value: m_minZValue);
669
670 if (!m_resolveTimer.isActive())
671 m_resolveTimer.start(msec: 0);
672 }
673}
674
675void QHeightMapSurfaceDataProxyPrivate::setMinYValue(float min)
676{
677 Q_Q(QHeightMapSurfaceDataProxy);
678 if (m_minYValue != min) {
679 bool maxChanged = false;
680 if (min >= m_maxYValue) {
681 float oldMax = m_maxYValue;
682 m_maxYValue = min + 1.0f;
683 qWarning(msg: "Warning: Tried to set minimum Y to equal or larger than maximum Y for value "
684 "range. Maximum automatically adjusted to a valid one: %f --> %f",
685 oldMax,
686 m_maxYValue);
687 maxChanged = true;
688 }
689 m_minYValue = min;
690 emit q->minYValueChanged(value: m_minYValue);
691 if (maxChanged)
692 emit q->maxYValueChanged(value: m_maxYValue);
693
694 if (!m_resolveTimer.isActive())
695 m_resolveTimer.start(msec: 0);
696 }
697}
698
699void QHeightMapSurfaceDataProxyPrivate::setMaxYValue(float max)
700{
701 Q_Q(QHeightMapSurfaceDataProxy);
702 if (m_maxYValue != max) {
703 bool minChanged = false;
704 if (max <= m_minYValue) {
705 float oldMin = m_minYValue;
706 m_minYValue = max - 1.0f;
707 qWarning(msg: "Warning: Tried to set maximum Y to equal or smaller than minimum Y for value "
708 "range. Minimum automatically adjusted to a valid one: %f --> %f",
709 oldMin,
710 m_minYValue);
711 minChanged = true;
712 }
713 m_maxYValue = max;
714 emit q->maxYValueChanged(value: m_maxYValue);
715 if (minChanged)
716 emit q->minYValueChanged(value: m_minYValue);
717
718 if (!m_resolveTimer.isActive())
719 m_resolveTimer.start(msec: 0);
720 }
721}
722
723void QHeightMapSurfaceDataProxyPrivate::setAutoScaleY(bool enabled)
724{
725 Q_Q(QHeightMapSurfaceDataProxy);
726 if (enabled != m_autoScaleY) {
727 m_autoScaleY = enabled;
728 emit q->autoScaleYChanged(enabled: m_autoScaleY);
729
730 if (!m_resolveTimer.isActive())
731 m_resolveTimer.start(msec: 0);
732 }
733}
734
735void QHeightMapSurfaceDataProxyPrivate::handlePendingResolve()
736{
737 Q_Q(QHeightMapSurfaceDataProxy);
738 QImage heightImage = m_heightMap;
739 int bytesInChannel = 1;
740 float yMul = 1.0f / UINT8_MAX;
741
742 bool is16bit = (heightImage.format() == QImage::Format_RGBX64
743 || heightImage.format() == QImage::Format_RGBA64
744 || heightImage.format() == QImage::Format_RGBA64_Premultiplied
745 || heightImage.format() == QImage::Format_Grayscale16);
746
747 // Convert to RGB32 to be sure we're reading the right bytes
748 if (is16bit) {
749 if (heightImage.format() != QImage::Format_RGBX64)
750 heightImage = heightImage.convertToFormat(f: QImage::Format_RGBX64);
751
752 bytesInChannel = 2;
753 yMul = 1.0f / UINT16_MAX;
754 } else if (heightImage.format() != QImage::Format_RGB32) {
755 heightImage = heightImage.convertToFormat(f: QImage::Format_RGB32);
756 }
757
758 uchar *bits = heightImage.bits();
759
760 int imageHeight = heightImage.height();
761 int imageWidth = heightImage.width();
762 int bitCount = imageWidth * 4 * (imageHeight - 1) * bytesInChannel;
763 int widthBits = imageWidth * 4 * bytesInChannel;
764 float height = 0;
765
766 // Do not recreate array if dimensions have not changed
767 QSurfaceDataArray dataArray = q->series()->dataArray();
768 if (imageWidth != q->columnCount() || imageHeight != dataArray.size()) {
769 dataArray.clear();
770 dataArray.reserve(asize: imageHeight);
771 for (int i = 0; i < imageHeight; i++) {
772 QSurfaceDataRow newProxyRow(imageWidth);
773 dataArray.append(t: newProxyRow);
774 }
775 }
776 yMul *= m_maxYValue - m_minYValue;
777 float xMul = (m_maxXValue - m_minXValue) / float(imageWidth - 1);
778 float zMul = (m_maxZValue - m_minZValue) / float(imageHeight - 1);
779
780 // Last row and column are explicitly set to max values, as relying
781 // on multiplier can cause rounding errors, resulting in the value being
782 // slightly over the specified maximum, which in turn can lead to it not
783 // getting rendered.
784 int lastRow = imageHeight - 1;
785 int lastCol = imageWidth - 1;
786 if (heightImage.isGrayscale()) {
787 // Grayscale, it's enough to read Red byte
788 for (int i = 0; i < imageHeight; i++, bitCount -= widthBits) {
789 QSurfaceDataRow &newRow = dataArray[i];
790 float zVal;
791 if (i == lastRow)
792 zVal = m_maxZValue;
793 else
794 zVal = (float(i) * zMul) + m_minZValue;
795 int j = 0;
796 float yVal = 0;
797 uchar *pixelptr;
798 for (; j < lastCol; j++) {
799 pixelptr = (uchar *) (bits + bitCount + (j * 4 * bytesInChannel));
800 if (!m_autoScaleY)
801 yVal = *pixelptr;
802 else
803 yVal = float(*pixelptr) * yMul + m_minYValue;
804 newRow[j].setPosition(QVector3D((float(j) * xMul) + m_minXValue, yVal, zVal));
805 }
806 newRow[j].setPosition(QVector3D(m_maxXValue, yVal, zVal));
807 }
808 } else {
809 // Not grayscale, we'll need to calculate height from RGB
810 for (int i = 0; i < imageHeight; i++, bitCount -= widthBits) {
811 QSurfaceDataRow &newRow = dataArray[i];
812 float zVal;
813 if (i == lastRow)
814 zVal = m_maxZValue;
815 else
816 zVal = (float(i) * zMul) + m_minZValue;
817 int j = 0;
818 float yVal = 0;
819 for (; j < lastCol; j++) {
820 int nextpixel = j * 4 * bytesInChannel;
821 uchar *pixelptr = (uchar *) (bits + bitCount + nextpixel);
822 if (is16bit) {
823 height = (float(*((ushort *) pixelptr)) + float(*(((ushort *) pixelptr) + 1))
824 + float(*(((ushort *) pixelptr) + 2)));
825 } else {
826 height = (float(*pixelptr) + float(*(pixelptr + 1)) + float(*(pixelptr + 2)));
827 }
828 if (!m_autoScaleY)
829 yVal = height / 3.0f;
830 else
831 yVal = (height / 3.0f * yMul) + m_minYValue;
832
833 newRow[j].setPosition(QVector3D((float(j) * xMul) + m_minXValue, yVal, zVal));
834 }
835 newRow[j].setPosition(QVector3D(m_maxXValue, yVal, zVal));
836 }
837 }
838
839 q->resetArray(newArray: dataArray);
840 emit q->heightMapChanged(image: m_heightMap);
841}
842
843QT_END_NAMESPACE
844

source code of qtgraphs/src/graphs3d/data/qheightmapsurfacedataproxy.cpp