1 // Copyright 2008-present Contributors to the OpenImageIO project.
2 // SPDX-License-Identifier: BSD-3-Clause
3 // https://github.com/OpenImageIO/oiio/blob/master/LICENSE.md
4
5 #include <cmath>
6 #include <iostream>
7 #ifndef _WIN32
8 # include <unistd.h>
9 #endif
10 #include <vector>
11
12 #include "imageviewer.h"
13 #include "ivgl.h"
14
15 #include <QApplication>
16 #include <QComboBox>
17 #include <QDesktopWidget>
18 #include <QFileDialog>
19 #include <QKeyEvent>
20 #include <QLabel>
21 #include <QMenu>
22 #include <QMenuBar>
23 #include <QMessageBox>
24 #include <QProgressBar>
25 #include <QResizeEvent>
26 #include <QSettings>
27 #include <QSpinBox>
28 #include <QStatusBar>
29 #include <QTimer>
30
31 #include <OpenImageIO/dassert.h>
32 #include <OpenImageIO/filesystem.h>
33 #include <OpenImageIO/strutil.h>
34 #include <OpenImageIO/sysutil.h>
35 #include <OpenImageIO/timer.h>
36
37 #include "ivutils.h"
38
39
40 namespace {
41
42 inline bool
IsSpecSrgb(const ImageSpec & spec)43 IsSpecSrgb(const ImageSpec& spec)
44 {
45 return Strutil::iequals(spec.get_string_attribute("oiio:ColorSpace"),
46 "sRGB");
47 }
48
49 } // namespace
50
51
52 // clang-format off
53 static const char *s_file_filters = ""
54 "Image Files (*.bmp *.cin *.dcm *.dds *.dpx *.f3d *.fits *.gif *.hdr *.ico *.iff "
55 "*.jpg *.jpe *.jpeg *.jif *.jfif *.jfi *.jp2 *.j2k *.exr *.png *.pbm *.pgm "
56 "*.ppm *.psd *.ptex *.rla *.sgi *.rgb *.rgba *.bw *.int *.inta *.pic *.tga "
57 "*.tpic *.tif *.tiff *.tx *.env *.sm *.vsm *.webp *.zfile);;"
58 "BMP (*.bmp);;"
59 "Cineon (*.cin);;"
60 "Direct Draw Surface (*.dds);;"
61 "DICOM (*.dcm);;"
62 "DPX (*.dpx);;"
63 "Field3D (*.f3d);;"
64 "FITS (*.fits);;"
65 "GIF (*.gif);;"
66 "HDR/RGBE (*.hdr);;"
67 "Icon (*.ico);;"
68 "IFF (*.iff);;"
69 "JPEG (*.jpg *.jpe *.jpeg *.jif *.jfif *.jfi);;"
70 "JPEG-2000 (*.jp2 *.j2k);;"
71 "OpenEXR (*.exr);;"
72 "PhotoShop (*.psd);;"
73 "Portable Network Graphics (*.png);;"
74 "PNM / Netpbm (*.pbm *.pgm *.ppm);;"
75 "Ptex (*.ptex);;"
76 "RLA (*.rla);;"
77 "SGI (*.sgi *.rgb *.rgba *.bw *.int *.inta);;"
78 "Softimage PIC (*.pic);;"
79 "Targa (*.tga *.tpic);;"
80 "TIFF (*.tif *.tiff *.tx *.env *.sm *.vsm);;"
81 "Webp (*.webp);;"
82 "Zfile (*.zfile);;"
83 "All Files (*)";
84 // clang-format on
85
86
87
ImageViewer()88 ImageViewer::ImageViewer()
89 : infoWindow(NULL)
90 , preferenceWindow(NULL)
91 , darkPaletteBox(NULL)
92 , m_current_image(-1)
93 , m_current_channel(0)
94 , m_color_mode(RGBA)
95 , m_last_image(-1)
96 , m_zoom(1)
97 , m_fullscreen(false)
98 , m_default_gamma(1)
99 , m_darkPalette(false)
100 {
101 readSettings(false);
102
103 float gam = Strutil::stof(Sysutil::getenv("GAMMA"));
104 if (gam >= 0.1 && gam <= 5)
105 m_default_gamma = gam;
106 // FIXME -- would be nice to have a more nuanced approach to display
107 // color space, in particular knowing whether the display is sRGB.
108 // Also, some time in the future we may want a real 3D LUT for
109 // "film look", etc.
110
111 if (darkPalette())
112 m_palette = QPalette(Qt::darkGray); // darkGray?
113 else
114 m_palette = QPalette();
115 QApplication::setPalette(m_palette); // FIXME -- why not work?
116 this->setPalette(m_palette);
117
118 slideTimer = new QTimer();
119 slideDuration_ms = 5000;
120 slide_loop = true;
121 glwin = new IvGL(this, *this);
122 glwin->setPalette(m_palette);
123 glwin->resize(m_default_width, m_default_height);
124 setCentralWidget(glwin);
125
126 createActions();
127 createMenus();
128 createToolBars();
129 createStatusBar();
130
131 readSettings();
132
133 setWindowTitle(tr("Image Viewer"));
134 resize(m_default_width, m_default_height);
135 // setSizePolicy (QSizePolicy::Ignored, QSizePolicy::Ignored);
136 }
137
138
139
~ImageViewer()140 ImageViewer::~ImageViewer()
141 {
142 for (auto i : m_images)
143 delete i;
144 }
145
146
147
148 void
closeEvent(QCloseEvent *)149 ImageViewer::closeEvent(QCloseEvent*)
150 {
151 writeSettings();
152 }
153
154
155
156 void
createActions()157 ImageViewer::createActions()
158 {
159 openAct = new QAction(tr("&Open..."), this);
160 openAct->setShortcut(tr("Ctrl+O"));
161 connect(openAct, SIGNAL(triggered()), this, SLOT(open()));
162
163 for (auto& i : openRecentAct) {
164 i = new QAction(this);
165 i->setVisible(false);
166 connect(i, SIGNAL(triggered()), this, SLOT(openRecentFile()));
167 }
168
169 reloadAct = new QAction(tr("&Reload image"), this);
170 reloadAct->setShortcut(tr("Ctrl+R"));
171 connect(reloadAct, SIGNAL(triggered()), this, SLOT(reload()));
172
173 closeImgAct = new QAction(tr("&Close Image"), this);
174 closeImgAct->setShortcut(tr("Ctrl+W"));
175 connect(closeImgAct, SIGNAL(triggered()), this, SLOT(closeImg()));
176
177 saveAsAct = new QAction(tr("&Save As..."), this);
178 saveAsAct->setShortcut(tr("Ctrl+S"));
179 connect(saveAsAct, SIGNAL(triggered()), this, SLOT(saveAs()));
180
181 saveWindowAsAct = new QAction(tr("Save Window As..."), this);
182 connect(saveWindowAsAct, SIGNAL(triggered()), this, SLOT(saveWindowAs()));
183
184 saveSelectionAsAct = new QAction(tr("Save Selection As..."), this);
185 connect(saveSelectionAsAct, SIGNAL(triggered()), this,
186 SLOT(saveSelectionAs()));
187
188 printAct = new QAction(tr("&Print..."), this);
189 printAct->setShortcut(tr("Ctrl+P"));
190 printAct->setEnabled(false);
191 connect(printAct, SIGNAL(triggered()), this, SLOT(print()));
192
193 deleteCurrentImageAct = new QAction(tr("&Delete from disk"), this);
194 deleteCurrentImageAct->setShortcut(tr("Delete"));
195 connect(deleteCurrentImageAct, SIGNAL(triggered()), this,
196 SLOT(deleteCurrentImage()));
197
198 editPreferencesAct = new QAction(tr("&Preferences..."), this);
199 editPreferencesAct->setShortcut(tr("Ctrl+,"));
200 editPreferencesAct->setEnabled(true);
201 connect(editPreferencesAct, SIGNAL(triggered()), this,
202 SLOT(editPreferences()));
203
204 exitAct = new QAction(tr("E&xit"), this);
205 exitAct->setShortcut(tr("Ctrl+Q"));
206 connect(exitAct, SIGNAL(triggered()), this, SLOT(close()));
207
208 exposurePlusOneTenthStopAct = new QAction(tr("Exposure +1/10 stop"), this);
209 exposurePlusOneTenthStopAct->setShortcut(tr("]"));
210 connect(exposurePlusOneTenthStopAct, SIGNAL(triggered()), this,
211 SLOT(exposurePlusOneTenthStop()));
212
213 exposurePlusOneHalfStopAct = new QAction(tr("Exposure +1/2 stop"), this);
214 exposurePlusOneHalfStopAct->setShortcut(tr("}"));
215 connect(exposurePlusOneHalfStopAct, SIGNAL(triggered()), this,
216 SLOT(exposurePlusOneHalfStop()));
217
218 exposureMinusOneTenthStopAct = new QAction(tr("Exposure -1/10 stop"), this);
219 exposureMinusOneTenthStopAct->setShortcut(tr("["));
220 connect(exposureMinusOneTenthStopAct, SIGNAL(triggered()), this,
221 SLOT(exposureMinusOneTenthStop()));
222
223 exposureMinusOneHalfStopAct = new QAction(tr("Exposure -1/2 stop"), this);
224 exposureMinusOneHalfStopAct->setShortcut(tr("{"));
225 connect(exposureMinusOneHalfStopAct, SIGNAL(triggered()), this,
226 SLOT(exposureMinusOneHalfStop()));
227
228 gammaPlusAct = new QAction(tr("Gamma +0.1"), this);
229 gammaPlusAct->setShortcut(tr(")"));
230 connect(gammaPlusAct, SIGNAL(triggered()), this, SLOT(gammaPlus()));
231
232 gammaMinusAct = new QAction(tr("Gamma -0.1"), this);
233 gammaMinusAct->setShortcut(tr("("));
234 connect(gammaMinusAct, SIGNAL(triggered()), this, SLOT(gammaMinus()));
235
236 viewChannelFullAct = new QAction(tr("Full Color"), this);
237 viewChannelFullAct->setShortcut(tr("c"));
238 viewChannelFullAct->setCheckable(true);
239 viewChannelFullAct->setChecked(true);
240 connect(viewChannelFullAct, SIGNAL(triggered()), this,
241 SLOT(viewChannelFull()));
242
243 viewChannelRedAct = new QAction(tr("Red"), this);
244 viewChannelRedAct->setShortcut(tr("r"));
245 viewChannelRedAct->setCheckable(true);
246 connect(viewChannelRedAct, SIGNAL(triggered()), this,
247 SLOT(viewChannelRed()));
248
249 viewChannelGreenAct = new QAction(tr("Green"), this);
250 viewChannelGreenAct->setShortcut(tr("g"));
251 viewChannelGreenAct->setCheckable(true);
252 connect(viewChannelGreenAct, SIGNAL(triggered()), this,
253 SLOT(viewChannelGreen()));
254
255 viewChannelBlueAct = new QAction(tr("Blue"), this);
256 viewChannelBlueAct->setShortcut(tr("b"));
257 viewChannelBlueAct->setCheckable(true);
258 connect(viewChannelBlueAct, SIGNAL(triggered()), this,
259 SLOT(viewChannelBlue()));
260
261 viewChannelAlphaAct = new QAction(tr("Alpha"), this);
262 viewChannelAlphaAct->setShortcut(tr("a"));
263 viewChannelAlphaAct->setCheckable(true);
264 connect(viewChannelAlphaAct, SIGNAL(triggered()), this,
265 SLOT(viewChannelAlpha()));
266
267 viewColorLumAct = new QAction(tr("Luminance"), this);
268 viewColorLumAct->setShortcut(tr("l"));
269 viewColorLumAct->setCheckable(true);
270 connect(viewColorLumAct, SIGNAL(triggered()), this,
271 SLOT(viewChannelLuminance()));
272
273 viewColorRGBAAct = new QAction(tr("RGBA"), this);
274 //viewColorRGBAAct->setShortcut (tr("Ctrl+c"));
275 viewColorRGBAAct->setCheckable(true);
276 viewColorRGBAAct->setChecked(true);
277 connect(viewColorRGBAAct, SIGNAL(triggered()), this, SLOT(viewColorRGBA()));
278
279 viewColorRGBAct = new QAction(tr("RGB"), this);
280 //viewColorRGBAct->setShortcut (tr("Ctrl+a"));
281 viewColorRGBAct->setCheckable(true);
282 connect(viewColorRGBAct, SIGNAL(triggered()), this, SLOT(viewColorRGB()));
283
284 viewColor1ChAct = new QAction(tr("Single channel"), this);
285 viewColor1ChAct->setShortcut(tr("1"));
286 viewColor1ChAct->setCheckable(true);
287 connect(viewColor1ChAct, SIGNAL(triggered()), this, SLOT(viewColor1Ch()));
288
289 viewColorHeatmapAct = new QAction(tr("Single channel (Heatmap)"), this);
290 viewColorHeatmapAct->setShortcut(tr("h"));
291 viewColorHeatmapAct->setCheckable(true);
292 connect(viewColorHeatmapAct, SIGNAL(triggered()), this,
293 SLOT(viewColorHeatmap()));
294
295 viewChannelPrevAct = new QAction(tr("Prev Channel"), this);
296 viewChannelPrevAct->setShortcut(tr(","));
297 connect(viewChannelPrevAct, SIGNAL(triggered()), this,
298 SLOT(viewChannelPrev()));
299
300 viewChannelNextAct = new QAction(tr("Next Channel"), this);
301 viewChannelNextAct->setShortcut(tr("."));
302 connect(viewChannelNextAct, SIGNAL(triggered()), this,
303 SLOT(viewChannelNext()));
304
305 viewSubimagePrevAct = new QAction(tr("Prev Subimage"), this);
306 viewSubimagePrevAct->setShortcut(tr("<"));
307 connect(viewSubimagePrevAct, SIGNAL(triggered()), this,
308 SLOT(viewSubimagePrev()));
309
310 viewSubimageNextAct = new QAction(tr("Next Subimage"), this);
311 viewSubimageNextAct->setShortcut(tr(">"));
312 connect(viewSubimageNextAct, SIGNAL(triggered()), this,
313 SLOT(viewSubimageNext()));
314
315 zoomInAct = new QAction(tr("Zoom &In"), this);
316 zoomInAct->setShortcut(tr("Ctrl++"));
317 connect(zoomInAct, SIGNAL(triggered()), this, SLOT(zoomIn()));
318
319 zoomOutAct = new QAction(tr("Zoom &Out"), this);
320 zoomOutAct->setShortcut(tr("Ctrl+-"));
321 connect(zoomOutAct, SIGNAL(triggered()), this, SLOT(zoomOut()));
322
323 normalSizeAct = new QAction(tr("&Normal Size (1:1)"), this);
324 normalSizeAct->setShortcut(tr("Ctrl+0"));
325 connect(normalSizeAct, SIGNAL(triggered()), this, SLOT(normalSize()));
326
327 fitWindowToImageAct = new QAction(tr("&Fit Window to Image"), this);
328 fitWindowToImageAct->setShortcut(tr("f"));
329 connect(fitWindowToImageAct, SIGNAL(triggered()), this,
330 SLOT(fitWindowToImage()));
331
332 fitImageToWindowAct = new QAction(tr("Fit Image to Window"), this);
333 // fitImageToWindowAct->setEnabled(false);
334 fitImageToWindowAct->setCheckable(true);
335 fitImageToWindowAct->setShortcut(tr("Alt+f"));
336 connect(fitImageToWindowAct, SIGNAL(triggered()), this,
337 SLOT(fitImageToWindow()));
338
339 fullScreenAct = new QAction(tr("Full screen"), this);
340 // fullScreenAct->setEnabled(false);
341 // fullScreenAct->setCheckable(true);
342 fullScreenAct->setShortcut(tr("Ctrl+f"));
343 connect(fullScreenAct, SIGNAL(triggered()), this, SLOT(fullScreenToggle()));
344
345 aboutAct = new QAction(tr("&About"), this);
346 connect(aboutAct, SIGNAL(triggered()), this, SLOT(about()));
347
348 prevImageAct = new QAction(tr("Previous Image"), this);
349 prevImageAct->setShortcut(tr("PgUp"));
350 // prevImageAct->setEnabled(true);
351 connect(prevImageAct, SIGNAL(triggered()), this, SLOT(prevImage()));
352
353 nextImageAct = new QAction(tr("Next Image"), this);
354 nextImageAct->setShortcut(tr("PgDown"));
355 // nextImageAct->setEnabled(true);
356 connect(nextImageAct, SIGNAL(triggered()), this, SLOT(nextImage()));
357
358 toggleImageAct = new QAction(tr("Toggle image"), this);
359 toggleImageAct->setShortcut(tr("T"));
360 // toggleImageAct->setEnabled(true);
361 connect(toggleImageAct, SIGNAL(triggered()), this, SLOT(toggleImage()));
362
363 slideShowAct = new QAction(tr("Start Slide Show"), this);
364 connect(slideShowAct, SIGNAL(triggered()), this, SLOT(slideShow()));
365
366 slideLoopAct = new QAction(tr("Loop slide show"), this);
367 slideLoopAct->setCheckable(true);
368 slideLoopAct->setChecked(true);
369 connect(slideLoopAct, SIGNAL(triggered()), this, SLOT(slideLoop()));
370
371 slideNoLoopAct = new QAction(tr("Stop at end"), this);
372 slideNoLoopAct->setCheckable(true);
373 connect(slideNoLoopAct, SIGNAL(triggered()), this, SLOT(slideNoLoop()));
374
375 sortByNameAct = new QAction(tr("By Name"), this);
376 connect(sortByNameAct, SIGNAL(triggered()), this, SLOT(sortByName()));
377
378 sortByPathAct = new QAction(tr("By File Path"), this);
379 connect(sortByPathAct, SIGNAL(triggered()), this, SLOT(sortByPath()));
380
381 sortByImageDateAct = new QAction(tr("By Image Date"), this);
382 connect(sortByImageDateAct, SIGNAL(triggered()), this,
383 SLOT(sortByImageDate()));
384
385 sortByFileDateAct = new QAction(tr("By File Date"), this);
386 connect(sortByFileDateAct, SIGNAL(triggered()), this,
387 SLOT(sortByFileDate()));
388
389 sortReverseAct = new QAction(tr("Reverse current order"), this);
390 connect(sortReverseAct, SIGNAL(triggered()), this, SLOT(sortReverse()));
391
392 showInfoWindowAct = new QAction(tr("&Image info..."), this);
393 showInfoWindowAct->setShortcut(tr("Ctrl+I"));
394 // showInfoWindowAct->setEnabled(true);
395 connect(showInfoWindowAct, SIGNAL(triggered()), this,
396 SLOT(showInfoWindow()));
397
398 showPixelviewWindowAct = new QAction(tr("&Pixel closeup view..."), this);
399 showPixelviewWindowAct->setCheckable(true);
400 showPixelviewWindowAct->setShortcut(tr("P"));
401 connect(showPixelviewWindowAct, SIGNAL(triggered()), this,
402 SLOT(showPixelviewWindow()));
403
404 pixelviewFollowsMouseBox = new QCheckBox(tr("Pixel view follows mouse"));
405 pixelviewFollowsMouseBox->setChecked(false);
406 linearInterpolationBox = new QCheckBox(tr("Linear interpolation"));
407 linearInterpolationBox->setChecked(true);
408 darkPaletteBox = new QCheckBox(tr("Dark palette"));
409 darkPaletteBox->setChecked(true);
410 autoMipmap = new QCheckBox(tr("Generate mipmaps (requires restart)"));
411 autoMipmap->setChecked(false);
412
413 maxMemoryICLabel = new QLabel(
414 tr("Image Cache max memory (requires restart)"));
415 maxMemoryIC = new QSpinBox();
416 if (sizeof(void*) == 4)
417 maxMemoryIC->setRange(128, 2048); //2GB is enough for 32 bit machines
418 else
419 maxMemoryIC->setRange(128, 8192); //8GB probably ok for 64 bit
420 maxMemoryIC->setSingleStep(64);
421 maxMemoryIC->setSuffix(" MB");
422
423 slideShowDurationLabel = new QLabel(tr("Slide Show delay"));
424 slideShowDuration = new QSpinBox();
425 slideShowDuration->setRange(1, 3600);
426 slideShowDuration->setSingleStep(1);
427 slideShowDuration->setSuffix(" s");
428 slideShowDuration->setAccelerated(true);
429 connect(slideShowDuration, SIGNAL(valueChanged(int)), this,
430 SLOT(setSlideShowDuration(int)));
431 }
432
433
434
435 void
createMenus()436 ImageViewer::createMenus()
437 {
438 openRecentMenu = new QMenu(tr("Open recent..."), this);
439 for (auto& i : openRecentAct)
440 openRecentMenu->addAction(i);
441
442 fileMenu = new QMenu(tr("&File"), this);
443 fileMenu->addAction(openAct);
444 fileMenu->addMenu(openRecentMenu);
445 fileMenu->addAction(reloadAct);
446 fileMenu->addAction(closeImgAct);
447 fileMenu->addSeparator();
448 fileMenu->addAction(saveAsAct);
449 fileMenu->addAction(saveWindowAsAct);
450 fileMenu->addAction(saveSelectionAsAct);
451 fileMenu->addSeparator();
452 fileMenu->addAction(printAct);
453 fileMenu->addAction(deleteCurrentImageAct);
454 fileMenu->addSeparator();
455 fileMenu->addAction(editPreferencesAct);
456 fileMenu->addAction(exitAct);
457 menuBar()->addMenu(fileMenu);
458
459 // Copy
460 // Paste
461 // Clear selection
462 // radio: prioritize selection, crop selection
463
464 expgamMenu = new QMenu(tr("Exposure/gamma")); // submenu
465 expgamMenu->addAction(exposureMinusOneHalfStopAct);
466 expgamMenu->addAction(exposureMinusOneTenthStopAct);
467 expgamMenu->addAction(exposurePlusOneHalfStopAct);
468 expgamMenu->addAction(exposurePlusOneTenthStopAct);
469 expgamMenu->addAction(gammaMinusAct);
470 expgamMenu->addAction(gammaPlusAct);
471
472 // imageMenu = new QMenu(tr("&Image"), this);
473 // menuBar()->addMenu (imageMenu);
474 slideMenu = new QMenu(tr("Slide Show"));
475 slideMenu->addAction(slideShowAct);
476 slideMenu->addAction(slideLoopAct);
477 slideMenu->addAction(slideNoLoopAct);
478
479 sortMenu = new QMenu(tr("Sort"));
480 sortMenu->addAction(sortByNameAct);
481 sortMenu->addAction(sortByPathAct);
482 sortMenu->addAction(sortByImageDateAct);
483 sortMenu->addAction(sortByFileDateAct);
484 sortMenu->addAction(sortReverseAct);
485
486 channelMenu = new QMenu(tr("Channels"));
487 // Color mode: true, random, falsegrgbacCrgR
488 channelMenu->addAction(viewChannelFullAct);
489 channelMenu->addAction(viewChannelRedAct);
490 channelMenu->addAction(viewChannelGreenAct);
491 channelMenu->addAction(viewChannelBlueAct);
492 channelMenu->addAction(viewChannelAlphaAct);
493 channelMenu->addAction(viewChannelPrevAct);
494 channelMenu->addAction(viewChannelNextAct);
495
496 colormodeMenu = new QMenu(tr("Color mode"));
497 colormodeMenu->addAction(viewColorRGBAAct);
498 colormodeMenu->addAction(viewColorRGBAct);
499 colormodeMenu->addAction(viewColor1ChAct);
500 colormodeMenu->addAction(viewColorLumAct);
501 colormodeMenu->addAction(viewColorHeatmapAct);
502
503 viewMenu = new QMenu(tr("&View"), this);
504 viewMenu->addAction(prevImageAct);
505 viewMenu->addAction(nextImageAct);
506 viewMenu->addAction(toggleImageAct);
507 viewMenu->addSeparator();
508 viewMenu->addAction(zoomInAct);
509 viewMenu->addAction(zoomOutAct);
510 viewMenu->addAction(normalSizeAct);
511 viewMenu->addAction(fitWindowToImageAct);
512 viewMenu->addAction(fitImageToWindowAct);
513 viewMenu->addAction(fullScreenAct);
514 viewMenu->addSeparator();
515 viewMenu->addAction(viewSubimagePrevAct);
516 viewMenu->addAction(viewSubimageNextAct);
517 viewMenu->addMenu(channelMenu);
518 viewMenu->addMenu(colormodeMenu);
519 viewMenu->addMenu(expgamMenu);
520 menuBar()->addMenu(viewMenu);
521 // Full screen mode
522 // prev subimage <, next subimage >
523 // fg/bg image...
524
525 toolsMenu = new QMenu(tr("&Tools"), this);
526 // Mode: select, zoom, pan, wipe
527 toolsMenu->addAction(showInfoWindowAct);
528 toolsMenu->addAction(showPixelviewWindowAct);
529 toolsMenu->addMenu(slideMenu);
530 toolsMenu->addMenu(sortMenu);
531
532 // Menus, toolbars, & status
533 // Annotate
534 // [check] overwrite render
535 // connect renderer
536 // kill renderer
537 // store render
538 // Playback: forward, reverse, faster, slower, loop/pingpong
539 menuBar()->addMenu(toolsMenu);
540
541 helpMenu = new QMenu(tr("&Help"), this);
542 helpMenu->addAction(aboutAct);
543 menuBar()->addMenu(helpMenu);
544 // Bring up user's guide
545 }
546
547
548
549 void
createToolBars()550 ImageViewer::createToolBars()
551 {
552 #if 0
553 fileToolBar = addToolBar(tr("File"));
554 fileToolBar->addAction(newAct);
555 fileToolBar->addAction(openAct);
556 fileToolBar->addAction(saveAct);
557
558 editToolBar = addToolBar(tr("Edit"));
559 editToolBar->addAction(cutAct);
560 editToolBar->addAction(copyAct);
561 editToolBar->addAction(pasteAct);
562 #endif
563 }
564
565
566
567 void
createStatusBar()568 ImageViewer::createStatusBar()
569 {
570 statusImgInfo = new QLabel;
571 statusBar()->addWidget(statusImgInfo);
572
573 statusViewInfo = new QLabel;
574 statusBar()->addWidget(statusViewInfo);
575
576 statusProgress = new QProgressBar;
577 statusProgress->setRange(0, 100);
578 statusProgress->reset();
579 statusBar()->addWidget(statusProgress);
580
581 mouseModeComboBox = new QComboBox;
582 mouseModeComboBox->addItem(tr("Zoom"));
583 mouseModeComboBox->addItem(tr("Pan"));
584 mouseModeComboBox->addItem(tr("Wipe"));
585 mouseModeComboBox->addItem(tr("Select"));
586 mouseModeComboBox->addItem(tr("Annotate"));
587 // Note: the order of the above MUST match the order of enum MouseMode
588 statusBar()->addWidget(mouseModeComboBox);
589 mouseModeComboBox->hide();
590 }
591
592
593
594 void
readSettings(bool ui_is_set_up)595 ImageViewer::readSettings(bool ui_is_set_up)
596 {
597 QSettings settings("OpenImageIO", "iv");
598 m_darkPalette = settings.value("darkPalette").toBool();
599 if (!ui_is_set_up)
600 return;
601 pixelviewFollowsMouseBox->setChecked(
602 settings.value("pixelviewFollowsMouse").toBool());
603 linearInterpolationBox->setChecked(
604 settings.value("linearInterpolation").toBool());
605 darkPaletteBox->setChecked(settings.value("darkPalette").toBool());
606 QStringList recent = settings.value("RecentFiles").toStringList();
607 for (auto&& s : recent)
608 addRecentFile(s.toStdString());
609 updateRecentFilesMenu(); // only safe because it's called after menu setup
610
611 autoMipmap->setChecked(settings.value("autoMipmap", false).toBool());
612 if (sizeof(void*) == 4) // 32 bit or 64?
613 maxMemoryIC->setValue(settings.value("maxMemoryIC", 512).toInt());
614 else
615 maxMemoryIC->setValue(settings.value("maxMemoryIC", 2048).toInt());
616 slideShowDuration->setValue(
617 settings.value("slideShowDuration", 10).toInt());
618
619 ImageCache* imagecache = ImageCache::create(true);
620 imagecache->attribute("automip", autoMipmap->isChecked());
621 imagecache->attribute("max_memory_MB", (float)maxMemoryIC->value());
622 }
623
624
625
626 void
writeSettings()627 ImageViewer::writeSettings()
628 {
629 QSettings settings("OpenImageIO", "iv");
630 settings.setValue("pixelviewFollowsMouse",
631 pixelviewFollowsMouseBox->isChecked());
632 settings.setValue("linearInterpolation",
633 linearInterpolationBox->isChecked());
634 settings.setValue("darkPalette", darkPaletteBox->isChecked());
635 settings.setValue("autoMipmap", autoMipmap->isChecked());
636 settings.setValue("maxMemoryIC", maxMemoryIC->value());
637 settings.setValue("slideShowDuration", slideShowDuration->value());
638 QStringList recent;
639 for (auto&& s : m_recent_files)
640 recent.push_front(QString(s.c_str()));
641 settings.setValue("RecentFiles", recent);
642 }
643
644
645
646 bool
image_progress_callback(void * opaque,float done)647 image_progress_callback(void* opaque, float done)
648 {
649 ImageViewer* viewer = (ImageViewer*)opaque;
650 viewer->statusProgress->setValue((int)(done * 100));
651 QApplication::processEvents();
652 return false;
653 }
654
655
656
657 void
open()658 ImageViewer::open()
659 {
660 static QString openPath = QDir::currentPath();
661 QFileDialog dialog(NULL, tr("Open File(s)"), openPath, tr(s_file_filters));
662 dialog.setAcceptMode(QFileDialog::AcceptOpen);
663 dialog.setFileMode(QFileDialog::ExistingFiles);
664 if (!dialog.exec())
665 return;
666 openPath = dialog.directory().path();
667 QStringList names = dialog.selectedFiles();
668
669 int old_lastimage = m_images.size() - 1;
670 for (auto& name : names) {
671 std::string filename = name.toUtf8().data();
672 if (filename.empty())
673 continue;
674 add_image(filename);
675 // int n = m_images.size()-1;
676 // IvImage *newimage = m_images[n];
677 // newimage->read_iv (0, false, image_progress_callback, this);
678 }
679 if (old_lastimage >= 0) {
680 // Otherwise, add_image already did this for us.
681 current_image(old_lastimage + 1);
682 fitWindowToImage(true, true);
683 }
684 }
685
686
687
688 void
openRecentFile()689 ImageViewer::openRecentFile()
690 {
691 QAction* action = qobject_cast<QAction*>(sender());
692 if (action) {
693 std::string filename = action->data().toString().toStdString();
694 // If it's an image we already have loaded, just switch to it
695 // (and reload) rather than loading a second copy.
696 for (size_t i = 0; i < m_images.size(); ++i) {
697 if (m_images[i]->name() == filename) {
698 current_image(i);
699 reload();
700 return;
701 }
702 }
703 // It's not an image we already have loaded
704 add_image(filename);
705 if (m_images.size() > 1) {
706 // Otherwise, add_image already did this for us.
707 current_image(m_images.size() - 1);
708 fitWindowToImage(true, true);
709 }
710 }
711 }
712
713
714
715 void
addRecentFile(const std::string & name)716 ImageViewer::addRecentFile(const std::string& name)
717 {
718 removeRecentFile(name);
719 m_recent_files.insert(m_recent_files.begin(), name);
720 if (m_recent_files.size() > MaxRecentFiles)
721 m_recent_files.resize(MaxRecentFiles);
722 }
723
724
725
726 void
removeRecentFile(const std::string & name)727 ImageViewer::removeRecentFile(const std::string& name)
728 {
729 for (size_t i = 0; i < m_recent_files.size(); ++i) {
730 if (m_recent_files[i] == name) {
731 m_recent_files.erase(m_recent_files.begin() + i);
732 --i;
733 }
734 }
735 }
736
737
738
739 void
updateRecentFilesMenu()740 ImageViewer::updateRecentFilesMenu()
741 {
742 for (size_t i = 0; i < MaxRecentFiles; ++i) {
743 if (i < m_recent_files.size()) {
744 std::string name = Filesystem::filename(m_recent_files[i]);
745 openRecentAct[i]->setText(QString::fromStdString(name));
746 openRecentAct[i]->setData(m_recent_files[i].c_str());
747 openRecentAct[i]->setVisible(true);
748 } else {
749 openRecentAct[i]->setVisible(false);
750 }
751 }
752 }
753
754
755
756 void
reload()757 ImageViewer::reload()
758 {
759 if (m_images.empty())
760 return;
761 IvImage* newimage = m_images[m_current_image];
762 newimage->invalidate();
763 //glwin->trigger_redraw ();
764 displayCurrentImage();
765 }
766
767
768
769 void
add_image(const std::string & filename)770 ImageViewer::add_image(const std::string& filename)
771 {
772 if (filename.empty())
773 return;
774 ImageSpec config;
775 if (rawcolor())
776 config.attribute("oiio:RawColor", 1);
777 IvImage* newimage = new IvImage(filename, &config);
778 newimage->gamma(m_default_gamma);
779 m_images.push_back(newimage);
780 addRecentFile(filename);
781 updateRecentFilesMenu();
782
783 #if 0
784 if (getspec) {
785 if (! newimage->init_spec (filename)) {
786 QMessageBox::information (this, tr("iv Image Viewer"),
787 tr("%1").arg(newimage->geterror().c_str()));
788 } else {
789 std::cerr << "Added image " << filename << ": "
790 << newimage->spec().width << " x " << newimage->spec().height << "\n";
791 }
792 return;
793 }
794 #endif
795 if (m_images.size() == 1) {
796 // If this is the first image, resize to fit it
797 displayCurrentImage();
798 fitWindowToImage(true, true);
799 }
800 }
801
802
803
804 void
saveAs()805 ImageViewer::saveAs()
806 {
807 IvImage* img = cur();
808 if (!img)
809 return;
810 QString name;
811 name = QFileDialog::getSaveFileName(this, tr("Save Image"),
812 QString(img->name().c_str()),
813 tr(s_file_filters));
814 if (name.isEmpty())
815 return;
816 bool ok = img->write(name.toStdString(), "", image_progress_callback, this);
817 if (!ok) {
818 std::cerr << "Save failed: " << img->geterror() << "\n";
819 }
820 }
821
822
823
824 void
saveWindowAs()825 ImageViewer::saveWindowAs()
826 {
827 IvImage* img = cur();
828 if (!img)
829 return;
830 QString name;
831 name = QFileDialog::getSaveFileName(this, tr("Save Window"),
832 QString(img->name().c_str()));
833 if (name.isEmpty())
834 return;
835 img->write(name.toStdString(), "", image_progress_callback, this); // FIXME
836 }
837
838
839
840 void
saveSelectionAs()841 ImageViewer::saveSelectionAs()
842 {
843 IvImage* img = cur();
844 if (!img)
845 return;
846 QString name;
847 name = QFileDialog::getSaveFileName(this, tr("Save Selection"),
848 QString(img->name().c_str()));
849 if (name.isEmpty())
850 return;
851 img->write(name.toStdString(), "", image_progress_callback, this); // FIXME
852 }
853
854
855
856 void
updateTitle()857 ImageViewer::updateTitle()
858 {
859 IvImage* img = cur();
860 if (!img) {
861 setWindowTitle(tr("iv Image Viewer (no image loaded)"));
862 return;
863 }
864 std::string message;
865 message = Strutil::sprintf("%s - iv Image Viewer", img->name().c_str());
866 setWindowTitle(QString::fromLocal8Bit(message.c_str()));
867 }
868
869
870
871 void
updateStatusBar()872 ImageViewer::updateStatusBar()
873 {
874 const ImageSpec* spec = curspec();
875 if (!spec) {
876 statusImgInfo->setText(tr("No image loaded"));
877 statusViewInfo->setText(tr(""));
878 return;
879 }
880 std::string message;
881 message = Strutil::sprintf("(%d/%d) : ", m_current_image + 1,
882 (int)m_images.size());
883 message += cur()->shortinfo();
884 statusImgInfo->setText(message.c_str());
885
886 message.clear();
887 switch (m_color_mode) {
888 case RGBA:
889 message = Strutil::sprintf("RGBA (%d-%d)", m_current_channel,
890 m_current_channel + 3);
891 break;
892 case RGB:
893 message = Strutil::sprintf("RGB (%d-%d)", m_current_channel,
894 m_current_channel + 2);
895 break;
896 case LUMINANCE:
897 message = Strutil::sprintf("Lum (%d-%d)", m_current_channel,
898 m_current_channel + 2);
899 break;
900 case HEATMAP: message = "Heat ";
901 case SINGLE_CHANNEL:
902 if ((int)spec->channelnames.size() > m_current_channel
903 && spec->channelnames[m_current_channel].size())
904 message += spec->channelnames[m_current_channel];
905 else if (m_color_mode == HEATMAP) {
906 message += Strutil::sprintf("%d", m_current_channel);
907 } else {
908 message = Strutil::sprintf("chan %d", m_current_channel);
909 }
910 break;
911 }
912 message += Strutil::sprintf(" %g:%g exp %+.1f gam %.2f",
913 zoom() >= 1 ? zoom() : 1.0f,
914 zoom() >= 1 ? 1.0f : 1.0f / zoom(),
915 cur()->exposure(), cur()->gamma());
916 if (cur()->nsubimages() > 1) {
917 if (cur()->auto_subimage()) {
918 message += Strutil::sprintf(" subimg AUTO (%d/%d)",
919 cur()->subimage() + 1,
920 cur()->nsubimages());
921 } else {
922 message += Strutil::sprintf(" subimg %d/%d", cur()->subimage() + 1,
923 cur()->nsubimages());
924 }
925 }
926 if (cur()->nmiplevels() > 1) {
927 message += Strutil::sprintf(" MIP %d/%d", cur()->miplevel() + 1,
928 cur()->nmiplevels());
929 }
930
931 statusViewInfo->setText(message.c_str()); // tr("iv status"));
932 }
933
934
935
936 bool
loadCurrentImage(int subimage,int miplevel)937 ImageViewer::loadCurrentImage(int subimage, int miplevel)
938 {
939 if (m_current_image < 0 || m_current_image >= (int)m_images.size()) {
940 m_current_image = 0;
941 }
942 IvImage* img = cur();
943 if (img) {
944 // We need the spec available to compare the image format with
945 // opengl's capabilities.
946 if (!img->init_spec(img->name(), subimage, miplevel)) {
947 statusImgInfo->setText(
948 tr("Could not display image: %1.").arg(img->name().c_str()));
949 statusViewInfo->setText(tr(""));
950 return false;
951 }
952
953 // Used to check whether we'll need to do adjustments in the
954 // CPU. If true, images should be loaded as UINT8.
955 bool allow_transforms = false;
956 bool srgb_transform = false;
957
958 // By default, we try to load into OpenGL with the same format,
959 TypeDesc read_format = TypeDesc::UNKNOWN;
960 const ImageSpec& image_spec = img->spec();
961
962 if (image_spec.format.basetype == TypeDesc::DOUBLE) {
963 // AFAIK, OpenGL doesn't support 64-bit floats as pixel size.
964 read_format = TypeDesc::FLOAT;
965 }
966 if (glwin->is_glsl_capable()) {
967 if (image_spec.format.basetype == TypeDesc::HALF
968 && !glwin->is_half_capable()) {
969 //std::cerr << "Loading HALF-FLOAT as FLOAT\n";
970 read_format = TypeDesc::FLOAT;
971 }
972 if (IsSpecSrgb(image_spec) && !glwin->is_srgb_capable()) {
973 // If the image is in sRGB, but OpenGL can't load sRGB textures then
974 // we'll need to do the transformation on the CPU after loading the
975 // image. We (so far) can only do this with UINT8 images, so make
976 // sure that it gets loaded in this format.
977 //std::cerr << "Loading as UINT8 to do sRGB\n";
978 read_format = TypeDesc::UINT8;
979 srgb_transform = true;
980 allow_transforms = true;
981 }
982 } else {
983 //std::cerr << "Loading as UINT8\n";
984 read_format = TypeDesc::UINT8;
985 allow_transforms = true;
986
987 if (IsSpecSrgb(image_spec) && !glwin->is_srgb_capable())
988 srgb_transform = true;
989 }
990
991 // FIXME: This actually won't work since the ImageCacheFile has already
992 // been created when we did the init_spec.
993 // Check whether IvGL recommends generating mipmaps for this image.
994 //ImageCache *imagecache = ImageCache::create (true);
995 //if (glwin->is_too_big (img) && autoMipmap->isChecked ()) {
996 // imagecache->attribute ("automip", 1);
997 //} else {
998 // imagecache->attribute ("automip", 0);
999 //}
1000
1001 // Read the image from disk or from the ImageCache if available.
1002 if (img->read_iv(subimage, miplevel, false, read_format,
1003 image_progress_callback, this, allow_transforms)) {
1004 // The image was read successfully.
1005 // Check if we've got to do sRGB to linear (ie, when not supported
1006 // by OpenGL).
1007 // Do the first pixel transform to fill-in the secondary image
1008 // buffer.
1009 if (allow_transforms) {
1010 img->pixel_transform(srgb_transform, (int)current_color_mode(),
1011 current_channel());
1012 }
1013 return true;
1014 } else {
1015 statusImgInfo->setText(
1016 tr("Could not display image: %1.").arg(img->name().c_str()));
1017 statusViewInfo->setText(tr(""));
1018 return false;
1019 }
1020 }
1021 return false;
1022 }
1023
1024
1025
1026 void
displayCurrentImage(bool update)1027 ImageViewer::displayCurrentImage(bool update)
1028 {
1029 if (m_current_image < 0 || m_current_image >= (int)m_images.size())
1030 m_current_image = 0;
1031 IvImage* img = cur();
1032 if (img) {
1033 if (!img->image_valid()) {
1034 bool load_result = false;
1035
1036 statusViewInfo->hide();
1037 statusProgress->show();
1038 load_result = loadCurrentImage(std::max(0, img->subimage()),
1039 std::max(0, img->miplevel()));
1040 statusProgress->hide();
1041 statusViewInfo->show();
1042
1043 if (load_result) {
1044 update = true;
1045 } else {
1046 return;
1047 }
1048 }
1049 } else {
1050 m_current_image = m_last_image = -1;
1051 ((QOpenGLWidget*)(glwin))->update();
1052 }
1053
1054 if (update) {
1055 glwin->update();
1056 }
1057 float z = zoom();
1058 if (fitImageToWindowAct->isChecked())
1059 z = zoom_needed_to_fit(glwin->width(), glwin->height());
1060 zoom(z);
1061 // glwin->trigger_redraw ();
1062
1063 updateTitle();
1064 updateStatusBar();
1065 if (infoWindow)
1066 infoWindow->update(img);
1067
1068 // printAct->setEnabled(true);
1069 // fitImageToWindowAct->setEnabled(true);
1070 // fullScreenAct->setEnabled(true);
1071 updateActions();
1072 }
1073
1074
1075
1076 void
deleteCurrentImage()1077 ImageViewer::deleteCurrentImage()
1078 {
1079 IvImage* img = cur();
1080 if (img) {
1081 const char* filename = img->name().c_str();
1082 QString message("Are you sure you want to remove <b>");
1083 message = message + QString(filename) + QString("</b> file from disk?");
1084 QMessageBox::StandardButton button;
1085 button = QMessageBox::question(this, "", message,
1086 QMessageBox::Yes | QMessageBox::No);
1087 if (button == QMessageBox::Yes) {
1088 closeImg();
1089 int r = remove(filename);
1090 if (r)
1091 QMessageBox::information(this, "", "Unable to delete file");
1092 }
1093 }
1094 }
1095
1096
1097
1098 void
current_image(int newimage)1099 ImageViewer::current_image(int newimage)
1100 {
1101 #ifndef NDEBUG
1102 Timer swap_image_time;
1103 swap_image_time.start();
1104 #endif
1105 if (m_images.empty() || newimage < 0 || newimage >= (int)m_images.size())
1106 m_current_image = 0;
1107 if (m_current_image != newimage) {
1108 m_last_image = (m_current_image >= 0) ? m_current_image : newimage;
1109 m_current_image = newimage;
1110 displayCurrentImage();
1111 } else {
1112 displayCurrentImage(false);
1113 }
1114 #ifndef NDEBUG
1115 swap_image_time.stop();
1116 std::cerr << "Current Image change elapsed time: " << swap_image_time()
1117 << " seconds \n";
1118 #endif
1119 }
1120
1121
1122
1123 void
prevImage()1124 ImageViewer::prevImage()
1125 {
1126 if (m_images.empty())
1127 return;
1128 if (m_current_image == 0)
1129 current_image((int)m_images.size() - 1);
1130 else
1131 current_image(current_image() - 1);
1132 }
1133
1134
1135 void
nextImage()1136 ImageViewer::nextImage()
1137 {
1138 if (m_images.empty())
1139 return;
1140 if (m_current_image >= (int)m_images.size() - 1)
1141 current_image(0);
1142 else
1143 current_image(current_image() + 1);
1144 }
1145
1146
1147
1148 void
toggleImage()1149 ImageViewer::toggleImage()
1150 {
1151 current_image(m_last_image);
1152 }
1153
1154
1155
1156 void
exposureMinusOneTenthStop()1157 ImageViewer::exposureMinusOneTenthStop()
1158 {
1159 if (m_images.empty())
1160 return;
1161 IvImage* img = m_images[m_current_image];
1162 img->exposure(img->exposure() - 0.1);
1163 if (!glwin->is_glsl_capable()) {
1164 bool srgb_transform = (!glwin->is_srgb_capable()
1165 && IsSpecSrgb(img->spec()));
1166 img->pixel_transform(srgb_transform, (int)current_color_mode(),
1167 current_channel());
1168 displayCurrentImage();
1169 } else {
1170 displayCurrentImage(false);
1171 }
1172 }
1173
1174
1175 void
exposureMinusOneHalfStop()1176 ImageViewer::exposureMinusOneHalfStop()
1177 {
1178 if (m_images.empty())
1179 return;
1180 IvImage* img = m_images[m_current_image];
1181 img->exposure(img->exposure() - 0.5);
1182 if (!glwin->is_glsl_capable()) {
1183 bool srgb_transform = (!glwin->is_srgb_capable()
1184 && IsSpecSrgb(img->spec()));
1185 img->pixel_transform(srgb_transform, (int)current_color_mode(),
1186 current_channel());
1187 displayCurrentImage();
1188 } else {
1189 displayCurrentImage(false);
1190 }
1191 }
1192
1193
1194 void
exposurePlusOneTenthStop()1195 ImageViewer::exposurePlusOneTenthStop()
1196 {
1197 if (m_images.empty())
1198 return;
1199 IvImage* img = m_images[m_current_image];
1200 img->exposure(img->exposure() + 0.1);
1201 if (!glwin->is_glsl_capable()) {
1202 bool srgb_transform = (!glwin->is_srgb_capable()
1203 && IsSpecSrgb(img->spec()));
1204 img->pixel_transform(srgb_transform, (int)current_color_mode(),
1205 current_channel());
1206 displayCurrentImage();
1207 } else {
1208 displayCurrentImage(false);
1209 }
1210 }
1211
1212
1213 void
exposurePlusOneHalfStop()1214 ImageViewer::exposurePlusOneHalfStop()
1215 {
1216 if (m_images.empty())
1217 return;
1218 IvImage* img = m_images[m_current_image];
1219 img->exposure(img->exposure() + 0.5);
1220 if (!glwin->is_glsl_capable()) {
1221 bool srgb_transform = (!glwin->is_srgb_capable()
1222 && IsSpecSrgb(img->spec()));
1223 img->pixel_transform(srgb_transform, (int)current_color_mode(),
1224 current_channel());
1225 displayCurrentImage();
1226 } else {
1227 displayCurrentImage(false);
1228 }
1229 }
1230
1231
1232
1233 void
gammaMinus()1234 ImageViewer::gammaMinus()
1235 {
1236 if (m_images.empty())
1237 return;
1238 IvImage* img = m_images[m_current_image];
1239 img->gamma(img->gamma() - 0.05);
1240 if (!glwin->is_glsl_capable()) {
1241 bool srgb_transform = (!glwin->is_srgb_capable()
1242 && IsSpecSrgb(img->spec()));
1243 img->pixel_transform(srgb_transform, (int)current_color_mode(),
1244 current_channel());
1245 displayCurrentImage();
1246 } else {
1247 displayCurrentImage(false);
1248 }
1249 }
1250
1251
1252 void
gammaPlus()1253 ImageViewer::gammaPlus()
1254 {
1255 if (m_images.empty())
1256 return;
1257 IvImage* img = m_images[m_current_image];
1258 img->gamma(img->gamma() + 0.05);
1259 if (!glwin->is_glsl_capable()) {
1260 bool srgb_transform = (!glwin->is_srgb_capable()
1261 && IsSpecSrgb(img->spec()));
1262 img->pixel_transform(srgb_transform, (int)current_color_mode(),
1263 current_channel());
1264 displayCurrentImage();
1265 } else {
1266 displayCurrentImage(false);
1267 }
1268 }
1269
1270
1271
1272 void
slide(long,bool b)1273 ImageViewer::slide(long /*t*/, bool b)
1274 {
1275 slideLoopAct->setChecked(b == true);
1276 slideNoLoopAct->setChecked(b == false);
1277 }
1278
1279
1280
1281 void
viewChannel(int c,COLOR_MODE colormode)1282 ImageViewer::viewChannel(int c, COLOR_MODE colormode)
1283 {
1284 #ifndef NDEBUG
1285 Timer change_channel_time;
1286 change_channel_time.start();
1287 #endif
1288 if (m_current_channel != c || colormode != m_color_mode) {
1289 bool update = true;
1290 if (!glwin->is_glsl_capable()) {
1291 IvImage* img = cur();
1292 if (img) {
1293 bool srgb_transform = (!glwin->is_srgb_capable()
1294 && IsSpecSrgb(img->spec()));
1295 img->pixel_transform(srgb_transform, (int)colormode, c);
1296 }
1297 } else {
1298 // FIXME: There are even more chances to avoid updating the textures
1299 // if we can keep trac of which channels are in the texture.
1300 if (m_current_channel == c) {
1301 if (m_color_mode == SINGLE_CHANNEL || m_color_mode == HEATMAP) {
1302 if (colormode == HEATMAP || colormode == SINGLE_CHANNEL)
1303 update = false;
1304 } else if (m_color_mode == RGB || m_color_mode == LUMINANCE) {
1305 if (colormode == RGB || colormode == LUMINANCE)
1306 update = false;
1307 }
1308 }
1309 }
1310 m_current_channel = c;
1311 m_color_mode = colormode;
1312 displayCurrentImage(update);
1313
1314 viewChannelFullAct->setChecked(c == 0 && m_color_mode == RGBA);
1315 viewChannelRedAct->setChecked(c == 0 && m_color_mode == SINGLE_CHANNEL);
1316 viewChannelGreenAct->setChecked(c == 1
1317 && m_color_mode == SINGLE_CHANNEL);
1318 viewChannelBlueAct->setChecked(c == 2
1319 && m_color_mode == SINGLE_CHANNEL);
1320 viewChannelAlphaAct->setChecked(c == 3
1321 && m_color_mode == SINGLE_CHANNEL);
1322 viewColorLumAct->setChecked(m_color_mode == LUMINANCE);
1323 viewColorRGBAAct->setChecked(m_color_mode == RGBA);
1324 viewColorRGBAct->setChecked(m_color_mode == RGB);
1325 viewColor1ChAct->setChecked(m_color_mode == SINGLE_CHANNEL);
1326 viewColorHeatmapAct->setChecked(m_color_mode == HEATMAP);
1327 }
1328 #ifndef NDEBUG
1329 change_channel_time.stop();
1330 std::cerr << "Change channel elapsed time: " << change_channel_time()
1331 << " seconds \n";
1332 #endif
1333 }
1334
1335
1336 void
slideImages()1337 ImageViewer::slideImages()
1338 {
1339 if (m_images.empty())
1340 return;
1341 if (m_current_image >= (int)m_images.size() - 1) {
1342 if (slide_loop == true)
1343 current_image(0);
1344 else {
1345 slideTimer->stop();
1346 disconnect(slideTimer, 0, 0, 0);
1347 }
1348 } else
1349 current_image(current_image() + 1);
1350 }
1351
1352
1353 void
slideShow()1354 ImageViewer::slideShow()
1355 {
1356 fullScreenToggle();
1357 connect(slideTimer, SIGNAL(timeout()), this, SLOT(slideImages()));
1358 slideTimer->start(slideDuration_ms);
1359 updateActions();
1360 }
1361
1362
1363
1364 void
slideLoop()1365 ImageViewer::slideLoop()
1366 {
1367 slide_loop = true;
1368 slide(slideDuration_ms, slide_loop);
1369 }
1370
1371
1372 void
slideNoLoop()1373 ImageViewer::slideNoLoop()
1374 {
1375 slide_loop = false;
1376 slide(slideDuration_ms, slide_loop);
1377 }
1378
1379
1380 void
setSlideShowDuration(int seconds)1381 ImageViewer::setSlideShowDuration(int seconds)
1382 {
1383 slideDuration_ms = seconds * 1000;
1384 }
1385
1386
1387
1388 static bool
compName(IvImage * first,IvImage * second)1389 compName(IvImage* first, IvImage* second)
1390 {
1391 std::string firstFile = Filesystem::filename(first->name());
1392 std::string secondFile = Filesystem::filename(second->name());
1393 return (firstFile.compare(secondFile) < 0);
1394 }
1395
1396
1397
1398 void
sortByName()1399 ImageViewer::sortByName()
1400 {
1401 int numImg = m_images.size();
1402 if (numImg < 2)
1403 return;
1404 std::sort(m_images.begin(), m_images.end(), &compName);
1405 current_image(0);
1406 displayCurrentImage();
1407 // updateActions();
1408 }
1409
1410
1411
1412 static bool
compPath(IvImage * first,IvImage * second)1413 compPath(IvImage* first, IvImage* second)
1414 {
1415 std::string firstFile = first->name();
1416 std::string secondFile = second->name();
1417 return (firstFile.compare(secondFile) < 0);
1418 }
1419
1420
1421
1422 void
sortByPath()1423 ImageViewer::sortByPath()
1424 {
1425 int numImg = m_images.size();
1426 if (numImg < 2)
1427 return;
1428 std::sort(m_images.begin(), m_images.end(), &compPath);
1429 current_image(0);
1430 displayCurrentImage();
1431 // updateActions();
1432 }
1433
1434
1435
1436 static bool
DateTime_to_time_t(const char * datetime,time_t & timet)1437 DateTime_to_time_t(const char* datetime, time_t& timet)
1438 {
1439 int year, month, day, hour, min, sec;
1440 int r = sscanf(datetime, "%d:%d:%d %d:%d:%d", &year, &month, &day, &hour,
1441 &min, &sec);
1442 // printf ("%d %d:%d:%d %d:%d:%d\n", r, year, month, day, hour, min, sec);
1443 if (r != 6)
1444 return false;
1445 struct tm tmtime;
1446 time_t now;
1447 Sysutil::get_local_time(&now, &tmtime); // fill in defaults
1448 tmtime.tm_sec = sec;
1449 tmtime.tm_min = min;
1450 tmtime.tm_hour = hour;
1451 tmtime.tm_mday = day;
1452 tmtime.tm_mon = month - 1;
1453 tmtime.tm_year = year - 1900;
1454 timet = mktime(&tmtime);
1455 return true;
1456 }
1457
1458
1459
1460 static bool
compImageDate(IvImage * first,IvImage * second)1461 compImageDate(IvImage* first, IvImage* second)
1462 {
1463 std::time_t firstFile = time(NULL);
1464 std::time_t secondFile = time(NULL);
1465 double diff;
1466 std::string metadatatime = first->spec().get_string_attribute("DateTime");
1467 if (metadatatime.empty()) {
1468 if (first->init_spec(first->name(), 0, 0)) {
1469 metadatatime = first->spec().get_string_attribute("DateTime");
1470 if (metadatatime.empty()) {
1471 if (!Filesystem::exists(first->name()))
1472 return false;
1473 firstFile = Filesystem::last_write_time(first->name());
1474 }
1475 } else
1476 return false;
1477 }
1478 DateTime_to_time_t(metadatatime.c_str(), firstFile);
1479 metadatatime = second->spec().get_string_attribute("DateTime");
1480 if (metadatatime.empty()) {
1481 if (second->init_spec(second->name(), 0, 0)) {
1482 metadatatime = second->spec().get_string_attribute("DateTime");
1483 if (metadatatime.empty()) {
1484 if (!Filesystem::exists(second->name()))
1485 return true;
1486 secondFile = Filesystem::last_write_time(second->name());
1487 }
1488 } else
1489 return true;
1490 }
1491 DateTime_to_time_t(metadatatime.c_str(), secondFile);
1492 diff = difftime(firstFile, secondFile);
1493 if (diff == 0)
1494 return compName(first, second);
1495 return (diff < 0);
1496 }
1497
1498
1499
1500 void
sortByImageDate()1501 ImageViewer::sortByImageDate()
1502 {
1503 int numImg = m_images.size();
1504 if (numImg < 2)
1505 return;
1506 std::sort(m_images.begin(), m_images.end(), &compImageDate);
1507 current_image(0);
1508 displayCurrentImage();
1509 // updateActions();
1510 }
1511
1512
1513
1514 static bool
compFileDate(IvImage * first,IvImage * second)1515 compFileDate(IvImage* first, IvImage* second)
1516 {
1517 std::time_t firstFile, secondFile;
1518 double diff;
1519 if (!Filesystem::exists(first->name()))
1520 return false;
1521 firstFile = Filesystem::last_write_time(first->name());
1522 if (!Filesystem::exists(second->name()))
1523 return true;
1524 secondFile = Filesystem::last_write_time(second->name());
1525 diff = difftime(firstFile, secondFile);
1526 if (diff == 0)
1527 return compName(first, second);
1528 return (diff < 0);
1529 }
1530
1531
1532
1533 void
sortByFileDate()1534 ImageViewer::sortByFileDate()
1535 {
1536 int numImg = m_images.size();
1537 if (numImg < 2)
1538 return;
1539 std::sort(m_images.begin(), m_images.end(), &compFileDate);
1540 current_image(0);
1541 displayCurrentImage();
1542 // updateActions();
1543 }
1544
1545
1546
1547 void
sortReverse()1548 ImageViewer::sortReverse()
1549 {
1550 int numImg = m_images.size();
1551 if (numImg < 2)
1552 return;
1553 std::reverse(m_images.begin(), m_images.end());
1554 current_image(0);
1555 displayCurrentImage();
1556 // updateActions();
1557 }
1558
1559
1560
1561 void
viewChannelFull()1562 ImageViewer::viewChannelFull()
1563 {
1564 viewChannel(0, RGBA);
1565 }
1566
1567
1568 void
viewChannelRed()1569 ImageViewer::viewChannelRed()
1570 {
1571 viewChannel(0, SINGLE_CHANNEL);
1572 }
1573
1574
1575 void
viewChannelGreen()1576 ImageViewer::viewChannelGreen()
1577 {
1578 viewChannel(1, SINGLE_CHANNEL);
1579 }
1580
1581
1582 void
viewChannelBlue()1583 ImageViewer::viewChannelBlue()
1584 {
1585 viewChannel(2, SINGLE_CHANNEL);
1586 }
1587
1588
1589 void
viewChannelAlpha()1590 ImageViewer::viewChannelAlpha()
1591 {
1592 viewChannel(3, SINGLE_CHANNEL);
1593 }
1594
1595
1596 void
viewChannelLuminance()1597 ImageViewer::viewChannelLuminance()
1598 {
1599 viewChannel(m_current_channel, LUMINANCE);
1600 }
1601
1602
1603 void
viewColorRGBA()1604 ImageViewer::viewColorRGBA()
1605 {
1606 viewChannel(m_current_channel, RGBA);
1607 }
1608
1609
1610 void
viewColorRGB()1611 ImageViewer::viewColorRGB()
1612 {
1613 viewChannel(m_current_channel, RGB);
1614 }
1615
1616
1617 void
viewColor1Ch()1618 ImageViewer::viewColor1Ch()
1619 {
1620 viewChannel(m_current_channel, SINGLE_CHANNEL);
1621 }
1622
1623
1624 void
viewColorHeatmap()1625 ImageViewer::viewColorHeatmap()
1626 {
1627 viewChannel(m_current_channel, HEATMAP);
1628 }
1629
1630
1631 void
viewChannelPrev()1632 ImageViewer::viewChannelPrev()
1633 {
1634 if (glwin->is_glsl_capable()) {
1635 if (m_current_channel > 0)
1636 viewChannel(m_current_channel - 1, m_color_mode);
1637 } else {
1638 // Simulate old behavior.
1639 if (m_color_mode == RGBA || m_color_mode == RGB) {
1640 viewChannel(m_current_channel, LUMINANCE);
1641 } else if (m_color_mode == SINGLE_CHANNEL) {
1642 if (m_current_channel == 0)
1643 viewChannelFull();
1644 else
1645 viewChannel(m_current_channel - 1, SINGLE_CHANNEL);
1646 }
1647 }
1648 }
1649
1650
1651 void
viewChannelNext()1652 ImageViewer::viewChannelNext()
1653 {
1654 if (glwin->is_glsl_capable()) {
1655 viewChannel(m_current_channel + 1, m_color_mode);
1656 } else {
1657 // Simulate old behavior.
1658 if (m_color_mode == LUMINANCE) {
1659 viewChannelFull();
1660 } else if (m_color_mode == RGBA || m_color_mode == RGB) {
1661 viewChannelRed();
1662 } else if (m_color_mode == SINGLE_CHANNEL) {
1663 viewChannel(m_current_channel + 1, SINGLE_CHANNEL);
1664 }
1665 }
1666 }
1667
1668
1669
1670 void
viewSubimagePrev()1671 ImageViewer::viewSubimagePrev()
1672 {
1673 IvImage* img = cur();
1674 if (!img)
1675 return;
1676 bool ok = false;
1677 if (img->miplevel() > 0) {
1678 ok = loadCurrentImage(img->subimage(), img->miplevel() - 1);
1679 } else if (img->subimage() > 0) {
1680 ok = loadCurrentImage(img->subimage() - 1);
1681 } else if (img->nsubimages() > 0) {
1682 img->auto_subimage(true);
1683 ok = loadCurrentImage(0);
1684 }
1685 if (ok) {
1686 if (fitImageToWindowAct->isChecked())
1687 fitImageToWindow();
1688 displayCurrentImage();
1689 }
1690 }
1691
1692
1693 void
viewSubimageNext()1694 ImageViewer::viewSubimageNext()
1695 {
1696 IvImage* img = cur();
1697 if (!img)
1698 return;
1699 bool ok = false;
1700 if (img->auto_subimage()) {
1701 img->auto_subimage(false);
1702 ok = loadCurrentImage(0);
1703 } else if (img->miplevel() < img->nmiplevels() - 1) {
1704 ok = loadCurrentImage(img->subimage(), img->miplevel() + 1);
1705 } else if (img->subimage() < img->nsubimages() - 1) {
1706 ok = loadCurrentImage(img->subimage() + 1);
1707 }
1708 if (ok) {
1709 if (fitImageToWindowAct->isChecked())
1710 fitImageToWindow();
1711 displayCurrentImage();
1712 }
1713 }
1714
1715
1716
1717 void
keyPressEvent(QKeyEvent * event)1718 ImageViewer::keyPressEvent(QKeyEvent* event)
1719 {
1720 switch (event->key()) {
1721 case Qt::Key_Left:
1722 case Qt::Key_Up:
1723 case Qt::Key_PageUp: prevImage(); return; //break;
1724 case Qt::Key_Right:
1725 // std::cerr << "Modifier is " << (int)event->modifiers() << '\n';
1726 // fprintf (stderr, "%x\n", (int)event->modifiers());
1727 // if (event->modifiers() & Qt::ShiftModifier)
1728 // std::cerr << "hey, ctrl right\n";
1729 case Qt::Key_Down:
1730 case Qt::Key_PageDown: nextImage(); return; //break;
1731 case Qt::Key_Escape:
1732 if (m_fullscreen)
1733 fullScreenToggle();
1734 return;
1735 case Qt::Key_Minus:
1736 case Qt::Key_Underscore: zoomOut(); break;
1737 case Qt::Key_Plus:
1738 case Qt::Key_Equal: zoomIn(); break;
1739 default:
1740 // std::cerr << "ImageViewer key " << (int)event->key() << '\n';
1741 QMainWindow::keyPressEvent(event);
1742 }
1743 }
1744
1745
1746
1747 void
resizeEvent(QResizeEvent * event)1748 ImageViewer::resizeEvent(QResizeEvent* event)
1749 {
1750 if (fitImageToWindowAct->isChecked())
1751 fitImageToWindow();
1752 QMainWindow::resizeEvent(event);
1753 }
1754
1755
1756
1757 void
closeImg()1758 ImageViewer::closeImg()
1759 {
1760 if (m_images.empty())
1761 return;
1762 delete m_images[m_current_image];
1763 m_images[m_current_image] = NULL;
1764 m_images.erase(m_images.begin() + m_current_image);
1765
1766 // Update image indices
1767 // This should be done for all image indices we may be storing
1768 if (m_last_image == m_current_image) {
1769 if (!m_images.empty() && m_last_image > 0)
1770 m_last_image = 0;
1771 else
1772 m_last_image = -1;
1773 }
1774 if (m_last_image > m_current_image)
1775 m_last_image--;
1776
1777 m_current_image = m_current_image < (int)m_images.size() ? m_current_image
1778 : 0;
1779 displayCurrentImage();
1780 }
1781
1782
1783
1784 void
print()1785 ImageViewer::print()
1786 {
1787 #if 0
1788 Q_ASSERT(imageLabel->pixmap());
1789 QPrintDialog dialog(&printer, this);
1790 if (dialog.exec()) {
1791 QPainter painter(&printer);
1792 QRect rect = painter.viewport();
1793 QSize size = imageLabel->pixmap()->size();
1794 size.scale(rect.size(), Qt::KeepAspectRatio);
1795 painter.setViewport(rect.x(), rect.y(), size.width(), size.height());
1796 painter.setWindow(imageLabel->pixmap()->rect());
1797 painter.drawPixmap(0, 0, *imageLabel->pixmap());
1798 }
1799 #endif
1800 }
1801
1802
1803
1804 void
zoomIn()1805 ImageViewer::zoomIn()
1806 {
1807 IvImage* img = cur();
1808 if (!img)
1809 return;
1810 if (zoom() >= 64)
1811 return;
1812 float oldzoom = zoom();
1813 float newzoom = ceil2f(oldzoom);
1814
1815 float xc, yc; // Center view position
1816 glwin->get_center(xc, yc);
1817 int xm, ym; // Mouse position
1818 glwin->get_focus_image_pixel(xm, ym);
1819 float xoffset = xc - xm;
1820 float yoffset = yc - ym;
1821 float maxzoomratio = std::max(oldzoom / newzoom, newzoom / oldzoom);
1822 int nsteps = (int)OIIO::clamp(20 * (maxzoomratio - 1), 2.0f, 10.0f);
1823 for (int i = 1; i <= nsteps; ++i) {
1824 float a = (float)i / (float)nsteps; // Interpolation amount
1825 float z = OIIO::lerp(oldzoom, newzoom, a);
1826 float zoomratio = z / oldzoom;
1827 view(xm + xoffset / zoomratio, ym + yoffset / zoomratio, z, false);
1828 if (i != nsteps) {
1829 QApplication::processEvents();
1830 Sysutil::usleep(1000000 / 4 / nsteps);
1831 }
1832 }
1833
1834 fitImageToWindowAct->setChecked(false);
1835 }
1836
1837
1838
1839 void
zoomOut()1840 ImageViewer::zoomOut()
1841 {
1842 IvImage* img = cur();
1843 if (!img)
1844 return;
1845 if (zoom() <= 1.0f / 64)
1846 return;
1847 float oldzoom = zoom();
1848 float newzoom = floor2f(oldzoom);
1849
1850 float xcpel, ycpel; // Center view position
1851 glwin->get_center(xcpel, ycpel);
1852 int xmpel, ympel; // Mouse position
1853 glwin->get_focus_image_pixel(xmpel, ympel);
1854 float xoffset = xcpel - xmpel;
1855 float yoffset = ycpel - ympel;
1856 float maxzoomratio = std::max(oldzoom / newzoom, newzoom / oldzoom);
1857 int nsteps = (int)OIIO::clamp(20 * (maxzoomratio - 1), 2.0f, 10.0f);
1858 for (int i = 1; i <= nsteps; ++i) {
1859 float a = (float)i / (float)nsteps; // Interpolation amount
1860 float z = OIIO::lerp(oldzoom, newzoom, a);
1861 float zoomratio = z / oldzoom;
1862 view(xmpel + xoffset / zoomratio, ympel + yoffset / zoomratio, z,
1863 false);
1864 if (i != nsteps) {
1865 QApplication::processEvents();
1866 Sysutil::usleep(1000000 / 4 / nsteps);
1867 }
1868 }
1869
1870 fitImageToWindowAct->setChecked(false);
1871 }
1872
1873
1874 void
normalSize()1875 ImageViewer::normalSize()
1876 {
1877 IvImage* img = cur();
1878 if (!img)
1879 return;
1880 fitImageToWindowAct->setChecked(false);
1881 float xcenter = img->oriented_full_x() + 0.5 * img->oriented_full_width();
1882 float ycenter = img->oriented_full_y() + 0.5 * img->oriented_full_height();
1883 view(xcenter, ycenter, 1.0, true);
1884 fitWindowToImage(false);
1885 }
1886
1887
1888
1889 float
zoom_needed_to_fit(int w,int h)1890 ImageViewer::zoom_needed_to_fit(int w, int h)
1891 {
1892 IvImage* img = cur();
1893 if (!img)
1894 return 1;
1895 float zw = (float)w / img->oriented_width();
1896 float zh = (float)h / img->oriented_height();
1897 return std::min(zw, zh);
1898 }
1899
1900
1901
1902 void
fitImageToWindow()1903 ImageViewer::fitImageToWindow()
1904 {
1905 IvImage* img = cur();
1906 if (!img)
1907 return;
1908 fitImageToWindowAct->setChecked(true);
1909 zoom(zoom_needed_to_fit(glwin->width(), glwin->height()));
1910 }
1911
1912
1913
1914 void
fitWindowToImage(bool zoomok,bool minsize)1915 ImageViewer::fitWindowToImage(bool zoomok, bool minsize)
1916 {
1917 IvImage* img = cur();
1918 // Don't resize when there's no image or the image hasn't been opened yet
1919 // (or we failed to open it).
1920 if (!img || !img->image_valid())
1921 return;
1922 // FIXME -- figure out a way to make it exactly right, even for the
1923 // main window border, etc.
1924 #ifdef __APPLE__
1925 int extraw = 0; //12; // width() - minimumWidth();
1926 int extrah = statusBar()->height()
1927 + 0; //40; // height() - minimumHeight();
1928 #else
1929 int extraw = 4; //12; // width() - minimumWidth();
1930 int extrah = statusBar()->height()
1931 + 4; //40; // height() - minimumHeight();
1932 #endif
1933 // std::cerr << "extra wh = " << extraw << ' ' << extrah << '\n';
1934
1935 float z = zoom();
1936 int w = (int)(img->oriented_full_width() * z) + extraw;
1937 int h = (int)(img->oriented_full_height() * z) + extrah;
1938 if (minsize) {
1939 if (w < m_default_width) {
1940 w = m_default_width;
1941 }
1942 if (h < m_default_height) {
1943 h = m_default_height;
1944 }
1945 }
1946
1947 if (!m_fullscreen) {
1948 QDesktopWidget* desktop = QApplication::desktop();
1949 QRect availgeom = desktop->availableGeometry(this);
1950 int availwidth = availgeom.width() - extraw - 20;
1951 int availheight = availgeom.height() - extrah - menuBar()->height()
1952 - 20;
1953 #if 0
1954 QRect screengeom = desktop->screenGeometry (this);
1955 std::cerr << "available desktop geom " << availgeom.x() << ' ' << availgeom.y() << ' ' << availgeom.width() << "x" << availgeom.height() << "\n";
1956 std::cerr << "screen desktop geom " << screengeom.x() << ' ' << screengeom.y() << ' ' << screengeom.width() << "x" << screengeom.height() << "\n";
1957 #endif
1958 if (w > availwidth || h > availheight) {
1959 w = std::min(w, availwidth);
1960 h = std::min(h, availheight);
1961 if (zoomok) {
1962 z = zoom_needed_to_fit(w, h);
1963 w = (int)(img->oriented_full_width() * z) + extraw;
1964 h = (int)(img->oriented_full_height() * z) + extrah;
1965 // std::cerr << "must rezoom to " << z << " to fit\n";
1966 }
1967 // std::cerr << "New window geom " << w << "x" << h << "\n";
1968 int posx = x(), posy = y();
1969 if (posx + w > availwidth || posy + h > availheight) {
1970 if (posx + w > availwidth)
1971 posx = std::max(0, availwidth - w) + availgeom.x();
1972 if (posy + h > availheight)
1973 posy = std::max(0, availheight - h) + availgeom.y();
1974 // std::cerr << "New position " << posx << ' ' << posy << "\n";
1975 move(QPoint(posx, posy));
1976 }
1977 }
1978 }
1979
1980 float midx = img->oriented_full_x() + 0.5 * img->oriented_full_width();
1981 float midy = img->oriented_full_y() + 0.5 * img->oriented_full_height();
1982 view(midx, midy, z, false, false);
1983 resize(w, h); // Resize will trigger a repaint.
1984
1985 #if 0
1986 QRect g = geometry();
1987 std::cerr << "geom " << g.x() << ' ' << g.y() << ' ' << g.width() << "x" << g.height() << "\n";
1988 g = frameGeometry();
1989 std::cerr << "frame geom " << g.x() << ' ' << g.y() << ' ' << g.width() << "x" << g.height() << "\n";
1990 g = glwin->geometry();
1991 std::cerr << "ogl geom " << g.x() << ' ' << g.y() << ' ' << g.width() << "x" << g.height() << "\n";
1992 std::cerr << "Status bar height = " << statusBar()->height() << "\n";
1993 #endif
1994
1995 #if 0
1996 bool fit = fitWindowToImageAct->isChecked();
1997 if (!fit) {
1998 normalSize();
1999 }
2000 #endif
2001 updateActions();
2002 }
2003
2004
2005
2006 void
fullScreenToggle()2007 ImageViewer::fullScreenToggle()
2008 {
2009 if (m_fullscreen) {
2010 menuBar()->show();
2011 statusBar()->show();
2012 showNormal();
2013 m_fullscreen = false;
2014 slideTimer->stop();
2015 disconnect(slideTimer, 0, 0, 0);
2016 } else {
2017 menuBar()->hide();
2018 statusBar()->hide();
2019 showFullScreen();
2020 m_fullscreen = true;
2021 fitImageToWindow();
2022 }
2023 }
2024
2025
2026
2027 void
about()2028 ImageViewer::about()
2029 {
2030 QMessageBox::about(
2031 this, tr("About iv"),
2032 tr("<p><b>iv</b> is the image viewer for OpenImageIO.</p>"
2033 "<p>(c) Copyright Contributors to the OpenImageIO project.</p>"
2034 "<p>See <a href='http://openimageio.org'>http://openimageio.org</a> for details.</p>"));
2035 }
2036
2037
2038 void
updateActions()2039 ImageViewer::updateActions()
2040 {
2041 // zoomInAct->setEnabled(!fitImageToWindowAct->isChecked());
2042 // zoomOutAct->setEnabled(!fitImageToWindowAct->isChecked());
2043 // normalSizeAct->setEnabled(!fitImageToWindowAct->isChecked());
2044 }
2045
2046
2047
2048 static inline void
calc_subimage_from_zoom(const IvImage * img,int & subimage,float & zoom,float & xcenter,float & ycenter)2049 calc_subimage_from_zoom(const IvImage* img, int& subimage, float& zoom,
2050 float& xcenter, float& ycenter)
2051 {
2052 int rel_subimage = std::trunc(std::log2(1.0f / zoom));
2053 subimage = clamp<int>(img->subimage() + rel_subimage, 0,
2054 img->nsubimages() - 1);
2055 if (!(img->subimage() == 0 && zoom > 1)
2056 && !(img->subimage() == img->nsubimages() - 1 && zoom < 1)) {
2057 float pow_zoom = powf(2.0f, (float)rel_subimage);
2058 zoom *= pow_zoom;
2059 xcenter /= pow_zoom;
2060 ycenter /= pow_zoom;
2061 }
2062 }
2063
2064
2065
2066 void
view(float xcenter,float ycenter,float newzoom,bool smooth,bool redraw)2067 ImageViewer::view(float xcenter, float ycenter, float newzoom, bool smooth,
2068 bool redraw)
2069 {
2070 IvImage* img = cur();
2071 if (!img)
2072 return;
2073
2074 float oldzoom = m_zoom;
2075 float oldxcenter, oldycenter;
2076 glwin->get_center(oldxcenter, oldycenter);
2077 float zoomratio = std::max(oldzoom / newzoom, newzoom / oldzoom);
2078 int nsteps = (int)OIIO::clamp(20 * (zoomratio - 1), 2.0f, 10.0f);
2079 if (!smooth || !redraw)
2080 nsteps = 1;
2081 for (int i = 1; i <= nsteps; ++i) {
2082 float a = (float)i / (float)nsteps; // Interpolation amount
2083 float xc = OIIO::lerp(oldxcenter, xcenter, a);
2084 float yc = OIIO::lerp(oldycenter, ycenter, a);
2085 m_zoom = OIIO::lerp(oldzoom, newzoom, a);
2086
2087 glwin->view(xc, yc, m_zoom, redraw); // Triggers redraw automatically
2088 if (i != nsteps) {
2089 QApplication::processEvents();
2090 Sysutil::usleep(1000000 / 4 / nsteps);
2091 }
2092 }
2093
2094 if (img->auto_subimage()) {
2095 int subimage = 0;
2096 calc_subimage_from_zoom(img, subimage, m_zoom, xcenter, ycenter);
2097 if (subimage != img->subimage()) {
2098 //std::cerr << "Changing to subimage " << subimage;
2099 //std::cerr << " With zoom: " << m_zoom << '\n';
2100 loadCurrentImage(subimage);
2101 glwin->update();
2102 glwin->view(xcenter, ycenter, m_zoom, redraw);
2103 }
2104 }
2105
2106 // zoomInAct->setEnabled (zoom() < 64.0);
2107 // zoomOutAct->setEnabled (zoom() > 1.0/64);
2108
2109 updateStatusBar();
2110 }
2111
2112
2113
2114 void
zoom(float newzoom,bool smooth)2115 ImageViewer::zoom(float newzoom, bool smooth)
2116 {
2117 float xcenter, ycenter;
2118 glwin->get_center(xcenter, ycenter);
2119 view(xcenter, ycenter, newzoom, smooth);
2120 }
2121
2122
2123
2124 void
showInfoWindow()2125 ImageViewer::showInfoWindow()
2126 {
2127 if (!infoWindow) {
2128 infoWindow = new IvInfoWindow(*this, true);
2129 infoWindow->setPalette(m_palette);
2130 }
2131 infoWindow->update(cur());
2132 if (infoWindow->isHidden())
2133 infoWindow->show();
2134 else
2135 infoWindow->hide();
2136 }
2137
2138
2139
2140 void
showPixelviewWindow()2141 ImageViewer::showPixelviewWindow()
2142 {
2143 ((QOpenGLWidget*)(glwin))->update();
2144 }
2145
2146
2147
2148 void
editPreferences()2149 ImageViewer::editPreferences()
2150 {
2151 if (!preferenceWindow) {
2152 preferenceWindow = new IvPreferenceWindow(*this);
2153 preferenceWindow->setPalette(m_palette);
2154 }
2155 preferenceWindow->show();
2156 }
2157