Blob Blame Raw


// TnzCore includes
#include "tsystem.h"
#include "tstream.h"
#include "tthreadmessage.h"
#include "tconvert.h"
#include "tstopwatch.h"
#include "tlevel_io.h"
#include "tflash.h"
#include "trasterimage.h"
#include "ttoonzimage.h"
#include "tvectorimage.h"
#include "timagecache.h"
#include "timageinfo.h"
#include "tropcm.h"
#include "tofflinegl.h"
#include "tvectorrenderdata.h"

// TnzBase includes
#include "ttzpimagefx.h"
#include "trasterfx.h"
#include "tzeraryfx.h"
#include "trenderer.h"
#include "tfxcachemanager.h"

// TnzLib includes
#include "toonz/toonzscene.h"
#include "toonz/txsheet.h"
#include "toonz/txshlevelcolumn.h"
#include "toonz/txshpalettecolumn.h"
#include "toonz/txshzeraryfxcolumn.h"
#include "toonz/txshlevel.h"
#include "toonz/txshcell.h"
#include "toonz/txshsimplelevel.h"
#include "toonz/txshpalettelevel.h"
#include "toonz/txshleveltypes.h"
#include "toonz/levelset.h"
#include "toonz/txshchildlevel.h"
#include "toonz/fxdag.h"
#include "toonz/tcolumnfxset.h"
#include "toonz/stage.h"
#include "toonz/fill.h"
#include "toonz/tstageobjectid.h"
#include "toonz/tstageobject.h"
#include "toonz/levelproperties.h"
#include "toonz/imagemanager.h"
#include "toonz/toonzimageutils.h"
#include "toonz/tvectorimageutils.h"
#include "toonz/preferences.h"
#include "toonz/dpiscale.h"
#include "imagebuilders.h"

// 4.6 compatibility - sandor fxs
#include "toonz4.6/raster.h"
#include "sandor_fxs/blend.h"
extern "C" {
#include "sandor_fxs/calligraph.h"
#include "sandor_fxs/patternmap.h"
}

#include "toonz/tcolumnfx.h"

//****************************************************************************************
//    Preliminaries
//****************************************************************************************

#ifdef _WIN32
template class DV_EXPORT_API TFxDeclarationT<TLevelColumnFx>;
template class DV_EXPORT_API TFxDeclarationT<TZeraryColumnFx>;
template class DV_EXPORT_API TFxDeclarationT<TXsheetFx>;
template class DV_EXPORT_API TFxDeclarationT<TOutputFx>;
#endif

TFxDeclarationT<TLevelColumnFx> columnFxInfo(TFxInfo("Toonz_columnFx", true));
TFxDeclarationT<TPaletteColumnFx> paletteColumnFxInfo(TFxInfo("Toonz_paletteColumnFx", true));
TFxDeclarationT<TZeraryColumnFx> zeraryColumnFxInfo(TFxInfo("Toonz_zeraryColumnFx", true));
TFxDeclarationT<TXsheetFx> infoTXsheetFx(TFxInfo("Toonz_xsheetFx", true));
TFxDeclarationT<TOutputFx> infoTOutputFx(TFxInfo("Toonz_outputFx", true));

//****************************************************************************************
//    Local namespace  -  misc functions
//****************************************************************************************

namespace
{

void setMaxMatte(TRasterP r)
{
	TRaster32P r32 = (TRaster32P)r;

	TRaster64P r64 = (TRaster64P)r;

	if (r32)
		for (int i = 0; i < r32->getLy(); i++) {
			TPixel *pix = r32->pixels(i);
			for (int j = 0; j < r32->getLx(); j++, pix++)
				pix->m = 255;
		}
	else if (r64)
		for (int i = 0; i < r64->getLy(); i++) {
			TPixel64 *pix = r64->pixels(i);
			for (int j = 0; j < r64->getLx(); j++, pix++)
				pix->m = 65535;
		}
}

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

char *strsave(const char *t)
{
	char *s;
	s = (char *)malloc(strlen(t) + 1); // I'm almost sure that this malloc is LEAKED! Please, check that !
	strcpy(s, t);
	return s;
}

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

inline TRect myConvert(const TRectD &r)
{
	return TRect(tfloor(r.x0), tfloor(r.y0), tceil(r.x1), tceil(r.y1));
}

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

inline TRect myConvert(const TRectD &r, TPointD &dp)
{
	TRect ri(tfloor(r.x0), tfloor(r.y0), tceil(r.x1), tceil(r.y1));
	dp.x = r.x0 - ri.x0;
	dp.y = r.y0 - ri.y0;
	assert(dp.x >= 0 && dp.y >= 0);
	return ri;
}

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

// Currently used on debug only
inline QString traduce(const TRectD &rect)
{
	return "[" + QString::number(rect.x0) + " " + QString::number(rect.y0) + " " +
		   QString::number(rect.x1) + " " + QString::number(rect.y1) + "]";
}

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

// Currently used on debug only
inline QString traduce(const TRectD &rect, const TRenderSettings &info)
{
	return "[" + QString::number(rect.x0) + " " + QString::number(rect.y0) + " " +
		   QString::number(rect.x1) + " " + QString::number(rect.y1) + "]; aff = (" +
		   QString::number(info.m_affine.a11, 'f') + " " +
		   QString::number(info.m_affine.a12, 'f') + " " +
		   QString::number(info.m_affine.a13, 'f') + " " +
		   QString::number(info.m_affine.a21, 'f') + " " +
		   QString::number(info.m_affine.a22, 'f') + " " +
		   QString::number(info.m_affine.a23, 'f') + ")";
}

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

inline int colorDistance(const TPixel32 &c0, const TPixel32 &c1)
{
	return (c0.r - c1.r) * (c0.r - c1.r) + (c0.g - c1.g) * (c0.g - c1.g) + (c0.b - c1.b) * (c0.b - c1.b);
}

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

std::string getAlias(TXsheet *xsh, double frame, const TRenderSettings &info)
{
	TFxSet *fxs = xsh->getFxDag()->getTerminalFxs();
	std::string alias;

	//Add the alias for each
	for (int i = 0; i < fxs->getFxCount(); ++i) {
		TRasterFx *fx = dynamic_cast<TRasterFx *>(fxs->getFx(i));
		assert(fx);
		if (!fx)
			continue;

		alias += fx->getAlias(frame, info) + ";";
	}

	return alias;
}

} // namespace

//****************************************************************************************
//    Local namespace  -  Colormap (Sandor) Fxs stuff
//****************************************************************************************

bool vectorMustApplyCmappedFx(const std::vector<TRasterFxRenderDataP> &fxs)
{
	std::vector<TRasterFxRenderDataP>::const_iterator ft, fEnd(fxs.end());
	for (ft = fxs.begin(); ft != fEnd; ++ft) {
		PaletteFilterFxRenderData *paletteFilterData = dynamic_cast<PaletteFilterFxRenderData *>(ft->getPointer());
		SandorFxRenderData *sandorData = dynamic_cast<SandorFxRenderData *>(ft->getPointer());

		// (Daniele) Sandor fxs perform on raster colormaps *only* - while texture fxs use palette filters to work.
		// In the latter case, vector-to-colormap conversion makes sure that regions are not rendered under full ink
		// pixels (which causes problems**).
		if (sandorData || (paletteFilterData && paletteFilterData->m_type != ::eApplyToInksAndPaints))
			return true;

		/*
      (Daniele) Disregarding the above reasons - palette filter fxs do not forcibly return true.
      
      Err... ok, my fault - when I wrote that, I forgot to specify WHICH problems** occurred. Whops!

      Now, it happens that if palette filters DO NOT convert to colormapped forcedly, special styles can be
      retained... so, let's see what happens.
      
      Will have to inquire further, though...
    */
	}

	return false;
}

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

