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/nmRender/nmVTKImageExporter.cpp

218 lines
6.9 KiB
C++

#include "nmVTKImageExporter.h"
#include <QApplication>
#include <QClipboard>
#include <QCoreApplication>
#include <QDialog>
#include <QPainter>
#include <QPrintDialog>
#include <QPrintPreviewDialog>
#include <QPrinter>
#include <QThread>
#include <QWidget>
#include <vtkImageData.h>
#include <vtkImageFlip.h>
#include <vtkPointData.h>
#include <vtkRenderWindow.h>
#include <vtkSmartPointer.h>
#include <vtkUnsignedCharArray.h>
#include <vtkWindowToImageFilter.h>
namespace {
// 剪贴板、打印对话框和 RenderWindow 均要求在 Qt GUI 线程访问。
bool isImageExporterGuiThread()
{
QCoreApplication* pApplication = QCoreApplication::instance();
return pApplication == NULL ||
pApplication->thread() == QThread::currentThread();
}
}
nmVTKImageExporter::nmVTKImageExporter(QObject* pParent)
: QObject(pParent),
m_pRenderWindow(NULL)
{
Q_ASSERT(isImageExporterGuiThread());
}
nmVTKImageExporter::~nmVTKImageExporter()
{
Q_ASSERT(isImageExporterGuiThread());
// RenderWindow 由容器中的 QVTKWidget 持有,本服务只解除非所有权引用。
m_pRenderWindow = NULL;
}
void nmVTKImageExporter::setRenderWindow(vtkRenderWindow* pRenderWindow)
{
Q_ASSERT(isImageExporterGuiThread());
// 不增加 VTK 引用计数,容器销毁时必须先传入 NULL 解除关系。
if(isImageExporterGuiThread()) {
m_pRenderWindow = pRenderWindow;
}
}
vtkRenderWindow* nmVTKImageExporter::getRenderWindow() const
{
return m_pRenderWindow;
}
QImage nmVTKImageExporter::captureImage() const
{
Q_ASSERT(isImageExporterGuiThread());
if(!isImageExporterGuiThread() || m_pRenderWindow == NULL) {
return QImage();
}
// 捕获前提交最新场景,随后从后台缓冲读取,避免交换缓冲产生黑图。
m_pRenderWindow->Render();
vtkSmartPointer<vtkWindowToImageFilter> pCapture =
vtkSmartPointer<vtkWindowToImageFilter>::New();
pCapture->SetInput(m_pRenderWindow);
pCapture->SetInputBufferTypeToRGB();
// Render 已完成后读取后台缓冲,避免前台缓冲在窗口交换时得到黑图。
pCapture->ReadFrontBufferOff();
pCapture->Update();
// VTK 原点位于左下角,统一翻转为 QImage 使用的左上角原点。
vtkSmartPointer<vtkImageFlip> pFlip =
vtkSmartPointer<vtkImageFlip>::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() == NULL) {
return false;
}
QApplication::clipboard()->setImage(oImage);
return true;
}
bool nmVTKImageExporter::printImage(QWidget* pParent)
{
Q_ASSERT(isImageExporterGuiThread());
if(!isImageExporterGuiThread() || m_pRenderWindow == NULL) {
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 == NULL) {
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 == NULL || pImageData->GetPointData() == NULL) {
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 == NULL) {
return QImage();
}
const int nComponents = pScalars->GetNumberOfComponents();
unsigned char* pSource = pScalars->GetPointer(0);
if(nComponents < 1 || nComponents > 4 || pSource == NULL) {
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<QRgb*>(oImage.scanLine(nY));
for(int nX = 0; nX < nWidth; ++nX) {
const vtkIdType nTuple =
static_cast<vtkIdType>(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 == NULL) {
return false;
}
const QImage oImage = captureImage();
if(oImage.isNull()) {
return false;
}
// 使用较小缩放比完整适配可打印区域,并将剩余留白平均分配。
const QRect oPageRect = pPrinter->pageRect();
const double dScale = qMin(
static_cast<double>(oPageRect.width()) / oImage.width(),
static_cast<double>(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;
}