1// Copyright (C) 2017 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:critical reason:execute-external-code
4
5#include "qcustom3dvolume_p.h"
6#include "utils_p.h"
7
8#include <QtGui/qquaternion.h>
9
10QT_BEGIN_NAMESPACE
11
12/*!
13 * \class QCustom3DVolume
14 * \inmodule QtDataVisualization
15 * \brief The QCustom3DVolume class adds a volume rendered object to a graph.
16 * \since QtDataVisualization 1.2
17 *
18 * A volume rendered
19 * object is a box with a 3D texture. Three slice planes are supported for the volume, one along
20 * each main axis of the volume.
21 *
22 * Rendering volume objects is very performance intensive, especially when the volume is largely
23 * transparent, as the contents of the volume are ray-traced. The performance scales nearly linearly
24 * with the amount of pixels that the volume occupies on the screen, so showing the volume in a
25 * smaller view or limiting the zoom level of the graph are easy ways to improve performance.
26 * Similarly, the volume texture dimensions have a large impact on performance.
27 * If the frame rate is more important than pixel-perfect rendering of the volume contents, consider
28 * turning the high definition shader off by setting the useHighDefShader property to \c{false}.
29 *
30 * \note Volumetric objects are only supported with orthographic projection.
31 *
32 * \note Volumetric objects utilize 3D textures, which are not supported in OpenGL ES2 environments.
33 *
34 * \sa QAbstract3DGraph::addCustomItem(), QAbstract3DGraph::orthoProjection, useHighDefShader
35 */
36
37/*!
38 * \qmltype Custom3DVolume
39 * \inqmlmodule QtDataVisualization
40 * \since QtDataVisualization 1.2
41 * \ingroup datavisualization_qml
42 * \nativetype QCustom3DVolume
43 * \inherits Custom3DItem
44 * \brief Adds a volume rendered object to a graph.
45 *
46 * A volume rendered
47 * object is a box with a 3D texture. Three slice planes are supported for the volume, one along
48 * each main axis of the volume.
49 *
50 * Rendering volume objects is very performance intensive, especially when the volume is largely
51 * transparent, as the contents of the volume are ray-traced. The performance scales nearly linearly
52 * with the amount of pixels that the volume occupies on the screen, so showing the volume in a
53 * smaller view or limiting the zoom level of the graph are easy ways to improve performance.
54 * Similarly, the volume texture dimensions have a large impact on performance.
55 * If the frame rate is more important than pixel-perfect rendering of the volume contents, consider
56 * turning the high definition shader off by setting the useHighDefShader property to \c{false}.
57 *
58 * \note Filling in the volume data would not typically be efficient or practical from pure QML,
59 * so properties directly related to that are not fully supported from QML.
60 * Create a hybrid QML/C++ application if you want to use volume objects with a Qt Quick UI.
61 *
62 * \note Volumetric objects are only supported with orthographic projection.
63 *
64 * \note Volumetric objects utilize 3D textures, which are not supported in OpenGL ES2 environments.
65 *
66 * \sa AbstractGraph3D::orthoProjection, useHighDefShader
67 */
68
69/*! \qmlproperty int Custom3DVolume::textureWidth
70 *
71 * The width of the 3D texture defining the volume content in pixels. Defaults to \c{0}.
72 *
73 * \note Changing this property from QML is not supported, as the texture data cannot be resized
74 * accordingly.
75 */
76
77/*! \qmlproperty int Custom3DVolume::textureHeight
78 *
79 * The height of the 3D texture defining the volume content in pixels. Defaults to \c{0}.
80 *
81 * \note Changing this property from QML is not supported, as the texture data cannot be resized
82 * accordingly.
83 */
84
85/*! \qmlproperty int Custom3DVolume::textureDepth
86 *
87 * The depth of the 3D texture defining the volume content in pixels. Defaults to \c{0}.
88 *
89 * \note Changing this property from QML is not supported, as the texture data cannot be resized
90 * accordingly.
91 */
92
93/*! \qmlproperty int Custom3DVolume::sliceIndexX
94 *
95 * The x-dimension index into the texture data indicating which vertical slice to show.
96 * Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn.
97 * If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn
98 * normally.
99 * Defaults to \c{-1}.
100 *
101 * \sa QCustom3DVolume::textureData, drawSlices, drawSliceFrames
102 */
103
104/*! \qmlproperty int Custom3DVolume::sliceIndexY
105 *
106 * The y-dimension index into the texture data indicating which horizontal slice to show.
107 * Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn.
108 * If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn
109 * normally.
110 * Defaults to \c{-1}.
111 *
112 * \sa QCustom3DVolume::textureData, drawSlices, drawSliceFrames
113 */
114
115/*! \qmlproperty int Custom3DVolume::sliceIndexZ
116 *
117 * The z-dimension index into the texture data indicating which vertical slice to show.
118 * Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn.
119 * If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn
120 * normally.
121 * Defaults to \c{-1}.
122 *
123 * \sa QCustom3DVolume::textureData, drawSlices, drawSliceFrames
124 */
125
126/*!
127 * \qmlproperty real Custom3DVolume::alphaMultiplier
128 *
129 * The alpha value of every texel of the volume texture is multiplied with this value at
130 * the render time. This can be used to introduce uniform transparency to the volume.
131 * If preserveOpacity is \c{true}, only texels with at least some transparency to begin with are
132 * affected, and fully opaque texels are not affected.
133 * The value must not be negative.
134 * Defaults to \c{1.0}.
135 *
136 * \sa preserveOpacity
137 */
138
139/*!
140 * \qmlproperty bool Custom3DVolume::preserveOpacity
141 *
142 * If this property value is \c{true}, alphaMultiplier is only applied to texels that already have
143 * some transparency. If it is \c{false}, the multiplier is applied to the alpha value of all
144 * texels.
145 * Defaults to \c{true}.
146 *
147 * \sa alphaMultiplier
148 */
149
150/*!
151 * \qmlproperty bool Custom3DVolume::useHighDefShader
152 *
153 * If this property value is \c{true}, a high definition shader is used to render the volume.
154 * If it is \c{false}, a low definition shader is used.
155 *
156 * The high definition shader guarantees that every visible texel of the volume texture is sampled
157 * when the volume is rendered.
158 * The low definition shader renders only a rough approximation of the volume contents,
159 * but at a much higher frame rate. The low definition shader does not guarantee every texel of the
160 * volume texture is sampled, so there may be flickering if the volume contains distinct thin
161 * features.
162 *
163 * \note This value does not affect the level of detail when rendering the
164 * slices of the volume.
165 *
166 * Defaults to \c{true}.
167 */
168
169/*!
170 * \qmlproperty bool Custom3DVolume::drawSlices
171 *
172 * If this property value is \c{true}, the slices indicated by slice index properties
173 * will be drawn instead of the full volume.
174 * If it is \c{false}, the full volume will always be drawn.
175 * Defaults to \c{false}.
176 *
177 * \note The slices are always drawn along the item axes, so if the item is rotated, the slices are
178 * rotated as well.
179 *
180 * \sa sliceIndexX, sliceIndexY, sliceIndexZ
181 */
182
183/*!
184 * \qmlproperty bool Custom3DVolume::drawSliceFrames
185 *
186 * If this property value is \c{true}, the frames of slices indicated by slice index properties
187 * will be drawn around the volume.
188 * If it is \c{false}, no slice frames will be drawn.
189 * Drawing slice frames is independent of drawing slices, so you can show the full volume and
190 * still draw the slice frames around it.
191 * Defaults to \c{false}.
192 *
193 * \sa sliceIndexX, sliceIndexY, sliceIndexZ, drawSlices
194 */
195
196/*!
197 * \qmlproperty color Custom3DVolume::sliceFrameColor
198 *
199 * The color of the slice frame. Transparent slice frame color is not supported.
200 *
201 * Defaults to black.
202 *
203 * \sa drawSliceFrames
204 */
205
206/*!
207 * \qmlproperty vector3d Custom3DVolume::sliceFrameWidths
208 *
209 * The widths of the slice frame. The width can be different on different dimensions,
210 * so you can for example omit drawing the frames on certain sides of the volume by setting the
211 * value for that dimension to zero. The values are fractions of the volume thickness in the same
212 * dimension. The values cannot be negative.
213 *
214 * Defaults to \c{vector3d(0.01, 0.01, 0.01)}.
215 *
216 * \sa drawSliceFrames
217 */
218
219/*!
220 * \qmlproperty vector3d Custom3DVolume::sliceFrameGaps
221 *
222 * The size of the air gap left between the volume itself and the frame in each dimension.
223 * The gap can be different on different dimensions. The values are fractions of the volume
224 * thickness in the same dimension. The values cannot be negative.
225 *
226 * Defaults to \c{vector3d(0.01, 0.01, 0.01)}.
227 *
228 * \sa drawSliceFrames
229 */
230
231/*!
232 * \qmlproperty vector3d Custom3DVolume::sliceFrameThicknesses
233 *
234 * The thickness of the slice frames for each dimension. The values are fractions of
235 * the volume thickness in the same dimension. The values cannot be negative.
236 *
237 * Defaults to \c{vector3d(0.01, 0.01, 0.01)}.
238 *
239 * \sa drawSliceFrames
240 */
241
242/*!
243 * Constructs a custom 3D volume with the given \a parent.
244 */
245QCustom3DVolume::QCustom3DVolume(QObject *parent) :
246 QCustom3DItem(new QCustom3DVolumePrivate(this), parent)
247{
248}
249
250/*!
251 * Constructs a custom 3D volume with the given \a position, \a scaling, \a rotation,
252 * \a textureWidth, \a textureHeight, \a textureDepth, \a textureData, \a textureFormat,
253 * \a colorTable, and optional \a parent.
254 *
255 * \sa textureData, setTextureFormat(), colorTable
256 */
257QCustom3DVolume::QCustom3DVolume(const QVector3D &position, const QVector3D &scaling,
258 const QQuaternion &rotation, int textureWidth, int textureHeight,
259 int textureDepth, QList<uchar> *textureData,
260 QImage::Format textureFormat, const QList<QRgb> &colorTable,
261 QObject *parent)
262 : QCustom3DItem(new QCustom3DVolumePrivate(this, position, scaling, rotation, textureWidth,
263 textureHeight, textureDepth, textureData,
264 textureFormat, colorTable),
265 parent)
266{
267}
268
269
270/*!
271 * Deletes the custom 3D volume.
272 */
273QCustom3DVolume::~QCustom3DVolume()
274{
275}
276
277/*! \property QCustom3DVolume::textureWidth
278 *
279 * \brief The width of the 3D texture defining the volume content in pixels.
280 *
281 * Defaults to \c{0}.
282 *
283 * \note The textureData value may need to be resized or recreated if this value
284 * is changed.
285 * Defaults to \c{0}.
286 *
287 * \sa textureData, textureHeight, textureDepth, setTextureFormat(), textureDataWidth()
288 */
289void QCustom3DVolume::setTextureWidth(int value)
290{
291 if (value >= 0) {
292 if (dptr()->m_textureWidth != value) {
293 dptr()->m_textureWidth = value;
294 dptr()->m_dirtyBitsVolume.textureDimensionsDirty = true;
295 emit textureWidthChanged(value);
296 emit dptr()->needUpdate();
297 }
298 } else {
299 qWarning() << __FUNCTION__ << "Cannot set negative value.";
300 }
301}
302
303int QCustom3DVolume::textureWidth() const
304{
305 return dptrc()->m_textureWidth;
306}
307
308/*! \property QCustom3DVolume::textureHeight
309 *
310 * \brief The height of the 3D texture defining the volume content in pixels.
311 *
312 * Defaults to \c{0}.
313 *
314 * \note The textureData value may need to be resized or recreated if this value
315 * is changed.
316 * Defaults to \c{0}.
317 *
318 * \sa textureData, textureWidth, textureDepth, setTextureFormat()
319 */
320void QCustom3DVolume::setTextureHeight(int value)
321{
322 if (value >= 0) {
323 if (dptr()->m_textureHeight != value) {
324 dptr()->m_textureHeight = value;
325 dptr()->m_dirtyBitsVolume.textureDimensionsDirty = true;
326 emit textureHeightChanged(value);
327 emit dptr()->needUpdate();
328 }
329 } else {
330 qWarning() << __FUNCTION__ << "Cannot set negative value.";
331 }
332
333}
334
335int QCustom3DVolume::textureHeight() const
336{
337 return dptrc()->m_textureHeight;
338}
339
340/*! \property QCustom3DVolume::textureDepth
341 *
342 * \brief The depth of the 3D texture defining the volume content in pixels.
343 *
344 * Defaults to \c{0}.
345 *
346 * \note The textureData value may need to be resized or recreated if this value
347 * is changed.
348 * Defaults to \c{0}.
349 *
350 * \sa textureData, textureWidth, textureHeight, setTextureFormat()
351 */
352void QCustom3DVolume::setTextureDepth(int value)
353{
354 if (value >= 0) {
355 if (dptr()->m_textureDepth != value) {
356 dptr()->m_textureDepth = value;
357 dptr()->m_dirtyBitsVolume.textureDimensionsDirty = true;
358 emit textureDepthChanged(value);
359 emit dptr()->needUpdate();
360 }
361 } else {
362 qWarning() << __FUNCTION__ << "Cannot set negative value.";
363 }
364}
365
366int QCustom3DVolume::textureDepth() const
367{
368 return dptrc()->m_textureDepth;
369}
370
371/*!
372 * A convenience function for setting all three texture dimensions
373 * (\a width, \a height, and \a depth) at once.
374 *
375 * \sa textureData
376 */
377void QCustom3DVolume::setTextureDimensions(int width, int height, int depth)
378{
379 setTextureWidth(width);
380 setTextureHeight(height);
381 setTextureDepth(depth);
382}
383
384/*!
385 * Returns the actual texture data width. When the texture format is QImage::Format_Indexed8,
386 * this value equals textureWidth aligned to a 32-bit boundary. Otherwise, this
387 * value equals four times textureWidth.
388 */
389int QCustom3DVolume::textureDataWidth() const
390{
391 int dataWidth = dptrc()->m_textureWidth;
392
393 if (dptrc()->m_textureFormat == QImage::Format_Indexed8)
394 dataWidth += dataWidth % 4;
395 else
396 dataWidth *= 4;
397
398 return dataWidth;
399}
400
401/*! \property QCustom3DVolume::sliceIndexX
402 *
403 * \brief The x-dimension index into the texture data indicating which vertical
404 * slice to show.
405 *
406 * Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn.
407 * If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn
408 * normally.
409 *
410 * Defaults to \c{-1}.
411 *
412 * \sa textureData, drawSlices, drawSliceFrames
413 */
414void QCustom3DVolume::setSliceIndexX(int value)
415{
416 if (dptr()->m_sliceIndexX != value) {
417 dptr()->m_sliceIndexX = value;
418 dptr()->m_dirtyBitsVolume.slicesDirty = true;
419 emit sliceIndexXChanged(value);
420 emit dptr()->needUpdate();
421 }
422}
423
424int QCustom3DVolume::sliceIndexX() const
425{
426 return dptrc()->m_sliceIndexX;
427}
428
429/*! \property QCustom3DVolume::sliceIndexY
430 *
431 * \brief The y-dimension index into the texture data indicating which
432 * horizontal slice to show.
433 *
434 * Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn.
435 * If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn
436 * normally.
437 *
438 * Defaults to \c{-1}.
439 *
440 * \sa textureData, drawSlices, drawSliceFrames
441 */
442void QCustom3DVolume::setSliceIndexY(int value)
443{
444 if (dptr()->m_sliceIndexY != value) {
445 dptr()->m_sliceIndexY = value;
446 dptr()->m_dirtyBitsVolume.slicesDirty = true;
447 emit sliceIndexYChanged(value);
448 emit dptr()->needUpdate();
449 }
450}
451
452int QCustom3DVolume::sliceIndexY() const
453{
454 return dptrc()->m_sliceIndexY;
455}
456
457/*! \property QCustom3DVolume::sliceIndexZ
458 *
459 * \brief The z-dimension index into the texture data indicating which vertical
460 * slice to show.
461 *
462 * Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn.
463 * If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn
464 * normally.
465 *
466 * Defaults to \c{-1}.
467 *
468 * \sa textureData, drawSlices, drawSliceFrames
469 */
470void QCustom3DVolume::setSliceIndexZ(int value)
471{
472 if (dptr()->m_sliceIndexZ != value) {
473 dptr()->m_sliceIndexZ = value;
474 dptr()->m_dirtyBitsVolume.slicesDirty = true;
475 emit sliceIndexZChanged(value);
476 emit dptr()->needUpdate();
477 }
478}
479
480int QCustom3DVolume::sliceIndexZ() const
481{
482 return dptrc()->m_sliceIndexZ;
483}
484
485/*!
486 * A convenience function for setting all three slice indices (\a x, \a y, and \a z) at once.
487 *
488 * \sa textureData
489 */
490void QCustom3DVolume::setSliceIndices(int x, int y, int z)
491{
492 setSliceIndexX(x);
493 setSliceIndexY(y);
494 setSliceIndexZ(z);
495}
496
497/*! \property QCustom3DVolume::colorTable
498 *
499 * \brief The array containing the colors for indexed texture formats.
500 *
501 * If the texture format is not indexed, this array is not used and can be empty.
502 *
503 * Defaults to \c{0}.
504 *
505 * \sa textureData, setTextureFormat(), QImage::colorTable()
506 */
507void QCustom3DVolume::setColorTable(const QList<QRgb> &colors)
508{
509 if (dptr()->m_colorTable != colors) {
510 dptr()->m_colorTable = colors;
511 dptr()->m_dirtyBitsVolume.colorTableDirty = true;
512 emit colorTableChanged();
513 emit dptr()->needUpdate();
514 }
515}
516
517QList<QRgb> QCustom3DVolume::colorTable() const
518{
519 return dptrc()->m_colorTable;
520}
521
522/*! \property QCustom3DVolume::textureData
523 *
524 * \brief The array containing the texture data in the format specified by textureFormat.
525 *
526 * The size of this array must be at least
527 * (\c{textureDataWidth * textureHeight * textureDepth * texture format color depth in bytes}).
528 *
529 * A 3D texture is defined by a stack of 2D subtextures. Each subtexture must be of identical size
530 * (\c{textureDataWidth * textureHeight}), and the depth of the stack is defined
531 * by the textureDepth property. The data in each 2D texture is identical to a
532 * QImage data with the same format, so
533 * QImage::bits() can be used to supply the data for each subtexture.
534 *
535 * Ownership of the new array transfers to the QCustom3DVolume instance.
536 * If another array is set, the previous array is deleted.
537 * If the same array is set again, it is assumed that the array contents have been changed and the
538 * graph rendering is triggered.
539 *
540 * \note Each x-dimension line of the data needs to be 32-bit aligned.
541 * If textureFormat is QImage::Format_Indexed8 and the textureWidth value is not
542 * divisible by four, padding bytes might need to be added to each x-dimension
543 * line of the \a data. The textureDataWidth() function returns the padded byte
544 * count. The padding bytes should indicate a fully transparent color to avoid
545 * rendering artifacts.
546 *
547 * Defaults to \c{0}.
548 *
549 * \sa colorTable, setTextureFormat(), setSubTextureData(), textureDataWidth()
550 */
551void QCustom3DVolume::setTextureData(QList<uchar> *data)
552{
553 if (dptr()->m_textureData != data)
554 delete dptr()->m_textureData;
555
556 // Even if the pointer is same as previously, consider this property changed, as the values
557 // can be changed unbeknownst to us via the array pointer.
558 dptr()->m_textureData = data;
559 dptr()->m_dirtyBitsVolume.textureDataDirty = true;
560 emit textureDataChanged(data);
561 emit dptr()->needUpdate();
562}
563
564/*!
565 * Creates a new texture data array from an array of \a images and sets it as
566 * textureData for this volume object. The texture dimensions are also set according to image
567 * and array dimensions. All of the images in the array must be the same size. If the images are not
568 * all in the QImage::Format_Indexed8 format, all texture data will be converted into the
569 * QImage::Format_ARGB32 format. If the images are in the
570 * QImage::Format_Indexed8 format, the colorTable value
571 * for the entire volume will be taken from the first image.
572 *
573 * Returns a pointer to the newly created array.
574 *
575 * \sa textureData, textureWidth, textureHeight, textureDepth, setTextureFormat()
576 */
577QList<uchar> *QCustom3DVolume::createTextureData(const QList<QImage *> &images)
578{
579 int imageCount = images.size();
580 if (imageCount) {
581 QImage *currentImage = images.at(i: 0);
582 int imageWidth = currentImage->width();
583 int imageHeight = currentImage->height();
584 QImage::Format imageFormat = currentImage->format();
585 bool convert = false;
586 if (imageFormat != QImage::Format_Indexed8 && imageFormat != QImage::Format_ARGB32) {
587 convert = true;
588 imageFormat = QImage::Format_ARGB32;
589 } else {
590 for (int i = 0; i < imageCount; i++) {
591 currentImage = images.at(i);
592 if (imageWidth != currentImage->width() || imageHeight != currentImage->height()) {
593 qWarning() << __FUNCTION__ << "Not all images were of the same size.";
594 setTextureData(0);
595 setTextureWidth(0);
596 setTextureHeight(0);
597 setTextureDepth(0);
598 return 0;
599
600 }
601 if (currentImage->format() != imageFormat) {
602 convert = true;
603 imageFormat = QImage::Format_ARGB32;
604 break;
605 }
606 }
607 }
608 int colorBytes = (imageFormat == QImage::Format_Indexed8) ? 1 : 4;
609 int imageByteWidth = (imageFormat == QImage::Format_Indexed8)
610 ? currentImage->bytesPerLine() : imageWidth;
611 int frameSize = imageByteWidth * imageHeight * colorBytes;
612 QList<uchar> *newTextureData = new QList<uchar>;
613 newTextureData->resize(size: frameSize * imageCount);
614 uchar *texturePtr = newTextureData->data();
615 QImage convertedImage;
616
617 for (int i = 0; i < imageCount; i++) {
618 currentImage = images.at(i);
619 if (convert) {
620 convertedImage = currentImage->convertToFormat(f: imageFormat);
621 currentImage = &convertedImage;
622 }
623 memcpy(dest: texturePtr, src: static_cast<void *>(currentImage->bits()), n: frameSize);
624 texturePtr += frameSize;
625 }
626
627 if (imageFormat == QImage::Format_Indexed8)
628 setColorTable(images.at(i: 0)->colorTable());
629 setTextureData(newTextureData);
630 setTextureFormat(imageFormat);
631 setTextureWidth(imageWidth);
632 setTextureHeight(imageHeight);
633 setTextureDepth(imageCount);
634 } else {
635 setTextureData(0);
636 setTextureWidth(0);
637 setTextureHeight(0);
638 setTextureDepth(0);
639 }
640 return dptr()->m_textureData;
641}
642
643QList<uchar> *QCustom3DVolume::textureData() const
644{
645 return dptrc()->m_textureData;
646}
647
648/*!
649 * Sets a single 2D subtexture of the 3D texture along the specified
650 * \a axis of the volume.
651 * The \a index parameter specifies the subtexture to set.
652 * The texture \a data must be in the format specified by the textureFormat
653 * property and have the size of
654 * the cross-section of the volume texture along the specified axis multiplied by
655 * the texture format color depth in bytes.
656 * The \a data is expected to be ordered similarly to the data in images
657 * produced by the renderSlice() method along the same axis.
658 *
659 * \note Each x-dimension line of the data needs to be 32-bit aligned when
660 * targeting the y-axis or z-axis. If textureFormat is QImage::Format_Indexed8
661 * and the textureWidth value is not divisible by four, padding bytes might need
662 * to be added to each x-dimension line of the \a data to properly align it. The
663 * padding bytes should indicate a fully transparent color to avoid rendering
664 * artifacts.
665 *
666 * \sa textureData, renderSlice()
667 */
668void QCustom3DVolume::setSubTextureData(Qt::Axis axis, int index, const uchar *data)
669{
670 if (data) {
671 int lineSize = textureDataWidth();
672 int frameSize = lineSize * dptr()->m_textureHeight;
673 int dataSize = dptr()->m_textureData->size();
674 int pixelWidth = (dptr()->m_textureFormat == QImage::Format_Indexed8) ? 1 : 4;
675 int targetIndex;
676 uchar *dataPtr = dptr()->m_textureData->data();
677 bool invalid = (index < 0);
678 if (axis == Qt::XAxis) {
679 targetIndex = index * pixelWidth;
680 if (index >= dptr()->m_textureWidth
681 || (frameSize * (dptr()->m_textureDepth - 1) + targetIndex) > dataSize) {
682 invalid = true;
683 }
684 } else if (axis == Qt::YAxis) {
685 targetIndex = (index * lineSize) + (frameSize * (dptr()->m_textureDepth - 1));
686 if (index >= dptr()->m_textureHeight || (targetIndex + lineSize > dataSize))
687 invalid = true;
688 } else {
689 targetIndex = index * frameSize;
690 if (index >= dptr()->m_textureDepth || ((targetIndex + frameSize) > dataSize))
691 invalid = true;
692 }
693
694 if (invalid) {
695 qWarning() << __FUNCTION__ << "Attempted to set invalid subtexture.";
696 } else {
697 const uchar *sourcePtr = data;
698 uchar *targetPtr = dataPtr + targetIndex;
699 if (axis == Qt::XAxis) {
700 int targetWidth = dptr()->m_textureDepth;
701 int targetHeight = dptr()->m_textureHeight;
702 for (int i = 0; i < targetHeight; i++) {
703 targetPtr = dataPtr + targetIndex + (lineSize * i);
704 for (int j = 0; j < targetWidth; j++) {
705 for (int k = 0; k < pixelWidth; k++)
706 *targetPtr++ = *sourcePtr++;
707 targetPtr += (frameSize - pixelWidth);
708 }
709 }
710 } else if (axis == Qt::YAxis) {
711 int targetHeight = dptr()->m_textureDepth;
712 for (int i = 0; i < targetHeight; i++){
713 for (int j = 0; j < lineSize; j++)
714 *targetPtr++ = *sourcePtr++;
715 targetPtr -= (frameSize + lineSize);
716 }
717 } else {
718 void *subTexPtr = dataPtr + targetIndex;
719 memcpy(dest: subTexPtr, src: static_cast<const void *>(data), n: frameSize);
720 }
721 dptr()->m_dirtyBitsVolume.textureDataDirty = true;
722 emit textureDataChanged(data: dptr()->m_textureData);
723 emit dptr()->needUpdate();
724 }
725 } else {
726 qWarning() << __FUNCTION__ << "Tried to set null data.";
727 }
728}
729
730/*!
731 * Sets a single 2D subtexture of the 3D texture along the specified
732 * \a axis of the volume.
733 * The \a index parameter specifies the subtexture to set.
734 * The source \a image must be in the format specified by the textureFormat property if
735 * textureFormat is indexed. If textureFormat is QImage::Format_ARGB32, the image is converted
736 * to that format. The image must have the size of the cross-section of the volume texture along
737 * the specified axis. The orientation of the image should correspond to the orientation of
738 * the slice image produced by renderSlice() method along the same axis.
739 *
740 * \note Each x-dimension line of the data needs to be 32-bit aligned when
741 * targeting the y-axis or z-axis. If textureFormat is QImage::Format_Indexed8
742 * and the textureWidth value is not divisible by four, padding bytes might need
743 * to be added to each x-dimension line of the image to properly align it. The
744 * padding bytes should indicate a fully transparent color to avoid rendering
745 * artifacts. It is not guaranteed that QImage will do this automatically.
746 *
747 * \sa textureData, renderSlice()
748 */
749void QCustom3DVolume::setSubTextureData(Qt::Axis axis, int index, const QImage &image)
750{
751 int sourceWidth = image.width();
752 int sourceHeight = image.height();
753 int targetWidth;
754 int targetHeight;
755 if (axis == Qt::XAxis) {
756 targetWidth = dptr()->m_textureDepth;
757 targetHeight = dptr()->m_textureHeight;
758 } else if (axis == Qt::YAxis) {
759 targetWidth = dptr()->m_textureWidth;
760 targetHeight = dptr()->m_textureDepth;
761 } else {
762 targetWidth = dptr()->m_textureWidth;
763 targetHeight = dptr()->m_textureHeight;
764 }
765
766 if (sourceWidth == targetWidth
767 && sourceHeight == targetHeight
768 && (image.format() == dptr()->m_textureFormat
769 || dptr()->m_textureFormat == QImage::Format_ARGB32)) {
770 QImage convertedImage;
771 if (dptr()->m_textureFormat == QImage::Format_ARGB32
772 && image.format() != QImage::Format_ARGB32) {
773 convertedImage = image.convertToFormat(f: QImage::Format_ARGB32);
774 } else {
775 convertedImage = image;
776 }
777 setSubTextureData(axis, index, data: convertedImage.bits());
778 } else {
779 qWarning() << __FUNCTION__ << "Invalid image size or format.";
780 }
781}
782
783// Note: textureFormat is not a Q_PROPERTY to work around an issue in meta object system that
784// doesn't allow QImage::format to be a property type. Qt 5.2.1 at least has this problem.
785
786/*!
787 * Sets the format of the textureData property to \a format. Only two formats
788 * are supported currently:
789 * QImage::Format_Indexed8 and QImage::Format_ARGB32. If an indexed format is specified, colorTable
790 * must also be set.
791 * Defaults to QImage::Format_ARGB32.
792 *
793 * \sa colorTable, textureData
794 */
795void QCustom3DVolume::setTextureFormat(QImage::Format format)
796{
797 if (format == QImage::Format_ARGB32 || format == QImage::Format_Indexed8) {
798 if (dptr()->m_textureFormat != format) {
799 dptr()->m_textureFormat = format;
800 dptr()->m_dirtyBitsVolume.textureFormatDirty = true;
801 emit textureFormatChanged(format);
802 emit dptr()->needUpdate();
803 }
804 } else {
805 qWarning() << __FUNCTION__ << "Attempted to set invalid texture format.";
806 }
807}
808
809/*!
810 * Returns the format of the textureData property value.
811 *
812 * \sa setTextureFormat()
813 */
814QImage::Format QCustom3DVolume::textureFormat() const
815{
816 return dptrc()->m_textureFormat;
817}
818
819/*!
820 * \fn void QCustom3DVolume::textureFormatChanged(QImage::Format format)
821 *
822 * This signal is emitted when the \a format of the textureData value changes.
823 *
824 * \sa setTextureFormat()
825 */
826
827/*!
828 * \property QCustom3DVolume::alphaMultiplier
829 *
830 * \brief The value that the alpha value of every texel of the volume texture is multiplied with at
831 * the render time.
832 *
833 * This property can be used to introduce uniform transparency to the volume.
834 * If preserveOpacity is \c{true}, only texels with at least some transparency to begin with are
835 * affected, and fully opaque texels are not affected.
836 * The value must not be negative.
837 * Defaults to \c{1.0f}.
838 *
839 * \sa preserveOpacity, textureData
840 */
841void QCustom3DVolume::setAlphaMultiplier(float mult)
842{
843 if (mult >= 0.0f) {
844 if (dptr()->m_alphaMultiplier != mult) {
845 dptr()->m_alphaMultiplier = mult;
846 dptr()->m_dirtyBitsVolume.alphaDirty = true;
847 emit alphaMultiplierChanged(mult);
848 emit dptr()->needUpdate();
849 }
850 } else {
851 qWarning() << __FUNCTION__ << "Attempted to set negative multiplier.";
852 }
853}
854
855float QCustom3DVolume::alphaMultiplier() const
856{
857 return dptrc()->m_alphaMultiplier;
858}
859
860/*!
861 * \property QCustom3DVolume::preserveOpacity
862 *
863 * \brief Whether the alpha multiplier is applied to all texels.
864 *
865 * If this property value is \c{true}, alphaMultiplier is only applied to texels that already have
866 * some transparency. If it is \c{false}, the multiplier is applied to the alpha value of all
867 * texels.
868 * Defaults to \c{true}.
869 *
870 * \sa alphaMultiplier
871 */
872void QCustom3DVolume::setPreserveOpacity(bool enable)
873{
874 if (dptr()->m_preserveOpacity != enable) {
875 dptr()->m_preserveOpacity = enable;
876 dptr()->m_dirtyBitsVolume.alphaDirty = true;
877 emit preserveOpacityChanged(enabled: enable);
878 emit dptr()->needUpdate();
879 }
880}
881
882bool QCustom3DVolume::preserveOpacity() const
883{
884 return dptrc()->m_preserveOpacity;
885}
886
887/*!
888 * \property QCustom3DVolume::useHighDefShader
889 *
890 * \brief Whether a high or low definition shader is used to render the volume.
891 *
892 * If this property value is \c{true}, a high definition shader is used.
893 * If it is \c{false}, a low definition shader is used.
894 *
895 * The high definition shader guarantees that every visible texel of the volume texture is sampled
896 * when the volume is rendered.
897 * The low definition shader renders only a rough approximation of the volume contents,
898 * but at a much higher frame rate. The low definition shader does not guarantee
899 * that every texel of the
900 * volume texture is sampled, so there may be flickering if the volume contains distinct thin
901 * features.
902 *
903 * \note This value does not affect the level of detail when rendering the
904 * slices of the volume.
905 *
906 * Defaults to \c{true}.
907 *
908 * \sa renderSlice()
909 */
910void QCustom3DVolume::setUseHighDefShader(bool enable)
911{
912 if (dptr()->m_useHighDefShader != enable) {
913 dptr()->m_useHighDefShader = enable;
914 dptr()->m_dirtyBitsVolume.shaderDirty = true;
915 emit useHighDefShaderChanged(enabled: enable);
916 emit dptr()->needUpdate();
917 }
918}
919
920bool QCustom3DVolume::useHighDefShader() const
921{
922 return dptrc()->m_useHighDefShader;
923}
924
925/*!
926 * \property QCustom3DVolume::drawSlices
927 *
928 * \brief Whether the specified slices are drawn instead of the full volume.
929 *
930 * If this property value is \c{true}, the slices indicated by slice index properties
931 * will be drawn instead of the full volume.
932 * If it is \c{false}, the full volume will always be drawn.
933 * Defaults to \c{false}.
934 *
935 * \note The slices are always drawn along the item axes, so if the item is rotated, the slices are
936 * rotated as well.
937 *
938 * \sa sliceIndexX, sliceIndexY, sliceIndexZ
939 */
940void QCustom3DVolume::setDrawSlices(bool enable)
941{
942 if (dptr()->m_drawSlices != enable) {
943 dptr()->m_drawSlices = enable;
944 dptr()->m_dirtyBitsVolume.slicesDirty = true;
945 emit drawSlicesChanged(enabled: enable);
946 emit dptr()->needUpdate();
947 }
948}
949
950bool QCustom3DVolume::drawSlices() const
951{
952 return dptrc()->m_drawSlices;
953}
954
955/*!
956 * \property QCustom3DVolume::drawSliceFrames
957 *
958 * \brief Whether slice frames are drawn around the volume.
959 *
960 * If this property value is \c{true}, the frames of slices indicated by slice index properties
961 * will be drawn around the volume.
962 * If it is \c{false}, no slice frames will be drawn.
963 *
964 * Drawing slice frames is independent of drawing slices, so you can show the full volume and
965 * still draw the slice frames around it. This is useful when using renderSlice() to display the
966 * slices outside the graph itself.
967 *
968 * Defaults to \c{false}.
969 *
970 * \sa sliceIndexX, sliceIndexY, sliceIndexZ, drawSlices, renderSlice()
971 */
972void QCustom3DVolume::setDrawSliceFrames(bool enable)
973{
974 if (dptr()->m_drawSliceFrames != enable) {
975 dptr()->m_drawSliceFrames = enable;
976 dptr()->m_dirtyBitsVolume.slicesDirty = true;
977 emit drawSliceFramesChanged(enabled: enable);
978 emit dptr()->needUpdate();
979 }
980}
981
982bool QCustom3DVolume::drawSliceFrames() const
983{
984 return dptrc()->m_drawSliceFrames;
985}
986
987/*!
988 * \property QCustom3DVolume::sliceFrameColor
989 *
990 * \brief The color of the slice frame.
991 *
992 * Transparent slice frame color is not supported.
993 *
994 * Defaults to black.
995 *
996 * \sa drawSliceFrames
997 */
998void QCustom3DVolume::setSliceFrameColor(const QColor &color)
999{
1000 if (dptr()->m_sliceFrameColor != color) {
1001 dptr()->m_sliceFrameColor = color;
1002 dptr()->m_dirtyBitsVolume.slicesDirty = true;
1003 emit sliceFrameColorChanged(color);
1004 emit dptr()->needUpdate();
1005 }
1006}
1007
1008QColor QCustom3DVolume::sliceFrameColor() const
1009{
1010 return dptrc()->m_sliceFrameColor;
1011}
1012
1013/*!
1014 * \property QCustom3DVolume::sliceFrameWidths
1015 *
1016 * \brief The width of the slice frame.
1017 *
1018 * The width can be different on different dimensions,
1019 * so you can for example omit drawing the frames on certain sides of the volume by setting the
1020 * value for that dimension to zero. The values are fractions of the volume thickness in the same
1021 * dimension. The values cannot be negative.
1022 *
1023 * Defaults to \c{QVector3D(0.01, 0.01, 0.01)}.
1024 *
1025 * \sa drawSliceFrames
1026 */
1027void QCustom3DVolume::setSliceFrameWidths(const QVector3D &values)
1028{
1029 if (values.x() < 0.0f || values.y() < 0.0f || values.z() < 0.0f) {
1030 qWarning() << __FUNCTION__ << "Attempted to set negative values.";
1031 } else if (dptr()->m_sliceFrameWidths != values) {
1032 dptr()->m_sliceFrameWidths = values;
1033 dptr()->m_dirtyBitsVolume.slicesDirty = true;
1034 emit sliceFrameWidthsChanged(values);
1035 emit dptr()->needUpdate();
1036 }
1037}
1038
1039QVector3D QCustom3DVolume::sliceFrameWidths() const
1040{
1041 return dptrc()->m_sliceFrameWidths;
1042}
1043
1044/*!
1045 * \property QCustom3DVolume::sliceFrameGaps
1046 *
1047 * \brief The size of the air gap left between the volume itself and the frame
1048 * in each dimension.
1049 *
1050 * The gap can be different on different dimensions. The values are fractions of the volume
1051 * thickness in the same dimension. The values cannot be negative.
1052 *
1053 * Defaults to \c{QVector3D(0.01, 0.01, 0.01)}.
1054 *
1055 * \sa drawSliceFrames
1056 */
1057void QCustom3DVolume::setSliceFrameGaps(const QVector3D &values)
1058{
1059 if (values.x() < 0.0f || values.y() < 0.0f || values.z() < 0.0f) {
1060 qWarning() << __FUNCTION__ << "Attempted to set negative values.";
1061 } else if (dptr()->m_sliceFrameGaps != values) {
1062 dptr()->m_sliceFrameGaps = values;
1063 dptr()->m_dirtyBitsVolume.slicesDirty = true;
1064 emit sliceFrameGapsChanged(values);
1065 emit dptr()->needUpdate();
1066 }
1067}
1068
1069QVector3D QCustom3DVolume::sliceFrameGaps() const
1070{
1071 return dptrc()->m_sliceFrameGaps;
1072}
1073
1074/*!
1075 * \property QCustom3DVolume::sliceFrameThicknesses
1076 *
1077 * \brief The thickness of the slice frames for each dimension.
1078 *
1079 * The values are fractions of
1080 * the volume thickness in the same dimension. The values cannot be negative.
1081 *
1082 * Defaults to \c{QVector3D(0.01, 0.01, 0.01)}.
1083 *
1084 * \sa drawSliceFrames
1085 */
1086void QCustom3DVolume::setSliceFrameThicknesses(const QVector3D &values)
1087{
1088 if (values.x() < 0.0f || values.y() < 0.0f || values.z() < 0.0f) {
1089 qWarning() << __FUNCTION__ << "Attempted to set negative values.";
1090 } else if (dptr()->m_sliceFrameThicknesses != values) {
1091 dptr()->m_sliceFrameThicknesses = values;
1092 dptr()->m_dirtyBitsVolume.slicesDirty = true;
1093 emit sliceFrameThicknessesChanged(values);
1094 emit dptr()->needUpdate();
1095 }
1096}
1097
1098QVector3D QCustom3DVolume::sliceFrameThicknesses() const
1099{
1100 return dptrc()->m_sliceFrameThicknesses;
1101}
1102
1103/*!
1104 * Renders the slice specified by \a index along the axis specified by \a axis
1105 * into an image.
1106 * The texture format of this object is used.
1107 *
1108 * Returns the rendered image of the slice, or a null image if an invalid index is
1109 * specified.
1110 *
1111 * \sa setTextureFormat()
1112 */
1113QImage QCustom3DVolume::renderSlice(Qt::Axis axis, int index)
1114{
1115 return dptr()->renderSlice(axis, index);
1116}
1117
1118/*!
1119 * \internal
1120 */
1121QCustom3DVolumePrivate *QCustom3DVolume::dptr()
1122{
1123 return static_cast<QCustom3DVolumePrivate *>(d_ptr.data());
1124}
1125
1126/*!
1127 * \internal
1128 */
1129const QCustom3DVolumePrivate *QCustom3DVolume::dptrc() const
1130{
1131 return static_cast<const QCustom3DVolumePrivate *>(d_ptr.data());
1132}
1133
1134QCustom3DVolumePrivate::QCustom3DVolumePrivate(QCustom3DVolume *q) :
1135 QCustom3DItemPrivate(q),
1136 m_textureWidth(0),
1137 m_textureHeight(0),
1138 m_textureDepth(0),
1139 m_sliceIndexX(-1),
1140 m_sliceIndexY(-1),
1141 m_sliceIndexZ(-1),
1142 m_textureFormat(QImage::Format_ARGB32),
1143 m_textureData(0),
1144 m_alphaMultiplier(1.0f),
1145 m_preserveOpacity(true),
1146 m_useHighDefShader(true),
1147 m_drawSlices(false),
1148 m_drawSliceFrames(false),
1149 m_sliceFrameColor(Qt::black),
1150 m_sliceFrameWidths(QVector3D(0.01f, 0.01f, 0.01f)),
1151 m_sliceFrameGaps(QVector3D(0.01f, 0.01f, 0.01f)),
1152 m_sliceFrameThicknesses(QVector3D(0.01f, 0.01f, 0.01f))
1153{
1154 m_isVolumeItem = true;
1155 m_meshFile = QStringLiteral(":/defaultMeshes/barFull");
1156}
1157
1158QCustom3DVolumePrivate::QCustom3DVolumePrivate(
1159 QCustom3DVolume *q, const QVector3D &position, const QVector3D &scaling,
1160 const QQuaternion &rotation, int textureWidth, int textureHeight, int textureDepth,
1161 QList<uchar> *textureData, QImage::Format textureFormat, const QList<QRgb> &colorTable)
1162 : QCustom3DItemPrivate(q, QStringLiteral(":/defaultMeshes/barFull"), position, scaling,
1163 rotation),
1164 m_textureWidth(textureWidth),
1165 m_textureHeight(textureHeight),
1166 m_textureDepth(textureDepth),
1167 m_sliceIndexX(-1),
1168 m_sliceIndexY(-1),
1169 m_sliceIndexZ(-1),
1170 m_textureFormat(textureFormat),
1171 m_colorTable(colorTable),
1172 m_textureData(textureData),
1173 m_alphaMultiplier(1.0f),
1174 m_preserveOpacity(true),
1175 m_useHighDefShader(true),
1176 m_drawSlices(false),
1177 m_drawSliceFrames(false),
1178 m_sliceFrameColor(Qt::black),
1179 m_sliceFrameWidths(QVector3D(0.01f, 0.01f, 0.01f)),
1180 m_sliceFrameGaps(QVector3D(0.01f, 0.01f, 0.01f)),
1181 m_sliceFrameThicknesses(QVector3D(0.01f, 0.01f, 0.01f))
1182{
1183 m_isVolumeItem = true;
1184 m_shadowCasting = false;
1185
1186 if (m_textureWidth < 0)
1187 m_textureWidth = 0;
1188 if (m_textureHeight < 0)
1189 m_textureHeight = 0;
1190 if (m_textureDepth < 0)
1191 m_textureDepth = 0;
1192
1193 if (m_textureFormat != QImage::Format_Indexed8)
1194 m_textureFormat = QImage::Format_ARGB32;
1195
1196}
1197
1198QCustom3DVolumePrivate::~QCustom3DVolumePrivate()
1199{
1200 delete m_textureData;
1201}
1202
1203void QCustom3DVolumePrivate::resetDirtyBits()
1204{
1205 QCustom3DItemPrivate::resetDirtyBits();
1206
1207 m_dirtyBitsVolume.textureDimensionsDirty = false;
1208 m_dirtyBitsVolume.slicesDirty = false;
1209 m_dirtyBitsVolume.colorTableDirty = false;
1210 m_dirtyBitsVolume.textureDataDirty = false;
1211 m_dirtyBitsVolume.textureFormatDirty = false;
1212 m_dirtyBitsVolume.alphaDirty = false;
1213 m_dirtyBitsVolume.shaderDirty = false;
1214}
1215
1216QImage QCustom3DVolumePrivate::renderSlice(Qt::Axis axis, int index)
1217{
1218 if (index < 0)
1219 return QImage();
1220
1221 int x;
1222 int y;
1223 if (axis == Qt::XAxis) {
1224 if (index >= m_textureWidth)
1225 return QImage();
1226 x = m_textureDepth;
1227 y = m_textureHeight;
1228 } else if (axis == Qt::YAxis) {
1229 if (index >= m_textureHeight)
1230 return QImage();
1231 x = m_textureWidth;
1232 y = m_textureDepth;
1233 } else {
1234 if (index >= m_textureDepth)
1235 return QImage();
1236 x = m_textureWidth;
1237 y = m_textureHeight;
1238 }
1239
1240 int padding = 0;
1241 int pixelWidth = 4;
1242 int dataWidth = qptr()->textureDataWidth();
1243 if (m_textureFormat == QImage::Format_Indexed8) {
1244 padding = x % 4;
1245 pixelWidth = 1;
1246 }
1247 QList<uchar> data((x + padding) * y * pixelWidth);
1248 int frameSize = qptr()->textureDataWidth() * m_textureHeight;
1249
1250 int dataIndex = 0;
1251 if (axis == Qt::XAxis) {
1252 for (int i = 0; i < y; i++) {
1253 const uchar *p = m_textureData->constData()
1254 + (index * pixelWidth) + (dataWidth * i);
1255 for (int j = 0; j < x; j++) {
1256 for (int k = 0; k < pixelWidth; k++)
1257 data[dataIndex++] = *(p + k);
1258 p += frameSize;
1259 }
1260 }
1261 } else if (axis == Qt::YAxis) {
1262 for (int i = y - 1; i >= 0; i--) {
1263 const uchar *p = m_textureData->constData() + (index * dataWidth)
1264 + (frameSize * i);
1265 for (int j = 0; j < (x * pixelWidth); j++) {
1266 data[dataIndex++] = *p;
1267 p++;
1268 }
1269 }
1270 } else {
1271 for (int i = 0; i < y; i++) {
1272 const uchar *p = m_textureData->constData() + (index * frameSize) + (dataWidth * i);
1273 for (int j = 0; j < (x * pixelWidth); j++) {
1274 data[dataIndex++] = *p;
1275 p++;
1276 }
1277 }
1278 }
1279
1280 if (m_textureFormat != QImage::Format_Indexed8 && m_alphaMultiplier != 1.0f) {
1281 for (int i = pixelWidth - 1; i < data.size(); i += pixelWidth)
1282 data[i] = static_cast<uchar>(multipliedAlphaValue(alpha: data.at(i)));
1283 }
1284
1285 QImage image(data.constData(), x, y, x * pixelWidth, m_textureFormat);
1286 image.bits(); // Call bits() to detach the new image from local data
1287 if (m_textureFormat == QImage::Format_Indexed8) {
1288 QList<QRgb> colorTable = m_colorTable;
1289 if (m_alphaMultiplier != 1.0f) {
1290 for (int i = 0; i < colorTable.size(); i++) {
1291 QRgb curCol = colorTable.at(i);
1292 int alpha = multipliedAlphaValue(alpha: qAlpha(rgb: curCol));
1293 if (alpha != qAlpha(rgb: curCol))
1294 colorTable[i] = qRgba(r: qRed(rgb: curCol), g: qGreen(rgb: curCol), b: qBlue(rgb: curCol), a: alpha);
1295 }
1296 }
1297 image.setColorTable(colorTable);
1298 }
1299
1300 return image;
1301}
1302
1303int QCustom3DVolumePrivate::multipliedAlphaValue(int alpha)
1304{
1305 int modifiedAlpha = alpha;
1306 if (!m_preserveOpacity || alpha != 255) {
1307 modifiedAlpha = int(m_alphaMultiplier * float(alpha));
1308 modifiedAlpha = qMin(a: modifiedAlpha, b: 255);
1309 }
1310 return modifiedAlpha;
1311}
1312
1313QCustom3DVolume *QCustom3DVolumePrivate::qptr()
1314{
1315 return static_cast<QCustom3DVolume *>(q_ptr);
1316}
1317
1318QT_END_NAMESPACE
1319

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