blob: d54f34d18be738a30a20f30feb0f7f707ca16277 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Data Visualization module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 or (at your option) any later version
** approved by the KDE Free Qt Foundation. The licenses are as published by
** the Free Software Foundation and appearing in the file LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qcustom3dvolume_p.h"
#include "utils_p.h"
QT_BEGIN_NAMESPACE_DATAVISUALIZATION
/*!
* \class QCustom3DVolume
* \inmodule QtDataVisualization
* \brief The QCustom3DVolume class adds a volume rendered object to a graph.
* \since QtDataVisualization 1.2
*
* A volume rendered
* object is a box with a 3D texture. Three slice planes are supported for the volume, one along
* each main axis of the volume.
*
* Rendering volume objects is very performance intensive, especially when the volume is largely
* transparent, as the contents of the volume are ray-traced. The performance scales nearly linearly
* with the amount of pixels that the volume occupies on the screen, so showing the volume in a
* smaller view or limiting the zoom level of the graph are easy ways to improve performance.
* Similarly, the volume texture dimensions have a large impact on performance.
* If the frame rate is more important than pixel-perfect rendering of the volume contents, consider
* turning the high definition shader off by setting the useHighDefShader property to \c{false}.
*
* \note Volumetric objects are only supported with orthographic projection.
*
* \note Volumetric objects utilize 3D textures, which are not supported in OpenGL ES2 environments.
*
* \sa QAbstract3DGraph::addCustomItem(), QAbstract3DGraph::orthoProjection, useHighDefShader
*/
/*!
* \qmltype Custom3DVolume
* \inqmlmodule QtDataVisualization
* \since QtDataVisualization 1.2
* \ingroup datavisualization_qml
* \instantiates QCustom3DVolume
* \inherits Custom3DItem
* \brief Adds a volume rendered object to a graph.
*
* A volume rendered
* object is a box with a 3D texture. Three slice planes are supported for the volume, one along
* each main axis of the volume.
*
* Rendering volume objects is very performance intensive, especially when the volume is largely
* transparent, as the contents of the volume are ray-traced. The performance scales nearly linearly
* with the amount of pixels that the volume occupies on the screen, so showing the volume in a
* smaller view or limiting the zoom level of the graph are easy ways to improve performance.
* Similarly, the volume texture dimensions have a large impact on performance.
* If the frame rate is more important than pixel-perfect rendering of the volume contents, consider
* turning the high definition shader off by setting the useHighDefShader property to \c{false}.
*
* \note Filling in the volume data would not typically be efficient or practical from pure QML,
* so properties directly related to that are not fully supported from QML.
* Create a hybrid QML/C++ application if you want to use volume objects with a Qt Quick UI.
*
* \note Volumetric objects are only supported with orthographic projection.
*
* \note Volumetric objects utilize 3D textures, which are not supported in OpenGL ES2 environments.
*
* \sa AbstractGraph3D::orthoProjection, useHighDefShader
*/
/*! \qmlproperty int Custom3DVolume::textureWidth
*
* The width of the 3D texture defining the volume content in pixels. Defaults to \c{0}.
*
* \note Changing this property from QML is not supported, as the texture data cannot be resized
* accordingly.
*/
/*! \qmlproperty int Custom3DVolume::textureHeight
*
* The height of the 3D texture defining the volume content in pixels. Defaults to \c{0}.
*
* \note Changing this property from QML is not supported, as the texture data cannot be resized
* accordingly.
*/
/*! \qmlproperty int Custom3DVolume::textureDepth
*
* The depth of the 3D texture defining the volume content in pixels. Defaults to \c{0}.
*
* \note Changing this property from QML is not supported, as the texture data cannot be resized
* accordingly.
*/
/*! \qmlproperty int Custom3DVolume::sliceIndexX
*
* The x-dimension index into the texture data indicating which vertical slice to show.
* Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn.
* If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn
* normally.
* Defaults to \c{-1}.
*
* \sa QCustom3DVolume::textureData, drawSlices, drawSliceFrames
*/
/*! \qmlproperty int Custom3DVolume::sliceIndexY
*
* The y-dimension index into the texture data indicating which horizontal slice to show.
* Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn.
* If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn
* normally.
* Defaults to \c{-1}.
*
* \sa QCustom3DVolume::textureData, drawSlices, drawSliceFrames
*/
/*! \qmlproperty int Custom3DVolume::sliceIndexZ
*
* The z-dimension index into the texture data indicating which vertical slice to show.
* Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn.
* If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn
* normally.
* Defaults to \c{-1}.
*
* \sa QCustom3DVolume::textureData, drawSlices, drawSliceFrames
*/
/*!
* \qmlproperty real Custom3DVolume::alphaMultiplier
*
* The alpha value of every texel of the volume texture is multiplied with this value at
* the render time. This can be used to introduce uniform transparency to the volume.
* If preserveOpacity is \c{true}, only texels with at least some transparency to begin with are
* affected, and fully opaque texels are not affected.
* The value must not be negative.
* Defaults to \c{1.0}.
*
* \sa preserveOpacity
*/
/*!
* \qmlproperty bool Custom3DVolume::preserveOpacity
*
* If this property value is \c{true}, alphaMultiplier is only applied to texels that already have
* some transparency. If it is \c{false}, the multiplier is applied to the alpha value of all
* texels.
* Defaults to \c{true}.
*
* \sa alphaMultiplier
*/
/*!
* \qmlproperty bool Custom3DVolume::useHighDefShader
*
* If this property value is \c{true}, a high definition shader is used to render the volume.
* If it is \c{false}, a low definition shader is used.
*
* The high definition shader guarantees that every visible texel of the volume texture is sampled
* when the volume is rendered.
* The low definition shader renders only a rough approximation of the volume contents,
* but at a much higher frame rate. The low definition shader does not guarantee every texel of the
* volume texture is sampled, so there may be flickering if the volume contains distinct thin
* features.
*
* \note This value does not affect the level of detail when rendering the
* slices of the volume.
*
* Defaults to \c{true}.
*/
/*!
* \qmlproperty bool Custom3DVolume::drawSlices
*
* If this property value is \c{true}, the slices indicated by slice index properties
* will be drawn instead of the full volume.
* If it is \c{false}, the full volume will always be drawn.
* Defaults to \c{false}.
*
* \note The slices are always drawn along the item axes, so if the item is rotated, the slices are
* rotated as well.
*
* \sa sliceIndexX, sliceIndexY, sliceIndexZ
*/
/*!
* \qmlproperty bool Custom3DVolume::drawSliceFrames
*
* If this property value is \c{true}, the frames of slices indicated by slice index properties
* will be drawn around the volume.
* If it is \c{false}, no slice frames will be drawn.
* Drawing slice frames is independent of drawing slices, so you can show the full volume and
* still draw the slice frames around it.
* Defaults to \c{false}.
*
* \sa sliceIndexX, sliceIndexY, sliceIndexZ, drawSlices
*/
/*!
* \qmlproperty color Custom3DVolume::sliceFrameColor
*
* The color of the slice frame. Transparent slice frame color is not supported.
*
* Defaults to black.
*
* \sa drawSliceFrames
*/
/*!
* \qmlproperty vector3d Custom3DVolume::sliceFrameWidths
*
* The widths of the slice frame. The width can be different on different dimensions,
* so you can for example omit drawing the frames on certain sides of the volume by setting the
* value for that dimension to zero. The values are fractions of the volume thickness in the same
* dimension. The values cannot be negative.
*
* Defaults to \c{vector3d(0.01, 0.01, 0.01)}.
*
* \sa drawSliceFrames
*/
/*!
* \qmlproperty vector3d Custom3DVolume::sliceFrameGaps
*
* The size of the air gap left between the volume itself and the frame in each dimension.
* The gap can be different on different dimensions. The values are fractions of the volume
* thickness in the same dimension. The values cannot be negative.
*
* Defaults to \c{vector3d(0.01, 0.01, 0.01)}.
*
* \sa drawSliceFrames
*/
/*!
* \qmlproperty vector3d Custom3DVolume::sliceFrameThicknesses
*
* The thickness of the slice frames for each dimension. The values are fractions of
* the volume thickness in the same dimension. The values cannot be negative.
*
* Defaults to \c{vector3d(0.01, 0.01, 0.01)}.
*
* \sa drawSliceFrames
*/
/*!
* Constructs a custom 3D volume with the given \a parent.
*/
QCustom3DVolume::QCustom3DVolume(QObject *parent) :
QCustom3DItem(new QCustom3DVolumePrivate(this), parent)
{
}
/*!
* Constructs a custom 3D volume with the given \a position, \a scaling, \a rotation,
* \a textureWidth, \a textureHeight, \a textureDepth, \a textureData, \a textureFormat,
* \a colorTable, and optional \a parent.
*
* \sa textureData, setTextureFormat(), colorTable
*/
QCustom3DVolume::QCustom3DVolume(const QVector3D &position, const QVector3D &scaling,
const QQuaternion &rotation, int textureWidth,
int textureHeight, int textureDepth,
QVector<uchar> *textureData, QImage::Format textureFormat,
const QVector<QRgb> &colorTable, QObject *parent) :
QCustom3DItem(new QCustom3DVolumePrivate(this, position, scaling, rotation, textureWidth,
textureHeight, textureDepth,
textureData, textureFormat, colorTable), parent)
{
}
/*!
* Deletes the custom 3D volume.
*/
QCustom3DVolume::~QCustom3DVolume()
{
}
/*! \property QCustom3DVolume::textureWidth
*
* \brief The width of the 3D texture defining the volume content in pixels.
*
* Defaults to \c{0}.
*
* \note The textureData value may need to be resized or recreated if this value
* is changed.
* Defaults to \c{0}.
*
* \sa textureData, textureHeight, textureDepth, setTextureFormat(), textureDataWidth()
*/
void QCustom3DVolume::setTextureWidth(int value)
{
if (value >= 0) {
if (dptr()->m_textureWidth != value) {
dptr()->m_textureWidth = value;
dptr()->m_dirtyBitsVolume.textureDimensionsDirty = true;
emit textureWidthChanged(value);
emit dptr()->needUpdate();
}
} else {
qWarning() << __FUNCTION__ << "Cannot set negative value.";
}
}
int QCustom3DVolume::textureWidth() const
{
return dptrc()->m_textureWidth;
}
/*! \property QCustom3DVolume::textureHeight
*
* \brief The height of the 3D texture defining the volume content in pixels.
*
* Defaults to \c{0}.
*
* \note The textureData value may need to be resized or recreated if this value
* is changed.
* Defaults to \c{0}.
*
* \sa textureData, textureWidth, textureDepth, setTextureFormat()
*/
void QCustom3DVolume::setTextureHeight(int value)
{
if (value >= 0) {
if (dptr()->m_textureHeight != value) {
dptr()->m_textureHeight = value;
dptr()->m_dirtyBitsVolume.textureDimensionsDirty = true;
emit textureHeightChanged(value);
emit dptr()->needUpdate();
}
} else {
qWarning() << __FUNCTION__ << "Cannot set negative value.";
}
}
int QCustom3DVolume::textureHeight() const
{
return dptrc()->m_textureHeight;
}
/*! \property QCustom3DVolume::textureDepth
*
* \brief The depth of the 3D texture defining the volume content in pixels.
*
* Defaults to \c{0}.
*
* \note The textureData value may need to be resized or recreated if this value
* is changed.
* Defaults to \c{0}.
*
* \sa textureData, textureWidth, textureHeight, setTextureFormat()
*/
void QCustom3DVolume::setTextureDepth(int value)
{
if (value >= 0) {
if (dptr()->m_textureDepth != value) {
dptr()->m_textureDepth = value;
dptr()->m_dirtyBitsVolume.textureDimensionsDirty = true;
emit textureDepthChanged(value);
emit dptr()->needUpdate();
}
} else {
qWarning() << __FUNCTION__ << "Cannot set negative value.";
}
}
int QCustom3DVolume::textureDepth() const
{
return dptrc()->m_textureDepth;
}
/*!
* A convenience function for setting all three texture dimensions
* (\a width, \a height, and \a depth) at once.
*
* \sa textureData
*/
void QCustom3DVolume::setTextureDimensions(int width, int height, int depth)
{
setTextureWidth(width);
setTextureHeight(height);
setTextureDepth(depth);
}
/*!
* Returns the actual texture data width. When the texture format is QImage::Format_Indexed8,
* this value equals textureWidth aligned to a 32-bit boundary. Otherwise, this
* value equals four times textureWidth.
*/
int QCustom3DVolume::textureDataWidth() const
{
int dataWidth = dptrc()->m_textureWidth;
if (dptrc()->m_textureFormat == QImage::Format_Indexed8)
dataWidth += dataWidth % 4;
else
dataWidth *= 4;
return dataWidth;
}
/*! \property QCustom3DVolume::sliceIndexX
*
* \brief The x-dimension index into the texture data indicating which vertical
* slice to show.
*
* Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn.
* If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn
* normally.
*
* Defaults to \c{-1}.
*
* \sa textureData, drawSlices, drawSliceFrames
*/
void QCustom3DVolume::setSliceIndexX(int value)
{
if (dptr()->m_sliceIndexX != value) {
dptr()->m_sliceIndexX = value;
dptr()->m_dirtyBitsVolume.slicesDirty = true;
emit sliceIndexXChanged(value);
emit dptr()->needUpdate();
}
}
int QCustom3DVolume::sliceIndexX() const
{
return dptrc()->m_sliceIndexX;
}
/*! \property QCustom3DVolume::sliceIndexY
*
* \brief The y-dimension index into the texture data indicating which
* horizontal slice to show.
*
* Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn.
* If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn
* normally.
*
* Defaults to \c{-1}.
*
* \sa textureData, drawSlices, drawSliceFrames
*/
void QCustom3DVolume::setSliceIndexY(int value)
{
if (dptr()->m_sliceIndexY != value) {
dptr()->m_sliceIndexY = value;
dptr()->m_dirtyBitsVolume.slicesDirty = true;
emit sliceIndexYChanged(value);
emit dptr()->needUpdate();
}
}
int QCustom3DVolume::sliceIndexY() const
{
return dptrc()->m_sliceIndexY;
}
/*! \property QCustom3DVolume::sliceIndexZ
*
* \brief The z-dimension index into the texture data indicating which vertical
* slice to show.
*
* Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn.
* If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn
* normally.
*
* Defaults to \c{-1}.
*
* \sa textureData, drawSlices, drawSliceFrames
*/
void QCustom3DVolume::setSliceIndexZ(int value)
{
if (dptr()->m_sliceIndexZ != value) {
dptr()->m_sliceIndexZ = value;
dptr()->m_dirtyBitsVolume.slicesDirty = true;
emit sliceIndexZChanged(value);
emit dptr()->needUpdate();
}
}
int QCustom3DVolume::sliceIndexZ() const
{
return dptrc()->m_sliceIndexZ;
}
/*!
* A convenience function for setting all three slice indices (\a x, \a y, and \a z) at once.
*
* \sa textureData
*/
void QCustom3DVolume::setSliceIndices(int x, int y, int z)
{
setSliceIndexX(x);
setSliceIndexY(y);
setSliceIndexZ(z);
}
/*! \property QCustom3DVolume::colorTable
*
* \brief The array containing the colors for indexed texture formats.
*
* If the texture format is not indexed, this array is not used and can be empty.
*
* Defaults to \c{0}.
*
* \sa textureData, setTextureFormat(), QImage::colorTable()
*/
void QCustom3DVolume::setColorTable(const QVector<QRgb> &colors)
{
if (dptr()->m_colorTable != colors) {
dptr()->m_colorTable = colors;
dptr()->m_dirtyBitsVolume.colorTableDirty = true;
emit colorTableChanged();
emit dptr()->needUpdate();
}
}
QVector<QRgb> QCustom3DVolume::colorTable() const
{
return dptrc()->m_colorTable;
}
/*! \property QCustom3DVolume::textureData
*
* \brief The array containing the texture data in the format specified by textureFormat.
*
* The size of this array must be at least
* (\c{textureDataWidth * textureHeight * textureDepth * texture format color depth in bytes}).
*
* A 3D texture is defined by a stack of 2D subtextures. Each subtexture must be of identical size
* (\c{textureDataWidth * textureHeight}), and the depth of the stack is defined
* by the textureDepth property. The data in each 2D texture is identical to a
* QImage data with the same format, so
* QImage::bits() can be used to supply the data for each subtexture.
*
* Ownership of the new array transfers to the QCustom3DVolume instance.
* If another array is set, the previous array is deleted.
* If the same array is set again, it is assumed that the array contents have been changed and the
* graph rendering is triggered.
*
* \note Each x-dimension line of the data needs to be 32-bit aligned.
* If textureFormat is QImage::Format_Indexed8 and the textureWidth value is not
* divisible by four, padding bytes might need to be added to each x-dimension
* line of the \a data. The textureDataWidth() function returns the padded byte
* count. The padding bytes should indicate a fully transparent color to avoid
* rendering artifacts.
*
* Defaults to \c{0}.
*
* \sa colorTable, setTextureFormat(), setSubTextureData(), textureDataWidth()
*/
void QCustom3DVolume::setTextureData(QVector<uchar> *data)
{
if (dptr()->m_textureData != data)
delete dptr()->m_textureData;
// Even if the pointer is same as previously, consider this property changed, as the values
// can be changed unbeknownst to us via the array pointer.
dptr()->m_textureData = data;
dptr()->m_dirtyBitsVolume.textureDataDirty = true;
emit textureDataChanged(data);
emit dptr()->needUpdate();
}
/*!
* Creates a new texture data array from an array of \a images and sets it as
* textureData for this volume object. The texture dimensions are also set according to image
* and array dimensions. All of the images in the array must be the same size. If the images are not
* all in the QImage::Format_Indexed8 format, all texture data will be converted into the
* QImage::Format_ARGB32 format. If the images are in the
* QImage::Format_Indexed8 format, the colorTable value
* for the entire volume will be taken from the first image.
*
* Returns a pointer to the newly created array.
*
* \sa textureData, textureWidth, textureHeight, textureDepth, setTextureFormat()
*/
QVector<uchar> *QCustom3DVolume::createTextureData(const QVector<QImage *> &images)
{
int imageCount = images.size();
if (imageCount) {
QImage *currentImage = images.at(0);
int imageWidth = currentImage->width();
int imageHeight = currentImage->height();
QImage::Format imageFormat = currentImage->format();
bool convert = false;
if (imageFormat != QImage::Format_Indexed8 && imageFormat != QImage::Format_ARGB32) {
convert = true;
imageFormat = QImage::Format_ARGB32;
} else {
for (int i = 0; i < imageCount; i++) {
currentImage = images.at(i);
if (imageWidth != currentImage->width() || imageHeight != currentImage->height()) {
qWarning() << __FUNCTION__ << "Not all images were of the same size.";
setTextureData(0);
setTextureWidth(0);
setTextureHeight(0);
setTextureDepth(0);
return 0;
}
if (currentImage->format() != imageFormat) {
convert = true;
imageFormat = QImage::Format_ARGB32;
break;
}
}
}
int colorBytes = (imageFormat == QImage::Format_Indexed8) ? 1 : 4;
int imageByteWidth = (imageFormat == QImage::Format_Indexed8)
? currentImage->bytesPerLine() : imageWidth;
int frameSize = imageByteWidth * imageHeight * colorBytes;
QVector<uchar> *newTextureData = new QVector<uchar>;
newTextureData->resize(frameSize * imageCount);
uchar *texturePtr = newTextureData->data();
QImage convertedImage;
for (int i = 0; i < imageCount; i++) {
currentImage = images.at(i);
if (convert) {
convertedImage = currentImage->convertToFormat(imageFormat);
currentImage = &convertedImage;
}
memcpy(texturePtr, static_cast<void *>(currentImage->bits()), frameSize);
texturePtr += frameSize;
}
if (imageFormat == QImage::Format_Indexed8)
setColorTable(images.at(0)->colorTable());
setTextureData(newTextureData);
setTextureFormat(imageFormat);
setTextureWidth(imageWidth);
setTextureHeight(imageHeight);
setTextureDepth(imageCount);
} else {
setTextureData(0);
setTextureWidth(0);
setTextureHeight(0);
setTextureDepth(0);
}
return dptr()->m_textureData;
}
QVector<uchar> *QCustom3DVolume::textureData() const
{
return dptrc()->m_textureData;
}
/*!
* Sets a single 2D subtexture of the 3D texture along the specified
* \a axis of the volume.
* The \a index parameter specifies the subtexture to set.
* The texture \a data must be in the format specified by the textureFormat
* property and have the size of
* the cross-section of the volume texture along the specified axis multiplied by
* the texture format color depth in bytes.
* The \a data is expected to be ordered similarly to the data in images
* produced by the renderSlice() method along the same axis.
*
* \note Each x-dimension line of the data needs to be 32-bit aligned when
* targeting the y-axis or z-axis. If textureFormat is QImage::Format_Indexed8
* and the textureWidth value is not divisible by four, padding bytes might need
* to be added to each x-dimension line of the \a data to properly align it. The
* padding bytes should indicate a fully transparent color to avoid rendering
* artifacts.
*
* \sa textureData, renderSlice()
*/
void QCustom3DVolume::setSubTextureData(Qt::Axis axis, int index, const uchar *data)
{
if (data) {
int lineSize = textureDataWidth();
int frameSize = lineSize * dptr()->m_textureHeight;
int dataSize = dptr()->m_textureData->size();
int pixelWidth = (dptr()->m_textureFormat == QImage::Format_Indexed8) ? 1 : 4;
int targetIndex;
uchar *dataPtr = dptr()->m_textureData->data();
bool invalid = (index < 0);
if (axis == Qt::XAxis) {
targetIndex = index * pixelWidth;
if (index >= dptr()->m_textureWidth
|| (frameSize * (dptr()->m_textureDepth - 1) + targetIndex) > dataSize) {
invalid = true;
}
} else if (axis == Qt::YAxis) {
targetIndex = (index * lineSize) + (frameSize * (dptr()->m_textureDepth - 1));
if (index >= dptr()->m_textureHeight || (targetIndex + lineSize > dataSize))
invalid = true;
} else {
targetIndex = index * frameSize;
if (index >= dptr()->m_textureDepth || ((targetIndex + frameSize) > dataSize))
invalid = true;
}
if (invalid) {
qWarning() << __FUNCTION__ << "Attempted to set invalid subtexture.";
} else {
const uchar *sourcePtr = data;
uchar *targetPtr = dataPtr + targetIndex;
if (axis == Qt::XAxis) {
int targetWidth = dptr()->m_textureDepth;
int targetHeight = dptr()->m_textureHeight;
for (int i = 0; i < targetHeight; i++) {
targetPtr = dataPtr + targetIndex + (lineSize * i);
for (int j = 0; j < targetWidth; j++) {
for (int k = 0; k < pixelWidth; k++)
*targetPtr++ = *sourcePtr++;
targetPtr += (frameSize - pixelWidth);
}
}
} else if (axis == Qt::YAxis) {
int targetHeight = dptr()->m_textureDepth;
for (int i = 0; i < targetHeight; i++){
for (int j = 0; j < lineSize; j++)
*targetPtr++ = *sourcePtr++;
targetPtr -= (frameSize + lineSize);
}
} else {
void *subTexPtr = dataPtr + targetIndex;
memcpy(subTexPtr, static_cast<const void *>(data), frameSize);
}
dptr()->m_dirtyBitsVolume.textureDataDirty = true;
emit textureDataChanged(dptr()->m_textureData);
emit dptr()->needUpdate();
}
} else {
qWarning() << __FUNCTION__ << "Tried to set null data.";
}
}
/*!
* Sets a single 2D subtexture of the 3D texture along the specified
* \a axis of the volume.
* The \a index parameter specifies the subtexture to set.
* The source \a image must be in the format specified by the textureFormat property if
* textureFormat is indexed. If textureFormat is QImage::Format_ARGB32, the image is converted
* to that format. The image must have the size of the cross-section of the volume texture along
* the specified axis. The orientation of the image should correspond to the orientation of
* the slice image produced by renderSlice() method along the same axis.
*
* \note Each x-dimension line of the data needs to be 32-bit aligned when
* targeting the y-axis or z-axis. If textureFormat is QImage::Format_Indexed8
* and the textureWidth value is not divisible by four, padding bytes might need
* to be added to each x-dimension line of the image to properly align it. The
* padding bytes should indicate a fully transparent color to avoid rendering
* artifacts. It is not guaranteed that QImage will do this automatically.
*
* \sa textureData, renderSlice()
*/
void QCustom3DVolume::setSubTextureData(Qt::Axis axis, int index, const QImage &image)
{
int sourceWidth = image.width();
int sourceHeight = image.height();
int targetWidth;
int targetHeight;
if (axis == Qt::XAxis) {
targetWidth = dptr()->m_textureDepth;
targetHeight = dptr()->m_textureHeight;
} else if (axis == Qt::YAxis) {
targetWidth = dptr()->m_textureWidth;
targetHeight = dptr()->m_textureDepth;
} else {
targetWidth = dptr()->m_textureWidth;
targetHeight = dptr()->m_textureHeight;
}
if (sourceWidth == targetWidth
&& sourceHeight == targetHeight
&& (image.format() == dptr()->m_textureFormat
|| dptr()->m_textureFormat == QImage::Format_ARGB32)) {
QImage convertedImage;
if (dptr()->m_textureFormat == QImage::Format_ARGB32
&& image.format() != QImage::Format_ARGB32) {
convertedImage = image.convertToFormat(QImage::Format_ARGB32);
} else {
convertedImage = image;
}
setSubTextureData(axis, index, convertedImage.bits());
} else {
qWarning() << __FUNCTION__ << "Invalid image size or format.";
}
}
// Note: textureFormat is not a Q_PROPERTY to work around an issue in meta object system that
// doesn't allow QImage::format to be a property type. Qt 5.2.1 at least has this problem.
/*!
* Sets the format of the textureData property to \a format. Only two formats
* are supported currently:
* QImage::Format_Indexed8 and QImage::Format_ARGB32. If an indexed format is specified, colorTable
* must also be set.
* Defaults to QImage::Format_ARGB32.
*
* \sa colorTable, textureData
*/
void QCustom3DVolume::setTextureFormat(QImage::Format format)
{
if (format == QImage::Format_ARGB32 || format == QImage::Format_Indexed8) {
if (dptr()->m_textureFormat != format) {
dptr()->m_textureFormat = format;
dptr()->m_dirtyBitsVolume.textureFormatDirty = true;
emit textureFormatChanged(format);
emit dptr()->needUpdate();
}
} else {
qWarning() << __FUNCTION__ << "Attempted to set invalid texture format.";
}
}
/*!
* Returns the format of the textureData property value.
*
* \sa setTextureFormat()
*/
QImage::Format QCustom3DVolume::textureFormat() const
{
return dptrc()->m_textureFormat;
}
/*!
* \fn void QCustom3DVolume::textureFormatChanged(QImage::Format format)
*
* This signal is emitted when the \a format of the textureData value changes.
*
* \sa setTextureFormat()
*/
/*!
* \property QCustom3DVolume::alphaMultiplier
*
* \brief The value that the alpha value of every texel of the volume texture is multiplied with at
* the render time.
*
* This property can be used to introduce uniform transparency to the volume.
* If preserveOpacity is \c{true}, only texels with at least some transparency to begin with are
* affected, and fully opaque texels are not affected.
* The value must not be negative.
* Defaults to \c{1.0f}.
*
* \sa preserveOpacity, textureData
*/
void QCustom3DVolume::setAlphaMultiplier(float mult)
{
if (mult >= 0.0f) {
if (dptr()->m_alphaMultiplier != mult) {
dptr()->m_alphaMultiplier = mult;
dptr()->m_dirtyBitsVolume.alphaDirty = true;
emit alphaMultiplierChanged(mult);
emit dptr()->needUpdate();
}
} else {
qWarning() << __FUNCTION__ << "Attempted to set negative multiplier.";
}
}
float QCustom3DVolume::alphaMultiplier() const
{
return dptrc()->m_alphaMultiplier;
}
/*!
* \property QCustom3DVolume::preserveOpacity
*
* \brief Whether the alpha multiplier is applied to all texels.
*
* If this property value is \c{true}, alphaMultiplier is only applied to texels that already have
* some transparency. If it is \c{false}, the multiplier is applied to the alpha value of all
* texels.
* Defaults to \c{true}.
*
* \sa alphaMultiplier
*/
void QCustom3DVolume::setPreserveOpacity(bool enable)
{
if (dptr()->m_preserveOpacity != enable) {
dptr()->m_preserveOpacity = enable;
dptr()->m_dirtyBitsVolume.alphaDirty = true;
emit preserveOpacityChanged(enable);
emit dptr()->needUpdate();
}
}
bool QCustom3DVolume::preserveOpacity() const
{
return dptrc()->m_preserveOpacity;
}
/*!
* \property QCustom3DVolume::useHighDefShader
*
* \brief Whether a high or low definition shader is used to render the volume.
*
* If this property value is \c{true}, a high definition shader is used.
* If it is \c{false}, a low definition shader is used.
*
* The high definition shader guarantees that every visible texel of the volume texture is sampled
* when the volume is rendered.
* The low definition shader renders only a rough approximation of the volume contents,
* but at a much higher frame rate. The low definition shader does not guarantee
* that every texel of the
* volume texture is sampled, so there may be flickering if the volume contains distinct thin
* features.
*
* \note This value does not affect the level of detail when rendering the
* slices of the volume.
*
* Defaults to \c{true}.
*
* \sa renderSlice()
*/
void QCustom3DVolume::setUseHighDefShader(bool enable)
{
if (dptr()->m_useHighDefShader != enable) {
dptr()->m_useHighDefShader = enable;
dptr()->m_dirtyBitsVolume.shaderDirty = true;
emit useHighDefShaderChanged(enable);
emit dptr()->needUpdate();
}
}
bool QCustom3DVolume::useHighDefShader() const
{
return dptrc()->m_useHighDefShader;
}
/*!
* \property QCustom3DVolume::drawSlices
*
* \brief Whether the specified slices are drawn instead of the full volume.
*
* If this property value is \c{true}, the slices indicated by slice index properties
* will be drawn instead of the full volume.
* If it is \c{false}, the full volume will always be drawn.
* Defaults to \c{false}.
*
* \note The slices are always drawn along the item axes, so if the item is rotated, the slices are
* rotated as well.
*
* \sa sliceIndexX, sliceIndexY, sliceIndexZ
*/
void QCustom3DVolume::setDrawSlices(bool enable)
{
if (dptr()->m_drawSlices != enable) {
dptr()->m_drawSlices = enable;
dptr()->m_dirtyBitsVolume.slicesDirty = true;
emit drawSlicesChanged(enable);
emit dptr()->needUpdate();
}
}
bool QCustom3DVolume::drawSlices() const
{
return dptrc()->m_drawSlices;
}
/*!
* \property QCustom3DVolume::drawSliceFrames
*
* \brief Whether slice frames are drawn around the volume.
*
* If this property value is \c{true}, the frames of slices indicated by slice index properties
* will be drawn around the volume.
* If it is \c{false}, no slice frames will be drawn.
*
* Drawing slice frames is independent of drawing slices, so you can show the full volume and
* still draw the slice frames around it. This is useful when using renderSlice() to display the
* slices outside the graph itself.
*
* Defaults to \c{false}.
*
* \sa sliceIndexX, sliceIndexY, sliceIndexZ, drawSlices, renderSlice()
*/
void QCustom3DVolume::setDrawSliceFrames(bool enable)
{
if (dptr()->m_drawSliceFrames != enable) {
dptr()->m_drawSliceFrames = enable;
dptr()->m_dirtyBitsVolume.slicesDirty = true;
emit drawSliceFramesChanged(enable);
emit dptr()->needUpdate();
}
}
bool QCustom3DVolume::drawSliceFrames() const
{
return dptrc()->m_drawSliceFrames;
}
/*!
* \property QCustom3DVolume::sliceFrameColor
*
* \brief The color of the slice frame.
*
* Transparent slice frame color is not supported.
*
* Defaults to black.
*
* \sa drawSliceFrames
*/
void QCustom3DVolume::setSliceFrameColor(const QColor &color)
{
if (dptr()->m_sliceFrameColor != color) {
dptr()->m_sliceFrameColor = color;
dptr()->m_dirtyBitsVolume.slicesDirty = true;
emit sliceFrameColorChanged(color);
emit dptr()->needUpdate();
}
}
QColor QCustom3DVolume::sliceFrameColor() const
{
return dptrc()->m_sliceFrameColor;
}
/*!
* \property QCustom3DVolume::sliceFrameWidths
*
* \brief The width of the slice frame.
*
* The width can be different on different dimensions,
* so you can for example omit drawing the frames on certain sides of the volume by setting the
* value for that dimension to zero. The values are fractions of the volume thickness in the same
* dimension. The values cannot be negative.
*
* Defaults to \c{QVector3D(0.01, 0.01, 0.01)}.
*
* \sa drawSliceFrames
*/
void QCustom3DVolume::setSliceFrameWidths(const QVector3D &values)
{
if (values.x() < 0.0f || values.y() < 0.0f || values.z() < 0.0f) {
qWarning() << __FUNCTION__ << "Attempted to set negative values.";
} else if (dptr()->m_sliceFrameWidths != values) {
dptr()->m_sliceFrameWidths = values;
dptr()->m_dirtyBitsVolume.slicesDirty = true;
emit sliceFrameWidthsChanged(values);
emit dptr()->needUpdate();
}
}
QVector3D QCustom3DVolume::sliceFrameWidths() const
{
return dptrc()->m_sliceFrameWidths;
}
/*!
* \property QCustom3DVolume::sliceFrameGaps
*
* \brief The size of the air gap left between the volume itself and the frame
* in each dimension.
*
* The gap can be different on different dimensions. The values are fractions of the volume
* thickness in the same dimension. The values cannot be negative.
*
* Defaults to \c{QVector3D(0.01, 0.01, 0.01)}.
*
* \sa drawSliceFrames
*/
void QCustom3DVolume::setSliceFrameGaps(const QVector3D &values)
{
if (values.x() < 0.0f || values.y() < 0.0f || values.z() < 0.0f) {
qWarning() << __FUNCTION__ << "Attempted to set negative values.";
} else if (dptr()->m_sliceFrameGaps != values) {
dptr()->m_sliceFrameGaps = values;
dptr()->m_dirtyBitsVolume.slicesDirty = true;
emit sliceFrameGapsChanged(values);
emit dptr()->needUpdate();
}
}
QVector3D QCustom3DVolume::sliceFrameGaps() const
{
return dptrc()->m_sliceFrameGaps;
}
/*!
* \property QCustom3DVolume::sliceFrameThicknesses
*
* \brief The thickness of the slice frames for each dimension.
*
* The values are fractions of
* the volume thickness in the same dimension. The values cannot be negative.
*
* Defaults to \c{QVector3D(0.01, 0.01, 0.01)}.
*
* \sa drawSliceFrames
*/
void QCustom3DVolume::setSliceFrameThicknesses(const QVector3D &values)
{
if (values.x() < 0.0f || values.y() < 0.0f || values.z() < 0.0f) {
qWarning() << __FUNCTION__ << "Attempted to set negative values.";
} else if (dptr()->m_sliceFrameThicknesses != values) {
dptr()->m_sliceFrameThicknesses = values;
dptr()->m_dirtyBitsVolume.slicesDirty = true;
emit sliceFrameThicknessesChanged(values);
emit dptr()->needUpdate();
}
}
QVector3D QCustom3DVolume::sliceFrameThicknesses() const
{
return dptrc()->m_sliceFrameThicknesses;
}
/*!
* Renders the slice specified by \a index along the axis specified by \a axis
* into an image.
* The texture format of this object is used.
*
* Returns the rendered image of the slice, or a null image if an invalid index is
* specified.
*
* \sa setTextureFormat()
*/
QImage QCustom3DVolume::renderSlice(Qt::Axis axis, int index)
{
return dptr()->renderSlice(axis, index);
}
/*!
* \internal
*/
QCustom3DVolumePrivate *QCustom3DVolume::dptr()
{
return static_cast<QCustom3DVolumePrivate *>(d_ptr.data());
}
/*!
* \internal
*/
const QCustom3DVolumePrivate *QCustom3DVolume::dptrc() const
{
return static_cast<const QCustom3DVolumePrivate *>(d_ptr.data());
}
QCustom3DVolumePrivate::QCustom3DVolumePrivate(QCustom3DVolume *q) :
QCustom3DItemPrivate(q),
m_textureWidth(0),
m_textureHeight(0),
m_textureDepth(0),
m_sliceIndexX(-1),
m_sliceIndexY(-1),
m_sliceIndexZ(-1),
m_textureFormat(QImage::Format_ARGB32),
m_textureData(0),
m_alphaMultiplier(1.0f),
m_preserveOpacity(true),
m_useHighDefShader(true),
m_drawSlices(false),
m_drawSliceFrames(false),
m_sliceFrameColor(Qt::black),
m_sliceFrameWidths(QVector3D(0.01f, 0.01f, 0.01f)),
m_sliceFrameGaps(QVector3D(0.01f, 0.01f, 0.01f)),
m_sliceFrameThicknesses(QVector3D(0.01f, 0.01f, 0.01f))
{
m_isVolumeItem = true;
m_meshFile = QStringLiteral(":/defaultMeshes/barFull");
}
QCustom3DVolumePrivate::QCustom3DVolumePrivate(QCustom3DVolume *q, const QVector3D &position,
const QVector3D &scaling,
const QQuaternion &rotation,
int textureWidth, int textureHeight,
int textureDepth, QVector<uchar> *textureData,
QImage::Format textureFormat,
const QVector<QRgb> &colorTable) :
QCustom3DItemPrivate(q, QStringLiteral(":/defaultMeshes/barFull"), position, scaling, rotation),
m_textureWidth(textureWidth),
m_textureHeight(textureHeight),
m_textureDepth(textureDepth),
m_sliceIndexX(-1),
m_sliceIndexY(-1),
m_sliceIndexZ(-1),
m_textureFormat(textureFormat),
m_colorTable(colorTable),
m_textureData(textureData),
m_alphaMultiplier(1.0f),
m_preserveOpacity(true),
m_useHighDefShader(true),
m_drawSlices(false),
m_drawSliceFrames(false),
m_sliceFrameColor(Qt::black),
m_sliceFrameWidths(QVector3D(0.01f, 0.01f, 0.01f)),
m_sliceFrameGaps(QVector3D(0.01f, 0.01f, 0.01f)),
m_sliceFrameThicknesses(QVector3D(0.01f, 0.01f, 0.01f))
{
m_isVolumeItem = true;
m_shadowCasting = false;
if (m_textureWidth < 0)
m_textureWidth = 0;
if (m_textureHeight < 0)
m_textureHeight = 0;
if (m_textureDepth < 0)
m_textureDepth = 0;
if (m_textureFormat != QImage::Format_Indexed8)
m_textureFormat = QImage::Format_ARGB32;
}
QCustom3DVolumePrivate::~QCustom3DVolumePrivate()
{
delete m_textureData;
}
void QCustom3DVolumePrivate::resetDirtyBits()
{
QCustom3DItemPrivate::resetDirtyBits();
m_dirtyBitsVolume.textureDimensionsDirty = false;
m_dirtyBitsVolume.slicesDirty = false;
m_dirtyBitsVolume.colorTableDirty = false;
m_dirtyBitsVolume.textureDataDirty = false;
m_dirtyBitsVolume.textureFormatDirty = false;
m_dirtyBitsVolume.alphaDirty = false;
m_dirtyBitsVolume.shaderDirty = false;
}
QImage QCustom3DVolumePrivate::renderSlice(Qt::Axis axis, int index)
{
if (index < 0)
return QImage();
int x;
int y;
if (axis == Qt::XAxis) {
if (index >= m_textureWidth)
return QImage();
x = m_textureDepth;
y = m_textureHeight;
} else if (axis == Qt::YAxis) {
if (index >= m_textureHeight)
return QImage();
x = m_textureWidth;
y = m_textureDepth;
} else {
if (index >= m_textureDepth)
return QImage();
x = m_textureWidth;
y = m_textureHeight;
}
int padding = 0;
int pixelWidth = 4;
int dataWidth = qptr()->textureDataWidth();
if (m_textureFormat == QImage::Format_Indexed8) {
padding = x % 4;
pixelWidth = 1;
}
QVector<uchar> data((x + padding) * y * pixelWidth);
int frameSize = qptr()->textureDataWidth() * m_textureHeight;
int dataIndex = 0;
if (axis == Qt::XAxis) {
for (int i = 0; i < y; i++) {
const uchar *p = m_textureData->constData()
+ (index * pixelWidth) + (dataWidth * i);
for (int j = 0; j < x; j++) {
for (int k = 0; k < pixelWidth; k++)
data[dataIndex++] = *(p + k);
p += frameSize;
}
}
} else if (axis == Qt::YAxis) {
for (int i = y - 1; i >= 0; i--) {
const uchar *p = m_textureData->constData() + (index * dataWidth)
+ (frameSize * i);
for (int j = 0; j < (x * pixelWidth); j++) {
data[dataIndex++] = *p;
p++;
}
}
} else {
for (int i = 0; i < y; i++) {
const uchar *p = m_textureData->constData() + (index * frameSize) + (dataWidth * i);
for (int j = 0; j < (x * pixelWidth); j++) {
data[dataIndex++] = *p;
p++;
}
}
}
if (m_textureFormat != QImage::Format_Indexed8 && m_alphaMultiplier != 1.0f) {
for (int i = pixelWidth - 1; i < data.size(); i += pixelWidth)
data[i] = static_cast<uchar>(multipliedAlphaValue(data.at(i)));
}
QImage image(data.constData(), x, y, x * pixelWidth, m_textureFormat);
image.bits(); // Call bits() to detach the new image from local data
if (m_textureFormat == QImage::Format_Indexed8) {
QVector<QRgb> colorTable = m_colorTable;
if (m_alphaMultiplier != 1.0f) {
for (int i = 0; i < colorTable.size(); i++) {
QRgb curCol = colorTable.at(i);
int alpha = multipliedAlphaValue(qAlpha(curCol));
if (alpha != qAlpha(curCol))
colorTable[i] = qRgba(qRed(curCol), qGreen(curCol), qBlue(curCol), alpha);
}
}
image.setColorTable(colorTable);
}
return image;
}
int QCustom3DVolumePrivate::multipliedAlphaValue(int alpha)
{
int modifiedAlpha = alpha;
if (!m_preserveOpacity || alpha != 255) {
modifiedAlpha = int(m_alphaMultiplier * float(alpha));
modifiedAlpha = qMin(modifiedAlpha, 255);
}
return modifiedAlpha;
}
QCustom3DVolume *QCustom3DVolumePrivate::qptr()
{
return static_cast<QCustom3DVolume *>(q_ptr);
}
QT_END_NAMESPACE_DATAVISUALIZATION