1 
2 
3 #include "adjustlevelspopup.h"
4 
5 // Tnz6 includes
6 #include "tapp.h"
7 #include "cellselection.h"
8 #include "filmstripselection.h"
9 #include "menubarcommandids.h"
10 
11 // TnzQt includes
12 #include "toonzqt/histogram.h"
13 #include "toonzqt/marksbar.h"
14 #include "toonzqt/menubarcommand.h"
15 #include "toonzqt/tselectionhandle.h"
16 #include "toonzqt/intfield.h"
17 #include "toonzqt/icongenerator.h"
18 
19 // TnzLib includes
20 #include "toonz/txshcell.h"
21 #include "toonz/txshsimplelevel.h"
22 #include "toonz/tframehandle.h"
23 #include "toonz/txshlevelhandle.h"
24 #include "toonz/tcolumnhandle.h"
25 #include "toonz/txsheethandle.h"
26 
27 // TnzCore includes
28 #include "trasterimage.h"
29 #include "trop.h"
30 #include "tundo.h"
31 #include "timagecache.h"
32 
33 // Qt includes
34 #include <QPushButton>
35 #include <QSplitter>
36 #include <QScrollArea>
37 #include <QMainWindow>
38 
39 //**************************************************************************
40 //    Local namespace stuff
41 //**************************************************************************
42 
43 namespace {
44 
resetMarksBar(MarksBar * marksBar)45 void resetMarksBar(MarksBar *marksBar) {
46   QVector<int> &values = marksBar->values();
47   values[0]            = 0;
48   values[1]            = 255;
49 }
50 
51 //--------------------------------------------------------------
52 
53 // Get the exact range for specified value
getRange(const QVector<int> & values,int & min,int & max)54 void getRange(const QVector<int> &values, int &min, int &max) {
55   int size = values.size();
56 
57   for (min = 0; min < size; ++min) {
58     if (values[min]) break;
59   }
60 
61   for (max = size - 1; max >= 0; --max) {
62     if (values[max]) break;
63   }
64 }
65 
66 //--------------------------------------------------------------
67 
68 // Get the threshold-permissive range for values.
getRange(const QVector<int> & values,int threshold,int & min,int & max)69 void getRange(const QVector<int> &values, int threshold, int &min, int &max) {
70   int val, size = values.size(), count;
71 
72   count = values[0];  // Always Consent 1 value cut
73   for (min = 1; min < size; ++min) {
74     val = values[min];
75     count += val;
76     if (val && count > threshold)  // Then, stop before a positive value when
77                                    // thresh is exceeded
78       break;
79   }
80 
81   count = values[size - 1];
82   for (max = size - 2; max >= 0; --max) {
83     val = values[max];
84     count += val;
85     if (val && count > threshold) break;
86   }
87 }
88 
89 }  // namespace
90 
91 //**************************************************************************
92 //    Adjust Levels Swatch
93 //**************************************************************************
94 
95 class AdjustLevelsPopup::Swatch final : public PlaneViewer {
96   TRasterP m_ras;
97 
98 public:
Swatch(QWidget * parent=0)99   Swatch(QWidget *parent = 0) : PlaneViewer(parent) {
100     setBgColor(TPixel32::White, TPixel32::White);
101   }
102 
raster() const103   TRasterP raster() const { return m_ras; }
raster()104   TRasterP &raster() { return m_ras; }
105 
paintGL()106   void paintGL() override {
107     drawBackground();
108 
109     if (m_ras) {
110       glEnable(GL_BLEND);
111       glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
112 
113       // Note GL_ONE instead of GL_SRC_ALPHA: it's needed since the input
114       // image is supposedly premultiplied - and it works because the
115       // viewer's background is opaque.
116       // See tpixelutils.h's overPixT function for comparison.
117 
118       pushGLWorldCoordinates();
119       draw(m_ras);
120       popGLCoordinates();
121 
122       glDisable(GL_BLEND);
123     }
124   }
125 };
126 
127 //**************************************************************************
128 //    EditableMarksBar implementation
129 //**************************************************************************
130 
EditableMarksBar(QWidget * parent)131 EditableMarksBar::EditableMarksBar(QWidget *parent) : QFrame(parent) {
132   QVBoxLayout *layout = new QVBoxLayout;
133   layout->setMargin(0);
134   layout->setSpacing(2);
135   setLayout(layout);
136 
137   // Add MarksBar
138   m_marksBar = new MarksBar;
139   m_marksBar->setContentsMargins(5, 0, 6, 0);
140   layout->addWidget(m_marksBar);
141 
142   // Customize it
143   m_marksBar->setRange(0, 256, 2);
144 
145   QVector<int> &values = m_marksBar->values();
146   values.push_back(0);
147   values.push_back(256);
148 
149   QVector<QColor> &colors = m_marksBar->colors();
150   colors.fill(Qt::black, 2);
151 
152   // MarksBar dominates values change notifications
153   bool ret =
154       connect(m_marksBar, SIGNAL(marksUpdated()), this, SLOT(updateFields()));
155   ret = ret && connect(m_marksBar, SIGNAL(marksUpdated()), this,
156                        SIGNAL(paramsChanged()));
157 
158   // Add fields layout
159   QHBoxLayout *hLayout = new QHBoxLayout;
160   hLayout->setMargin(0);
161   hLayout->setContentsMargins(4, 0, 5, 0);
162   layout->addLayout(hLayout);
163 
164   m_fields[0] = new DVGui::IntLineEdit;
165 
166   hLayout->addWidget(m_fields[0]);
167   ret = ret && connect(m_fields[0], SIGNAL(editingFinished()), this,
168                        SLOT(onFieldEdited()));
169 
170   hLayout->addStretch(1);
171 
172   m_fields[1] = new DVGui::IntLineEdit;
173   hLayout->addWidget(m_fields[1]);
174   ret = ret && connect(m_fields[1], SIGNAL(editingFinished()), this,
175                        SLOT(onFieldEdited()));
176 
177   updateFields();
178 }
179 
180 //--------------------------------------------------------------
181 
~EditableMarksBar()182 EditableMarksBar::~EditableMarksBar() {}
183 
184 //--------------------------------------------------------------
185 
getValues(int * values) const186 void EditableMarksBar::getValues(int *values) const {
187   const QVector<int> &marks = m_marksBar->values();
188 
189   values[0] = marks[0];
190   values[1] = marks[1] - 1;
191 }
192 
193 //--------------------------------------------------------------
194 
updateFields()195 void EditableMarksBar::updateFields() {
196   const QVector<int> &values = m_marksBar->values();
197 
198   m_fields[0]->setValue(values[0]);
199   m_fields[1]->setValue(values[1] - 1);
200 
201   // No emission - as it's the marksbar that dominate signal emission
202 }
203 
204 //--------------------------------------------------------------
205 
onFieldEdited()206 void EditableMarksBar::onFieldEdited() {
207   QVector<int> &values = m_marksBar->values();
208 
209   // Copy the values to the marksBar
210   values[0] = m_fields[0]->getValue();
211   values[1] = m_fields[1]->getValue() + 1;
212 
213   m_marksBar->conformValues();
214 
215   emit paramsChanged();
216 }
217 
218 //**************************************************************************
219 //    Adjust Levels Popup implementation
220 //**************************************************************************
221 
AdjustLevelsPopup()222 AdjustLevelsPopup::AdjustLevelsPopup()
223     : DVGui::Dialog(TApp::instance()->getMainWindow(), true, false,
224                     "AdjustLevels")
225     , m_thresholdD(0.005)  // 0.5% of the image size
226 {
227   int i, j;
228 
229   setWindowTitle(tr("Adjust Levels"));
230   setLabelWidth(0);
231   setModal(false);
232 
233   setTopMargin(0);
234   setTopSpacing(0);
235 
236   beginVLayout();
237 
238   QSplitter *splitter = new QSplitter(Qt::Vertical);
239   splitter->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding,
240                                       QSizePolicy::MinimumExpanding));
241   addWidget(splitter);
242 
243   endVLayout();
244 
245   //------------------------- Top Layout --------------------------
246 
247   QScrollArea *scrollArea = new QScrollArea(splitter);
248   scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
249   scrollArea->setWidgetResizable(true);
250   scrollArea->setMinimumWidth(450);
251   splitter->addWidget(scrollArea);
252   splitter->setStretchFactor(0, 1);
253 
254   QFrame *topWidget = new QFrame(scrollArea);
255   scrollArea->setWidget(topWidget);
256 
257   QVBoxLayout *topVLayout =
258       new QVBoxLayout(topWidget);  // Needed to justify at top
259   topWidget->setLayout(topVLayout);
260 
261   QHBoxLayout *topLayout = new QHBoxLayout(topWidget);
262   topVLayout->addLayout(topLayout);
263   topVLayout->addStretch(1);
264 
265   //------------------------- Histogram ---------------------------
266 
267   m_histogram = new Histogram(topWidget);
268   topLayout->addWidget(m_histogram);
269 
270   //------------------------- Mark Bars ---------------------------
271 
272   QVBoxLayout *histogramViewLayout;
273 
274   for (i = 0; i < 5; ++i) {
275     HistogramView *view = m_histogram->getHistograms()->getHistogramView(i);
276     histogramViewLayout = static_cast<QVBoxLayout *>(view->layout());
277 
278     // Don't draw channel numbers
279     view->channelBar()->setDrawNumbers(false);
280 
281     for (j = 0; j < 2; ++j) {
282       EditableMarksBar *editableMarksBar = m_marksBar[j + (i << 1)] =
283           new EditableMarksBar;
284       MarksBar *marksBar = editableMarksBar->marksBar();
285 
286       // Set margins up to cover the histogram
287       editableMarksBar->layout()->setContentsMargins(6, 0, 5, 0);
288       connect(editableMarksBar, SIGNAL(paramsChanged()), this,
289               SLOT(onParamsChanged()));
290 
291       histogramViewLayout->insertWidget(1 + (j << 1), editableMarksBar);
292     }
293   }
294 
295   //------------------------- View Widget -------------------------
296 
297   // NOTE: It's IMPORTANT that parent widget is supplied. It's somewhat
298   // used by QSplitter to decide the initial widget sizes...
299 
300   m_viewer = new Swatch(splitter);
301   m_viewer->setMinimumHeight(150);
302   m_viewer->setFocusPolicy(Qt::WheelFocus);
303   splitter->addWidget(m_viewer);
304 
305   //--------------------------- Buttons ---------------------------
306 
307   QVBoxLayout *buttonsLayout = new QVBoxLayout(topWidget);
308   topLayout->addLayout(buttonsLayout);
309 
310   buttonsLayout->addSpacing(50);
311 
312   QPushButton *clampRange = new QPushButton(tr("Clamp"), topWidget);
313   clampRange->setMinimumSize(65, 25);
314   buttonsLayout->addWidget(clampRange);
315   connect(clampRange, SIGNAL(clicked(bool)), this, SLOT(clampRange()));
316 
317   QPushButton *autoAdjust = new QPushButton(tr("Auto"), topWidget);
318   autoAdjust->setMinimumSize(65, 25);
319   buttonsLayout->addWidget(autoAdjust);
320   connect(autoAdjust, SIGNAL(clicked(bool)), this, SLOT(autoAdjust()));
321 
322   QPushButton *resetBtn = new QPushButton(tr("Reset"), topWidget);
323   resetBtn->setMinimumSize(65, 25);
324   buttonsLayout->addWidget(resetBtn);
325   connect(resetBtn, SIGNAL(clicked(bool)), this, SLOT(reset()));
326 
327   buttonsLayout->addStretch(1);
328 
329   m_okBtn = new QPushButton(tr("Apply"));
330   addButtonBarWidget(m_okBtn);
331 
332   connect(m_okBtn, SIGNAL(clicked()), this, SLOT(apply()));
333 
334   // Finally, acquire current selection
335   acquireRaster();
336 
337   m_viewer->resize(0, 350);
338   resize(600, 700);
339 }
340 
341 //--------------------------------------------------------------
342 
showEvent(QShowEvent * se)343 void AdjustLevelsPopup::showEvent(QShowEvent *se) {
344   TSelectionHandle *selectionHandle = TApp::instance()->getCurrentSelection();
345   connect(selectionHandle, SIGNAL(selectionChanged(TSelection *)), this,
346           SLOT(onSelectionChanged()));
347 
348   acquireRaster();
349 }
350 
351 //--------------------------------------------------------------
352 
hideEvent(QHideEvent * he)353 void AdjustLevelsPopup::hideEvent(QHideEvent *he) {
354   Dialog::hideEvent(he);
355 
356   TSelectionHandle *selectionHandle = TApp::instance()->getCurrentSelection();
357   disconnect(selectionHandle, SIGNAL(selectionChanged(TSelection *)), this,
358              SLOT(onSelectionChanged()));
359 
360   m_inputRas         = TRasterP();
361   m_viewer->raster() = TRasterP();
362 }
363 
364 //--------------------------------------------------------------
365 
acquireRaster()366 void AdjustLevelsPopup::acquireRaster() {
367   // Retrieve current selection
368   TApp *app                     = TApp::instance();
369   TSelection *selection         = app->getCurrentSelection()->getSelection();
370   TCellSelection *cellSelection = dynamic_cast<TCellSelection *>(selection);
371   TFilmstripSelection *filmstripSelection =
372       dynamic_cast<TFilmstripSelection *>(selection);
373 
374   // Retrieve the input raster
375   m_inputRas = TRasterP();
376   if (cellSelection) {
377     TXsheet *xsh  = app->getCurrentXsheet()->getXsheet();
378     TXshCell cell = xsh->getCell(app->getCurrentFrame()->getFrameIndex(),
379                                  app->getCurrentColumn()->getColumnIndex());
380     TRasterImageP rasImage                            = cell.getImage(true);
381     if (rasImage && rasImage->getRaster()) m_inputRas = rasImage->getRaster();
382   } else if (filmstripSelection) {
383     TXshSimpleLevel *simpleLevel = app->getCurrentLevel()->getSimpleLevel();
384     if (simpleLevel) {
385       TRasterImageP rasImage = (TRasterImageP)simpleLevel->getFrame(
386           app->getCurrentFrame()->getFid(), true);
387       if (rasImage && rasImage->getRaster()) m_inputRas = rasImage->getRaster();
388     }
389   }
390 
391   if (m_inputRas) {
392     m_threshold = m_inputRas->getLx() * m_inputRas->getLy() * m_thresholdD;
393     m_okBtn->setEnabled(true);
394   } else {
395     m_inputRas = TRasterP();
396     m_okBtn->setEnabled(false);
397   }
398 
399   // Build histograms
400   m_histogram->setRaster(m_inputRas);
401 
402   // Update the corresponding processed image in the viewer
403   updateProcessedImage();
404 }
405 
406 //--------------------------------------------------------------
407 
setThreshold(double t)408 void AdjustLevelsPopup::setThreshold(double t) {
409   m_thresholdD = t;
410   if (m_inputRas)
411     m_threshold = m_inputRas->getLx() * m_inputRas->getLy() * m_thresholdD;
412 }
413 
414 //--------------------------------------------------------------
415 
getParameters(int * in0,int * in1,int * out0,int * out1)416 void AdjustLevelsPopup::getParameters(int *in0, int *in1, int *out0,
417                                       int *out1) {
418   int p[2];
419 
420   int i, j;
421   for (i = j = 0; i < 10; i += 2, ++j) {
422     m_marksBar[i]->getValues(p);
423     in0[j] = p[0], in1[j] = p[1];
424 
425     m_marksBar[i + 1]->getValues(p);
426     out0[j] = p[0], out1[j] = p[1];
427   }
428 }
429 
430 //--------------------------------------------------------------
431 
updateProcessedImage()432 void AdjustLevelsPopup::updateProcessedImage() {
433   if (!m_inputRas) {
434     m_viewer->raster() = TRasterP();
435     m_viewer->update();
436     return;
437   }
438 
439   // Allocate a conformant output, if necessary
440   TRasterP &outRas = m_viewer->raster();
441   if (!outRas || outRas->getPixelSize() != m_inputRas->getPixelSize() ||
442       outRas->getSize() != m_inputRas->getSize())
443     outRas = m_inputRas->create(m_inputRas->getLx(), m_inputRas->getLy());
444 
445   // Perform the operation preview
446   int in0[5], in1[5], out0[5], out1[5];
447   getParameters(in0, in1, out0, out1);
448 
449   TRop::rgbmAdjust(outRas, m_inputRas, in0, in1, out0, out1);
450 
451   // Update the swatch
452   m_viewer->update();
453 }
454 
455 //--------------------------------------------------------------
456 
onSelectionChanged()457 void AdjustLevelsPopup::onSelectionChanged() { acquireRaster(); }
458 
459 //--------------------------------------------------------------
460 
461 // Params were changed. Content must be updated.
onParamsChanged()462 void AdjustLevelsPopup::onParamsChanged() { updateProcessedImage(); }
463 
464 //--------------------------------------------------------------
465 
466 // Reset ALL channels
reset()467 void AdjustLevelsPopup::reset() {
468   int i;
469   for (i = 0; i < 10; ++i) {
470     EditableMarksBar *editableMarksBar = m_marksBar[i];
471 
472     QVector<int> &marks = editableMarksBar->marksBar()->values();
473     marks[0] = 0, marks[1] = 256;
474 
475     editableMarksBar->updateFields();
476   }
477 
478   onParamsChanged();
479   update();
480 }
481 
482 //--------------------------------------------------------------
483 
clampRange()484 void AdjustLevelsPopup::clampRange() {
485   if (!m_inputRas) return;
486 
487   Histograms *histograms = m_histogram->getHistograms();
488   int channelIdx         = histograms->currentIndex();
489   int inputBarIdx        = (channelIdx << 1);
490 
491   EditableMarksBar *editableMarksBar = m_marksBar[inputBarIdx];
492 
493   int min, max;
494 
495   // Clamp histogram
496   const QVector<int> &values =
497       histograms->getHistogramView(channelIdx)->values();
498   ::getRange(values, min, max);
499 
500   QVector<int> &marks = editableMarksBar->marksBar()->values();
501   if (min < max)
502     marks[0] = min, marks[1] = max + 1;
503   else
504     marks[0] = 0, marks[1] = 256;
505 
506   editableMarksBar->updateFields();
507   onParamsChanged();
508   update();
509 }
510 
511 //--------------------------------------------------------------
512 
autoAdjust()513 void AdjustLevelsPopup::autoAdjust() {
514   if (!m_inputRas) return;
515 
516   Histograms *histograms = m_histogram->getHistograms();
517   int channelIdx         = histograms->currentIndex();
518   int inputBarIdx        = (channelIdx << 1);
519 
520   EditableMarksBar *editableMarksBar = m_marksBar[inputBarIdx];
521 
522   int min, max;
523 
524   // Clamp histogram
525   const QVector<int> &values =
526       histograms->getHistogramView(channelIdx)->values();
527   if (channelIdx == 0) {
528     int minR, maxR, minG, maxG, minB, maxB;
529 
530     ::getRange(histograms->getHistogramView(1)->values(), m_threshold, minR,
531                maxR);
532     ::getRange(histograms->getHistogramView(2)->values(), m_threshold, minG,
533                maxG);
534     ::getRange(histograms->getHistogramView(3)->values(), m_threshold, minB,
535                maxB);
536 
537     min = std::min({minR, minG, minB});
538     max = std::max({maxR, maxG, maxB});
539   } else
540     ::getRange(values, m_threshold, min, max);
541 
542   QVector<int> &marks = editableMarksBar->marksBar()->values();
543   if (min < max)
544     marks[0] = min, marks[1] = max + 1;
545   else
546     marks[0] = 0, marks[1] = 256;
547 
548   editableMarksBar->updateFields();
549   onParamsChanged();
550   update();
551 }
552 
553 //**************************************************************************
554 //    TGBMScale Undo
555 //**************************************************************************
556 
557 class AdjustLevelsUndo final : public TUndo {
558   int m_in0[5], m_in1[5], m_out0[5], m_out1[5];
559   int m_r, m_c;
560 
561   QString m_rasId;
562   int m_rasSize;
563 
564 public:
565   AdjustLevelsUndo(int *in0, int *in1, int *out0, int *out1, int r, int c,
566                    TRasterP ras);
567   ~AdjustLevelsUndo();
568 
569   void undo() const override;
570   void redo() const override;
571 
getSize() const572   int getSize() const override { return sizeof(*this) + m_rasSize; }
573 };
574 
575 //--------------------------------------------------------------
576 
AdjustLevelsUndo(int * in0,int * in1,int * out0,int * out1,int r,int c,TRasterP ras)577 AdjustLevelsUndo::AdjustLevelsUndo(int *in0, int *in1, int *out0, int *out1,
578                                    int r, int c, TRasterP ras)
579     : m_r(r)
580     , m_c(c)
581     , m_rasSize(ras->getLx() * ras->getLy() * ras->getPixelSize()) {
582   memcpy(m_in0, in0, sizeof(m_in0));
583   memcpy(m_in1, in1, sizeof(m_in1));
584   memcpy(m_out0, out0, sizeof(m_out0));
585   memcpy(m_out1, out1, sizeof(m_out1));
586 
587   static int counter = 0;
588   m_rasId            = QString("AdjustLevelsUndo") + QString::number(++counter);
589 
590   TImageCache::instance()->add(m_rasId, TRasterImageP(ras));
591 }
592 
593 //--------------------------------------------------------------
594 
~AdjustLevelsUndo()595 AdjustLevelsUndo::~AdjustLevelsUndo() {
596   TImageCache::instance()->remove(m_rasId);
597 }
598 
599 //--------------------------------------------------------------
600 
undo() const601 void AdjustLevelsUndo::undo() const {
602   TXsheet *xsheet = TApp::instance()->getCurrentXsheet()->getXsheet();
603   TXshCell cell   = xsheet->getCell(m_r, m_c);
604 
605   TRasterImageP rasImage = (TRasterImageP)cell.getImage(true);
606   if (!rasImage) return;  //...Should never happen, though...
607 
608   rasImage->setRaster(
609       ((TRasterImageP)TImageCache::instance()->get(m_rasId, true))
610           ->getRaster()
611           ->clone());
612 
613   TXshSimpleLevel *simpleLevel = cell.getSimpleLevel();
614   assert(simpleLevel);
615   simpleLevel->touchFrame(cell.getFrameId());
616   simpleLevel->setDirtyFlag(true);
617   IconGenerator::instance()->invalidate(simpleLevel, cell.getFrameId());
618 
619   if (m_isLastInBlock)
620     TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
621 }
622 
623 //--------------------------------------------------------------
624 
redo() const625 void AdjustLevelsUndo::redo() const {
626   TXsheet *xsheet = TApp::instance()->getCurrentXsheet()->getXsheet();
627   TXshCell cell   = xsheet->getCell(m_r, m_c);
628 
629   TRasterImageP rasImage = (TRasterImageP)cell.getImage(true);
630   if (!rasImage) return;
631 
632   TRasterP ras = rasImage->getRaster();
633   if (!ras) return;
634 
635   TRop::rgbmAdjust(ras, ras, m_in0, m_in1, m_out0, m_out1);
636 
637   TXshSimpleLevel *simpleLevel = cell.getSimpleLevel();
638   assert(simpleLevel);
639   simpleLevel->touchFrame(cell.getFrameId());
640   simpleLevel->setDirtyFlag(true);
641   IconGenerator::instance()->invalidate(simpleLevel, cell.getFrameId());
642 
643   if (m_isLastInBlock)
644     TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
645 }
646 
647 //**************************************************************************
648 //    Apply stuff
649 //**************************************************************************
650 
apply()651 void AdjustLevelsPopup::apply() {
652   // Retrieve parameters
653   int in0[5], in1[5], out0[5], out1[5];
654   getParameters(in0, in1, out0, out1);
655 
656   // Operate depending on the selection kind
657   TCellSelection *cellSelection =
658       dynamic_cast<TCellSelection *>(TSelection::getCurrent());
659   if (cellSelection) {
660     std::set<TRasterImage *>
661         images;  // Multiple cells may yield the same image...
662 
663     int r0, c0, r1, c1;
664     cellSelection->getSelectedCells(r0, c0, r1, c1);
665     TXsheet *xsheet      = TApp::instance()->getCurrentXsheet()->getXsheet();
666     bool oneImageChanged = false;
667 
668     TUndoManager::manager()->beginBlock();
669     {
670       int c, r;
671       for (c = c0; c <= c1; c++) {
672         for (r = r0; r <= r1; r++) {
673           const TXshCell &cell = xsheet->getCell(r, c);
674 
675           TRasterImageP rasImage = (TRasterImageP)cell.getImage(true);
676           if (!rasImage) continue;
677 
678           if (images.find(rasImage.getPointer()) != images.end()) continue;
679 
680           TRasterP ras = rasImage->getRaster();
681           if (!ras) continue;
682 
683           images.insert(rasImage.getPointer());
684           oneImageChanged = true;
685 
686           TUndoManager::manager()->add(
687               new AdjustLevelsUndo(in0, in1, out0, out1, r, c, ras->clone()));
688           TRop::rgbmAdjust(ras, ras, in0, in1, out0, out1);
689 
690           TXshSimpleLevel *simpleLevel = cell.getSimpleLevel();
691           assert(simpleLevel);
692           simpleLevel->touchFrame(cell.getFrameId());
693           simpleLevel->setDirtyFlag(true);
694 
695           IconGenerator::instance()->invalidate(simpleLevel, cell.getFrameId());
696         }
697       }
698     }
699     TUndoManager::manager()->endBlock();
700 
701     if (oneImageChanged) {
702       close();
703       return;
704     }
705   }
706 
707   TFilmstripSelection *filmstripSelection =
708       dynamic_cast<TFilmstripSelection *>(TSelection::getCurrent());
709   if (filmstripSelection) {
710     TXshSimpleLevel *simpleLevel =
711         TApp::instance()->getCurrentLevel()->getSimpleLevel();
712     if (simpleLevel) {
713       std::set<TFrameId> fids = filmstripSelection->getSelectedFids();
714       bool oneImageChanged    = false;
715 
716       for (auto const &fid : fids) {
717         TRasterImageP rasImage =
718             (TRasterImageP)simpleLevel->getFrame(fid, true);
719         if (!rasImage) continue;
720 
721         TRasterP ras = rasImage->getRaster();
722         if (!ras) continue;
723 
724         oneImageChanged = true;
725         TRop::rgbmAdjust(ras, ras, in0, in1, out0, out1);
726 
727         simpleLevel->touchFrame(fid);
728         simpleLevel->setDirtyFlag(true);
729 
730         IconGenerator::instance()->invalidate(simpleLevel, fid);
731       }
732 
733       if (oneImageChanged) {
734         close();
735         return;
736       }
737     }
738   }
739 
740   DVGui::error(QObject::tr("The current selection is invalid."));
741   return;
742 }
743 
744 //**************************************************************************
745 //    Open Popup Command Handler instantiation
746 //**************************************************************************
747 
748 OpenPopupCommandHandler<AdjustLevelsPopup> openAdjustLevelsPopup(
749     MI_AdjustLevels);
750