#include "nmVTKImageExporter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { // 剪贴板、打印对话框和 RenderWindow 均要求在 Qt GUI 线程访问。 bool isImageExporterGuiThread() { QCoreApplication* pApplication = QCoreApplication::instance(); return pApplication == nullptr || pApplication->thread() == QThread::currentThread(); } } nmVTKImageExporter::nmVTKImageExporter(QObject* pParent) : QObject(pParent), m_pRenderWindow(nullptr) { Q_ASSERT(isImageExporterGuiThread()); } nmVTKImageExporter::~nmVTKImageExporter() { Q_ASSERT(isImageExporterGuiThread()); // RenderWindow 由容器中的 QVTKWidget 持有,本服务只解除非所有权引用。 m_pRenderWindow = nullptr; } void nmVTKImageExporter::setRenderWindow(vtkRenderWindow* pRenderWindow) { Q_ASSERT(isImageExporterGuiThread()); // 不增加 VTK 引用计数,容器销毁时必须先传入 nullptr 解除关系。 if(isImageExporterGuiThread()) { m_pRenderWindow = pRenderWindow; } } vtkRenderWindow* nmVTKImageExporter::getRenderWindow() const { return m_pRenderWindow; } QImage nmVTKImageExporter::captureImage() const { Q_ASSERT(isImageExporterGuiThread()); if(!isImageExporterGuiThread() || m_pRenderWindow == nullptr) { return QImage(); } // 捕获前提交最新场景,随后从后台缓冲读取,避免交换缓冲产生黑图。 m_pRenderWindow->Render(); vtkSmartPointer pCapture = vtkSmartPointer::New(); pCapture->SetInput(m_pRenderWindow); pCapture->SetInputBufferTypeToRGB(); // Render 已完成后读取后台缓冲,避免前台缓冲在窗口交换时得到黑图。 pCapture->ReadFrontBufferOff(); pCapture->Update(); // VTK 原点位于左下角,统一翻转为 QImage 使用的左上角原点。 vtkSmartPointer pFlip = vtkSmartPointer::New(); pFlip->SetInputData(pCapture->GetOutput()); pFlip->SetFilteredAxis(1); pFlip->Update(); return createQImage(pFlip->GetOutput()); } bool nmVTKImageExporter::saveImage(const QString& sFileName) const { Q_ASSERT(isImageExporterGuiThread()); if(!isImageExporterGuiThread() || sFileName.isEmpty()) { return false; } const QImage oImage = captureImage(); // QImage::save 在 Qt 4.8 中可按扩展名选择已部署的图片插件。 return !oImage.isNull() && oImage.save(sFileName); } bool nmVTKImageExporter::copyImageToClipboard() const { Q_ASSERT(isImageExporterGuiThread()); if(!isImageExporterGuiThread()) { return false; } const QImage oImage = captureImage(); if(oImage.isNull() || QApplication::clipboard() == nullptr) { return false; } QApplication::clipboard()->setImage(oImage); return true; } bool nmVTKImageExporter::printImage(QWidget* pParent) { Q_ASSERT(isImageExporterGuiThread()); if(!isImageExporterGuiThread() || m_pRenderWindow == nullptr) { return false; } QPrinter oPrinter; QPrintDialog oDialog(&oPrinter, pParent); if(oDialog.exec() != QDialog::Accepted) { return false; } return paintImage(&oPrinter); } bool nmVTKImageExporter::printPreview(QWidget* pParent) { Q_ASSERT(isImageExporterGuiThread()); if(!isImageExporterGuiThread() || m_pRenderWindow == nullptr) { return false; } QPrinter oPrinter(QPrinter::HighResolution); QPrintPreviewDialog oPreview(&oPrinter, pParent); oPreview.resize(800, 600); // Qt 4 使用字符串式信号槽;预览每次请求页面时同步重绘当前画面。 connect(&oPreview, SIGNAL(paintRequested(QPrinter*)), this, SLOT(renderForPrint(QPrinter*))); oPreview.exec(); return true; } void nmVTKImageExporter::renderForPrint(QPrinter* pPrinter) { paintImage(pPrinter); } QImage nmVTKImageExporter::createQImage(vtkImageData* pImageData) const { if(pImageData == nullptr || pImageData->GetPointData() == nullptr) { return QImage(); } const int nWidth = pImageData->GetDimensions()[0]; const int nHeight = pImageData->GetDimensions()[1]; vtkUnsignedCharArray* pScalars = vtkUnsignedCharArray::SafeDownCast( pImageData->GetPointData()->GetScalars()); if(nWidth <= 0 || nHeight <= 0 || pScalars == nullptr) { return QImage(); } const int nComponents = pScalars->GetNumberOfComponents(); unsigned char* pSource = pScalars->GetPointer(0); if(nComponents < 1 || nComponents > 4 || pSource == nullptr) { return QImage(); } // QImage 使用自有 ARGB32 缓冲,不引用 VTK 过滤器的临时输出。 // 单/双分量按灰度解释,三/四分量按 RGB/RGBA 解释。 QImage oImage(nWidth, nHeight, QImage::Format_ARGB32); for(int nY = 0; nY < nHeight; ++nY) { QRgb* pTargetRow = reinterpret_cast(oImage.scanLine(nY)); for(int nX = 0; nX < nWidth; ++nX) { const vtkIdType nTuple = static_cast(nY) * nWidth + nX; unsigned char* pTuple = pSource + nTuple * nComponents; const int nRed = pTuple[0]; const int nGreen = nComponents >= 3 ? pTuple[1] : pTuple[0]; const int nBlue = nComponents >= 3 ? pTuple[2] : pTuple[0]; const int nAlpha = (nComponents == 2 || nComponents == 4) ? pTuple[nComponents - 1] : 255; pTargetRow[nX] = qRgba(nRed, nGreen, nBlue, nAlpha); } } return oImage; } bool nmVTKImageExporter::paintImage(QPrinter* pPrinter) const { if(pPrinter == nullptr) { return false; } const QImage oImage = captureImage(); if(oImage.isNull()) { return false; } // 使用较小缩放比完整适配可打印区域,并将剩余留白平均分配。 const QRect oPageRect = pPrinter->pageRect(); const double dScale = qMin( static_cast(oPageRect.width()) / oImage.width(), static_cast(oPageRect.height()) / oImage.height()); const double dWidth = oImage.width() * dScale; const double dHeight = oImage.height() * dScale; const QRectF oTarget( oPageRect.left() + (oPageRect.width() - dWidth) * 0.5, oPageRect.top() + (oPageRect.height() - dHeight) * 0.5, dWidth, dHeight); QPainter oPainter(pPrinter); // QPainter 构造时绑定打印设备,离开作用域后自动结束打印页面。 oPainter.drawImage(oTarget, oImage); return true; }