From 28777a99f79bc9db1a28e1b93080b005be03353b Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Wed, 3 Sep 2014 13:37:31 +0300 Subject: Allow setting volume subtexture along any axis Change-Id: Iafaac6bd2106253bec913d1d9ee8a3f40e339adf Reviewed-by: Mika Salmela --- src/datavisualization/data/qcustom3dvolume.cpp | 130 +++++++++++++++++++------ src/datavisualization/data/qcustom3dvolume.h | 4 +- tests/volumetrictest/logo_no_padding.png | Bin 0 -> 2278 bytes tests/volumetrictest/main.cpp | 10 +- tests/volumetrictest/volumetrictest.cpp | 74 ++++++++++++-- tests/volumetrictest/volumetrictest.h | 3 + tests/volumetrictest/volumetrictest.qrc | 1 + 7 files changed, 183 insertions(+), 39 deletions(-) create mode 100644 tests/volumetrictest/logo_no_padding.png diff --git a/src/datavisualization/data/qcustom3dvolume.cpp b/src/datavisualization/data/qcustom3dvolume.cpp index ec5129ff..393533c0 100644 --- a/src/datavisualization/data/qcustom3dvolume.cpp +++ b/src/datavisualization/data/qcustom3dvolume.cpp @@ -428,7 +428,8 @@ QVector QCustom3DVolume::colorTable() const * \note Each X-line of the data needs to be 32bit aligned. If the textureFormat is * QImage::Format_Indexed8 and textureWidth is not divisible by four, padding bytes need * to be added to each X-line of the \a data. You can get the padded byte count with - * textureDataWidth() function. + * textureDataWidth() function. The padding bytes should indicate an fully transparent color + * to avoid rendering artifacts. * * Defaults to \c{0}. * @@ -531,29 +532,76 @@ QVector *QCustom3DVolume::textureData() const } /*! - * This function allows setting a single 2D subtexture of the 3D texture. - * The \a depthIndex parameter specifies the subtexture to set. - * The texture\a data must be in the format specified by textureFormat property and have size of - * (\c{textureDataWidth * textureHeight * texture format color depth in bytes}). + * This function allows setting 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 textureFormat property and have 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 renderSlice() + * method along the same axis. * - * \note Each X-line of the data needs to be 32bit aligned. If the textureFormat is - * QImage::Format_Indexed8 and textureWidth is not divisible by four, padding bytes need - * to be added to each X-line of the \a data. + * \note Each X-line of the data needs to be 32bit aligned when targeting Y-axis or Z-axis. + * If the textureFormat is QImage::Format_Indexed8 and textureWidth is not divisible by four, + * padding bytes need to be added to each X-line of the \a data in cases it is not already + * properly aligned. The padding bytes should indicate an fully transparent color to avoid + * rendering artifacts. * - * \sa textureData + * \sa textureData, renderSlice() */ -void QCustom3DVolume::setSubTextureData(int depthIndex, const uchar *data) +void QCustom3DVolume::setSubTextureData(Qt::Axis axis, int index, const uchar *data) { if (data) { - int frameSize = textureDataWidth() * dptr()->m_textureHeight; - int startIndex = depthIndex * frameSize; + 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 (depthIndex >= dptr()->m_textureDepth - || (startIndex + frameSize) > dptr()->m_textureData->size()) { + if (invalid) { qWarning() << __FUNCTION__ << "Attempted to set invalid subtexture."; } else { - void *subTexPtr = dptr()->m_textureData->data() + startIndex; - memcpy(subTexPtr, static_cast(data), frameSize); + 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(data), frameSize); + } dptr()->m_dirtyBitsVolume.textureDataDirty = true; emit textureDataChanged(dptr()->m_textureData); emit dptr()->needUpdate(); @@ -564,18 +612,42 @@ void QCustom3DVolume::setSubTextureData(int depthIndex, const uchar *data) } /*! - * This function allows setting a single 2D subtexture of the 3D texture to a source \a image; - * The \a depthIndex parameter specifies the subtexture to set. - * The image must be in the format specified by the textureFormat property if the textureFormat - * is indexed. If the textureFormat is QImage::Format_ARGB32, the image is converted to that format. - * The image must have the size of (\c{textureWidth * textureHeight}). - * - * \sa textureData - */ -void QCustom3DVolume::setSubTextureData(int depthIndex, const QImage &image) -{ - if (image.width() == dptr()->m_textureWidth - && image.height() == dptr()->m_textureHeight + * This function allows setting 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 the + * textureFormat is indexed. If the 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-line of the data needs to be 32bit aligned when targeting Y-axis or Z-axis. + * If the textureFormat is QImage::Format_Indexed8 and textureWidth is not divisible by four, + * padding bytes need to be added to each X-line of the \a data in cases it is not already + * properly aligned. The padding bytes should indicate an fully transparent color to avoid + * rendering artifacts. It is not guaranteed 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; @@ -585,7 +657,7 @@ void QCustom3DVolume::setSubTextureData(int depthIndex, const QImage &image) } else { convertedImage = image; } - setSubTextureData(depthIndex, convertedImage.bits()); + setSubTextureData(axis, index, convertedImage.bits()); } else { qWarning() << __FUNCTION__ << "Invalid image size or format."; } diff --git a/src/datavisualization/data/qcustom3dvolume.h b/src/datavisualization/data/qcustom3dvolume.h index 23ae07d9..04434263 100644 --- a/src/datavisualization/data/qcustom3dvolume.h +++ b/src/datavisualization/data/qcustom3dvolume.h @@ -76,8 +76,8 @@ public: void setTextureData(QVector *data); QVector *createTextureData(const QVector &images); QVector *textureData() const; - void setSubTextureData(int depthIndex, const uchar *data); - void setSubTextureData(int depthIndex, const QImage &image); + void setSubTextureData(Qt::Axis axis, int index, const uchar *data); + void setSubTextureData(Qt::Axis axis, int index, const QImage &image); void setTextureFormat(QImage::Format format); QImage::Format textureFormat() const; diff --git a/tests/volumetrictest/logo_no_padding.png b/tests/volumetrictest/logo_no_padding.png new file mode 100644 index 00000000..714234aa Binary files /dev/null and b/tests/volumetrictest/logo_no_padding.png differ diff --git a/tests/volumetrictest/main.cpp b/tests/volumetrictest/main.cpp index e838c43a..ba5ba6d3 100644 --- a/tests/volumetrictest/main.cpp +++ b/tests/volumetrictest/main.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include int main(int argc, char **argv) @@ -93,6 +94,9 @@ int main(int argc, char **argv) sliceImageYLabel->setScaledContents(true); sliceImageZLabel->setScaledContents(true); + QPushButton *testSubTextureSetting = new QPushButton(widget); + testSubTextureSetting->setText(QStringLiteral("Test subtexture settings")); + vLayout->addWidget(fpsLabel); vLayout->addWidget(sliceXCheckBox); vLayout->addWidget(sliceXSlider); @@ -102,8 +106,8 @@ int main(int argc, char **argv) vLayout->addWidget(sliceImageYLabel); vLayout->addWidget(sliceZCheckBox); vLayout->addWidget(sliceZSlider); - vLayout->addWidget(sliceImageZLabel, 1, Qt::AlignTop); - + vLayout->addWidget(sliceImageZLabel); + vLayout->addWidget(testSubTextureSetting, 1, Qt::AlignTop); VolumetricModifier *modifier = new VolumetricModifier(graph); modifier->setFpsLabel(fpsLabel); @@ -121,6 +125,8 @@ int main(int argc, char **argv) &VolumetricModifier::adjustSliceY); QObject::connect(sliceZSlider, &QSlider::valueChanged, modifier, &VolumetricModifier::adjustSliceZ); + QObject::connect(testSubTextureSetting, &QPushButton::clicked, modifier, + &VolumetricModifier::testSubtextureSetting); widget->show(); return app.exec(); diff --git a/tests/volumetrictest/volumetrictest.cpp b/tests/volumetrictest/volumetrictest.cpp index 53b0a875..555cc286 100644 --- a/tests/volumetrictest/volumetrictest.cpp +++ b/tests/volumetrictest/volumetrictest.cpp @@ -168,7 +168,67 @@ void VolumetricModifier::handleFpsChange() // const QString sceneDimensionsFormat = QStringLiteral("%1 x %2"); // m_fpsLabel->setText(sceneDimensionsFormat // .arg(m_graph->scene()->viewport().width()) -// .arg(m_graph->scene()->viewport().height())); + // .arg(m_graph->scene()->viewport().height())); +} + +void VolumetricModifier::testSubtextureSetting() +{ + // Setting the rendered Slice as subtexture should result in identical volume + QVector dataBefore = *m_volumeItem->textureData(); + dataBefore[0] = dataBefore.at(0); // Make sure we are detached + + checkRenderCase(1, Qt::XAxis, 56, dataBefore, m_volumeItem); + checkRenderCase(2, Qt::YAxis, 64, dataBefore, m_volumeItem); + checkRenderCase(3, Qt::ZAxis, 87, dataBefore, m_volumeItem); + + checkRenderCase(4, Qt::XAxis, 0, dataBefore, m_volumeItem); + checkRenderCase(5, Qt::YAxis, 0, dataBefore, m_volumeItem); + checkRenderCase(6, Qt::ZAxis, 0, dataBefore, m_volumeItem); + + checkRenderCase(7, Qt::XAxis, m_volumeItem->textureWidth() - 1, dataBefore, m_volumeItem); + checkRenderCase(8, Qt::YAxis, m_volumeItem->textureHeight() - 1, dataBefore, m_volumeItem); + checkRenderCase(9, Qt::ZAxis, m_volumeItem->textureDepth() - 1, dataBefore, m_volumeItem); + + dataBefore = *m_volumeItem2->textureData(); + dataBefore[0] = dataBefore.at(0); // Make sure we are detached + + checkRenderCase(11, Qt::XAxis, 56, dataBefore, m_volumeItem2); + checkRenderCase(12, Qt::YAxis, 64, dataBefore, m_volumeItem2); + checkRenderCase(13, Qt::ZAxis, 87, dataBefore, m_volumeItem2); + + checkRenderCase(14, Qt::XAxis, 0, dataBefore, m_volumeItem2); + checkRenderCase(15, Qt::YAxis, 0, dataBefore, m_volumeItem2); + checkRenderCase(16, Qt::ZAxis, 0, dataBefore, m_volumeItem2); + + checkRenderCase(17, Qt::XAxis, m_volumeItem2->textureWidth() - 1, dataBefore, m_volumeItem2); + checkRenderCase(18, Qt::YAxis, m_volumeItem2->textureHeight() - 1, dataBefore, m_volumeItem2); + checkRenderCase(19, Qt::ZAxis, m_volumeItem2->textureDepth() - 1, dataBefore, m_volumeItem2); + + // Do some visible swaps on volume 3 + QImage slice = m_volumeItem3->renderSlice(Qt::XAxis, 144); + slice = slice.mirrored(); + m_volumeItem3->setSubTextureData(Qt::XAxis, 144, slice); + + slice = m_volumeItem3->renderSlice(Qt::YAxis, 80); + slice = slice.mirrored(); + m_volumeItem3->setSubTextureData(Qt::YAxis, 80, slice); + + slice = m_volumeItem3->renderSlice(Qt::ZAxis, 190); + slice = slice.mirrored(true, false); + m_volumeItem3->setSubTextureData(Qt::ZAxis, 190, slice); +} + +void VolumetricModifier::checkRenderCase(int id, Qt::Axis axis, int index, + const QVector &dataBefore, + QCustom3DVolume *volumeItem) +{ + QImage slice = volumeItem->renderSlice(axis, index); + volumeItem->setSubTextureData(axis, index, slice); + + if (dataBefore == *volumeItem->textureData()) + qDebug() << __FUNCTION__ << "Case:" << id << "Vectors identical"; + else + qDebug() << __FUNCTION__ << "Case:" << id << "BUG: VECTORS DIFFER!"; } void VolumetricModifier::createVolume() @@ -179,7 +239,8 @@ void VolumetricModifier::createVolume() m_volumeItem->setPosition(QVector3D(-0.5f, 0.6f, 0.0f)); QImage logo; - logo.load(QStringLiteral(":/logo.png")); + logo.load(QStringLiteral(":/logo_no_padding.png")); + //logo.load(QStringLiteral(":/logo.png")); qDebug() << "image dimensions:" << logo.width() << logo.height() << logo.byteCount() << (logo.width() * logo.height()) << logo.bytesPerLine(); @@ -233,7 +294,7 @@ void VolumetricModifier::createVolume() // Change one picture using subtexture replacement QImage flipped = logo.mirrored(); - m_volumeItem->setSubTextureData(100, flipped); + m_volumeItem->setSubTextureData(Qt::ZAxis, 100, flipped); // Clean up the two extra pixels p = data + width - 1; @@ -331,7 +392,8 @@ void VolumetricModifier::createAnotherVolume() m_volumeItem2->setPosition(QVector3D(0.5f, -0.5f, 0.0f)); QImage logo; - logo.load(QStringLiteral(":/logo.png")); + logo.load(QStringLiteral(":/logo_no_padding.png")); + //logo.load(QStringLiteral(":/logo.png")); logo = logo.convertToFormat(QImage::Format_ARGB8555_Premultiplied); qDebug() << "second image dimensions:" << logo.width() << logo.height() << logo.byteCount() << (logo.width() * logo.height()) @@ -361,8 +423,8 @@ void VolumetricModifier::createAnotherVolume() // Change one picture using subtexture replacement QImage flipped = logo.mirrored(); - m_volumeItem2->setSubTextureData(100, flipped); - m_volumeItem2->setAlphaMultiplier(0.2f); + m_volumeItem2->setSubTextureData(Qt::ZAxis, 100, flipped); + //m_volumeItem2->setAlphaMultiplier(0.2f); m_volumeItem2->setPreserveOpacity(false); } diff --git a/tests/volumetrictest/volumetrictest.h b/tests/volumetrictest/volumetrictest.h index f21fd528..b1b98455 100644 --- a/tests/volumetrictest/volumetrictest.h +++ b/tests/volumetrictest/volumetrictest.h @@ -45,11 +45,14 @@ public slots: void adjustSliceY(int value); void adjustSliceZ(int value); void handleFpsChange(); + void testSubtextureSetting(); private: void createVolume(); void createAnotherVolume(); void createYetAnotherVolume(); + void checkRenderCase(int id, Qt::Axis axis, int index, const QVector &dataBefore, + QCustom3DVolume *volumeItem); Q3DScatter *m_graph; QCustom3DVolume *m_volumeItem; diff --git a/tests/volumetrictest/volumetrictest.qrc b/tests/volumetrictest/volumetrictest.qrc index 5b9623f0..7cd8533b 100644 --- a/tests/volumetrictest/volumetrictest.qrc +++ b/tests/volumetrictest/volumetrictest.qrc @@ -2,5 +2,6 @@ logo.png cubeFilledFlat.obj + logo_no_padding.png -- cgit v1.2.3