You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nmWTAI-Platform/Src/nmNum/nmSubWxs/nmWxGridVTKContainerWidget.cpp

440 lines
17 KiB
C++

#include "nmWxGridVTKContainerWidget.h"
// Qt 核心头文件
#include <QVBoxLayout>
#include <QFrame>
#include <QDebug>
#include <QPixmap> // 用于捕获部件内容为图像
#include <QFileDialog> // 用于文件保存对话框
#include <QMessageBox> // 用于显示消息框
#include <QApplication> // 用于访问全局应用程序对象 (如剪贴板)
#include <QClipboard> // 用于剪贴板操作
#include <QPrinter> // 用于打印机抽象
#include <QPrintDialog> // 用于打印设置对话框
#include <QPrintPreviewDialog> // 用于打印预览对话框
#include <QPainter> // 用于绘制打印内容
#include <QMenu> // 用于创建右键上下文菜单
#include <QAction> // 用于菜单动作
#include <QLinearGradient>
#include <QCoreApplication>
// VTK 核心头文件
#include <vtkAutoInit.h>
VTK_MODULE_INIT(vtkRenderingOpenGL)
VTK_MODULE_INIT(vtkInteractionStyle)
// VTK 可视化相关
#include <vtkActor.h>
#include <vtkDataSetMapper.h>
#include <vtkProperty.h>
#include <vtkRenderWindow.h>
#include <vtkRenderer.h>
#include <vtkSmartPointer.h>
#include <vtkUnstructuredGrid.h>
#include <vtkCellData.h>
#include <vtkLookupTable.h>
#include <vtkDataArray.h>
#include <QVTKWidget.h>
#include <vtkRendererCollection.h>
#include <vtkWindowToImageFilter.h>
#include <vtkImageData.h>
#include <vtkImageFlip.h>
#include <vtkPNGWriter.h>
#include <vtkUnsignedCharArray.h>
#include <vtkPointData.h>
// 项目特定头文件
#include "nmDataAnalyzeManager.h"
// Windows 库链接 (保留必要项)
#pragma comment(lib,"User32.lib")
#pragma comment(lib,"gdi32.lib")
#pragma comment(lib,"OpenGL32.lib")
#pragma comment(lib,"AdvAPI32.lib")
#pragma comment(lib,"shell32.lib")
nmWxGridVTKContainerWidget::nmWxGridVTKContainerWidget(QWidget *parent) : QWidget(parent)
{
m_pVtkWidget = nullptr;
this->initLayout();
this->initVTKWidget();
}
QImage nmWxGridVTKContainerWidget::createQImage1(int nWidth, int nHeight, vtkUnsignedCharArray* pScalars)
{
QImage qImage(nWidth, nHeight, QImage::Format_ARGB32);
vtkIdType tupleIndex = 0;
int nImageBitIndex = 0;
QRgb* pImageBits = (QRgb*)qImage.bits();
unsigned char* scalarTuples = pScalars->GetPointer(0);
for(int j = 0; j < nHeight; j++) {
for(int i = 0; i < nWidth; i++) {
unsigned char* tuple = scalarTuples + (tupleIndex++);
QRgb color = qRgba(tuple[0], tuple[0], tuple[0], 255);
*(pImageBits + (nImageBitIndex++)) = color;
}
}
return qImage;
}
QImage nmWxGridVTKContainerWidget::createQImage2(int nWidth, int nHeight, vtkUnsignedCharArray* pScalars)
{
QImage qImage(nWidth, nHeight, QImage::Format_ARGB32);
vtkIdType tupleIndex = 0;
int nImageBitIndex = 0;
QRgb* pImageBits = (QRgb*)qImage.bits();
unsigned char* scalarTuples = pScalars->GetPointer(0);
for(int j = 0; j < nHeight; j++) {
for(int i = 0; i < nWidth; i++) {
unsigned char* tuple = scalarTuples + (tupleIndex++ * 2);
QRgb color = qRgba(tuple[0], tuple[0], tuple[0], tuple[1]);
*(pImageBits + (nImageBitIndex++)) = color;
}
}
return qImage;
}
QImage nmWxGridVTKContainerWidget::createQImage3(int nWidth, int nHeight, vtkUnsignedCharArray* pScalars)
{
QImage qImage(nWidth, nHeight, QImage::Format_ARGB32);
vtkIdType tupleIndex = 0;
int nImageBitIndex = 0;
QRgb* pImageBits = (QRgb*)qImage.bits();
unsigned char* scalarTuples = pScalars->GetPointer(0);
for(int j = 0; j < nHeight; j++) {
for(int i = 0; i < nWidth; i++) {
unsigned char* tuple = scalarTuples + (tupleIndex++ * 3);
QRgb color = qRgba(tuple[0], tuple[1], tuple[2], 255);
*(pImageBits + (nImageBitIndex++)) = color;
}
}
return qImage;
}
QImage nmWxGridVTKContainerWidget::createQImage4(int nWidth, int nHeight, vtkUnsignedCharArray* pScalars)
{
QImage qImage(nWidth, nHeight, QImage::Format_ARGB32);
vtkIdType tupleIndex = 0;
int nImageBitIndex = 0;
QRgb* pImageBits = (QRgb*)qImage.bits();
unsigned char* scalarTuples = pScalars->GetPointer(0);
for(int j = 0; j < nHeight; j++) {
for(int i = 0; i < nWidth; i++) {
unsigned char* tuple = scalarTuples + (tupleIndex++ * 4);
QRgb color = qRgba(tuple[0], tuple[1], tuple[2], tuple[3]);
*(pImageBits + (nImageBitIndex++)) = color;
}
}
return qImage;
}
QImage nmWxGridVTKContainerWidget::createQImage(vtkImageData* pImageData)
{
if(!pImageData)
return QImage();
int nWidth = pImageData->GetDimensions()[0];
int nHeight = pImageData->GetDimensions()[1];
vtkUnsignedCharArray* pScalars = vtkUnsignedCharArray::SafeDownCast(pImageData->GetPointData()->GetScalars());
if(!nWidth || !nHeight || !pScalars)
return QImage();
switch(pScalars->GetNumberOfComponents()) {
case 1:
return createQImage1(nWidth, nHeight, pScalars);
case 2:
return createQImage2(nWidth, nHeight, pScalars);
case 3:
return createQImage3(nWidth, nHeight, pScalars);
case 4:
return createQImage4(nWidth, nHeight, pScalars);
}
return QImage();
}
// --- **新增:事件过滤器实现** ---
bool nmWxGridVTKContainerWidget::eventFilter(QObject *pObj, QEvent *event)
{
// 检查事件是否来自我们的 VTK 部件,并且是鼠标按下事件
if(pObj == m_pVtkWidget && event->type() == QEvent::MouseButtonPress) {
QMouseEvent *pMouseEVent = static_cast<QMouseEvent*>(event);
if(pMouseEVent->button() == Qt::RightButton) {
// 在右键点击 m_pVtkWidget 时,直接显示我们的自定义菜单
QMenu menu(this); // 菜单的父对象仍然是 nmWxGridVTKContainerWidget
QString appDir = QCoreApplication::applicationDirPath();
appDir = appDir.section('/', 0, -2); // 获取上一级目录
// 设置菜单图标
QIcon saveIcon(appDir + "/Res/Icon/SaveImg.png");
QIcon copyIcon(appDir + "/Res/Icon/Copy.png");
QIcon printIcon(appDir + "/Res/Icon/Print.png");
QIcon printPreviewIcon(appDir + "/Res/Icon/PrePrint.png");
// 添加“保存为图片”动作,并为其添加图标
QAction *pSaveAction = new QAction(saveIcon, tr("Save as Image"), this);
connect(pSaveAction, SIGNAL(triggered()), this, SLOT(saveWidgetAsImage()));
menu.addAction(pSaveAction);
// 添加“复制图片”动作,并为其添加图标
QAction *pCopyAction = new QAction(copyIcon, tr("Copy Image"), this);
connect(pCopyAction, SIGNAL(triggered()), this, SLOT(copyWidgetImage()));
menu.addAction(pCopyAction);
menu.addSeparator(); // 添加分隔线
// 添加“打印”动作,并为其添加图标
QAction *pPrintAction = new QAction(printIcon, tr("Print"), this);
connect(pPrintAction, SIGNAL(triggered()), this, SLOT(printWidget()));
menu.addAction(pPrintAction);
// 添加“打印预览”动作,并为其添加图标
QAction *pPrintPreviewAction = new QAction(printPreviewIcon, tr("Print Preview"), this);
connect(pPrintPreviewAction, SIGNAL(triggered()), this, SLOT(printPreviewWidget()));
menu.addAction(pPrintPreviewAction);
menu.exec(pMouseEVent->globalPos()); // 在鼠标的全局位置执行菜单
return true; // **事件已处理,停止 QVTKWidget 的进一步处理**
}
}
// 对于其他事件或对象,将它们传递给基类的事件过滤器
return QWidget::eventFilter(pObj, event);
}
void nmWxGridVTKContainerWidget::initVTKWidget()
{
// 从数据中心获取当前非结构化网格对象
nmDataAnalyzeManager* pCurDataManager = nmDataAnalyzeManager::getCurrentInstance();
Q_ASSERT(nullptr != pCurDataManager);
vtkSmartPointer<vtkUnstructuredGrid> pGrid = pCurDataManager->getUnstructuredGrid();
Q_ASSERT(nullptr != pGrid);
// 创建自定义 nmWxQVTKWidget 的实例。
// 此部件将处理其自身的上下文菜单事件。
m_pVtkWidget = new QVTKWidget(this); // 'this' 是 QVTKWidget 的父部件。
m_pMainLayout->addWidget(m_pVtkWidget); // 将 VTK 部件添加到 m_pMainLayout 的布局中。
// 安装事件过滤器
m_pVtkWidget->installEventFilter(this);
// 创建映射器
vtkSmartPointer<vtkDataSetMapper> pMapper = vtkSmartPointer<vtkDataSetMapper>::New();
pMapper->SetInputData(pGrid); // 传入非结构化网格
// 检查是否有Material场数据
vtkSmartPointer<vtkCellData> pCellData = pGrid->GetCellData();
vtkDataArray* pMaterialArray = pCellData->GetArray("Material");
if(pMaterialArray) {
// 创建颜色映射表
vtkSmartPointer<vtkLookupTable> pLut = vtkSmartPointer<vtkLookupTable>::New();
pLut->SetNumberOfColors(256);
pLut->SetHueRange(0.67, 0.0); // 从蓝色到红色
pLut->SetSaturationRange(1.0, 1.0);
pLut->SetValueRange(1.0, 1.0);
pLut->Build();
pMapper->SetScalarModeToUseCellData();
pMapper->SelectColorArray("Material");
pMapper->SetScalarRange(pMaterialArray->GetRange());
pMapper->SetLookupTable(pLut);
}
// 创建actor并设置属性
vtkSmartPointer<vtkActor> pActor = vtkSmartPointer<vtkActor>::New();
pActor->SetMapper(pMapper);
vtkProperty* pActorProperty = pActor->GetProperty();
pActorProperty->SetRepresentationToSurface();
pActorProperty->EdgeVisibilityOn(); // 显示边缘
pActorProperty->SetEdgeVisibility(true); // 允许交互
pActorProperty->SetColor(0, 120.0 / 255.0, 215.0 / 255.0); // 设置面的颜色
// 创建渲染器并设置背景
vtkSmartPointer<vtkRenderer> pRenderer = vtkSmartPointer<vtkRenderer>::New();
pRenderer->AddActor(pActor);
pRenderer->SetGradientBackground(true);
pRenderer->SetBackground(0.67, 0.82, 0.94); // 浅蓝
pRenderer->SetBackground2(0.0, 0.5, 1.0); // 深蓝
pRenderer->ResetCamera();
// 创建渲染窗口
vtkSmartPointer<vtkRenderWindow> pRenderWindow = vtkSmartPointer<vtkRenderWindow>::New();
pRenderWindow->AddRenderer(pRenderer);
// 将 VTK 渲染窗口设置到我们的 nmWxQVTKWidget。
m_pVtkWidget->SetRenderWindow(pRenderWindow); // 设置渲染窗口
pRenderWindow->Render(); // 执行初始渲染
}
void nmWxGridVTKContainerWidget::initLayout()
{
m_pMainLayout = new QVBoxLayout(this); // 设置当前Widget的布局
// 移除布局的默认边距和间距
m_pMainLayout->setContentsMargins(0, 0, 0, 0); // 设置边距为0
m_pMainLayout->setSpacing(0); // 设置子控件间距为0
}
// --- 图片和打印操作 ---
// saveWidgetAsImage: 将 m_pVtkWidget 的内容保存到图像文件。
void nmWxGridVTKContainerWidget::saveWidgetAsImage()
{
// 检查 QVTKWidget 及其底层 VTK 渲染窗口是否已初始化。
if(!m_pVtkWidget || !m_pVtkWidget->GetRenderWindow()) {
return;
}
// 将 QVTKWidget 的当前内容抓取为 QPixmap。
QPixmap pixmap = QPixmap::grabWidget(m_pVtkWidget);
// 打开文件保存对话框,让用户选择保存位置和格式。
QString sFileName = QFileDialog::getSaveFileName(this,
tr("Save Image"),
"", // 默认目录 (空字符串表示当前工作目录)
tr("PNG Image (*.png);;JPEG Image (*.jpg);;BMP Image (*.bmp)")); // 文件过滤器
// 如果用户提供了文件名 (没有取消对话框)。
if(!sFileName.isEmpty()) {
// 尝试将 pixmap 保存到选定的文件。
pixmap.save(sFileName);
}
}
// copyWidgetImage: 将 m_pVtkWidget 的内容复制到剪贴板。
void nmWxGridVTKContainerWidget::copyWidgetImage()
{
// 检查 QVTKWidget 及其底层 VTK 渲染窗口是否已初始化。
if(!m_pVtkWidget || !m_pVtkWidget->GetRenderWindow()) {
return;
}
// 将 QVTKWidget 的当前内容抓取为 QPixmap。
QPixmap pixmap = QPixmap::grabWidget(m_pVtkWidget);
// 获取全局剪贴板实例并设置抓取的 pixmap。
QClipboard *pClipboard = QApplication::clipboard();
pClipboard->setPixmap(pixmap);
}
// printWidget: 打印 m_pVtkWidget 的内容。
void nmWxGridVTKContainerWidget::printWidget()
{
// 检查 QVTKWidget 及其底层 VTK 渲染窗口是否已初始化。
if(!m_pVtkWidget || !m_pVtkWidget->GetRenderWindow()) {
return;
}
QPrinter printer; // 创建 QPrinter 对象。
QPrintDialog printDialog(&printer, this); // 创建用于打印设置的打印对话框。
// 如果用户接受打印对话框(点击“打印”)。
if(printDialog.exec() == QDialog::Accepted) {
// 调用 renderWidgetForPrint 槽函数,将打印逻辑统一。
// 这样,实际打印也会应用缩放和居中。
renderWidgetForPrint(&printer);
}
}
// printPreviewWidget: 显示 m_pVtkWidget 的打印预览。
void nmWxGridVTKContainerWidget::printPreviewWidget()
{
// 检查 QVTKWidget 及其底层 VTK 渲染窗口是否已初始化。
if(!m_pVtkWidget || !m_pVtkWidget->GetRenderWindow()) {
return;
}
QPrinter printer(QPrinter::HighResolution); // 创建高分辨率打印机以获得更好的预览质量。
QPrintPreviewDialog preview(&printer, this); // 创建打印预览对话框。
preview.resize(800, 600); // 设置预览窗体大小
// 将预览对话框的 paintRequested 信号连接到我们的自定义渲染槽。
// 当预览对话框需要绘制页面时,将调用此槽。
connect(&preview, SIGNAL(paintRequested(QPrinter*)),
this, SLOT(renderWidgetForPrint(QPrinter*)));
preview.exec(); // 显示打印预览对话框。
}
// renderWidgetForPrint: 辅助槽函数,用于渲染 m_pVtkWidget 的内容以进行打印/预览。
void nmWxGridVTKContainerWidget::renderWidgetForPrint(QPrinter *printer)
{
// 调用辅助函数获取包含VTK渲染内容的QImage
QImage image = getVTKRenderWindowAsImage();
if(image.isNull()) {
return;
}
QPainter painter(printer); // 创建与给定打印机关联的 QPainter。
// 计算缩放因子,以按比例将部件内容适应到打印页面。
// 这里使用捕获到的QImage的尺寸而不是QVTKWidget的尺寸因为QImage已经是实际渲染内容的准确捕获。
double xscale = printer->pageRect().width() / (double)image.width();
double yscale = printer->pageRect().height() / (double)image.height();
// 使用较小的缩放因子,以确保整个内容完全适应页面内,不会被裁剪。
double scale = qMin(xscale, yscale);
painter.scale(scale, scale); // 然后应用缩放变换
// 将捕获到的 QImage 绘制到打印机上。
// 这样VTK渲染的所有内容包括渐变背景都会被完整地打印出来。
painter.drawImage(0, 0, image);
}
QImage nmWxGridVTKContainerWidget::getVTKRenderWindowAsImage()
{
// 检查 QVTKWidget 及其底层的 VTK 渲染窗口是否已初始化。
if(!m_pVtkWidget || !m_pVtkWidget->GetRenderWindow()) {
qWarning() << "QVTKWidget or RenderWindow is not initialized.";
return QImage(); // 返回空图像
}
// 获取 VTK 渲染窗口的指针
vtkRenderWindow* pRenderWindow = m_pVtkWidget->GetRenderWindow();
// 确保在捕获前渲染最新帧
pRenderWindow->Render();
// 使用 vtkWindowToImageFilter 将渲染窗口的内容转换为 VTK 图像数据
vtkSmartPointer<vtkWindowToImageFilter> windowToImageFilter =
vtkSmartPointer<vtkWindowToImageFilter>::New();
windowToImageFilter->SetInput(pRenderWindow);
// 明确设置为 RGB
windowToImageFilter->SetInputBufferTypeToRGB();
// 从后台缓冲区读取以确保完整渲染
windowToImageFilter->ReadFrontBufferOff();
windowToImageFilter->Update(); // 执行图像转换
// 获取 VTK 图像数据 (未经翻转)
vtkImageData* pImageData = windowToImageFilter->GetOutput();
// VTK 图像数据的原点在左下角,而 Qt 的 QImage 原点在左上角,需要垂直翻转
vtkSmartPointer<vtkImageFlip> flip = vtkSmartPointer<vtkImageFlip>::New();
flip->SetInputData(pImageData);
//flip->SetInputConnection(windowToImageFilter->GetOutputPort()); // 用这个方法翻转后图像显示的是黑色的!!!
flip->SetFilteredAxis(1); // 翻转 Y 轴(垂直翻转)
flip->Update(); // 翻转操作在这里执行
// 获取翻转后的 VTK 图像数据
vtkImageData* pFlippedImageData = flip->GetOutput(); // 获取翻转后的数据
return createQImage(pFlippedImageData);
}