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

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