Blob Blame Raw


#include "tapp.h"
#include "tpalette.h"
#include "toonz/txsheet.h"
#include "toonz/toonzscene.h"
#include "toonz/levelset.h"
#include "toonz/txshsimplelevel.h"
#include "toonz/txshlevelcolumn.h"
#include "toonz/txshcell.h"
//#include "tw/action.h"
#include "tropcm.h"
#include "ttoonzimage.h"
#include "matchline.h"
#include "toonz/scenefx.h"
#include "toonz/dpiscale.h"
#include "toonz/txsheethandle.h"
#include "toonz/palettecontroller.h"
#include "toonz/tpalettehandle.h"
#include "toonz/txshlevelhandle.h"
#include "toonz/txshleveltypes.h"
#include "toonz/tscenehandle.h"
#include "toonz/tframehandle.h"
#include "toonzqt/icongenerator.h"
#include <map>
#include <QRadioButton>
#include <QPushButton>
#include <QApplication>
#include "tundo.h"
#include "tools/toolutils.h"
#include "timagecache.h"
#include "tcolorstyles.h"

#include "historytypes.h"

using namespace DVGui;

namespace
{

class MatchlinePair
{
public:
	const TXshCell *m_cell;
	TAffine m_imgAff;
	const TXshCell *m_mcell;
	TAffine m_matchAff;

