Skip to content

Commit

Permalink
Hires screenshot (Stellarium#141)
Browse files Browse the repository at this point in the history
* Docfix and use canonical Q_NULLPTR.

* Towards custom-sized screenshots!

Unfinished. Basic operation and GUI editing OK.
Button bars are currently not yet visible. For large sizes,
font and dialogs become smallish as expected (like on 4k devices).
Maybe this requires an artificial pixel aspect ratio?

* Replace inputMask by QIntValidator, fixes GUI problems.

* Forgot remembering a button, docfixes.

* Proper relocation of button bars for screenshot.

Need another scale factor that can be used by
other components like StarMgr to adjust some settings.
Also enable compiling with old widgets (deactivates hi-res screenshot)
A bit of cleanup

* Solve screenshot image format issues when running OpenGL ES

* Guide note on screenshots

* GUI improvement for screenshot sizes

* Exclude diagnostic test on OSX

Still unclear under which circumstances we
can trigger a fatal memory error.
  • Loading branch information
gzotti authored and alex-w committed May 2, 2018
1 parent d49fdae commit f7acb15
Show file tree
Hide file tree
Showing 14 changed files with 345 additions and 37 deletions.
7 changes: 5 additions & 2 deletions guide/app_config_ini.tex
Original file line number Diff line number Diff line change
Expand Up @@ -502,11 +502,14 @@ \subsection{\big[main\big]}
\begin{tabularx}{\textwidth}{l|l|X}\toprule
\emph{ID} & \emph{Type} & \emph{Description}\\\midrule
invert\_screenshots\_colors & bool & If \emph{true}, Stellarium will saving the screenshorts with inverted colors.\\%\midrule
restore\_defaults & bool & If \emph{true}, Stellarium will restore default settings at startup.
This is equivalent to calling Stellarium with the \command{--restore-defaults} option.\\%\midrule
screenshot\_dir & string & Path for saving screenshots\\%\midrule
screenshot\_custom\_size & bool & \emph{true} if you want to specify particular dimensions next:\\
screenshot\_custom\_width & int & custom width of screenshots. Max.\ available size is hardware dependent!\\
screenshot\_custom\_height & int & custom height of screenshots. Max.\ available size is hardware dependent!\\\midrule
version & string & Version of Stellarium. This parameter may be used to detect necessary changes in config.ini file, do not edit.\\%\midrule
use\_separate\_output\_file & bool & Set to \emph{true} if you want to create a new file for script output for each start of Stellarium\\%\midrule
restore\_defaults & bool & If \emph{true}, Stellarium will restore default settings at startup.
This is equivalent to calling Stellarium with the \command{--restore-defaults} option.\\%\midrule
ignore\_opengl\_warning & bool & Set to \emph{true} if you don't want to see OpenGL warnings for each start of Stellarium.\\%\midrule
check\_requirements & bool & Set to \emph{false} if you want to disable and permanently ignore checking hardware requirements at startup.
Expect problems if hardware is below requirements!\\
Expand Down
10 changes: 8 additions & 2 deletions guide/ch_interface.tex
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,14 @@ \subsection{The Tools Tab}
\item[Indication for mount mode] You can activate the short display of a message when switching type of used mount.
\end{description}

\noindent In addition, you can set the directory where screenshots will be stored,
and you can download more star catalogs on this tab.
\noindent In addition, you can set the directory where screenshots
will be stored, and \newFeature{0.18.1} also whether you want
screenshots sized like Stellarium's window or some other, likely
larger size. The maximum possible size depends on your
hardware. $4096\times4096$ should be possible on most PCs, others may
even create $16384\times16384$ images.

Another button allows you to download more star catalogs on this tab.

\subsection{The Scripts Tab}
\label{sec:gui:scripts}
Expand Down
128 changes: 117 additions & 11 deletions src/StelMainView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,14 @@
#include "StelModuleMgr.hpp"
#include "StelPainter.hpp"
#include "StelGui.hpp"
#include "SkyGui.hpp"
#include "StelTranslator.hpp"
#include "StelUtils.hpp"
#include "StelActionMgr.hpp"
#include "StelOpenGL.hpp"
#include "StelOpenGLArray.hpp"
#include "StelProjector.hpp"
#include "StelMovementMgr.hpp"

#include <QDebug>
#include <QDir>
Expand Down Expand Up @@ -68,6 +71,9 @@
#ifdef OPENGL_DEBUG_LOGGING
#include <QOpenGLDebugLogger>
#endif
#include <QLoggingCategory>

Q_LOGGING_CATEGORY(mainview, "stel.MainView")

#include <clocale>

Expand Down Expand Up @@ -561,6 +567,12 @@ StelMainView::StelMainView(QSettings* settings)
updateQueued(false),
flagInvertScreenShotColors(false),
flagOverwriteScreenshots(false),
#ifndef USE_OLD_QGLWIDGET
flagUseCustomScreenshotSize(false),
customScreenshotWidth(1024),
customScreenshotHeight(768),
customScreenshotMagnification(1.0f),
#endif
screenShotPrefix("stellarium-"),
screenShotDir(""),
flagCursorTimeout(false),
Expand Down Expand Up @@ -720,8 +732,7 @@ QSurfaceFormat StelMainView::getDesiredGLFormat() const
fmt.setBlueBufferSize(8);
fmt.setAlphaBufferSize(8);
fmt.setDepthBufferSize(24);
//I dont think we use the stencil buffer for anything
//but maybe Qt needs it
//Stencil buffer seems necessary for GUI boxes
fmt.setStencilBufferSize(8);

#ifdef OPENGL_DEBUG_LOGGING
Expand Down Expand Up @@ -773,7 +784,7 @@ void StelMainView::init()

QSettings* conf = configuration;

// Should be check of requirements disabled?
// Should be check of requirements disabled? -- NO! This is intentional here, and does no harm.
if (conf->value("main/check_requirements", true).toBool())
{
// Find out lots of debug info about supported version of OpenGL and vendor/renderer.
Expand Down Expand Up @@ -843,6 +854,11 @@ void StelMainView::init()
}

flagInvertScreenShotColors = conf->value("main/invert_screenshots_colors", false).toBool();
#ifndef USE_OLD_QGLWIDGET
flagUseCustomScreenshotSize=conf->value("main/screenshot_custom_size", false).toBool();
customScreenshotWidth=conf->value("main/screenshot_custom_width", 1024).toUInt();
customScreenshotHeight=conf->value("main/screenshot_custom_height", 768).toUInt();
#endif
setFlagCursorTimeout(conf->value("gui/flag_mouse_cursor_timeout", false).toBool());
setCursorTimeout(conf->value("gui/mouse_cursor_timeout", 10.f).toFloat());
setMaxFps(conf->value("video/maximum_fps",10000.f).toFloat());
Expand Down Expand Up @@ -1398,23 +1414,113 @@ void StelMainView::doScreenshot(void)
#ifdef USE_OLD_QGLWIDGET
QImage im = glWidget->grabFrameBuffer();
#else
// Make a screenshot which may be larger than the current window. This is harder than you would think:
// fbObj the framebuffer governs size of the target image, that's the easy part, but it also has its limits.
// However, the GUI parts need to be placed properly,
// HiDPI screens interfere, and the viewing angle has to be maintained.
// First, image size:
glWidget->makeCurrent();
float pixelRatio = QOpenGLContext::currentContext()->screen()->devicePixelRatio();
int imgWidth =stelScene->width() * pixelRatio;
int imgHeight=stelScene->height() * pixelRatio;
if (flagUseCustomScreenshotSize)
{
// Borrowed from Scenery3d renderer: determine maximum framebuffer size as minimum of texture, viewport and renderbuffer size
QOpenGLContext *context = QOpenGLContext::currentContext();
if (context)
{
context->functions()->initializeOpenGLFunctions();
//qDebug() << "initializeOpenGLFunctions()...";
#ifndef Q_OS_MAC
// Make sure we have enough free GPU memory!
GLint freeGLmemory;
context->functions()->glGetIntegerv(GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX, &freeGLmemory);
qCDebug(mainview)<<"Free GPU memory:" << freeGLmemory << "kB -- we ask for " << customScreenshotWidth*customScreenshotHeight*8 / 1024 <<"kB";
GLint freeGLmemoryAMD[4];
context->functions()->glGetIntegerv(GL_RENDERBUFFER_FREE_MEMORY_ATI, freeGLmemoryAMD);
qCDebug(mainview)<<"Free GPU memory (AMD version):" << (uint)freeGLmemoryAMD[1]/1024 << "+" << (uint)freeGLmemoryAMD[3]/1024 << " of " << (uint)freeGLmemoryAMD[0]/1024 << "+" << (uint)freeGLmemoryAMD[2]/1024 << "kB -- we ask for " << customScreenshotWidth*customScreenshotHeight*8 / 1024 <<"kB";
#endif

GLint texSize,viewportSize[2],rbSize;
context->functions()->glGetIntegerv(GL_MAX_TEXTURE_SIZE, &texSize);
context->functions()->glGetIntegerv(GL_MAX_VIEWPORT_DIMS, viewportSize);
context->functions()->glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &rbSize);
qCDebug(mainview)<<"Maximum texture size:"<<texSize;
qCDebug(mainview)<<"Maximum viewport dims:"<<viewportSize[0]<<viewportSize[1];
qCDebug(mainview)<<"Maximum renderbuffer size:"<<rbSize;
int maximumFramebufferSize = qMin(texSize,qMin(rbSize,qMin(viewportSize[0],viewportSize[1])));
qCDebug(mainview)<<"Maximum framebuffer size:"<<maximumFramebufferSize;

imgWidth =qMin(maximumFramebufferSize, customScreenshotWidth);
imgHeight=qMin(maximumFramebufferSize, customScreenshotHeight);
}
else
{
qCWarning(mainview) << "No GL context for screenshot! Aborting.";
return;
}
}
// The texture format depends on used GL version. RGB is fine on OpenGL. on GLES, we must use RGBA and circumvent problems with a few more steps.
bool isGLES=(QOpenGLContext::currentContext()->format().renderableType() == QSurfaceFormat::OpenGLES);