bool mustApplySandorFx(const std::vector<TRasterFxRenderDataP> &fxs)
{
	std::vector<TRasterFxRenderDataP>::const_iterator ft, fEnd(fxs.end());
	for (ft = fxs.begin(); ft != fEnd; ++ft) {
		SandorFxRenderData *sandorData = dynamic_cast<SandorFxRenderData *>(ft->getPointer());

		if (sandorData)
			return true;
	}
	return false;
}

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

int getEnlargement(const std::vector<TRasterFxRenderDataP> &fxs, double scale)
{
	int enlargement = 1;

	std::vector<TRasterFxRenderDataP>::const_iterator ft, fEnd(fxs.end());
	for (ft = fxs.begin(); ft != fEnd; ++ft) {
		SandorFxRenderData *sandorData = dynamic_cast<SandorFxRenderData *>(ft->getPointer());

		if (sandorData) {
			switch (sandorData->m_type) {
			case BlendTz: {
				//Nothing happen, unless we have color 0 among the blended ones. In such case,
				//we have to enlarge the bbox proportionally to the amount param.
				std::vector<std::string> items;
				std::string indexes = std::string(sandorData->m_argv[0]);
				parseIndexes(indexes, items);
				PaletteFilterFxRenderData paletteFilterData;
				insertIndexes(items, &paletteFilterData);

				if (!paletteFilterData.m_colors.empty() && *paletteFilterData.m_colors.begin() == 0) {
					BlendTzParams &params = sandorData->m_blendParams;
					enlargement = params.m_amount * scale;
				}
			}

				CASE Calligraphic : __OR OutBorder:
				{
					CalligraphicParams &params = sandorData->m_callParams;
					enlargement = params.m_thickness * scale;
				}

				CASE ArtAtContour:
				{
					ArtAtContourParams &params = sandorData->m_contourParams;
					enlargement =
						tmax(tceil(sandorData->m_controllerBBox.getLx()), tceil(sandorData->m_controllerBBox.getLy())) *
						params.m_maxSize;
				}
			}
		}
	}

	return enlargement;
}

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

void applyPaletteFilter(TPalette *&plt, bool keep, const set<int> &colors, const TPalette *srcPlt)
{
	if (colors.empty())
		return;

	if (!plt)
		plt = srcPlt->clone();

	if (keep) {
		for (int i = 0; i < plt->getStyleCount(); ++i) {
			if (colors.find(i) == colors.end())
				plt->setStyle(i, TPixel32::Transparent);
		}
	} else {
		std::set<int>::const_iterator ct, cEnd(colors.end());
		for (ct = colors.begin(); ct != cEnd; ++ct) {
			TColorStyle *style = plt->getStyle(*ct);
			if (style)
				plt->setStyle(*ct, TPixel32::Transparent);
		}
	}
}

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

TPalette *getPliPalette(const TFilePath &path)
{
	TLevelReaderP levelReader = TLevelReaderP(path);
	if (!levelReader.getPointer())
		return 0;

	TLevelP level = levelReader->loadInfo();
	TPalette *plt = level->getPalette();

	return plt ? plt->clone() : (TPalette *)0;
}

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

inline bool fxLess(TRasterFxRenderDataP a, TRasterFxRenderDataP b)
{
	SandorFxRenderData *sandorDataA = dynamic_cast<SandorFxRenderData *>(a.getPointer());
	if (!sandorDataA)
		return false;

	SandorFxRenderData *sandorDataB = dynamic_cast<SandorFxRenderData *>(b.getPointer());
	if (!sandorDataB)
		return true;

	int aIndex =
		sandorDataA->m_type == OutBorder ? 2 : sandorDataA->m_type == BlendTz ? 1 : 0;
	int bIndex =
		sandorDataB->m_type == OutBorder ? 2 : sandorDataB->m_type == BlendTz ? 1 : 0;

	return aIndex < bIndex;
}

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

inline void sortCmappedFxs(std::vector<TRasterFxRenderDataP> &fxs)
{
	std::stable_sort(fxs.begin(), fxs.end(), fxLess);
}

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

std::vector<int> getAllBut(std::vector<int> &colorIds)
{
	assert(TPixelCM32::getMaxInk() == TPixelCM32::getMaxPaint());

	std::vector<int> curColorIds;
	std::sort(colorIds.begin(), colorIds.end());

	// Taking all colors EXCEPT those in colorIds
	unsigned int count1 = 0, count2 = 0;
	int size = TPixelCM32::getMaxInk();

	curColorIds.resize(size + 1 - colorIds.size());
	for (int i = 0; i < size; i++)
		if (count1 < colorIds.size() && colorIds[count1] == i)
			count1++;
		else
			curColorIds[count2++] = i;

	return curColorIds;
}

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

//! \b IMPORTANT \b NOTE: This function is now written so that the passed Toonz Image
//! will be destroyed at the most appropriate time. You should definitely *COPY* all
//! necessary informations before calling it - however, since the intent was that of
//! optimizing memory usage, please avoid copying the entire image buffer...

