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

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