QOpenGLFramebufferObjectFormat fbFormat;
fbFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
fbFormat.setInternalTextureFormat(GL_RGB); // avoid transparent background!
QOpenGLFramebufferObject * fbObj = new QOpenGLFramebufferObject(stelScene->width() * pixelRatio, stelScene->height() * pixelRatio, fbFormat);
fbFormat.setInternalTextureFormat(isGLES ? GL_RGBA : GL_RGB); // try to avoid transparent background!
QOpenGLFramebufferObject * fbObj = new QOpenGLFramebufferObject(imgWidth, imgHeight, fbFormat);
fbObj->bind();
QOpenGLPaintDevice fbObjPaintDev(stelScene->width(), stelScene->height());
// Now the painter has to be convinced to paint to the potentially larger image frame.
QOpenGLPaintDevice fbObjPaintDev(imgWidth, imgHeight);
fbObjPaintDev.setDevicePixelRatio(pixelRatio);

// It seems the projector has its own knowledge about image size. We must adjust fov and image size, but reset afterwards.
StelProjector::StelProjectorParams pParams=StelApp::getInstance().getCore()->getCurrentStelProjectorParams();
StelProjector::StelProjectorParams sParams=pParams;
//qCDebug(mainview) << "Screenshot Viewport: x" << pParams.viewportXywh[0] << "/y" << pParams.viewportXywh[1] << "/w" << pParams.viewportXywh[2] << "/h" << pParams.viewportXywh[3];
sParams.viewportXywh[2]=imgWidth;
sParams.viewportXywh[3]=imgHeight;

