1 /****************************************************************************
2 
3  Copyright (C) 2002-2014 Gilles Debunne. All rights reserved.
4 
5  This file is part of the QGLViewer library version 2.7.2.
6 
7  http://www.libqglviewer.com - contact@libqglviewer.com
8 
9  This file may be used under the terms of the GNU General Public License
10  versions 2.0 or 3.0 as published by the Free Software Foundation and
11  appearing in the LICENSE file included in the packaging of this file.
12  In addition, as a special exception, Gilles Debunne gives you certain
13  additional rights, described in the file GPL_EXCEPTION in this package.
14 
15  libQGLViewer uses dual licensing. Commercial/proprietary software must
16  purchase a libQGLViewer Commercial License.
17 
18  This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
19  WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
20 
21 *****************************************************************************/
22 
23 #include "qglviewer.h"
24 
25 #ifndef NO_VECTORIAL_RENDER
26 #include "VRender/VRender.h"
27 #include "ui_VRenderInterface.h"
28 #endif
29 
30 #include "ui_ImageInterface.h"
31 
32 // Output format list
33 #include <QImageWriter>
34 
35 #include <qapplication.h>
36 #include <qcursor.h>
37 #include <qfiledialog.h>
38 #include <qfileinfo.h>
39 #include <qinputdialog.h>
40 #include <qmap.h>
41 #include <qmessagebox.h>
42 #include <qprogressdialog.h>
43 
44 using namespace std;
45 
46 ////// Static global variables - local to this file //////
47 // List of available output file formats, formatted for QFileDialog.
48 static QString formats;
49 // Converts QFileDialog resulting format to Qt snapshotFormat.
50 static QMap<QString, QString> Qtformat;
51 // Converts Qt snapshotFormat to QFileDialog menu string.
52 static QMap<QString, QString> FDFormatString;
53 // Converts snapshotFormat to file extension
54 static QMap<QString, QString> extension;
55 
56 /*! Sets snapshotFileName(). */
setSnapshotFileName(const QString & name)57 void QGLViewer::setSnapshotFileName(const QString &name) {
58   snapshotFileName_ = QFileInfo(name).absoluteFilePath();
59 }
60 
61 #ifndef DOXYGEN
snapshotFilename() const62 const QString &QGLViewer::snapshotFilename() const {
63   qWarning("snapshotFilename is deprecated. Use snapshotFileName() (uppercase "
64            "N) instead.");
65   return snapshotFileName();
66 }
67 #endif
68 
69 /*! Opens a dialog that displays the different available snapshot formats.
70 
71 Then calls setSnapshotFormat() with the selected one (unless the user cancels).
72 
73 Returns \c false if the user presses the Cancel button and \c true otherwise. */
openSnapshotFormatDialog()74 bool QGLViewer::openSnapshotFormatDialog() {
75   bool ok = false;
76   QStringList list = formats.split(";;", QString::SkipEmptyParts);
77   int current = list.indexOf(FDFormatString[snapshotFormat()]);
78   QString format =
79       QInputDialog::getItem(this, "Snapshot format", "Select a snapshot format",
80                             list, current, false, &ok);
81   if (ok)
82     setSnapshotFormat(Qtformat[format]);
83   return ok;
84 }
85 
86 // Finds all available Qt output formats, so that they can be available in
87 // saveSnapshot dialog. Initialize snapshotFormat() to the first one.
initializeSnapshotFormats()88 void QGLViewer::initializeSnapshotFormats() {
89   QList<QByteArray> list = QImageWriter::supportedImageFormats();
90   QStringList formatList;
91   for (int i = 0; i < list.size(); ++i)
92     formatList << QString(list.at(i).toUpper());
93 //        qWarning("Available image formats: ");
94 //        QStringList::Iterator it = formatList.begin();
95 //        while( it != formatList.end() )
96 //  	      qWarning((*it++).);  QT4 change this. qWarning no longer accepts
97 //  QString
98 
99 #ifndef NO_VECTORIAL_RENDER
100   // We add the 3 vectorial formats to the list
101   formatList += "EPS";
102   formatList += "PS";
103   formatList += "XFIG";
104 #endif
105 
106   // Check that the interesting formats are available and add them in "formats"
107   // Unused formats: XPM XBM PBM PGM
108   QStringList QtText, MenuText, Ext;
109   QtText += "JPEG";
110   MenuText += "JPEG (*.jpg)";
111   Ext += "jpg";
112   QtText += "PNG";
113   MenuText += "PNG (*.png)";
114   Ext += "png";
115   QtText += "EPS";
116   MenuText += "Encapsulated Postscript (*.eps)";
117   Ext += "eps";
118   QtText += "PS";
119   MenuText += "Postscript (*.ps)";
120   Ext += "ps";
121   QtText += "PPM";
122   MenuText += "24bit RGB Bitmap (*.ppm)";
123   Ext += "ppm";
124   QtText += "BMP";
125   MenuText += "Windows Bitmap (*.bmp)";
126   Ext += "bmp";
127   QtText += "XFIG";
128   MenuText += "XFig (*.fig)";
129   Ext += "fig";
130 
131   QStringList::iterator itText = QtText.begin();
132   QStringList::iterator itMenu = MenuText.begin();
133   QStringList::iterator itExt = Ext.begin();
134 
135   while (itText != QtText.end()) {
136     // QMessageBox::information(this, "Snapshot ", "Trying format\n"+(*itText));
137     if (formatList.contains((*itText))) {
138       // QMessageBox::information(this, "Snapshot ", "Recognized
139       // format\n"+(*itText));
140       if (formats.isEmpty())
141         setSnapshotFormat(*itText);
142       else
143         formats += ";;";
144       formats += (*itMenu);
145       Qtformat[(*itMenu)] = (*itText);
146       FDFormatString[(*itText)] = (*itMenu);
147       extension[(*itText)] = (*itExt);
148     }
149     // Synchronize parsing
150     itText++;
151     itMenu++;
152     itExt++;
153   }
154 }
155 
156 // Returns false if the user refused to use the fileName
checkFileName(QString & fileName,QWidget * widget,const QString & snapshotFormat)157 static bool checkFileName(QString &fileName, QWidget *widget,
158                           const QString &snapshotFormat) {
159   if (fileName.isEmpty())
160     return false;
161 
162   // Check that extension has been provided
163   QFileInfo info(fileName);
164 
165   if (info.suffix().isEmpty()) {
166     // No extension given. Silently add one
167     if (fileName.right(1) != ".")
168       fileName += ".";
169     fileName += extension[snapshotFormat];
170     info.setFile(fileName);
171   } else if (info.suffix() != extension[snapshotFormat]) {
172     // Extension is not appropriate. Propose a modification
173     QString modifiedName = info.absolutePath() + '/' + info.baseName() + "." +
174                            extension[snapshotFormat];
175     QFileInfo modifInfo(modifiedName);
176     int i = (QMessageBox::warning(
177         widget, "Wrong extension",
178         info.fileName() + " has a wrong extension.\nSave as " +
179             modifInfo.fileName() + " instead ?",
180         QMessageBox::Yes, QMessageBox::No, QMessageBox::Cancel));
181     if (i == QMessageBox::Cancel)
182       return false;
183 
184     if (i == QMessageBox::Yes) {
185       fileName = modifiedName;
186       info.setFile(fileName);
187     }
188   }
189 
190   return true;
191 }
192 
193 #ifndef NO_VECTORIAL_RENDER
194 // static void drawVectorial(void* param)
drawVectorial(void * param)195 void drawVectorial(void *param) { ((QGLViewer *)param)->drawVectorial(); }
196 
197 #ifndef DOXYGEN
198 class ProgressDialog {
199 public:
200   static void showProgressDialog(QOpenGLWidget *parent);
201   static void updateProgress(float progress, const QString &stepString);
202   static void hideProgressDialog();
203 
204 private:
205   static QProgressDialog *progressDialog;
206 };
207 
208 QProgressDialog *ProgressDialog::progressDialog = nullptr;
209 
showProgressDialog(QOpenGLWidget * parent)210 void ProgressDialog::showProgressDialog(QOpenGLWidget *parent) {
211   progressDialog = new QProgressDialog(parent);
212   progressDialog->setWindowTitle("Image rendering progress");
213   progressDialog->setMinimumSize(300, 40);
214   progressDialog->setCancelButton(nullptr);
215   progressDialog->show();
216 }
217 
updateProgress(float progress,const QString & stepString)218 void ProgressDialog::updateProgress(float progress, const QString &stepString) {
219   progressDialog->setValue(int(progress * 100));
220   QString message(stepString);
221   if (message.length() > 33)
222     message = message.left(17) + "..." + message.right(12);
223   progressDialog->setLabelText(message);
224   progressDialog->update();
225   qApp->processEvents();
226 }
227 
hideProgressDialog()228 void ProgressDialog::hideProgressDialog() {
229   progressDialog->close();
230   delete progressDialog;
231   progressDialog = nullptr;
232 }
233 
234 class VRenderInterface : public QDialog, public Ui::VRenderInterface {
235 public:
VRenderInterface(QWidget * parent)236   VRenderInterface(QWidget *parent) : QDialog(parent) { setupUi(this); }
237 };
238 
239 #endif // DOXYGEN
240 
241 // Pops-up a vectorial output option dialog box and save to fileName
242 // Returns -1 in case of Cancel, 0 for success and (todo) error code in case of
243 // problem.
saveVectorialSnapshot(const QString & fileName,QOpenGLWidget * widget,const QString & snapshotFormat)244 static int saveVectorialSnapshot(const QString &fileName, QOpenGLWidget *widget,
245                                  const QString &snapshotFormat) {
246   static VRenderInterface *VRinterface = nullptr;
247 
248   if (!VRinterface)
249     VRinterface = new VRenderInterface(widget);
250 
251   // Configure interface according to selected snapshotFormat
252   if (snapshotFormat == "XFIG") {
253     VRinterface->tightenBBox->setEnabled(false);
254     VRinterface->colorBackground->setEnabled(false);
255   } else {
256     VRinterface->tightenBBox->setEnabled(true);
257     VRinterface->colorBackground->setEnabled(true);
258   }
259 
260   if (VRinterface->exec() == QDialog::Rejected)
261     return -1;
262 
263   vrender::VRenderParams vparams;
264   vparams.setFilename(fileName);
265 
266   if (snapshotFormat == "EPS")
267     vparams.setFormat(vrender::VRenderParams::EPS);
268   if (snapshotFormat == "PS")
269     vparams.setFormat(vrender::VRenderParams::PS);
270   if (snapshotFormat == "XFIG")
271     vparams.setFormat(vrender::VRenderParams::XFIG);
272 
273   vparams.setOption(vrender::VRenderParams::CullHiddenFaces,
274                     !(VRinterface->includeHidden->isChecked()));
275   vparams.setOption(vrender::VRenderParams::OptimizeBackFaceCulling,
276                     VRinterface->cullBackFaces->isChecked());
277   vparams.setOption(vrender::VRenderParams::RenderBlackAndWhite,
278                     VRinterface->blackAndWhite->isChecked());
279   vparams.setOption(vrender::VRenderParams::AddBackground,
280                     VRinterface->colorBackground->isChecked());
281   vparams.setOption(vrender::VRenderParams::TightenBoundingBox,
282                     VRinterface->tightenBBox->isChecked());
283 
284   switch (VRinterface->sortMethod->currentIndex()) {
285   case 0:
286     vparams.setSortMethod(vrender::VRenderParams::NoSorting);
287     break;
288   case 1:
289     vparams.setSortMethod(vrender::VRenderParams::BSPSort);
290     break;
291   case 2:
292     vparams.setSortMethod(vrender::VRenderParams::TopologicalSort);
293     break;
294   case 3:
295     vparams.setSortMethod(vrender::VRenderParams::AdvancedTopologicalSort);
296     break;
297   default:
298     qWarning("VRenderInterface::saveVectorialSnapshot: Unknown SortMethod");
299   }
300 
301   vparams.setProgressFunction(&ProgressDialog::updateProgress);
302   ProgressDialog::showProgressDialog(widget);
303   widget->makeCurrent();
304   widget->raise();
305   vrender::VectorialRender(drawVectorial, (void *)widget, vparams);
306   ProgressDialog::hideProgressDialog();
307   widget->setCursor(QCursor(Qt::ArrowCursor));
308 
309   // Should return vparams.error(), but this is currently not set.
310   return 0;
311 }
312 #endif // NO_VECTORIAL_RENDER
313 
314 class ImageInterface : public QDialog, public Ui::ImageInterface {
315 public:
ImageInterface(QWidget * parent)316   ImageInterface(QWidget *parent) : QDialog(parent) { setupUi(this); }
317 };
318 
319 // Pops-up an image settings dialog box and save to fileName.
320 // Returns false in case of problem.
saveImageSnapshot(const QString & fileName)321 bool QGLViewer::saveImageSnapshot(const QString &fileName) {
322   static ImageInterface *imageInterface = nullptr;
323 
324   if (!imageInterface)
325     imageInterface = new ImageInterface(this);
326 
327   imageInterface->imgWidth->setValue(width());
328   imageInterface->imgHeight->setValue(height());
329 
330   imageInterface->imgQuality->setValue(snapshotQuality());
331 
332   if (imageInterface->exec() == QDialog::Rejected)
333     return true;
334 
335   // Hide closed dialog
336   qApp->processEvents();
337 
338   setSnapshotQuality(imageInterface->imgQuality->value());
339 
340   QColor previousBGColor = backgroundColor();
341   if (imageInterface->whiteBackground->isChecked())
342     setBackgroundColor(Qt::white);
343 
344   QSize finalSize(imageInterface->imgWidth->value(),
345                   imageInterface->imgHeight->value());
346 
347   qreal oversampling = imageInterface->oversampling->value();
348   QSize subSize(int(this->width() / oversampling),
349                 int(this->height() / oversampling));
350 
351   qreal aspectRatio = width() / static_cast<qreal>(height());
352   qreal newAspectRatio =
353       finalSize.width() / static_cast<qreal>(finalSize.height());
354 
355   qreal zNear = camera()->zNear();
356   qreal zFar = camera()->zFar();
357 
358   qreal xMin, yMin;
359   bool expand = imageInterface->expandFrustum->isChecked();
360   if (camera()->type() == qglviewer::Camera::PERSPECTIVE)
361     if ((expand && (newAspectRatio > aspectRatio)) ||
362         (!expand && (newAspectRatio < aspectRatio))) {
363       yMin = zNear * tan(camera()->fieldOfView() / 2.0);
364       xMin = newAspectRatio * yMin;
365     } else {
366       xMin = zNear * tan(camera()->fieldOfView() / 2.0) * aspectRatio;
367       yMin = xMin / newAspectRatio;
368     }
369   else {
370     GLdouble width, height;
371     camera()->getOrthoWidthHeight(width, height);
372     xMin = qreal(width);
373     yMin = qreal(height);
374     if ((expand && (newAspectRatio > aspectRatio)) ||
375         (!expand && (newAspectRatio < aspectRatio)))
376       xMin = newAspectRatio * yMin;
377     else
378       yMin = xMin / newAspectRatio;
379   }
380 
381   QImage image(finalSize.width(), finalSize.height(), QImage::Format_ARGB32);
382 
383   if (image.isNull()) {
384     QMessageBox::warning(this, "Image saving error",
385                          "Unable to create resulting image", QMessageBox::Ok,
386                          QMessageBox::NoButton);
387     return false;
388   }
389 
390   // ProgressDialog disabled since it interfers with the screen grabing mecanism
391   // on some platforms. Too bad. ProgressDialog::showProgressDialog(this);
392 
393   qreal scaleX = subSize.width() / static_cast<qreal>(finalSize.width());
394   qreal scaleY = subSize.height() / static_cast<qreal>(finalSize.height());
395 
396   qreal deltaX = 2.0 * xMin * scaleX;
397   qreal deltaY = 2.0 * yMin * scaleY;
398 
399   int nbX = finalSize.width() / subSize.width();
400   int nbY = finalSize.height() / subSize.height();
401 
402   // Extra subimage on the right/bottom border(s) if needed
403   if (nbX * subSize.width() < finalSize.width())
404     nbX++;
405   if (nbY * subSize.height() < finalSize.height())
406     nbY++;
407 
408   makeCurrent();
409 
410   // tileRegion_ is used by startScreenCoordinatesSystem to appropriately set
411   // the local coordinate system when tiling
412   tileRegion_ = new TileRegion();
413   qreal tileXMin, tileWidth, tileYMin, tileHeight;
414   if ((expand && (newAspectRatio > aspectRatio)) ||
415       (!expand && (newAspectRatio < aspectRatio))) {
416     qreal tileTotalWidth = newAspectRatio * height();
417     tileXMin = (width() - tileTotalWidth) / 2.0;
418     tileWidth = tileTotalWidth * scaleX;
419     tileYMin = 0.0;
420     tileHeight = height() * scaleY;
421     tileRegion_->textScale = 1.0 / scaleY;
422   } else {
423     qreal tileTotalHeight = width() / newAspectRatio;
424     tileYMin = (height() - tileTotalHeight) / 2.0;
425     tileHeight = tileTotalHeight * scaleY;
426     tileXMin = 0.0;
427     tileWidth = width() * scaleX;
428     tileRegion_->textScale = 1.0 / scaleX;
429   }
430 
431   int count = 0;
432   for (int i = 0; i < nbX; i++)
433     for (int j = 0; j < nbY; j++) {
434       preDraw();
435 
436       // Change projection matrix
437       glMatrixMode(GL_PROJECTION);
438       glLoadIdentity();
439       if (camera()->type() == qglviewer::Camera::PERSPECTIVE)
440         glFrustum(-xMin + i * deltaX, -xMin + (i + 1) * deltaX,
441                   yMin - (j + 1) * deltaY, yMin - j * deltaY, zNear, zFar);
442       else
443         glOrtho(-xMin + i * deltaX, -xMin + (i + 1) * deltaX,
444                 yMin - (j + 1) * deltaY, yMin - j * deltaY, zNear, zFar);
445       glMatrixMode(GL_MODELVIEW);
446 
447       tileRegion_->xMin = tileXMin + i * tileWidth;
448       tileRegion_->xMax = tileXMin + (i + 1) * tileWidth;
449       tileRegion_->yMin = tileYMin + j * tileHeight;
450       tileRegion_->yMax = tileYMin + (j + 1) * tileHeight;
451 
452       draw();
453       postDraw();
454 
455       // ProgressDialog::hideProgressDialog();
456       // qApp->processEvents();
457 
458       QImage snapshot = QOpenGLWidget::grabFramebuffer();
459 
460       // ProgressDialog::showProgressDialog(this);
461       // ProgressDialog::updateProgress(count / (qreal)(nbX*nbY),
462       // "Generating image
463       // ["+QString::number(count)+"/"+QString::number(nbX*nbY)+"]");
464       // qApp->processEvents();
465 
466       QImage subImage = snapshot.scaled(subSize, Qt::IgnoreAspectRatio,
467                                         Qt::SmoothTransformation);
468 
469       // Copy subImage in image
470       for (int ii = 0; ii < subSize.width(); ii++) {
471         int fi = i * subSize.width() + ii;
472         if (fi == image.width())
473           break;
474         for (int jj = 0; jj < subSize.height(); jj++) {
475           int fj = j * subSize.height() + jj;
476           if (fj == image.height())
477             break;
478           image.setPixel(fi, fj, subImage.pixel(ii, jj));
479         }
480       }
481       count++;
482     }
483 
484   bool saveOK = image.save(fileName, snapshotFormat().toLatin1().constData(),
485                            snapshotQuality());
486 
487   // ProgressDialog::hideProgressDialog();
488   // setCursor(QCursor(Qt::ArrowCursor));
489 
490   delete tileRegion_;
491   tileRegion_ = nullptr;
492 
493   if (imageInterface->whiteBackground->isChecked())
494     setBackgroundColor(previousBGColor);
495 
496   return saveOK;
497 }
498 
499 /*! Saves a snapshot of the current image displayed by the widget.
500 
501  Options are set using snapshotFormat(), snapshotFileName() and
502  snapshotQuality(). For non vectorial image formats, the image size is equal to
503  the current viewer's dimensions (see width() and height()). See
504  snapshotFormat() for details on supported formats.
505 
506  If \p automatic is \c false (or if snapshotFileName() is empty), a file dialog
507  is opened to ask for the file name.
508 
509  When \p automatic is \c true, the file name is set to \c NAME-NUMBER, where \c
510  NAME is snapshotFileName() and \c NUMBER is snapshotCounter(). The
511  snapshotCounter() is automatically incremented after each snapshot saving. This
512  is useful to create videos from your application: \code void Viewer::init()
513  {
514    resize(720, 576); // PAL DV format (use 720x480 for NTSC DV)
515    connect(this, SIGNAL(drawFinished(bool)), SLOT(saveSnapshot(bool)));
516  }
517  \endcode
518  Then call draw() in a loop (for instance using animate() and/or a camera()
519  KeyFrameInterpolator replay) to create your image sequence.
520 
521  If you want to create a Quicktime VR panoramic sequence, simply use code like
522  this: \code void Viewer::createQuicktime()
523  {
524    const int nbImages = 36;
525    for (int i=0; i<nbImages; ++i)
526          {
527            camera()->setOrientation(2.0*M_PI/nbImages, 0.0); // Theta-Phi
528  orientation showEntireScene(); update(); // calls draw(), which emits
529  drawFinished(), which calls saveSnapshot()
530          }
531  }
532  \endcode
533 
534  If snapshotCounter() is negative, no number is appended to snapshotFileName()
535  and the snapshotCounter() is not incremented. This is useful to force the
536  creation of a file, overwriting the previous one.
537 
538  When \p overwrite is set to \c false (default), a window asks for confirmation
539  if the file already exists. In \p automatic mode, the snapshotCounter() is
540  incremented (if positive) until a non-existing file name is found instead.
541  Otherwise the file is overwritten without confirmation.
542 
543  The VRender library was written by Cyril Soler (Cyril dot Soler at imag dot
544  fr). If the generated PS or EPS file is not properly displayed, remove the
545  anti-aliasing option in your postscript viewer.
546 
547  \note In order to correctly grab the frame buffer, the QGLViewer window is
548  raised in front of other windows by this method. */
saveSnapshot(bool automatic,bool overwrite)549 void QGLViewer::saveSnapshot(bool automatic, bool overwrite) {
550   // Ask for file name
551   if (snapshotFileName().isEmpty() || !automatic) {
552     QString fileName;
553     QString selectedFormat = FDFormatString[snapshotFormat()];
554     fileName = QFileDialog::getSaveFileName(
555         this, "Choose a file name to save under", snapshotFileName(), formats,
556         &selectedFormat,
557         overwrite ? QFileDialog::DontConfirmOverwrite
558                   : QFlags<QFileDialog::Option>(0));
559     setSnapshotFormat(Qtformat[selectedFormat]);
560 
561     if (checkFileName(fileName, this, snapshotFormat()))
562       setSnapshotFileName(fileName);
563     else
564       return;
565   }
566 
567   QFileInfo fileInfo(snapshotFileName());
568 
569   if ((automatic) && (snapshotCounter() >= 0)) {
570     // In automatic mode, names have a number appended
571     const QString baseName = fileInfo.baseName();
572     QString count;
573     count.sprintf("%.04d", snapshotCounter_++);
574     QString suffix;
575     suffix = fileInfo.suffix();
576     if (suffix.isEmpty())
577       suffix = extension[snapshotFormat()];
578     fileInfo.setFile(fileInfo.absolutePath() + '/' + baseName + '-' + count +
579                      '.' + suffix);
580 
581     if (!overwrite)
582       while (fileInfo.exists()) {
583         count.sprintf("%.04d", snapshotCounter_++);
584         fileInfo.setFile(fileInfo.absolutePath() + '/' + baseName + '-' +
585                          count + '.' + fileInfo.suffix());
586       }
587   }
588 
589   bool saveOK;
590 #ifndef NO_VECTORIAL_RENDER
591   if ((snapshotFormat() == "EPS") || (snapshotFormat() == "PS") ||
592       (snapshotFormat() == "XFIG"))
593     // Vectorial snapshot. -1 means cancel, 0 is ok, >0 (should be) an error
594     saveOK = (saveVectorialSnapshot(fileInfo.filePath(), this,
595                                     snapshotFormat()) <= 0);
596   else
597 #endif
598       if (automatic) {
599     QImage snapshot = frameBufferSnapshot();
600     saveOK = snapshot.save(fileInfo.filePath(),
601                            snapshotFormat().toLatin1().constData(),
602                            snapshotQuality());
603   } else
604     saveOK = saveImageSnapshot(fileInfo.filePath());
605 
606   if (!saveOK)
607     QMessageBox::warning(this, "Snapshot problem",
608                          "Unable to save snapshot in\n" + fileInfo.filePath());
609 }
610 
frameBufferSnapshot()611 QImage QGLViewer::frameBufferSnapshot() {
612   // Viewer must be on top of other windows.
613   makeCurrent();
614   raise();
615   // Hack: Qt has problems if the frame buffer is grabbed after QFileDialog is
616   // displayed. We grab the frame buffer before, even if it might be not
617   // necessary (vectorial rendering). The problem could not be reproduced on a
618   // simple example to submit a Qt bug. However, only grabs the backgroundImage
619   // in the eponym example. May come from the driver.
620   return QOpenGLWidget::grabFramebuffer();
621 }
622 
623 /*! Same as saveSnapshot(), except that it uses \p fileName instead of
624  snapshotFileName().
625 
626  If \p fileName is empty, opens a file dialog to select the name.
627 
628  Snapshot settings are set from snapshotFormat() and snapshotQuality().
629 
630  Asks for confirmation when \p fileName already exists and \p overwrite is \c
631  false (default).
632 
633  \attention If \p fileName is a char* (as is "myFile.jpg"), it may be casted
634  into a \c bool, and the other saveSnapshot() method may be used instead. Pass
635  QString("myFile.jpg") as a parameter to prevent this. */
saveSnapshot(const QString & fileName,bool overwrite)636 void QGLViewer::saveSnapshot(const QString &fileName, bool overwrite) {
637   const QString previousName = snapshotFileName();
638   const int previousCounter = snapshotCounter();
639   setSnapshotFileName(fileName);
640   setSnapshotCounter(-1);
641   saveSnapshot(true, overwrite);
642   setSnapshotFileName(previousName);
643   setSnapshotCounter(previousCounter);
644 }
645 
646 /*! Takes a snapshot of the current display and pastes it to the clipboard.
647 
648 This action is activated by the KeyboardAction::SNAPSHOT_TO_CLIPBOARD enum,
649 binded to \c Ctrl+C by default.
650 */
snapshotToClipboard()651 void QGLViewer::snapshotToClipboard() {
652   QClipboard *cb = QApplication::clipboard();
653   cb->setImage(frameBufferSnapshot());
654 }
655