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

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