// Configure a helper value to allow some modules to tweak their output sizes. Currently used by StarMgr, maybe solve font issues?
customScreenshotMagnification=imgHeight/QApplication::desktop()->screenGeometry().height();

sParams.viewportCenter.set(0.0+(0.5+pParams.viewportCenterOffset.v[0])*imgWidth, 0.0+(0.5+pParams.viewportCenterOffset.v[1])*imgHeight);
sParams.viewportFovDiameter = qMin(imgWidth,imgHeight);
StelApp::getInstance().getCore()->setCurrentStelProjectorParams(sParams);

QPainter painter;
painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
painter.begin(&fbObjPaintDev);
stelScene->render(&painter);
// next line was above begin(), but caused a complaint. Maybe use after begin()?
painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
stelScene->setSceneRect(0, 0, imgWidth, imgHeight);

// push the button bars back to the sides where they belong, and fix root item clipping its children.
dynamic_cast<StelGui*>(gui)->getSkyGui()->setGeometry(0, 0, imgWidth, imgHeight);
rootItem->setSize(QSize(imgWidth, imgHeight));
dynamic_cast<StelGui*>(gui)->forceRefreshGui(); // refresh bar position.

stelScene->render(&painter, QRectF(), QRectF(0,0,imgWidth,imgHeight) , Qt::KeepAspectRatio);
painter.end();
QImage im = fbObj->toImage();