TImageP applyCmappedFx(TToonzImageP &ti, const std::vector<TRasterFxRenderDataP> &fxs, int frame, double scale)
{
	TImageP result = ti;
	TTile resultTile; //Just a quick wrapper to the ImageCache
	TPalette *inPalette, *tempPlt;
	TPaletteP filteredPalette;
	TRasterCM32P copyRas;
	std::string cmRasCacheId;

	//Retrieve the image dpi
	double dpiX, dpiY;
	ti->getDpi(dpiX, dpiY);
	double dpi = (dpiX > 0) ? dpiX / Stage::inch : 1.0;

	//First, sort the fxs.
	std::vector<TRasterFxRenderDataP> fxsCopy = fxs;
	sortCmappedFxs(fxsCopy);

	//First, let's deal with all fxs working on palettes
	inPalette = ti->getPalette();

	std::vector<TRasterFxRenderDataP>::reverse_iterator it;
	for (it = fxsCopy.rbegin(); it != fxsCopy.rend(); ++it) {
		ExternalPaletteFxRenderData *extpltData = dynamic_cast<ExternalPaletteFxRenderData *>((*it).getPointer());
		PaletteFilterFxRenderData *PaletteFilterData = dynamic_cast<PaletteFilterFxRenderData *>((*it).getPointer());
		if (extpltData && extpltData->m_palette) {
			filteredPalette = extpltData->m_palette->clone();
			filteredPalette->setFrame(frame);
		} else if (PaletteFilterData && PaletteFilterData->m_type == eApplyToInksAndPaints) {
			bool keep = PaletteFilterData->m_keep;

			set<int> colors;
			colors.insert(PaletteFilterData->m_colors.begin(), PaletteFilterData->m_colors.end());

			//Apply the palette filters
			tempPlt = filteredPalette.getPointer();
			applyPaletteFilter(tempPlt, keep, colors, inPalette);
			filteredPalette = tempPlt;

			inPalette = filteredPalette.getPointer();
		}
	}

	if (filteredPalette) {
		//result= ti = ti->clone();   //Is copying truly necessary??
		result = ti = TToonzImageP(ti->getRaster(), ti->getSavebox());
		filteredPalette->setFrame(frame);
		ti->setPalette(filteredPalette.getPointer());
	}

	//Next, deal with fxs working on colormaps themselves
	bool firstSandorFx = true;
	TRasterCM32P cmRas = ti->getRaster();
	TRect tiSaveBox(ti->getSavebox());
	TPaletteP tiPalette(ti->getPalette());
	ti = 0; //Release the reference to colormap

	//Now, apply cmapped->cmapped fxs
	for (it = fxsCopy.rbegin(); it != fxsCopy.rend(); ++it) {
		PaletteFilterFxRenderData *PaletteFilterData = dynamic_cast<PaletteFilterFxRenderData *>(it->getPointer());
		if (PaletteFilterData && PaletteFilterData->m_type != eApplyToInksAndPaints) {
			std::vector<int> indexes;
			indexes.resize(PaletteFilterData->m_colors.size());

			set<int>::const_iterator jt = PaletteFilterData->m_colors.begin();
			for (int j = 0; j < (int)indexes.size(); ++j, ++jt)
				indexes[j] = *jt;

			if (copyRas == TRasterCM32P())
				copyRas = cmRas->clone(); //Pixels are literally cleared on a copy buffer

			/*-- 処理するIndexを反転 --*/
			if (PaletteFilterData->m_keep)
				indexes = getAllBut(indexes);

			/*-- Paintの消去("Lines Including All Areas"のみ、Areaに何も操作をしない) --*/
			if (PaletteFilterData->m_type != eApplyToInksKeepingAllPaints) //se non e' eApplyToInksKeepingAllPaints, sicuramente devo cancellare dei paint
				TRop::eraseColors(copyRas, PaletteFilterData->m_type == eApplyToInksDeletingAllPaints ? 0 : &indexes, false);

			/*-- Inkの消去 --*/
			if (PaletteFilterData->m_type != eApplyToPaintsKeepingAllInks) //se non e' eApplyToPaintsKeepingAllInks, sicuramente devo cancellare degli ink
				TRop::eraseColors(copyRas, PaletteFilterData->m_type == eApplyToPaintsDeletingAllInks ? 0 : &indexes, true);
		}
	}

	if (copyRas) {
		cmRas = copyRas;
		result = TToonzImageP(cmRas, tiSaveBox);
		result->setPalette(tiPalette.getPointer());
	}

	//Finally, apply cmapped->fullcolor fxs

	//Prefetch all Blend fxs
	std::vector<BlendParam> blendParams;
	for (it = fxsCopy.rbegin(); it != fxsCopy.rend(); ++it) {
		SandorFxRenderData *sandorData = dynamic_cast<SandorFxRenderData *>(it->getPointer());
		if (sandorData && sandorData->m_type == BlendTz) {
			BlendParam param;

			param.intensity = toDouble(std::string(sandorData->m_argv[3])) * scale * dpi;
			param.smoothness = sandorData->m_blendParams.m_smoothness;
			param.stopAtCountour = sandorData->m_blendParams.m_noBlending;

			param.superSampling = sandorData->m_blendParams.m_superSampling;

			//Build the color indexes
			std::vector<std::string> items;
			std::string indexes = std::string(sandorData->m_argv[0]);
			parseIndexes(indexes, items);
			PaletteFilterFxRenderData paletteFilterData;
			insertIndexes(items, &paletteFilterData);

			param.colorsIndexes.reserve(paletteFilterData.m_colors.size());

			std::set<int>::iterator it;
			for (it = paletteFilterData.m_colors.begin(); it != paletteFilterData.m_colors.end(); ++it)
				param.colorsIndexes.push_back(*it);

			blendParams.push_back(param);
		}
	}

	//Apply each sandor
	for (it = fxsCopy.rbegin(); it != fxsCopy.rend(); ++it) {
		SandorFxRenderData *sandorData = dynamic_cast<SandorFxRenderData *>(it->getPointer());

		if (sandorData) {
			//Avoid dealing with specific cases
			if ((sandorData->m_type == BlendTz && blendParams.empty()) ||
				(sandorData->m_type == OutBorder && !firstSandorFx))
				continue;

			if (!firstSandorFx) {
				//Retrieve the colormap from cache
				cmRas = TToonzImageP(TImageCache::instance()->get(cmRasCacheId, true))->getRaster();

				//Apply a palette filter in order to keep only the colors specified in the sandor argv
				std::vector<std::string> items;
				std::string indexes = std::string(sandorData->m_argv[0]);
				parseIndexes(indexes, items);
				PaletteFilterFxRenderData paletteFilterData;
				insertIndexes(items, &paletteFilterData);

				filteredPalette = tempPlt = 0;
				applyPaletteFilter(tempPlt, true, paletteFilterData.m_colors, tiPalette.getPointer());
				filteredPalette = tempPlt;
			} else {
				//Pass the input colormap to the cache and release its reference as final result
				cmRasCacheId = TImageCache::instance()->getUniqueId();
				TImageCache::instance()->add(cmRasCacheId, TToonzImageP(cmRas, tiSaveBox));
				result = 0;
			}

			//Convert current smart pointers to a 4.6 'fashion'. The former ones are released - so they
			//do not occupy smart object references.
			RASTER *oldRasterIn, *oldRasterOut;
			oldRasterIn = TRop::convertRaster50to46(cmRas, filteredPalette ? filteredPalette : tiPalette.getPointer());
			cmRas = TRasterCM32P(0);
			{
				TRaster32P rasterOut(TDimension(oldRasterIn->lx, oldRasterIn->ly));
				oldRasterOut = TRop::convertRaster50to46(rasterOut, 0);
			}

			switch (sandorData->m_type) {
			case BlendTz: {
				if (blendParams.empty())
					continue;

				//Retrieve the colormap from cache
				cmRas = TToonzImageP(TImageCache::instance()->get(cmRasCacheId, true))->getRaster();

				TToonzImageP ti(cmRas, tiSaveBox);
				ti->setPalette(filteredPalette ? filteredPalette.getPointer() : tiPalette.getPointer());

				TRasterImageP riOut(TImageCache::instance()->get(std::string(oldRasterOut->cacheId, oldRasterOut->cacheIdLength), true));
				TRaster32P rasterOut = riOut->getRaster();

				blend(ti, rasterOut, blendParams);

				blendParams.clear();
			}
				CASE Calligraphic : __OR OutBorder:
				{
					if (sandorData->m_type == OutBorder && !firstSandorFx)
						continue;

					const char *argv[12];
					memcpy(argv, sandorData->m_argv, 12 * sizeof(const char *));

					double thickness = toDouble(std::string(sandorData->m_argv[7])) * scale * dpi;
					argv[7] = strsave(toString(thickness).c_str());

					calligraph(oldRasterIn, oldRasterOut, sandorData->m_border, sandorData->m_argc,
							   argv, sandorData->m_shrink, sandorData->m_type == OutBorder);
				}
				CASE ArtAtContour:
				{
					const char *argv[12];
					memcpy(argv, sandorData->m_argv, 12 * sizeof(const char *));

					double distance = toDouble(std::string(sandorData->m_argv[6])) * scale * dpi;
					argv[6] = strsave(toString(distance).c_str());
					distance = toDouble(std::string(sandorData->m_argv[7])) * scale * dpi;
					argv[7] = strsave(toString(distance).c_str());
					double density = toDouble(std::string(sandorData->m_argv[8])) / sq(scale * dpi);
					argv[8] = strsave(toString(density).c_str());
					double size = toDouble(std::string(sandorData->m_argv[1])) * scale * dpi;
					argv[1] = strsave(toString(size).c_str());
					size = toDouble(std::string(sandorData->m_argv[2])) * scale * dpi;
					argv[2] = strsave(toString(size).c_str());
					RASTER *imgContour = TRop::convertRaster50to46(sandorData->m_controller, 0);
					patternmap(oldRasterIn, oldRasterOut, sandorData->m_border, sandorData->m_argc,
							   argv, sandorData->m_shrink, imgContour);
					TRop::releaseRaster46(imgContour);
				}
			DEFAULT:
				assert(false);
			}

			TRasterImageP riOut(TImageCache::instance()->get(std::string(oldRasterOut->cacheId, oldRasterOut->cacheIdLength), true));
			TRaster32P rasterOut = riOut->getRaster();

			TRop::releaseRaster46(oldRasterIn);
			TRop::releaseRaster46(oldRasterOut);

			if (firstSandorFx) {
				resultTile.setRaster(rasterOut);
				firstSandorFx = false;
			} else
				TRop::over(resultTile.getRaster(), rasterOut);
		}
	}

	//Release cmRas cache identifier if any
	TImageCache::instance()->remove(cmRasCacheId);

	if (!result)
		result = TRasterImageP(resultTile.getRaster());

	return result;
}

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

