saveSnapshot.cpp
1 /****************************************************************************
2 
3  Copyright (C) 2002-2013 Gilles Debunne. All rights reserved.
4 
5  This file is part of the QGLViewer library version 2.5.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 "ui_VRenderInterface.h"
27 # include "VRender/VRender.h"
28 #endif
29 
30 #include "ui_ImageInterface.h"
31 
32 // Output format list
33 # include <QImageWriter>
34 
35 #include <qfileinfo.h>
36 #include <qfiledialog.h>
37 #include <qmessagebox.h>
38 #include <qapplication.h>
39 #include <qmap.h>
40 #include <qinputdialog.h>
41 #include <qprogressdialog.h>
42 #include <qcursor.h>
43 
44 using namespace std;
45 
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 
58 void QGLViewer::setSnapshotFileName(const QString& name)
59 {
60  snapshotFileName_ = QFileInfo(name).absoluteFilePath();
61 }
62 
63 #ifndef DOXYGEN
64 const QString& QGLViewer::snapshotFilename() const
65 {
66  qWarning("snapshotFilename is deprecated. Use snapshotFileName() (uppercase N) instead.");
67  return snapshotFileName();
68 }
69 #endif
70 
71 
78 {
79  bool ok = false;
80  QStringList list = formats.split(";;", QString::SkipEmptyParts);
81  int current = list.indexOf(FDFormatString[snapshotFormat()]);
82  QString format = QInputDialog::getItem(this, "Snapshot format", "Select a snapshot format", list, current, false, &ok);
83  if (ok)
84  setSnapshotFormat(Qtformat[format]);
85  return ok;
86 }
87 
88 
89 // Finds all available Qt output formats, so that they can be available in
90 // saveSnapshot dialog. Initialize snapshotFormat() to the first one.
91 void QGLViewer::initializeSnapshotFormats()
92 {
93  QList<QByteArray> list = QImageWriter::supportedImageFormats();
94  QStringList formatList;
95  for (int i=0; i < list.size(); ++i)
96  formatList << QString(list.at(i).toUpper());
97  // qWarning("Available image formats: ");
98  // QStringList::Iterator it = formatList.begin();
99  // while( it != formatList.end() )
100  // qWarning((*it++).); QT4 change this. qWarning no longer accepts QString
101 
102 #ifndef NO_VECTORIAL_RENDER
103  // We add the 3 vectorial formats to the list
104  formatList += "EPS";
105  formatList += "PS";
106  formatList += "XFIG";
107 #endif
108 
109  // Check that the interesting formats are available and add them in "formats"
110  // Unused formats: XPM XBM PBM PGM
111  QStringList QtText, MenuText, Ext;
112  QtText += "JPEG"; MenuText += "JPEG (*.jpg)"; Ext += "jpg";
113  QtText += "PNG"; MenuText += "PNG (*.png)"; Ext += "png";
114  QtText += "EPS"; MenuText += "Encapsulated Postscript (*.eps)"; Ext += "eps";
115  QtText += "PS"; MenuText += "Postscript (*.ps)"; Ext += "ps";
116  QtText += "PPM"; MenuText += "24bit RGB Bitmap (*.ppm)"; Ext += "ppm";
117  QtText += "BMP"; MenuText += "Windows Bitmap (*.bmp)"; Ext += "bmp";
118  QtText += "XFIG"; MenuText += "XFig (*.fig)"; Ext += "fig";
119 
120  QStringList::iterator itText = QtText.begin();
121  QStringList::iterator itMenu = MenuText.begin();
122  QStringList::iterator itExt = Ext.begin();
123 
124  while (itText != QtText.end())
125  {
126  //QMessageBox::information(this, "Snapshot ", "Trying format\n"+(*itText));
127  if (formatList.contains((*itText)))
128  {
129  //QMessageBox::information(this, "Snapshot ", "Recognized format\n"+(*itText));
130  if (formats.isEmpty())
131  setSnapshotFormat(*itText);
132  else
133  formats += ";;";
134  formats += (*itMenu);
135  Qtformat[(*itMenu)] = (*itText);
136  FDFormatString[(*itText)] = (*itMenu);
137  extension[(*itText)] = (*itExt);
138  }
139  // Synchronize parsing
140  itText++;
141  itMenu++;
142  itExt++;
143  }
144 }
145 
146 // Returns false if the user refused to use the fileName
147 static bool checkFileName(QString& fileName, QWidget* widget, const QString& snapshotFormat)
148 {
149  if (fileName.isEmpty())
150  return false;
151 
152  // Check that extension has been provided
153  QFileInfo info(fileName);
154 
155  if (info.suffix().isEmpty())
156  {
157  // No extension given. Silently add one
158  if (fileName.right(1) != ".")
159  fileName += ".";
160  fileName += extension[snapshotFormat];
161  info.setFile(fileName);
162  }
163  else if (info.suffix() != extension[snapshotFormat])
164  {
165  // Extension is not appropriate. Propose a modification
166  QString modifiedName = info.absolutePath() + '/' + info.baseName() + "." + extension[snapshotFormat];
167  QFileInfo modifInfo(modifiedName);
168  int i=(QMessageBox::warning(widget,"Wrong extension",
169  info.fileName()+" has a wrong extension.\nSave as "+modifInfo.fileName()+" instead ?",
170  QMessageBox::Yes,
171  QMessageBox::No,
172  QMessageBox::Cancel));
173  if (i==QMessageBox::Cancel)
174  return false;
175 
176  if (i==QMessageBox::Yes)
177  {
178  fileName = modifiedName;
179  info.setFile(fileName);
180  }
181  }
182 
183  return true;
184 }
185 
186 #ifndef NO_VECTORIAL_RENDER
187 // static void drawVectorial(void* param)
188 void drawVectorial(void* param)
189 {
190  ( (QGLViewer*) param )->drawVectorial();
191 }
192 
193 #ifndef DOXYGEN
195 {
196 public:
197  static void showProgressDialog(QGLWidget* parent);
198  static void updateProgress(float progress, const QString& stepString);
199  static void hideProgressDialog();
200 
201 private:
202  static QProgressDialog* progressDialog;
203 };
204 
205 QProgressDialog* ProgressDialog::progressDialog = NULL;
206 
207 void ProgressDialog::showProgressDialog(QGLWidget* parent)
208 {
209  progressDialog = new QProgressDialog(parent);
210  progressDialog->setWindowTitle("Image rendering progress");
211  progressDialog->setMinimumSize(300, 40);
212  progressDialog->setCancelButton(NULL);
213  progressDialog->show();
214 }
215 
216 void ProgressDialog::updateProgress(float progress, const QString& stepString)
217 {
218  progressDialog->setValue(int(progress*100));
219  QString message(stepString);
220  if (message.length() > 33)
221  message = message.left(17) + "..." + message.right(12);
222  progressDialog->setLabelText(message);
223  progressDialog->update();
224  qApp->processEvents();
225 }
226 
227 void ProgressDialog::hideProgressDialog()
228 {
229  progressDialog->close();
230  delete progressDialog;
231  progressDialog = NULL;
232 }
233 
234 class VRenderInterface: public QDialog, public Ui::VRenderInterface
235 {
236 public: 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 problem.
243 static int saveVectorialSnapshot(const QString& fileName, QGLWidget* widget, const QString& snapshotFormat)
244 {
245  static VRenderInterface* VRinterface = NULL;
246 
247  if (!VRinterface)
248  VRinterface = new VRenderInterface(widget);
249 
250 
251  // Configure interface according to selected snapshotFormat
252  if (snapshotFormat == "XFIG")
253  {
254  VRinterface->tightenBBox->setEnabled(false);
255  VRinterface->colorBackground->setEnabled(false);
256  }
257  else
258  {
259  VRinterface->tightenBBox->setEnabled(true);
260  VRinterface->colorBackground->setEnabled(true);
261  }
262 
263  if (VRinterface->exec() == QDialog::Rejected)
264  return -1;
265 
266  vrender::VRenderParams vparams;
267  vparams.setFilename(fileName);
268 
269  if (snapshotFormat == "EPS") vparams.setFormat(vrender::VRenderParams::EPS);
270  if (snapshotFormat == "PS") vparams.setFormat(vrender::VRenderParams::PS);
271  if (snapshotFormat == "XFIG") vparams.setFormat(vrender::VRenderParams::XFIG);
272 
273  vparams.setOption(vrender::VRenderParams::CullHiddenFaces, !(VRinterface->includeHidden->isChecked()));
274  vparams.setOption(vrender::VRenderParams::OptimizeBackFaceCulling, VRinterface->cullBackFaces->isChecked());
275  vparams.setOption(vrender::VRenderParams::RenderBlackAndWhite, VRinterface->blackAndWhite->isChecked());
276  vparams.setOption(vrender::VRenderParams::AddBackground, VRinterface->colorBackground->isChecked());
277  vparams.setOption(vrender::VRenderParams::TightenBoundingBox, VRinterface->tightenBBox->isChecked());
278 
279  switch (VRinterface->sortMethod->currentIndex())
280  {
281  case 0: vparams.setSortMethod(vrender::VRenderParams::NoSorting); break;
282  case 1: vparams.setSortMethod(vrender::VRenderParams::BSPSort); break;
283  case 2: vparams.setSortMethod(vrender::VRenderParams::TopologicalSort); break;
284  case 3: vparams.setSortMethod(vrender::VRenderParams::AdvancedTopologicalSort); break;
285  default:
286  qWarning("VRenderInterface::saveVectorialSnapshot: Unknown SortMethod");
287  }
288 
289  vparams.setProgressFunction(&ProgressDialog::updateProgress);
290  ProgressDialog::showProgressDialog(widget);
291  widget->makeCurrent();
292  widget->raise();
293  vrender::VectorialRender(drawVectorial, (void*) widget, vparams);
294  ProgressDialog::hideProgressDialog();
295  widget->setCursor(QCursor(Qt::ArrowCursor));
296 
297  // Should return vparams.error(), but this is currently not set.
298  return 0;
299 }
300 #endif // NO_VECTORIAL_RENDER
301 
302 
303 class ImageInterface: public QDialog, public Ui::ImageInterface
304 {
305 public: ImageInterface(QWidget *parent) : QDialog(parent) { setupUi(this); }
306 };
307 
308 
309 // Pops-up an image settings dialog box and save to fileName.
310 // Returns false in case of problem.
311 bool QGLViewer::saveImageSnapshot(const QString& fileName)
312 {
313  static ImageInterface* imageInterface = NULL;
314 
315  if (!imageInterface)
316  imageInterface = new ImageInterface(this);
317 
318  imageInterface->imgWidth->setValue(width());
319  imageInterface->imgHeight->setValue(height());
320 
321  imageInterface->imgQuality->setValue(snapshotQuality());
322 
323  if (imageInterface->exec() == QDialog::Rejected)
324  return true;
325 
326  // Hide closed dialog
327  qApp->processEvents();
328 
329  setSnapshotQuality(imageInterface->imgQuality->value());
330 
331  QColor previousBGColor = backgroundColor();
332  if (imageInterface->whiteBackground->isChecked())
333  setBackgroundColor(Qt::white);
334 
335  QSize finalSize(imageInterface->imgWidth->value(), imageInterface->imgHeight->value());
336 
337  double oversampling = imageInterface->oversampling->value();
338  QSize subSize(int(this->width()/oversampling), int(this->height()/oversampling));
339 
340  double aspectRatio = width() / static_cast<double>(height());
341  double newAspectRatio = finalSize.width() / static_cast<double>(finalSize.height());
342 
343  double zNear = camera()->zNear();
344  double zFar = camera()->zFar();
345 
346  double xMin, yMin;
347  bool expand = imageInterface->expandFrustum->isChecked();
348  if (camera()->type() == qglviewer::Camera::PERSPECTIVE)
349  if ((expand && (newAspectRatio>aspectRatio)) || (!expand && (newAspectRatio<aspectRatio)))
350  {
351  yMin = zNear * tan(camera()->fieldOfView() / 2.0);
352  xMin = newAspectRatio * yMin;
353  }
354  else
355  {
356  xMin = zNear * tan(camera()->fieldOfView() / 2.0) * aspectRatio;
357  yMin = xMin / newAspectRatio;
358  }
359  else
360  {
361  camera()->getOrthoWidthHeight(xMin, yMin);
362  if ((expand && (newAspectRatio>aspectRatio)) || (!expand && (newAspectRatio<aspectRatio)))
363  xMin = newAspectRatio * yMin;
364  else
365  yMin = xMin / newAspectRatio;
366  }
367 
368  QImage image(finalSize.width(), finalSize.height(), QImage::Format_ARGB32);
369 
370  if (image.isNull())
371  {
372  QMessageBox::warning(this, "Image saving error",
373  "Unable to create resulting image",
374  QMessageBox::Ok, QMessageBox::NoButton);
375  return false;
376  }
377 
378  // ProgressDialog disabled since it interfers with the screen grabing mecanism on some platforms. Too bad.
379  // ProgressDialog::showProgressDialog(this);
380 
381  double scaleX = subSize.width() / static_cast<double>(finalSize.width());
382  double scaleY = subSize.height() / static_cast<double>(finalSize.height());
383 
384  double deltaX = 2.0 * xMin * scaleX;
385  double deltaY = 2.0 * yMin * scaleY;
386 
387  int nbX = finalSize.width() / subSize.width();
388  int nbY = finalSize.height() / subSize.height();
389 
390  // Extra subimage on the right/bottom border(s) if needed
391  if (nbX * subSize.width() < finalSize.width())
392  nbX++;
393  if (nbY * subSize.height() < finalSize.height())
394  nbY++;
395 
396  makeCurrent();
397 
398  // tileRegion_ is used by startScreenCoordinatesSystem to appropriately set the local
399  // coordinate system when tiling
400  tileRegion_ = new TileRegion();
401  double tileXMin, tileWidth, tileYMin, tileHeight;
402  if ((expand && (newAspectRatio>aspectRatio)) || (!expand && (newAspectRatio<aspectRatio)))
403  {
404  double tileTotalWidth = newAspectRatio * height();
405  tileXMin = (width() - tileTotalWidth) / 2.0;
406  tileWidth = tileTotalWidth * scaleX;
407  tileYMin = 0.0;
408  tileHeight = height() * scaleY;
409  tileRegion_->textScale = 1.0 / scaleY;
410  }
411  else
412  {
413  double tileTotalHeight = width() / newAspectRatio;
414  tileYMin = (height() - tileTotalHeight) / 2.0;
415  tileHeight = tileTotalHeight * scaleY;
416  tileXMin = 0.0;
417  tileWidth = width() * scaleX;
418  tileRegion_->textScale = 1.0 / scaleX;
419  }
420 
421  int count=0;
422  for (int i=0; i<nbX; i++)
423  for (int j=0; j<nbY; j++)
424  {
425  preDraw();
426 
427  // Change projection matrix
428  glMatrixMode(GL_PROJECTION);
429  glLoadIdentity();
430  if (camera()->type() == qglviewer::Camera::PERSPECTIVE)
431  glFrustum(-xMin + i*deltaX, -xMin + (i+1)*deltaX, yMin - (j+1)*deltaY, yMin - j*deltaY, zNear, zFar);
432  else
433  glOrtho(-xMin + i*deltaX, -xMin + (i+1)*deltaX, yMin - (j+1)*deltaY, yMin - j*deltaY, zNear, zFar);
434  glMatrixMode(GL_MODELVIEW);
435 
436  tileRegion_->xMin = tileXMin + i * tileWidth;
437  tileRegion_->xMax = tileXMin + (i+1) * tileWidth;
438  tileRegion_->yMin = tileYMin + j * tileHeight;
439  tileRegion_->yMax = tileYMin + (j+1) * tileHeight;
440 
441  draw();
442  postDraw();
443 
444  // ProgressDialog::hideProgressDialog();
445  // qApp->processEvents();
446 
447  QImage snapshot = grabFrameBuffer(true);
448 
449  // ProgressDialog::showProgressDialog(this);
450  // ProgressDialog::updateProgress(count / (float)(nbX*nbY),
451  // "Generating image ["+QString::number(count)+"/"+QString::number(nbX*nbY)+"]");
452  // qApp->processEvents();
453 
454  QImage subImage = snapshot.scaled(subSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
455 
456  // Copy subImage in image
457  for (int ii=0; ii<subSize.width(); ii++)
458  {
459  int fi = i*subSize.width() + ii;
460  if (fi == image.width())
461  break;
462  for (int jj=0; jj<subSize.height(); jj++)
463  {
464  int fj = j*subSize.height() + jj;
465  if (fj == image.height())
466  break;
467  image.setPixel(fi, fj, subImage.pixel(ii,jj));
468  }
469  }
470  count++;
471  }
472 
473  bool saveOK = image.save(fileName, snapshotFormat().toLatin1().constData(), snapshotQuality());
474 
475  // ProgressDialog::hideProgressDialog();
476  // setCursor(QCursor(Qt::ArrowCursor));
477 
478  delete tileRegion_;
479  tileRegion_ = NULL;
480 
481  if (imageInterface->whiteBackground->isChecked())
482  setBackgroundColor(previousBGColor);
483 
484  return saveOK;
485 }
486 
487 
537 void QGLViewer::saveSnapshot(bool automatic, bool overwrite)
538 {
539  // Ask for file name
540  if (snapshotFileName().isEmpty() || !automatic)
541  {
542  QString fileName;
543  QString selectedFormat = FDFormatString[snapshotFormat()];
544  fileName = QFileDialog::getSaveFileName(this, "Choose a file name to save under", snapshotFileName(), formats, &selectedFormat,
545  overwrite?QFileDialog::DontConfirmOverwrite:QFileDialog::Options(0));
546  setSnapshotFormat(Qtformat[selectedFormat]);
547 
548  if (checkFileName(fileName, this, snapshotFormat()))
549  setSnapshotFileName(fileName);
550  else
551  return;
552  }
553 
554  QFileInfo fileInfo(snapshotFileName());
555 
556  if ((automatic) && (snapshotCounter() >= 0))
557  {
558  // In automatic mode, names have a number appended
559  const QString baseName = fileInfo.baseName();
560  QString count;
561  count.sprintf("%.04d", snapshotCounter_++);
562  QString suffix;
563  suffix = fileInfo.suffix();
564  if (suffix.isEmpty())
565  suffix = extension[snapshotFormat()];
566  fileInfo.setFile(fileInfo.absolutePath()+ '/' + baseName + '-' + count + '.' + suffix);
567 
568  if (!overwrite)
569  while (fileInfo.exists())
570  {
571  count.sprintf("%.04d", snapshotCounter_++);
572  fileInfo.setFile(fileInfo.absolutePath() + '/' +baseName + '-' + count + '.' + fileInfo.suffix());
573  }
574  }
575 
576  bool saveOK;
577 #ifndef NO_VECTORIAL_RENDER
578  if ( (snapshotFormat() == "EPS") || (snapshotFormat() == "PS") || (snapshotFormat() == "XFIG") )
579  // Vectorial snapshot. -1 means cancel, 0 is ok, >0 (should be) an error
580  saveOK = (saveVectorialSnapshot(fileInfo.filePath(), this, snapshotFormat()) <= 0);
581  else
582 #endif
583  if (automatic)
584  {
585  QImage snapshot = frameBufferSnapshot();
586  saveOK = snapshot.save(fileInfo.filePath(), snapshotFormat().toLatin1().constData(), snapshotQuality());
587  }
588  else
589  saveOK = saveImageSnapshot(fileInfo.filePath());
590 
591  if (!saveOK)
592  QMessageBox::warning(this, "Snapshot problem", "Unable to save snapshot in\n"+fileInfo.filePath());
593 }
594 
595 QImage QGLViewer::frameBufferSnapshot()
596 {
597  // Viewer must be on top of other windows.
598  makeCurrent();
599  raise();
600  // Hack: Qt has problems if the frame buffer is grabbed after QFileDialog is displayed.
601  // We grab the frame buffer before, even if it might be not necessary (vectorial rendering).
602  // The problem could not be reproduced on a simple example to submit a Qt bug.
603  // However, only grabs the backgroundImage in the eponym example. May come from the driver.
604  return grabFrameBuffer(true);
605 }
606 
618 void QGLViewer::saveSnapshot(const QString& fileName, bool overwrite)
619 {
620  const QString previousName = snapshotFileName();
621  const int previousCounter = snapshotCounter();
622  setSnapshotFileName(fileName);
623  setSnapshotCounter(-1);
624  saveSnapshot(true, overwrite);
625  setSnapshotFileName(previousName);
626  setSnapshotCounter(previousCounter);
627 }
628 
634 {
635  QClipboard *cb = QApplication::clipboard();
636  cb->setImage(frameBufferSnapshot());
637 }
638 
bool openSnapshotFormatDialog()
Opens a dialog that displays the different available snapshot formats.
void saveSnapshot(bool automatic=true, bool overwrite=false)
Saves a snapshot of the current image displayed by the widget.
void setSnapshotFileName(const QString &name)
Sets snapshotFileName().
A versatile 3D OpenGL viewer based on QGLWidget.
Definition: qglviewer.h:62
void snapshotToClipboard()
Takes a snapshot of the current display and pastes it to the clipboard.