tahoma2d/toonz/sources/toonzlib/txshsimplelevel.cpp
Campbell Barton b3bd842e04 Make functions static, ensure declarations match headers (#610)
This patch used -Wmissing-declarations warning
to show functions and symbols that had no declarations, and either:

- Make static
- Add to header

This helps avoid possability that declarations and functions get out of sync.
And ensures all source files reference headers correctly.

It also makes sure functions defined with extern "C",
have this defined in the header. An error found in calligraph.h while writing this patch.

This has been applied to toonzlib, to avoid making very large global changes.
If accepted, -Wmissing-declarations warning could be added to CMake.
2016-07-13 21:05:06 +09:00

2220 lines
72 KiB
C++

#include "toonz/txshsimplelevel.h"
#include "imagebuilders.h"
// TnzLib includes
#include "toonz/txshleveltypes.h"
#include "toonz/imagemanager.h"
#include "toonz/studiopalette.h"
#include "toonz/hook.h"
#include "toonz/toonzscene.h"
#include "toonz/levelproperties.h"
#include "toonz/levelupdater.h"
#include "toonz/fullcolorpalette.h"
#include "toonz/preferences.h"
#include "toonz/stage.h"
#include "toonz/textureutils.h"
#include "toonz/levelset.h"
// TnzBase includes
#include "tenv.h"
// TnzCore includes
#include "trasterimage.h"
#include "tvectorimage.h"
#include "tmeshimage.h"
#include "timagecache.h"
#include "tofflinegl.h"
#include "tvectorgl.h"
#include "tvectorrenderdata.h"
#include "tropcm.h"
#include "tpixelutils.h"
#include "timageinfo.h"
#include "tlogger.h"
#include "tstream.h"
#include "tsystem.h"
#include "tcontenthistory.h"
// Qt includes
#include <QDir>
#include <QRegExp>
#include <QMessageBox>
#include <QtCore>
#include "../common/psdlib/psd.h"
namespace bc = boost::container;
//******************************************************************************************
// Global stuff
//******************************************************************************************
DEFINE_CLASS_CODE(TXshSimpleLevel, 20)
PERSIST_IDENTIFIER(TXshSimpleLevel, "level")
//******************************************************************************************
// Local namespace stuff
//******************************************************************************************
namespace {
int idBaseCode = 1;
//-----------------------------------------------------------------------------
struct CompatibilityStruct {
int writeMask, neededMask, forbiddenMask;
};
CompatibilityStruct compatibility = {
0x00F1, // mask written. Note: Student main must be 0x00F2
// Note: the 0x00F0 part is currently not used.
0x0000, // mandatory mask: loaded levels MUST have a mask with these bits
// set
// Note: if mandatory mask != 0 then no old level (without
// mask)
// can be loaded
// Note: this mask is currently not used.
0x000E // forbidden mask: loaded levels MUST NOT have a mask with these
// bits set
//
};
//-----------------------------------------------------------------------------
inline std::string rasterized(std::string id) { return id + "_rasterized"; }
inline std::string filled(std::string id) { return id + "_filled"; }
//-----------------------------------------------------------------------------
QString getCreatorString() {
QString creator = QString::fromStdString(TEnv::getApplicationName()) + " " +
QString::fromStdString(TEnv::getApplicationVersion()) +
" CM(" + QString::number(compatibility.writeMask, 16) + ")";
return creator;
}
//-----------------------------------------------------------------------------
bool checkCreatorString(const QString &creator) {
int mask = 0;
if (creator != "") {
QRegExp rx("CM\\([0-9A-Fa-f]*\\)");
int pos = rx.indexIn(creator);
int len = rx.matchedLength();
if (pos >= 0 && len >= 4) {
QString v;
if (len > 4) v = creator.mid(pos + 3, len - 4);
bool ok = true;
mask = v.toInt(&ok, 16);
}
}
return (mask & compatibility.neededMask) == compatibility.neededMask &&
(mask & compatibility.forbiddenMask) == 0;
}
//-----------------------------------------------------------------------------
bool isAreadOnlyLevel(const TFilePath &path) {
if (path.isEmpty() || !path.isAbsolute()) return false;
if (path.getDots() == "." ||
(path.getDots() == ".." &&
(path.getType() == "tlv" || path.getType() == "tpl"))) {
if (!TSystem::doesExistFileOrLevel(path)) return false;
TFileStatus fs(path);
return !fs.isWritable();
}
/*- 処理が重くなるので、連番ファイルは全てfalseを返す -*/
/*
else if(path.getDots() == "..")
{
TFilePath dir = path.getParentDir();
QDir qDir(QString::fromStdWString(dir.getWideString()));
QString levelName =
QRegExp::escape(QString::fromStdWString(path.getWideName()));
QString levelType = QString::fromStdString(path.getType());
QString exp(levelName+".[0-9]{1,4}."+levelType);
QRegExp regExp(exp);
QStringList list = qDir.entryList(QDir::Files);
QStringList livelFrames = list.filter(regExp);
bool isReadOnly=false;
int i;
for(i=0; i<livelFrames.size() && !isReadOnly; i++)
{
TFilePath frame = dir+TFilePath(livelFrames[i].toStdWString());
if(frame.isEmpty() || !frame.isAbsolute()) continue;
TFileStatus fs(frame);
isReadOnly = !fs.isWritable();
}
return isReadOnly;
}
*/
else
return false;
}
//-----------------------------------------------------------------------------
void getIndexesRangefromFids(TXshSimpleLevel *level,
const std::set<TFrameId> &fids, int &fromIndex,
int &toIndex) {
if (fids.empty()) {
fromIndex = toIndex = -1;
return;
}
toIndex = 0;
fromIndex = level->getFrameCount() - 1;
std::set<TFrameId>::const_iterator it;
for (it = fids.begin(); it != fids.end(); ++it) {
int index = level->guessIndex(*it);
if (index > toIndex) toIndex = index;
if (index < fromIndex) fromIndex = index;
}
}
} // namespace
//******************************************************************************************
// TXshSimpleLevel impementation
//******************************************************************************************
bool TXshSimpleLevel::m_rasterizePli = false;
bool TXshSimpleLevel::m_fillFullColorRaster = false;
//-----------------------------------------------------------------------------
TXshSimpleLevel::TXshSimpleLevel(const std::wstring &name)
: TXshLevel(m_classCode, name)
, m_properties(new LevelProperties)
, m_palette(0)
, m_idBase(std::to_string(idBaseCode++))
, m_editableRangeUserInfo(L"")
, m_isSubsequence(false)
, m_16BitChannelLevel(false)
, m_isReadOnly(false)
, m_temporaryHookMerged(false) {}
//-----------------------------------------------------------------------------
TXshSimpleLevel::~TXshSimpleLevel() {
clearFrames();
if (m_palette) m_palette->release();
}
//-----------------------------------------------------------------------------
void TXshSimpleLevel::setEditableRange(unsigned int from, unsigned int to,
const std::wstring &userName) {
assert(from <= to && to < (unsigned int)getFrameCount());
unsigned int i;
for (i = from; i <= to; i++) m_editableRange.insert(index2fid(i));
QString hostName = TSystem::getHostName();
m_editableRangeUserInfo = userName + L"_" + hostName.toStdWString();
std::wstring fileName = getEditableFileName();
TFilePath dstPath = getScene()->decodeFilePath(m_path);
dstPath = dstPath.withName(fileName).withType(dstPath.getType());
// Load temporary level file (for pli and tlv types only)
if (getType() != OVL_XSHLEVEL && TSystem::doesExistFileOrLevel(dstPath)) {
TLevelReaderP lr(dstPath);
TLevelP level = lr->loadInfo();
setPalette(level->getPalette());
for (TLevel::Iterator it = level->begin(); it != level->end(); it++) {
TImageP img = lr->getFrameReader(it->first)->load();
setFrame(it->first, img);
}
}
// Merge temporary hook file with current hookset
const TFilePath &hookFile = getHookPath(dstPath);
mergeTemporaryHookFile(from, to, hookFile);
}
//-----------------------------------------------------------------------------
void TXshSimpleLevel::mergeTemporaryHookFile(unsigned int from, unsigned int to,
const TFilePath &hookFile) {
if (!TFileStatus(hookFile).doesExist()) return;
HookSet *tempHookSet = new HookSet;
TIStream is(hookFile);
std::string tagName;
try {
if (is.matchTag(tagName) && tagName == "hooks") tempHookSet->loadData(is);
} catch (...) {
}
HookSet *hookSet = getHookSet();
int tempHookCount = tempHookSet->getHookCount();
if (tempHookCount == 0) {
for (unsigned int f = from; f <= to; f++) {
TFrameId fid = index2fid(f);
hookSet->eraseFrame(fid);
}
} else {
for (int i = 0; i < tempHookCount; i++) {
Hook *hook = tempHookSet->getHook(i);
Hook *newHook = hookSet->touchHook(hook->getId());
newHook->setTrackerObjectId(hook->getTrackerObjectId());
newHook->setTrackerRegionHeight(hook->getTrackerRegionHeight());
newHook->setTrackerRegionWidth(hook->getTrackerRegionWidth());
for (unsigned int f = from; f <= to; f++) {
TFrameId fid = index2fid(f);
newHook->setAPos(fid, hook->getAPos(fid));
newHook->setBPos(fid, hook->getBPos(fid));
}
}
}
m_temporaryHookMerged = true;
}
//-----------------------------------------------------------------------------
void TXshSimpleLevel::clearEditableRange() {
m_editableRange.clear();
m_editableRangeUserInfo = L"";
}
//-----------------------------------------------------------------------------
std::wstring TXshSimpleLevel::getEditableFileName() {
#ifdef MACOSX
std::wstring fileName = L"." + m_path.getWideName();
#else
std::wstring fileName = m_path.getWideName();
#endif
fileName += L"_" + m_editableRangeUserInfo;
int from, to;
getIndexesRangefromFids(this, m_editableRange, from, to);
if (from == -1 && to == -1) return L"";
fileName += L"_" + std::to_wstring(from + 1) + L"-" + std::to_wstring(to + 1);
return fileName;
}
//-----------------------------------------------------------------------------
std::set<TFrameId> TXshSimpleLevel::getEditableRange() {
return m_editableRange;
}
//-----------------------------------------------------------------------------
void TXshSimpleLevel::setRenumberTable() {
m_renumberTable.clear();
FramesSet::iterator ft, fEnd = m_frames.end();
for (ft = m_frames.begin(); ft != fEnd; ++ft) m_renumberTable[*ft] = *ft;
}
//-----------------------------------------------------------------------------
void TXshSimpleLevel::setDirtyFlag(bool on) { m_properties->setDirtyFlag(on); }
//-----------------------------------------------------------------------------
bool TXshSimpleLevel::getDirtyFlag() const {
return m_properties->getDirtyFlag();
}
//-----------------------------------------------------------------------------
void TXshSimpleLevel::touchFrame(const TFrameId &fid) {
m_properties->setDirtyFlag(true);
TContentHistory *ch = getContentHistory();
if (!ch) {
ch = new TContentHistory(true);
setContentHistory(ch);
}
ch->frameModifiedNow(fid);
if (getType() == PLI_XSHLEVEL) {
std::string id = rasterized(getImageId(fid));
ImageManager::instance()->invalidate(id);
}
if (getType() & FULLCOLOR_TYPE) {
std::string id = filled(getImageId(fid));
ImageManager::instance()->invalidate(id);
}
}
//-----------------------------------------------------------------------------
void TXshSimpleLevel::onPaletteChanged() {
FramesSet::iterator ft, fEnd = m_frames.end();
for (ft = m_frames.begin(); ft != fEnd; ++ft) {
const TFrameId &fid = *ft;
if (getType() == PLI_XSHLEVEL) {
std::string id = rasterized(getImageId(fid));
ImageManager::instance()->invalidate(id);
}
if (getType() & FULLCOLOR_TYPE) {
std::string id = filled(getImageId(fid));
ImageManager::instance()->invalidate(id);
}
texture_utils::invalidateTexture(this, fid);
}
}
//-----------------------------------------------------------------------------
void TXshSimpleLevel::setScannedPath(const TFilePath &fp) {
m_scannedPath = fp;
}
//-----------------------------------------------------------------------------
void TXshSimpleLevel::setPath(const TFilePath &fp, bool keepFrames) {
m_path = fp;
if (!keepFrames) {
clearFrames();
assert(getScene());
try {
load();
} catch (...) {
}
}
if (getType() != PLI_XSHLEVEL) {
if (!m_frames.empty()) {
std::string imageId = getImageId(getFirstFid());
const TImageInfo *imageInfo =
ImageManager::instance()->getInfo(imageId, ImageManager::none, 0);
if (imageInfo) {
TDimension imageRes(0, 0);
TPointD imageDpi;
imageRes.lx = imageInfo->m_lx;
imageRes.ly = imageInfo->m_ly;
imageDpi.x = imageInfo->m_dpix;
imageDpi.y = imageInfo->m_dpiy;
m_properties->setImageDpi(imageDpi);
m_properties->setImageRes(imageRes);
m_properties->setBpp(imageInfo->m_bitsPerSample *
imageInfo->m_samplePerPixel);
}
}
}
}
//-----------------------------------------------------------------------------
void TXshSimpleLevel::clonePropertiesFrom(const TXshSimpleLevel *oldSl) {
m_properties->setImageDpi(
oldSl->m_properties
->getImageDpi()); // Watch out - may change dpi policy!
m_properties->setDpi(oldSl->m_properties->getDpi());
m_properties->setDpiPolicy(oldSl->m_properties->getDpiPolicy());
m_properties->setImageRes(oldSl->m_properties->getImageRes());
m_properties->setBpp(oldSl->m_properties->getBpp());
m_properties->setSubsampling(oldSl->m_properties->getSubsampling());
}
//-----------------------------------------------------------------------------
TPalette *TXshSimpleLevel::getPalette() const { return m_palette; }
//-----------------------------------------------------------------------------
void TXshSimpleLevel::setPalette(TPalette *palette) {
if (m_palette != palette) {
if (m_palette) m_palette->release();
m_palette = palette;
if (m_palette) {
m_palette->addRef();
if (!(getType() & FULLCOLOR_TYPE)) m_palette->setPaletteName(getName());
}
}
}
//-----------------------------------------------------------------------------
void TXshSimpleLevel::getFids(std::vector<TFrameId> &fids) const {
fids.assign(m_frames.begin(), m_frames.end());
}
//-----------------------------------------------------------------------------
std::vector<TFrameId> TXshSimpleLevel::getFids() const {
return std::vector<TFrameId>(m_frames.begin(), m_frames.end());
}
//-----------------------------------------------------------------------------
bool TXshSimpleLevel::isFid(const TFrameId &fid) const {
return m_frames.count(fid);
}
//-----------------------------------------------------------------------------
const TFrameId &TXshSimpleLevel::getFrameId(int index) const {
return *(m_frames.begin() += index);
}
//-----------------------------------------------------------------------------
TFrameId TXshSimpleLevel::getFirstFid() const {
return !isEmpty() ? *m_frames.begin() : TFrameId(TFrameId::NO_FRAME);
}
//-----------------------------------------------------------------------------
TFrameId TXshSimpleLevel::getLastFid() const {
return !isEmpty() ? *m_frames.rbegin() : TFrameId(TFrameId::NO_FRAME);
}
//-----------------------------------------------------------------------------
int TXshSimpleLevel::guessStep() const {
int frameCount = m_frames.size();
if (frameCount < 2)
return 1; // un livello con zero o un frame ha per def. step=1
FramesSet::const_iterator ft = m_frames.begin();
TFrameId firstFid = *ft++, secondFid = *ft++;
if (firstFid.getLetter() != 0 || secondFid.getLetter() != 0) return 1;
int step = secondFid.getNumber() - firstFid.getNumber();
if (step == 1) return 1;
// controllo subito se lo step vale per l'ultimo frame
// (cerco di limitare il numero di volte in cui devo controllare tutta la
// lista)
TFrameId lastFid = *m_frames.rbegin();
if (lastFid.getLetter() != 0) return 1;
if (lastFid.getNumber() != firstFid.getNumber() + step * (frameCount - 1))
return 1;
for (int i = 2; ft != m_frames.end(); ++ft, ++i) {
const TFrameId &fid = *ft;
if (fid.getLetter() != 0) return 1;
if (fid.getNumber() != firstFid.getNumber() + step * i) return 1;
}
return step;
}
//-----------------------------------------------------------------------------
int TXshSimpleLevel::fid2index(const TFrameId &fid) const {
FramesSet::const_iterator ft = m_frames.find(fid);
return (ft != m_frames.end()) ? std::distance(m_frames.begin(), ft)
: // Note: flat_set has random access
-1; // iterators, so this is FAST
}
//-----------------------------------------------------------------------------
int TXshSimpleLevel::guessIndex(const TFrameId &fid) const {
if (m_frames.empty()) return 0; // no frames, return 0 (by definition)
FramesSet::const_iterator ft = m_frames.lower_bound(fid);
if (ft == m_frames.end()) {
const TFrameId &maxFid = *m_frames.rbegin();
assert(fid > maxFid);
// fid not in the table, but greater than the last one.
// return a suitable index. (e.g. frames are 1,3,5,7; fid2index(11) should
// return index=5)
int step = guessStep();
int i = (fid.getNumber() - maxFid.getNumber()) / step;
return m_frames.size() - 1 + i;
} else
return std::distance(m_frames.begin(), ft);
}
//-----------------------------------------------------------------------------
TFrameId TXshSimpleLevel::index2fid(int index) const {
if (index < 0) return TFrameId(-2);
int frameCount = m_frames.size();
if (frameCount == 0) return TFrameId(1); // o_o?
if (index < frameCount) {
FramesSet::const_iterator ft = m_frames.begin();
std::advance(ft, index);
return *ft;
} else {
int step = guessStep();
TFrameId maxFid = *m_frames.rbegin();
int d = step * (index - frameCount + 1);
return TFrameId(maxFid.getNumber() + d);
}
}
//-----------------------------------------------------------------------------
TImageP TXshSimpleLevel::getFrame(const TFrameId &fid, UCHAR imFlags,
int subsampling) const {
assert(m_type != UNKNOWN_XSHLEVEL);
// If the required frame is not in range, quit
if (m_frames.count(fid) == 0) return TImageP();
const std::string &imgId = getImageId(fid);
ImageLoader::BuildExtData extData(this, fid, subsampling);
TImageP img = ImageManager::instance()->getImage(imgId, imFlags, &extData);
if (imFlags & ImageManager::toBeModified) {
// The image will be modified. Perform any related invalidation.
texture_utils::invalidateTexture(
this, fid); // We must rebuild associated textures
}
return img;
}
//-----------------------------------------------------------------------------
TImageInfo *TXshSimpleLevel::getFrameInfo(const TFrameId &fid,
bool toBeModified) {
assert(m_type != UNKNOWN_XSHLEVEL);
// If the required frame is not in range, quit
if (m_frames.count(fid) == 0) return 0;
const std::string &imgId = getImageId(fid);
TImageInfo *info = ImageManager::instance()->getInfo(
imgId, toBeModified ? ImageManager::toBeModified : ImageManager::none, 0);
return info;
}
//-----------------------------------------------------------------------------
TImageP TXshSimpleLevel::getFrameIcon(const TFrameId &fid) const {
assert(m_type != UNKNOWN_XSHLEVEL);
if (m_frames.count(fid) == 0) return TImageP();
// NOTE: Icons caching is DISABLED at this stage. It is now responsibility of
// ToonzQt's IconGenerator class.
ImageLoader::BuildExtData extData(this, fid);
extData.m_subs = 1, extData.m_icon = true;
const std::string &imgId = getImageId(fid);
TImageP img = ImageManager::instance()->getImage(
imgId, ImageManager::dontPutInCache, &extData);
TToonzImageP timg = (TToonzImageP)img;
if (timg && m_palette) timg->setPalette(m_palette);
return img;
}
//-----------------------------------------------------------------------------
// load icon (and image) data of all frames into cache
void TXshSimpleLevel::loadAllIconsAndPutInCache(bool cacheImagesAsWell) {
if (m_type != TZP_XSHLEVEL) return;
std::vector<TFrameId> fids;
getFids(fids);
std::vector<std::string> iconIds;
for (int i = 0; i < (int)fids.size(); i++) {
iconIds.push_back(getIconId(fids[i]));
}
ImageManager::instance()->loadAllTlvIconsAndPutInCache(this, fids, iconIds,
cacheImagesAsWell);
}
//-----------------------------------------------------------------------------
TRasterImageP TXshSimpleLevel::getFrameToCleanup(const TFrameId &fid) const {
assert(m_type != UNKNOWN_XSHLEVEL);
FramesSet::const_iterator ft = m_frames.find(fid);
if (ft == m_frames.end()) return TImageP();
bool flag = (m_scannedPath != TFilePath());
std::string imageId = getImageId(fid, flag ? Scanned : 0);
ImageLoader::BuildExtData extData(this, fid, 1);
TRasterImageP img = ImageManager::instance()->getImage(
imageId, ImageManager::dontPutInCache, &extData);
if (!img) return img;
double x_dpi, y_dpi;
img->getDpi(x_dpi, y_dpi);
if (!x_dpi && !y_dpi) {
TPointD dpi = m_properties->getDpi();
img->setDpi(dpi.x, dpi.y);
}
return img;
}
//-----------------------------------------------------------------------------
TImageP TXshSimpleLevel::getFullsampledFrame(const TFrameId &fid,
UCHAR imFlags) const {
assert(m_type != UNKNOWN_XSHLEVEL);
FramesSet::const_iterator it = m_frames.find(fid);
if (it == m_frames.end()) return TRasterImageP();
std::string imageId = getImageId(fid);
ImageLoader::BuildExtData extData(this, fid, 1);
TImageP img = ImageManager::instance()->getImage(imageId, imFlags, &extData);
if (imFlags & ImageManager::toBeModified) {
// The image will be modified. Perform any related invalidation.
texture_utils::invalidateTexture(
this, fid); // We must rebuild associated textures
}
return img;
}
//-----------------------------------------------------------------------------
std::string TXshSimpleLevel::getIconId(const TFrameId &fid,
int frameStatus) const {
return "icon:" + getImageId(fid, frameStatus);
}
//-----------------------------------------------------------------------------
std::string TXshSimpleLevel::getIconId(const TFrameId &fid,
const TDimension &size) const {
return getImageId(fid) + ":" + std::to_string(size.lx) + "x" +
std::to_string(size.ly);
}
//-----------------------------------------------------------------------------
namespace {
TAffine getAffine(const TDimension &srcSize, const TDimension &dstSize) {
double scx = 1 * dstSize.lx / (double)srcSize.lx;
double scy = 1 * dstSize.ly / (double)srcSize.ly;
double sc = std::min(scx, scy);
double dx = (dstSize.lx - srcSize.lx * sc) * 0.5;
double dy = (dstSize.ly - srcSize.ly * sc) * 0.5;
return TScale(sc) *
TTranslation(0.5 * TPointD(srcSize.lx, srcSize.ly) + TPointD(dx, dy));
}
//-----------------------------------------------------------------------------
/*!Costruisce l'icona di dimesione \b size dell'immagine \b img.*/
TImageP buildIcon(const TImageP &img, const TDimension &size) {
TRaster32P raster(size);
if (TVectorImageP vi = img) {
TOfflineGL *glContext = new TOfflineGL(size);
// TDimension cameraSize(768, 576);
TDimension cameraSize(1920, 1080);
TPalette *vPalette = img->getPalette();
assert(vPalette);
const TVectorRenderData rd(getAffine(cameraSize, size), TRect(), vPalette,
0, false);
glContext->clear(TPixel32::White);
glContext->draw(vi, rd);
raster->copy(glContext->getRaster());
delete glContext;
} else if (TToonzImageP ti = img) {
raster->fill(TPixel32(255, 255, 255, 255));
TRasterCM32P rasCM32 = ti->getRaster();
TRect bbox;
bbox = ti->getSavebox();
if (!bbox.isEmpty()) {
rasCM32 = rasCM32->extractT(bbox);
double sx = raster->getLx() / (double)rasCM32->getLx();
double sy = raster->getLy() / (double)rasCM32->getLy();
double sc = std::min(sx, sy);
TAffine aff =
TScale(sc).place(rasCM32->getCenterD(), raster->getCenterD());
TRop::resample(raster, rasCM32, ti->getPalette(), aff);
raster->lock();
for (int y = 0; y < raster->getLy(); y++) {
TPixel32 *pix = raster->pixels(y);
TPixel32 *endPix = pix + raster->getLx();
while (pix < endPix) {
*pix = overPix(TPixel32::White, *pix);
pix++;
}
}
raster->unlock();
}
} else {
TRasterImageP ri = img;
if (ri) {
ri->makeIcon(raster);
TRop::addBackground(raster, TPixel32::White);
} else
raster->fill(TPixel32(127, 50, 20));
}
return TRasterImageP(raster);
}
} // anonymous namespace
//-----------------------------------------------------------------------------
void TXshSimpleLevel::setFrame(const TFrameId &fid, const TImageP &img) {
assert(m_type != UNKNOWN_XSHLEVEL);
if (img) img->setPalette(getPalette());
m_frames.insert(fid);
TFilePath path = m_path;
int frameStatus = getFrameStatus(fid);
static const int SCANNED_OR_CLEANUPPED = (Scanned | Cleanupped);
if ((frameStatus & SCANNED_OR_CLEANUPPED) == Scanned) path = m_scannedPath;
// Deal with the ImageManger: ensure the identifiers are bound, and the
// associated image is either modified to img or (if !img) invalidated.
const std::string &imageId = getImageId(fid);
if (!ImageManager::instance()->isBound(imageId)) {
const TFilePath &decodedPath = getScene()->decodeFilePath(path);
ImageManager::instance()->bind(imageId, new ImageLoader(decodedPath, fid));
}
ImageManager::instance()->setImage(imageId, img); // Invalidates if !img
if (frameStatus == Normal) {
// Only a normal frame can have these. Justified since:
// a) PLIs have nothing to share with cleanup stuff
// b) The latter is used only in LineTest - which does not have Cleanup
if (m_type == PLI_XSHLEVEL) {
const std::string &imageId2 = rasterized(imageId);
if (!ImageManager::instance()->isBound(imageId2))
ImageManager::instance()->bind(imageId2, new ImageRasterizer);
else
ImageManager::instance()->invalidate(imageId2);
}
if (m_type == OVL_XSHLEVEL || m_type == TZI_XSHLEVEL) {
const std::string &imageId2 = filled(imageId);
if (!ImageManager::instance()->isBound(imageId2))
ImageManager::instance()->bind(imageId2, new ImageFiller);
else
ImageManager::instance()->invalidate(imageId2);
}
}
}
//-----------------------------------------------------------------------------
void TXshSimpleLevel::eraseFrame(const TFrameId &fid) {
FramesSet::iterator ft = m_frames.find(fid);
if (ft == m_frames.end()) return;
// Erase the corresponding entry in the renumber table
std::map<TFrameId, TFrameId>::iterator rt, rEnd(m_renumberTable.end());
for (rt = m_renumberTable.begin(); rt != rEnd; ++rt) {
if (rt->second == fid) {
m_renumberTable.erase(rt->first);
break;
}
}
m_frames.erase(ft);
getHookSet()->eraseFrame(fid);
ImageManager *im = ImageManager::instance();
{
im->unbind(getImageId(fid, Normal));
im->unbind(getImageId(fid, Scanned));
im->unbind(getImageId(fid, CleanupPreview));
if (m_type == PLI_XSHLEVEL) im->unbind(rasterized(getImageId(fid)));
if (m_type == OVL_XSHLEVEL || m_type == TZI_XSHLEVEL)
im->unbind(filled(getImageId(fid)));
texture_utils::invalidateTexture(this, fid);
}
}
//-----------------------------------------------------------------------------
void TXshSimpleLevel::clearFrames() {
ImageManager *im = ImageManager::instance();
// Unbind frames
FramesSet::iterator ft, fEnd = m_frames.end();
for (ft = m_frames.begin(); ft != fEnd; ++ft) {
im->unbind(getImageId(*ft, Scanned));
im->unbind(getImageId(*ft, Cleanupped));
im->unbind(getImageId(*ft, CleanupPreview));
if (m_type == PLI_XSHLEVEL) im->unbind(rasterized(getImageId(*ft)));
if (m_type == OVL_XSHLEVEL || m_type == TZI_XSHLEVEL)
im->unbind(filled(getImageId(*ft)));
texture_utils::invalidateTexture(this, *ft);
}
// Clear level
m_frames.clear();
m_editableRange.clear();
m_editableRangeUserInfo.clear();
m_renumberTable.clear();
m_framesStatus.clear();
}
//-----------------------------------------------------------------------------
void TXshSimpleLevel::loadData(TIStream &is) {
std::string tagName;
bool flag = false;
int type = UNKNOWN_XSHLEVEL;
for (;;) {
if (is.matchTag(tagName)) {
if (tagName == "path") {
is >> m_path;
is.matchEndTag();
} else if (tagName == "scannedPath") {
is >> m_scannedPath;
is.matchEndTag();
} else if (tagName == "info") {
std::string v;
double xdpi = 0, ydpi = 0;
int subsampling = 1;
int doPremultiply = 0;
int whiteTransp = 0;
int antialiasSoftness = 0;
LevelProperties::DpiPolicy dpiPolicy = LevelProperties::DP_ImageDpi;
if (is.getTagParam("dpix", v)) xdpi = std::stod(v);
if (is.getTagParam("dpiy", v)) ydpi = std::stod(v);
if (xdpi != 0 && ydpi != 0) dpiPolicy = LevelProperties::DP_CustomDpi;
std::string dpiType = is.getTagAttribute("dpiType");
if (dpiType == "image") dpiPolicy = LevelProperties::DP_ImageDpi;
if (is.getTagParam("type", v) && v == "s") type = TZI_XSHLEVEL;
if (is.getTagParam("subsampling", v)) subsampling = std::stoi(v);
if (is.getTagParam("premultiply", v)) doPremultiply = std::stoi(v);
if (is.getTagParam("antialias", v)) antialiasSoftness = std::stoi(v);
if (is.getTagParam("whiteTransp", v)) whiteTransp = std::stoi(v);
m_properties->setDpiPolicy(dpiPolicy);
m_properties->setDpi(TPointD(xdpi, ydpi));
m_properties->setSubsampling(subsampling);
m_properties->setDoPremultiply(doPremultiply);
m_properties->setDoAntialias(antialiasSoftness);
m_properties->setWhiteTransp(whiteTransp);
} else
throw TException("unexpected tag " + tagName);
} else {
if (flag) break; // ci puo' essere un solo nome
flag = true;
std::wstring token;
is >> token;
if (token == L"__empty") {
// empty = true;
is >> token;
}
if (token == L"_raster") // obsoleto (Tab2.2)
{
double xdpi = 1, ydpi = 1;
is >> xdpi >> ydpi >> m_name;
setName(m_name);
type = OVL_XSHLEVEL;
m_properties->setDpi(TPointD(xdpi, ydpi));
setType(type);
setPath(
TFilePath("+drawings/") + (getName() + L"." + ::to_wstring("bmp")),
true);
} else if (token == L"__raster") // obsoleto (Tab2.2)
{
double xdpi = 1, ydpi = 1;
std::string extension;
is >> xdpi >> ydpi >> m_name >> extension;
setName(m_name);
type = OVL_XSHLEVEL;
m_properties->setDpi(TPointD(xdpi, ydpi));
setType(type);
setPath(TFilePath("+drawings/") +
(getName() + L"." + ::to_wstring(extension)),
true);
} else {
m_name = token;
setName(m_name);
}
}
}
if (type == UNKNOWN_XSHLEVEL) {
std::string ext = m_path.getType();
if (ext == "pli" || ext == "svg")
type = PLI_XSHLEVEL;
else if (ext == "tlv" || ext == "tzu" || ext == "tzp" || ext == "tzl")
type = TZP_XSHLEVEL;
else if (ext == "tzi")
type = TZI_XSHLEVEL;
else if (ext == "mesh")
type = MESH_XSHLEVEL;
else
type = OVL_XSHLEVEL;
}
setType(type);
}
//-----------------------------------------------------------------------------
namespace {
class LoadingLevelRange {
public:
TFrameId m_fromFid, m_toFid;
LoadingLevelRange() : m_fromFid(1), m_toFid(0) {}
bool match(const TFrameId &fid) const {
/*-- ↓SubSequent範囲内にある条件 ↓全部ロードする場合 --*/
return m_fromFid <= fid && fid <= m_toFid || m_fromFid > m_toFid;
}
bool isEnabled() const { return m_fromFid <= m_toFid; }
void reset() {
m_fromFid = TFrameId(1);
m_toFid = TFrameId(0);
}
} loadingLevelRange;
//-----------------------------------------------------------------------------
} // namespace
//-----------------------------------------------------------------------------
void setLoadingLevelRange(const TFrameId &fromFid, const TFrameId &toFid) {
loadingLevelRange.m_fromFid = fromFid;
loadingLevelRange.m_toFid = toFid;
}
void getLoadingLevelRange(TFrameId &fromFid, TFrameId &toFid) {
fromFid = loadingLevelRange.m_fromFid;
toFid = loadingLevelRange.m_toFid;
}
static TFilePath getLevelPathAndSetNameWithPsdLevelName(TXshSimpleLevel *xshLevel) {
TFilePath retfp = xshLevel->getPath();
QString name = QString::fromStdWString(retfp.getWideName());
QStringList list = name.split("#");
if (list.size() >= 2 && list.at(1) != "frames") {
bool hasLayerId;
int layid = list.at(1).toInt(&hasLayerId);
QTextCodec *layerNameCodec = QTextCodec::codecForName(
Preferences::instance()->getLayerNameEncoding().c_str());
if (hasLayerId) {
// An explicit photoshop layer id must be converted to the associated
// level name
TPSDParser psdparser(xshLevel->getScene()->decodeFilePath(retfp));
std::string levelName = psdparser.getLevelNameWithCounter(
layid); // o_o what about UNICODE names??
list[1] = layerNameCodec->toUnicode(levelName.c_str());
std::wstring wLevelName = list.join("#").toStdWString();
retfp = retfp.withName(wLevelName);
TLevelSet *levelSet = xshLevel->getScene()->getLevelSet();
if (levelSet &&
levelSet->hasLevel(
wLevelName)) // levelSet should be asserted instead
levelSet->renameLevel(xshLevel, wLevelName);
xshLevel->setName(wLevelName);
}
}
return retfp;
}
//-----------------------------------------------------------------------------
// Nota: load() NON fa clearFrames(). si limita ad aggiungere le informazioni
// relative ai frames su disco
void TXshSimpleLevel::load() {
getProperties()->setCreator("");
QString creator;
assert(getScene());
if (!getScene()) return;
m_isSubsequence = loadingLevelRange.isEnabled();
TFilePath checkpath = getScene()->decodeFilePath(m_path);
std::string type = checkpath.getType();
if (m_scannedPath != TFilePath()) {
getProperties()->setDirtyFlag(
false); // Level is now supposedly loaded from disk
static const int ScannedCleanuppedMask = Scanned | Cleanupped;
TFilePath path = getScene()->decodeFilePath(m_scannedPath);
if (TSystem::doesExistFileOrLevel(path)) {
TLevelReaderP lr(path);
assert(lr);
TLevelP level = lr->loadInfo();
if (!checkCreatorString(creator = lr->getCreator()))
getProperties()->setIsForbidden(true);
else
for (TLevel::Iterator it = level->begin(); it != level->end(); it++) {
TFrameId fid = it->first;
if (!loadingLevelRange.match(fid)) continue;
setFrameStatus(
fid, (getFrameStatus(fid) & ~ScannedCleanuppedMask) | Scanned);
setFrame(fid, TImageP());
}
}
path = getScene()->decodeFilePath(m_path);
if (TSystem::doesExistFileOrLevel(path)) {
TLevelReaderP lr(path);
assert(lr);
TLevelP level = lr->loadInfo();
if (getType() & FULLCOLOR_TYPE)
setPalette(FullColorPalette::instance()->getPalette(getScene()));
else
setPalette(level->getPalette());
if (!checkCreatorString(creator = lr->getCreator()))
getProperties()->setIsForbidden(true);
else
for (TLevel::Iterator it = level->begin(); it != level->end(); it++) {
TFrameId fid = it->first;
if (!loadingLevelRange.match(fid)) continue;
setFrameStatus(fid, getFrameStatus(fid) | Cleanupped);
setFrame(fid, TImageP());
}
setContentHistory(
lr->getContentHistory() ? lr->getContentHistory()->clone() : 0);
}
} else {
// Not a scan + cleanup level
if (m_path.getType() == "psd" &&
this->getScene()->getVersionNumber().first < 71)
m_path = getLevelPathAndSetNameWithPsdLevelName(this);
TFilePath path = getScene()->decodeFilePath(m_path);
getProperties()->setDirtyFlag(
false); // Level is now supposedly loaded from disk
TLevelReaderP lr(path); // May throw
assert(lr);
TLevelP level = lr->loadInfo();
if (level->getFrameCount() > 0) {
const TImageInfo *info = lr->getImageInfo(level->begin()->first);
if (info && info->m_samplePerPixel >= 5) {
QString msg = QString(
"Failed to open %1.\nSamples per pixel is more than "
"4. It may containt more than one alpha channel.")
.arg(QString::fromStdWString(m_path.getWideString()));
QMessageBox::warning(0, "Image format not supported", msg);
return;
}
if (info) set16BitChannelLevel(info->m_bitsPerSample == 16);
}
if ((getType() & FULLCOLOR_TYPE) && !is16BitChannelLevel())
setPalette(FullColorPalette::instance()->getPalette(getScene()));
else
setPalette(level->getPalette());
if (!checkCreatorString(creator = lr->getCreator()))
getProperties()->setIsForbidden(true);
else
for (TLevel::Iterator it = level->begin(); it != level->end(); it++) {
m_renumberTable[it->first] = it->first; // Voglio che la tabella
// contenga anche i frame che
// non vengono caricati
if (!loadingLevelRange.match(it->first)) continue;
setFrame(it->first, TImageP());
}
setContentHistory(lr->getContentHistory() ? lr->getContentHistory()->clone()
: 0);
}
getProperties()->setCreator(creator.toStdString());
loadingLevelRange.reset();
if (getType() != PLI_XSHLEVEL) {
if (m_properties->getImageDpi() == TPointD() && !m_frames.empty()) {
TDimension imageRes(0, 0);
TPointD imageDpi;
const TFrameId &firstFid = getFirstFid();
std::string imageId = getImageId(firstFid);
const TImageInfo *imageInfo =
ImageManager::instance()->getInfo(imageId, ImageManager::none, 0);
if (imageInfo) {
imageRes.lx = imageInfo->m_lx;
imageRes.ly = imageInfo->m_ly;
imageDpi.x = imageInfo->m_dpix;
imageDpi.y = imageInfo->m_dpiy;
m_properties->setImageDpi(imageDpi);
m_properties->setImageRes(imageRes);
m_properties->setBpp(imageInfo->m_bitsPerSample *
imageInfo->m_samplePerPixel);
}
}
setRenumberTable();
}
if (getPalette() && StudioPalette::isEnabled())
StudioPalette::instance()->updateLinkedColors(getPalette());
TFilePath refImgName;
if (m_palette) {
refImgName = m_palette->getRefImgPath();
TFilePath refImgPath = refImgName;
if (refImgName != TFilePath() && TFileStatus(refImgPath).doesExist()) {
TLevelReaderP lr(refImgPath);
if (lr) {
TLevelP level = lr->loadInfo();
if (level->getFrameCount() > 0) {
TImageP img = lr->getFrameReader(level->begin()->first)->load();
if (img && getPalette()) {
img->setPalette(0);
getPalette()->setRefImg(img);
std::vector<TFrameId> fids;
for (TLevel::Iterator it = level->begin(); it != level->end(); ++it)
fids.push_back(it->first);
getPalette()->setRefLevelFids(fids);
}
}
}
}
}
// Load hooks
HookSet *hookSet = getHookSet();
hookSet->clearHooks();
const TFilePath &hookFile =
TXshSimpleLevel::getExistingHookFile(getScene()->decodeFilePath(m_path));
if (!hookFile.isEmpty()) {
TIStream is(hookFile);
std::string tagName;
try {
if (is.matchTag(tagName) && tagName == "hooks") hookSet->loadData(is);
} catch (...) {
}
}
updateReadOnly();
}
//-----------------------------------------------------------------------------
void TXshSimpleLevel::load(const std::vector<TFrameId> &fIds) {
getProperties()->setCreator("");
QString creator;
assert(getScene());
getProperties()->setDirtyFlag(false);
m_isSubsequence = loadingLevelRange.isEnabled();
// non e' un livello scan+cleanup
TFilePath path = getScene()->decodeFilePath(m_path);
TLevelReaderP lr(path);
assert(lr);
if (!checkCreatorString(creator = lr->getCreator()))
getProperties()->setIsForbidden(true);
else {
if (fIds.size() != 0) {
for (int i = 0; i < (int)fIds.size(); i++) {
m_renumberTable[fIds[i]] = fIds[i];
if (!loadingLevelRange.match(fIds[i])) continue;
setFrame(fIds[i], TImageP());
}
} else {
TLevelP level = lr->loadInfo();
for (TLevel::Iterator it = level->begin(); it != level->end(); it++) {
m_renumberTable[it->first] = it->first;
if (!loadingLevelRange.match(it->first)) continue;
setFrame(it->first, TImageP());
}
}
}
setContentHistory(lr->getContentHistory() ? lr->getContentHistory()->clone()
: 0);
getProperties()->setCreator(creator.toStdString());
loadingLevelRange.reset();
if (getType() != PLI_XSHLEVEL) {
if (m_properties->getImageDpi() == TPointD() && !m_frames.empty()) {
TDimension imageRes(0, 0);
TPointD imageDpi;
std::string imageId = getImageId(getFirstFid());
const TImageInfo *imageInfo =
ImageManager::instance()->getInfo(imageId, ImageManager::none, 0);
if (imageInfo) {
imageRes.lx = imageInfo->m_lx;
imageRes.ly = imageInfo->m_ly;
imageDpi.x = imageInfo->m_dpix;
imageDpi.y = imageInfo->m_dpiy;
m_properties->setImageDpi(imageDpi);
m_properties->setImageRes(imageRes);
}
}
setRenumberTable();
}
}
//-----------------------------------------------------------------------------
void TXshSimpleLevel::updateReadOnly() {
TFilePath path = getScene()->decodeFilePath(m_path);
m_isReadOnly = isAreadOnlyLevel(path);
}
//-----------------------------------------------------------------------------
void TXshSimpleLevel::saveData(TOStream &os) {
os << m_name;
std::map<std::string, std::string> attr;
if (getProperties()->getDpiPolicy() == LevelProperties::DP_CustomDpi) {
TPointD dpi = getProperties()->getDpi();
if (dpi.x != 0 && dpi.y != 0) {
attr["dpix"] = std::to_string(dpi.x);
attr["dpiy"] = std::to_string(dpi.y);
}
} else {
attr["dpiType"] = "image";
}
if (getProperties()->getSubsampling() != 1) {
attr["subsampling"] = std::to_string(getProperties()->getSubsampling());
}
if (getProperties()->antialiasSoftness() > 0) {
attr["antialias"] = std::to_string(getProperties()->antialiasSoftness());
}
if (getProperties()->doPremultiply()) {
attr["premultiply"] = std::to_string(getProperties()->doPremultiply());
} else if (getProperties()->whiteTransp()) {
attr["whiteTransp"] = std::to_string(getProperties()->whiteTransp());
}
if (m_type == TZI_XSHLEVEL) attr["type"] = "s";
os.openCloseChild("info", attr);
os.child("path") << m_path; // fp;
if (m_scannedPath != TFilePath())
os.child("scannedPath") << m_scannedPath; // fp;
}
//-----------------------------------------------------------------------------
void TXshSimpleLevel::save() {
assert(getScene());
TFilePath path = getScene()->decodeFilePath(m_path);
TSystem::outputDebug("save() : " + ::to_string(m_path) + " = " +
::to_string(path) + "\n");
if (getProperties()->getDirtyFlag() == false &&
getPalette()->getDirtyFlag() == false &&
TSystem::doesExistFileOrLevel(path))
return;
if (!TFileStatus(path.getParentDir()).doesExist()) {
try {
TSystem::mkDir(path.getParentDir());
} catch (...) {
}
}
save(path);
}
//-----------------------------------------------------------------------------
static void saveBackup(TFilePath path) {
try {
TFilePath backup = path.withName(path.getName() + "_backup");
if (TSystem::doesExistFileOrLevel(backup))
TSystem::removeFileOrLevel_throw(backup);
TSystem::copyFileOrLevel_throw(backup, path);
} catch (...) {
}
}
//-----------------------------------------------------------------------------
void TXshSimpleLevel::save(const TFilePath &fp, const TFilePath &oldFp,
bool overwritePalette) {
TFilePath dOldPath =
(!oldFp.isEmpty()) ? oldFp : getScene()->decodeFilePath(m_path);
TFilePath dDstPath = getScene()->decodeFilePath(fp);
TSystem::touchParentDir(dDstPath);
// backup
if (Preferences::instance()->isLevelsBackupEnabled() &&
dOldPath == dDstPath && TSystem::doesExistFileOrLevel(dDstPath))
saveBackup(dDstPath);
if (isAreadOnlyLevel(dDstPath)) {
if (m_editableRange.empty() &&
!m_temporaryHookMerged) // file interaly locked
throw TSystemException(
dDstPath, "The level cannot be saved: it is a read only level.");
else if (getType() != OVL_XSHLEVEL) {
// file partially unlocked
std::wstring fileName = getEditableFileName();
assert(!fileName.empty());
TFilePath app = dDstPath.withName(fileName).withType(dDstPath.getType());
// removes old files
if (TSystem::doesExistFileOrLevel(app)) TSystem::removeFileOrLevel(app);
TFilePathSet oldFilePaths;
getFiles(app, oldFilePaths);
TFilePathSet::iterator it;
for (it = oldFilePaths.begin(); it != oldFilePaths.end(); ++it) {
if (TSystem::doesExistFileOrLevel(*it)) TSystem::removeFileOrLevel(*it);
}
// save new files
TXshSimpleLevel *sl = new TXshSimpleLevel;
sl->setScene(getScene());
sl->setPalette(getPalette());
sl->setPath(getScene()->codeFilePath(app));
sl->setType(getType());
std::set<TFrameId>::iterator eft, efEnd;
for (eft = m_editableRange.begin(); eft != efEnd; ++eft) {
const TFrameId &fid = *eft;
sl->setFrame(fid, getFrame(fid, false));
}
// Copy hooks
HookSet *hookSet = sl->getHookSet();
*hookSet = *getHookSet();
FramesSet::iterator ft, fEnd = m_frames.end();
for (ft = m_frames.begin(); ft != fEnd; ++ft) {
const TFrameId &fid = *ft;
if (m_editableRange.find(fid) == m_editableRange.end())
hookSet->eraseFrame(fid);
}
// Copy mesh level
sl->save(app);
#ifdef _WIN32
// hides files
oldFilePaths.clear();
if (TSystem::doesExistFileOrLevel(app)) TSystem::hideFileOrLevel(app);
getFiles(app, oldFilePaths);
for (it = oldFilePaths.begin(); it != oldFilePaths.end(); ++it) {
if (TSystem::doesExistFileOrLevel(*it)) TSystem::hideFileOrLevel(*it);
}
#endif
return;
}
}
if (dOldPath != dDstPath && m_path != TFilePath()) {
const TFilePath &dSrcPath = dOldPath;
try {
if (TSystem::doesExistFileOrLevel(dSrcPath)) {
if (TSystem::doesExistFileOrLevel(dDstPath))
TSystem::removeFileOrLevel(dDstPath);
copyFiles(dDstPath, dSrcPath);
}
} catch (...) {
}
}
// when saving the level palette with global name
if (overwritePalette && getType() == TZP_XSHLEVEL && getPalette() &&
getPalette()->getGlobalName() != L"") {
overwritePalette = false;
TFilePath palettePath = dDstPath.withNoFrame().withType("tpl");
StudioPalette::instance()->save(palettePath, getPalette());
}
saveSimpleLevel(dDstPath, overwritePalette);
}
//-----------------------------------------------------------------------------
void TXshSimpleLevel::saveSimpleLevel(const TFilePath &decodedFp,
bool overwritePalette) {
/* Precondition: Destination level with path decodedFp is supposed to already
store those image frames not tagged as 'modified' in this level
instance. */
TFilePath oldPath = m_path;
TFilePath dOldPath = getScene()->decodeFilePath(oldPath);
// Substitute m_path with decodedFp until the function quits.
struct CopyOnExit {
TFilePath &m_dstPath, &m_srcPath;
~CopyOnExit() { m_dstPath = m_srcPath; }
} copyOnExit = {m_path = decodedFp,
oldPath}; // m_path substituted here until function quits
bool savingOriginal = (decodedFp == dOldPath), paletteNotSaved = false;
int imFlags = savingOriginal
? ImageManager::dontPutInCache | ImageManager::toBeSaved
: ImageManager::dontPutInCache;
std::vector<TFrameId> fids;
getFids(fids);
bool isLevelModified = getProperties()->getDirtyFlag();
bool isPaletteModified = false;
if (getPalette()) isPaletteModified = getPalette()->getDirtyFlag();
if (isLevelModified || isPaletteModified) {
// gmt (8/8/08. provo a risolvere il pasticcio della scrittura dei tlv.
// Dobbiamo
// ripensarci con piu' calma. Per ora cerco di fare meno danno possibile).
TDimension oldRes(0, 0);
if (TSystem::doesExistFileOrLevel(decodedFp)) {
TLevelReaderP lr(decodedFp);
const TImageInfo *imageInfo = m_frames.empty()
? lr->getImageInfo()
: lr->getImageInfo(*(m_frames.begin()));
if (imageInfo) {
oldRes.lx = imageInfo->m_lx;
oldRes.ly = imageInfo->m_ly;
lr = TLevelReaderP();
if (getProperties()->getImageRes() != oldRes) {
// Il comando canvas size cambia le dimensioni del livello!!!
// Se il file già esiste, nel level writer vengono risettate le
// dimesnioni del file esistente
// e salva male
TSystem::removeFileOrLevel(decodedFp);
}
}
}
// overwrite tlv
if (decodedFp.getType() == "tlv" &&
TSystem::doesExistFileOrLevel(decodedFp)) {
if (isLevelModified) {
// in questo caso dovrei scrivere solo i frame modificati.
// certamente NON DEVO scrivere quelli che non ho (e che dovrei
// rileggere dallo stesso file che sto scrivendo
int oldSubs = getProperties()->getSubsampling();
TLevelWriterP lw;
try {
lw = TLevelWriterP(decodedFp);
} catch (...) {
// revert subsampling
m_properties->setSubsampling(oldSubs);
m_path = oldPath;
throw TSystemException(decodedFp,
"can't fopen.\nSomeone may be saving the same "
"file. Please wait a while and retry.");
}
lw->setOverwritePaletteFlag(overwritePalette);
lw->setCreator(getCreatorString());
lw->setPalette(getPalette());
// Filter out of the renumber table all non-tlv frames (could happen if
// the level
// is a scan-cleanup mix). This is fine even on the temporarily
// substituted m_path.
std::map<TFrameId, TFrameId> renumberTable;
for (auto it = m_renumberTable.rbegin(); it != m_renumberTable.rend();
++it) {
TFrameId id = (*it).first;
if ((getFrameStatus(id) != Scanned) &&
(getFrameStatus(id) != CleanupPreview)) {
renumberTable[id] = (*it).second;
}
}
m_renumberTable.clear();
m_renumberTable = renumberTable;
lw->setIconSize(Preferences::instance()->getIconSize());
if (!isSubsequence()) lw->renumberFids(m_renumberTable);
if (getContentHistory())
lw->setContentHistory(getContentHistory()->clone());
ImageLoader::BuildExtData extData(this, TFrameId());
for (auto const &fid : fids) {
std::string imageId = getImageId(
fid, Normal); // Retrieve the actual level frames ("L_whatever")
if (!ImageManager::instance()->isModified(imageId)) continue;
extData.m_fid = fid;
TImageP img =
ImageManager::instance()->getImage(imageId, imFlags, &extData);
assert(img);
if (!img) continue;
int subs = 1;
if (TToonzImageP ti = img)
subs = ti->getSubsampling();
else if (TRasterImageP ri = img)
subs = ri->getSubsampling();
assert(subs == 1);
if (subs != 1) continue;
if (TToonzImageP ti = img) {
/*-
* SaveBoxを塗り漏れ防止に使用している場合、実際の画像範囲とSaveBoxのサイズが異なるため、ここで更新しておく-*/
TRect saveBox;
TRop::computeBBox(ti->getRaster(), saveBox);
ti->setSavebox(saveBox);
}
lw->getFrameWriter(fid)->save(img);
}
lw = TLevelWriterP(); // TLevelWriterP's destructor saves the palette
} else if (isPaletteModified && overwritePalette) {
TFilePath palettePath = decodedFp.withNoFrame().withType("tpl");
if (Preferences::instance()->isLevelsBackupEnabled() &&
TSystem::doesExistFileOrLevel(palettePath))
saveBackup(palettePath);
TOStream os(palettePath);
if (os.checkStatus())
os << getPalette();
else
paletteNotSaved = true;
}
} else {
// per ora faccio quello che facevo prima, ma dobbiamo rivedere tutta la
// strategia
LevelUpdater updater(this);
updater.getLevelWriter()->setCreator(getCreatorString());
if (isLevelModified) {
// Apply the level's renumber table, before saving other files.
// NOTE: This is currently NOT under LevelUpdater's responsibility, as
// renumber tables
// are set/manipulated heavily here. The approach should be re-designed,
// though...
updater.getLevelWriter()->renumberFids(m_renumberTable);
if (!m_editableRange.empty())
fids = std::vector<TFrameId>(m_editableRange.begin(),
m_editableRange.end());
ImageLoader::BuildExtData extData(this, TFrameId());
for (auto const &fid : fids) {
std::string imageId = getImageId(
fid, Normal); // Retrieve the actual level frames ("L_whatever")
if (!ImageManager::instance()->isModified(imageId)) continue;
extData.m_fid = fid;
TImageP img =
ImageManager::instance()->getImage(imageId, imFlags, &extData);
assert(img);
if (!img) continue;
int subs = 1;
if (TToonzImageP ti = img)
subs = ti->getSubsampling();
else if (TRasterImageP ri = img)
subs = ri->getSubsampling();
assert(subs == 1);
if (subs != 1) continue;
updater.update(fid, img);
}
}
updater.close(); // Needs the original level subs
if ((getType() & FULLCOLOR_TYPE) && isPaletteModified)
FullColorPalette::instance()->savePalette(getScene());
}
}
// Save hooks
TFilePath hookFile;
HookSet *hookSet = 0;
// Save the hookSet in a temporary hook file
if (getType() == OVL_XSHLEVEL && !m_editableRange.empty()) {
hookSet = new HookSet(*getHookSet());
FramesSet::const_iterator it;
for (it = m_frames.begin(); it != m_frames.end(); ++it) {
TFrameId fid = *it;
if (m_editableRange.find(fid) == m_editableRange.end())
hookSet->eraseFrame(fid);
}
// file partially unlocked
std::wstring fileName = getEditableFileName();
assert(!fileName.empty());
TFilePath app = decodedFp.withName(fileName).withType(decodedFp.getType());
hookFile = getHookPath(app);
} else {
hookFile = getHookPath(decodedFp);
hookSet = getHookSet();
}
#ifdef _WIN32
// Remove the hidden attribute (since TOStream's fopen fails on hidden files)
if (getType() == OVL_XSHLEVEL && !m_editableRange.empty())
SetFileAttributesW(hookFile.getWideString().c_str(), FILE_ATTRIBUTE_NORMAL);
#endif
if (hookSet && hookSet->getHookCount() > 0) {
TOStream os(hookFile);
os.openChild("hooks");
hookSet->saveData(os);
os.closeChild();
} else if (TFileStatus(hookFile).doesExist()) {
try {
TSystem::deleteFile(hookFile);
} catch (...) {
}
}
#ifdef _WIN32
if (getType() == OVL_XSHLEVEL && !m_editableRange.empty())
TSystem::hideFileOrLevel(hookFile);
#endif
if (savingOriginal) {
setRenumberTable(); // Since the renumber table refers to the
// 'original' frames saved on disk
if (m_properties) m_properties->setDirtyFlag(false);
if (getPalette() && overwritePalette) getPalette()->setDirtyFlag(false);
}
if (paletteNotSaved)
throw TSystemException(m_path,
"The palette of the level could not be saved.");
}
//-----------------------------------------------------------------------------
std::string TXshSimpleLevel::getImageId(const TFrameId &fid,
int frameStatus) const {
if (frameStatus < 0) frameStatus = getFrameStatus(fid);
std::string prefix = "L";
if (frameStatus & CleanupPreview)
prefix = "P";
else if ((frameStatus & (Scanned | Cleanupped)) == Scanned)
prefix = "S";
std::string imageId = m_idBase + "_" + prefix + fid.expand();
return imageId;
}
//-----------------------------------------------------------------------------
int TXshSimpleLevel::getFrameStatus(const TFrameId &fid) const {
std::map<TFrameId, int>::const_iterator it = m_framesStatus.find(fid);
return (it != m_framesStatus.end()) ? it->second : Normal;
}
//-----------------------------------------------------------------------------
void TXshSimpleLevel::setFrameStatus(const TFrameId &fid, int status) {
assert((status & ~(Scanned | Cleanupped | CleanupPreview)) == 0);
m_framesStatus[fid] = status;
}
//-----------------------------------------------------------------------------
/*- CleanupPopup::setCurrentLevel / TCleanupper で使用 -*/
void TXshSimpleLevel::makeTlv(const TFilePath &tlvPath) {
int ltype = getType();
if (!(ltype & FULLCOLOR_TYPE)) {
assert(ltype & FULLCOLOR_TYPE);
return;
}
setType(TZP_XSHLEVEL);
m_scannedPath = m_path;
assert(tlvPath.getType() == "tlv");
m_path = tlvPath;
FramesSet::const_iterator it;
for (it = m_frames.begin(); it != m_frames.end(); ++it) {
TFrameId fid = *it;
setFrameStatus(fid, Scanned);
ImageManager::instance()->rebind(getImageId(fid, Scanned),
getImageId(fid, 0));
ImageManager::instance()->rebind(getIconId(fid, Scanned),
getIconId(fid, 0));
}
}
//-----------------------------------------------------------------------------
void TXshSimpleLevel::invalidateFrames() {
FramesSet::iterator ft, fEnd = m_frames.end();
for (ft = m_frames.begin(); ft != fEnd; ++ft)
ImageManager::instance()->invalidate(getImageId(*ft));
}
//-----------------------------------------------------------------------------
/*- 指定したFIdのみInvalidateする -*/
void TXshSimpleLevel::invalidateFrame(const TFrameId &fid) {
std::string id = getImageId(fid);
ImageManager::instance()->invalidate(id);
}
//-----------------------------------------------------------------------------
// crea un frame con tipo, dimensioni, dpi, ecc. compatibili con il livello
TImageP TXshSimpleLevel::createEmptyFrame() {
TImageP result;
switch (m_type) {
case PLI_XSHLEVEL:
result = new TVectorImage;
break;
case MESH_XSHLEVEL:
assert(false); // Not implemented yet
break;
default: {
// normally the image must have the level->getProperties()->getImageDpi().
// if this value is missing (for some reason - can this happen, ever?) then
// we use the getDpi() (that is the current dpi, e.g. cameraDpi or
// customDpi).
TPointD dpi = getProperties()->getImageDpi();
/*--
tgaからtlvにconvertしたものをInsert Pasteしたとき、
ペーストしたフレームにのみDPIが付いてしまうので、この処理は省く
--*/
// if(dpi.x==0.0 || dpi.y==0.0)
// dpi = getProperties()->getDpi();
TDimension res = getProperties()->getImageRes();
if (m_type == TZP_XSHLEVEL) {
TRasterCM32P raster(res);
raster->fill(TPixelCM32());
TToonzImageP ti(raster, TRect());
ti->setDpi(dpi.x, dpi.y);
ti->setSavebox(TRect(0, 0, res.lx - 1, res.ly - 1));
result = ti;
} else {
TRaster32P raster(res);
raster->fill(TPixel32(0, 0, 0, 0));
TRasterImageP ri(raster);
ri->setDpi(dpi.x, dpi.y);
result = ri;
}
break;
}
}
return result;
}
//-----------------------------------------------------------------------------
// ritorna la risoluzione dei frames del livello (se il livello non e'
// vettoriale)
TDimension TXshSimpleLevel::getResolution() {
if (isEmpty() || getType() == PLI_XSHLEVEL) return TDimension();
return m_properties->getImageRes();
}
//-----------------------------------------------------------------------------
// ritorna il dpi letto da file
TPointD TXshSimpleLevel::getImageDpi(const TFrameId &fid, int frameStatus) {
if (isEmpty() || getType() == PLI_XSHLEVEL) return TPointD();
const TFrameId &theFid =
(fid == TFrameId::NO_FRAME || !isFid(fid)) ? getFirstFid() : fid;
const std::string &imageId = getImageId(theFid, frameStatus);
const TImageInfo *imageInfo =
ImageManager::instance()->getInfo(imageId, ImageManager::none, 0);
if (!imageInfo) return TPointD();
return TPointD(imageInfo->m_dpix, imageInfo->m_dpiy);
}
//-----------------------------------------------------------------------------
int TXshSimpleLevel::getImageSubsampling(const TFrameId &fid) const {
if (isEmpty() || getType() == PLI_XSHLEVEL) return 1;
TImageP img = TImageCache::instance()->get(getImageId(fid), false);
if (!img) return 1;
if (TRasterImageP ri = img) return ri->getSubsampling();
if (TToonzImageP ti = img) return ti->getSubsampling();
return 1;
}
//-----------------------------------------------------------------------------
// ritorna il dpi corrente del livello
TPointD TXshSimpleLevel::getDpi(const TFrameId &fid, int frameStatus) {
TPointD dpi;
if (m_properties->getDpiPolicy() == LevelProperties::DP_ImageDpi)
dpi = getImageDpi(fid, frameStatus);
else
dpi = m_properties->getDpi();
return dpi;
}
//-----------------------------------------------------------------------------
void TXshSimpleLevel::renumber(const std::vector<TFrameId> &fids) {
assert(fids.size() == m_frames.size());
int n = fids.size();
int i = 0;
std::vector<TFrameId> oldFids;
getFids(oldFids);
std::map<TFrameId, TFrameId> table;
std::map<TFrameId, TFrameId> newRenumberTable;
for (std::vector<TFrameId>::iterator it = oldFids.begin();
it != oldFids.end(); ++it) {
TFrameId oldFrameId = *it;
TFrameId newFrameId = fids[i++];
table[oldFrameId] = newFrameId;
for (auto const &renumber : m_renumberTable) {
if (renumber.second == oldFrameId) {
newRenumberTable[renumber.first] = newFrameId;
break;
}
}
}
for (auto const &renumber : newRenumberTable) {
m_renumberTable[renumber.first] = renumber.second;
}
m_frames.clear();
for (i = 0; i < n; ++i) {
TFrameId fid(fids[i]);
assert(m_frames.count(fid) == 0);
m_frames.insert(fid);
}
ImageManager *im = ImageManager::instance();
std::map<TFrameId, TFrameId>::iterator jt;
{
for (i = 0, jt = table.begin(); jt != table.end(); ++jt, ++i)
im->rebind(getImageId(jt->first), "^" + std::to_string(i));
for (i = 0, jt = table.begin(); jt != table.end(); ++jt, ++i)
im->rebind("^" + std::to_string(i), getImageId(jt->second));
}
if (getType() == PLI_XSHLEVEL) {
for (i = 0, jt = table.begin(); jt != table.end(); ++jt, ++i) {
const std::string &id = rasterized(getImageId(jt->first));
if (im->isBound(id)) im->rebind(id, rasterized("^" + std::to_string(i)));
}
for (i = 0, jt = table.begin(); jt != table.end(); ++jt, ++i) {
const std::string &id = rasterized("^" + std::to_string(i));
if (im->isBound(id)) im->rebind(id, rasterized(getImageId(jt->second)));
}
}
if (getType() & FULLCOLOR_TYPE) {
for (i = 0, jt = table.begin(); jt != table.end(); ++jt, ++i) {
const std::string &id = filled(getImageId(jt->first));
if (im->isBound(id)) im->rebind(id, filled("^" + std::to_string(i)));
}
for (i = 0, jt = table.begin(); jt != table.end(); ++jt, ++i) {
const std::string &id = filled("^" + std::to_string(i));
if (im->isBound(id)) im->rebind(id, filled(getImageId(jt->second)));
}
}
m_properties->setDirtyFlag(true);
if (getHookSet()) getHookSet()->renumber(table);
}
//-----------------------------------------------------------------------------
void TXshSimpleLevel::copyFiles(const TFilePath &dst, const TFilePath &src) {
if (dst == src) return;
TSystem::touchParentDir(dst);
TSystem::copyFileOrLevel_throw(dst, src);
if (dst.getType() == "tlv") {
// Copio la palette del livello
TFilePath srcPltPath =
src.getParentDir() + TFilePath(src.getWideName() + L".tpl");
if (TFileStatus(srcPltPath).doesExist())
TSystem::copyFile(
dst.getParentDir() + TFilePath(dst.getWideName() + L".tpl"),
srcPltPath, true);
}
if (dst.getType() == "tzp" || dst.getType() == "tzu") {
// Copio la palette del livello
TFilePath srcPltPath =
src.getParentDir() + TFilePath(src.getWideName() + L".plt");
if (TFileStatus(srcPltPath).doesExist())
TSystem::copyFile(
dst.getParentDir() + TFilePath(dst.getWideName() + L".plt"),
srcPltPath, true);
}
const TFilePath &srcHookFile = TXshSimpleLevel::getExistingHookFile(src);
if (!srcHookFile.isEmpty()) {
const TFilePath &dstHookFile = getHookPath(dst);
TSystem::copyFile(dstHookFile, srcHookFile, true);
}
TFilePath files = src.getParentDir() + (src.getName() + "_files");
if (TFileStatus(files).doesExist() && TFileStatus(files).isDirectory())
TSystem::copyDir(dst.getParentDir() + (src.getName() + "_files"), files);
}
//-----------------------------------------------------------------------------
void TXshSimpleLevel::renameFiles(const TFilePath &dst, const TFilePath &src) {
if (dst == src) return;
TSystem::touchParentDir(dst);
if (TSystem::doesExistFileOrLevel(dst)) TXshSimpleLevel::removeFiles(dst);
TSystem::renameFileOrLevel_throw(dst, src);
if (dst.getType() == "tlv")
TSystem::renameFile(dst.withType("tpl"), src.withType("tpl"));
const TFilePath &srcHookFile = TXshSimpleLevel::getExistingHookFile(src);
if (!srcHookFile.isEmpty()) {
const TFilePath &dstHookFile = getHookPath(dst);
TSystem::renameFile(dstHookFile, srcHookFile);
}
TFilePath files = src.getParentDir() + (src.getName() + "_files");
if (TFileStatus(files).doesExist() && TFileStatus(files).isDirectory())
TSystem::renameFile(dst.getParentDir() + (dst.getName() + "_files"), files);
}
//-----------------------------------------------------------------------------
void TXshSimpleLevel::removeFiles(const TFilePath &fp) {
TSystem::moveFileOrLevelToRecycleBin(fp);
if (fp.getType() == "tlv") {
TFilePath tpl = fp.withType("tpl");
if (TFileStatus(tpl).doesExist()) TSystem::moveFileToRecycleBin(tpl);
}
// Delete ALL hook files (ie from every Toonz version)
const QStringList &hookFiles = TXshSimpleLevel::getHookFiles(fp);
int f, fCount = hookFiles.size();
for (f = 0; f != fCount; ++f)
TSystem::moveFileToRecycleBin(TFilePath(hookFiles[f].toStdWString()));
TFilePath files = fp.getParentDir() + (fp.getName() + "_files");
if (TFileStatus(files).doesExist() && TFileStatus(files).isDirectory())
TSystem::rmDirTree(files);
}
//-----------------------------------------------------------------------------
void TXshSimpleLevel::getFiles(const TFilePath &fp, TFilePathSet &fpset) {
if (fp.getType() == "tlv") {
TFilePath tpl = fp.withType("tpl");
if (TFileStatus(tpl).doesExist()) fpset.push_back(tpl);
}
// Store the hooks file if any (NOTE: there could be more than one hooks
// file. I'm retaining the behavior I've seen, but was this really intended?
// Shouldn't we return ALL the hooks files?)
const TFilePath &hookFile = getExistingHookFile(fp);
if (!hookFile.isEmpty()) fpset.push_back(hookFile);
// Needed for TAB Manga & Kids and not used in Toonz
// TFilePath files = fp.getParentDir() + (fp.getName() + "_files");
// if(TFileStatus(files).doesExist() && TFileStatus(files).isDirectory())
// TSystem::rmDirTree(files);
}
//-----------------------------------------------------------------------------
void TXshSimpleLevel::setContentHistory(TContentHistory *contentHistory) {
if (contentHistory != m_contentHistory.get())
m_contentHistory.reset(contentHistory);
}
//-----------------------------------------------------------------------------
void TXshSimpleLevel::setCompatibilityMasks(int writeMask, int neededMask,
int forbiddenMask) {
compatibility.writeMask = writeMask;
compatibility.neededMask = neededMask;
compatibility.forbiddenMask = forbiddenMask;
}
//-----------------------------------------------------------------------------
TFilePath TXshSimpleLevel::getHookPath(const TFilePath &path) {
// Translates: levelName..ext into levelName_hooks..ext.xml
// levelName.ext into levelName_hooks.ext.xml
// Observe that retaining the original level extension IS IMPORTANT, as it
// ensures
// the UNICITY of the association between a level path and its hook path (ie
// levels test..png and test..tif have separate hook files).
return TFilePath(path.withName(path.getName() + "_hooks").getWideString() +
L".xml");
}
//-----------------------------------------------------------------------------
QStringList TXshSimpleLevel::getHookFiles(const TFilePath &decodedLevelPath) {
const TFilePath &dirPath = decodedLevelPath.getParentDir();
QDir levelDir(QString::fromStdWString(dirPath.getWideString()));
QStringList hookFileFilter(
QString::fromStdWString( // We have to scan for files of the
decodedLevelPath.getWideName() +
L"_hooks*.xml")); // form levelName_hooks*.xml to
// retain backward compatibility
return levelDir.entryList( //
hookFileFilter,
QDir::Files | QDir::NoDotAndDotDot, // Observe that we cleverly sort by
QDir::Time); // mod date :)
}
//-----------------------------------------------------------------------------
TFilePath TXshSimpleLevel::getExistingHookFile(
const TFilePath &decodedLevelPath) {
static const int pCount = 3;
static const QRegExp pattern[pCount] = {
// Prioritized in this order
QRegExp(".*\\.\\.?.+\\.xml$"), // whatever.(.)ext.xml
QRegExp(".*\\.xml$"), // whatever.xml
QRegExp(".*\\.\\.?xml$") // whatever.(.)xml
};
struct locals {
static inline int getPattern(const QString &fp) {
for (int p = 0; p != pCount; ++p)
if (pattern[p].exactMatch(fp)) return p;
return -1;
}
}; // locals
const QStringList &hookFiles = getHookFiles(decodedLevelPath);
if (hookFiles.empty()) return TFilePath();
// Return the hook file with the most recent (smallest) identified
// regexp pattern
int fPattern, p = pCount, h = -1;
int f, fCount = hookFiles.size();
for (f = 0; f != fCount; ++f) {
fPattern = locals::getPattern(hookFiles[f]);
if (fPattern < p) p = fPattern, h = f;
}
assert(h >= 0);
return (h < 0) ? TFilePath()
: decodedLevelPath.getParentDir() +
TFilePath(hookFiles[h].toStdWString());
}
//-----------------------------------------------------------------------------
TRectD TXshSimpleLevel::getBBox(const TFrameId &fid) const {
TRectD bbox;
double dpiX = Stage::inch, dpiY = dpiX;
// Get the frame bbox in image coordinates
switch (getType()) {
case PLI_XSHLEVEL:
case MESH_XSHLEVEL: {
// Load the image and extract its bbox forcibly
TImageP img = getFrame(fid, false);
if (!img) return TRectD();
bbox = img->getBBox();
if (TMeshImageP mi = img) mi->getDpi(dpiX, dpiY);
break;
}
default: {
// Raster case: retrieve the image info from the ImageManager
const std::string &imageId = getImageId(fid);
const TImageInfo *info =
ImageManager::instance()->getInfo(imageId, ImageManager::none, 0);
if (!info) return TRectD();
bbox = TRectD(TPointD(info->m_x0, info->m_y0),
TDimensionD(info->m_lx,
info->m_ly)) - // Using lx, ly is less ambiguous
0.5 * TPointD(info->m_lx, info->m_ly);
if (info->m_dpix > 0.0 && info->m_dpiy > 0.0)
dpiX = info->m_dpix, dpiY = info->m_dpiy;
break;
}
}
// Get the frame's dpi and traduce the bbox to inch coordinates
return TScale(1.0 / dpiX, 1.0 / dpiY) * bbox;
}