|
|
|
|
|
#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 == 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<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() == 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<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 == nullptr) {
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|