1 
2 
3 #include "tapp.h"
4 #include "tpalette.h"
5 #include "toonz/txsheet.h"
6 #include "toonz/toonzscene.h"
7 #include "toonz/levelset.h"
8 #include "toonz/txshsimplelevel.h"
9 #include "toonz/txshlevelcolumn.h"
10 #include "toonz/txshcell.h"
11 //#include "tw/action.h"
12 #include "tropcm.h"
13 #include "ttoonzimage.h"
14 #include "matchline.h"
15 #include "toonz/scenefx.h"
16 #include "toonz/dpiscale.h"
17 #include "toonz/txsheethandle.h"
18 #include "toonz/palettecontroller.h"
19 #include "toonz/tpalettehandle.h"
20 #include "toonz/txshlevelhandle.h"
21 #include "toonz/txshleveltypes.h"
22 #include "toonz/tscenehandle.h"
23 #include "toonz/tframehandle.h"
24 #include "toonzqt/icongenerator.h"
25 #include <map>
26 #include <QRadioButton>
27 #include <QPushButton>
28 #include <QApplication>
29 #include "tundo.h"
30 #include "tools/toolutils.h"
31 #include "timagecache.h"
32 #include "tcolorstyles.h"
33 
34 #include "historytypes.h"
35 
36 using namespace DVGui;
37 
38 namespace {
39 
40 class MatchlinePair {
41 public:
42   const TXshCell *m_cell;
43   TAffine m_imgAff;
44   const TXshCell *m_mcell;
45   TAffine m_matchAff;
46 
MatchlinePair(const TXshCell & cell,const TAffine & imgAff,const TXshCell & mcell,const TAffine & matchAff)47   MatchlinePair(const TXshCell &cell, const TAffine &imgAff,
48                 const TXshCell &mcell, const TAffine &matchAff)
49       : m_cell(&cell)
50       , m_imgAff(imgAff)
51       , m_mcell(&mcell)
52       , m_matchAff(matchAff){};
53 };
54 
55 //-----------------------------------------------------------------------------------
56 
mergeRasterColumns(const std::vector<MatchlinePair> & matchingLevels)57 void mergeRasterColumns(const std::vector<MatchlinePair> &matchingLevels) {
58   if (matchingLevels.empty()) return;
59 
60   int i = 0;
61   for (i = 0; i < (int)matchingLevels.size(); i++) {
62     TRasterImageP img = (TRasterImageP)matchingLevels[i].m_cell->getImage(true);
63     TRasterImageP match =
64         (TRasterImageP)matchingLevels[i].m_mcell->getImage(false);
65     if (!img || !match)
66       throw TRopException("Can merge columns only on raster images!");
67     // img->lock();
68     TRaster32P ras      = img->getRaster();    // img->getCMapped(false);
69     TRaster32P matchRas = match->getRaster();  // match->getCMapped(true);
70     if (!ras || !matchRas) {
71       DVGui::warning(QObject::tr(
72           "The merge command is not available for greytones images."));
73       return;
74     }
75     TAffine aff =
76         matchingLevels[i].m_imgAff.inv() * matchingLevels[i].m_matchAff;
77     int mlx = matchRas->getLx();
78     int mly = matchRas->getLy();
79     int rlx = ras->getLx();
80     int rly = ras->getLy();
81 
82     TRectD in = convert(matchRas->getBounds()) - matchRas->getCenterD();
83 
84     TRectD out = aff * in;
85 
86     TPoint offs((rlx - mlx) / 2 + tround(out.getP00().x - in.getP00().x),
87                 (rly - mly) / 2 + tround(out.getP00().y - in.getP00().y));
88 
89     int lxout = tround(out.getLx()) + 1 + ((offs.x < 0) ? offs.x : 0);
90     int lyout = tround(out.getLy()) + 1 + ((offs.y < 0) ? offs.y : 0);
91 
92     if (lxout <= 0 || lyout <= 0 || offs.x >= rlx || offs.y >= rly) {
93       // tmsg_error("no intersections between matchline and level");
94       continue;
95     }
96 
97     aff = aff.place((double)(in.getLx() / 2.0), (double)(in.getLy() / 2.0),
98                     (out.getLx()) / 2.0 + ((offs.x < 0) ? offs.x : 0),
99                     (out.getLy()) / 2.0 + ((offs.y < 0) ? offs.y : 0));
100 
101     if (offs.x < 0) offs.x = 0;
102     if (offs.y < 0) offs.y = 0;
103 
104     if (lxout + offs.x > rlx || lyout + offs.y > rly) {
105       // PRINTF("TAGLIO L'IMMAGINE\n");
106       lxout = (lxout + offs.x > rlx) ? (rlx - offs.x) : lxout;
107       lyout = (lyout + offs.y > rly) ? (rly - offs.y) : lyout;
108     }
109 
110     if (!aff.isIdentity(1e-4)) {
111       TRaster32P aux(lxout, lyout);
112       TRop::resample(aux, matchRas, aff);
113       matchRas = aux;
114     }
115     ras->lock();
116     matchRas->lock();
117     TRect raux = matchRas->getBounds() + offs;
118     TRasterP r = ras->extract(raux);
119 
120     TRop::over(r, matchRas);
121 
122     ras->unlock();
123     matchRas->unlock();
124 
125     img->setSavebox(img->getSavebox() + (matchRas->getBounds() + offs));
126     // img->setSavebox(rout);
127     // img->unlock();
128   }
129 }
130 
131 /*------------------------------------------------------------------------*/
132 /// verifica se tutta l'immagine e' gia' contenuta in un gruppo.
needTobeGrouped(const TVectorImageP & vimg)133 bool needTobeGrouped(const TVectorImageP &vimg) {
134   if (vimg->getStrokeCount() <= 1) return false;
135 
136   if (!vimg->isStrokeGrouped(0)) return true;
137 
138   for (int i = 1; i < vimg->getStrokeCount(); i++) {
139     if (vimg->areDifferentGroup(0, false, i, false) == 0) return true;
140   }
141 
142   return false;
143 }
144 
145 //---------------------------------------------------------------------------------------
146 
mergeVectorColumns(const std::vector<MatchlinePair> & matchingLevels,bool groupLevels)147 void mergeVectorColumns(const std::vector<MatchlinePair> &matchingLevels,
148                         bool groupLevels) {
149   if (matchingLevels.empty()) return;
150 
151   int i = 0;
152   for (i = 0; i < (int)matchingLevels.size(); i++) {
153     TVectorImageP vimg =
154         (TVectorImageP)matchingLevels[i].m_cell->getImage(true);
155     TVectorImageP vmatch =
156         (TVectorImageP)matchingLevels[i].m_mcell->getImage(false);
157     if (!vimg || !vmatch)
158       throw TRopException("Cannot merge columns of different image types!");
159     // img->lock();
160 
161     if (needTobeGrouped(vimg) && groupLevels)
162       vimg->group(0, vimg->getStrokeCount());
163     bool ungroup = false;
164     if (needTobeGrouped(vmatch) && groupLevels) {
165       ungroup = true;
166       vmatch->group(0, vmatch->getStrokeCount());
167     }
168     TAffine aff =
169         matchingLevels[i].m_imgAff.inv() * matchingLevels[i].m_matchAff;
170     vimg->mergeImage(vmatch, aff);
171     if (ungroup) vmatch->ungroup(0);
172   }
173 }
174 
175 /*------------------------------------------------------------------------*/
176 
177 /*------------------------------------------------------------------------*/
178 }  // namespace
179 //-----------------------------------------------------------------------------
180 
181 class MergeColumnsUndo final : public TUndo {
182   TXshLevelP m_xl;
183   int m_matchlineSessionId;
184   std::map<TFrameId, QString> m_images;
185   TXshSimpleLevel *m_level;
186 
187   int m_column, m_mColumn;
188   TPalette *m_palette;
189   bool m_groupLevels;
190 
191 public:
MergeColumnsUndo(TXshLevelP xl,int matchlineSessionId,int column,TXshSimpleLevel * level,const std::map<TFrameId,QString> & images,int mColumn,TPalette * palette,bool groupLevels)192   MergeColumnsUndo(TXshLevelP xl, int matchlineSessionId, int column,
193                    TXshSimpleLevel *level,
194                    const std::map<TFrameId, QString> &images, int mColumn,
195                    TPalette *palette, bool groupLevels)
196       : TUndo()
197       , m_xl(xl)
198       , m_matchlineSessionId(matchlineSessionId)
199       , m_level(level)
200       , m_column(column)
201       , m_mColumn(mColumn)
202       , m_images(images)
203       , m_palette(palette->clone())
204       , m_groupLevels(groupLevels) {}
205 
undo() const206   void undo() const override {
207     QApplication::setOverrideCursor(Qt::WaitCursor);
208 
209     std::map<TFrameId, QString>::const_iterator it = m_images.begin();
210 
211     std::vector<TFrameId> fids;
212     m_level->setPalette(m_palette->clone());
213     for (; it != m_images.end(); ++it)  //, ++mit)
214     {
215       QString id = "MergeColumnsUndo" + QString::number(m_matchlineSessionId) +
216                    "-" + QString::number(it->first.getNumber());
217       TImageP img = TImageCache::instance()->get(id, false)->cloneImage();
218       m_level->setFrame(it->first, img);
219       fids.push_back(it->first);
220     }
221 
222     if (m_xl) invalidateIcons(m_xl.getPointer(), fids);
223 
224     m_level->setDirtyFlag(true);
225     TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
226     QApplication::restoreOverrideCursor();
227   }
228 
redo() const229   void redo() const override {
230     QApplication::setOverrideCursor(Qt::WaitCursor);
231 
232     mergeColumns(m_column, m_mColumn, true, m_groupLevels);
233 
234     QApplication::restoreOverrideCursor();
235   }
236 
getSize() const237   int getSize() const override { return sizeof(*this); }
238 
~MergeColumnsUndo()239   ~MergeColumnsUndo() {
240     std::map<TFrameId, QString>::const_iterator it = m_images.begin();
241     for (; it != m_images.end(); ++it)  //, ++mit)
242     {
243       QString id = "MergeColumnsUndo" + QString::number(m_matchlineSessionId) +
244                    "-" + QString::number(it->first.getNumber());
245       TImageCache::instance()->remove(id);
246     }
247   }
248 
getHistoryString()249   QString getHistoryString() override {
250     if (m_level->getType() == PLI_XSHLEVEL)
251       return QObject::tr("Merge Vector Levels");
252     else
253       return QObject::tr("Merge Raster Levels");
254   }
getHistoryType()255   int getHistoryType() override { return HistoryType::FilmStrip; }
256 };
257 
258 //-----------------------------------------------------------------------------
259 
mergeColumns(const std::set<int> & columns,bool groupLevels)260 void mergeColumns(const std::set<int> &columns, bool groupLevels) {
261   QApplication::setOverrideCursor(Qt::WaitCursor);
262 
263   std::set<int>::const_iterator it = columns.begin();
264 
265   int dstColumn = *it;
266   ++it;
267 
268   TUndoManager::manager()->beginBlock();
269 
270   for (; it != columns.end(); ++it)
271     mergeColumns(dstColumn, *it, false, groupLevels);
272 
273   TUndoManager::manager()->endBlock();
274 
275   QApplication::restoreOverrideCursor();
276 }
277 
278 //-----------------------------------------------------------------------------
279 
280 static int MergeColumnsSessionId = 0;
281 
mergeColumns(int column,int mColumn,bool isRedo,bool groupLevels)282 void mergeColumns(int column, int mColumn, bool isRedo, bool groupLevels) {
283   if (!isRedo) MergeColumnsSessionId++;
284   TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
285   int start, end;
286   xsh->getCellRange(column, start, end);
287 
288   if (start > end) return;
289   std::vector<TXshCell> cell(end - start + 1);
290   std::vector<TXshCell> mCell(end - start + 1);
291 
292   xsh->getCells(start, column, cell.size(), &(cell[0]));
293 
294   xsh->getCells(start, mColumn, cell.size(), &(mCell[0]));
295 
296   TXshColumn *col  = xsh->getColumn(column);
297   TXshColumn *mcol = xsh->getColumn(mColumn);
298 
299   std::vector<MatchlinePair> matchingLevels;
300 
301   std::set<TFrameId> alreadyDoneSet;
302 
303   TXshSimpleLevel *level = 0, *mLevel = 0;
304   TXshLevelP xl;
305   bool areRasters = false;
306 
307   std::map<TFrameId, QString> images;
308 
309   for (int i = 0; i < (int)cell.size(); i++) {
310     if (cell[i].isEmpty() || mCell[i].isEmpty()) continue;
311     if (!level) {
312       level = cell[i].getSimpleLevel();
313       xl    = cell[i].m_level;
314     }
315 
316     else if (level != cell[i].getSimpleLevel()) {
317       DVGui::warning(
318           QObject::tr("It is not possible to perform a merging involving more "
319                       "than one level per column."));
320       return;
321     }
322 
323     if (!mLevel)
324       mLevel = mCell[i].getSimpleLevel();
325     else if (mLevel != mCell[i].getSimpleLevel()) {
326       DVGui::warning(
327           QObject::tr("It is not possible to perform a merging involving more "
328                       "than one level per column."));
329       return;
330     }
331     TImageP img   = cell[i].getImage(true);
332     TImageP match = mCell[i].getImage(false);
333     TFrameId fid  = cell[i].m_frameId;
334     TFrameId mFid = mCell[i].m_frameId;
335 
336     if (!img || !match) continue;
337 
338     if (alreadyDoneSet.find(fid) == alreadyDoneSet.end()) {
339       TRasterImageP timg   = (TRasterImageP)img;
340       TRasterImageP tmatch = (TRasterImageP)match;
341       TVectorImageP vimg   = (TVectorImageP)img;
342       TVectorImageP vmatch = (TVectorImageP)match;
343 
344       if (timg) {
345         if (!tmatch) {
346           DVGui::warning(QObject::tr(
347               "Only raster levels can be merged to a raster level."));
348           return;
349         }
350         areRasters = true;
351       } else if (vimg) {
352         if (!vmatch) {
353           DVGui::warning(QObject::tr(
354               "Only vector levels can be merged to a vector level."));
355           return;
356         }
357       } else {
358         DVGui::warning(
359             QObject::tr("It is possible to merge only Toonz vector levels or "
360                         "standard raster levels."));
361         return;
362       }
363 
364       if (!isRedo) {
365         QString id = "MergeColumnsUndo" +
366                      QString::number(MergeColumnsSessionId) + "-" +
367                      QString::number(fid.getNumber());
368         TImageCache::instance()->add(
369             id, (timg) ? timg->cloneImage() : vimg->cloneImage());
370         images[fid] = id;
371       }
372       TAffine imgAff, matchAff;
373       getColumnPlacement(imgAff, xsh, start + i, column, false);
374       getColumnPlacement(matchAff, xsh, start + i, mColumn, false);
375       TAffine dpiAff  = getDpiAffine(level, fid);
376       TAffine mdpiAff = getDpiAffine(mLevel, mFid);
377       matchingLevels.push_back(MatchlinePair(cell[i], imgAff * dpiAff, mCell[i],
378                                              matchAff * mdpiAff));
379       alreadyDoneSet.insert(fid);
380     }
381   }
382 
383   if (matchingLevels.empty()) {
384     DVGui::warning(
385         QObject::tr("It is possible to merge only Toonz vector levels or "
386                     "standard raster levels."));
387     return;
388   }
389 
390   if (!isRedo)
391     TUndoManager::manager()->add(
392         new MergeColumnsUndo(xl, MergeColumnsSessionId, column, level, images,
393                              mColumn, level->getPalette(), groupLevels));
394 
395   if (areRasters) {
396     mergeRasterColumns(matchingLevels);
397     for (int i = 0; i < (int)cell.size(); i++)  // the saveboxes must be updated
398     {
399       if (cell[i].isEmpty() || mCell[i].isEmpty()) continue;
400 
401       if (!cell[i].getImage(false) || !mCell[i].getImage(false)) continue;
402 
403       ToolUtils::updateSaveBox(cell[i].getSimpleLevel(), cell[i].m_frameId);
404     }
405   } else
406     mergeVectorColumns(matchingLevels, groupLevels);
407 
408   std::vector<TFrameId> fids(alreadyDoneSet.begin(), alreadyDoneSet.end());
409   invalidateIcons(level, fids);
410   level->setDirtyFlag(true);
411   TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
412 }
413 
414 namespace {
415 
findCell(int column,const TFrameId & fid)416 const TXshCell *findCell(int column, const TFrameId &fid) {
417   TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
418   int i;
419   for (i = 0; i < xsh->getColumn(column)->getMaxFrame(); i++)
420     if (xsh->getCell(i, column).getFrameId() == fid)
421       return &(xsh->getCell(i, column));
422   return 0;
423 }
424 
contains(const std::vector<TFrameId> & v,const TFrameId & val)425 bool contains(const std::vector<TFrameId> &v, const TFrameId &val) {
426   int i;
427   for (i = 0; i < (int)v.size(); i++)
428     if (v[i] == val) return true;
429   return false;
430 }
431 
432 //-----------------------------------------------------------------------------
433 
indexes2string(const std::set<TFrameId> fids)434 QString indexes2string(const std::set<TFrameId> fids) {
435   if (fids.empty()) return "";
436 
437   QString str;
438 
439   std::set<TFrameId>::const_iterator it = fids.begin();
440 
441   str = QString::number(it->getNumber());
442 
443   while (it != fids.end()) {
444     std::set<TFrameId>::const_iterator it1 = it;
445     it1++;
446 
447     int lastVal = it->getNumber();
448     while (it1 != fids.end() && it1->getNumber() == lastVal + 1) {
449       lastVal = it1->getNumber();
450       it1++;
451     }
452 
453     if (lastVal != it->getNumber()) str += "-" + QString::number(lastVal);
454     if (it1 == fids.end()) return str;
455 
456     str += ", " + QString::number(it1->getNumber());
457 
458     it = it1;
459   }
460 
461   return str;
462 }
463 
464 }  // namespace
465