QImage im;
if (isGLES)
{
// We have RGBA texture with possibly empty spots when atmosphere was off.
// See toImage() help entry why to create wrapper here.
QImage fboImage(fbObj->toImage());
//qDebug() << "FBOimage format:" << fboImage.format(); // returns Format_RGBA8888_Premultiplied
QImage im2(fboImage.constBits(), fboImage.width(), fboImage.height(), QImage::Format_RGBX8888);
im=im2.copy();
}
else
im=fbObj->toImage();
fbObj->release();
delete fbObj;
// reset viewport and GUI
StelApp::getInstance().getCore()->setCurrentStelProjectorParams(pParams);
customScreenshotMagnification=1.0f;
stelScene->setSceneRect(0, 0, pParams.viewportXywh[2], pParams.viewportXywh[3]);
rootItem->setSize(QSize(pParams.viewportXywh[2], pParams.viewportXywh[3]));
dynamic_cast<StelGui*>(gui)->getSkyGui()->setGeometry(0, 0, pParams.viewportXywh[2], pParams.viewportXywh[3]);
dynamic_cast<StelGui*>(gui)->forceRefreshGui();
#endif

if (flagInvertScreenShotColors)
Expand Down Expand Up @@ -1502,15 +1608,15 @@ void StelMainView::glContextDoneCurrent()
glWidget->doneCurrent();
}

//! Set the sky background color. Everything else than black creates an work of art!
// Set the sky background color. Everything else than black creates a work of art!
void StelMainView::setSkyBackgroundColor(Vec3f color)
{
rootItem->setSkyBackgroundColor(color);
StelApp::getInstance().getSettings()->setValue("color/sky_background_color", StelUtils::vec3fToStr(color));
emit skyBackgroundColorChanged(color);
}

