diff --git a/DVRTransferFunction/src/InteractiveShape.cpp b/DVRTransferFunction/src/InteractiveShape.cpp index c6b33f5..e5f6252 100644 --- a/DVRTransferFunction/src/InteractiveShape.cpp +++ b/DVRTransferFunction/src/InteractiveShape.cpp @@ -1,8 +1,15 @@ #include "InteractiveShape.h" + +#include + #include -InteractiveShape::InteractiveShape(const QPixmap& pixmap, const QRectF& rect, const QRect& bounds, QColor pixmapColor, float globalAlphaValue, qreal threshold) - : _pixmap(pixmap), _rect(rect), _bounds(bounds), _isSelected(false), _pixmapColor(pixmapColor), _globalAlphaValue(globalAlphaValue), _threshold(threshold) { +InteractiveShape::InteractiveShape(const QPixmap& pixmap, const QRectF& rect, const QRect& bounds, const QColor& pixmapColor, + int globalAlphaValue, mv::gui::PointRenderer* pointRenderer, qreal threshold): + _pixmap(pixmap), _rect(rect), _bounds(bounds), _isSelected(false), + _threshold(threshold), _pixmapColor(pixmapColor), _globalAlphaValue(globalAlphaValue), _pointRenderer(pointRenderer) +{ + assert(pointRenderer); _mask = _pixmap.createMaskFromColor(Qt::transparent); _gradient1D = QImage(":textures/gaussian1D_texture", ".png"); @@ -15,8 +22,10 @@ InteractiveShape::InteractiveShape(const QPixmap& pixmap, const QRectF& rect, co setGlobalAlphaValue(globalAlphaValue); // This will create the globalAlphaPixmap } -void InteractiveShape::draw(QPainter& painter, bool drawBorder, bool useGlobalAlpha, bool normalizeWindow /*true*/, QColor borderColor /* Black */) const { - const QRectF adjustedRect = normalizeWindow ? getRelativeRect() : getAbsoluteRect(); +void InteractiveShape::draw(QPainter& painter, bool drawBorder, bool useGlobalAlpha, bool normalizeWindow , QColor borderColor /* Black */, int scaleTo /* 0 */) const { + const QRectF adjustedRect = normalizeWindow ? getRelativeRect() : getAdjustedWorldRect(scaleTo); + const QPixmap& pixmap = useGlobalAlpha ? _globalAlphaColormap : _pixmap; + if (drawBorder) { QPen pen(borderColor); pen.setWidth(2); @@ -29,18 +38,13 @@ void InteractiveShape::draw(QPainter& painter, bool drawBorder, bool useGlobalAl } painter.setCompositionMode(QPainter::CompositionMode_SourceOver); - if (useGlobalAlpha) { - painter.drawPixmap(adjustedRect.toRect(), _globalAlphaColormap); - } - else { - painter.drawPixmap(adjustedRect.toRect(), _pixmap); - } + painter.drawPixmap(adjustedRect.toRect(), pixmap); } -void InteractiveShape::drawID(QPainter& painter, bool normalizeWindow, int id) const { - const QRectF adjustedRect = normalizeWindow ? getRelativeRect() : getAbsoluteRect(); +void InteractiveShape::drawID(QPainter& painter, bool normalizeWindow, int id, int scaleTo) const { + const QRectF adjustedRect = normalizeWindow ? getRelativeRect() : getAdjustedWorldRect(scaleTo); - painter.setCompositionMode(QPainter::CompositionMode_SourceOver); + painter.setCompositionMode(QPainter::CompositionMode_SourceOver); QPixmap newPixmap(_pixmap.size()); newPixmap.fill(Qt::transparent); @@ -75,6 +79,7 @@ void InteractiveShape::resizeBy(const QPointF& delta, SelectedSide& side) { case SelectedSide::Bottom: _rect.setBottom(_rect.bottom() + delta.y() / _bounds.height()); break; + case SelectedSide::None: [[fallthrough]]; default: break; } @@ -154,7 +159,7 @@ QColor InteractiveShape::getColor() const { } bool InteractiveShape::isNearTopRightCorner(const QPointF& point) const { - QPointF topRight = getRelativeRect().topRight(); + const QPointF topRight = getRelativeRect().topRight(); return (std::abs(point.x() - topRight.x()) <= _threshold && std::abs(point.y() - topRight.y()) <= _threshold); } @@ -166,7 +171,7 @@ void InteractiveShape::setBounds(const QRect& bounds) { _bounds = bounds; } -void InteractiveShape::updateGradient(gradientData data) +void InteractiveShape::updateGradient(const gradientData& data) { _gradientData = data; if (_gradientData.gradient) { @@ -179,11 +184,11 @@ void InteractiveShape::updateGradient(gradientData data) qCritical() << "Unknown texture ID: currently only 0 and 1 exist"; return; } - QSize pixmapSize = _pixmap.size(); - float biggestFit = std::max(gradient.width() / pixmapSize.width(), gradient.height() / pixmapSize.height()); + const QSize pixmapSize = _pixmap.size(); + const float biggestFit = std::max(gradient.width() / pixmapSize.width(), gradient.height() / pixmapSize.height()); gradient = gradient.copy(QRect((_gradientData.xOffset + ((1 - _gradientData.width) / 2)) * gradient.width(), (_gradientData.yOffset + ((1 - _gradientData.height) / 2)) * gradient.height(), _gradientData.width * gradient.width(), _gradientData.height * gradient.height())); - gradient.scaled(_pixmap.size() * biggestFit); //scale to the ratio of the pixmap to simulate gradient width and height + gradient.scaled(pixmapSize * biggestFit); //scale to the ratio of the pixmap to simulate gradient width and height gradient = gradient.transformed(QTransform().rotate(_gradientData.rotation)); //gradient = gradient.copy(QRect((gradient.width() - _pixmap.width()) / 2, (gradient.height() - _pixmap.height()) / 2, _pixmap.width(), _pixmap.height())); @@ -244,20 +249,56 @@ void InteractiveShape::updatePixmap() } } +QRectF InteractiveShape::getAdjustedWorldRect(int scaleTo) const { + + const auto left = -1.f * _pointRenderer->getDataBounds().left(); + const auto top = -1.f * _pointRenderer->getDataBounds().top(); + auto adjustedWorldRec = getWorldRect().adjusted(left, top, left, top); + + const auto dataExtendX = _pointRenderer->getDataBounds().width(); + const auto dataExtendY = _pointRenderer->getDataBounds().height(); + + // Ensure rect is in data bounds + adjustedWorldRec.setLeft(std::clamp(adjustedWorldRec.left(), 0., dataExtendX)); + adjustedWorldRec.setTop(std::clamp(adjustedWorldRec.top(), 0., dataExtendY)); + adjustedWorldRec.setRight(std::clamp(adjustedWorldRec.right(), 0., dataExtendX)); + adjustedWorldRec.setBottom(std::clamp(adjustedWorldRec.bottom(), 0., dataExtendY)); + + // Scale to [0, scaleTo] + const auto scaleX = static_cast(scaleTo) / dataExtendX; + const auto scaleY = static_cast(scaleTo) / dataExtendY; + + adjustedWorldRec.setLeft(adjustedWorldRec.left() * scaleX); + adjustedWorldRec.setTop(adjustedWorldRec.top() * scaleY); + adjustedWorldRec.setRight(adjustedWorldRec.right() * scaleX); + adjustedWorldRec.setBottom(adjustedWorldRec.bottom() * scaleY); + + adjustedWorldRec = adjustedWorldRec.normalized(); + + return adjustedWorldRec; +} + +QRectF InteractiveShape::getWorldRect() const { + const auto relRect = getRelativeRect(); + + const auto topLeft = _pointRenderer->getScreenPointToWorldPosition(_pointRenderer->getNavigator().getViewMatrix(), relRect.topLeft().toPoint()); + const auto bottomRight = _pointRenderer->getScreenPointToWorldPosition(_pointRenderer->getNavigator().getViewMatrix(), relRect.bottomRight().toPoint()); + + return QRectF{ + QPointF{ topLeft.x(), topLeft.y() }, + QPointF{ bottomRight.x(), bottomRight.y() } + }; +} + QRectF InteractiveShape::getRelativeRect() const { - return QRectF( - _bounds.left() + _rect.left() * _bounds.width(), - _bounds.top() + _rect.top() * _bounds.height(), - _rect.width() * _bounds.width(), - _rect.height() * _bounds.height() - ); + return getAbsoluteRect().adjusted(_bounds.left(), _bounds.top(), _bounds.left(), _bounds.top()); } QRectF InteractiveShape::getAbsoluteRect() const { - return QRectF( + return { _rect.left() * _bounds.width(), _rect.top() * _bounds.height(), _rect.width() * _bounds.width(), _rect.height() * _bounds.height() - ); + }; } diff --git a/DVRTransferFunction/src/InteractiveShape.h b/DVRTransferFunction/src/InteractiveShape.h index 2efc62d..4f3c813 100644 --- a/DVRTransferFunction/src/InteractiveShape.h +++ b/DVRTransferFunction/src/InteractiveShape.h @@ -23,12 +23,17 @@ struct gradientData { int rotation; }; +namespace mv::gui +{ + class PointRenderer; +} + class InteractiveShape { public: - InteractiveShape(const QPixmap& pixmap, const QRectF& rect, const QRect& bounds, QColor pixmapColor, float globalAlphaValue, qreal threshold = 10.0); + InteractiveShape(const QPixmap& pixmap, const QRectF& rect, const QRect& bounds, const QColor& pixmapColor, int globalAlphaValue, mv::gui::PointRenderer* pointRenderer, qreal threshold = 10.0); - void draw(QPainter& painter, bool drawBorder, bool useGlobalAlpha, bool normalizeWindow = true, QColor borderColor = Qt::black) const; - void drawID(QPainter& painter, bool normalizeWindow, int id) const; + void draw(QPainter& painter, bool drawBorder, bool useGlobalAlpha, bool normalizeWindow, QColor borderColor = Qt::black, int scaleTo = 0) const; + void drawID(QPainter& painter, bool normalizeWindow, int id, int scaleTo = 0) const; bool contains(const QPointF& point) const; void moveBy(const QPointF& delta); void resizeBy(const QPointF& delta, SelectedSide& side); @@ -45,17 +50,18 @@ class InteractiveShape { void setThreshold(qreal threshold); void setBounds(const QRect& bounds); - void updateGradient(gradientData data); + void updateGradient(const gradientData& data); QImage getGradientImage() const; gradientData getGradientData() const; void setGlobalAlphaValue(int globalAlphaValue); QRectF getRelativeRect() const; + QRectF getWorldRect() const; + QRectF getAdjustedWorldRect(int scaleTo = 0) const; private: QRectF getAbsoluteRect() const; - void updatePixmap(); private: @@ -78,4 +84,6 @@ class InteractiveShape { QImage _usedGradient; gradientData _gradientData; + + mv::gui::PointRenderer* _pointRenderer = nullptr; }; diff --git a/DVRTransferFunction/src/TransferFunctionWidget.cpp b/DVRTransferFunction/src/TransferFunctionWidget.cpp index 9e6eae7..ab1f89a 100644 --- a/DVRTransferFunction/src/TransferFunctionWidget.cpp +++ b/DVRTransferFunction/src/TransferFunctionWidget.cpp @@ -78,12 +78,12 @@ TransferFunctionWidget::TransferFunctionWidget() : static_cast(_areaSelectionBounds.width()) / _boundsPointsWindow.width(), static_cast(_areaSelectionBounds.height()) / _boundsPointsWindow.height() ); - const int borderWidth = 2; + constexpr int borderWidth = 2; const QRectF adjustedBounds = _areaSelectionBounds.adjusted(borderWidth, borderWidth, -borderWidth, -borderWidth); // The areapixmap doesn't contain the borders QColor areaColor = _pixelSelectionTool.getMainColor(); areaColor.setAlpha(50); // This is the default modification of the areaColor compared to the mainColor which we can not reach but we simulate here - _interactiveShapes.push_back(InteractiveShape(_pixelSelectionTool.getAreaPixmap().copy(adjustedBounds.toRect()), relativeRect, _boundsPointsWindow, areaColor, _globalAlphaValue)); + _interactiveShapes.emplace_back(_pixelSelectionTool.getAreaPixmap().copy(adjustedBounds.toRect()), relativeRect, _boundsPointsWindow, areaColor, _globalAlphaValue, &_pointRenderer); emit shapeCreated(_interactiveShapes); _areaSelectionBounds = QRect(0, 0, 0, 0); // Invalid Rectangle set to signal that no area is selected @@ -237,9 +237,6 @@ bool TransferFunctionWidget::event(QEvent* event) return QOpenGLWidget::event(event); } - - - QRect TransferFunctionWidget::getMousePositionsBounds(QPoint newMousePosition) { if (!_areaSelectionBounds.isValid()) { _areaSelectionBounds = QRect(_mousePositions[0], _mousePositions[0]); @@ -267,20 +264,19 @@ PixelSelectionTool& TransferFunctionWidget::getPixelSelectionTool() void TransferFunctionWidget::setData(const std::vector* points) { const auto dataBounds = getDataBounds(*points); + _dataRectangleAction.setBounds(dataBounds); // pass un-adjusted data bounds to renderer for 2D colormapping const auto dataBoundsRect = QRectF(QPointF(dataBounds.getLeft(), dataBounds.getBottom()), QSizeF(dataBounds.getWidth(), dataBounds.getHeight())); _pointRenderer.setDataBounds(dataBoundsRect); + _pointRenderer.setData(*points); + _pointRenderer.getNavigator().resetView(true); - _dataRectangleAction.setBounds(dataBounds); const int w = width(); const int h = height(); const int size = w < h ? w : h; _boundsPointsWindow = QRect((w - size) / 2.0f, (h - size) / 2.0f, size, size); - _pointRenderer.setData(*points); - _pointRenderer.getNavigator().resetView(true); - update(); } @@ -423,7 +419,6 @@ void TransferFunctionWidget::paintGL() if (!_isInitialized) return; - try { QPainter painter; @@ -462,7 +457,7 @@ void TransferFunctionWidget::paintGL() shapePainter.setCompositionMode(QPainter::CompositionMode_SourceOver); for (const auto& obj : _interactiveShapes) { - obj.draw(shapePainter, true, _useGlobalAlpha); + obj.draw(shapePainter, true, _useGlobalAlpha, true); } shapePainter.end(); @@ -505,33 +500,48 @@ void TransferFunctionWidget::updateTfTexture() if (!_tfTextures.isValid()) return; + auto materialMap = QImage(_tfTextureSize, _tfTextureSize, QImage::Format_ARGB32); - auto materialMap = QImage(_boundsPointsWindow.width(), _boundsPointsWindow.height(), QImage::Format_ARGB32); QPainter painter(&materialMap); painter.setCompositionMode(QPainter::CompositionMode_SourceOver); for (const auto& obj : _interactiveShapes) { - obj.draw(painter, false, _useGlobalAlpha, false); + obj.draw(painter, false, _useGlobalAlpha, false, Qt::black, _tfTextureSize); } - painter.end(); - std::vector data; - data.reserve(_tfTextureSize * _tfTextureSize * 4); +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + materialMap = materialMap.flipped(Qt::Vertical); +#else + materialMap = materialMap.mirrored(false, true); +#endif + + constexpr float inv255 = 1.0f / 255.0f; + constexpr int tfDataSize = _tfTextureSize * _tfTextureSize * 4; + std::vector tfData(tfDataSize, 0.0f); + + auto pixels = reinterpret_cast(materialMap.constBits()); + const size_t stride = materialMap.bytesPerLine() / sizeof(uint32_t); + + size_t tfDataId = 0; for (int y = _tfTextureSize - 1; y >= 0; y--) { for (int x = 0; x < _tfTextureSize; x++) { - const int normalizedX = x * materialMap.width() / _tfTextureSize; - const int normalizedY = y * materialMap.height() / _tfTextureSize; - - const QColor color = materialMap.pixelColor(normalizedX, normalizedY); - data.push_back(color.redF()); - data.push_back(color.greenF()); - data.push_back(color.blueF()); - data.push_back(color.alphaF()); + + const uint32_t pixel = pixels[y * stride + x]; + + if (pixel == 0) { + tfDataId += 4; + continue; + } + + tfData[tfDataId++] = static_cast((pixel >> 16) & 0xFF) * inv255; + tfData[tfDataId++] = static_cast((pixel >> 8) & 0xFF) * inv255; + tfData[tfDataId++] = static_cast((pixel) & 0xFF) * inv255; + tfData[tfDataId++] = static_cast((pixel >> 24) & 0xFF) * inv255; } } - _tfSourceDataset->setData(data, 4); // update the data in the dataset + _tfSourceDataset->setData(std::move(tfData), 4); // update the data in the dataset events().notifyDatasetDataChanged(_tfSourceDataset); events().notifyDatasetDataChanged(_tfTextures); @@ -542,32 +552,40 @@ void TransferFunctionWidget::updateMaterialPositionsTexture() if (!_materialPositionTexture.isValid()) return; + auto materialMap = QImage(_materialPositionTextureSize, _materialPositionTextureSize, QImage::Format_ARGB32); - auto materialMap = QImage(_boundsPointsWindow.width(), _boundsPointsWindow.height(), QImage::Format_ARGB32); QPainter painter(&materialMap); painter.setCompositionMode(QPainter::CompositionMode_SourceOver); int id = 1; for (auto& obj : _interactiveShapes) { - obj.drawID(painter, false, id); + obj.drawID(painter, false, id, _materialPositionTextureSize); id++; } painter.end(); - std::vector data; - data.reserve(_materialPositionTextureSize * _materialPositionTextureSize); +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + materialMap = materialMap.flipped(Qt::Vertical); +#else + materialMap = materialMap.mirrored(false, true); +#endif + + constexpr float inv255 = 1.0f / 255.0f; + constexpr auto materialPositionDataSize = _materialPositionTextureSize * _materialPositionTextureSize; + std::vector materialPositionData(materialPositionDataSize, 0.0f); + + auto pixels = reinterpret_cast(materialMap.constBits()); + const size_t stride = materialMap.bytesPerLine() / sizeof(uint32_t); + size_t materialPositionDataId = 0; for (int y = _materialPositionTextureSize - 1; y >= 0; y--) { for (int x = 0; x < _materialPositionTextureSize; x++) { - const int normalizedX = x * materialMap.width() / _materialPositionTextureSize; - const int normalizedY = y * materialMap.height() / _materialPositionTextureSize; - - const QColor color = materialMap.pixelColor(normalizedX, normalizedY); - data.push_back(color.redF()); + const uint32_t pixel = pixels[y * stride + x]; + materialPositionData[materialPositionDataId++] = static_cast((pixel >> 16) & 0xFF) * inv255; } } - _materialPositionSourceDataset->setData(data, 1); // update the data in the dataset + _materialPositionSourceDataset->setData(std::move(materialPositionData), 1); // update the data in the dataset events().notifyDatasetDataChanged(_materialPositionSourceDataset); events().notifyDatasetDataChanged(_materialPositionTexture); @@ -578,32 +596,30 @@ void TransferFunctionWidget::updateMaterialTransitionTexture(const std::vector data; - data.reserve(_materialTextureSize * _materialTextureSize * 4); + // A table of all shape transitions, the absence of a colored area is its own material + constexpr auto materialTransitionDataSize = _materialTextureSize * _materialTextureSize * 4; + std::vector materialTransitionData(materialTransitionDataSize, 0.0f); + size_t materialPositionDataId = 0; //for (int y = _materialTextureSize - 1; y >= 0; y--) { for (size_t y = 0; y < _materialTextureSize; y++) { for (size_t x = 0; x < _materialTextureSize; x++) { if (y < transitionsTable.size() && x < transitionsTable[y].size()) // If the transition table is not big enough, we fill the rest with black { - const QColor color = transitionsTable[y][x]; - data.push_back(color.redF()); - data.push_back(color.greenF()); - data.push_back(color.blueF()); - data.push_back(color.alphaF()); + const QColor& color = transitionsTable[y][x]; + materialTransitionData[materialPositionDataId++] = color.redF(); + materialTransitionData[materialPositionDataId++] = color.greenF(); + materialTransitionData[materialPositionDataId++] = color.blueF(); + materialTransitionData[materialPositionDataId++] = color.alphaF(); } else { - data.push_back(0.0f); - data.push_back(0.0f); - data.push_back(0.0f); - data.push_back(0.0f); + materialPositionDataId += 4; } } } - _materialTransitionSourceDataset->setData(data, 4); // update the data in the dataset + _materialTransitionSourceDataset->setData(std::move(materialTransitionData), 4); // update the data in the dataset events().notifyDatasetDataChanged(_materialTransitionSourceDataset); events().notifyDatasetDataChanged(_materialTransitionTexture); diff --git a/DVRTransferFunction/src/TransferFunctionWidget.h b/DVRTransferFunction/src/TransferFunctionWidget.h index d142f50..4f88aa7 100644 --- a/DVRTransferFunction/src/TransferFunctionWidget.h +++ b/DVRTransferFunction/src/TransferFunctionWidget.h @@ -171,7 +171,7 @@ private slots: QRect _areaSelectionBounds; /** Area selection bounds */ QRect _boundsPointsWindow; /** Bounds of the points in the UI window */ - std::vector _interactiveShapes; /** Stores all the interactive shapes in the transferfunction widget*/ + std::vector _interactiveShapes; /** Stores all the interactive shapes in the transfer function widget*/ InteractiveShape* _selectedObject = nullptr; /** Pointer to the selected object */ SelectedSide _selectedSide = SelectedSide::None; /** Selected side of the object */ bool _createShape = false; /** Boolean determining whether a shape is to be created or not */ @@ -179,7 +179,7 @@ private slots: bool _useGlobalAlpha = false; /** The global alpha changes the alpha value of all the colors the user sees on their screen not the colors that are passes along */ int _globalAlphaValue = 100; - const int _tfTextureSize = 512; - const int _materialTextureSize = 128; - const int _materialPositionTextureSize = 1024; + constexpr static int _tfTextureSize = 512; + constexpr static int _materialTextureSize = 128; + constexpr static int _materialPositionTextureSize = 1024; }; diff --git a/DVRVolumeData/DVRVolumeData/Volumes.cpp b/DVRVolumeData/DVRVolumeData/Volumes.cpp index db594d0..d98a9a9 100644 --- a/DVRVolumeData/DVRVolumeData/Volumes.cpp +++ b/DVRVolumeData/DVRVolumeData/Volumes.cpp @@ -408,7 +408,6 @@ QVariantMap Volumes::toVariantMap() const { auto variantMap = DatasetImpl::toVariantMap(); - variantMap["VolumeSize"] = QVariantMap({ { "Width", getVolumeSize().width() }, { "Height", getVolumeSize().height() }, { "Depth", getVolumeSize().depth() } }); variantMap["NumberOfComponentsPerVoxel"] = getComponentsPerVoxel(); variantMap["VolumeFilePaths"] = getVolumeFilePaths();