Problems with cropped offscreen rendering on dual monitor Mac
The below small test case is meant to mimic how we implement "Save to Image" in our application, where the user can pick the desired output resolution, without having to replicate the complete renderer + actors setup. It works by temporarily "stealing" the renderer from the render window, and hook it up to an offscreen render window for the duration of the image capture, then giving it back.
The test case looks like this:
Clicking the "Save Image" button should save a 1000x1000 PNG called "test.png" next to the test case executable. The image is supposed to look like this:
This generally seems to work fine, but we have an issue on dual monitor Macs, when the application runs on the external (non-Retina) monitor.
When running the test case on the external monitor of a Retina Mac, the result is as follows:
There's two issues here:
-
The image is twice the pixel size it should be (2000x2000 instead of 1000x1000). This can probably be explained by the VTK code picking up the device pixel ratio of the primary (built-in) Retina screen (which is 2.0) instead of the external one on which the test case is running. This limitation can probably be worked around by us.
-
However, as you can see, the rendering is also cropped to one quadrant of the image.
It is this latter issue which we haven't found a solution/workaround for. No matter what we do, the image ends up cropped if the capture happens while the application is on the external (non-Retina) screen. I suspect it's something internal to the render window and/or renderer that doesn't deal well with a mixed dual monitor setup (Retina + non-Retina).
Any advise on how to solve this is much appreciated!
(Side note: Another workaround we have tried is to not use OffScreenRenderingOn()
on Mac. This also seems to work, but brings with it another problem: It seems the render window cannot produce images bigger than the resolution of the current screen when in non-offscreen mode, which is a blocker for us.)
offscreenbug.cpp
#include <QApplication>
#include <QSurfaceFormat>
#include <QDir>
#include <QPushButton>
#include <QtDebug>
#include <QVTKOpenGLNativeWidget.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <vtkConeSource.h>
#include <vtkPolyDataMapper.h>
#include <vtkActor.h>
#include <vtkCamera.h>
#include <vtkRenderer.h>
#include <vtkPNGWriter.h>
#include <vtkWindowToImageFilter.h>
int main(int argc, char *argv[]) {
QSurfaceFormat::setDefaultFormat(QVTKOpenGLNativeWidget::defaultFormat());
QApplication app(argc, argv);
vtkNew<vtkConeSource> coneSource;
vtkNew<vtkPolyDataMapper> mapper;
mapper->SetInputConnection(coneSource->GetOutputPort());
vtkNew<vtkActor> actor;
actor->SetMapper(mapper);
vtkNew<vtkRenderer> renderer;
renderer->SetBackground(0.8, 0.8, 0.8);
renderer->AddActor(actor);
vtkNew<vtkGenericOpenGLRenderWindow> window;
window->AddRenderer(renderer);
QVTKOpenGLNativeWidget widget;
QPushButton button("Save Image", &widget);
widget.SetRenderWindow(window.Get());
widget.resize(500, 300);
widget.show();
QObject::connect(&button, &QPushButton::clicked, [&renderer]() {
auto oldRenderWindow = renderer->GetRenderWindow();
auto renderWindow = vtkSmartPointer<vtkRenderWindow>::New();
renderWindow->SetSize(1000, 1000);
renderWindow->OffScreenRenderingOn();
renderWindow->AddRenderer(renderer);
auto oldCamera = renderer->GetActiveCamera();
auto camera = vtkSmartPointer<vtkCamera>::New();
camera->DeepCopy(oldCamera);
renderer->ResetCamera();
auto windowToImageFilter = vtkSmartPointer<vtkWindowToImageFilter>::New();
windowToImageFilter->SetInput(renderWindow);
windowToImageFilter->SetInputBufferTypeToRGB();
windowToImageFilter->ReadFrontBufferOff();
auto writer = vtkSmartPointer<vtkPNGWriter>::New();
renderWindow->Render();
windowToImageFilter->Modified();
const auto fileName = QDir(QApplication::applicationDirPath()).absoluteFilePath("test.png");
writer->SetInputConnection(windowToImageFilter->GetOutputPort());
writer->SetFileName(fileName.toLocal8Bit().constData());
writer->Write();
qDebug() << "Image saved to" << fileName;
renderWindow->RemoveRenderer(renderer);
renderer->SetRenderWindow(oldRenderWindow);
renderer->SetActiveCamera(camera);
});
return app.exec();
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.5)
project(offscreenbug)
find_package(Qt5Widgets REQUIRED)
find_package(VTK 8.2.0 REQUIRED COMPONENTS
vtkFiltersSources
vtkGUISupportQt
vtkInteractionStyle
vtkIOImage
vtkRenderingCore
vtkRenderingOpenGL2
)
add_executable(offscreenbug WIN32
offscreenbug.cpp
)
target_link_libraries(offscreenbug PUBLIC
Qt5::Widgets
vtkFiltersSources
vtkGUISupportQt
vtkInteractionStyle
vtkIOImage
vtkRenderingCore
vtkRenderingOpenGL2
)
target_include_directories(offscreenbug SYSTEM PUBLIC
${VTK_INCLUDE_DIRS}
)
target_compile_definitions(offscreenbug PRIVATE
${VTK_DEFINITIONS}
)
target_include_directories(offscreenbug PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_CURRENT_BINARY_DIR}/offscreenbug_autogen/include
)
set_target_properties(offscreenbug PROPERTIES
AUTOMOC ON
AUTOUIC ON
AUTORCC ON
)