void applyCmappedFx(TVectorImageP &vi, const std::vector<TRasterFxRenderDataP> &fxs, int frame)
{
	TRasterP ras;
	bool keep = false;
	TPaletteP modPalette;
	set<int> colors;
	std::vector<TRasterFxRenderDataP>::const_iterator it = fxs.begin();

	//prima tutti gli effetti che agiscono sulla paletta....
	for (; it != fxs.end(); ++it) {
		ExternalPaletteFxRenderData *extpltData = dynamic_cast<ExternalPaletteFxRenderData *>((*it).getPointer());
		PaletteFilterFxRenderData *pltFilterData = dynamic_cast<PaletteFilterFxRenderData *>((*it).getPointer());

		if (extpltData && extpltData->m_palette)
			modPalette = extpltData->m_palette->clone();
		else if (pltFilterData) {
			assert(pltFilterData->m_type == eApplyToInksAndPaints); // Must have been converted to CM32 otherwise

			keep = pltFilterData->m_keep;
			colors.insert(pltFilterData->m_colors.begin(), pltFilterData->m_colors.end());
		}
	}

	TPalette *tempPlt = modPalette.getPointer();
	applyPaletteFilter(tempPlt, keep, colors, vi->getPalette());
	modPalette = tempPlt;

	if (modPalette) {
		vi = vi->clone();
		vi->setPalette(modPalette.getPointer());
	}
}

//****************************************************************************************
//    LevelFxResourceBuilder  definition
//****************************************************************************************

class LevelFxBuilder : public ResourceBuilder
{
	TRasterP m_loadedRas;
	TPaletteP m_palette;

	TXshSimpleLevel *m_sl;
	TFrameId m_fid;
	TRectD m_tileGeom;
	bool m_64bit;

	TRect m_rasBounds;

public:
	LevelFxBuilder(const std::string &resourceName, double frame, const TRenderSettings &rs,
				   TXshSimpleLevel *sl, TFrameId fid)
		: ResourceBuilder(resourceName, 0, frame, rs), m_loadedRas(), m_palette(), m_sl(sl), m_fid(fid), m_64bit(rs.m_bpp == 64) {}

	void setRasBounds(const TRect &rasBounds)
	{
		m_rasBounds = rasBounds;
	}

	void compute(const TRectD &tileRect)
	{
		//Load the image
		TImageP img(m_sl->getFullsampledFrame(m_fid, (m_64bit ? ImageManager::is64bitEnabled : 0) |
														 ImageManager::dontPutInCache));

		if (!img)
			return;

		TRasterImageP rimg(img);
		TToonzImageP timg(img);

		m_loadedRas = rimg ? (TRasterP)rimg->getRaster() : timg ? (TRasterP)timg->getRaster() : TRasterP();
		assert(m_loadedRas);

		if (timg)
			m_palette = timg->getPalette();

		assert(tileRect == TRectD(0, 0, m_loadedRas->getLx(), m_loadedRas->getLy()));
	}

	void simCompute(const TRectD &rect) {}

	void upload(TCacheResourceP &resource)
	{
		assert(m_loadedRas);
		resource->upload(TPoint(), m_loadedRas);
		if (m_palette)
			resource->uploadPalette(m_palette);
	}

	bool download(TCacheResourceP &resource)
	{
		//If the image has been loaded in this builder, just use it
		if (m_loadedRas)
			return true;

		//If the image has yet to be loaded by this builder, skip without
		//allocating anything
		if (resource->canDownloadAll(m_rasBounds)) {
			m_loadedRas = resource->buildCompatibleRaster(m_rasBounds.getSize());
			resource->downloadPalette(m_palette);
			return resource->downloadAll(TPoint(), m_loadedRas);
		} else
			return false;
	}

	TImageP getImage() const
	{
		if (!m_loadedRas)
			return TImageP();

		TRasterCM32P cm(m_loadedRas);

		TImageP result(cm ? TImageP(TToonzImageP(cm, cm->getBounds())) : TImageP(TRasterImageP(m_loadedRas)));
		if (m_palette)
			result->setPalette(m_palette.getPointer());

		return result;
	}
};

//****************************************************************************************
//    TLevelColumnFx  implementation
//****************************************************************************************

TLevelColumnFx::TLevelColumnFx()
	: m_levelColumn(0), m_isCachable(true), m_mutex(), m_offlineContext(0)
{
	setName(L"LevelColumn");
}

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

TLevelColumnFx::~TLevelColumnFx()
{
	if (m_offlineContext)
		delete m_offlineContext;
}

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

bool TLevelColumnFx::canHandle(const TRenderSettings &info, double frame)
{
	//NOTE 1: Currently, it is necessary that level columns return FALSE for
	//raster levels - just a quick way to tell the cache functions that they
	//have to be cached.

	if (!m_levelColumn)
		return true;

	TXshCell cell = m_levelColumn->getCell(m_levelColumn->getFirstRow());
	if (cell.isEmpty())
		return true;

	TXshSimpleLevel *sl = cell.m_level->getSimpleLevel();
	if (!sl)
		return true;

	return (sl->getType() == PLI_XSHLEVEL && !vectorMustApplyCmappedFx(info.m_data));
}

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

TAffine TLevelColumnFx::handledAffine(const TRenderSettings &info, double frame)
{
	if (!m_levelColumn)
		return TAffine();

	TXshCell cell = m_levelColumn->getCell(m_levelColumn->getFirstRow());
	if (cell.isEmpty())
		return TAffine();

	TXshSimpleLevel *sl = cell.m_level->getSimpleLevel();
	if (!sl)
		return TAffine();

	if (sl->getType() == PLI_XSHLEVEL)
		return vectorMustApplyCmappedFx(info.m_data) ? TRasterFx::handledAffine(info, frame) : info.m_affine;

	// Accept any translation consistent with the image's pixels geometry
	TImageInfo imageInfo;
	getImageInfo(imageInfo, sl, cell.m_frameId);

	TPointD pixelsOrigin(-0.5 * imageInfo.m_lx, -0.5 * imageInfo.m_ly);

	const TAffine &aff = info.m_affine;
	if (aff.a11 != 1.0 || aff.a22 != 1.0 || aff.a12 != 0.0 || aff.a21 != 0.0)
		return TTranslation(-pixelsOrigin);

	// This is a translation, ok. Just ensure it is consistent.
	TAffine consistentAff(aff);

	consistentAff.a13 -= pixelsOrigin.x, consistentAff.a23 -= pixelsOrigin.y;
	consistentAff.a13 = tfloor(consistentAff.a13), consistentAff.a23 = tfloor(consistentAff.a23);
	consistentAff.a13 += pixelsOrigin.x, consistentAff.a23 += pixelsOrigin.y;

	return consistentAff;
}

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

TFilePath TLevelColumnFx::getPalettePath(int frame) const
{
	if (!m_levelColumn)
		return TFilePath();

	TXshCell cell = m_levelColumn->getCell(frame);
	if (cell.isEmpty())
		return TFilePath();

	TXshSimpleLevel *sl = cell.m_level->getSimpleLevel();
	if (!sl)
		return TFilePath();

	if (sl->getType() == TZP_XSHLEVEL)
		return sl->getScene()->decodeFilePath(sl->getPath().withNoFrame().withType("tpl"));

	if (sl->getType() == PLI_XSHLEVEL)
		return sl->getScene()->decodeFilePath(sl->getPath());

	return TFilePath();
}

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

TPalette *TLevelColumnFx::getPalette(int frame) const
{
	if (!m_levelColumn)
		return 0;

	TXshCell cell = m_levelColumn->getCell(frame);
	if (cell.isEmpty())
		return 0;

	TXshSimpleLevel *sl = cell.m_level->getSimpleLevel();
	if (!sl)
		return 0;

	return sl->getPalette();
}

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