//! Get the sky background color. Everything else than black creates an work of art!
// Get the sky background color. Everything else than black creates a work of art!
Vec3f StelMainView::getSkyBackgroundColor()
{
return rootItem->getSkyBackgroundColor();
Expand Down
37 changes: 34 additions & 3 deletions src/StelMainView.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ class StelMainView : public QGraphicsView
Q_PROPERTY(bool fullScreen READ isFullScreen WRITE setFullScreen NOTIFY fullScreenChanged)
Q_PROPERTY(bool flagInvertScreenShotColors READ getFlagInvertScreenShotColors WRITE setFlagInvertScreenShotColors NOTIFY flagInvertScreenShotColorsChanged)
Q_PROPERTY(bool flagOverwriteScreenshots READ getFlagOverwriteScreenShots WRITE setFlagOverwriteScreenShots NOTIFY flagOverwriteScreenshotsChanged)
#ifndef USE_OLD_QGLWIDGET
Q_PROPERTY(bool flagUseCustomScreenshotSize READ getFlagUseCustomScreenshotSize WRITE setFlagUseCustomScreenshotSize NOTIFY flagUseCustomScreenshotSizeChanged)
Q_PROPERTY(int customScreenshotWidth READ getCustomScreenshotWidth WRITE setCustomScreenshotWidth NOTIFY customScreenshotWidthChanged)
Q_PROPERTY(int customScreenshotHeight READ getCustomScreenshotHeight WRITE setCustomScreenshotHeight NOTIFY customScreenshotHeightChanged)
#endif
Q_PROPERTY(bool flagUseButtonsBackground READ getFlagUseButtonsBackground WRITE setFlagUseButtonsBackground NOTIFY flagUseButtonsBackgroundChanged)
Q_PROPERTY(bool flagCursorTimeout READ getFlagCursorTimeout WRITE setFlagCursorTimeout NOTIFY flagCursorTimeoutChanged)
Q_PROPERTY(double cursorTimeout READ getCursorTimeout WRITE setCursorTimeout NOTIFY cursorTimeoutChanged)
Expand Down Expand Up @@ -131,6 +136,23 @@ public slots:
//! Set whether existing files are overwritten when saving screenshot
void setFlagOverwriteScreenShots(bool b) {flagOverwriteScreenshots=b; emit flagOverwriteScreenshotsChanged(b);}

#ifndef USE_OLD_QGLWIDGET
//! Get whether custom size should be used for screenshots
bool getFlagUseCustomScreenshotSize() const {return flagUseCustomScreenshotSize;}
//! Set whether custom size should be used for screenshots
void setFlagUseCustomScreenshotSize(bool b) {flagUseCustomScreenshotSize=b; emit flagUseCustomScreenshotSizeChanged(b);}
//! Get custom screenshot width
int getCustomScreenshotWidth() const {return customScreenshotWidth;}
//! Set whether custom size should be used for screenshots
void setCustomScreenshotWidth(int width) {customScreenshotWidth=width; emit customScreenshotWidthChanged(width);}
//! Get custom screenshot height
int getCustomScreenshotHeight() const {return customScreenshotHeight;}
//! Set whether custom size should be used for screenshots
void setCustomScreenshotHeight(int height) {customScreenshotHeight=height; emit customScreenshotHeightChanged(height);}
//! Get screenshot magnification. This should be used by StarMgr, text drawing and other elements which may
//! want to enlarge their output in screenshots to keep them visible.
float getCustomScreenshotMagnification() {return customScreenshotMagnification;}
#endif
//! Get the state of the mouse cursor timeout flag
bool getFlagCursorTimeout() {return flagCursorTimeout;}
//! Get the state of the mouse cursor timeout flag
Expand Down Expand Up @@ -168,9 +190,9 @@ public slots:
//! Get the state of the flag of usage background for GUI buttons
bool getFlagUseButtonsBackground() { return flagUseButtonsBackground; }

//! Set the sky background color. (Actually forwards to the StelRootItem.) Everything else than black creates an work of art!
//! Set the sky background color. (Actually forwards to the StelRootItem.) Everything else than black creates a work of art!
void setSkyBackgroundColor(Vec3f color);
//! Get the sky background color. (Actually retrieves from the StelRootItem.) Everything else than black creates an work of art!
//! Get the sky background color. (Actually retrieves from the StelRootItem.) Everything else than black creates a work of art!
Vec3f getSkyBackgroundColor();

protected:
Expand Down Expand Up @@ -200,6 +222,10 @@ public slots:
void updateIconsRequested();
void flagInvertScreenShotColorsChanged(bool b);
void flagOverwriteScreenshotsChanged(bool b);
void flagUseCustomScreenshotSizeChanged(bool use);
void customScreenshotWidthChanged(int width);
void customScreenshotHeightChanged(int height);

void flagUseButtonsBackgroundChanged(bool b);
void skyBackgroundColorChanged(Vec3f color);
void flagCursorTimeoutChanged(bool b);
Expand Down Expand Up @@ -255,7 +281,12 @@ private slots:
bool updateQueued;
bool flagInvertScreenShotColors;
bool flagOverwriteScreenshots; //! if set to true, screenshot is named exactly screenShotPrefix.png and overwrites existing file

#ifndef USE_OLD_QGLWIDGET
bool flagUseCustomScreenshotSize; //! if true, the next 2 values are observed for screenshots.
int customScreenshotWidth; //! used when flagCustomResolutionScreenshots==true
int customScreenshotHeight; //! used when flagCustomResolutionScreenshots==true
float customScreenshotMagnification; //! tracks the magnification factor customScreenshotHeight/NormalWindowHeight
#endif
QString screenShotPrefix;
QString screenShotDir;

Expand Down
6 changes: 3 additions & 3 deletions src/core/StelHips.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,10 @@ class HipsSurvey : public QObject
//! Render the survey.
//! @param sPainter the painter to use.
//! @param angle total visible angle of the survey in radians. This is used to optimize the rendering of planet
// surveys. Should be set to 2 pi for sky surveys.
//! surveys. Should be set to 2 pi for sky surveys.
//! @param callback if set this will be called for each visible tile, and the callback should do it rendering
// itself. If set to NULL, the function will draw the tiles using the default shader.
void draw(StelPainter* sPainter, double angle = 2 * M_PI, DrawCallback callback = nullptr);
//! itself. If set to Q_NULLPTR, the function will draw the tiles using the default shader.
void draw(StelPainter* sPainter, double angle = 2.0 * M_PI, DrawCallback callback = Q_NULLPTR);

//! Return the source URL of the survey.
const QString& getUrl() const {return url;}
Expand Down
4 changes: 2 additions & 2 deletions src/core/StelPainter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -205,9 +205,9 @@ class StelPainter : protected QOpenGLFunctions
//! @param stacks: number of horizontal segments ("latitude zones")
//! @param orientInside: 1 to have normals point inside, e.g. for Milky Way, Zodiacal Light, etc.
//! @param flipTexture: if texture should be mapped to inside of sphere, e.g. Milky Way.
//! @param topAngle GZ: new parameter. An opening angle [radians] at top of the sphere. Useful if there is an empty
//! @param topAngle: An opening angle [radians] at top of the sphere. Useful if there is an empty
//! region around the top pole, like North Galactic Pole.
//! @param bottomAngle GZ: new parameter. An opening angle [radians] at bottom of the sphere. Useful if there is an empty
//! @param bottomAngle: An opening angle [radians] at bottom of the sphere. Useful if there is an empty
//! region around the bottom pole, like South Galactic Pole.
static StelVertexArray computeSphereNoLight(float radius, float oneMinusOblateness, int slices, int stacks,
int orientInside = 0, bool flipTexture = false,
Expand Down
7 changes: 6 additions & 1 deletion src/core/StelSkyDrawer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
#include "StelUtils.hpp"
#include "StelMovementMgr.hpp"
#include "StelPainter.hpp"
#ifndef USE_OLD_QGLWIDGET
#include "StelMainView.hpp"
#endif

#include "StelModuleMgr.hpp"
#include "LandscapeMgr.hpp"
Expand Down Expand Up @@ -344,7 +347,9 @@ bool StelSkyDrawer::computeRCMag(float mag, RCMag* rcMag) const
{
rcMag->radius = eye->adaptLuminanceScaledLn(pointSourceMagToLnLuminance(mag), starRelativeScale*1.40f/2.f);
rcMag->radius *=starLinearScale;

#ifndef USE_OLD_QGLWIDGET
rcMag->radius *=StelMainView::getInstance().getCustomScreenshotMagnification();
#endif
// Use now statically min_rmag = 0.5, because higher and too small values look bad
if (rcMag->radius < 0.3f)
{
Expand Down
Loading

0 comments on commit f7acb15

Please sign in to comment.