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

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