TFx *TLevelColumnFx::clone(bool recursive) const
{
	TLevelColumnFx *clonedFx = dynamic_cast<TLevelColumnFx *>(TFx::clone(recursive));
	assert(clonedFx);
	clonedFx->m_levelColumn = m_levelColumn;
	clonedFx->m_isCachable = m_isCachable;
	return clonedFx;
}

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

void TLevelColumnFx::doDryCompute(TRectD &rect, double frame, const TRenderSettings &info)
{
	if (!m_levelColumn)
		return;

	int row = (int)frame;
	TXshCell cell = m_levelColumn->getCell(row);
	if (cell.isEmpty())
		return;

	TXshSimpleLevel *sl = cell.m_level->getSimpleLevel();
	if (!sl)
		return;

	//In case this is a vector level, the image is renderized quickly and directly at the
	//correct resolution. Caching is disabled in such case, at the moment.
	if (sl->getType() == PLI_XSHLEVEL)
		return;

	int renderStatus = TRenderer::instance().getRenderStatus(TRenderer::renderId());

	std::string alias = getAlias(frame, TRenderSettings()) + "_image";

	TImageInfo imageInfo;
	getImageInfo(imageInfo, sl, cell.m_frameId);
	TRectD imgRect(0, 0, imageInfo.m_lx, imageInfo.m_ly);

	if (renderStatus == TRenderer::FIRSTRUN) {
		ResourceBuilder::declareResource(alias, 0, imgRect, frame, info, false);
	} else {
		LevelFxBuilder builder(alias, frame, info, sl, cell.m_frameId);
		builder.setRasBounds(TRect(0, 0, imageInfo.m_lx - 1, imageInfo.m_ly - 1));
		builder.simBuild(imgRect);
	}
}

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

bool isSubsheetChainOnColumn0(TXsheet *topXsheet, TXsheet *subsheet, int frame)
{
	if (topXsheet == subsheet)
		return true;

	const TXshCell cell = topXsheet->getCell(frame, 0);
	if (!cell.m_level)
		return false;
	TXshChildLevel *cl = cell.m_level->getChildLevel();
	if (!cl)
		return false;
	return isSubsheetChainOnColumn0(cl->getXsheet(), subsheet, frame);
}

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

void TLevelColumnFx::doCompute(TTile &tile, double frame, const TRenderSettings &info)
{
	if (!m_levelColumn)
		return;

	// Ensure that a corresponding cell and level exists
	int row = (int)frame;
	TXshCell cell = m_levelColumn->getCell(row);

	if (cell.isEmpty())
		return;

	TXshSimpleLevel *sl = cell.m_level->getSimpleLevel();
	if (!sl)
		return;

	TFrameId fid = cell.m_frameId;

	TImageP img;
	TImageInfo imageInfo;

	// Now, fetch the image
	if (sl->getType() != PLI_XSHLEVEL) {
		// Raster case
		LevelFxBuilder builder(getAlias(frame, TRenderSettings()) + "_image",
							   frame, info, sl, fid);

		getImageInfo(imageInfo, sl, fid);
		TRectD imgRect(0, 0, imageInfo.m_lx, imageInfo.m_ly);

		builder.setRasBounds(TRect(0, 0, imageInfo.m_lx - 1, imageInfo.m_ly - 1));
		builder.build(imgRect);

		img = builder.getImage();
	} else {
		// Vector case (loading is immediate)
		if (!img) {
			img = sl->getFullsampledFrame(fid, ((info.m_bpp == 64) ? ImageManager::is64bitEnabled : 0) |
												   ImageManager::dontPutInCache);
		}
	}

	//Extract the required geometry
	TRect tileBounds(tile.getRaster()->getBounds());
	TRectD tileRectD = TRectD(tileBounds.x0, tileBounds.y0, tileBounds.x1 + 1, tileBounds.y1 + 1) + tile.m_pos;

	//To be sure, if there is no image, return.
	if (!img)
		return;

	TRectD bBox = img->getBBox();

	// Discriminate image type
	if (TVectorImageP vectorImage = img) {
		// Vector case

		if (vectorMustApplyCmappedFx(info.m_data)) {
			// Deal separately
			applyTzpFxsOnVector(vectorImage, tile, frame, info);
		} else {
			QMutexLocker m(&m_mutex);
			bBox = info.m_affine * vectorImage->getBBox();
			TDimension size(tile.getRaster()->getSize());

			TAffine aff =
				TTranslation(-tile.m_pos.x, -tile.m_pos.y) *
				TScale(1.0 / info.m_shrinkX, 1.0 / info.m_shrinkY) *
				info.m_affine;

			applyCmappedFx(vectorImage, info.m_data, (int)frame);
			TPalette *vpalette = vectorImage->getPalette();
			assert(vpalette);
			m_isCachable = !vpalette->isAnimated();
			int oldFrame = vpalette->getFrame();

			TVectorRenderData rd(TVectorRenderData::ProductionSettings(),
								 aff, TRect(size), vpalette);

			if (!m_offlineContext || m_offlineContext->getLx() < size.lx || m_offlineContext->getLy() < size.ly) {
				if (m_offlineContext)
					delete m_offlineContext;
				m_offlineContext = new TOfflineGL(size);
			}

			m_offlineContext->makeCurrent();
			m_offlineContext->clear(TPixel32(0, 0, 0, 0));

			//If level has animated palette, it is necessary to lock palette's color against
			//concurrents TPalette::setFrame.
			if (!m_isCachable)
				vpalette->mutex()->lock();

			vpalette->setFrame((int)frame);
			m_offlineContext->draw(vectorImage, rd, true);
			vpalette->setFrame(oldFrame);

			if (!m_isCachable)
				vpalette->mutex()->unlock();

			m_offlineContext->getRaster(tile.getRaster());

			m_offlineContext->doneCurrent();
		}
	} else {
		// Raster case

		TRasterP ras;
		TAffine aff;

		TRasterImageP ri = img;
		TToonzImageP ti = img;
		img = 0;

		if (ri) {
			// Fullcolor case

			ras = ri->getRaster();
			ri = 0;
			TRaster32P ras32(ras);
			TRaster64P ras64(ras);

			//Ensure that ras is either a 32 or 64 fullcolor.
			//Otherwise, we have to convert it.
			if (!ras32 && !ras64) {
				TRasterP tileRas(tile.getRaster());
				TRaster32P tileRas32(tileRas);
				TRaster64P tileRas64(tileRas);

				if (tileRas32) {
					ras32 = TRaster32P(ras->getLx(), ras->getLy());
					TRop::convert(ras32, ras);
					ras = ras32;
				} else if (tileRas64) {
					ras64 = TRaster64P(ras->getLx(), ras->getLy());
					TRop::convert(ras64, ras);
					ras = ras64;
				} else
					assert(0);
			}
			TXsheet *xsh = m_levelColumn->getLevelColumn()->getXsheet();
			TXsheet *txsh = sl->getScene()->getTopXsheet();

			LevelProperties *levelProp = sl->getProperties();
			if (Preferences::instance()->isIgnoreAlphaonColumn1Enabled() && m_levelColumn->getIndex() == 0 && isSubsheetChainOnColumn0(txsh, xsh, frame)) {
				TRasterP appRas(ras->clone());
				setMaxMatte(appRas);
				ras = appRas;
			}
			if (levelProp->doPremultiply()) {
				TRasterP appRas = ras->clone();
				TRop::premultiply(appRas);
				ras = appRas;
			}
			if (levelProp->whiteTransp()) {
				TRasterP appRas(ras->clone());
				TRop::whiteTransp(appRas);
				ras = appRas;
			}
			if (levelProp->antialiasSoftness() > 0) {
				TRasterP appRas = ras->create(ras->getLx(), ras->getLy());
				TRop::antialias(ras, appRas, 10, levelProp->antialiasSoftness());
				ras = appRas;
			}

			if (TXshSimpleLevel::m_fillFullColorRaster) {
				TRasterP appRas(ras->clone());
				FullColorAreaFiller filler(appRas);
				TPaletteP palette = new TPalette();
				int styleId = palette->getPage(0)->addStyle(TPixel32::White);
				FillParameters params;
				params.m_palette = palette.getPointer();
				params.m_styleId = styleId;
				params.m_minFillDepth = 0;
				params.m_maxFillDepth = 15;
				filler.rectFill(appRas->getBounds(), params, false);
				ras = appRas;
			}
		} else if (ti) {
			// Colormap case

			//Use the imageInfo's dpi
			ti->setDpi(imageInfo.m_dpix, imageInfo.m_dpiy);

			TImageP newImg = applyTzpFxs(ti, frame, info);

			ri = newImg;
			ti = newImg;

			if (ri)
				ras = ri->getRaster();
			else
				ras = ti->getRaster();

			if (sl->getProperties()->antialiasSoftness() > 0) {
				TRasterP appRas = ras->create(ras->getLx(), ras->getLy());
				TRop::antialias(ras, appRas, 10, sl->getProperties()->antialiasSoftness());
				ras = appRas;
			}

			ri = 0;
		}

		if (ras) {
			double lx_2 = ras->getLx() / 2.0;
			double ly_2 = ras->getLy() / 2.0;

			TRenderSettings infoAux(info);
			assert(info.m_affine.isTranslation());
			infoAux.m_data.clear();

			//Place the output rect in the image's reference
			tileRectD += TPointD(lx_2 - info.m_affine.a13, ly_2 - info.m_affine.a23);

			//Then, retrieve loaded image's interesting region
			TRectD inTileRectD;
			if (ti) {
				TRect saveBox(ti->getSavebox());
				inTileRectD = TRectD(saveBox.x0, saveBox.y0, saveBox.x1 + 1, saveBox.y1 + 1);
			} else {
				TRect rasBounds(ras->getBounds());
				inTileRectD = TRectD(rasBounds.x0, rasBounds.y0, rasBounds.x1 + 1, rasBounds.y1 + 1);
			}

			//And intersect the two
			inTileRectD *= tileRectD;

			//Should the intersection be void, return
			if (inTileRectD.x0 >= inTileRectD.x1 || inTileRectD.y0 >= inTileRectD.y1)
				return;

			//Output that intersection in the requested tile
			TRect inTileRect(tround(inTileRectD.x0), tround(inTileRectD.y0), tround(inTileRectD.x1) - 1, tround(inTileRectD.y1) - 1);
			TTile inTile(ras->extract(inTileRect), inTileRectD.getP00() + TPointD(-lx_2, -ly_2));

			//Observe that inTile is in the standard reference, ie image's minus the center coordinates

			if (ti) {
				//In the colormapped case, we have to convert the cmap to fullcolor
				TPalette *palette = ti->getPalette();

				if (!m_isCachable)
					palette->mutex()->lock();

				int prevFrame = palette->getFrame();

				palette->setFrame((int)frame);
				TTile auxtile(TRaster32P(inTile.getRaster()->getSize()), inTile.m_pos);
				TRop::convert(auxtile, inTile, palette, false, true);
				palette->setFrame(prevFrame);

				if (!m_isCachable)
					palette->mutex()->unlock();

				inTile.setRaster(auxtile.getRaster());
			}

			TRasterFx::applyAffine(tile, inTile, infoAux);
		}
	}
}

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

