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 | |
6 | QT_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 | */ |
332 | QCustom3DVolume::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 | */ |
343 | QCustom3DVolume::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 | */ |
368 | QCustom3DVolume::~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 | */ |
383 | void 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 | |
398 | int 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 | */ |
416 | void 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 | |
431 | int 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 | */ |
449 | void 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 | |
464 | int 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 | */ |
476 | void 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 | */ |
488 | int 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 | */ |
514 | void 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 | |
525 | int 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 | */ |
544 | void 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 | |
555 | int 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 | */ |
574 | void 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 | |
585 | int 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 | */ |
597 | void 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 | */ |
615 | void 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 | |
626 | QList<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 | */ |
664 | void 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 | */ |
692 | QList<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 | |
759 | QList<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 | */ |
785 | void 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 | */ |
869 | void 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 | */ |
913 | void 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 | */ |
934 | QImage::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 | */ |
961 | void 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 | |
977 | float 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 | */ |
994 | void 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 | |
1005 | bool 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 | */ |
1033 | void 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 | |
1044 | bool 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 | */ |
1064 | void 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 | |
1075 | bool 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 | */ |
1098 | void 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 | |
1109 | bool 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 | */ |
1126 | void 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 | |
1137 | QColor 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 | */ |
1157 | void 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 | |
1171 | QVector3D 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 | */ |
1190 | void 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 | |
1204 | QVector3D 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 | */ |
1222 | void 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 | |
1236 | QVector3D 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 | */ |
1252 | QImage QCustom3DVolume::renderSlice(Qt::Axis axis, int index) |
1253 | { |
1254 | Q_D(QCustom3DVolume); |
1255 | return d->renderSlice(axis, index); |
1256 | } |
1257 | |
1258 | QCustom3DVolumePrivate::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 | |
1281 | QCustom3DVolumePrivate::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 | |
1327 | QCustom3DVolumePrivate::~QCustom3DVolumePrivate() |
1328 | { |
1329 | delete m_textureData; |
1330 | } |
1331 | |
1332 | void 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 | |
1345 | QImage 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 | |
1431 | int 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 | |
1441 | QT_END_NAMESPACE |
1442 |
Definitions
- QCustom3DVolume
- QCustom3DVolume
- ~QCustom3DVolume
- setTextureWidth
- textureWidth
- setTextureHeight
- textureHeight
- setTextureDepth
- textureDepth
- setTextureDimensions
- textureDataWidth
- setSliceIndexX
- sliceIndexX
- setSliceIndexY
- sliceIndexY
- setSliceIndexZ
- sliceIndexZ
- setSliceIndices
- setColorTable
- colorTable
- setTextureData
- createTextureData
- textureData
- setSubTextureData
- setSubTextureData
- setTextureFormat
- textureFormat
- setAlphaMultiplier
- alphaMultiplier
- setPreserveOpacity
- preserveOpacity
- setUseHighDefShader
- useHighDefShader
- setDrawSlices
- drawSlices
- setDrawSliceFrames
- drawSliceFrames
- setSliceFrameColor
- sliceFrameColor
- setSliceFrameWidths
- sliceFrameWidths
- setSliceFrameGaps
- sliceFrameGaps
- setSliceFrameThicknesses
- sliceFrameThicknesses
- renderSlice
- QCustom3DVolumePrivate
- QCustom3DVolumePrivate
- ~QCustom3DVolumePrivate
- resetDirtyBits
- renderSlice
Start learning QML with our Intro Training
Find out more