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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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