	MatchlinePair(const TXshCell &cell, const TAffine &imgAff,
				  const TXshCell &mcell, const TAffine &matchAff)
		: m_cell(&cell), m_imgAff(imgAff), m_mcell(&mcell), m_matchAff(matchAff){};
};

//-----------------------------------------------------------------------------------

void mergeRasterColumns(const vector<MatchlinePair> &matchingLevels)
{
	if (matchingLevels.empty())
		return;

	int i = 0;
	for (i = 0; i < (int)matchingLevels.size(); i++) {
		TRasterImageP img = (TRasterImageP)matchingLevels[i].m_cell->getImage(true);
		TRasterImageP match = (TRasterImageP)matchingLevels[i].m_mcell->getImage(false);
		if (!img || !match)
			throw TRopException("Can merge columns only on raster images!");
		// img->lock();
		TRaster32P ras = img->getRaster();		  // img->getCMapped(false);
		TRaster32P matchRas = match->getRaster(); // match->getCMapped(true);
		if (!ras || !matchRas) {
			DVGui::warning(QObject::tr("The merge command is not available for greytones images."));
			return;
		}
		TAffine aff = matchingLevels[i].m_imgAff.inv() * matchingLevels[i].m_matchAff;
		int mlx = matchRas->getLx();
		int mly = matchRas->getLy();
		int rlx = ras->getLx();
		int rly = ras->getLy();

		TRectD in = convert(matchRas->getBounds()) - matchRas->getCenterD();

		TRectD out = aff * in;

		TPoint offs((rlx - mlx) / 2 + tround(out.getP00().x - in.getP00().x),
					(rly - mly) / 2 + tround(out.getP00().y - in.getP00().y));

		int lxout = tround(out.getLx()) + 1 + ((offs.x < 0) ? offs.x : 0);
		int lyout = tround(out.getLy()) + 1 + ((offs.y < 0) ? offs.y : 0);

		if (lxout <= 0 || lyout <= 0 || offs.x >= rlx || offs.y >= rly) {
			//tmsg_error("no intersections between matchline and level");
			continue;
		}

		aff = aff.place((double)(in.getLx() / 2.0),
						(double)(in.getLy() / 2.0),
						(out.getLx()) / 2.0 + ((offs.x < 0) ? offs.x : 0),
						(out.getLy()) / 2.0 + ((offs.y < 0) ? offs.y : 0));

		if (offs.x < 0)
			offs.x = 0;
		if (offs.y < 0)
			offs.y = 0;

		if (lxout + offs.x > rlx || lyout + offs.y > rly) {
			//PRINTF("TAGLIO L'IMMAGINE\n");
			lxout = (lxout + offs.x > rlx) ? (rlx - offs.x) : lxout;
			lyout = (lyout + offs.y > rly) ? (rly - offs.y) : lyout;
		}

		if (!aff.isIdentity(1e-4)) {
			TRaster32P aux(lxout, lyout);
			TRop::resample(aux, matchRas, aff);
			matchRas = aux;
		}
		ras->lock();
		matchRas->lock();
		TRect raux = matchRas->getBounds() + offs;
		TRasterP r = ras->extract(raux);

		TRop::over(r, matchRas);

		ras->unlock();
		matchRas->unlock();

		img->setSavebox(img->getSavebox() + (matchRas->getBounds() + offs));
		//img->setSavebox(rout);
		// img->unlock();
	}
}

/*------------------------------------------------------------------------*/
///verifica se tutta l'immagine e' gia' contenuta in un gruppo.
bool needTobeGrouped(const TVectorImageP &vimg)
{
	if (vimg->getStrokeCount() <= 1)
		return false;

	if (!vimg->isStrokeGrouped(0))
		return true;

	for (int i = 1; i < vimg->getStrokeCount(); i++) {
		if (vimg->areDifferentGroup(0, false, i, false) == 0)
			return true;
	}

	return false;
}

//---------------------------------------------------------------------------------------

void mergeVectorColumns(const vector<MatchlinePair> &matchingLevels)
{
	if (matchingLevels.empty())
		return;

	int i = 0;
	for (i = 0; i < (int)matchingLevels.size(); i++) {
		TVectorImageP vimg = (TVectorImageP)matchingLevels[i].m_cell->getImage(true);
		TVectorImageP vmatch = (TVectorImageP)matchingLevels[i].m_mcell->getImage(false);
		if (!vimg || !vmatch)
			throw TRopException("Cannot merge columns of different image types!");
		// img->lock();

		if (needTobeGrouped(vimg))
			vimg->group(0, vimg->getStrokeCount());
		bool ungroup = false;
		if (needTobeGrouped(vmatch)) {
			ungroup = true;
			vmatch->group(0, vmatch->getStrokeCount());
		}
		TAffine aff = matchingLevels[i].m_imgAff.inv() * matchingLevels[i].m_matchAff;
		vimg->mergeImage(vmatch, aff);
		if (ungroup)
			vmatch->ungroup(0);
	}
}

/*------------------------------------------------------------------------*/

/*------------------------------------------------------------------------*/
} //namespace
//-----------------------------------------------------------------------------

class MergeColumnsUndo : public TUndo
{
	TXshLevelP m_xl;
	int m_matchlineSessionId;
	std::map<TFrameId, QString> m_images;
	TXshSimpleLevel *m_level;

	int m_column, m_mColumn;
	TPalette *m_palette;

public:
	MergeColumnsUndo(TXshLevelP xl, int matchlineSessionId,
					 int column, TXshSimpleLevel *level, const std::map<TFrameId, QString> &images,
					 int mColumn, TPalette *palette)
		: TUndo(), m_xl(xl), m_matchlineSessionId(matchlineSessionId), m_level(level), m_column(column), m_mColumn(mColumn), m_images(images), m_palette(palette->clone())
	{
	}

	void undo() const
	{
		QApplication::setOverrideCursor(Qt::WaitCursor);

		std::map<TFrameId, QString>::const_iterator it = m_images.begin();

		vector<TFrameId> fids;
		m_level->setPalette(m_palette);
		for (; it != m_images.end(); ++it) //, ++mit)
		{
			QString id = "MergeColumnsUndo" + QString::number(m_matchlineSessionId) + "-" + QString::number(it->first.getNumber());
			TImageP img = TImageCache::instance()->get(id, false)->cloneImage();
			m_level->setFrame(it->first, img);
			fids.push_back(it->first);
		}

		if (m_xl)
			invalidateIcons(m_xl.getPointer(), fids);

		m_level->setDirtyFlag(true);
		TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
		QApplication::restoreOverrideCursor();
	}

