Skip to content

Commit

Permalink
ENH: Add qImageToVTKImageData to ctkVTKWidgetsUtils
Browse files Browse the repository at this point in the history
Allows converting Qt image to VTK image.

Also added test for qImageToVTKImageData and vtkImageDataToQImage.
  • Loading branch information
lassoan committed Sep 4, 2019
1 parent 9e89c22 commit 0a1f9c1
Show file tree
Hide file tree
Showing 4 changed files with 268 additions and 24 deletions.
2 changes: 2 additions & 0 deletions Libs/Visualization/VTK/Widgets/Testing/Cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ set(TEST_SOURCES
ctkVTKThumbnailViewTest1.cpp
ctkVTKTransferFunctionRepresentationTest1.cpp
ctkVTKWidgetsUtilsTestGrabWidget.cpp
ctkVTKWidgetsUtilsTestImageConversion.cpp
)

if(CTK_USE_CHARTS)
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <QApplication>
#include <QDialog>
#include <QFrame>
#include <QIcon>
#include <QImage>
#include <QLabel>
#include <QPixmap.h>
#include <QStyle>
#include <QTimer>
#include <QVBoxLayout>

// CTK includes
#include "ctkVTKOpenGLNativeWidget.h"
#include "ctkVTKRenderView.h"
#include "ctkVTKWidgetsUtils.h"
#include "ctkWidgetsUtils.h"

// VTK includes
#include "vtkImageData.h"

// STD includes
#include <cstdlib>
#include <iostream>

//-----------------------------------------------------------------------------
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<vtkImageData> 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<unsigned char*>(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();
}
101 changes: 78 additions & 23 deletions Libs/Visualization/VTK/Widgets/ctkVTKWidgetsUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

// Qt includes
#include <QApplication>
#include <QDebug>
#include <QImage>
#include <QPainter>
#include <QStyle>
Expand All @@ -35,8 +36,10 @@
#else
#include <QVTKWidget.h>
#endif
#include <vtkDataArray.h>
#include <vtkImageData.h>
#include <vtkPiecewiseFunction.h>
#include <vtkPointData.h>
#include <vtkScalarsToColors.h>
#include <vtkVersion.h>

Expand Down Expand Up @@ -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<QRgb*>(image.bits()) +
width * (height-1);
unsigned char* colorsPtr = reinterpret_cast<unsigned char*>(
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;
}

//----------------------------------------------------------------------------
Expand Down
11 changes: 10 additions & 1 deletion Libs/Visualization/VTK/Widgets/ctkVTKWidgetsUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 0a1f9c1

Please sign in to comment.