1602 lines
53 KiB
C++
1602 lines
53 KiB
C++
|
||
|
||
#include "cleanuppopup.h"
|
||
|
||
// Toonz includes
|
||
#include "tapp.h"
|
||
#include "imageviewer.h"
|
||
#include "cleanupsettingsmodel.h"
|
||
#include "cellselection.h"
|
||
#include "columnselection.h"
|
||
#include "mainwindow.h"
|
||
|
||
// ToonzQt includes
|
||
#include "toonzqt/gutil.h"
|
||
#include "toonzqt/dvdialog.h"
|
||
#include "toonzqt/lineedit.h"
|
||
#include "toonzqt/menubarcommand.h"
|
||
#include "toonzqt/icongenerator.h"
|
||
|
||
// ToonzLib includes
|
||
#include "toonz/toonzscene.h"
|
||
#include "toonz/txshcell.h"
|
||
#include "toonz/txshsimplelevel.h"
|
||
#include "toonz/txshleveltypes.h"
|
||
#include "toonz/levelproperties.h"
|
||
#include "toonz/imagemanager.h"
|
||
#include "toonz/levelupdater.h"
|
||
#include "toonz/tcleanupper.h"
|
||
#include "toonz/preferences.h"
|
||
#include "toonz/tscenehandle.h"
|
||
#include "toonz/txsheethandle.h"
|
||
#include "toonz/txshlevelhandle.h"
|
||
#include "toonz/palettecontroller.h"
|
||
#include "toonz/tpalettehandle.h"
|
||
#include "toonz/toonzfolders.h"
|
||
|
||
// TnzCore includes
|
||
#include "tsystem.h"
|
||
#include "tlevel_io.h"
|
||
#include "timageinfo.h"
|
||
#include "tstream.h"
|
||
|
||
// Qt includes
|
||
#include <QLabel>
|
||
#include <QPushButton>
|
||
#include <QRadioButton>
|
||
#include <QButtonGroup>
|
||
#include <QCoreApplication>
|
||
#include <QMainWindow>
|
||
#include <QGroupBox>
|
||
|
||
// STL includes
|
||
#include <set>
|
||
#include <map>
|
||
#include <numeric>
|
||
#include <functional>
|
||
|
||
//*****************************************************************************
|
||
// Local namespace stuff
|
||
//*****************************************************************************
|
||
|
||
namespace {
|
||
|
||
enum Resolution {
|
||
NO_RESOLUTION, //!< No required resolution.
|
||
CANCEL, //!< Validation was canceled.
|
||
OVERWRITE, //!< Does not delete old cleanupped levels, but overwrites found
|
||
//! frames.
|
||
WRITE_NEW, //!< Like above, but does not overwrite. Just adds not cleanupped
|
||
//! frames.
|
||
REPLACE, //!< Destroy the old level and build one anew.
|
||
ADD_SUFFIX, //!< Add a suffix to the output path.
|
||
NOPAINT_ONLY //!< overwrite the result only in "nopaint" folder
|
||
};
|
||
|
||
//-----------------------------------------------------------------------------
|
||
|
||
static const std::wstring unpaintedStr = L"-unpainted";
|
||
|
||
//-----------------------------------------------------------------------------
|
||
|
||
inline QString suffix(int num) { return QString("_") + QString::number(num); }
|
||
|
||
//-----------------------------------------------------------------------------
|
||
|
||
inline TFilePath withSuffix(const TFilePath &fp, int num) {
|
||
return fp.withName(fp.getWideName() + suffix(num).toStdWString());
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
|
||
inline bool exists(const TFilePath &fp) {
|
||
return TSystem::doesExistFileOrLevel(
|
||
TApp::instance()->getCurrentScene()->getScene()->decodeFilePath(fp));
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
|
||
inline bool exists(const TFilePath &fp, int num) {
|
||
return exists(withSuffix(fp, num));
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
|
||
void loadCleanupParams(CleanupParameters *params, TXshSimpleLevel *sl) {
|
||
params->assign(CleanupSettingsModel::instance()->getCurrentParameters());
|
||
CleanupSettingsModel::loadSettings(params,
|
||
CleanupSettingsModel::getClnPath(sl));
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
|
||
double getBestFactor(QSize viewSize, QSize imageSize) {
|
||
if (abs(viewSize.width() - imageSize.width()) >
|
||
abs(viewSize.height() - imageSize.height())) {
|
||
if (viewSize.width() > imageSize.width())
|
||
return double(imageSize.width()) / double(viewSize.width());
|
||
else
|
||
return double(viewSize.width()) / double(imageSize.width());
|
||
} else {
|
||
if (viewSize.height() > imageSize.height())
|
||
return double(imageSize.height()) / double(viewSize.height());
|
||
else
|
||
return double(viewSize.height()) / double(imageSize.height());
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
/*! cleanup後のファイルlevelPathに対してUnpaintedファイルを作る。
|
||
Cleanup後のUnpaintedの保存先を1階層下げる(nopaintフォルダ内に入れ、
|
||
"A_np.tlv"のように"_np"を付ける。"_unpainted"は長いので)
|
||
Paletteをキープするかどうかのフラグを追加
|
||
*/
|
||
void saveUnpaintedLevel(const TFilePath &levelPath, TXshSimpleLevel *sl,
|
||
std::vector<TFrameId> fids, bool keepOriginalPalette) {
|
||
try {
|
||
/*---nopaintフォルダの作成---*/
|
||
TFilePath nopaintDir = levelPath.getParentDir() + "nopaint";
|
||
if (!TFileStatus(nopaintDir).doesExist()) {
|
||
try {
|
||
TSystem::mkDir(nopaintDir);
|
||
} catch (...) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
TFilePath unpaintedLevelPath =
|
||
levelPath.getParentDir() + "nopaint\\" +
|
||
TFilePath(levelPath.getName() + "_np." + levelPath.getType());
|
||
|
||
if (!TSystem::doesExistFileOrLevel(unpaintedLevelPath)) {
|
||
// No unpainted level exists. So, just copy the output file.
|
||
TSystem::copyFile(unpaintedLevelPath, levelPath);
|
||
if (keepOriginalPalette) return;
|
||
|
||
TFilePath levelPalettePath(levelPath.withType("tpl"));
|
||
TFilePath unpaintedLevelPalettePath =
|
||
levelPalettePath.getParentDir() + "nopaint\\" +
|
||
TFilePath(levelPalettePath.getName() + "_np." +
|
||
levelPalettePath.getType());
|
||
|
||
TSystem::copyFile(unpaintedLevelPalettePath, levelPalettePath);
|
||
|
||
return;
|
||
}
|
||
|
||
TLevelWriterP lw(unpaintedLevelPath);
|
||
|
||
if (keepOriginalPalette) lw->setOverwritePaletteFlag(false);
|
||
|
||
int i, fidsCount = fids.size();
|
||
for (i = 0; i < fidsCount; ++i) {
|
||
const TFrameId &fid = fids[i];
|
||
|
||
TToonzImageP ti = sl->getFrame(fid, false);
|
||
if (!ti) continue;
|
||
|
||
lw->getFrameWriter(fid)->save(ti);
|
||
}
|
||
} catch (...) {
|
||
}
|
||
}
|
||
|
||
//------------------------------------------------------------------------------
|
||
/*! Cleanup後のデフォルトPaletteを追加する。
|
||
TODO:
|
||
Cleanup後にデフォルトPaletteの内容を追加する仕様、Preferencesでオプション化
|
||
2016/1/16 shun_iwasawa
|
||
*/
|
||
void addCleanupDefaultPalette(TXshSimpleLevelP sl) {
|
||
/*--- CleanupデフォルトパレットはStudioPaletteフォルダ内に入れる ---*/
|
||
TFilePath palettePath =
|
||
ToonzFolder::getStudioPaletteFolder() + "cleanup_default.tpl";
|
||
TFileStatus pfs(palettePath);
|
||
|
||
if (!pfs.doesExist() || !pfs.isReadable()) {
|
||
DVGui::warning(
|
||
QString("CleanupDefaultPalette file: %1 is not found!")
|
||
.arg(QString::fromStdWString(palettePath.getWideString())));
|
||
return;
|
||
}
|
||
|
||
TIStream is(palettePath);
|
||
if (!is) {
|
||
DVGui::warning(
|
||
QString("CleanupDefaultPalette file: failed to get TIStream"));
|
||
return;
|
||
}
|
||
|
||
std::string tagName;
|
||
if (!is.matchTag(tagName) || tagName != "palette") {
|
||
DVGui::warning(
|
||
QString("CleanupDefaultPalette file: This is not palette file"));
|
||
return;
|
||
}
|
||
|
||
std::string gname;
|
||
is.getTagParam("name", gname);
|
||
TPalette *defaultPalette = new TPalette();
|
||
defaultPalette->loadData(is);
|
||
|
||
sl->getPalette()->setIsCleanupPalette(false);
|
||
|
||
TPalette::Page *dstPage = sl->getPalette()->getPage(0);
|
||
TPalette::Page *srcPage = defaultPalette->getPage(0);
|
||
|
||
for (int srcIndexInPage = 0; srcIndexInPage < srcPage->getStyleCount();
|
||
srcIndexInPage++) {
|
||
int id = srcPage->getStyleId(srcIndexInPage);
|
||
|
||
bool isUsedInCleanupPalette;
|
||
isUsedInCleanupPalette = false;
|
||
|
||
for (int dstIndexInPage = 0; dstIndexInPage < dstPage->getStyleCount();
|
||
dstIndexInPage++) {
|
||
if (dstPage->getStyleId(dstIndexInPage) == id) {
|
||
isUsedInCleanupPalette = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (isUsedInCleanupPalette)
|
||
continue;
|
||
|
||
else {
|
||
int addedId = sl->getPalette()->addStyle(
|
||
srcPage->getStyle(srcIndexInPage)->clone());
|
||
dstPage->addStyle(addedId);
|
||
/*---
|
||
* StudioPalette由来のDefaultPaletteの場合、GrobalName(リンク)を消去する
|
||
* ---*/
|
||
sl->getPalette()->getStyle(addedId)->setGlobalName(L"");
|
||
sl->getPalette()->getStyle(addedId)->setOriginalName(L"");
|
||
}
|
||
}
|
||
delete defaultPalette;
|
||
}
|
||
|
||
} // namespace
|
||
|
||
//*****************************************************************************
|
||
// CleanupLevel definition
|
||
//*****************************************************************************
|
||
|
||
struct CleanupPopup::CleanupLevel {
|
||
TXshSimpleLevel *m_sl; //!< Level to be cleanupped.
|
||
TFilePath m_outputPath; //!< Output path for the cleanupped level.
|
||
std::vector<TFrameId> m_frames; //!< Frames to cleanup.
|
||
Resolution m_resolution; //!< Resolution for verified file conflicts.
|
||
|
||
public:
|
||
CleanupLevel(TXshSimpleLevel *sl, const CleanupParameters ¶ms)
|
||
: m_sl(sl)
|
||
, m_outputPath(CleanupSettingsModel::getOutputPath(m_sl, ¶ms))
|
||
, m_resolution(NO_RESOLUTION) {}
|
||
|
||
bool empty() const { return m_frames.empty(); }
|
||
};
|
||
|
||
//*****************************************************************************
|
||
// CleanupPopup implementation
|
||
//*****************************************************************************
|
||
|
||
CleanupPopup::CleanupPopup()
|
||
: QDialog(TApp::instance()->getMainWindow())
|
||
, m_params(new CleanupParameters)
|
||
, m_updater(new LevelUpdater)
|
||
, m_originalLevelPath()
|
||
, m_originalPalette(0)
|
||
, m_firstLevelFrame(true) {
|
||
setWindowTitle(tr("Cleanup"));
|
||
// Progress Bar
|
||
m_progressLabel = new QLabel(tr("Cleanup in progress"));
|
||
m_progressBar = new QProgressBar;
|
||
// Text
|
||
m_cleanupQuestionLabel = new QLabel(tr("Do you want to cleanup this frame?"));
|
||
|
||
m_imageViewer = new ImageViewer(0, 0, false);
|
||
|
||
// Buttons
|
||
m_cleanupButton = new QPushButton(tr("Cleanup"));
|
||
m_skipButton = new QPushButton(tr("Skip"));
|
||
m_cleanupAllButton = new QPushButton(tr("Cleanup All"));
|
||
QPushButton *cancelButton = new QPushButton(tr("Cancel"));
|
||
m_imgViewBox = new QGroupBox(tr("View"), this);
|
||
|
||
m_imgViewBox->setCheckable(true);
|
||
m_imgViewBox->setChecked(false);
|
||
m_imageViewer->setVisible(false);
|
||
m_imageViewer->resize(406, 306);
|
||
|
||
//---layout
|
||
QVBoxLayout *mainLayout = new QVBoxLayout();
|
||
mainLayout->setMargin(5);
|
||
mainLayout->setSpacing(5);
|
||
{
|
||
mainLayout->addWidget(m_progressLabel, 0);
|
||
mainLayout->addWidget(m_progressBar, 0);
|
||
|
||
mainLayout->addWidget(m_cleanupQuestionLabel);
|
||
|
||
QVBoxLayout *imgBoxLay = new QVBoxLayout();
|
||
imgBoxLay->setMargin(5);
|
||
{ imgBoxLay->addWidget(m_imageViewer); }
|
||
m_imgViewBox->setLayout(imgBoxLay);
|
||
mainLayout->addWidget(m_imgViewBox, 1);
|
||
|
||
QHBoxLayout *buttonLay = new QHBoxLayout();
|
||
buttonLay->setMargin(0);
|
||
buttonLay->setSpacing(5);
|
||
{
|
||
buttonLay->addWidget(m_cleanupButton);
|
||
buttonLay->addWidget(m_skipButton);
|
||
buttonLay->addWidget(m_cleanupAllButton);
|
||
|
||
buttonLay->addWidget(cancelButton);
|
||
}
|
||
mainLayout->addLayout(buttonLay);
|
||
mainLayout->addStretch();
|
||
}
|
||
setLayout(mainLayout);
|
||
|
||
//--- signal-slot connections
|
||
|
||
bool ret = true;
|
||
ret = ret && connect(m_progressBar, SIGNAL(valueChanged(int)), this,
|
||
SLOT(onValueChanged(int)));
|
||
|
||
// NOTE: On MAC it seems that QAbstractButton's pressed() signal is reemitted
|
||
// at
|
||
// every mouseMoveEvent when the button is pressed...
|
||
// This is why clicked() substitutes pressed() below.
|
||
|
||
ret = ret && connect(m_cleanupButton, SIGNAL(clicked()), this,
|
||
SLOT(onCleanupFrame()));
|
||
ret = ret &&
|
||
connect(m_skipButton, SIGNAL(clicked()), this, SLOT(onSkipFrame()));
|
||
ret = ret && connect(m_cleanupAllButton, SIGNAL(clicked()), this,
|
||
SLOT(onCleanupAllFrame()));
|
||
ret = ret &&
|
||
connect(cancelButton, SIGNAL(clicked()), this, SLOT(onCancelCleanup()));
|
||
ret = ret && connect(m_imgViewBox, SIGNAL(toggled(bool)), this,
|
||
SLOT(onImgViewBoxToggled(bool)));
|
||
|
||
assert(ret);
|
||
|
||
reset(); // Initialize remaining variables
|
||
|
||
resize(450, 400);
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
|
||
CleanupPopup::~CleanupPopup() {}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
|
||
void CleanupPopup::closeEvent(QCloseEvent *ce) { reset(); }
|
||
|
||
//-----------------------------------------------------------------------------
|
||
|
||
void CleanupPopup::reset() {
|
||
closeLevel();
|
||
|
||
m_idx = m_completion = std::pair<int, int>(-1, -1);
|
||
m_cleanupLevels.clear();
|
||
|
||
m_imageViewer->setImage(TImageP());
|
||
|
||
TCleanupper::instance()->setParameters(
|
||
CleanupSettingsModel::instance()->getCurrentParameters());
|
||
|
||
m_cleanupQuestionLabel->show();
|
||
|
||
m_levelAlreadyExists.clear();
|
||
|
||
/*---タイトルバーを元の表示に戻す---*/
|
||
MainWindow *mainWin =
|
||
qobject_cast<MainWindow *>(TApp::instance()->getMainWindow());
|
||
if (mainWin) mainWin->changeWindowTitle();
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
|
||
void CleanupPopup::buildCleanupList() {
|
||
struct locals {
|
||
static inline bool supportsCleanup(TXshSimpleLevel *sl) {
|
||
return (
|
||
sl->getType() & FULLCOLOR_TYPE ||
|
||
(sl->getType() == TZP_XSHLEVEL && !sl->getScannedPath().isEmpty()));
|
||
}
|
||
}; // locals
|
||
|
||
typedef std::set<TFrameId> FramesList;
|
||
|
||
/*--- これからCleanupするLevel/FrameIdのリスト ---*/
|
||
std::map<TXshSimpleLevel *, FramesList>
|
||
cleanupList; // List of frames to be cleanupped
|
||
std::vector<TXshSimpleLevel *>
|
||
levelsList; // List of levels in the cleanup list,
|
||
// ordered as found in the xsheet.
|
||
m_cleanupLevels.clear();
|
||
|
||
// Retrieve current selection
|
||
TCellSelection *selection =
|
||
dynamic_cast<TCellSelection *>(TSelection::getCurrent());
|
||
TColumnSelection *columnSel =
|
||
dynamic_cast<TColumnSelection *>(TSelection::getCurrent());
|
||
/*--- セル選択でも、カラム選択でも無い場合はCleanup自体を無効にする ---*/
|
||
if (!selection && !columnSel) return;
|
||
|
||
TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
|
||
|
||
/*--- セル選択の場合 ---*/
|
||
if (selection) {
|
||
if (selection->isEmpty()) return;
|
||
// Store frames in the specified selection
|
||
int r0, c0, r1, c1;
|
||
selection->getSelectedCells(r0, c0, r1, c1);
|
||
|
||
int r, c;
|
||
for (c = c0; c <= c1; ++c) {
|
||
for (r = r0; r <= r1; ++r) {
|
||
/*---選択範囲内にLevelが無い場合はcontinue---*/
|
||
const TXshCell &cell = xsh->getCell(r, c);
|
||
if (cell.isEmpty()) continue;
|
||
TXshSimpleLevel *sl = cell.getSimpleLevel();
|
||
if (!(sl && locals::supportsCleanup(sl))) continue;
|
||
/*---もし新しいLevelなら、Levelのリストに登録---*/
|
||
std::map<TXshSimpleLevel *, FramesList>::iterator it =
|
||
cleanupList.find(sl);
|
||
if (it == cleanupList.end()) {
|
||
it = cleanupList.insert(std::make_pair(sl, FramesList())).first;
|
||
levelsList.push_back(sl);
|
||
}
|
||
/*---TFrameIdを登録---*/
|
||
it->second.insert(cell.getFrameId());
|
||
}
|
||
}
|
||
}
|
||
/*--- カラム選択の場合 ---*/
|
||
else {
|
||
int frameCount = xsh->getFrameCount();
|
||
if (columnSel->isEmpty() || frameCount <= 0) return;
|
||
/*--- 選択された各カラムについて ---*/
|
||
std::set<int>::const_iterator it = columnSel->getIndices().begin();
|
||
for (; it != columnSel->getIndices().end(); ++it) {
|
||
int c = (*it);
|
||
for (int r = 0; r < frameCount; r++) {
|
||
/*--- 選択範囲内にLevelが無い場合はcontinue ---*/
|
||
const TXshCell &cell = xsh->getCell(r, c);
|
||
if (cell.isEmpty()) continue;
|
||
TXshSimpleLevel *sl = cell.getSimpleLevel();
|
||
if (!sl && locals::supportsCleanup(sl)) continue;
|
||
/*---もし新しいLevelなら、Levelのリストに登録---*/
|
||
std::map<TXshSimpleLevel *, FramesList>::iterator it =
|
||
cleanupList.find(sl);
|
||
if (it == cleanupList.end()) {
|
||
it = cleanupList.insert(std::make_pair(sl, FramesList())).first;
|
||
levelsList.push_back(sl);
|
||
}
|
||
/*---TFrameIdを登録---*/
|
||
it->second.insert(cell.getFrameId());
|
||
}
|
||
}
|
||
}
|
||
|
||
// Finally, copy the retrieved data to the sorted output vector
|
||
std::vector<TXshSimpleLevel *>::iterator lt, lEnd = levelsList.end();
|
||
for (lt = levelsList.begin(); lt != lEnd; ++lt) {
|
||
loadCleanupParams(
|
||
m_params.get(),
|
||
*lt); // Load cleanup parameters associated with current level.
|
||
// This is necessary since the output path is specified among them.
|
||
m_cleanupLevels.push_back(CleanupLevel(*lt, *m_params.get()));
|
||
CleanupLevel &cl = m_cleanupLevels.back();
|
||
|
||
FramesList &framesList = cleanupList[cl.m_sl];
|
||
cl.m_frames.assign(framesList.begin(), framesList.end());
|
||
}
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
|
||
bool CleanupPopup::analyzeCleanupList() {
|
||
ToonzScene *scene = TApp::instance()->getCurrentScene()->getScene();
|
||
|
||
bool shownOverwriteDialog = false, shownWritingOnSourceFile = false;
|
||
|
||
/*--- 消されるLevel名の確認ダイアログを出すため ---*/
|
||
QList<TFilePath> filePathsToBeDeleted;
|
||
/* Cleanup前に既存のLevelを消す場合、セルのStatusをScannedに変更するために、
|
||
TXshSimpleLevelポインタを格納しておく
|
||
*/
|
||
QList<TXshSimpleLevel *> levelsToBeDeleted;
|
||
|
||
// Traverse the cleanup list
|
||
for (auto &clt : m_cleanupLevels) {
|
||
TXshSimpleLevel *sl = clt.m_sl;
|
||
|
||
/*--- Cleanup対象LevelのCleanupSettingを取得 ---*/
|
||
loadCleanupParams(m_params.get(),
|
||
sl); // Needed to retrieve output level resolution
|
||
|
||
// Check level existence
|
||
/*--- Cleanup後に得られるであろうTLVのパス ---*/
|
||
TFilePath outputPath = scene->decodeFilePath(clt.m_outputPath);
|
||
{
|
||
/*-- 出力先にTLVファイルが無ければ問題なし(このLevelはCleanupする) --*/
|
||
if (!TSystem::doesExistFileOrLevel(outputPath)) {
|
||
m_levelAlreadyExists[sl] = false;
|
||
continue; // Newly cleaned up level. No problem.
|
||
} else
|
||
m_levelAlreadyExists[sl] = true;
|
||
|
||
// Check whether output file == input file
|
||
/*--- 入力となるScanned(TIFなど)のパスを得る ---*/
|
||
const TFilePath &inputPath =
|
||
scene->decodeFilePath(CleanupSettingsModel::getInputPath(sl));
|
||
|
||
if (!shownWritingOnSourceFile && inputPath == outputPath) {
|
||
shownWritingOnSourceFile = true;
|
||
|
||
int ret = DVGui::MsgBox(tr("Selected drawings will overwrite the "
|
||
"original files after the cleanup process.\n"
|
||
"Do you want to continue?"),
|
||
tr("Ok"), tr("Cancel"));
|
||
|
||
if (ret != 1) return false;
|
||
}
|
||
|
||
/*---一度も上書き確認のダイアログを出していなかったら、ここで出す---*/
|
||
// Reset the overwrite dialog
|
||
if (!shownOverwriteDialog) {
|
||
shownOverwriteDialog = true;
|
||
|
||
if (!m_overwriteDialog)
|
||
m_overwriteDialog.reset(new OverwriteDialog);
|
||
else
|
||
m_overwriteDialog->reset();
|
||
}
|
||
|
||
// Prompt user for file conflict resolution
|
||
clt.m_resolution = Resolution(m_overwriteDialog->execute(&clt.m_outputPath));
|
||
switch (clt.m_resolution) {
|
||
case CANCEL:
|
||
return false;
|
||
|
||
case NO_RESOLUTION:
|
||
assert(false);
|
||
break;
|
||
case REPLACE:
|
||
/*--- 既存のTLVを消すオプションの場合、消されるファイルのリストを作る
|
||
* ---*/
|
||
break;
|
||
case ADD_SUFFIX:
|
||
continue;
|
||
case NOPAINT_ONLY:
|
||
/*--- NOPAINT_ONLY の場合は、nopaintのみを変更。
|
||
ただし、nopaintのLevelは消さず、処理したフレームを Overwrite
|
||
する
|
||
---*/
|
||
outputPath =
|
||
outputPath.getParentDir() + "nopaint\\" +
|
||
TFilePath(outputPath.getName() + "_np." + outputPath.getType());
|
||
/*--- nopaintの有無を確かめる。無ければ次のLevelへ
|
||
* (このLevelはCleanupする) ---*/
|
||
if (!TSystem::doesExistFileOrLevel(outputPath)) {
|
||
m_levelAlreadyExists[sl] = false;
|
||
continue;
|
||
}
|
||
default:
|
||
break;
|
||
}
|
||
|
||
TLevelP level(0); // Current level info. Yeah the init is a shame... :(
|
||
/*--- 元のLevelと新しいCleanup結果が混合する場合。REPLACE以外 ---*/
|
||
if (clt.m_resolution == OVERWRITE || clt.m_resolution == WRITE_NEW ||
|
||
clt.m_resolution == NOPAINT_ONLY) {
|
||
// Check output resolution consistency
|
||
// Retrieve file resolution
|
||
/*---現在在るTLVのサイズと、CleanupSettingsのサイズが一致しているかチェック---*/
|
||
TDimension oldRes(0, 0);
|
||
try {
|
||
TLevelReaderP lr(outputPath);
|
||
level = lr->loadInfo();
|
||
|
||
if (const TImageInfo *imageInfo = lr->getImageInfo())
|
||
oldRes = TDimension(imageInfo->m_lx, imageInfo->m_ly);
|
||
else
|
||
throw TException();
|
||
} catch (...) {
|
||
// There was a problem reading the existing level data.
|
||
// Thus, the conservative approach is not feasible.
|
||
|
||
// Inform the user and abort cleanup
|
||
DVGui::warning(
|
||
tr("There were errors opening the existing level "
|
||
"\"%1\".\n\nPlease choose to delete the existing level and "
|
||
"create a new one\nwhen running the cleanup process.")
|
||
.arg(QString::fromStdWString(outputPath.getLevelNameW())));
|
||
|
||
return false;
|
||
}
|
||
|
||
// Retrieve output resolution
|
||
TDimension outRes(0, 0);
|
||
TPointD outDpi;
|
||
m_params->getOutputImageInfo(outRes, outDpi.x, outDpi.y);
|
||
|
||
if (oldRes != outRes) {
|
||
DVGui::warning(
|
||
tr("The resulting resolution of level \"%1\"\ndoes not match "
|
||
"with that of previously cleaned up level drawings.\n\nPlease "
|
||
"set the right camera resolution and closest field, or choose "
|
||
"to delete\nthe existing level and create a new one when "
|
||
"running the cleanup process.")
|
||
.arg(QString::fromStdWString(outputPath.getLevelNameW())));
|
||
|
||
return false;
|
||
}
|
||
}
|
||
/*--- REPLACEの場合、消されるファイルパスのリストを作る ---*/
|
||
else if (clt.m_resolution == REPLACE) {
|
||
filePathsToBeDeleted.push_back(outputPath);
|
||
|
||
levelsToBeDeleted.push_back(sl);
|
||
|
||
/*--- パレットファイルも、あれば消す ---*/
|
||
TFilePath palettePath =
|
||
(outputPath.getParentDir() + outputPath.getName()).withType("tpl");
|
||
if (TSystem::doesExistFileOrLevel(palettePath))
|
||
filePathsToBeDeleted.push_back(palettePath);
|
||
/*--- つぎに、nopaintのTLV。これは、REPLACE、NOPAINT_ONLY 両方で消す
|
||
* ---*/
|
||
TFilePath unpaintedLevelPath =
|
||
outputPath.getParentDir() + "nopaint\\" +
|
||
TFilePath(outputPath.getName() + "_np." + outputPath.getType());
|
||
if (TSystem::doesExistFileOrLevel(unpaintedLevelPath)) {
|
||
filePathsToBeDeleted.push_back(unpaintedLevelPath);
|
||
filePathsToBeDeleted.push_back(
|
||
(unpaintedLevelPath.getParentDir() + unpaintedLevelPath.getName())
|
||
.withType("tpl"));
|
||
}
|
||
}
|
||
|
||
// Finally, apply resolution to individual frames.
|
||
/*--- WRITE_NEW は、「未Cleanupのフレームだけ処理する」オプション ---*/
|
||
if (clt.m_resolution == WRITE_NEW) {
|
||
const TLevel::Table *table = level->getTable();
|
||
|
||
clt.m_frames.erase(
|
||
std::remove_if(clt.m_frames.begin(), clt.m_frames.end(),
|
||
[table](TLevel::Table::key_type const &key) {
|
||
return table->count(key);
|
||
}),
|
||
clt.m_frames.end());
|
||
}
|
||
}
|
||
}
|
||
|
||
/*--- ファイル消去の確認ダイアログを表示 ---*/
|
||
if (!filePathsToBeDeleted.isEmpty()) {
|
||
QString question = QObject::tr(
|
||
"Delete and Re-cleanup : The following files will be deleted.\n\n");
|
||
for (int i = 0; i < filePathsToBeDeleted.size(); i++) {
|
||
question +=
|
||
" " +
|
||
QString::fromStdWString(filePathsToBeDeleted[i].getWideString()) +
|
||
"\n";
|
||
}
|
||
question += QObject::tr("\nAre you sure ?");
|
||
|
||
int ret = DVGui::MsgBox(question, QObject::tr("Delete"),
|
||
QObject::tr("Cancel"), 0);
|
||
if (ret == 0 || ret == 2) {
|
||
return false;
|
||
} else if (ret == 1) {
|
||
/*--- 先にCleanup処理で出力先となるファイルを消す ---*/
|
||
try {
|
||
for (int i = 0; i < filePathsToBeDeleted.size(); i++) {
|
||
TSystem::removeFileOrLevel_throw(filePathsToBeDeleted[i]);
|
||
}
|
||
} catch (...) {
|
||
return false;
|
||
}
|
||
|
||
// Reset level status
|
||
for (int i = 0; i < levelsToBeDeleted.size(); i++) {
|
||
TXshSimpleLevel *lev = levelsToBeDeleted.at(i);
|
||
/*--- TLVだった場合、Scanned(TIFレベル)に戻す ---*/
|
||
TFilePath scannedPath = lev->getScannedPath();
|
||
if (scannedPath != TFilePath()) {
|
||
lev->setScannedPath(TFilePath());
|
||
lev->setPath(scannedPath, true);
|
||
lev->clearFrames();
|
||
lev->setType(OVL_XSHLEVEL); // OVL_XSHLEVEL
|
||
lev->setPalette(0);
|
||
if (lev == TApp::instance()->getCurrentLevel()->getLevel())
|
||
TApp::instance()
|
||
->getPaletteController()
|
||
->getCurrentLevelPalette()
|
||
->setPalette(0);
|
||
|
||
lev->load();
|
||
int i, frameCount = lev->getFrameCount();
|
||
for (i = 0; i < frameCount; i++) {
|
||
TFrameId id = lev->index2fid(i);
|
||
IconGenerator::instance()->invalidate(lev, id);
|
||
}
|
||
IconGenerator::instance()->invalidateSceneIcon();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Before returning, erase levels whose frames list is empty
|
||
/*--- Cleanup対象フレームが無くなったLevelを対象から外す ---*/
|
||
m_cleanupLevels.erase(
|
||
std::remove_if(m_cleanupLevels.begin(), m_cleanupLevels.end(),
|
||
std::mem_fn(&CleanupLevel::empty)),
|
||
m_cleanupLevels.end());
|
||
|
||
return true;
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
|
||
bool CleanupPopup::isValidPosition(const std::pair<int, int> &pos) const {
|
||
if (pos.first < 0 || int(m_cleanupLevels.size()) <= pos.first) return false;
|
||
|
||
const CleanupLevel &cl = m_cleanupLevels[pos.first];
|
||
|
||
if (pos.second < 0 || int(cl.m_frames.size()) <= pos.second) return false;
|
||
|
||
return true;
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
|
||
QString CleanupPopup::currentString() const {
|
||
if (!isValidPosition(m_idx)) return QString();
|
||
|
||
const CleanupLevel &cl = m_cleanupLevels[m_idx.first];
|
||
|
||
TXshSimpleLevel *sl = cl.m_sl;
|
||
const TFrameId &fid = cl.m_frames[m_idx.second];
|
||
|
||
TFilePath scannedPath(sl->getScannedPath());
|
||
if (scannedPath.isEmpty()) scannedPath = sl->getPath();
|
||
|
||
TFilePath levelName(scannedPath.getLevelNameW());
|
||
QString imageName = toQString(levelName.withFrame(fid));
|
||
|
||
return tr("Cleanup in progress: ") + imageName + " " +
|
||
QString::number(m_completion.first) + "/" +
|
||
QString::number(m_completion.second);
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
/*--- これからCleanupするフレームを取得 ---*/
|
||
TImageP CleanupPopup::currentImage() const {
|
||
if (!isValidPosition(m_idx)) return TImageP();
|
||
|
||
const CleanupLevel &cl = m_cleanupLevels[m_idx.first];
|
||
return cl.m_sl->getFrameToCleanup(cl.m_frames[m_idx.second]);
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
|
||
void CleanupPopup::execute() {
|
||
struct locals {
|
||
static inline int addFrames(int a, const CleanupLevel &cl) {
|
||
return a + int(cl.m_frames.size());
|
||
}
|
||
}; // locals
|
||
|
||
// Re-initialize the list of frames to be cleanupped
|
||
/*--- Cleanup対象のリストを作る ---*/
|
||
buildCleanupList();
|
||
|
||
// In case some cleaned up level already exists, let the user decide what to
|
||
// do
|
||
if (!analyzeCleanupList()) {
|
||
reset();
|
||
return;
|
||
}
|
||
|
||
// Initialize completion variable
|
||
m_completion = std::pair<int, int>(
|
||
0, std::accumulate(m_cleanupLevels.begin(), m_cleanupLevels.end(), 0,
|
||
locals::addFrames));
|
||
|
||
// If there are no (more) frames to cleanup, warn and quit
|
||
int framesCount = m_completion.second;
|
||
if (!framesCount) {
|
||
DVGui::info(
|
||
tr("It is not possible to cleanup: the cleanup list is empty."));
|
||
|
||
reset();
|
||
return;
|
||
}
|
||
|
||
// Initialize the cleanup process and show the popup
|
||
|
||
m_idx = std::pair<int, int>(0, 0);
|
||
|
||
// Reset progress bar
|
||
m_progressLabel->setText(currentString());
|
||
|
||
m_progressBar->setRange(0, framesCount);
|
||
m_progressBar->setValue(0);
|
||
|
||
// show the progress to the main window's title bar
|
||
updateTitleString();
|
||
|
||
TImageP image = currentImage();
|
||
if (image) {
|
||
m_imageViewer->setImage(image);
|
||
|
||
// Set the zoom factor depending on image and viewer sizes, so that the
|
||
// image fits
|
||
// the preview area.
|
||
|
||
QSize viewSize = m_imageViewer->size();
|
||
QSize imageSize = QSize(image->getBBox().getLx(), image->getBBox().getLy());
|
||
double factor = getBestFactor(viewSize, imageSize);
|
||
TPointD delta(0, 0);
|
||
TAffine viewAff =
|
||
TTranslation(delta) * TScale(factor) * TTranslation(-delta);
|
||
m_imageViewer->setViewAff(viewAff);
|
||
}
|
||
|
||
show();
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
|
||
QString CleanupPopup::setupLevel() {
|
||
// Level's pre-cleanup stuff initialization.
|
||
// Invoked right before the cleanupFrame() of the first level frame.
|
||
|
||
assert(isValidPosition(m_idx));
|
||
|
||
TApp *app = TApp::instance();
|
||
ToonzScene *scene = app->getCurrentScene()->getScene();
|
||
|
||
/*--- これからCleanupするLevel ---*/
|
||
CleanupLevel &cl = m_cleanupLevels[m_idx.first];
|
||
TXshSimpleLevel *sl = cl.m_sl;
|
||
|
||
/*--- 保存先のTLVが既に存在する、かつ、REPLACE でも
|
||
NOPAINT_ONLY でもない場合、Paletteを変更せず維持する ---*/
|
||
if (cl.m_resolution != REPLACE && cl.m_resolution != NOPAINT_ONLY &&
|
||
m_levelAlreadyExists[sl] == true)
|
||
m_keepOriginalPalette = true;
|
||
else
|
||
m_keepOriginalPalette = false;
|
||
|
||
// Update cleanup parameters, loading a cln if necessary
|
||
TCleanupper *cleanupper = TCleanupper::instance();
|
||
|
||
/*--- CleanupSettingsを読み込み、TCleanupperに渡す ---*/
|
||
loadCleanupParams(m_params.get(), sl);
|
||
cleanupper->setParameters(m_params.get());
|
||
|
||
// Touch the output parent directory
|
||
TFilePath &outputPath = cl.m_outputPath;
|
||
|
||
/*--- 保存先PathをFull Pathにする ---*/
|
||
TFilePath decodedPath(scene->decodeFilePath(outputPath));
|
||
if (!TSystem::touchParentDir(decodedPath))
|
||
return tr("Couldn't create directory \"%1\"").arg(decodedPath.getQString());
|
||
|
||
/*---
|
||
* 上書きオプションが選択されているとき、既存のLevel,Palette,Nopaintファイルを消す
|
||
* ---*/
|
||
// If the user decided to remove any existing level, do so now.
|
||
if (cl.m_resolution == REPLACE) {
|
||
const QString &err = resetLevel();
|
||
|
||
if (!err.isEmpty()) return err;
|
||
}
|
||
/*--- Nopaintのみ上書きのオプションが選択されているとき(再Clenaupの場合)
|
||
---*/
|
||
else if (cl.m_resolution == NOPAINT_ONLY) {
|
||
m_originalLevelPath = outputPath;
|
||
/*--- 必要なら、nopaintフォルダを作成 ---*/
|
||
TFilePath nopaintDir = decodedPath.getParentDir() + "nopaint";
|
||
if (!TFileStatus(nopaintDir).doesExist()) {
|
||
try {
|
||
TSystem::mkDir(nopaintDir);
|
||
} catch (...) {
|
||
return NULL;
|
||
}
|
||
}
|
||
/*--- 保存先のパスをnopaintの方に変更 ---*/
|
||
outputPath =
|
||
outputPath.getParentDir() + "nopaint\\" +
|
||
TFilePath(outputPath.getName() + "_np." + outputPath.getType());
|
||
decodedPath = scene->decodeFilePath(outputPath);
|
||
}
|
||
|
||
// Frames are cleaned-up at full-sampling. Thus, clear subsampling on the
|
||
// level.
|
||
if (sl->getProperties()->getSubsampling() != 1) {
|
||
sl->getProperties()->setSubsampling(1);
|
||
sl->invalidateFrames();
|
||
}
|
||
|
||
bool lineProcessing = (m_params->m_lineProcessingMode != lpNone),
|
||
notLineProcessed = (sl->getType() != TZP_XSHLEVEL);
|
||
|
||
if (lineProcessing) {
|
||
/*--- Keep original palette which will be reverted after cleanup ---*/
|
||
if (m_keepOriginalPalette) {
|
||
if ((sl->getType() == TZP_XSHLEVEL || sl->getType() == TZI_XSHLEVEL) &&
|
||
sl->getPalette() != NULL)
|
||
m_originalPalette = sl->getPalette()->clone();
|
||
else /*--- In case the level has been already cleanupped,
|
||
and is cleanupped again from raster level ---*/
|
||
{
|
||
/*--- Load and keep the palette from destination TLV ---*/
|
||
TFilePath targetPalettePath = outputPath.getParentDir() +
|
||
TFilePath(outputPath.getName() + ".tpl");
|
||
TFileStatus pfs(targetPalettePath);
|
||
if (pfs.doesExist() && pfs.isReadable()) {
|
||
TIStream is(targetPalettePath);
|
||
std::string tagName;
|
||
if (!is.matchTag(tagName) || tagName != "palette") {
|
||
DVGui::warning(QString(
|
||
"CleanupDefaultPalette file: This is not palette file"));
|
||
return NULL;
|
||
}
|
||
m_originalPalette = new TPalette();
|
||
m_originalPalette->loadData(is);
|
||
} else
|
||
m_originalPalette = 0;
|
||
}
|
||
}
|
||
|
||
if (notLineProcessed) {
|
||
/*-- Type, Pathを切り替えてTLVにする --*/
|
||
// The level type changes to TLV
|
||
sl->makeTlv(outputPath);
|
||
|
||
// Remap all current images under the IM control to Scanned status.
|
||
int f, fCount = sl->getFrameCount();
|
||
|
||
for (f = 0; f != fCount; ++f) {
|
||
const TFrameId &fid = sl->getFrameId(f);
|
||
|
||
/*--- 「スキャン済み」のステータスにし、画像、アイコンのIDを切り替える
|
||
* ---*/
|
||
sl->setFrameStatus(fid, TXshSimpleLevel::Scanned);
|
||
ImageManager::instance()->rebind(
|
||
sl->getImageId(fid, 0),
|
||
sl->getImageId(fid, TXshSimpleLevel::Scanned));
|
||
|
||
const std::string &oldIconId = sl->getIconId(fid, 0);
|
||
const std::string &newIconId =
|
||
sl->getIconId(fid, TXshSimpleLevel::Scanned);
|
||
|
||
IconGenerator::instance()->remap(newIconId, oldIconId);
|
||
}
|
||
}
|
||
/*--- 対象が既にTLVファイルで、出力先パスが違う場合、切り替える ---*/
|
||
else if (outputPath != sl->getPath()) {
|
||
// Just wants to be written to a different destination path. Update it.
|
||
sl->setPath(outputPath, false);
|
||
}
|
||
|
||
/*-- Cleanup用のパレットを作る --*/
|
||
// Update the level palette
|
||
TPaletteP palette =
|
||
TCleanupper::instance()->createToonzPaletteFromCleanupPalette();
|
||
|
||
sl->setPalette(palette.getPointer());
|
||
|
||
/*--- カレントPaletteを切り替える ---*/
|
||
if (sl == app->getCurrentLevel()->getLevel())
|
||
app->getPaletteController()->getCurrentLevelPalette()->setPalette(
|
||
palette.getPointer());
|
||
|
||
// Notify the xsheet that the level has changed visual type informations
|
||
// (either the level type,
|
||
// cleanup status, etc)
|
||
app->getCurrentXsheet()->notifyXsheetChanged();
|
||
} else if (!m_params->getPath(scene)
|
||
.isEmpty()) // Should never be empty, AFAIK...
|
||
{
|
||
// No line processing
|
||
|
||
if (notLineProcessed) {
|
||
// Just change paths
|
||
if (sl->getScannedPath().isEmpty()) sl->setScannedPath(sl->getPath());
|
||
|
||
sl->setPath(outputPath, false); // Reload frames from the result, too
|
||
} else {
|
||
// Return to scan level type
|
||
sl->clearFrames();
|
||
sl->setType(OVL_XSHLEVEL);
|
||
sl->setPath(outputPath);
|
||
}
|
||
}
|
||
|
||
// Finally, open the LevelUpdater on the level.
|
||
assert(!m_updater->opened());
|
||
try {
|
||
m_updater->open(sl);
|
||
} catch (...) {
|
||
return tr("Couldn't open \"%1\" for write").arg(outputPath.getQString());
|
||
}
|
||
|
||
m_updater->getLevelWriter()->setOverwritePaletteFlag(!m_keepOriginalPalette);
|
||
|
||
return QString();
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
/* setupLevel()から、 m_overwriteAction == REPLACE のとき呼ばれる。
|
||
選択LevelのCleanup後Levelを消す
|
||
*/
|
||
QString CleanupPopup::resetLevel() {
|
||
struct locals {
|
||
static bool removeFileOrLevel(const TFilePath &fp) {
|
||
return (!TSystem::doesExistFileOrLevel(fp) ||
|
||
TSystem::removeFileOrLevel(fp));
|
||
}
|
||
|
||
static QString decorate(const TFilePath &fp) {
|
||
return tr("Couldn't remove file \"%1\"").arg(fp.getQString());
|
||
}
|
||
};
|
||
|
||
// Try to remove the existing level
|
||
const CleanupLevel &cl = m_cleanupLevels[m_idx.first];
|
||
|
||
TXshSimpleLevel *sl = cl.m_sl;
|
||
assert(sl);
|
||
|
||
ToonzScene *scene = TApp::instance()->getCurrentScene()->getScene();
|
||
|
||
/*--- Cleanup後のTLVファイルパスを得る。すなわちこれから消すファイル ---*/
|
||
// Ensure outputPath != inputPath
|
||
const TFilePath &outputPath = scene->decodeFilePath(cl.m_outputPath),
|
||
&inputPath = scene->decodeFilePath(
|
||
CleanupSettingsModel::getInputPath(sl));
|
||
|
||
if (inputPath == outputPath)
|
||
return QString(); // Cannot remove source file - however, it can be
|
||
// overwritten
|
||
|
||
// Remove existing output files
|
||
if (!locals::removeFileOrLevel(outputPath))
|
||
return locals::decorate(outputPath);
|
||
|
||
if (m_params->m_lineProcessingMode != lpNone) {
|
||
TFilePath fp;
|
||
|
||
// Line processing on - remove palette too
|
||
if (!locals::removeFileOrLevel(fp = outputPath.withType("tpl")))
|
||
return locals::decorate(fp);
|
||
|
||
// Also remove unpainted output path if any
|
||
const TFilePath &unpaintedPath(
|
||
outputPath.getParentDir() + "nopaint\\" +
|
||
TFilePath(outputPath.getName() + "_np." + outputPath.getType()));
|
||
|
||
if (!locals::removeFileOrLevel(unpaintedPath))
|
||
return locals::decorate(unpaintedPath);
|
||
|
||
if (!locals::removeFileOrLevel(fp = unpaintedPath.withType("tpl")))
|
||
return locals::decorate(fp);
|
||
}
|
||
|
||
// Reset level status
|
||
sl->setPath(sl->getPath(), false); // false rebuilds level data
|
||
// NOTE: sl is the INPUT level - so this instruction
|
||
return QString(); // should take place AFTER output availability
|
||
// has been ensured.
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
/*---
|
||
* 現在処理を行っているLevelの最後のフレームの処理が終わってから、フレームを進めるときに呼ばれる
|
||
* ---*/
|
||
void CleanupPopup::closeLevel() {
|
||
if (m_cleanuppedLevelFrames.empty()) {
|
||
if (m_updater->opened()) m_updater->close();
|
||
return;
|
||
}
|
||
|
||
// Save the unpainted level if necessary
|
||
const CleanupLevel &cl = m_cleanupLevels[m_idx.first];
|
||
|
||
TXshSimpleLevel *sl = cl.m_sl;
|
||
assert(sl);
|
||
|
||
/*--- Nopaintのみ上書きの場合、Cleanup前に戻す ---*/
|
||
if (cl.m_resolution == NOPAINT_ONLY && !m_originalLevelPath.isEmpty()) {
|
||
sl->setPath(m_originalLevelPath);
|
||
sl->invalidateFrames();
|
||
std::vector<TFrameId> fIds;
|
||
sl->getFids(fIds);
|
||
invalidateIcons(sl, fIds);
|
||
m_originalLevelPath = TFilePath();
|
||
}
|
||
/*--- Paletteを更新しない場合は、ここで取っておいたPaletteデータを元に戻す
|
||
* ---*/
|
||
if (m_keepOriginalPalette && m_originalPalette) {
|
||
sl->setPalette(m_originalPalette);
|
||
if (sl == TApp::instance()->getCurrentLevel()->getLevel())
|
||
TApp::instance()
|
||
->getPaletteController()
|
||
->getCurrentLevelPalette()
|
||
->setPalette(m_originalPalette);
|
||
sl->invalidateFrames();
|
||
std::vector<TFrameId> fIds;
|
||
sl->getFids(fIds);
|
||
invalidateIcons(sl, fIds);
|
||
TApp::instance()->getCurrentPalette()->notifyPaletteSwitched();
|
||
}
|
||
|
||
// Close the level updater. Silence any exception (ok, something went bad, old
|
||
// story now).
|
||
try {
|
||
m_updater->close();
|
||
} catch (...) {
|
||
}
|
||
|
||
if (sl->getType() == TZP_XSHLEVEL &&
|
||
Preferences::instance()->isSaveUnpaintedInCleanupEnable() &&
|
||
cl.m_resolution !=
|
||
NOPAINT_ONLY) /*--- 再Cleanupの場合は既にNoPaintに上書きしている ---*/
|
||
{
|
||
const TFilePath &outputPath = cl.m_outputPath;
|
||
|
||
ToonzScene *scene = TApp::instance()->getCurrentScene()->getScene();
|
||
TFilePath decodedPath(scene->decodeFilePath(outputPath));
|
||
|
||
if (outputPath.getLevelNameW().find(L"-np.") == std::wstring::npos &&
|
||
TFileStatus(decodedPath).doesExist()) {
|
||
saveUnpaintedLevel(decodedPath, sl, m_cleanuppedLevelFrames,
|
||
(m_keepOriginalPalette && m_originalPalette));
|
||
}
|
||
}
|
||
|
||
m_cleanuppedLevelFrames.clear();
|
||
m_firstLevelFrame = true;
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
/*-- 各フレームの処理 --*/
|
||
void CleanupPopup::cleanupFrame() {
|
||
assert(isValidPosition(m_idx));
|
||
|
||
TRasterImageP original(currentImage());
|
||
if (!original) return;
|
||
|
||
CleanupLevel &cl = m_cleanupLevels[m_idx.first];
|
||
TXshSimpleLevel *sl = cl.m_sl;
|
||
const TFrameId &fid = cl.m_frames[m_idx.second];
|
||
|
||
assert(sl);
|
||
|
||
// Perform image cleanup
|
||
{
|
||
TCleanupper *cl = TCleanupper::instance();
|
||
const CleanupParameters *params = cl->getParameters();
|
||
|
||
if (params->m_lineProcessingMode == lpNone) {
|
||
// No line processing
|
||
|
||
TRasterImageP ri(original);
|
||
if (params->m_autocenterType != CleanupTypes::AUTOCENTER_NONE) {
|
||
bool autocentered;
|
||
ri = cl->autocenterOnly(original, false, autocentered);
|
||
if (!autocentered)
|
||
DVGui::warning(
|
||
QObject::tr("The autocentering failed on the current drawing."));
|
||
}
|
||
|
||
sl->setFrame(fid, ri);
|
||
|
||
// Update the associated file. In case the operation throws, oh well the
|
||
// image gets skipped.
|
||
try {
|
||
m_updater->update(fid, ri);
|
||
} catch (...) {
|
||
}
|
||
|
||
IconGenerator::instance()->invalidate(sl, fid);
|
||
} else {
|
||
// Perform main processing
|
||
|
||
// Obtain the source dpi. Changed it to be done once at the first frame of
|
||
// each level in order to avoid the following problem:
|
||
// If the original raster level has no dpi (such as TGA images), obtaining
|
||
// dpi in every frame causes dpi mismatch between the first frame and the
|
||
// following frames, since the value
|
||
// TXshSimpleLevel::m_properties->getDpi() will be changed to the
|
||
// dpi of cleanup camera (= TLV's dpi) after finishing the first frame.
|
||
if (m_firstLevelFrame) {
|
||
TPointD dpi;
|
||
original->getDpi(dpi.x, dpi.y);
|
||
if (dpi.x == 0 && dpi.y == 0) dpi = sl->getProperties()->getDpi();
|
||
cl->setSourceDpi(dpi);
|
||
}
|
||
|
||
CleanupPreprocessedImage *cpi;
|
||
{
|
||
TRasterImageP resampledRaster;
|
||
cpi = cl->process(original, m_firstLevelFrame, resampledRaster);
|
||
}
|
||
|
||
if (!cpi) return;
|
||
|
||
// Perform post-processing
|
||
TToonzImageP ti(cl->finalize(cpi));
|
||
|
||
/*--- Cleanup Default Paletteを作成、適用 ---*/
|
||
if (m_firstLevelFrame) {
|
||
addCleanupDefaultPalette(sl);
|
||
sl->getPalette()->setPaletteName(sl->getName());
|
||
}
|
||
|
||
ti->setPalette(sl->getPalette()); // Assigned to sl in setupLevel()
|
||
assert(sl->getPalette());
|
||
|
||
// Update the level data about the cleanupped frame
|
||
sl->setFrameStatus(fid,
|
||
sl->getFrameStatus(fid) | TXshSimpleLevel::Cleanupped);
|
||
sl->setFrame(fid, TImageP()); // Invalidate the old image data
|
||
|
||
// Output the cleanupped image to disk
|
||
try {
|
||
m_updater->update(fid, ti);
|
||
} // The file image data will be reloaded upon request
|
||
catch (...) {
|
||
}
|
||
|
||
// Invalidate icons
|
||
IconGenerator::instance()->invalidate(sl, fid);
|
||
|
||
int autocenterType = params->m_autocenterType;
|
||
if (autocenterType == CleanupTypes::AUTOCENTER_FDG &&
|
||
!cpi->m_autocentered)
|
||
DVGui::warning(
|
||
QObject::tr("The autocentering failed on the current drawing."));
|
||
|
||
delete cpi;
|
||
|
||
if (m_firstLevelFrame) {
|
||
// Update result-dependent level data
|
||
TPointD dpi(0, 0);
|
||
ti->getDpi(dpi.x, dpi.y);
|
||
if (dpi.x != 0 && dpi.y != 0) sl->getProperties()->setDpi(dpi);
|
||
|
||
sl->getProperties()->setImageRes(ti->getSize());
|
||
sl->getProperties()->setBpp(32);
|
||
}
|
||
}
|
||
}
|
||
|
||
// this enables to view the level during cleanup by another user. this
|
||
// behavior may abort Toonz.
|
||
/*
|
||
try { m_updater->flush(); } // Release
|
||
the opened level from writing
|
||
catch(...) {} // It is
|
||
required to have it open for read
|
||
// when
|
||
rebuilding icons... (still dangerous though)
|
||
*/
|
||
m_firstLevelFrame = false;
|
||
m_cleanuppedLevelFrames.push_back(fid);
|
||
|
||
TApp *app = TApp::instance();
|
||
app->getCurrentLevel()->notifyLevelChange();
|
||
app->getCurrentXsheet()->notifyXsheetChanged();
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
|
||
void CleanupPopup::advanceFrame() {
|
||
assert(isValidPosition(m_idx));
|
||
|
||
std::pair<int, int> newIdx = std::make_pair(m_idx.first, m_idx.second + 1);
|
||
|
||
// In case the level was completely processed, close down the old level
|
||
if (!isValidPosition(newIdx)) {
|
||
if (!m_cleanuppedLevelFrames.empty()) closeLevel();
|
||
|
||
newIdx = std::make_pair(m_idx.first + 1, 0);
|
||
}
|
||
|
||
// Advance in the cleanup list
|
||
m_idx = newIdx;
|
||
|
||
if (m_imgViewBox->isChecked()) {
|
||
TImageP image = currentImage();
|
||
if (image) m_imageViewer->setImage(image);
|
||
}
|
||
|
||
// show the progress in the mainwindow's title bar
|
||
updateTitleString();
|
||
|
||
// Update the progress bar
|
||
m_progressBar->setValue(++m_completion.first);
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
|
||
void CleanupPopup::onValueChanged(int value) {
|
||
if (value == m_progressBar->maximum()) {
|
||
close();
|
||
return;
|
||
}
|
||
|
||
m_progressLabel->setText(currentString());
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
|
||
void CleanupPopup::onCleanupFrame() {
|
||
/*--- Busy時にボタンをUnableする ---*/
|
||
m_cleanupAllButton->setEnabled(false);
|
||
m_cleanupButton->setEnabled(false);
|
||
m_skipButton->setEnabled(false);
|
||
|
||
/*--- 新しいLevelに取り掛かり始めたとき ---*/
|
||
if (m_cleanuppedLevelFrames.empty()) {
|
||
const QString &err = setupLevel();
|
||
|
||
if (!err.isEmpty()) {
|
||
DVGui::error(err);
|
||
return;
|
||
}
|
||
}
|
||
|
||
cleanupFrame();
|
||
advanceFrame();
|
||
|
||
/*--- ボタンを元に戻す---*/
|
||
m_cleanupAllButton->setEnabled(true);
|
||
m_cleanupButton->setEnabled(true);
|
||
m_skipButton->setEnabled(true);
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
|
||
void CleanupPopup::onSkipFrame() { advanceFrame(); }
|
||
|
||
//-----------------------------------------------------------------------------
|
||
|
||
void CleanupPopup::onCleanupAllFrame() {
|
||
m_cleanupQuestionLabel->hide();
|
||
|
||
/*--- Busy時にボタンをUnableする ---*/
|
||
m_cleanupAllButton->setEnabled(false);
|
||
m_cleanupButton->setEnabled(false);
|
||
m_skipButton->setEnabled(false);
|
||
|
||
while (isValidPosition(m_idx)) {
|
||
if (m_cleanuppedLevelFrames.empty()) {
|
||
const QString &err = setupLevel();
|
||
|
||
if (!err.isEmpty()) {
|
||
DVGui::error(err);
|
||
return;
|
||
}
|
||
}
|
||
|
||
cleanupFrame();
|
||
advanceFrame();
|
||
|
||
QCoreApplication::processEvents(); // Allow cancels to be received
|
||
}
|
||
/*--- ボタンを元に戻す---*/
|
||
m_cleanupAllButton->setEnabled(true);
|
||
m_cleanupButton->setEnabled(true);
|
||
m_skipButton->setEnabled(true);
|
||
|
||
close();
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
|
||
void CleanupPopup::onCancelCleanup() { close(); }
|
||
|
||
//*****************************************************************************
|
||
// CleanupPopup::OverwriteDialog implementation
|
||
//*****************************************************************************
|
||
|
||
CleanupPopup::OverwriteDialog::OverwriteDialog()
|
||
: DVGui::ValidatedChoiceDialog(TApp::instance()->getMainWindow()) {
|
||
setWindowTitle(tr("Warning!"));
|
||
|
||
bool ret = connect(m_buttonGroup, SIGNAL(buttonClicked(int)),
|
||
SLOT(onButtonClicked(int)));
|
||
assert(ret);
|
||
|
||
// Option 1: OVERWRITE
|
||
QRadioButton *radioButton = new QRadioButton;
|
||
radioButton->setText(
|
||
tr("Cleanup all selected drawings overwriting those previously cleaned "
|
||
"up.*"));
|
||
radioButton->setFixedHeight(20);
|
||
radioButton->setChecked(true); // initial option: OVERWRITE
|
||
|
||
m_buttonGroup->addButton(radioButton, OVERWRITE);
|
||
addWidget(radioButton);
|
||
|
||
// Option 2: WRITE_NEW
|
||
radioButton = new QRadioButton;
|
||
radioButton->setText(
|
||
tr("Cleanup only non-cleaned up drawings and keep those previously "
|
||
"cleaned up.*"));
|
||
radioButton->setFixedHeight(20);
|
||
|
||
m_buttonGroup->addButton(radioButton, WRITE_NEW);
|
||
addWidget(radioButton);
|
||
|
||
// Option 3: REPLACE
|
||
radioButton = new QRadioButton;
|
||
radioButton->setText(
|
||
tr("Delete existing level and create a new level with selected drawings "
|
||
"only."));
|
||
radioButton->setFixedHeight(20);
|
||
|
||
m_buttonGroup->addButton(radioButton, REPLACE);
|
||
addWidget(radioButton);
|
||
|
||
// Option 4: ADD_SUFFIX
|
||
QHBoxLayout *suffixLayout = new QHBoxLayout;
|
||
{
|
||
radioButton = new QRadioButton;
|
||
radioButton->setText(tr("Rename the new level adding the suffix "));
|
||
radioButton->setFixedHeight(20);
|
||
|
||
m_buttonGroup->addButton(radioButton, ADD_SUFFIX);
|
||
suffixLayout->addWidget(radioButton);
|
||
|
||
m_suffix = new DVGui::LineEdit;
|
||
m_suffix->setEnabled(false);
|
||
|
||
suffixLayout->addWidget(m_suffix);
|
||
}
|
||
addLayout(suffixLayout); // Couldnt' place it right after allocation,
|
||
// DVGui::Dialog::addLayout() crashed...
|
||
// Option 5: NOPAINT_ONLY
|
||
radioButton = new QRadioButton(this);
|
||
radioButton->setText(
|
||
tr("This is Re-Cleanup. Overwrite only to the no-paint files."));
|
||
radioButton->setFixedHeight(20);
|
||
m_buttonGroup->addButton(radioButton, NOPAINT_ONLY);
|
||
addWidget(radioButton);
|
||
|
||
QLabel *note = new QLabel(tr("* Palette will not be changed."), this);
|
||
note->setStyleSheet("font-size: 10px; font: italic;");
|
||
addWidget(note);
|
||
|
||
endVLayout();
|
||
|
||
layout()->setSizeConstraint(QLayout::SetFixedSize);
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
|
||
void CleanupPopup::OverwriteDialog::reset() {
|
||
ValidatedChoiceDialog::reset();
|
||
m_suffixText.clear();
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
|
||
QString CleanupPopup::OverwriteDialog::acceptResolution(void *obj,
|
||
int resolution,
|
||
bool applyToAll) {
|
||
struct locals {
|
||
static inline QString existsStr(const TFilePath &fp) {
|
||
return tr("File \"%1\" already exists.\nWhat do you want to do?")
|
||
.arg(fp.getQString());
|
||
}
|
||
}; // locals
|
||
|
||
assert(obj);
|
||
|
||
TFilePath &fp = *static_cast<TFilePath *>(obj);
|
||
|
||
QString error;
|
||
|
||
switch (resolution) {
|
||
case NO_RESOLUTION:
|
||
// fp was already found to be invalid
|
||
assert(::exists(fp));
|
||
error = locals::existsStr(fp);
|
||
|
||
// Restore previous apply-to-all options if necessary
|
||
if (!error.isEmpty() && appliedToAll()) {
|
||
assert(!m_suffixText.isEmpty());
|
||
m_suffix->setText(m_suffixText);
|
||
}
|
||
break;
|
||
|
||
case ADD_SUFFIX:
|
||
// Save resolution options if necessary
|
||
if (applyToAll) m_suffixText = m_suffix->text();
|
||
|
||
// Test produced file path
|
||
const TFilePath &fp_suf =
|
||
fp.withName(fp.getWideName() + m_suffix->text().toStdWString());
|
||
|
||
if (::exists(fp_suf))
|
||
error = locals::existsStr(fp_suf);
|
||
else
|
||
fp = fp_suf;
|
||
|
||
break;
|
||
}
|
||
|
||
return error;
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
|
||
void CleanupPopup::OverwriteDialog::initializeUserInteraction(const void *obj) {
|
||
const TFilePath &fp = *static_cast<const TFilePath *>(obj);
|
||
|
||
// Generate a suitable initial suffix
|
||
int num = 1;
|
||
for (; ::exists(fp, num); ++num)
|
||
;
|
||
|
||
m_suffix->setText(::suffix(num));
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
|
||
void CleanupPopup::OverwriteDialog::onButtonClicked(int buttonId) {
|
||
m_suffix->setEnabled(buttonId == ADD_SUFFIX);
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
|
||
void CleanupPopup::onImgViewBoxToggled(bool on) {
|
||
m_imageViewer->setVisible(on);
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
/*! Show the progress in the mainwindow's title bar
|
||
*/
|
||
void CleanupPopup::updateTitleString() {
|
||
if (!TApp::instance()->getMainWindow()) return;
|
||
MainWindow *mainWin =
|
||
qobject_cast<MainWindow *>(TApp::instance()->getMainWindow());
|
||
if (!mainWin) return;
|
||
|
||
QString str = QString::number(m_completion.first) + "/" +
|
||
QString::number(m_completion.second) +
|
||
tr(" : Cleanup in progress");
|
||
|
||
mainWin->changeWindowTitle(str);
|
||
}
|
||
|
||
//*****************************************************************************
|
||
// CleanupCommand definition
|
||
//*****************************************************************************
|
||
|
||
class CleanupCommand final : public MenuItemHandler {
|
||
public:
|
||
CleanupCommand() : MenuItemHandler("MI_Cleanup") {}
|
||
|
||
void execute() override {
|
||
static CleanupPopup *popup = new CleanupPopup;
|
||
popup->execute();
|
||
}
|
||
|
||
} CleanupCommand;
|