	void redo() const
	{
		QApplication::setOverrideCursor(Qt::WaitCursor);

		mergeColumns(m_column, m_mColumn, true);

		QApplication::restoreOverrideCursor();
	}

	int getSize() const
	{
		return sizeof(*this);
	}

	~MergeColumnsUndo()
	{
		std::map<TFrameId, QString>::const_iterator it = m_images.begin();
		for (; it != m_images.end(); ++it) //, ++mit)
		{
			QString id = "MergeColumnsUndo" + QString::number((uintptr_t) this) + "-" + QString::number(it->first.getNumber());
			TImageCache::instance()->remove(id);
		}
	}

	QString getHistoryString()
	{
		return QObject::tr("Merge Raster Levels");
	}
	int getHistoryType()
	{
		return HistoryType::FilmStrip;
	}
};

//-----------------------------------------------------------------------------

void mergeColumns(const std::set<int> &columns)
{
	QApplication::setOverrideCursor(Qt::WaitCursor);

	std::set<int>::const_iterator it = columns.begin();

	int dstColumn = *it;
	++it;

	TUndoManager::manager()->beginBlock();

	for (; it != columns.end(); ++it)
		mergeColumns(dstColumn, *it, false);

	TUndoManager::manager()->endBlock();

	QApplication::restoreOverrideCursor();
}

//-----------------------------------------------------------------------------

static int MergeColumnsSessionId = 0;