TImageP TLevelColumnFx::applyTzpFxs(TToonzImageP &ti, double frame, const TRenderSettings &info)
{
	double scale = sqrt(fabs(info.m_affine.det()));

	int prevFrame = ti->getPalette()->getFrame();
	m_isCachable = !ti->getPalette()->isAnimated();

	if (!m_isCachable)
		ti->getPalette()->mutex()->lock();

	TPaletteP palette(ti->getPalette());
	palette->setFrame((int)frame);
	TImageP newImg = applyCmappedFx(ti, info.m_data, (int)frame, scale);
	palette->setFrame(prevFrame);

	if (!m_isCachable)
		palette->mutex()->unlock();

	return newImg;
}

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

void TLevelColumnFx::applyTzpFxsOnVector(
	const TVectorImageP &vi,
	TTile &tile, double frame, const TRenderSettings &info)
{
	TRect tileBounds(tile.getRaster()->getBounds());
	TRectD tileRectD = TRectD(tileBounds.x0, tileBounds.y0, tileBounds.x1 + 1, tileBounds.y1 + 1) + tile.m_pos;

	//Info's affine is not the identity only in case the loaded image is a vector one.
	double scale = sqrt(fabs(info.m_affine.det()));
	int enlargement = getEnlargement(info.m_data, scale);

	//Extract the vector image's groups
	std::vector<TVectorImageP> groupsList;
	getGroupsList(vi, groupsList);

	//For each group, apply the tzp fxs stored in info. The result is immediately converted
	//to a raster image if necessary.
	unsigned int i;
	for (i = 0; i < groupsList.size(); ++i) {
		TVectorImageP &groupVi = groupsList[i];

		//Extract the group's bbox.
		TRectD groupBBox(info.m_affine * groupVi->getBBox());
		if (!mustApplySandorFx(info.m_data))
			groupBBox *= tileRectD;

		groupBBox = groupBBox.enlarge(enlargement);

		if (groupBBox.x0 >= groupBBox.x1 || groupBBox.y0 >= groupBBox.y1)
			continue;

		//Ensure that groupBBox and tile have the same integer geometry
		groupBBox -= tile.m_pos;
		groupBBox.x0 = tfloor(groupBBox.x0);
		groupBBox.y0 = tfloor(groupBBox.y0);
		groupBBox.x1 = tceil(groupBBox.x1);
		groupBBox.y1 = tceil(groupBBox.y1);
		groupBBox += tile.m_pos;

		//Build groupBBox's relative position to the tile
		TPoint groupRelativePosToTile(groupBBox.x0 - tile.m_pos.x, groupBBox.y0 - tile.m_pos.y);

		//Convert the group to a strictly sufficient Toonz image
		TToonzImageP groupTi = ToonzImageUtils::vectorToToonzImage(
			groupVi, info.m_affine, groupVi->getPalette(),
			groupBBox.getP00(), TDimension(groupBBox.getLx(), groupBBox.getLy()),
			&info.m_data, true);

		//Apply the tzp fxs to the converted Toonz image
		TImageP groupResult = applyTzpFxs(groupTi, frame, info);

		//If necessary, convert the result to fullcolor
		TRasterImageP groupRi = groupResult;
		if (!groupRi) {
			groupTi = groupResult;
			assert(groupTi);

			TRasterP groupTiRas(groupTi->getRaster());
			TRaster32P tempRas(groupTiRas->getSize());
			groupRi = TRasterImageP(tempRas);

			TRop::convert(tempRas, groupTiRas, groupTi->getPalette());
		}

		//Over the group image on the output
		TRasterP tileRas(tile.getRaster());
		TRop::over(tileRas, groupRi->getRaster(), groupRelativePosToTile);
	}
}

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

int TLevelColumnFx::getMemoryRequirement(const TRectD &rect, double frame, const TRenderSettings &info)
{
	//Sandor fxs are currently considered *VERY* inefficient upon tile subdivision
	if (mustApplySandorFx(info.m_data)) {
		return -1;
	}
	return 0;
}

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

void TLevelColumnFx::getImageInfo(TImageInfo &info, TXshSimpleLevel *sl, TFrameId frameId)
{
	int type = sl->getType();
	assert(type != PLI_XSHLEVEL);
	if (type == PLI_XSHLEVEL)
		return;

	std::string imageId = sl->getImageId(frameId);

	const TImageInfo *storedInfo = ImageManager::instance()->getInfo(imageId, ImageManager::none, 0);

	if (!storedInfo) //sulle pict caricate info era nullo, ma l'immagine c'e'!
					 // con la getFullSampleFrame riprendo   l'immagine e ricalcolo la savebox...
	{
		TImageP img;
		if (!(img = sl->getFullsampledFrame(frameId, ImageManager::dontPutInCache))) {
			assert(false);
			return;
		}

		info.m_lx = (int)img->getBBox().getLx();
		info.m_ly = (int)img->getBBox().getLy();
		info.m_x0 = info.m_y0 = 0;
		info.m_x1 = (int)img->getBBox().getP11().x;
		info.m_y1 = (int)img->getBBox().getP11().y;
	} else
		info = *storedInfo;
}

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

