1 #include "toonz/txshsimplelevel.h"
2 #include "imagebuilders.h"
3 
4 // TnzLib includes
5 #include "toonz/txshleveltypes.h"
6 #include "toonz/imagemanager.h"
7 #include "toonz/studiopalette.h"
8 #include "toonz/hook.h"
9 #include "toonz/toonzscene.h"
10 #include "toonz/levelproperties.h"
11 #include "toonz/levelupdater.h"
12 #include "toonz/fullcolorpalette.h"
13 #include "toonz/preferences.h"
14 #include "toonz/stage.h"
15 #include "toonz/textureutils.h"
16 #include "toonz/levelset.h"
17 #include "toonz/tcamera.h"
18 
19 // TnzBase includes
20 #include "tenv.h"
21 
22 // TnzCore includes
23 #include "trasterimage.h"
24 #include "tvectorimage.h"
25 #include "tmeshimage.h"
26 #include "timagecache.h"
27 #include "tofflinegl.h"
28 #include "tvectorgl.h"
29 #include "tvectorrenderdata.h"
30 #include "tropcm.h"
31 #include "tpixelutils.h"
32 #include "timageinfo.h"
33 #include "tlogger.h"
34 #include "tstream.h"
35 #include "tsystem.h"
36 #include "tcontenthistory.h"
37 
38 // Qt includes
39 #include <QDir>
40 #include <QRegExp>
41 #include <QMessageBox>
42 #include <QtCore>
43 
44 #include "../common/psdlib/psd.h"
45 
46 //******************************************************************************************
47 //    Global stuff
48 //******************************************************************************************
49 
50 DEFINE_CLASS_CODE(TXshSimpleLevel, 20)
51 PERSIST_IDENTIFIER(TXshSimpleLevel, "level")
52 
53 //******************************************************************************************
54 //    Local namespace stuff
55 //******************************************************************************************
56 
57 namespace {
58 
59 int idBaseCode = 1;
60 
61 //-----------------------------------------------------------------------------
62 
63 struct CompatibilityStruct {
64   int writeMask, neededMask, forbiddenMask;
65 };
66 
67 CompatibilityStruct compatibility = {
68     0x00F1,  // mask written.   Note: Student main must be 0x00F2
69     //                 Note: the 0x00F0 part is currently not used.
70     0x0000,  // mandatory mask: loaded levels MUST have a mask with these bits
71              // set
72     //                 Note: if mandatory mask != 0 then no old level (without
73     //                 mask)
74     //                       can be loaded
75     //                 Note: this mask is currently not used.
76     0x000E  // forbidden mask: loaded levels MUST NOT have a mask with these
77             // bits set
78             //
79 };
80 
81 //-----------------------------------------------------------------------------
82 
rasterized(std::string id)83 inline std::string rasterized(std::string id) { return id + "_rasterized"; }
filled(std::string id)84 inline std::string filled(std::string id) { return id + "_filled"; }
85 
86 //-----------------------------------------------------------------------------
87 
getCreatorString()88 QString getCreatorString() {
89   QString creator = QString::fromStdString(TEnv::getApplicationName()) + " " +
90                     QString::fromStdString(TEnv::getApplicationVersion()) +
91                     " CM(" + QString::number(compatibility.writeMask, 16) + ")";
92   return creator;
93 }
94 
95 //-----------------------------------------------------------------------------
96 
checkCreatorString(const QString & creator)97 bool checkCreatorString(const QString &creator) {
98   int mask = 0;
99   if (creator != "") {
100     QRegExp rx("CM\\([0-9A-Fa-f]*\\)");
101     int pos = rx.indexIn(creator);
102     int len = rx.matchedLength();
103     if (pos >= 0 && len >= 4) {
104       QString v;
105       if (len > 4) v = creator.mid(pos + 3, len - 4);
106       bool ok = true;
107       mask    = v.toInt(&ok, 16);
108     }
109   }
110   return (mask & compatibility.neededMask) == compatibility.neededMask &&
111          (mask & compatibility.forbiddenMask) == 0;
112 }
113 
114 //-----------------------------------------------------------------------------
115 
isAreadOnlyLevel(const TFilePath & path)116 bool isAreadOnlyLevel(const TFilePath &path) {
117   if (path.isEmpty() || !path.isAbsolute()) return false;
118   if (path.getDots() == "." ||
119       (path.getDots() == ".." &&
120        (path.getType() == "tlv" || path.getType() == "tpl"))) {
121     if (path.getType() == "psd" || path.getType() == "gif" ||
122         path.getType() == "mp4" || path.getType() == "webm")
123       return true;
124     if (!TSystem::doesExistFileOrLevel(path)) return false;
125     TFileStatus fs(path);
126     return !fs.isWritable();
127   }
128   /*- 処理が重くなるので、連番ファイルは全てfalseを返す -*/
129   /*
130 else if(path.getDots() == "..")
131 {
132 TFilePath dir = path.getParentDir();
133 QDir qDir(QString::fromStdWString(dir.getWideString()));
134 QString levelName =
135 QRegExp::escape(QString::fromStdWString(path.getWideName()));
136 QString levelType = QString::fromStdString(path.getType());
137 QString exp(levelName+".[0-9]{1,4}."+levelType);
138 QRegExp regExp(exp);
139 QStringList list = qDir.entryList(QDir::Files);
140 QStringList livelFrames = list.filter(regExp);
141 
142 bool isReadOnly=false;
143 int i;
144 for(i=0; i<livelFrames.size() && !isReadOnly; i++)
145 {
146 TFilePath frame = dir+TFilePath(livelFrames[i].toStdWString());
147 if(frame.isEmpty() || !frame.isAbsolute()) continue;
148 TFileStatus fs(frame);
149 isReadOnly = !fs.isWritable();
150 }
151 return isReadOnly;
152 }
153 */
154   else
155     return false;
156 }
157 
158 //-----------------------------------------------------------------------------
159 
getIndexesRangefromFids(TXshSimpleLevel * level,const std::set<TFrameId> & fids,int & fromIndex,int & toIndex)160 void getIndexesRangefromFids(TXshSimpleLevel *level,
161                              const std::set<TFrameId> &fids, int &fromIndex,
162                              int &toIndex) {
163   if (fids.empty()) {
164     fromIndex = toIndex = -1;
165     return;
166   }
167 
168   toIndex   = 0;
169   fromIndex = level->getFrameCount() - 1;
170 
171   std::set<TFrameId>::const_iterator it;
172   for (it = fids.begin(); it != fids.end(); ++it) {
173     int index = level->guessIndex(*it);
174     if (index > toIndex) toIndex = index;
175     if (index < fromIndex) fromIndex = index;
176   }
177 }
178 
179 }  // namespace
180 
181 //******************************************************************************************
182 //    TXshSimpleLevel  implementation
183 //******************************************************************************************
184 
185 bool TXshSimpleLevel::m_rasterizePli        = false;
186 bool TXshSimpleLevel::m_fillFullColorRaster = false;
187 
188 //-----------------------------------------------------------------------------
189 
TXshSimpleLevel(const std::wstring & name)190 TXshSimpleLevel::TXshSimpleLevel(const std::wstring &name)
191     : TXshLevel(m_classCode, name)
192     , m_properties(new LevelProperties)
193     , m_palette(0)
194     , m_idBase(std::to_string(idBaseCode++))
195     , m_editableRangeUserInfo(L"")
196     , m_isSubsequence(false)
197     , m_16BitChannelLevel(false)
198     , m_isReadOnly(false)
199     , m_temporaryHookMerged(false) {}
200 
201 //-----------------------------------------------------------------------------
202 
~TXshSimpleLevel()203 TXshSimpleLevel::~TXshSimpleLevel() {
204   clearFrames();
205 
206   if (m_palette) m_palette->release();
207 }
208 
209 //-----------------------------------------------------------------------------
210 
setEditableRange(unsigned int from,unsigned int to,const std::wstring & userName)211 void TXshSimpleLevel::setEditableRange(unsigned int from, unsigned int to,
212                                        const std::wstring &userName) {
213   assert(from <= to && to < (unsigned int)getFrameCount());
214   unsigned int i;
215   for (i = from; i <= to; i++) m_editableRange.insert(index2fid(i));
216 
217   QString hostName        = TSystem::getHostName();
218   m_editableRangeUserInfo = userName + L"_" + hostName.toStdWString();
219 
220   std::wstring fileName = getEditableFileName();
221   TFilePath dstPath     = getScene()->decodeFilePath(m_path);
222   dstPath = dstPath.withName(fileName).withType(dstPath.getType());
223 
224   // Load temporary level file (for pli and tlv types only)
225   if (getType() != OVL_XSHLEVEL && TSystem::doesExistFileOrLevel(dstPath)) {
226     TLevelReaderP lr(dstPath);
227     TLevelP level = lr->loadInfo();
228     setPalette(level->getPalette());
229     for (TLevel::Iterator it = level->begin(); it != level->end(); it++) {
230       TImageP img = lr->getFrameReader(it->first)->load();
231       setFrame(it->first, img);
232     }
233   }
234 
235   // Merge temporary hook file with current hookset
236   const TFilePath &hookFile = getHookPath(dstPath);
237   mergeTemporaryHookFile(from, to, hookFile);
238 }
239 
240 //-----------------------------------------------------------------------------
241 
mergeTemporaryHookFile(unsigned int from,unsigned int to,const TFilePath & hookFile)242 void TXshSimpleLevel::mergeTemporaryHookFile(unsigned int from, unsigned int to,
243                                              const TFilePath &hookFile) {
244   if (!TFileStatus(hookFile).doesExist()) return;
245 
246   HookSet *tempHookSet = new HookSet;
247   TIStream is(hookFile);
248   std::string tagName;
249   try {
250     if (is.matchTag(tagName) && tagName == "hooks") tempHookSet->loadData(is);
251   } catch (...) {
252   }
253 
254   HookSet *hookSet  = getHookSet();
255   int tempHookCount = tempHookSet->getHookCount();
256 
257   if (tempHookCount == 0) {
258     for (unsigned int f = from; f <= to; f++) {
259       TFrameId fid = index2fid(f);
260       hookSet->eraseFrame(fid);
261     }
262   } else {
263     for (int i = 0; i < tempHookCount; i++) {
264       Hook *hook    = tempHookSet->getHook(i);
265       Hook *newHook = hookSet->touchHook(hook->getId());
266       newHook->setTrackerObjectId(hook->getTrackerObjectId());
267       newHook->setTrackerRegionHeight(hook->getTrackerRegionHeight());
268       newHook->setTrackerRegionWidth(hook->getTrackerRegionWidth());
269       for (unsigned int f = from; f <= to; f++) {
270         TFrameId fid = index2fid(f);
271         newHook->setAPos(fid, hook->getAPos(fid));
272         newHook->setBPos(fid, hook->getBPos(fid));
273       }
274     }
275   }
276 
277   m_temporaryHookMerged = true;
278 }
279 
280 //-----------------------------------------------------------------------------
281 
clearEditableRange()282 void TXshSimpleLevel::clearEditableRange() {
283   m_editableRange.clear();
284   m_editableRangeUserInfo = L"";
285 }
286 
287 //-----------------------------------------------------------------------------
288 
getEditableFileName()289 std::wstring TXshSimpleLevel::getEditableFileName() {
290 #ifdef MACOSX
291   std::wstring fileName = L"." + m_path.getWideName();
292 #else
293   std::wstring fileName = m_path.getWideName();
294 #endif
295   fileName += L"_" + m_editableRangeUserInfo;
296   int from, to;
297   getIndexesRangefromFids(this, m_editableRange, from, to);
298   if (from == -1 && to == -1) return L"";
299   fileName += L"_" + std::to_wstring(from + 1) + L"-" + std::to_wstring(to + 1);
300   return fileName;
301 }
302 
303 //-----------------------------------------------------------------------------
304 
getEditableRange()305 std::set<TFrameId> TXshSimpleLevel::getEditableRange() {
306   return m_editableRange;
307 }
308 
309 //-----------------------------------------------------------------------------
310 
setRenumberTable()311 void TXshSimpleLevel::setRenumberTable() {
312   m_renumberTable.clear();
313 
314   FramesSet::iterator ft, fEnd = m_frames.end();
315   for (ft = m_frames.begin(); ft != fEnd; ++ft) m_renumberTable[*ft] = *ft;
316 }
317 
318 //-----------------------------------------------------------------------------
319 
setDirtyFlag(bool on)320 void TXshSimpleLevel::setDirtyFlag(bool on) { m_properties->setDirtyFlag(on); }
321 
322 //-----------------------------------------------------------------------------
323 
getDirtyFlag() const324 bool TXshSimpleLevel::getDirtyFlag() const {
325   return m_properties->getDirtyFlag();
326 }
327 
328 //-----------------------------------------------------------------------------
329 
touchFrame(const TFrameId & fid)330 void TXshSimpleLevel::touchFrame(const TFrameId &fid) {
331   m_properties->setDirtyFlag(true);
332   TContentHistory *ch = getContentHistory();
333   if (!ch) {
334     ch = new TContentHistory(true);
335     setContentHistory(ch);
336   }
337   ch->frameModifiedNow(fid);
338 
339   if (getType() == PLI_XSHLEVEL) {
340     std::string id = rasterized(getImageId(fid));
341     ImageManager::instance()->invalidate(id);
342   }
343   if (getType() & FULLCOLOR_TYPE) {
344     std::string id = filled(getImageId(fid));
345     ImageManager::instance()->invalidate(id);
346   }
347 }
348 
349 //-----------------------------------------------------------------------------
350 
onPaletteChanged()351 void TXshSimpleLevel::onPaletteChanged() {
352   FramesSet::iterator ft, fEnd = m_frames.end();
353   for (ft = m_frames.begin(); ft != fEnd; ++ft) {
354     const TFrameId &fid = *ft;
355 
356     if (getType() == PLI_XSHLEVEL) {
357       std::string id = rasterized(getImageId(fid));
358       ImageManager::instance()->invalidate(id);
359     }
360     if (getType() & FULLCOLOR_TYPE) {
361       std::string id = filled(getImageId(fid));
362       ImageManager::instance()->invalidate(id);
363     }
364 
365     texture_utils::invalidateTexture(this, fid);
366   }
367 }
368 
369 //-----------------------------------------------------------------------------
370 
setScannedPath(const TFilePath & fp)371 void TXshSimpleLevel::setScannedPath(const TFilePath &fp) {
372   m_scannedPath = fp;
373 }
374 
375 //-----------------------------------------------------------------------------
376 
setPath(const TFilePath & fp,bool keepFrames)377 void TXshSimpleLevel::setPath(const TFilePath &fp, bool keepFrames) {
378   m_path = fp;
379   if (!keepFrames) {
380     clearFrames();
381     assert(getScene());
382     try {
383       load();
384     } catch (...) {
385     }
386   }
387 
388   if (getType() != PLI_XSHLEVEL) {
389     if (!m_frames.empty()) {
390       std::string imageId = getImageId(getFirstFid());
391       const TImageInfo *imageInfo =
392           ImageManager::instance()->getInfo(imageId, ImageManager::none, 0);
393       if (imageInfo) {
394         TDimension imageRes(0, 0);
395         TPointD imageDpi;
396         imageRes.lx = imageInfo->m_lx;
397         imageRes.ly = imageInfo->m_ly;
398         imageDpi.x  = imageInfo->m_dpix;
399         imageDpi.y  = imageInfo->m_dpiy;
400         m_properties->setImageDpi(imageDpi);
401         m_properties->setImageRes(imageRes);
402         m_properties->setBpp(imageInfo->m_bitsPerSample *
403                              imageInfo->m_samplePerPixel);
404       }
405     }
406   }
407 }
408 
409 //-----------------------------------------------------------------------------
410 
clonePropertiesFrom(const TXshSimpleLevel * oldSl)411 void TXshSimpleLevel::clonePropertiesFrom(const TXshSimpleLevel *oldSl) {
412   m_properties->setImageDpi(
413       oldSl->m_properties
414           ->getImageDpi());  // Watch out - may change dpi policy!
415   m_properties->setDpi(oldSl->m_properties->getDpi());
416   m_properties->setDpiPolicy(oldSl->m_properties->getDpiPolicy());
417   m_properties->setImageRes(oldSl->m_properties->getImageRes());
418   m_properties->setBpp(oldSl->m_properties->getBpp());
419   m_properties->setSubsampling(oldSl->m_properties->getSubsampling());
420 }
421 
422 //-----------------------------------------------------------------------------
423 
getPalette() const424 TPalette *TXshSimpleLevel::getPalette() const { return m_palette; }
425 
426 //-----------------------------------------------------------------------------
427 
setPalette(TPalette * palette)428 void TXshSimpleLevel::setPalette(TPalette *palette) {
429   if (m_palette != palette) {
430     if (m_palette) m_palette->release();
431 
432     m_palette = palette;
433     if (m_palette) {
434       m_palette->addRef();
435       if (!(getType() & FULLCOLOR_TYPE)) m_palette->setPaletteName(getName());
436     }
437   }
438 }
439 
440 //-----------------------------------------------------------------------------
441 
getFids(std::vector<TFrameId> & fids) const442 void TXshSimpleLevel::getFids(std::vector<TFrameId> &fids) const {
443   fids.assign(m_frames.begin(), m_frames.end());
444 }
445 
446 //-----------------------------------------------------------------------------
447 
getFids() const448 std::vector<TFrameId> TXshSimpleLevel::getFids() const {
449   return std::vector<TFrameId>(m_frames.begin(), m_frames.end());
450 }
451 
452 //-----------------------------------------------------------------------------
453 
isFid(const TFrameId & fid) const454 bool TXshSimpleLevel::isFid(const TFrameId &fid) const {
455   return m_frames.count(fid);
456 }
457 
458 //-----------------------------------------------------------------------------
459 
getFrameId(int index) const460 const TFrameId &TXshSimpleLevel::getFrameId(int index) const {
461   return *(m_frames.begin() += index);
462 }
463 
464 //-----------------------------------------------------------------------------
465 
getFirstFid() const466 TFrameId TXshSimpleLevel::getFirstFid() const {
467   return !isEmpty() ? *m_frames.begin() : TFrameId(TFrameId::NO_FRAME);
468 }
469 
470 //-----------------------------------------------------------------------------
471 
getLastFid() const472 TFrameId TXshSimpleLevel::getLastFid() const {
473   return !isEmpty() ? *m_frames.rbegin() : TFrameId(TFrameId::NO_FRAME);
474 }
475 
476 //-----------------------------------------------------------------------------
477 
guessStep() const478 int TXshSimpleLevel::guessStep() const {
479   int frameCount = m_frames.size();
480   if (frameCount < 2)
481     return 1;  // un livello con zero o un frame ha per def. step=1
482 
483   FramesSet::const_iterator ft = m_frames.begin();
484 
485   TFrameId firstFid = *ft++, secondFid = *ft++;
486 
487   if (firstFid.getLetter() != 0 || secondFid.getLetter() != 0) return 1;
488 
489   int step = secondFid.getNumber() - firstFid.getNumber();
490   if (step == 1) return 1;
491 
492   // controllo subito se lo step vale per l'ultimo frame
493   // (cerco di limitare il numero di volte in cui devo controllare tutta la
494   // lista)
495   TFrameId lastFid = *m_frames.rbegin();
496   if (lastFid.getLetter() != 0) return 1;
497 
498   if (lastFid.getNumber() != firstFid.getNumber() + step * (frameCount - 1))
499     return 1;
500 
501   for (int i = 2; ft != m_frames.end(); ++ft, ++i) {
502     const TFrameId &fid = *ft;
503 
504     if (fid.getLetter() != 0) return 1;
505 
506     if (fid.getNumber() != firstFid.getNumber() + step * i) return 1;
507   }
508 
509   return step;
510 }
511 
512 //-----------------------------------------------------------------------------
513 
fid2index(const TFrameId & fid) const514 int TXshSimpleLevel::fid2index(const TFrameId &fid) const {
515   FramesSet::const_iterator ft = m_frames.find(fid);
516   return (ft != m_frames.end()) ? std::distance(m_frames.begin(), ft)
517                                 :  // Note: flat_set has random access
518              -1;                   // iterators, so this is FAST
519 }
520 
521 //-----------------------------------------------------------------------------
522 
guessIndex(const TFrameId & fid) const523 int TXshSimpleLevel::guessIndex(const TFrameId &fid) const {
524   if (m_frames.empty()) return 0;  // no frames, return 0 (by definition)
525 
526   FramesSet::const_iterator ft = m_frames.lower_bound(fid);
527   if (ft == m_frames.end()) {
528     const TFrameId &maxFid = *m_frames.rbegin();
529     assert(fid > maxFid);
530 
531     // fid not in the table, but greater than the last one.
532     // return a suitable index. (e.g. frames are 1,3,5,7; fid2index(11) should
533     // return index=5)
534     int step = guessStep();
535     int i    = (fid.getNumber() - maxFid.getNumber()) / step;
536     return m_frames.size() - 1 + i;
537   } else
538     return std::distance(m_frames.begin(), ft);
539 }
540 
541 //-----------------------------------------------------------------------------
542 
index2fid(int index) const543 TFrameId TXshSimpleLevel::index2fid(int index) const {
544   if (index < 0) return TFrameId(-2);
545 
546   int frameCount = m_frames.size();
547   if (frameCount == 0) return TFrameId(1);  // o_o?
548 
549   if (index < frameCount) {
550     FramesSet::const_iterator ft = m_frames.begin();
551     std::advance(ft, index);
552     return *ft;
553   } else {
554     int step        = guessStep();
555     TFrameId maxFid = *m_frames.rbegin();
556     int d           = step * (index - frameCount + 1);
557     return TFrameId(maxFid.getNumber() + d);
558   }
559 }
560 
561 //-----------------------------------------------------------------------------
562 
getFrame(const TFrameId & fid,UCHAR imFlags,int subsampling) const563 TImageP TXshSimpleLevel::getFrame(const TFrameId &fid, UCHAR imFlags,
564                                   int subsampling) const {
565   assert(m_type != UNKNOWN_XSHLEVEL);
566 
567   // If the required frame is not in range, quit
568   if (m_frames.count(fid) == 0) return TImageP();
569 
570   const std::string &imgId = getImageId(fid);
571 
572   ImageLoader::BuildExtData extData(this, fid, subsampling);
573   TImageP img = ImageManager::instance()->getImage(imgId, imFlags, &extData);
574 
575   if (imFlags & ImageManager::toBeModified) {
576     // The image will be modified. Perform any related invalidation.
577     texture_utils::invalidateTexture(
578         this, fid);  // We must rebuild associated textures
579   }
580 
581   return img;
582 }
583 
584 //-----------------------------------------------------------------------------
585 
getFrameInfo(const TFrameId & fid,bool toBeModified)586 TImageInfo *TXshSimpleLevel::getFrameInfo(const TFrameId &fid,
587                                           bool toBeModified) {
588   assert(m_type != UNKNOWN_XSHLEVEL);
589 
590   // If the required frame is not in range, quit
591   if (m_frames.count(fid) == 0) return 0;
592 
593   const std::string &imgId = getImageId(fid);
594 
595   TImageInfo *info = ImageManager::instance()->getInfo(
596       imgId, toBeModified ? ImageManager::toBeModified : ImageManager::none, 0);
597 
598   return info;
599 }
600 
601 //-----------------------------------------------------------------------------
602 
getFrameIcon(const TFrameId & fid) const603 TImageP TXshSimpleLevel::getFrameIcon(const TFrameId &fid) const {
604   assert(m_type != UNKNOWN_XSHLEVEL);
605 
606   if (m_frames.count(fid) == 0) return TImageP();
607 
608   // NOTE: Icons caching is DISABLED at this stage. It is now responsibility of
609   // ToonzQt's IconGenerator class.
610 
611   ImageLoader::BuildExtData extData(this, fid);
612   extData.m_subs = 1, extData.m_icon = true;
613 
614   const std::string &imgId = getImageId(fid);
615   TImageP img              = ImageManager::instance()->getImage(
616       imgId, ImageManager::dontPutInCache, &extData);
617 
618   TToonzImageP timg = (TToonzImageP)img;
619   if (timg && m_palette) timg->setPalette(m_palette);
620 
621   return img;
622 }
623 
624 //-----------------------------------------------------------------------------
625 // load icon (and image) data of all frames into cache
loadAllIconsAndPutInCache(bool cacheImagesAsWell)626 void TXshSimpleLevel::loadAllIconsAndPutInCache(bool cacheImagesAsWell) {
627   if (m_type != TZP_XSHLEVEL) return;
628 
629   std::vector<TFrameId> fids;
630   getFids(fids);
631 
632   std::vector<std::string> iconIds;
633 
634   for (int i = 0; i < (int)fids.size(); i++) {
635     iconIds.push_back(getIconId(fids[i]));
636   }
637 
638   ImageManager::instance()->loadAllTlvIconsAndPutInCache(this, fids, iconIds,
639                                                          cacheImagesAsWell);
640 }
641 
642 //-----------------------------------------------------------------------------
643 
getFrameToCleanup(const TFrameId & fid) const644 TRasterImageP TXshSimpleLevel::getFrameToCleanup(const TFrameId &fid) const {
645   assert(m_type != UNKNOWN_XSHLEVEL);
646 
647   FramesSet::const_iterator ft = m_frames.find(fid);
648   if (ft == m_frames.end()) return TImageP();
649 
650   bool flag           = (m_scannedPath != TFilePath());
651   std::string imageId = getImageId(fid, flag ? Scanned : 0);
652 
653   ImageLoader::BuildExtData extData(this, fid, 1);
654   TRasterImageP img = ImageManager::instance()->getImage(
655       imageId, ImageManager::dontPutInCache, &extData);
656   if (!img) return img;
657 
658   double x_dpi, y_dpi;
659   img->getDpi(x_dpi, y_dpi);
660   if (!x_dpi && !y_dpi) {
661     TPointD dpi = m_properties->getDpi();
662     img->setDpi(dpi.x, dpi.y);
663   }
664 
665   return img;
666 }
667 
668 //-----------------------------------------------------------------------------
669 
getFullsampledFrame(const TFrameId & fid,UCHAR imFlags) const670 TImageP TXshSimpleLevel::getFullsampledFrame(const TFrameId &fid,
671                                              UCHAR imFlags) const {
672   assert(m_type != UNKNOWN_XSHLEVEL);
673 
674   FramesSet::const_iterator it = m_frames.find(fid);
675   if (it == m_frames.end()) return TRasterImageP();
676 
677   std::string imageId = getImageId(fid);
678 
679   ImageLoader::BuildExtData extData(this, fid, 1);
680   TImageP img = ImageManager::instance()->getImage(imageId, imFlags, &extData);
681 
682   if (imFlags & ImageManager::toBeModified) {
683     // The image will be modified. Perform any related invalidation.
684     texture_utils::invalidateTexture(
685         this, fid);  // We must rebuild associated textures
686   }
687 
688   return img;
689 }
690 
691 //-----------------------------------------------------------------------------
692 
getIconId(const TFrameId & fid,int frameStatus) const693 std::string TXshSimpleLevel::getIconId(const TFrameId &fid,
694                                        int frameStatus) const {
695   return "icon:" + getImageId(fid, frameStatus);
696 }
697 
698 //-----------------------------------------------------------------------------
699 
getIconId(const TFrameId & fid,const TDimension & size) const700 std::string TXshSimpleLevel::getIconId(const TFrameId &fid,
701                                        const TDimension &size) const {
702   return getImageId(fid) + ":" + std::to_string(size.lx) + "x" +
703          std::to_string(size.ly);
704 }
705 
706 //-----------------------------------------------------------------------------
707 
708 namespace {
709 
getAffine(const TDimension & srcSize,const TDimension & dstSize)710 TAffine getAffine(const TDimension &srcSize, const TDimension &dstSize) {
711   double scx = 1 * dstSize.lx / (double)srcSize.lx;
712   double scy = 1 * dstSize.ly / (double)srcSize.ly;
713   double sc  = std::min(scx, scy);
714   double dx  = (dstSize.lx - srcSize.lx * sc) * 0.5;
715   double dy  = (dstSize.ly - srcSize.ly * sc) * 0.5;
716   return TScale(sc) *
717          TTranslation(0.5 * TPointD(srcSize.lx, srcSize.ly) + TPointD(dx, dy));
718 }
719 
720 //-----------------------------------------------------------------------------
721 
722 /*!Costruisce l'icona di dimesione \b size dell'immagine \b img.*/
buildIcon(const TImageP & img,const TDimension & size)723 TImageP buildIcon(const TImageP &img, const TDimension &size) {
724   TRaster32P raster(size);
725   if (TVectorImageP vi = img) {
726     TOfflineGL *glContext = new TOfflineGL(size);
727     // TDimension cameraSize(768, 576);
728     TDimension cameraSize(1920, 1080);
729     TPalette *vPalette = img->getPalette();
730     assert(vPalette);
731     const TVectorRenderData rd(getAffine(cameraSize, size), TRect(), vPalette,
732                                0, false);
733     glContext->clear(TPixel32::White);
734     glContext->draw(vi, rd);
735     raster->copy(glContext->getRaster());
736     delete glContext;
737   } else if (TToonzImageP ti = img) {
738     raster->fill(TPixel32(255, 255, 255, 255));
739     TRasterCM32P rasCM32 = ti->getRaster();
740     TRect bbox;
741     bbox = ti->getSavebox();
742     if (!bbox.isEmpty()) {
743       rasCM32   = rasCM32->extractT(bbox);
744       double sx = raster->getLx() / (double)rasCM32->getLx();
745       double sy = raster->getLy() / (double)rasCM32->getLy();
746       double sc = std::min(sx, sy);
747       TAffine aff =
748           TScale(sc).place(rasCM32->getCenterD(), raster->getCenterD());
749       TRop::resample(raster, rasCM32, ti->getPalette(), aff);
750       raster->lock();
751       for (int y = 0; y < raster->getLy(); y++) {
752         TPixel32 *pix    = raster->pixels(y);
753         TPixel32 *endPix = pix + raster->getLx();
754         while (pix < endPix) {
755           *pix = overPix(TPixel32::White, *pix);
756           pix++;
757         }
758       }
759       raster->unlock();
760     }
761   } else {
762     TRasterImageP ri = img;
763     if (ri) {
764       ri->makeIcon(raster);
765       TRop::addBackground(raster, TPixel32::White);
766     } else
767       raster->fill(TPixel32(127, 50, 20));
768   }
769 
770   return TRasterImageP(raster);
771 }
772 
773 }  // anonymous namespace
774 
775 //-----------------------------------------------------------------------------
776 
setFrame(const TFrameId & fid,const TImageP & img)777 void TXshSimpleLevel::setFrame(const TFrameId &fid, const TImageP &img) {
778   assert(m_type != UNKNOWN_XSHLEVEL);
779 
780   if (img) img->setPalette(getPalette());
781 
782   m_frames.insert(fid);
783 
784   TFilePath path = m_path;
785 
786   int frameStatus                        = getFrameStatus(fid);
787   static const int SCANNED_OR_CLEANUPPED = (Scanned | Cleanupped);
788 
789   if ((frameStatus & SCANNED_OR_CLEANUPPED) == Scanned) path = m_scannedPath;
790 
791   // Deal with the ImageManger: ensure the identifiers are bound, and the
792   // associated image is either modified to img or (if !img) invalidated.
793   const std::string &imageId = getImageId(fid);
794 
795   if (!ImageManager::instance()->isBound(imageId)) {
796     const TFilePath &decodedPath = getScene()->decodeFilePath(path);
797     ImageManager::instance()->bind(imageId, new ImageLoader(decodedPath, fid));
798   }
799 
800   ImageManager::instance()->setImage(imageId, img);  // Invalidates if !img
801 
802   if (frameStatus == Normal) {
803     // Only a normal frame can have these. Justified since:
804     //  PLIs have nothing to share with cleanup stuff
805 
806     if (m_type == PLI_XSHLEVEL) {
807       const std::string &imageId2 = rasterized(imageId);
808       if (!ImageManager::instance()->isBound(imageId2))
809         ImageManager::instance()->bind(imageId2, new ImageRasterizer);
810       else
811         ImageManager::instance()->invalidate(imageId2);
812     }
813 
814     if (m_type == OVL_XSHLEVEL || m_type == TZI_XSHLEVEL) {
815       const std::string &imageId2 = filled(imageId);
816       if (!ImageManager::instance()->isBound(imageId2))
817         ImageManager::instance()->bind(imageId2, new ImageFiller);
818       else
819         ImageManager::instance()->invalidate(imageId2);
820     }
821   }
822 }
823 
824 //-----------------------------------------------------------------------------
825 
eraseFrame(const TFrameId & fid)826 void TXshSimpleLevel::eraseFrame(const TFrameId &fid) {
827   FramesSet::iterator ft = m_frames.find(fid);
828   if (ft == m_frames.end()) return;
829 
830   // Erase the corresponding entry in the renumber table
831   std::map<TFrameId, TFrameId>::iterator rt, rEnd(m_renumberTable.end());
832   for (rt = m_renumberTable.begin(); rt != rEnd; ++rt) {
833     if (rt->second == fid) {
834       m_renumberTable.erase(rt->first);
835       break;
836     }
837   }
838 
839   m_frames.erase(ft);
840   getHookSet()->eraseFrame(fid);
841 
842   ImageManager *im = ImageManager::instance();
843   TImageCache *ic  = TImageCache::instance();
844   {
845     im->unbind(getImageId(fid, Normal));
846     im->unbind(getImageId(fid, Scanned));
847     im->unbind(getImageId(fid, CleanupPreview));
848     // remove icon cache as well
849     ic->remove(getIconId(fid, Normal));
850     ic->remove(getIconId(fid, Scanned));
851     ic->remove(getIconId(fid, CleanupPreview));
852 
853     if (m_type == PLI_XSHLEVEL) im->unbind(rasterized(getImageId(fid)));
854 
855     if (m_type == OVL_XSHLEVEL || m_type == TZI_XSHLEVEL)
856       im->unbind(filled(getImageId(fid)));
857 
858     texture_utils::invalidateTexture(this, fid);
859   }
860 }
861 
862 //-----------------------------------------------------------------------------
863 
clearFrames()864 void TXshSimpleLevel::clearFrames() {
865   ImageManager *im = ImageManager::instance();
866   TImageCache *ic  = TImageCache::instance();
867   // Unbind frames
868   FramesSet::iterator ft, fEnd = m_frames.end();
869   for (ft = m_frames.begin(); ft != fEnd; ++ft) {
870     im->unbind(getImageId(*ft, Scanned));
871     im->unbind(getImageId(*ft, Cleanupped));
872     im->unbind(getImageId(*ft, CleanupPreview));
873     // remove icon cache as well
874     ic->remove(getIconId(*ft, Normal));
875     ic->remove(getIconId(*ft, Scanned));
876     ic->remove(getIconId(*ft, CleanupPreview));
877 
878     if (m_type == PLI_XSHLEVEL) im->unbind(rasterized(getImageId(*ft)));
879 
880     if (m_type == OVL_XSHLEVEL || m_type == TZI_XSHLEVEL)
881       im->unbind(filled(getImageId(*ft)));
882 
883     texture_utils::invalidateTexture(this, *ft);
884   }
885 
886   // Clear level
887   m_frames.clear();
888   m_editableRange.clear();
889   m_editableRangeUserInfo.clear();
890   m_renumberTable.clear();
891   m_framesStatus.clear();
892 }
893 
894 //-----------------------------------------------------------------------------
895 
loadData(TIStream & is)896 void TXshSimpleLevel::loadData(TIStream &is) {
897   std::string tagName;
898   bool flag = false;
899 
900   int type = UNKNOWN_XSHLEVEL;
901 
902   for (;;) {
903     if (is.matchTag(tagName)) {
904       if (tagName == "path") {
905         is >> m_path;
906         is.matchEndTag();
907       } else if (tagName == "scannedPath") {
908         is >> m_scannedPath;
909         is.matchEndTag();
910       } else if (tagName == "info") {
911         std::string v;
912         double xdpi = 0, ydpi = 0;
913         int subsampling                      = 1;
914         int doPremultiply                    = 0;
915         int whiteTransp                      = 0;
916         int antialiasSoftness                = 0;
917         int isStopMotionLevel                = 0;
918         LevelProperties::DpiPolicy dpiPolicy = LevelProperties::DP_ImageDpi;
919         if (is.getTagParam("dpix", v)) xdpi = std::stod(v);
920         if (is.getTagParam("dpiy", v)) ydpi = std::stod(v);
921         if (xdpi != 0 && ydpi != 0) dpiPolicy = LevelProperties::DP_CustomDpi;
922         std::string dpiType = is.getTagAttribute("dpiType");
923         if (dpiType == "image") dpiPolicy = LevelProperties::DP_ImageDpi;
924         if (is.getTagParam("type", v) && v == "s") type = TZI_XSHLEVEL;
925         if (is.getTagParam("subsampling", v)) subsampling = std::stoi(v);
926         if (is.getTagParam("premultiply", v)) doPremultiply = std::stoi(v);
927         if (is.getTagParam("antialias", v)) antialiasSoftness = std::stoi(v);
928         if (is.getTagParam("whiteTransp", v)) whiteTransp = std::stoi(v);
929         if (is.getTagParam("isStopMotionLevel", v))
930           isStopMotionLevel = std::stoi(v);
931 
932         m_properties->setDpiPolicy(dpiPolicy);
933         m_properties->setDpi(TPointD(xdpi, ydpi));
934         m_properties->setSubsampling(subsampling);
935         m_properties->setDoPremultiply(doPremultiply);
936         m_properties->setDoAntialias(antialiasSoftness);
937         m_properties->setWhiteTransp(whiteTransp);
938         m_properties->setIsStopMotion(isStopMotionLevel);
939         if (isStopMotionLevel == 1) setIsReadOnly(true);
940       } else
941         throw TException("unexpected tag " + tagName);
942     } else {
943       if (flag) break;  // ci puo' essere un solo nome
944       flag = true;
945       std::wstring token;
946       is >> token;
947       if (token == L"__empty") {
948         // empty = true;
949         is >> token;
950       }
951 
952       if (token == L"_raster")  // obsoleto (Tab2.2)
953       {
954         double xdpi = 1, ydpi = 1;
955         is >> xdpi >> ydpi >> m_name;
956         setName(m_name);
957         type = OVL_XSHLEVEL;
958         m_properties->setDpi(TPointD(xdpi, ydpi));
959         setType(type);
960         setPath(
961             TFilePath("+drawings/") + (getName() + L"." + ::to_wstring("bmp")),
962             true);
963       } else if (token == L"__raster")  // obsoleto (Tab2.2)
964       {
965         double xdpi = 1, ydpi = 1;
966         std::string extension;
967         is >> xdpi >> ydpi >> m_name >> extension;
968         setName(m_name);
969         type = OVL_XSHLEVEL;
970         m_properties->setDpi(TPointD(xdpi, ydpi));
971         setType(type);
972         setPath(TFilePath("+drawings/") +
973                     (getName() + L"." + ::to_wstring(extension)),
974                 true);
975       } else {
976         m_name = token;
977         setName(m_name);
978       }
979     }
980   }
981   if (type == UNKNOWN_XSHLEVEL) {
982     std::string ext = m_path.getType();
983     if (ext == "pli" || ext == "svg")
984       type = PLI_XSHLEVEL;
985     else if (ext == "tlv" || ext == "tzu" || ext == "tzp" || ext == "tzl")
986       type = TZP_XSHLEVEL;
987     else if (ext == "tzi")
988       type = TZI_XSHLEVEL;
989     else if (ext == "mesh")
990       type = MESH_XSHLEVEL;
991     else
992       type = OVL_XSHLEVEL;
993   }
994   setType(type);
995 }
996 
997 //-----------------------------------------------------------------------------
998 
999 namespace {
1000 class LoadingLevelRange {
1001 public:
1002   TFrameId m_fromFid, m_toFid;
LoadingLevelRange()1003   LoadingLevelRange() : m_fromFid(1), m_toFid(0) {}
1004 
match(const TFrameId & fid) const1005   bool match(const TFrameId &fid) const {
1006     /*-- ↓SubSequent範囲内にある条件		↓全部ロードする場合 --*/
1007     return ((m_fromFid <= fid && fid <= m_toFid) || m_fromFid > m_toFid);
1008   }
isEnabled() const1009   bool isEnabled() const { return m_fromFid <= m_toFid; }
reset()1010   void reset() {
1011     m_fromFid = TFrameId(1);
1012     m_toFid   = TFrameId(0);
1013   }
1014 
1015 } loadingLevelRange;
1016 
1017 //-----------------------------------------------------------------------------
1018 }  // namespace
1019 //-----------------------------------------------------------------------------
1020 
setLoadingLevelRange(const TFrameId & fromFid,const TFrameId & toFid)1021 void setLoadingLevelRange(const TFrameId &fromFid, const TFrameId &toFid) {
1022   loadingLevelRange.m_fromFid = fromFid;
1023   loadingLevelRange.m_toFid   = toFid;
1024 }
1025 
getLoadingLevelRange(TFrameId & fromFid,TFrameId & toFid)1026 void getLoadingLevelRange(TFrameId &fromFid, TFrameId &toFid) {
1027   fromFid = loadingLevelRange.m_fromFid;
1028   toFid   = loadingLevelRange.m_toFid;
1029 }
1030 
getLevelPathAndSetNameWithPsdLevelName(TXshSimpleLevel * xshLevel)1031 static TFilePath getLevelPathAndSetNameWithPsdLevelName(
1032     TXshSimpleLevel *xshLevel) {
1033   TFilePath retfp = xshLevel->getPath();
1034 
1035   QString name        = QString::fromStdWString(retfp.getWideName());
1036   bool removeFileName = name.contains("##");
1037   if (removeFileName) {
1038     retfp = TFilePath(
1039         QString::fromStdWString(retfp.getWideString()).replace("##", "#"));
1040   }
1041   QStringList list = name.split("#", QString::SkipEmptyParts);
1042 
1043   if (list.size() >= 2 && list.at(1) != "frames") {
1044     bool hasLayerId;
1045     int layid                  = list.at(1).toInt(&hasLayerId);
1046     QTextCodec *layerNameCodec = QTextCodec::codecForName(
1047         Preferences::instance()->getLayerNameEncoding().c_str());
1048 
1049     if (hasLayerId) {
1050       // An explicit photoshop layer id must be converted to the associated
1051       // level name
1052       TPSDParser psdparser(xshLevel->getScene()->decodeFilePath(retfp));
1053       std::string levelName = psdparser.getLevelNameWithCounter(
1054           layid);  // o_o  what about UNICODE names??
1055 
1056       list[1]                 = layerNameCodec->toUnicode(levelName.c_str());
1057       std::wstring wLevelName = list.join("#").toStdWString();
1058       retfp                   = retfp.withName(wLevelName);
1059 
1060       if (removeFileName) wLevelName = list[1].toStdWString();
1061 
1062       TLevelSet *levelSet = xshLevel->getScene()->getLevelSet();
1063       if (levelSet && levelSet->hasLevel(
1064                           wLevelName))  // levelSet should be asserted instead
1065         levelSet->renameLevel(xshLevel, wLevelName);
1066 
1067       xshLevel->setName(wLevelName);
1068     }
1069   }
1070 
1071   return retfp;
1072 }
1073 //-----------------------------------------------------------------------------
1074 
1075 // Nota: load() NON fa clearFrames(). si limita ad aggiungere le informazioni
1076 // relative ai frames su disco
load()1077 void TXshSimpleLevel::load() {
1078   getProperties()->setCreator("");
1079   QString creator;
1080 
1081   assert(getScene());
1082   if (!getScene()) return;
1083 
1084   m_isSubsequence = loadingLevelRange.isEnabled();
1085 
1086   TFilePath checkpath = getScene()->decodeFilePath(m_path);
1087   std::string type    = checkpath.getType();
1088 
1089   if (m_scannedPath != TFilePath()) {
1090     getProperties()->setDirtyFlag(
1091         false);  // Level is now supposedly loaded from disk
1092 
1093     static const int ScannedCleanuppedMask = Scanned | Cleanupped;
1094     TFilePath path = getScene()->decodeFilePath(m_scannedPath);
1095     if (TSystem::doesExistFileOrLevel(path)) {
1096       TLevelReaderP lr(path);
1097       assert(lr);
1098       TLevelP level = lr->loadInfo();
1099       if (!checkCreatorString(creator = lr->getCreator()))
1100         getProperties()->setIsForbidden(true);
1101       else
1102         for (TLevel::Iterator it = level->begin(); it != level->end(); it++) {
1103           TFrameId fid = it->first;
1104           if (!loadingLevelRange.match(fid)) continue;
1105           setFrameStatus(
1106               fid, (getFrameStatus(fid) & ~ScannedCleanuppedMask) | Scanned);
1107           setFrame(fid, TImageP());
1108         }
1109     }
1110 
1111     path = getScene()->decodeFilePath(m_path);
1112     if (TSystem::doesExistFileOrLevel(path)) {
1113       TLevelReaderP lr(path);
1114       assert(lr);
1115       TLevelP level = lr->loadInfo();
1116       if (getType() & FULLCOLOR_TYPE)
1117         setPalette(FullColorPalette::instance()->getPalette(getScene()));
1118       else
1119         setPalette(level->getPalette());
1120       if (!checkCreatorString(creator = lr->getCreator()))
1121         getProperties()->setIsForbidden(true);
1122       else
1123         for (TLevel::Iterator it = level->begin(); it != level->end(); it++) {
1124           TFrameId fid = it->first;
1125           if (!loadingLevelRange.match(fid)) continue;
1126           setFrameStatus(fid, getFrameStatus(fid) | Cleanupped);
1127           setFrame(fid, TImageP());
1128         }
1129       setContentHistory(
1130           lr->getContentHistory() ? lr->getContentHistory()->clone() : 0);
1131     }
1132 
1133   } else {
1134     // Not a scan + cleanup level
1135 
1136     if (m_path.getType() == "psd" &&
1137         this->getScene()->getVersionNumber().first < 71)
1138       m_path = getLevelPathAndSetNameWithPsdLevelName(this);
1139 
1140     TFilePath path = getScene()->decodeFilePath(m_path);
1141 
1142     getProperties()->setDirtyFlag(
1143         false);  // Level is now supposedly loaded from disk
1144 
1145     TLevelReaderP lr(path);  // May throw
1146     assert(lr);
1147 
1148     TLevelP level = lr->loadInfo();
1149     if (level->getFrameCount() > 0) {
1150       const TImageInfo *info = lr->getImageInfo(level->begin()->first);
1151 
1152       if (info && info->m_samplePerPixel >= 5) {
1153         QString msg = QString(
1154                           "Failed to open %1.\nSamples per pixel is more than "
1155                           "4. It may contain more than one alpha channel.")
1156                           .arg(QString::fromStdWString(m_path.getWideString()));
1157         QMessageBox::warning(0, "Image format not supported", msg);
1158         return;
1159       }
1160 
1161       if (info) set16BitChannelLevel(info->m_bitsPerSample == 16);
1162     }
1163     if ((getType() & FULLCOLOR_TYPE) && !is16BitChannelLevel())
1164       setPalette(FullColorPalette::instance()->getPalette(getScene()));
1165     else
1166       setPalette(level->getPalette());
1167 
1168     if (!checkCreatorString(creator = lr->getCreator()))
1169       getProperties()->setIsForbidden(true);
1170     else
1171       for (TLevel::Iterator it = level->begin(); it != level->end(); it++) {
1172         m_renumberTable[it->first] = it->first;  // Voglio che la tabella
1173                                                  // contenga anche i frame che
1174                                                  // non vengono caricati
1175         if (!loadingLevelRange.match(it->first)) continue;
1176         setFrame(it->first, TImageP());
1177       }
1178 
1179     setContentHistory(lr->getContentHistory() ? lr->getContentHistory()->clone()
1180                                               : 0);
1181   }
1182   getProperties()->setCreator(creator.toStdString());
1183 
1184   loadingLevelRange.reset();
1185   if (getType() != PLI_XSHLEVEL) {
1186     if (m_properties->getImageDpi() == TPointD() && !m_frames.empty()) {
1187       TDimension imageRes(0, 0);
1188       TPointD imageDpi;
1189 
1190       const TFrameId &firstFid = getFirstFid();
1191       std::string imageId      = getImageId(firstFid);
1192 
1193       const TImageInfo *imageInfo =
1194           ImageManager::instance()->getInfo(imageId, ImageManager::none, 0);
1195       if (imageInfo) {
1196         imageRes.lx = imageInfo->m_lx;
1197         imageRes.ly = imageInfo->m_ly;
1198         imageDpi.x  = imageInfo->m_dpix;
1199         imageDpi.y  = imageInfo->m_dpiy;
1200         m_properties->setImageDpi(imageDpi);
1201         m_properties->setImageRes(imageRes);
1202         m_properties->setBpp(imageInfo->m_bitsPerSample *
1203                              imageInfo->m_samplePerPixel);
1204       }
1205     }
1206     setRenumberTable();
1207   }
1208 
1209   if (getPalette() && StudioPalette::isEnabled())
1210     StudioPalette::instance()->updateLinkedColors(getPalette());
1211 
1212   TFilePath refImgName;
1213   if (m_palette) {
1214     refImgName           = m_palette->getRefImgPath();
1215     TFilePath refImgPath = refImgName;
1216     if (refImgName != TFilePath() && TFileStatus(refImgPath).doesExist()) {
1217       TLevelReaderP lr(refImgPath);
1218       if (lr) {
1219         TLevelP level = lr->loadInfo();
1220         if (level->getFrameCount() > 0) {
1221           TImageP img = lr->getFrameReader(level->begin()->first)->load();
1222           if (img && getPalette()) {
1223             img->setPalette(0);
1224             getPalette()->setRefImg(img);
1225             std::vector<TFrameId> fids = getPalette()->getRefLevelFids();
1226             // in case the fids are specified by user
1227             if (fids.size() > 0) {
1228               // check existence of each fid
1229               auto itr = fids.begin();
1230               while (itr != fids.end()) {
1231                 bool found = false;
1232                 for (TLevel::Iterator it = level->begin(); it != level->end();
1233                      ++it) {
1234                   if (itr->getNumber() == it->first.getNumber()) {
1235                     found = true;
1236                     break;
1237                   }
1238                 }
1239                 if (!found)  // remove the fid if it does not exist in the level
1240                   itr = fids.erase(itr);
1241                 else
1242                   itr++;
1243               }
1244             }
1245             // in case the fids are not specified, or all specified fids are
1246             // absent
1247             if (fids.size() == 0) {
1248               for (TLevel::Iterator it = level->begin(); it != level->end();
1249                    ++it)
1250                 fids.push_back(it->first);
1251               getPalette()->setRefLevelFids(fids, false);
1252             } else if (fids.size() != getPalette()->getRefLevelFids().size())
1253               getPalette()->setRefLevelFids(fids, true);
1254           }
1255         }
1256       }
1257     }
1258   }
1259 
1260   // Load hooks
1261   HookSet *hookSet = getHookSet();
1262   hookSet->clearHooks();
1263 
1264   const TFilePath &hookFile =
1265       TXshSimpleLevel::getExistingHookFile(getScene()->decodeFilePath(m_path));
1266 
1267   if (!hookFile.isEmpty()) {
1268     TIStream is(hookFile);
1269     std::string tagName;
1270     try {
1271       if (is.matchTag(tagName) && tagName == "hooks") hookSet->loadData(is);
1272     } catch (...) {
1273     }
1274   }
1275   updateReadOnly();
1276 }
1277 
1278 //-----------------------------------------------------------------------------
1279 
load(const std::vector<TFrameId> & fIds)1280 void TXshSimpleLevel::load(const std::vector<TFrameId> &fIds) {
1281   getProperties()->setCreator("");
1282   QString creator;
1283   assert(getScene());
1284   getProperties()->setDirtyFlag(false);
1285 
1286   m_isSubsequence = loadingLevelRange.isEnabled();
1287 
1288   // non e' un livello scan+cleanup
1289   TFilePath path = getScene()->decodeFilePath(m_path);
1290 
1291   TLevelReaderP lr(path);
1292   assert(lr);
1293 
1294   if (!checkCreatorString(creator = lr->getCreator()))
1295     getProperties()->setIsForbidden(true);
1296   else {
1297     if (fIds.size() != 0) {
1298       for (int i = 0; i < (int)fIds.size(); i++) {
1299         m_renumberTable[fIds[i]] = fIds[i];
1300         if (!loadingLevelRange.match(fIds[i])) continue;
1301         setFrame(fIds[i], TImageP());
1302       }
1303       const TImageInfo *info = lr->getImageInfo(fIds[0]);
1304       if (info) set16BitChannelLevel(info->m_bitsPerSample == 16);
1305     } else {
1306       TLevelP level = lr->loadInfo();
1307       for (TLevel::Iterator it = level->begin(); it != level->end(); it++) {
1308         m_renumberTable[it->first] = it->first;
1309         if (!loadingLevelRange.match(it->first)) continue;
1310         setFrame(it->first, TImageP());
1311       }
1312       const TImageInfo *info = lr->getImageInfo(level->begin()->first);
1313       if (info) set16BitChannelLevel(info->m_bitsPerSample == 16);
1314     }
1315 
1316     if ((getType() & FULLCOLOR_TYPE) && !is16BitChannelLevel())
1317       setPalette(FullColorPalette::instance()->getPalette(getScene()));
1318   }
1319 
1320   setContentHistory(lr->getContentHistory() ? lr->getContentHistory()->clone()
1321                                             : 0);
1322 
1323   getProperties()->setCreator(creator.toStdString());
1324 
1325   loadingLevelRange.reset();
1326 
1327   if (getType() != PLI_XSHLEVEL) {
1328     if (m_properties->getImageDpi() == TPointD() && !m_frames.empty()) {
1329       TDimension imageRes(0, 0);
1330       TPointD imageDpi;
1331       std::string imageId = getImageId(getFirstFid());
1332       const TImageInfo *imageInfo =
1333           ImageManager::instance()->getInfo(imageId, ImageManager::none, 0);
1334       if (imageInfo) {
1335         imageRes.lx = imageInfo->m_lx;
1336         imageRes.ly = imageInfo->m_ly;
1337         imageDpi.x  = imageInfo->m_dpix;
1338         imageDpi.y  = imageInfo->m_dpiy;
1339         m_properties->setImageDpi(imageDpi);
1340         m_properties->setImageRes(imageRes);
1341       }
1342     }
1343     setRenumberTable();
1344   }
1345 }
1346 
1347 //-----------------------------------------------------------------------------
1348 
updateReadOnly()1349 void TXshSimpleLevel::updateReadOnly() {
1350   TFilePath path = getScene()->decodeFilePath(m_path);
1351   m_isReadOnly   = isAreadOnlyLevel(path);
1352 }
1353 
1354 //-----------------------------------------------------------------------------
1355 
saveData(TOStream & os)1356 void TXshSimpleLevel::saveData(TOStream &os) {
1357   os << m_name;
1358 
1359   std::map<std::string, std::string> attr;
1360   if (getProperties()->getDpiPolicy() == LevelProperties::DP_CustomDpi) {
1361     TPointD dpi = getProperties()->getDpi();
1362     if (dpi.x != 0 && dpi.y != 0) {
1363       attr["dpix"] = std::to_string(dpi.x);
1364       attr["dpiy"] = std::to_string(dpi.y);
1365     }
1366   } else {
1367     attr["dpiType"] = "image";
1368   }
1369 
1370   if (getProperties()->getSubsampling() != 1) {
1371     attr["subsampling"] = std::to_string(getProperties()->getSubsampling());
1372   }
1373   if (getProperties()->antialiasSoftness() > 0) {
1374     attr["antialias"] = std::to_string(getProperties()->antialiasSoftness());
1375   }
1376   if (getProperties()->doPremultiply()) {
1377     attr["premultiply"] = std::to_string(getProperties()->doPremultiply());
1378   } else if (getProperties()->whiteTransp()) {
1379     attr["whiteTransp"] = std::to_string(getProperties()->whiteTransp());
1380   } else if (getProperties()->isStopMotionLevel()) {
1381     attr["isStopMotionLevel"] =
1382         std::to_string(getProperties()->isStopMotionLevel());
1383   }
1384 
1385   if (m_type == TZI_XSHLEVEL) attr["type"] = "s";
1386 
1387   os.openCloseChild("info", attr);
1388 
1389   os.child("path") << m_path;  // fp;
1390   if (m_scannedPath != TFilePath())
1391     os.child("scannedPath") << m_scannedPath;  // fp;
1392 }
1393 
1394 //-----------------------------------------------------------------------------
1395 
save()1396 void TXshSimpleLevel::save() {
1397   assert(getScene());
1398   TFilePath path = getScene()->decodeFilePath(m_path);
1399   TSystem::outputDebug("save() : " + ::to_string(m_path) + " = " +
1400                        ::to_string(path) + "\n");
1401 
1402   if (getProperties()->getDirtyFlag() == false &&
1403       getPalette()->getDirtyFlag() == false &&
1404       TSystem::doesExistFileOrLevel(path))
1405     return;
1406 
1407   if (!TFileStatus(path.getParentDir()).doesExist()) {
1408     try {
1409       TSystem::mkDir(path.getParentDir());
1410     } catch (...) {
1411     }
1412   }
1413   save(path);
1414 }
1415 
1416 //-----------------------------------------------------------------------------
1417 
saveBackup(TFilePath path)1418 static void saveBackup(TFilePath path) {
1419   // The additional .bak extension keeps it from being detected as a sequence.
1420   // If the original path is a sequence, find the individual files and back it
1421   // up individually
1422   if (path.isLevelName()) {
1423     TFilePathSet files =
1424         TSystem::readDirectory(path.getParentDir(), false, true);
1425     for (TFilePathSet::iterator file = files.begin(); file != files.end();
1426          file++) {
1427       if (file->getLevelName() == path.getLevelName()) saveBackup(*file);
1428     }
1429     return;
1430   }
1431 
1432   int totalBackups = Preferences::instance()->getBackupKeepCount();
1433   totalBackups -= 1;
1434   TFilePath backup = path.withType(path.getType() + ".bak");
1435   TFilePath prevBackup =
1436       path.withType(path.getType() + ".bak" + std::to_string(totalBackups));
1437   while (--totalBackups >= 0) {
1438     std::string bakExt =
1439         ".bak" + (totalBackups > 0 ? std::to_string(totalBackups) : "");
1440     backup = path.withType(path.getType() + bakExt);
1441     if (TSystem::doesExistFileOrLevel(backup)) {
1442       try {
1443         TSystem::copyFileOrLevel_throw(prevBackup, backup);
1444       } catch (...) {
1445       }
1446     }
1447     prevBackup = backup;
1448   }
1449 
1450   try {
1451     if (TSystem::doesExistFileOrLevel(backup))
1452       TSystem::removeFileOrLevel_throw(backup);
1453     TSystem::copyFileOrLevel_throw(backup, path);
1454   } catch (...) {
1455   }
1456 }
1457 
1458 //-----------------------------------------------------------------------------
1459 
save(const TFilePath & fp,const TFilePath & oldFp,bool overwritePalette)1460 void TXshSimpleLevel::save(const TFilePath &fp, const TFilePath &oldFp,
1461                            bool overwritePalette) {
1462   TFilePath dOldPath =
1463       (!oldFp.isEmpty()) ? oldFp : getScene()->decodeFilePath(m_path);
1464 
1465   TFilePath dDstPath = getScene()->decodeFilePath(fp);
1466   if (!TSystem::touchParentDir(dDstPath))
1467     throw TSystemException(
1468         dDstPath,
1469         "The level cannot be saved: failed to access the target folder.");
1470 
1471   // backup
1472   if (Preferences::instance()->isBackupEnabled() && dOldPath == dDstPath &&
1473       TSystem::doesExistFileOrLevel(dDstPath) &&
1474       !getProperties()->isStopMotionLevel())
1475     saveBackup(dDstPath);
1476 
1477   if (isAreadOnlyLevel(dDstPath)) {
1478     if (m_editableRange.empty() &&
1479         !m_temporaryHookMerged)  // file internally locked
1480       throw TSystemException(
1481           dDstPath, "The level cannot be saved: it is a read only level.");
1482     else if (getType() != OVL_XSHLEVEL) {
1483       // file partially unlocked
1484       std::wstring fileName = getEditableFileName();
1485       assert(!fileName.empty());
1486 
1487       TFilePath app = dDstPath.withName(fileName).withType(dDstPath.getType());
1488 
1489       // removes old files
1490       if (TSystem::doesExistFileOrLevel(app)) TSystem::removeFileOrLevel(app);
1491 
1492       TFilePathSet oldFilePaths;
1493       getFiles(app, oldFilePaths);
1494 
1495       TFilePathSet::iterator it;
1496       for (it = oldFilePaths.begin(); it != oldFilePaths.end(); ++it) {
1497         if (TSystem::doesExistFileOrLevel(*it)) TSystem::removeFileOrLevel(*it);
1498       }
1499 
1500       // save new files
1501       TXshSimpleLevel *sl = new TXshSimpleLevel;
1502       sl->setScene(getScene());
1503       sl->setPalette(getPalette());
1504       sl->setPath(getScene()->codeFilePath(app));
1505       sl->setType(getType());
1506       sl->setDirtyFlag(getDirtyFlag());
1507       sl->addRef();  // Needed so levelUpdater doesn't destroy it right away
1508                      // when its done writing
1509 
1510       std::set<TFrameId>::iterator eft, efEnd = m_editableRange.end();
1511       for (eft = m_editableRange.begin(); eft != efEnd; ++eft) {
1512         const TFrameId &fid = *eft;
1513         sl->setFrame(fid, getFrame(fid, false));
1514       }
1515 
1516       // Copy hooks
1517       HookSet *hookSet = sl->getHookSet();
1518       *hookSet         = *getHookSet();
1519 
1520       FramesSet::iterator ft, fEnd = m_frames.end();
1521       for (ft = m_frames.begin(); ft != fEnd; ++ft) {
1522         const TFrameId &fid = *ft;
1523 
1524         if (m_editableRange.find(fid) == m_editableRange.end())
1525           hookSet->eraseFrame(fid);
1526       }
1527 
1528       sl->setRenumberTable();
1529 
1530       // Copy mesh level
1531       sl->save(app);
1532 
1533 #ifdef _WIN32
1534 
1535       // hides files
1536       oldFilePaths.clear();
1537 
1538       if (TSystem::doesExistFileOrLevel(app)) TSystem::hideFileOrLevel(app);
1539 
1540       getFiles(app, oldFilePaths);
1541 
1542       for (it = oldFilePaths.begin(); it != oldFilePaths.end(); ++it) {
1543         if (TSystem::doesExistFileOrLevel(*it)) TSystem::hideFileOrLevel(*it);
1544       }
1545 #endif
1546       return;
1547     }
1548   }
1549 
1550   if (dOldPath != dDstPath && m_path != TFilePath()) {
1551     const TFilePath &dSrcPath = dOldPath;
1552 
1553     try {
1554       if (TSystem::doesExistFileOrLevel(dSrcPath)) {
1555         if (TSystem::doesExistFileOrLevel(dDstPath))
1556           TSystem::removeFileOrLevel(dDstPath);
1557 
1558         copyFiles(dDstPath, dSrcPath);
1559       }
1560     } catch (...) {
1561     }
1562   }
1563   // when saving the level palette with global name
1564   if (overwritePalette && getType() == TZP_XSHLEVEL && getPalette() &&
1565       getPalette()->getGlobalName() != L"") {
1566     overwritePalette      = false;
1567     TFilePath palettePath = dDstPath.withNoFrame().withType("tpl");
1568     StudioPalette::instance()->save(palettePath, getPalette());
1569     getPalette()->setDirtyFlag(false);
1570   }
1571 
1572   saveSimpleLevel(dDstPath, overwritePalette);
1573 }
1574 
1575 //-----------------------------------------------------------------------------
1576 
saveSimpleLevel(const TFilePath & decodedFp,bool overwritePalette)1577 void TXshSimpleLevel::saveSimpleLevel(const TFilePath &decodedFp,
1578                                       bool overwritePalette) {
1579   /* Precondition: Destination level with path decodedFp is supposed to already
1580              store those image frames not tagged as 'modified' in this level
1581              instance. */
1582 
1583   TFilePath oldPath  = m_path;
1584   TFilePath dOldPath = getScene()->decodeFilePath(oldPath);
1585 
1586   // Substitute m_path with decodedFp until the function quits.
1587   struct CopyOnExit {
1588     TFilePath &m_dstPath, &m_srcPath;
1589     ~CopyOnExit() { m_dstPath = m_srcPath; }
1590   } copyOnExit = {m_path = decodedFp,
1591                   oldPath};  // m_path substituted here until function quits
1592 
1593   bool savingOriginal = (decodedFp == dOldPath), paletteNotSaved = false;
1594 
1595   int imFlags = savingOriginal
1596                     ? ImageManager::dontPutInCache | ImageManager::toBeSaved
1597                     : ImageManager::dontPutInCache;
1598 
1599   std::vector<TFrameId> fids;
1600   getFids(fids);
1601 
1602   bool isLevelModified   = getProperties()->getDirtyFlag();
1603   bool isPaletteModified = false;
1604   if (getPalette()) isPaletteModified = getPalette()->getDirtyFlag();
1605 
1606   if (isLevelModified || isPaletteModified) {
1607     // gmt (8/8/08. provo a risolvere il pasticcio della scrittura dei tlv.
1608     // Dobbiamo
1609     // ripensarci con piu' calma. Per ora cerco di fare meno danno possibile).
1610     TDimension oldRes(0, 0);
1611 
1612     if (TSystem::doesExistFileOrLevel(decodedFp)) {
1613       TLevelReaderP lr(decodedFp);
1614       lr->doReadPalette(false);
1615       const TImageInfo *imageInfo = m_frames.empty()
1616                                         ? lr->getImageInfo()
1617                                         : lr->getImageInfo(*(m_frames.begin()));
1618 
1619       if (imageInfo) {
1620         oldRes.lx = imageInfo->m_lx;
1621         oldRes.ly = imageInfo->m_ly;
1622         lr        = TLevelReaderP();
1623         if (getProperties()->getImageRes() != oldRes) {
1624           // Il comando canvas size cambia le dimensioni del livello!!!
1625           // Se il file già esiste, nel level writer vengono risettate le
1626           // dimesnioni del file esistente
1627           // e salva male
1628           TSystem::removeFileOrLevel(decodedFp);
1629         }
1630       }
1631     }
1632     // overwrite tlv
1633     if (decodedFp.getType() == "tlv" &&
1634         TSystem::doesExistFileOrLevel(decodedFp)) {
1635       if (isLevelModified) {
1636         // in questo caso dovrei scrivere solo i frame modificati.
1637         // certamente NON DEVO scrivere quelli che non ho (e che dovrei
1638         // rileggere dallo stesso file che sto scrivendo
1639 
1640         int oldSubs = getProperties()->getSubsampling();
1641 
1642         TLevelWriterP lw;
1643         try {
1644           lw = TLevelWriterP(decodedFp);
1645         } catch (...) {
1646           // revert subsampling
1647           m_properties->setSubsampling(oldSubs);
1648           m_path = oldPath;
1649           throw TSystemException(decodedFp,
1650                                  "Can't open file.\nAccess may be denied or \n"
1651                                  "someone else may be saving the same file.\n"
1652                                  "Please wait and try again.");
1653         }
1654 
1655         lw->setOverwritePaletteFlag(overwritePalette);
1656 
1657         lw->setCreator(getCreatorString());
1658         lw->setPalette(getPalette());
1659 
1660         // Filter out of the renumber table all non-tlv frames (could happen if
1661         // the level
1662         // is a scan-cleanup mix). This is fine even on the temporarily
1663         // substituted m_path.
1664         std::map<TFrameId, TFrameId> renumberTable;
1665 
1666         for (auto it = m_renumberTable.rbegin(); it != m_renumberTable.rend();
1667              ++it) {
1668           TFrameId id = (*it).first;
1669           if ((getFrameStatus(id) != Scanned) &&
1670               (getFrameStatus(id) != CleanupPreview)) {
1671             renumberTable[id] = (*it).second;
1672           }
1673         }
1674 
1675         m_renumberTable.clear();
1676         m_renumberTable = renumberTable;
1677 
1678         lw->setIconSize(Preferences::instance()->getIconSize());
1679         if (!isSubsequence()) lw->renumberFids(m_renumberTable);
1680 
1681         if (getContentHistory())
1682           lw->setContentHistory(getContentHistory()->clone());
1683 
1684         ImageLoader::BuildExtData extData(this, TFrameId());
1685 
1686         for (auto const &fid : fids) {
1687           std::string imageId = getImageId(
1688               fid, Normal);  // Retrieve the actual level frames ("L_whatever")
1689           if (!ImageManager::instance()->isModified(imageId)) continue;
1690 
1691           extData.m_fid = fid;
1692           TImageP img =
1693               ImageManager::instance()->getImage(imageId, imFlags, &extData);
1694 
1695           assert(img);
1696           if (!img) continue;
1697 
1698           int subs = 1;
1699           if (TToonzImageP ti = img)
1700             subs = ti->getSubsampling();
1701           else if (TRasterImageP ri = img)
1702             subs = ri->getSubsampling();
1703 
1704           assert(subs == 1);
1705           if (subs != 1) continue;
1706 
1707           if (TToonzImageP ti = img) {
1708             /*-
1709              * SaveBoxを塗り漏れ防止に使用している場合、実際の画像範囲とSaveBoxのサイズが異なるため、ここで更新しておく-*/
1710             TRect saveBox;
1711             TRop::computeBBox(ti->getRaster(), saveBox);
1712             ti->setSavebox(saveBox);
1713           }
1714 
1715           lw->getFrameWriter(fid)->save(img);
1716         }
1717 
1718         lw = TLevelWriterP();  // TLevelWriterP's destructor saves the palette
1719       } else if (isPaletteModified && overwritePalette) {
1720         TFilePath palettePath = decodedFp.withNoFrame().withType("tpl");
1721         if (Preferences::instance()->isBackupEnabled() &&
1722             TSystem::doesExistFileOrLevel(palettePath))
1723           saveBackup(palettePath);
1724         TOStream os(palettePath);
1725         if (os.checkStatus())
1726           os << getPalette();
1727         else
1728           paletteNotSaved = true;
1729       }
1730     } else {
1731       // per ora faccio quello che facevo prima, ma dobbiamo rivedere tutta la
1732       // strategia
1733       LevelUpdater updater(this);
1734       updater.getLevelWriter()->setCreator(getCreatorString());
1735       if (updater.getImageInfo())
1736         updater.getLevelWriter()->setFrameRate(
1737             updater.getImageInfo()->m_frameRate);
1738 
1739       if (isLevelModified) {
1740         // Apply the level's renumber table, before saving other files.
1741         // NOTE: This is currently NOT under LevelUpdater's responsibility, as
1742         // renumber tables
1743         // are set/manipulated heavily here. The approach should be re-designed,
1744         // though...
1745         updater.getLevelWriter()->renumberFids(m_renumberTable);
1746 
1747         if (!m_editableRange.empty())
1748           fids = std::vector<TFrameId>(m_editableRange.begin(),
1749                                        m_editableRange.end());
1750 
1751         ImageLoader::BuildExtData extData(this, TFrameId());
1752 
1753         for (auto const &fid : fids) {
1754           std::string imageId = getImageId(
1755               fid, Normal);  // Retrieve the actual level frames ("L_whatever")
1756           if (!ImageManager::instance()->isModified(imageId)) continue;
1757 
1758           extData.m_fid = fid;
1759           TImageP img =
1760               ImageManager::instance()->getImage(imageId, imFlags, &extData);
1761 
1762           assert(img);
1763           if (!img) continue;
1764 
1765           int subs = 1;
1766           if (TToonzImageP ti = img)
1767             subs = ti->getSubsampling();
1768           else if (TRasterImageP ri = img)
1769             subs = ri->getSubsampling();
1770 
1771           assert(subs == 1);
1772           if (subs != 1) continue;
1773 
1774           updater.update(fid, img);
1775         }
1776       }
1777       updater.close();  // Needs the original level subs
1778       if ((getType() & FULLCOLOR_TYPE) && isPaletteModified)
1779         FullColorPalette::instance()->savePalette(getScene());
1780     }
1781   }
1782 
1783   // Save hooks
1784 
1785   TFilePath hookFile;
1786   HookSet *hookSet = 0;
1787 
1788   // Save the hookSet in a temporary hook file
1789   if (getType() == OVL_XSHLEVEL && !m_editableRange.empty()) {
1790     hookSet = new HookSet(*getHookSet());
1791 
1792     FramesSet::const_iterator it;
1793     for (it = m_frames.begin(); it != m_frames.end(); ++it) {
1794       TFrameId fid = *it;
1795       if (m_editableRange.find(fid) == m_editableRange.end())
1796         hookSet->eraseFrame(fid);
1797     }
1798 
1799     // file partially unlocked
1800     std::wstring fileName = getEditableFileName();
1801     assert(!fileName.empty());
1802     TFilePath app = decodedFp.withName(fileName).withType(decodedFp.getType());
1803     hookFile      = getHookPath(app);
1804   } else {
1805     hookFile = getHookPath(decodedFp);
1806     hookSet  = getHookSet();
1807   }
1808 
1809 #ifdef _WIN32
1810   // Remove the hidden attribute (since TOStream's fopen fails on hidden files)
1811   if (getType() == OVL_XSHLEVEL && !m_editableRange.empty())
1812     SetFileAttributesW(hookFile.getWideString().c_str(), FILE_ATTRIBUTE_NORMAL);
1813 #endif
1814 
1815   if (hookSet && hookSet->getHookCount() > 0) {
1816     TOStream os(hookFile);
1817     os.openChild("hooks");
1818     hookSet->saveData(os);
1819     os.closeChild();
1820   } else if (TFileStatus(hookFile).doesExist()) {
1821     try {
1822       TSystem::deleteFile(hookFile);
1823     } catch (...) {
1824     }
1825   }
1826 
1827 #ifdef _WIN32
1828   if (getType() == OVL_XSHLEVEL && !m_editableRange.empty())
1829     TSystem::hideFileOrLevel(hookFile);
1830 #endif
1831 
1832   if (savingOriginal) {
1833     setRenumberTable();  // Since the renumber table refers to the
1834                          // 'original' frames saved on disk
1835     if (m_properties) m_properties->setDirtyFlag(false);
1836 
1837     if (getPalette() && overwritePalette) getPalette()->setDirtyFlag(false);
1838   }
1839 
1840   if (paletteNotSaved)
1841     throw TSystemException(m_path,
1842                            "The palette of the level could not be saved.");
1843 }
1844 
1845 //-----------------------------------------------------------------------------
1846 
getImageId(const TFrameId & fid,int frameStatus) const1847 std::string TXshSimpleLevel::getImageId(const TFrameId &fid,
1848                                         int frameStatus) const {
1849   if (frameStatus < 0) frameStatus = getFrameStatus(fid);
1850   std::string prefix = "L";
1851   if (frameStatus & CleanupPreview)
1852     prefix = "P";
1853   else if ((frameStatus & (Scanned | Cleanupped)) == Scanned)
1854     prefix = "S";
1855   std::string imageId = m_idBase + "_" + prefix + fid.expand();
1856   return imageId;
1857 }
1858 
1859 //-----------------------------------------------------------------------------
1860 
getFrameStatus(const TFrameId & fid) const1861 int TXshSimpleLevel::getFrameStatus(const TFrameId &fid) const {
1862   std::map<TFrameId, int>::const_iterator it = m_framesStatus.find(fid);
1863   return (it != m_framesStatus.end()) ? it->second : Normal;
1864 }
1865 
1866 //-----------------------------------------------------------------------------
1867 
setFrameStatus(const TFrameId & fid,int status)1868 void TXshSimpleLevel::setFrameStatus(const TFrameId &fid, int status) {
1869   assert((status & ~(Scanned | Cleanupped | CleanupPreview)) == 0);
1870   m_framesStatus[fid] = status;
1871 }
1872 
1873 //-----------------------------------------------------------------------------
1874 /*- CleanupPopup::setCurrentLevel / TCleanupper で使用 -*/
makeTlv(const TFilePath & tlvPath)1875 void TXshSimpleLevel::makeTlv(const TFilePath &tlvPath) {
1876   int ltype = getType();
1877 
1878   if (!(ltype & FULLCOLOR_TYPE)) {
1879     assert(ltype & FULLCOLOR_TYPE);
1880     return;
1881   }
1882 
1883   setType(TZP_XSHLEVEL);
1884 
1885   m_scannedPath = m_path;
1886 
1887   assert(tlvPath.getType() == "tlv");
1888   m_path = tlvPath;
1889 
1890   FramesSet::const_iterator it;
1891   for (it = m_frames.begin(); it != m_frames.end(); ++it) {
1892     TFrameId fid = *it;
1893     setFrameStatus(fid, Scanned);
1894     ImageManager::instance()->rebind(getImageId(fid, Scanned),
1895                                      getImageId(fid, 0));
1896     ImageManager::instance()->rebind(getIconId(fid, Scanned),
1897                                      getIconId(fid, 0));
1898   }
1899 }
1900 
1901 //-----------------------------------------------------------------------------
1902 
invalidateFrames()1903 void TXshSimpleLevel::invalidateFrames() {
1904   FramesSet::iterator ft, fEnd = m_frames.end();
1905   for (ft = m_frames.begin(); ft != fEnd; ++ft)
1906     ImageManager::instance()->invalidate(getImageId(*ft));
1907 }
1908 
1909 //-----------------------------------------------------------------------------
1910 /*- 指定したFIdのみInvalidateする -*/
invalidateFrame(const TFrameId & fid)1911 void TXshSimpleLevel::invalidateFrame(const TFrameId &fid) {
1912   std::string id = getImageId(fid);
1913   ImageManager::instance()->invalidate(id);
1914 }
1915 
1916 //-----------------------------------------------------------------------------
1917 // note that the palette will always be replaced by the new one.
initializePalette()1918 void TXshSimpleLevel::initializePalette() {
1919   assert(getScene());
1920   int type = getType();
1921   if (type == TZP_XSHLEVEL || type == PLI_XSHLEVEL) setPalette(new TPalette());
1922   if (type == OVL_XSHLEVEL)
1923     setPalette(FullColorPalette::instance()->getPalette(getScene()));
1924   TPalette *palette = getPalette();
1925   if (palette && type != OVL_XSHLEVEL) {
1926     palette->setPaletteName(getName());
1927     palette->setDirtyFlag(true);
1928   }
1929 }
1930 
1931 //-----------------------------------------------------------------------------
1932 
initializeResolutionAndDpi(const TDimension & dim,double dpi)1933 void TXshSimpleLevel::initializeResolutionAndDpi(const TDimension &dim,
1934                                                  double dpi) {
1935   assert(getScene());
1936   if (getProperties()->getImageRes() != TDimension() &&
1937       getProperties()->getDpi() != TPointD())
1938     return;
1939 
1940   double dpiY = dpi;
1941   getProperties()->setDpiPolicy(LevelProperties::DP_ImageDpi);
1942   if (dim == TDimension()) {
1943     double w, h;
1944     Preferences *pref = Preferences::instance();
1945     if (pref->isNewLevelSizeToCameraSizeEnabled()) {
1946       TDimensionD camSize = getScene()->getCurrentCamera()->getSize();
1947       w                   = camSize.lx;
1948       h                   = camSize.ly;
1949       getProperties()->setDpiPolicy(LevelProperties::DP_CustomDpi);
1950       dpi  = getScene()->getCurrentCamera()->getDpi().x;
1951       dpiY = getScene()->getCurrentCamera()->getDpi().y;
1952     } else {
1953       w    = pref->getDefLevelWidth();
1954       h    = pref->getDefLevelHeight();
1955       dpi  = pref->getDefLevelDpi();
1956       dpiY = dpi;
1957     }
1958 
1959     getProperties()->setImageRes(TDimension(tround(w * dpi), tround(h * dpiY)));
1960   } else
1961     getProperties()->setImageRes(dim);
1962 
1963   getProperties()->setImageDpi(TPointD(dpi, dpiY));
1964   getProperties()->setDpi(dpi);
1965 }
1966 
1967 //-----------------------------------------------------------------------------
1968 
1969 // crea un frame con tipo, dimensioni, dpi, ecc. compatibili con il livello
createEmptyFrame()1970 TImageP TXshSimpleLevel::createEmptyFrame() {
1971   // In case this is the first frame to be created in this level (i.e. the level
1972   // file was missing when loading resources) initialize the level in the same
1973   // manner as createNewLevel() in order to avoid crash. This can be happened if
1974   // the level was not saved after creating and being placed in the xsheet.
1975   if (isEmpty()) {
1976     if (!getPalette()) initializePalette();
1977     initializeResolutionAndDpi();
1978   }
1979 
1980   TImageP result;
1981 
1982   switch (m_type) {
1983   case PLI_XSHLEVEL:
1984     result = new TVectorImage;
1985     break;
1986 
1987   case MESH_XSHLEVEL:
1988     assert(false);  // Not implemented yet
1989     break;
1990 
1991   default: {
1992     // normally the image must have the level->getProperties()->getImageDpi().
1993     // if this value is missing (for some reason - can this happen, ever?) then
1994     // we use the getDpi() (that is the current dpi, e.g. cameraDpi or
1995     // customDpi).
1996 
1997     TPointD dpi = getProperties()->getImageDpi();
1998     /*--
1999     tgaからtlvにconvertしたものをInsert Pasteしたとき、
2000     ペーストしたフレームにのみDPIが付いてしまうので、この処理は省く
2001     --*/
2002     // if(dpi.x==0.0 || dpi.y==0.0)
2003     //  dpi = getProperties()->getDpi();
2004 
2005     TDimension res = getProperties()->getImageRes();
2006 
2007     if (m_type == TZP_XSHLEVEL) {
2008       TRasterCM32P raster(res);
2009       raster->fill(TPixelCM32());
2010       TToonzImageP ti(raster, TRect());
2011       ti->setDpi(dpi.x, dpi.y);
2012       ti->setSavebox(TRect(0, 0, res.lx - 1, res.ly - 1));
2013 
2014       result = ti;
2015     } else {
2016       TRaster32P raster(res);
2017       raster->fill(TPixel32(0, 0, 0, 0));
2018       TRasterImageP ri(raster);
2019       ri->setDpi(dpi.x, dpi.y);
2020 
2021       result = ri;
2022     }
2023 
2024     break;
2025   }
2026   }
2027 
2028   return result;
2029 }
2030 
2031 //-----------------------------------------------------------------------------
2032 
2033 // ritorna la risoluzione dei frames del livello (se il livello non e'
2034 // vettoriale)
getResolution()2035 TDimension TXshSimpleLevel::getResolution() {
2036   if (isEmpty() || getType() == PLI_XSHLEVEL) return TDimension();
2037   return m_properties->getImageRes();
2038 }
2039 
2040 //-----------------------------------------------------------------------------
2041 
2042 // ritorna il dpi letto da file
getImageDpi(const TFrameId & fid,int frameStatus)2043 TPointD TXshSimpleLevel::getImageDpi(const TFrameId &fid, int frameStatus) {
2044   if (isEmpty() || getType() == PLI_XSHLEVEL) return TPointD();
2045 
2046   const TFrameId &theFid =
2047       (fid == TFrameId::NO_FRAME || !isFid(fid)) ? getFirstFid() : fid;
2048   const std::string &imageId = getImageId(theFid, frameStatus);
2049 
2050   const TImageInfo *imageInfo =
2051       ImageManager::instance()->getInfo(imageId, ImageManager::none, 0);
2052 
2053   if (!imageInfo) return TPointD();
2054 
2055   return TPointD(imageInfo->m_dpix, imageInfo->m_dpiy);
2056 }
2057 
2058 //-----------------------------------------------------------------------------
2059 
getImageSubsampling(const TFrameId & fid) const2060 int TXshSimpleLevel::getImageSubsampling(const TFrameId &fid) const {
2061   if (isEmpty() || getType() == PLI_XSHLEVEL) return 1;
2062   TImageP img = TImageCache::instance()->get(getImageId(fid), false);
2063   if (!img) return 1;
2064   if (TRasterImageP ri = img) return ri->getSubsampling();
2065   if (TToonzImageP ti = img) return ti->getSubsampling();
2066   return 1;
2067 }
2068 
2069 //-----------------------------------------------------------------------------
2070 
2071 // ritorna il dpi corrente del livello
getDpi(const TFrameId & fid,int frameStatus)2072 TPointD TXshSimpleLevel::getDpi(const TFrameId &fid, int frameStatus) {
2073   TPointD dpi;
2074   if (m_properties->getDpiPolicy() == LevelProperties::DP_ImageDpi)
2075     dpi = getImageDpi(fid, frameStatus);
2076   else
2077     dpi = m_properties->getDpi();
2078   return dpi;
2079 }
2080 
2081 //-----------------------------------------------------------------------------
2082 
renumber(const std::vector<TFrameId> & fids)2083 void TXshSimpleLevel::renumber(const std::vector<TFrameId> &fids) {
2084   assert(fids.size() == m_frames.size());
2085   int n = fids.size();
2086 
2087   int i = 0;
2088   std::vector<TFrameId> oldFids;
2089   getFids(oldFids);
2090   std::map<TFrameId, TFrameId> table;
2091   std::map<TFrameId, TFrameId> newRenumberTable;
2092   for (std::vector<TFrameId>::iterator it = oldFids.begin();
2093        it != oldFids.end(); ++it) {
2094     TFrameId oldFrameId = *it;
2095     TFrameId newFrameId = fids[i++];
2096     table[oldFrameId]   = newFrameId;
2097     for (auto const &renumber : m_renumberTable) {
2098       if (renumber.second == oldFrameId) {
2099         newRenumberTable[renumber.first] = newFrameId;
2100         break;
2101       }
2102     }
2103   }
2104   for (auto const &renumber : newRenumberTable) {
2105     m_renumberTable[renumber.first] = renumber.second;
2106   }
2107 
2108   m_frames.clear();
2109   for (i = 0; i < n; ++i) {
2110     TFrameId fid(fids[i]);
2111     assert(m_frames.count(fid) == 0);
2112     m_frames.insert(fid);
2113   }
2114 
2115   ImageManager *im = ImageManager::instance();
2116   TImageCache *ic  = TImageCache::instance();
2117 
2118   std::map<TFrameId, TFrameId>::iterator jt;
2119 
2120   {
2121     for (i = 0, jt = table.begin(); jt != table.end(); ++jt, ++i) {
2122       std::string Id = getImageId(jt->first);
2123       ImageLoader::BuildExtData extData(this, jt->first);
2124       TImageP img = im->getImage(Id, ImageManager::none, &extData);
2125       ic->add(getIconId(jt->first), img, false);
2126       im->rebind(Id, "^" + std::to_string(i));
2127       ic->remap("^icon:" + std::to_string(i), getIconId(jt->first));
2128     }
2129 
2130     for (i = 0, jt = table.begin(); jt != table.end(); ++jt, ++i) {
2131       std::string Id = getImageId(jt->second);
2132       im->rebind("^" + std::to_string(i), Id);
2133       ic->remap(getIconId(jt->second), "^icon:" + std::to_string(i));
2134       im->renumber(Id, jt->second);
2135     }
2136   }
2137 
2138   if (getType() == PLI_XSHLEVEL) {
2139     for (i = 0, jt = table.begin(); jt != table.end(); ++jt, ++i) {
2140       const std::string &id = rasterized(getImageId(jt->first));
2141       if (im->isBound(id)) im->rebind(id, rasterized("^" + std::to_string(i)));
2142     }
2143     for (i = 0, jt = table.begin(); jt != table.end(); ++jt, ++i) {
2144       const std::string &id = rasterized("^" + std::to_string(i));
2145       if (im->isBound(id)) im->rebind(id, rasterized(getImageId(jt->second)));
2146     }
2147   }
2148 
2149   if (getType() & FULLCOLOR_TYPE) {
2150     for (i = 0, jt = table.begin(); jt != table.end(); ++jt, ++i) {
2151       const std::string &id = filled(getImageId(jt->first));
2152       if (im->isBound(id)) im->rebind(id, filled("^" + std::to_string(i)));
2153     }
2154     for (i = 0, jt = table.begin(); jt != table.end(); ++jt, ++i) {
2155       const std::string &id = filled("^" + std::to_string(i));
2156       if (im->isBound(id)) im->rebind(id, filled(getImageId(jt->second)));
2157     }
2158   }
2159 
2160   m_properties->setDirtyFlag(true);
2161 
2162   if (getHookSet()) getHookSet()->renumber(table);
2163 }
2164 
2165 //-----------------------------------------------------------------------------
2166 
copyFiles(const TFilePath & dst,const TFilePath & src)2167 void TXshSimpleLevel::copyFiles(const TFilePath &dst, const TFilePath &src) {
2168   if (dst == src) return;
2169   TSystem::touchParentDir(dst);
2170   TSystem::copyFileOrLevel_throw(dst, src);
2171   if (dst.getType() == "tlv") {
2172     // Copio la palette del livello
2173     TFilePath srcPltPath =
2174         src.getParentDir() + TFilePath(src.getWideName() + L".tpl");
2175     if (TFileStatus(srcPltPath).doesExist())
2176       TSystem::copyFile(
2177           dst.getParentDir() + TFilePath(dst.getWideName() + L".tpl"),
2178           srcPltPath, true);
2179   }
2180   if (dst.getType() == "tzp" || dst.getType() == "tzu") {
2181     // Copio la palette del livello
2182     TFilePath srcPltPath =
2183         src.getParentDir() + TFilePath(src.getWideName() + L".plt");
2184     if (TFileStatus(srcPltPath).doesExist())
2185       TSystem::copyFile(
2186           dst.getParentDir() + TFilePath(dst.getWideName() + L".plt"),
2187           srcPltPath, true);
2188   }
2189 
2190   const TFilePath &srcHookFile = TXshSimpleLevel::getExistingHookFile(src);
2191   if (!srcHookFile.isEmpty()) {
2192     const TFilePath &dstHookFile = getHookPath(dst);
2193     TSystem::copyFile(dstHookFile, srcHookFile, true);
2194   }
2195   TFilePath files = src.getParentDir() + (src.getName() + "_files");
2196   if (TFileStatus(files).doesExist() && TFileStatus(files).isDirectory())
2197     TSystem::copyDir(dst.getParentDir() + (src.getName() + "_files"), files);
2198 }
2199 
2200 //-----------------------------------------------------------------------------
2201 
renameFiles(const TFilePath & dst,const TFilePath & src)2202 void TXshSimpleLevel::renameFiles(const TFilePath &dst, const TFilePath &src) {
2203   if (dst == src) return;
2204   TSystem::touchParentDir(dst);
2205   if (TSystem::doesExistFileOrLevel(dst)) TXshSimpleLevel::removeFiles(dst);
2206   TSystem::renameFileOrLevel_throw(dst, src);
2207   if (dst.getType() == "tlv")
2208     TSystem::renameFile(dst.withType("tpl"), src.withType("tpl"));
2209 
2210   const TFilePath &srcHookFile = TXshSimpleLevel::getExistingHookFile(src);
2211   if (!srcHookFile.isEmpty()) {
2212     const TFilePath &dstHookFile = getHookPath(dst);
2213     TSystem::renameFile(dstHookFile, srcHookFile);
2214   }
2215 
2216   TFilePath files = src.getParentDir() + (src.getName() + "_files");
2217   if (TFileStatus(files).doesExist() && TFileStatus(files).isDirectory())
2218     TSystem::renameFile(dst.getParentDir() + (dst.getName() + "_files"), files);
2219 }
2220 
2221 //-----------------------------------------------------------------------------
2222 
removeFiles(const TFilePath & fp)2223 void TXshSimpleLevel::removeFiles(const TFilePath &fp) {
2224   TSystem::moveFileOrLevelToRecycleBin(fp);
2225   if (fp.getType() == "tlv") {
2226     TFilePath tpl = fp.withType("tpl");
2227     if (TFileStatus(tpl).doesExist()) TSystem::moveFileToRecycleBin(tpl);
2228   }
2229 
2230   // Delete ALL hook files (ie from every Toonz version)
2231   const QStringList &hookFiles = TXshSimpleLevel::getHookFiles(fp);
2232 
2233   int f, fCount = hookFiles.size();
2234   for (f = 0; f != fCount; ++f)
2235     TSystem::moveFileToRecycleBin(TFilePath(hookFiles[f].toStdWString()));
2236 
2237   TFilePath files = fp.getParentDir() + (fp.getName() + "_files");
2238   if (TFileStatus(files).doesExist() && TFileStatus(files).isDirectory())
2239     TSystem::rmDirTree(files);
2240 }
2241 
2242 //-----------------------------------------------------------------------------
2243 
getFiles(const TFilePath & fp,TFilePathSet & fpset)2244 void TXshSimpleLevel::getFiles(const TFilePath &fp, TFilePathSet &fpset) {
2245   if (fp.getType() == "tlv") {
2246     TFilePath tpl = fp.withType("tpl");
2247     if (TFileStatus(tpl).doesExist()) fpset.push_back(tpl);
2248   }
2249 
2250   // Store the hooks file if any (NOTE: there could be more than one hooks
2251   // file. I'm retaining the behavior I've seen, but was this really intended?
2252   // Shouldn't we return ALL the hooks files?)
2253   const TFilePath &hookFile = getExistingHookFile(fp);
2254   if (!hookFile.isEmpty()) fpset.push_back(hookFile);
2255 
2256   // Needed for TAB Manga & Kids and not used in Toonz
2257 
2258   // TFilePath files = fp.getParentDir() + (fp.getName() + "_files");
2259   // if(TFileStatus(files).doesExist() && TFileStatus(files).isDirectory())
2260   //  TSystem::rmDirTree(files);
2261 }
2262 
2263 //-----------------------------------------------------------------------------
2264 
setContentHistory(TContentHistory * contentHistory)2265 void TXshSimpleLevel::setContentHistory(TContentHistory *contentHistory) {
2266   if (contentHistory != m_contentHistory.get())
2267     m_contentHistory.reset(contentHistory);
2268 }
2269 
2270 //-----------------------------------------------------------------------------
2271 
setCompatibilityMasks(int writeMask,int neededMask,int forbiddenMask)2272 void TXshSimpleLevel::setCompatibilityMasks(int writeMask, int neededMask,
2273                                             int forbiddenMask) {
2274   compatibility.writeMask     = writeMask;
2275   compatibility.neededMask    = neededMask;
2276   compatibility.forbiddenMask = forbiddenMask;
2277 }
2278 
2279 //-----------------------------------------------------------------------------
2280 
getHookPath(const TFilePath & path)2281 TFilePath TXshSimpleLevel::getHookPath(const TFilePath &path) {
2282   // Translates:  levelName..ext  into  levelName_hooks..ext.xml
2283   //              levelName.ext   into  levelName_hooks.ext.xml
2284 
2285   // Observe that retaining the original level extension IS IMPORTANT, as it
2286   // ensures
2287   // the UNICITY of the association between a level path and its hook path (ie
2288   // levels  test..png  and  test..tif  have separate hook files).
2289 
2290   return TFilePath(path.withName(path.getName() + "_hooks").getWideString() +
2291                    L".xml");
2292 }
2293 
2294 //-----------------------------------------------------------------------------
2295 
getHookFiles(const TFilePath & decodedLevelPath)2296 QStringList TXshSimpleLevel::getHookFiles(const TFilePath &decodedLevelPath) {
2297   const TFilePath &dirPath = decodedLevelPath.getParentDir();
2298   QDir levelDir(QString::fromStdWString(dirPath.getWideString()));
2299 
2300   QStringList hookFileFilter(
2301       QString::fromStdWString(  // We have to scan for files of the
2302           decodedLevelPath.getWideName() +
2303           L"_hooks*.xml"));   // form  levelName_hooks*.xml  to
2304                               // retain backward compatibility
2305   return levelDir.entryList(  //
2306       hookFileFilter,
2307       QDir::Files | QDir::NoDotAndDotDot,  // Observe that we cleverly sort by
2308       QDir::Time);                         // mod date :)
2309 }
2310 
2311 //-----------------------------------------------------------------------------
2312 
getExistingHookFile(const TFilePath & decodedLevelPath)2313 TFilePath TXshSimpleLevel::getExistingHookFile(
2314     const TFilePath &decodedLevelPath) {
2315   static const int pCount              = 3;
2316   static const QRegExp pattern[pCount] = {
2317       // Prioritized in this order
2318       QRegExp(".*\\.\\.?.+\\.xml$"),  // whatever.(.)ext.xml
2319       QRegExp(".*\\.xml$"),           // whatever.xml
2320       QRegExp(".*\\.\\.?xml$")        // whatever.(.)xml
2321   };
2322 
2323   struct locals {
2324     static inline int getPattern(const QString &fp) {
2325       for (int p = 0; p != pCount; ++p)
2326         if (pattern[p].exactMatch(fp)) return p;
2327       return -1;
2328     }
2329   };  // locals
2330 
2331   const QStringList &hookFiles = getHookFiles(decodedLevelPath);
2332   if (hookFiles.empty()) return TFilePath();
2333 
2334   // Return the hook file with the most recent (smallest) identified
2335   // regexp pattern
2336   int fPattern, p = pCount, h = -1;
2337 
2338   int f, fCount = hookFiles.size();
2339   for (f = 0; f != fCount; ++f) {
2340     fPattern = locals::getPattern(hookFiles[f]);
2341     if (fPattern < p) p = fPattern, h = f;
2342   }
2343 
2344   assert(h >= 0);
2345   return (h < 0) ? TFilePath()
2346                  : decodedLevelPath.getParentDir() +
2347                        TFilePath(hookFiles[h].toStdWString());
2348 }
2349 
2350 //-----------------------------------------------------------------------------
2351 
getBBox(const TFrameId & fid) const2352 TRectD TXshSimpleLevel::getBBox(const TFrameId &fid) const {
2353   TRectD bbox;
2354   double dpiX = Stage::inch, dpiY = dpiX;
2355 
2356   // Get the frame bbox in image coordinates
2357   switch (getType()) {
2358   case PLI_XSHLEVEL:
2359   case MESH_XSHLEVEL: {
2360     // Load the image and extract its bbox forcibly
2361     TImageP img = getFrame(fid, false);
2362     if (!img) return TRectD();
2363 
2364     bbox = img->getBBox();
2365 
2366     if (TMeshImageP mi = img) mi->getDpi(dpiX, dpiY);
2367 
2368     break;
2369   }
2370 
2371   default: {
2372     // Raster case: retrieve the image info from the ImageManager
2373     const std::string &imageId = getImageId(fid);
2374 
2375     const TImageInfo *info =
2376         ImageManager::instance()->getInfo(imageId, ImageManager::none, 0);
2377     if (!info) return TRectD();
2378 
2379     bbox = TRectD(TPointD(info->m_x0, info->m_y0),
2380                   TPointD(info->m_x1, info->m_y1)) -
2381            0.5 * TPointD(info->m_lx, info->m_ly);
2382 
2383     if (info->m_dpix > 0.0 && info->m_dpiy > 0.0)
2384       dpiX = info->m_dpix, dpiY = info->m_dpiy;
2385 
2386     break;
2387   }
2388   }
2389 
2390   // Get the frame's dpi and traduce the bbox to inch coordinates
2391   return TScale(1.0 / dpiX, 1.0 / dpiY) * bbox;
2392 }
2393 
isFrameReadOnly(TFrameId fid)2394 bool TXshSimpleLevel::isFrameReadOnly(TFrameId fid) {
2395   // For Raster and mesh files, check to see if files are marked as read-only at
2396   // the OS level
2397   if (getType() == OVL_XSHLEVEL || getType() == TZI_XSHLEVEL ||
2398       getType() == MESH_XSHLEVEL) {
2399     if (getProperties()->isStopMotionLevel()) return true;
2400     TFilePath fullPath   = getScene()->decodeFilePath(m_path);
2401     std::string fileType = fullPath.getType();
2402     if (fileType == "psd" || fileType == "gif" || fileType == "mp4" ||
2403         fileType == "webm")
2404       return true;
2405     TFilePath path =
2406         fullPath.getDots() == ".." ? fullPath.withFrame(fid) : fullPath;
2407     if (!TSystem::doesExistFileOrLevel(path)) return false;
2408     TFileStatus fs(path);
2409     return !fs.isWritable();
2410   }
2411 
2412   // If Level is marked read only, check for editable frames
2413   if (m_isReadOnly && !m_editableRange.empty() &&
2414       m_editableRange.count(fid) != 0)
2415     return false;
2416 
2417   return m_isReadOnly;
2418 }
2419