void mergeColumns(int column, int mColumn, bool isRedo)
{
	MergeColumnsSessionId++;
	TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
	int start, end;
	xsh->getCellRange(column, start, end);

	if (start > end)
		return;
	vector<TXshCell> cell(end - start + 1);
	vector<TXshCell> mCell(end - start + 1);

	xsh->getCells(start, column, cell.size(), &(cell[0]));

	xsh->getCells(start, mColumn, cell.size(), &(mCell[0]));

	TXshColumn *col = xsh->getColumn(column);
	TXshColumn *mcol = xsh->getColumn(mColumn);

	vector<MatchlinePair> matchingLevels;

	std::set<TFrameId> alreadyDoneSet;

	TXshSimpleLevel *level = 0, *mLevel = 0;
	TXshLevelP xl;
	bool areRasters = false;

	std::map<TFrameId, QString> images;

	for (int i = 0; i < (int)cell.size(); i++) {
		if (cell[i].isEmpty() || mCell[i].isEmpty())
			continue;
		if (!level) {
			level = cell[i].getSimpleLevel();
			xl = cell[i].m_level;
		}

		else if (level != cell[i].getSimpleLevel()) {
			DVGui::warning(QObject::tr("It is not possible to perform a merging involving more than one level per column."));
			return;
		}

		if (!mLevel)
			mLevel = mCell[i].getSimpleLevel();
		else if (mLevel != mCell[i].getSimpleLevel()) {
			DVGui::warning(QObject::tr("It is not possible to perform a merging involving more than one level per column."));
			return;
		}
		TImageP img = cell[i].getImage(true);
		TImageP match = mCell[i].getImage(false);
		TFrameId fid = cell[i].m_frameId;
		TFrameId mFid = mCell[i].m_frameId;

		if (!img || !match)
			continue;

		if (alreadyDoneSet.find(fid) == alreadyDoneSet.end()) {
			TRasterImageP timg = (TRasterImageP)img;
			TRasterImageP tmatch = (TRasterImageP)match;
			TVectorImageP vimg = (TVectorImageP)img;
			TVectorImageP vmatch = (TVectorImageP)match;

			if (timg) {
				if (!tmatch) {
					DVGui::warning(QObject::tr("Only raster levels can be merged to a raster level."));
					return;
				}
				areRasters = true;
			} else if (vimg) {
				if (!vmatch) {
					DVGui::warning(QObject::tr("Only vector levels can be merged to a vector level."));
					return;
				}
			} else {
				DVGui::warning(QObject::tr("It is possible to merge only Toonz vector levels or standard raster levels."));
				return;
			}

			QString id = "MergeColumnsUndo" + QString::number(MergeColumnsSessionId) + "-" + QString::number(fid.getNumber());
			TImageCache::instance()->add(id, (timg) ? timg->cloneImage() : vimg->cloneImage());
			images[fid] = id;
			TAffine imgAff, matchAff;
			getColumnPlacement(imgAff, xsh, start + i, column, false);
			getColumnPlacement(matchAff, xsh, start + i, mColumn, false);
			TAffine dpiAff = getDpiAffine(level, fid);
			TAffine mdpiAff = getDpiAffine(mLevel, mFid);
			matchingLevels.push_back(MatchlinePair(cell[i], imgAff * dpiAff, mCell[i], matchAff * mdpiAff));
			alreadyDoneSet.insert(fid);
		}
	}

	if (matchingLevels.empty()) {
		DVGui::warning(QObject::tr("It is possible to merge only Toonz vector levels or standard raster levels."));
		return;
	}

	ToonzScene *sc = TApp::instance()->getCurrentScene()->getScene();
	TXshSimpleLevel *simpleLevel = sc->getLevelSet()->getLevel(column)->getSimpleLevel();

	if (!isRedo)
		TUndoManager::manager()->add(new MergeColumnsUndo(xl, MergeColumnsSessionId,
														  column, level, images,
														  mColumn, level->getPalette()));

	if (areRasters) {
		mergeRasterColumns(matchingLevels);
		for (int i = 0; i < (int)cell.size(); i++) //the saveboxes must be updated
		{
			if (cell[i].isEmpty() || mCell[i].isEmpty())
				continue;

			if (!cell[i].getImage(false) || !mCell[i].getImage(false))
				continue;

			ToolUtils::updateSaveBox(cell[i].getSimpleLevel(), cell[i].m_frameId);
		}
	} else
		mergeVectorColumns(matchingLevels);

	TXshLevel *sl = TApp::instance()->getCurrentScene()->getScene()->getLevelSet()->getLevel(column);
	vector<TFrameId> fidsss;
	sl->getFids(fidsss);
	invalidateIcons(sl, fidsss);
	sl->setDirtyFlag(true);
	level->setDirtyFlag(true);
	TApp::instance()->getCurrentXsheet()->notifyXsheetChanged();
}

namespace
{

const TXshCell *findCell(int column, const TFrameId &fid)
{
	TXsheet *xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
	int i;
	for (i = 0; i < xsh->getColumn(column)->getMaxFrame(); i++)
		if (xsh->getCell(i, column).getFrameId() == fid)
			return &(xsh->getCell(i, column));
	return 0;
}

bool contains(const vector<TFrameId> &v, const TFrameId &val)
{
	int i;
	for (i = 0; i < (int)v.size(); i++)
		if (v[i] == val)
			return true;
	return false;
}

//-----------------------------------------------------------------------------

QString indexes2string(const std::set<TFrameId> fids)
{
	if (fids.empty())
		return "";

	QString str;

	std::set<TFrameId>::const_iterator it = fids.begin();

	str = QString::number(it->getNumber());

	while (it != fids.end()) {
		std::set<TFrameId>::const_iterator it1 = it;
		it1++;

		int lastVal = it->getNumber();
		while (it1 != fids.end() && it1->getNumber() == lastVal + 1) {
			lastVal = it1->getNumber();
			it1++;
		}

		if (lastVal != it->getNumber())
			str += "-" + QString::number(lastVal);
		if (it1 == fids.end())
			return str;

		str += ", " + QString::number(it1->getNumber());

		it = it1;
	}

	return str;
}

} //namespace