bool TLevelColumnFx::doGetBBox(double frame, TRectD &bBox, const TRenderSettings &info)
{
	//Usual preliminaries (make sure a level/cell exists, etc...)
	if (!m_levelColumn)
		return false;

	int row = (int)frame;
	const TXshCell &cell = m_levelColumn->getCell(row);
	if (cell.isEmpty())
		return false;

	TXshLevelP xshl = cell.m_level;
	if (!xshl)
		return false;

	TXshSimpleLevel *sl = xshl->getSimpleLevel();
	if (!sl)
		return false;

	double dpi = 1.0;

	//Discriminate for level type
	int type = xshl->getType();
	if (type != PLI_XSHLEVEL) {
		TImageInfo imageInfo;
		getImageInfo(imageInfo, sl, cell.m_frameId);

		TRect imageSavebox(imageInfo.m_x0, imageInfo.m_y0, imageInfo.m_x1, imageInfo.m_y1);
		double cx = 0.5 * imageInfo.m_lx;
		double cy = 0.5 * imageInfo.m_ly;
		double x0 = (imageSavebox.x0 - cx);
		double y0 = (imageSavebox.y0 - cy);
		double x1 = x0 + imageSavebox.getLx();
		double y1 = y0 + imageSavebox.getLy();
		bBox = TRectD(x0, y0, x1, y1);

		dpi = imageInfo.m_dpix / Stage::inch;
	} else {
		TImageP img = m_levelColumn->getCell(row).getImage(false);
		if (!img)
			return false;
		bBox = img->getBBox();
	}

	//Add the enlargement of the bbox due to Tzp render datas
	if (info.m_data.size()) {
		TRectD imageBBox(bBox);
		for (unsigned int i = 0; i < info.m_data.size(); ++i) {
			TRectD enlargedImageBBox = info.m_data[i]->getBBoxEnlargement(imageBBox);
			double enlargement = enlargedImageBBox.x1 - imageBBox.x1;
			bBox += imageBBox.enlarge(enlargement * dpi);
		}
	}

	return true;
}

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

const TPersistDeclaration *TLevelColumnFx::getDeclaration() const
{
	return &columnFxInfo;
}

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

std::string TLevelColumnFx::getPluginId() const
{
	return "Toonz_";
}

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

TFxTimeRegion TLevelColumnFx::getTimeRegion() const
{
	if (!m_levelColumn)
		return TFxTimeRegion();

	int first = m_levelColumn->getFirstRow();
	int last = m_levelColumn->getRowCount();

	return TFxTimeRegion(first, last);
}

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

void TLevelColumnFx::setColumn(TXshLevelColumn *column)
{
	m_levelColumn = column;
}

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

std::wstring TLevelColumnFx::getColumnId() const
{
	if (!m_levelColumn)
		return L"Col?";
	return L"Col" + toWideString(m_levelColumn->getIndex() + 1);
}

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

std::wstring TLevelColumnFx::getColumnName() const
{
	if (!m_levelColumn)
		return L"";
	int idx = getColumnIndex();
	return toWideString(m_levelColumn->getXsheet()->getStageObject(TStageObjectId::ColumnId(idx))->getName());
}

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

std::string TLevelColumnFx::getAlias(double frame, const TRenderSettings &info) const
{
	if (!m_levelColumn || m_levelColumn->getCell((int)frame).isEmpty())
		return std::string();

	const TXshCell &cell = m_levelColumn->getCell((int)frame);

	TFilePath fp;
	TXshSimpleLevel *sl = cell.getSimpleLevel();

	if (!sl) {
		//Try with the sub-xsheet case
		TXshChildLevel *childLevel = cell.m_level->getChildLevel();
		if (childLevel)
			return ::getAlias(childLevel->getXsheet(), frame, info);

		return std::string();
	}

	TFilePath path = sl->getPath();
	if (!cell.m_frameId.isNoFrame())
		fp = path.withFrame(cell.m_frameId);
	else
		fp = path;

	std::string rdata;
	std::vector<TRasterFxRenderDataP>::const_iterator it = info.m_data.begin();
	for (; it != info.m_data.end(); ++it) {
		TRasterFxRenderDataP data = *it;
		if (data)
			rdata += data->toString();
	}

	if (sl->getType() == PLI_XSHLEVEL || sl->getType() == TZP_XSHLEVEL) {
		TPalette *palette = cell.getPalette();
		if (palette && palette->isAnimated())
			rdata += "animatedPlt" + toString(frame);
	}

	if (Preferences::instance()->isIgnoreAlphaonColumn1Enabled()) {
		TXsheet *xsh = m_levelColumn->getLevelColumn()->getXsheet();
		TXsheet *txsh = sl->getScene()->getTopXsheet();

		if (m_levelColumn->getIndex() == 0 && isSubsheetChainOnColumn0(txsh, xsh, frame))
			rdata += "column_0";
	}

	return getFxType() + "[" + toString(fp.getWideString()) + "," + rdata + "]";
}

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

int TLevelColumnFx::getColumnIndex() const
{
	return m_levelColumn ? m_levelColumn->getIndex() : -1;
}

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

TXshColumn *TLevelColumnFx::getXshColumn() const
{
	return m_levelColumn;
}

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

void TLevelColumnFx::compute(TFlash &flash, int frame)
{
	if (!m_levelColumn)
		return;

	TImageP img = m_levelColumn->getCell(frame).getImage(false);
	if (!img)
		return;

	flash.draw(img, 0);
}

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

TAffine TLevelColumnFx::getDpiAff(int frame)
{
	if (!m_levelColumn)
		return TAffine();

	TXshCell cell = m_levelColumn->getCell(frame);
	if (cell.isEmpty())
		return TAffine();

	TXshSimpleLevel *sl = cell.m_level->getSimpleLevel();
	TFrameId fid = cell.m_frameId;

	if (sl)
		return ::getDpiAffine(sl, cell.m_frameId, true);

	return TAffine();
}

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

void TLevelColumnFx::saveData(TOStream &os)
{
	assert(m_levelColumn);
	TFx::saveData(os);
}

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

void TLevelColumnFx::loadData(TIStream &is)
{
	// The internal numeric identifier is set here but not in constructors.
	// This is *intended* as fx cloning needs to retain the original id
	// through the fxs tree deployment procedure happening just before a
	// render process (see scenefx.cpp).

	TFx::loadData(is);
	setNewIdentifier();
}

//****************************************************************************************
//    TPaletteColumnFx  implementation
//****************************************************************************************

TPaletteColumnFx::TPaletteColumnFx()
	: m_paletteColumn(0)
{
}

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

TPaletteColumnFx::~TPaletteColumnFx()
{
}

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

TFx *TPaletteColumnFx::clone(bool recursive) const
{
	TPaletteColumnFx *clonedFx = dynamic_cast<TPaletteColumnFx *>(TFx::clone(recursive));
	assert(clonedFx);
	clonedFx->m_paletteColumn = m_paletteColumn;
	return clonedFx;
}

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

void TPaletteColumnFx::doCompute(TTile &tile, double frame, const TRenderSettings &)
{
}

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

bool TPaletteColumnFx::doGetBBox(double frame, TRectD &bBox, const TRenderSettings &info)
{
	return false;
}

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

TAffine TPaletteColumnFx::getDpiAff(int frame)
{
	return TAffine();
}

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

