QVTKOpenGLNativeWidget中绘制vtkButtonWidget

本文探讨了如何结合Qt和VTK(Visualization Toolkit)开发图形用户界面(GUI),通过一个具体的案例,详细介绍了如何在Qt应用程序中嵌入VTK渲染器,并创建交互式的图形元素,如按钮,以实现更丰富的用户体验。文章还深入剖析了如何处理按钮点击事件,以及如何将Qt的图像数据转换为VTK格式。

介绍: 在现代软件开发中,图形用户界面(GUI)的设计和开发是至关重要的一部分。Qt作为一种流行的跨平台应用程序框架,提供了丰富的GUI开发工具和功能。而VTK作为一个强大的可视化工具包,用于创建复杂的3D图形和数据可视化。本文将介绍如何结合Qt和VTK,利用它们各自的优势,开发出功能丰富、交互性强的图形用户界面。

  1. Qt与VTK的结合 Qt和VTK可以无缝地集成在一起,从而实现强大的图形用户界面和数据可视化功能。在本文的示例代码中,我们首先创建了一个Qt应用程序,并使用QVTKOpenGLNativeWidget类创建了一个VTK渲染窗口。这样就可以在Qt应用程序中嵌入VTK渲染器,实现图形界面和数据可视化的结合。

    int main(int argc, char** argv)
    {
    	QApplication a(argc, argv);
    
    	QVTKOpenGLNativeWidget w;
    
    	// 创建渲染器
    	vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
    	auto reWin = w.renderWindow();
    	reWin->AddRenderer(renderer);
    	...
    	w.show();
        ...
    }
    
  2. 创建交互式按钮 在我们的示例中,我们使用vtkButtonWidget类创建了一个交互式按钮。这个按钮可以响应用户的点击事件,并执行相应的操作。为了在按钮上显示图像,我们使用了vtkTexturedButtonRepresentation2D类,并将按钮的纹理设置为图像数据。通过这种方式,我们可以在图形界面中创建交互式的按钮,为用户提供更友好的操作体验。

    // 创建按钮的2D表示
    vtkSmartPointer<vtkTexturedButtonRepresentation2D> btnRepresentation2D = vtkSmartPointer<vtkTexturedButtonRepresentation2D>::New();
    btnRepresentation2D->SetPlaceFactor(1);
    btnRepresentation2D->SetNumberOfStates(2);
    
    vtkSmartPointer<vtkPNGReader> reader0 = vtkSmartPointer<vtkPNGReader>::New();
    QString tempFilePath0 = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/poweroff.png";
    QFile::copy(":/resources/poweroff.png", tempFilePath0);
    reader0->SetFileName(tempFilePath0.toStdString().c_str());
    reader0->Update();
    btnRepresentation2D->SetButtonTexture(0, reader0->GetOutput());
    
    QImage poweron = QImage(":/resources/poweron.png");
    btnRepresentation2D->SetButtonTexture(1, QImage2vtkImageData(poweron));
    
    // 创建按钮并设置表示
    vtkSmartPointer<vtkButtonWidget> btnWidget = vtkSmartPointer<vtkButtonWidget>::New();
    btnWidget->SetInteractor(w.interactor());
    btnWidget->SetRepresentation(btnRepresentation2D);
    //btnWidget->SetEnabled(1);
    btnWidget->On();
    
    // 创建按钮点击事件处理命令对象
    vtkSmartPointer<ButtonCallback> callback = vtkSmartPointer<ButtonCallback>::New();
    btnWidget->AddObserver(vtkCommand::StateChangedEvent, callback);
    
  3. 处理按钮点击事件为了处理按钮的点击事件,我们创建了一个自定义的vtkCommand类,并重写了它的Execute方法。在Execute方法中,我们检查了事件的类型,并根据需要执行相应的操作。通过这种方式,我们可以实现对按钮点击事件的灵活处理,从而实现更加复杂的交互功能。

    // 创建一个命令类来处理按钮点击事件
    class ButtonCallback : public vtkCommand {
    public:
    	static ButtonCallback* New()
    	{
    		auto result = new ButtonCallback();
    		result->InitializeObjectBase();
    		return result;
    	}
    
    	virtual void Execute(vtkObject* caller, unsigned long eventId, void* callData) override 
    	{
    		// 检查事件类型
    		if (eventId == vtkCommand::StateChangedEvent)
    		{
    			// 处理按钮状态变化事件
    			vtkButtonWidget* buttonWidget = reinterpret_cast<vtkButtonWidget*>(caller);
    			qDebug() << vtkCommand::GetStringFromEventId(eventId);
    			vtkButtonRepresentation* representation = reinterpret_cast<vtkButtonRepresentation*>(buttonWidget->GetRepresentation());
    			qDebug() << "vtkButtonWidget State: " << representation->GetState();
    		}
    	}
    };
    

    当然也是可以添加Qt的UI元素的,本文是为了介绍vtkWidget。

  4. 图像数据转换 在Qt和VTK之间传递图像数据时,需要进行格式转换。在我们的示例中,我们演示了如何将Qt中的QImage对象转换为VTK中的vtkImageData对象。通过这种方式,我们可以实现在Qt应用程序和VTK渲染器之间高效地传递图像数据,从而实现更加丰富的数据可视化功能。

    /// @brief 从QImage转换到vtkImageData
    /// @param src 
    /// @return 
    vtkSmartPointer<vtkImageData> QImage2vtkImageData(const QImage& src)
    {
    	vtkSmartPointer<vtkImageData> dst = vtkSmartPointer<vtkImageData>::New();
    	int w = src.width();
    	int h = src.height();
    	// 设置维度
    	dst->SetDimensions(w, h, 1);
    	// 设置数据类型和组件数
    	vtkNew<vtkInformation> info;
    	dst->SetScalarType(VTK_UNSIGNED_CHAR, info);
    	int channels = src.pixelFormat().channelCount();
    	dst->SetNumberOfScalarComponents(channels, info);
    	// 分配内存
    	dst->AllocateScalars(info);
    	// 获取图像数据的首地址
    	unsigned char* dstPtr = static_cast<unsigned char*>(dst->GetScalarPointer());
    	
    	// 遍历图像像素并复制到vtkImageData中
    	for (int y = 0; y < h; y++)
    	{
    		for (int x = 0; x < w; x++)
    		{
    			// 获取像素值
    			QRgb pixel = src.pixel(x, y);
    			// QImage的行顺序是从上到下,vtkImageData的行顺序是从下到上
    			switch (channels)
    			{
    			case 1:
    				dstPtr[(h - y - 1) * w + x] = qGray(pixel);
    				break;
    			case 3:
    				dstPtr[((h - y - 1) * w + x) * 3 + 0] = qRed(pixel);
    				dstPtr[((h - y - 1) * w + x) * 3 + 1] = qGreen(pixel);
    				dstPtr[((h - y - 1) * w + x) * 3 + 2] = qBlue(pixel);
    				break;
    			case 4:
    				dstPtr[((h - y - 1) * w + x) * 4 + 0] = qRed(pixel);
    				dstPtr[((h - y - 1) * w + x) * 4 + 1] = qGreen(pixel);
    				dstPtr[((h - y - 1) * w + x) * 4 + 2] = qBlue(pixel);
    				dstPtr[((h - y - 1) * w + x) * 4 + 3] = qAlpha(pixel);
    				break;
    			}
    		}
    	}
    
    	return dst;
    }
    

