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

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