diff --git a/Libs/Visualization/VTK/Widgets/Testing/Cpp/CMakeLists.txt b/Libs/Visualization/VTK/Widgets/Testing/Cpp/CMakeLists.txt index 89fddc8acb..7522b38183 100644 --- a/Libs/Visualization/VTK/Widgets/Testing/Cpp/CMakeLists.txt +++ b/Libs/Visualization/VTK/Widgets/Testing/Cpp/CMakeLists.txt @@ -35,6 +35,7 @@ set(TEST_SOURCES ctkVTKThumbnailViewTest1.cpp ctkVTKTransferFunctionRepresentationTest1.cpp ctkVTKWidgetsUtilsTestGrabWidget.cpp + ctkVTKWidgetsUtilsTestImageConversion.cpp ) if(CTK_USE_CHARTS) @@ -195,6 +196,7 @@ SIMPLE_TEST( ctkVTKTextPropertyWidgetTest1 ) SIMPLE_TEST( ctkVTKThumbnailViewTest1 ) SIMPLE_TEST( ctkVTKTransferFunctionRepresentationTest1 ) SIMPLE_TEST( ctkVTKWidgetsUtilsTestGrabWidget ) +SIMPLE_TEST( ctkVTKWidgetsUtilsTestImageConversion ) # # Add Tests expecting CTKData to be set diff --git a/Libs/Visualization/VTK/Widgets/Testing/Cpp/ctkVTKWidgetsUtilsTestImageConversion.cpp b/Libs/Visualization/VTK/Widgets/Testing/Cpp/ctkVTKWidgetsUtilsTestImageConversion.cpp new file mode 100644 index 0000000000..8db7ec1c0b --- /dev/null +++ b/Libs/Visualization/VTK/Widgets/Testing/Cpp/ctkVTKWidgetsUtilsTestImageConversion.cpp @@ -0,0 +1,178 @@ +/*========================================================================= + + Library: CTK + + Copyright (c) Kitware Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + =========================================================================*/ + +// QT includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// CTK includes +#include "ctkVTKOpenGLNativeWidget.h" +#include "ctkVTKRenderView.h" +#include "ctkVTKWidgetsUtils.h" +#include "ctkWidgetsUtils.h" + +// VTK includes +#include "vtkImageData.h" + +// STD includes +#include +#include + +//----------------------------------------------------------------------------- +int ctkVTKWidgetsUtilsTestImageConversion(int argc, char * argv [] ) +{ +#if CTK_USE_QVTKOPENGLWIDGET + QSurfaceFormat format = ctkVTKOpenGLNativeWidget::defaultFormat(); + format.setSamples(0); + QSurfaceFormat::setDefaultFormat(format); +#endif + + QApplication app(argc, argv); + + QFrame parentWidget; + parentWidget.setFrameStyle(QFrame::Panel | QFrame::Raised); + parentWidget.setLineWidth(2); + + ctkVTKRenderView vtkWidget(&parentWidget); + QVBoxLayout* layout = new QVBoxLayout(&parentWidget); + layout->addWidget(&vtkWidget); + parentWidget.setLayout(layout); + + QIcon someQIcon = parentWidget.style()->standardIcon(QStyle::SP_DirIcon); + QImage someQImage = someQIcon.pixmap(32,32).toImage(); + + vtkNew someVtkImage; + + // Test qImageToVTKImageData + ////////////////////////////////////// + + // force alpha channel (default) - RGBA input + ctk::qImageToVTKImageData(someQImage, someVtkImage); + if (someVtkImage->GetDimensions()[0] != someQImage.size().width() + || someVtkImage->GetDimensions()[1] != someQImage.size().height()) + { + std::cout << "qImageToVTKImageData size mismatch" << std::endl; + return EXIT_FAILURE; + } + if (someVtkImage->GetNumberOfScalarComponents()!=4) + { + std::cout << "qImageToVTKImageData expected 4 scalar components for RGBA input + alpha forced, got: " + << someVtkImage->GetNumberOfScalarComponents() << std::endl; + return EXIT_FAILURE; + } + + // Test pixel color + int testPosition[2] = { 8, 12 }; + QColor someQImageColor = QColor(someQImage.pixel(testPosition[0], testPosition[1])); + unsigned char* someVtkImagePixel = static_cast(someVtkImage->GetScalarPointer( + testPosition[0], someVtkImage->GetDimensions()[1] - testPosition[1] - 1, 0)); + QColor someVtkImageColor = QColor(someVtkImagePixel[0], someVtkImagePixel[1], someVtkImagePixel[2]); + if (someQImageColor != someVtkImageColor) + { + std::cout << "qImageToVTKImageData Pixel color error at " << testPosition[0] << " " << testPosition[1] << ": " + << " QImage " << someQImageColor.red() << " " << someQImageColor.green() << " " << someQImageColor.blue() + << ", vtkImageData " << someVtkImageColor.red() << " " << someVtkImageColor.green() << " " << someVtkImageColor.blue() + << std::endl; + return EXIT_FAILURE; + } + + // do not force alpha channel - RGBA input + ctk::qImageToVTKImageData(someQImage, someVtkImage, false); + if (someVtkImage->GetNumberOfScalarComponents() != 4) + { + std::cout << "qImageToVTKImageData expected 4 scalar components for RGBA input + alpha not forced, got: " + << someVtkImage->GetNumberOfScalarComponents() << std::endl; + return EXIT_FAILURE; + } + + // do not force alpha channel - RGB input + ctk::qImageToVTKImageData(someQImage.convertToFormat(QImage::Format_RGB888), someVtkImage, false); + if (someVtkImage->GetNumberOfScalarComponents() != 3) + { + std::cout << "qImageToVTKImageData expected 3 scalar components for RGBA input + alpha not forced, got: " + << someVtkImage->GetNumberOfScalarComponents() << std::endl; + return EXIT_FAILURE; + } + + // force alpha channel - RGB input + ctk::qImageToVTKImageData(someQImage.convertToFormat(QImage::Format_RGB888), someVtkImage, true); + if (someVtkImage->GetNumberOfScalarComponents() != 4) + { + std::cout << "qImageToVTKImageData expected 4 scalar components for RGBA input + alpha forced, got: " + << someVtkImage->GetNumberOfScalarComponents() << std::endl; + return EXIT_FAILURE; + } + + // Test qImageToVTKImageData + ////////////////////////////////////// + + // RGBA input + QImage convertedQImage = ctk::vtkImageDataToQImage(someVtkImage); + if (someVtkImage->GetDimensions()[0] != convertedQImage.size().width() + || someVtkImage->GetDimensions()[1] != convertedQImage.size().height()) + { + std::cout << "vtkImageDataToQImage size mismatch" << std::endl; + return EXIT_FAILURE; + } + if (!convertedQImage.hasAlphaChannel()) + { + std::cout << "vtkImageDataToQImage expected alpha channel" << std::endl; + return EXIT_FAILURE; + } + + // Test pixel color + QColor convertedQImageColor = QColor(convertedQImage.pixel(testPosition[0], testPosition[1])); + if (someQImageColor != someVtkImageColor) + { + std::cout << "vtkImageDataToQImage Pixel color error at " << testPosition[0] << " " << testPosition[1] << ": " + << " QImage " << convertedQImageColor.red() << " " << convertedQImageColor.green() << " " << convertedQImageColor.blue() + << ", vtkImageData " << someVtkImageColor.red() << " " << someVtkImageColor.green() << " " << someVtkImageColor.blue() + << std::endl; + return EXIT_FAILURE; + } + + // RGB input + ctk::qImageToVTKImageData(someQImage.convertToFormat(QImage::Format_RGB888), someVtkImage, false); + convertedQImage = ctk::vtkImageDataToQImage(someVtkImage); + if (convertedQImage.hasAlphaChannel()) + { + std::cout << "vtkImageDataToQImage expected no alpha channel" << std::endl; + return EXIT_FAILURE; + } + + // Display results + QLabel screenshotLabel; + screenshotLabel.setPixmap(QPixmap::fromImage(someQImage)); + screenshotLabel.show(); + + if (argc < 2 || QString(argv[1]) != "-I") + { + QTimer::singleShot(100, &app, SLOT(quit())); + } + return app.exec(); +} diff --git a/Libs/Visualization/VTK/Widgets/ctkVTKWidgetsUtils.cpp b/Libs/Visualization/VTK/Widgets/ctkVTKWidgetsUtils.cpp index acb61e3967..1f97bc287f 100644 --- a/Libs/Visualization/VTK/Widgets/ctkVTKWidgetsUtils.cpp +++ b/Libs/Visualization/VTK/Widgets/ctkVTKWidgetsUtils.cpp @@ -20,6 +20,7 @@ // Qt includes #include +#include #include #include #include @@ -35,8 +36,10 @@ #else #include #endif +#include #include #include +#include #include #include @@ -85,33 +88,85 @@ QImage ctk::grabVTKWidget(QWidget* widget, QRect rectangle) //---------------------------------------------------------------------------- QImage ctk::vtkImageDataToQImage(vtkImageData* imageData) { - if (!imageData) - { + if (!imageData + || !imageData->GetPointData() + || !imageData->GetPointData()->GetScalars() + || imageData->GetScalarType() != VTK_UNSIGNED_CHAR) + { return QImage(); - } -#if VTK_MAJOR_VERSION <= 5 - imageData->Update(); -#endif + } /// \todo retrieve just the UpdateExtent int width = imageData->GetDimensions()[0]; int height = imageData->GetDimensions()[1]; - QImage image(width, height, QImage::Format_RGB32); - QRgb* rgbPtr = reinterpret_cast(image.bits()) + - width * (height-1); - unsigned char* colorsPtr = reinterpret_cast( - imageData->GetScalarPointer()); - // mirror vertically - for(int row = 0; row < height; ++row) - { - for (int col = 0; col < width; ++col) - { - // Swap rgb - *(rgbPtr++) = QColor(colorsPtr[0], colorsPtr[1], colorsPtr[2]).rgb(); - colorsPtr += 3; - } - rgbPtr -= width * 2; - } - return image; + vtkIdType numberOfScalarComponents = imageData->GetNumberOfScalarComponents(); + QImage image; + if (numberOfScalarComponents == 3) + { + image = QImage(width, height, QImage::Format_RGB888); + } + else if (numberOfScalarComponents == 4) + { + image = QImage(width, height, QImage::Format_RGBA8888); + } +#if QT_VERSION >= QT_VERSION_CHECK(5,5,0) + else if (numberOfScalarComponents == 1) + { + image = QImage(width, height, QImage::Format_Grayscale8); + } +#endif + else + { + // unsupported pixel format + return QImage(); + } + + const unsigned char* qtImageBuffer = image.bits(); + memcpy(imageData->GetPointData()->GetScalars()->GetVoidPointer(0), + qtImageBuffer, numberOfScalarComponents * width * height); + + // Qt image is upside-down compared to VTK, so return mirrored image + return image.mirrored(); +} + +//---------------------------------------------------------------------------- +bool ctk::qImageToVTKImageData(const QImage& inputQImage, vtkImageData* outputVTKImageData, bool forceAlphaChannel/*=true*/) +{ + if (!outputVTKImageData) + { + qWarning() << Q_FUNC_INFO << " failed: outputVTKImageData is invalid"; + return false; + } + + QSize size = inputQImage.size(); + vtkIdType width = size.width(); + vtkIdType height = size.height(); + if (width < 1 || height < 1) + { + qWarning() << Q_FUNC_INFO << " failed: input image is invalid"; + return false; + } + + QImage normalizedQtImage; + vtkIdType numberOfScalarComponents = 0; + if (inputQImage.hasAlphaChannel() || forceAlphaChannel) + { + normalizedQtImage = inputQImage.convertToFormat(QImage::Format_RGBA8888).mirrored(); + numberOfScalarComponents = 4; + } + else + { + normalizedQtImage = inputQImage.convertToFormat(QImage::Format_RGB888).mirrored(); + numberOfScalarComponents = 3; + } + + const unsigned char* normalizedQtImageBuffer = normalizedQtImage.bits(); + outputVTKImageData->SetExtent(0, width-1, 0, height-1, 0, 0); + outputVTKImageData->AllocateScalars(VTK_UNSIGNED_CHAR, numberOfScalarComponents); + memcpy(outputVTKImageData->GetPointData()->GetScalars()->GetVoidPointer(0), + normalizedQtImageBuffer, numberOfScalarComponents * width * height); + outputVTKImageData->GetPointData()->GetScalars()->Modified(); + + return true; } //---------------------------------------------------------------------------- diff --git a/Libs/Visualization/VTK/Widgets/ctkVTKWidgetsUtils.h b/Libs/Visualization/VTK/Widgets/ctkVTKWidgetsUtils.h index 693e33f2e6..b90e2fdf3d 100644 --- a/Libs/Visualization/VTK/Widgets/ctkVTKWidgetsUtils.h +++ b/Libs/Visualization/VTK/Widgets/ctkVTKWidgetsUtils.h @@ -44,9 +44,18 @@ QImage CTK_VISUALIZATION_VTK_WIDGETS_EXPORT grabVTKWidget(QWidget* widget, QRect /// /// \ingroup Visualization_VTK_Widgets -/// Convert a vtkImageData into a QImage +/// Convert a vtkImageData into a QImage with alpha channel (RGBA). +/// Creates a deep copy of the input image. QImage CTK_VISUALIZATION_VTK_WIDGETS_EXPORT vtkImageDataToQImage(vtkImageData* imageData); +/// +/// \ingroup Visualization_VTK_Widgets +/// Convert a QImage into a vtkImageData. +/// Creates a deep copy of the input image. +/// \param forceAlphaChannel If set to true then the VTK image data will always contain alpha channel. +/// If set to false then VTK image will be RGBA if QImage had alpha channel, RGB otherwise. +bool CTK_VISUALIZATION_VTK_WIDGETS_EXPORT qImageToVTKImageData(const QImage& image, vtkImageData* imageData, bool forceAlphaChannel=true); + /// /// \ingroup Visualization_VTK_Widgets /// Convert a vtkScalarsToColors into a QImage