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