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