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

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