完整的代码:

#include <QApplication>
#include <QVTKOpenGLNativeWidget.h>
#include <QFile>
#include <QStandardPaths>
#include <QImage>
#include <QString>
#include <QColorSpace>
#include <QDebug>

#include <vtkActor.h>
#include <vtkProperty.h>
#include <vtkSmartPointer.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkButtonWidget.h>
#include <vtkTexturedButtonRepresentation.h>
#include <vtkTexturedButtonRepresentation2D.h>
#include <vtkImageData.h>
#include <vtkPNGReader.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkCommand.h>
#include <vtkNew.h>
#include <vtkInformation.h>

// 创建一个命令类来处理按钮点击事件
class ButtonCallback : public vtkCommand {
public:
	static ButtonCallback* New()
	{
		auto result = new ButtonCallback();
		result->InitializeObjectBase();
		return result;
	}

	virtual void Execute(vtkObject* caller, unsigned long eventId, void* callData) override 
	{
		// 检查事件类型
		if (eventId == vtkCommand::StateChangedEvent)
		{
			// 处理按钮状态变化事件
			vtkButtonWidget* buttonWidget = reinterpret_cast<vtkButtonWidget*>(caller);
			qDebug() << vtkCommand::GetStringFromEventId(eventId);
			vtkButtonRepresentation* representation = reinterpret_cast<vtkButtonRepresentation*>(buttonWidget->GetRepresentation());
			qDebug() << "vtkButtonWidget State: " << representation->GetState();
		}
	}
};