bool TPaletteColumnFx::canHandle(const TRenderSettings &info, double frame)
{
	return false;
}

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

TFilePath TPaletteColumnFx::getPalettePath(int frame) const
{
	if (!m_paletteColumn)
		return TFilePath();
	TXshCell cell = m_paletteColumn->getCell(frame);
	if (cell.isEmpty() || cell.m_level->getPaletteLevel() == 0)
		return TFilePath();

	TXshPaletteLevel *paletteLevel = cell.m_level->getPaletteLevel();
	TFilePath path = paletteLevel->getPath();
	path = paletteLevel->getScene()->decodeFilePath(path);
	return path;
}

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

TPalette *TPaletteColumnFx::getPalette(int frame) const
{
	if (!m_paletteColumn)
		return 0;
	TXshCell cell = m_paletteColumn->getCell(frame);
	if (cell.isEmpty() || cell.m_level->getPaletteLevel() == 0)
		return 0;

	TXshPaletteLevel *paletteLevel = cell.m_level->getPaletteLevel();
	return paletteLevel->getPalette();
}

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

const TPersistDeclaration *TPaletteColumnFx::getDeclaration() const
{
	return &paletteColumnFxInfo;
}

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

std::string TPaletteColumnFx::getPluginId() const
{
	return "Toonz_";
}

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

TFxTimeRegion TPaletteColumnFx::getTimeRegion() const
{
	int first = 0;
	int last = 11;
	return TFxTimeRegion(first, last);
}

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

std::wstring TPaletteColumnFx::getColumnName() const
{
	if (!m_paletteColumn)
		return L"Col?";
	return L"Col" + toWideString(m_paletteColumn->getIndex() + 1);
}

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

std::wstring TPaletteColumnFx::getColumnId() const
{
	if (!m_paletteColumn)
		return L"Col?";
	return L"Col" + toWideString(m_paletteColumn->getIndex() + 1);
}

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

std::string TPaletteColumnFx::getAlias(double frame, const TRenderSettings &info) const
{
	TFilePath palettePath = getPalettePath(frame);
	return "TPaletteColumnFx[" + toString(palettePath.getWideString()) + "]";
}

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

void TPaletteColumnFx::compute(TFlash &flash, int frame)
{
}

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

int TPaletteColumnFx::getColumnIndex() const
{
	return m_paletteColumn ? m_paletteColumn->getIndex() : -1;
}

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

TXshColumn *TPaletteColumnFx::getXshColumn() const
{
	return m_paletteColumn;
}

//****************************************************************************************
//    TZeraryColumnFx  implementation
//****************************************************************************************

TZeraryColumnFx::TZeraryColumnFx()
	: m_zeraryFxColumn(0), m_fx(0)
{
	setName(L"ZeraryColumn");
}

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

TZeraryColumnFx::~TZeraryColumnFx()
{
	if (m_zeraryFxColumn)
		m_zeraryFxColumn->release();
	if (m_fx) {
		m_fx->m_columnFx = 0;
		m_fx->release();
	}
}

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

void TZeraryColumnFx::doCompute(TTile &tile, double frame, const TRenderSettings &info)
{
	if (m_zeraryFxColumn) {
		TRasterFx *fx = dynamic_cast<TRasterFx *>(m_fx);
		if (fx)
			fx->compute(tile, frame, info);
	}
}

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

bool TZeraryColumnFx::doGetBBox(double frame, TRectD &bBox, const TRenderSettings &info)
{
	if (m_zeraryFxColumn) {
		TRasterFx *fx = dynamic_cast<TRasterFx *>(m_fx);
		if (fx)
			return fx->doGetBBox(frame, bBox, info);
	}

	return false;
}

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

const TPersistDeclaration *TZeraryColumnFx::getDeclaration() const
{
	return &zeraryColumnFxInfo;
}

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

std::string TZeraryColumnFx::getPluginId() const
{
	return "Toonz_";
}

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

TFxTimeRegion TZeraryColumnFx::getTimeRegion() const
{
	return TFxTimeRegion(0, (std::numeric_limits<double>::max)());
}

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

void TZeraryColumnFx::setColumn(TXshZeraryFxColumn *column)
{
	m_zeraryFxColumn = column;
}

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

std::wstring TZeraryColumnFx::getColumnName() const
{
	return getZeraryFx()->getName();
}

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

std::wstring TZeraryColumnFx::getColumnId() const
{
	return getZeraryFx()->getFxId();
}

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

void TZeraryColumnFx::setZeraryFx(TFx *fx)
{
	TZeraryFx *zfx = static_cast<TZeraryFx *>(fx);

	if (fx) {
		assert(dynamic_cast<TZeraryFx *>(fx));

		fx->addRef();
		fx->setNewIdentifier(); // When adopting zerary fxs, ensure that they
								// have a new numeric identifier
		zfx->m_columnFx = this;
	}

	if (m_fx) {
		m_fx->m_columnFx = 0;
		m_fx->release();
	}

	m_fx = zfx;
}

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

std::string TZeraryColumnFx::getAlias(double frame, const TRenderSettings &info) const
{
	return "TZeraryColumnFx[" + m_fx->getAlias(frame, info) + "]";
}

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

void TZeraryColumnFx::loadData(TIStream &is)
{
	if (m_fx)
		m_fx->release();

	m_fx = 0;
	TPersist *p = 0;
	is >> p;

	m_fx = dynamic_cast<TZeraryFx *>(p);
	if (m_fx) {
		m_fx->addRef();
		m_fx->m_columnFx = this;
		m_fx->setNewIdentifier();
	}

	TFx::loadData(is);
	setNewIdentifier(); // Same as with TColumnFx
}

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

void TZeraryColumnFx::saveData(TOStream &os)
{
	assert(m_fx);
	os << m_fx;
	TFx::saveData(os);
}

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

int TZeraryColumnFx::getColumnIndex() const
{
	return m_zeraryFxColumn ? m_zeraryFxColumn->getIndex() : -1;
}

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

TXshColumn *TZeraryColumnFx::getXshColumn() const
{
	return m_zeraryFxColumn;
}

//****************************************************************************************
//    TXSheetFx  implementation
//****************************************************************************************

const TPersistDeclaration *TXsheetFx::getDeclaration() const
{
	return &infoTXsheetFx;
}

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

TXsheetFx::TXsheetFx()
	: m_fxDag(0)
{
	setName(L"Xsheet");
}

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

void TXsheetFx::doCompute(TTile &tile, double frame, const TRenderSettings &)
{
}

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

bool TXsheetFx::doGetBBox(double frame, TRectD &bBox, const TRenderSettings &info)
{
	return false;
}

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

std::string TXsheetFx::getPluginId() const
{
	return "Toonz_";
}

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

std::string TXsheetFx::getAlias(double frame, const TRenderSettings &info) const
{
	std::string alias = getFxType();
	alias += "[";

	//Add each terminal fx's alias
	TFxSet *terminalFxs = m_fxDag->getTerminalFxs();
	int i, fxsCount = terminalFxs->getFxCount();
	for (i = 0; i < fxsCount; ++i) {
		alias += static_cast<TRasterFx *>(terminalFxs->getFx(i))->getAlias(frame, info) + ",";
	}

	return alias + "]";
}

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

void TXsheetFx::setFxDag(FxDag *fxDag)
{
	m_fxDag = fxDag;
}

//****************************************************************************************
//    TOutputFx  implementation
//****************************************************************************************

TOutputFx::TOutputFx()
{
	addInputPort("source", m_input);
	setName(L"Output");
}

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

const TPersistDeclaration *TOutputFx::getDeclaration() const
{
	return &infoTOutputFx;
}

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

void TOutputFx::doCompute(TTile &tile, double frame, const TRenderSettings &)
{
}

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

bool TOutputFx::doGetBBox(double frame, TRectD &bBox, const TRenderSettings &info)
{
	return false;
}

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

std::string TOutputFx::getPluginId() const
{
	return "Toonz_";
}