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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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