/// @brief 从QImage转换到vtkImageData
/// @param src 
/// @return 
vtkSmartPointer<vtkImageData> QImage2vtkImageData(const QImage& src)
{
	vtkSmartPointer<vtkImageData> dst = vtkSmartPointer<vtkImageData>::New();
	int w = src.width();
	int h = src.height();
	// 设置维度
	dst->SetDimensions(w, h, 1);
	// 设置数据类型和组件数
	vtkNew<vtkInformation> info;
	dst->SetScalarType(VTK_UNSIGNED_CHAR, info);
	int channels = src.pixelFormat().channelCount();
	dst->SetNumberOfScalarComponents(channels, info);
	// 分配内存
	dst->AllocateScalars(info);
	// 获取图像数据的首地址
	unsigned char* dstPtr = static_cast<unsigned char*>(dst->GetScalarPointer());
	
	// 遍历图像像素并复制到vtkImageData中
	for (int y = 0; y < h; y++)
	{
		for (int x = 0; x < w; x++)
		{
			// 获取像素值
			QRgb pixel = src.pixel(x, y);
			// QImage的行顺序是从上到下,vtkImageData的行顺序是从下到上
			switch (channels)
			{
			case 1:
				dstPtr[(h - y - 1) * w + x] = qGray(pixel);
				break;
			case 3:
				dstPtr[((h - y - 1) * w + x) * 3 + 0] = qRed(pixel);
				dstPtr[((h - y - 1) * w + x) * 3 + 1] = qGreen(pixel);
				dstPtr[((h - y - 1) * w + x) * 3 + 2] = qBlue(pixel);
				break;
			case 4:
				dstPtr[((h - y - 1) * w + x) * 4 + 0] = qRed(pixel);
				dstPtr[((h - y - 1) * w + x) * 4 + 1] = qGreen(pixel);
				dstPtr[((h - y - 1) * w + x) * 4 + 2] = qBlue(pixel);
				dstPtr[((h - y - 1) * w + x) * 4 + 3] = qAlpha(pixel);
				break;
			}
		}
	}

	return dst;
}

int main(int argc, char** argv)
{
	QApplication a(argc, argv);

	QVTKOpenGLNativeWidget w;

	// 创建渲染器
	vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
	auto reWin = w.renderWindow();
	reWin->AddRenderer(renderer);

	// 创建按钮的2D表示
	vtkSmartPointer<vtkTexturedButtonRepresentation2D> btnRepresentation2D = vtkSmartPointer<vtkTexturedButtonRepresentation2D>::New();
	btnRepresentation2D->SetPlaceFactor(1);
	btnRepresentation2D->SetNumberOfStates(2);

	vtkSmartPointer<vtkPNGReader> reader0 = vtkSmartPointer<vtkPNGReader>::New();
	QString tempFilePath0 = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/poweroff.png";
	QFile::copy(":/resources/poweroff.png", tempFilePath0);
	reader0->SetFileName(tempFilePath0.toStdString().c_str());
	reader0->Update();
	btnRepresentation2D->SetButtonTexture(0, reader0->GetOutput());

	QImage poweron = QImage(":/resources/poweron.png");
	btnRepresentation2D->SetButtonTexture(1, QImage2vtkImageData(poweron));

	// 创建按钮并设置表示
	vtkSmartPointer<vtkButtonWidget> btnWidget = vtkSmartPointer<vtkButtonWidget>::New();
	btnWidget->SetInteractor(w.interactor());
	btnWidget->SetRepresentation(btnRepresentation2D);
	//btnWidget->SetEnabled(1);
	btnWidget->On();

	// 创建按钮点击事件处理命令对象
	vtkSmartPointer<ButtonCallback> callback = vtkSmartPointer<ButtonCallback>::New();
	btnWidget->AddObserver(vtkCommand::StateChangedEvent, callback);

	w.show();

	int code = a.exec();
	QFile::remove(tempFilePath0);
	//QFile::remove(tempFilePath1);
	return code;
}

通过本文的介绍,读者可以了解到如何结合Qt和VTK,开发出功能丰富、交互性强的图形用户界面。我们详细介绍了如何在Qt应用程序中嵌入VTK渲染器,并创建交互式的按钮。同时,我们还深入剖析了如何处理按钮点击事件,以及如何进行图像数据的格式转换。希望本文对读者能够有所帮助,让他们能够更加灵活地利用Qt和VTK进行图形用户界面开发和数据可视化。

posted @ 2024-03-01 16:11  非法关键字  阅读(690)  评论(0编辑  收藏  举报