Blob Blame Raw


#include "tfxparam.h"
#include "tfx.h"

#include "tparamset.h"
#include "stdfx.h"
#include "time.h"
#include "trop.h"
#include "trasterfx.h"
#include "tpixelutils.h"
#include "tparamuiconcept.h"

#include "timage_io.h"

#include "toonz/tdistort.h"

//==============================================================================

namespace
{
inline bool myIsEmpty(const TRectD &r)
{
	return r.x0 >= r.x1 || r.y0 >= r.y1;
}
}

//==============================================================================

class FreeDistortBaseFx : public TStandardRasterFx
{
	enum { PERSPECTIVE,
		   BILINEAR };

public:
	FreeDistortBaseFx(bool isCastShadow);
	~FreeDistortBaseFx();

	bool doGetBBox(double frame, TRectD &bBox, const TRenderSettings &info);

	void doCompute(TTile &tile, double frame, const TRenderSettings &ri);
	void doDryCompute(TRectD &rect, double frame, const TRenderSettings &ri);
	void onPortConnected(TFxPort *port);
	bool canHandle(const TRenderSettings &info, double frame);
	int getMemoryRequirement(const TRectD &rect, double frame, const TRenderSettings &info);

	void transform(double frame,
				   int port,
				   const TRectD &rectOnOutput,
				   const TRenderSettings &infoOnOutput,
				   TRectD &rectOnInput,
				   TRenderSettings &infoOnInput);

	void safeTransform(
		double frame,
		int port,
		const TRectD &rectOnOutput,
		const TRenderSettings &infoOnOutput,
		TRectD &rectOnInput,
		TRenderSettings &infoOnInput,
		TRectD &inBBox);

	void getParamUIs(TParamUIConcept *&concepts, int &length);

private:
	bool m_isCastShadow;
	TRasterFxPort m_input;

	TIntEnumParamP m_distortType;

	TPointParamP m_p00_a;
	TPointParamP m_p00_b;
	TPointParamP m_p01_a;
	TPointParamP m_p01_b;
	TPointParamP m_p11_a;
	TPointParamP m_p11_b;
	TPointParamP m_p10_a;
	TPointParamP m_p10_b;

	TBoolParamP m_deactivate;
	TPixelParamP m_color;
	TDoubleParamP m_fade;
	TDoubleParamP m_upTransp;
	TDoubleParamP m_downTransp;
	TDoubleParamP m_upBlur;
	TDoubleParamP m_downBlur;
};

class FreeDistortFx : public FreeDistortBaseFx
{
	FX_PLUGIN_DECLARATION(FreeDistortFx)

public:
	FreeDistortFx() : FreeDistortBaseFx(false) {}
};

class CastShadowFx : public FreeDistortBaseFx
{
	FX_PLUGIN_DECLARATION(CastShadowFx)
public:
	CastShadowFx() : FreeDistortBaseFx(true)
	{
	}
};

//==============================================================================

FreeDistortBaseFx::FreeDistortBaseFx(bool isCastShadow)
	: m_deactivate(false), m_color(TPixel32::Black), m_fade(0.0), m_upTransp(0.0), m_downTransp(0.0), m_upBlur(0.0), m_downBlur(0.0), m_isCastShadow(isCastShadow), m_distortType(new TIntEnumParam(PERSPECTIVE, "Perspective"))
{
	m_upBlur->setMeasureName("fxLength");
	m_downBlur->setMeasureName("fxLength");
	double ext = 400.;
	double inn = 400.;
	m_p00_a = TPointD(-ext, -ext);
	m_p00_b = TPointD(-inn, -inn);

	m_p01_a = TPointD(-ext, ext);
	m_p01_b = TPointD(-inn, inn);

	m_p11_a = TPointD(ext, ext);
	m_p11_b = TPointD(inn, inn);

	m_p10_a = TPointD(ext, -ext);
	m_p10_b = TPointD(inn, -inn);

	m_p00_b->getX()->setMeasureName("fxLength");
	m_p00_b->getY()->setMeasureName("fxLength");
	m_p10_b->getX()->setMeasureName("fxLength");
	m_p10_b->getY()->setMeasureName("fxLength");
	m_p01_b->getX()->setMeasureName("fxLength");
	m_p01_b->getY()->setMeasureName("fxLength");
	m_p11_b->getX()->setMeasureName("fxLength");
	m_p11_b->getY()->setMeasureName("fxLength");

	m_p00_a->getX()->setMeasureName("fxLength");
	m_p00_a->getY()->setMeasureName("fxLength");
	m_p10_a->getX()->setMeasureName("fxLength");
	m_p10_a->getY()->setMeasureName("fxLength");
	m_p01_a->getX()->setMeasureName("fxLength");
	m_p01_a->getY()->setMeasureName("fxLength");
	m_p11_a->getX()->setMeasureName("fxLength");
	m_p11_a->getY()->setMeasureName("fxLength");

	bindParam(this, "distort_type", m_distortType);

	bindParam(this, "bottom_left_b", m_p00_b);
	bindParam(this, "bottom_right_b", m_p10_b);
	bindParam(this, "top_left_b", m_p01_b);
	bindParam(this, "top_right_b", m_p11_b);

	bindParam(this, "bottom_left_a", m_p00_a);
	bindParam(this, "bottom_right_a", m_p10_a);
	bindParam(this, "top_left_a", m_p01_a);
	bindParam(this, "top_right_a", m_p11_a);

	if (isCastShadow) {
		bindParam(this, "color", m_color);
		bindParam(this, "fade", m_fade);
		bindParam(this, "up_transp", m_upTransp);
		bindParam(this, "down_transp", m_downTransp);
		bindParam(this, "up_blur", m_upBlur);
		bindParam(this, "down_blur", m_downBlur);

		m_color->enableMatte(false);
		m_fade->setValueRange(0.0, 100.0);
		m_upTransp->setValueRange(0.0, 100.0);
		m_downTransp->setValueRange(0.0, 100.0);
		m_upBlur->setValueRange(0.0, (std::numeric_limits<double>::max)());
		m_downBlur->setValueRange(0.0, (std::numeric_limits<double>::max)());
	}

	bindParam(this, "deactivate", m_deactivate);
	addInputPort("source", m_input);

	m_p00_a->getX()->setValueRange(-1000, 1000);
	m_p00_b->getX()->setValueRange(-1000, 1000);

	m_p00_a->getY()->setValueRange(-1000, 1000);
	m_p00_b->getY()->setValueRange(-1000, 1000);

	m_p01_a->getX()->setValueRange(-1000, 1000);
	m_p01_b->getX()->setValueRange(-1000, 1000);

	m_p01_a->getY()->setValueRange(-1000, 1000);
	m_p01_b->getY()->setValueRange(-1000, 1000);

	m_p11_a->getX()->setValueRange(-1000, 1000);
	m_p11_b->getX()->setValueRange(-1000, 1000);

	m_p11_a->getY()->setValueRange(-1000, 1000);
	m_p11_b->getY()->setValueRange(-1000, 1000);

	m_p10_a->getX()->setValueRange(-1000, 1000);
	m_p10_b->getX()->setValueRange(-1000, 1000);

	m_p10_a->getY()->setValueRange(-1000, 1000);
	m_p10_b->getY()->setValueRange(-1000, 1000);

	m_distortType->addItem(BILINEAR, "Bilinear");
}

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

FreeDistortBaseFx::~FreeDistortBaseFx()
{
}

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

bool FreeDistortBaseFx::canHandle(const TRenderSettings &info, double frame)
{
	//Blurs cant deal with anisotropic affines, so these are retained above
	return m_upBlur->getValue(frame) == 0 && m_downBlur->getValue(frame) == 0;
}

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

bool FreeDistortBaseFx::doGetBBox(double frame, TRectD &bBox, const TRenderSettings &info)
{
	if (m_input.isConnected()) {
		//We'll treat this fx like a zerary one. The idea is, since
		//the task of taking images and pre-images of rects through
		//these kind of distortions is definitely non-trivial, we'll
		//simply avoid doing so...
		bBox = TConsts::infiniteRectD;
		return true;

		/*
    bool ret = m_input->doGetBBox(frame, bBox);

    if(m_deactivate->getValue())
      return ret;

    TPointD p00_a = m_p00_a->getValue(frame);
    TPointD p00_b = m_p00_b->getValue(frame);
    TPointD p10_a = m_p10_a->getValue(frame);
    TPointD p10_b = m_p10_b->getValue(frame);
    TPointD p01_a = m_p01_a->getValue(frame);
    TPointD p01_b = m_p01_b->getValue(frame);
    TPointD p11_a = m_p11_a->getValue(frame);
    TPointD p11_b = m_p11_b->getValue(frame);

		if (m_isCastShadow)
    {
			tswap(p00_a, p01_a);
      tswap(p10_a, p11_a);
		}*/

		/*TRectD source;
    source.x0 = std::min(p00_b.x, p10_b.x, p01_b.x, p11_b.x);
    source.y0 = std::min(p00_b.y, p10_b.y, p01_b.y, p11_b.y);
    source.x1 = std::max(p00_b.x, p10_b.x, p01_b.x, p11_b.x);
    source.y1 = std::max(p00_b.y, p10_b.y, p01_b.y, p11_b.y);
    bBox *= source;
    
    int distortType = m_distortType->getValue();
    if(distortType == PERSPECTIVE)
    {
      PerspectiveDistorter distorter(
          p00_b, p10_b, p01_b, p11_b,
          p00_a, p10_a, p01_a, p11_a
      );
      bBox = distorter.map(bBox);
    }
    else if(distortType == BILINEAR)
    {
      BilinearDistorter distorter(
        p00_b, p10_b, p01_b, p11_b,
        p00_a, p10_a, p01_a, p11_a
      );
      bBox = distorter.map(bBox);
    }
    else assert(0);*/

		//Further, we will also avoid the assumption that the bbox
		//is defined by that of destination points...
		/*TRectD result;
    result.x0 = std::min(p00_a.x, p10_a.x, p01_a.x, p11_a.x);
    result.y0 = std::min(p00_a.y, p10_a.y, p01_a.y, p11_a.y);
    result.x1 = std::max(p00_a.x, p10_a.x, p01_a.x, p11_a.x);
    result.y1 = std::max(p00_a.y, p10_a.y, p01_a.y, p11_a.y);
    bBox = result;    //bBox *= result;*/
	} else {
		bBox = TRectD();
		return false;
	}

	if (m_upBlur->getValue(frame) > 0 || m_downBlur->getValue(frame) > 0) {
		int brad = std::max((int)m_upBlur->getValue(frame), (int)m_downBlur->getValue(frame));
		bBox.x0 -= brad;
		bBox.x1 += brad;
		bBox.y0 -= brad;
		bBox.y1 += brad;
	}

	return true;
}

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

void FreeDistortBaseFx::transform(
	double frame,
	int port,
	const TRectD &rectOnOutput,
	const TRenderSettings &infoOnOutput,
	TRectD &rectOnInput,
	TRenderSettings &infoOnInput)
{
	TPointD p00_b = m_p00_b->getValue(frame);
	TPointD p10_b = m_p10_b->getValue(frame);
	TPointD p01_b = m_p01_b->getValue(frame);
	TPointD p11_b = m_p11_b->getValue(frame);

	TPointD p00_a = m_p00_a->getValue(frame);
	TPointD p10_a = m_p10_a->getValue(frame);
	TPointD p01_a = m_p01_a->getValue(frame);
	TPointD p11_a = m_p11_a->getValue(frame);

	if (m_isCastShadow) {
		//Shadows are mirrored
		tswap(p00_a, p01_a);
		tswap(p10_a, p11_a);
	}

	//Build the input affine
	infoOnInput = infoOnOutput;

	double scale = 0;
	scale = std::max(scale, norm(p10_a - p00_a) / norm(p10_b - p00_b));
	scale = std::max(scale, norm(p01_a - p00_a) / norm(p01_b - p00_b));
	scale = std::max(scale, norm(p11_a - p10_a) / norm(p11_b - p10_b));
	scale = std::max(scale, norm(p11_a - p01_a) / norm(p11_b - p01_b));

	scale *= sqrt(fabs(infoOnInput.m_affine.det()));

	if (infoOnOutput.m_isSwatch) {
		//Swatch viewers work out a lite version of the distort: only contractions
		//are passed below in the schematic - so that input memory load is always at minimum.
		//This is allowed in order to make fx adjusts the less frustrating as possible.
		if (scale > 1)
			scale = 1;
	}

	infoOnInput.m_affine = TScale(scale);

	//Build rectOnInput
	p00_a = infoOnOutput.m_affine * p00_a;
	p10_a = infoOnOutput.m_affine * p10_a;
	p01_a = infoOnOutput.m_affine * p01_a;
	p11_a = infoOnOutput.m_affine * p11_a;

	p00_b = infoOnInput.m_affine * p00_b;
	p10_b = infoOnInput.m_affine * p10_b;
	p01_b = infoOnInput.m_affine * p01_b;
	p11_b = infoOnInput.m_affine * p11_b;

	if (m_distortType->getValue() == PERSPECTIVE) {
		PerspectiveDistorter pd(
			p00_b, p10_b, p01_b, p11_b,
			p00_a, p10_a, p01_a, p11_a);

		rectOnInput = ((TQuadDistorter *)&pd)->invMap(rectOnOutput);
	} else {
		BilinearDistorter bd(
			p00_b, p10_b, p01_b, p11_b,
			p00_a, p10_a, p01_a, p11_a);

		rectOnInput = ((TQuadDistorter *)&bd)->invMap(rectOnOutput);
	}

	if (rectOnInput.x0 != TConsts::infiniteRectD.x0)
		rectOnInput.x0 = tfloor(rectOnInput.x0);
	if (rectOnInput.y0 != TConsts::infiniteRectD.y0)
		rectOnInput.y0 = tfloor(rectOnInput.y0);
	if (rectOnInput.x1 != TConsts::infiniteRectD.x1)
		rectOnInput.x1 = tceil(rectOnInput.x1);
	if (rectOnInput.y1 != TConsts::infiniteRectD.y1)
		rectOnInput.y1 = tceil(rectOnInput.y1);
}

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

void FreeDistortBaseFx::safeTransform(
	double frame,
	int port,
	const TRectD &rectOnOutput,
	const TRenderSettings &infoOnOutput,
	TRectD &rectOnInput,
	TRenderSettings &infoOnInput,
	TRectD &inBBox)
{
	assert(port == 0 && m_input.isConnected());

	if (m_deactivate->getValue()) {
		infoOnInput = infoOnOutput;
		rectOnInput = rectOnOutput;
		m_input->getBBox(frame, inBBox, infoOnInput);
		return;
	}

	//Avoid the singular matrix cases
	if (fabs(infoOnOutput.m_affine.det()) < 1.e-3) {
		infoOnInput = infoOnOutput;
		rectOnInput.empty();
		m_input->getBBox(frame, inBBox, infoOnInput);
		return;
	}

	transform(frame, port, rectOnOutput, infoOnOutput, rectOnInput, infoOnInput);

	m_input->getBBox(frame, inBBox, infoOnInput);

	//In case inBBox is infinite, such as with gradients, also intersect with the input quadrilateral's bbox
	if (inBBox == TConsts::infiniteRectD) {
		TPointD affP00_b(infoOnInput.m_affine * m_p00_b->getValue(frame));
		TPointD affP10_b(infoOnInput.m_affine * m_p10_b->getValue(frame));
		TPointD affP01_b(infoOnInput.m_affine * m_p01_b->getValue(frame));
		TPointD affP11_b(infoOnInput.m_affine * m_p11_b->getValue(frame));

		TRectD source;
		source.x0 = std::min({affP00_b.x, affP10_b.x, affP01_b.x, affP11_b.x});
		source.y0 = std::min({affP00_b.y, affP10_b.y, affP01_b.y, affP11_b.y});
		source.x1 = std::max({affP00_b.x, affP10_b.x, affP01_b.x, affP11_b.x});
		source.y1 = std::max({affP00_b.y, affP10_b.y, affP01_b.y, affP11_b.y});

		rectOnInput *= source;
	}
}

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

void FreeDistortBaseFx::getParamUIs(TParamUIConcept *&concepts, int &length)
{
	concepts = new TParamUIConcept[length = 14];

	concepts[0].m_type = TParamUIConcept::QUAD;
	concepts[0].m_params.push_back(m_p00_b);
	concepts[0].m_params.push_back(m_p10_b);
	concepts[0].m_params.push_back(m_p11_b);
	concepts[0].m_params.push_back(m_p01_b);

	concepts[1].m_type = TParamUIConcept::QUAD;
	concepts[1].m_params.push_back(m_p00_a);
	concepts[1].m_params.push_back(m_p10_a);
	concepts[1].m_params.push_back(m_p11_a);
	concepts[1].m_params.push_back(m_p01_a);

	concepts[2].m_type = TParamUIConcept::VECTOR;
	concepts[2].m_params.push_back(m_p00_b);
	concepts[2].m_params.push_back(m_p00_a);

	concepts[3].m_type = TParamUIConcept::VECTOR;
	concepts[3].m_params.push_back(m_p10_b);
	concepts[3].m_params.push_back(m_p10_a);

	concepts[4].m_type = TParamUIConcept::VECTOR;
	concepts[4].m_params.push_back(m_p01_b);
	concepts[4].m_params.push_back(m_p01_a);

	concepts[5].m_type = TParamUIConcept::VECTOR;
	concepts[5].m_params.push_back(m_p11_b);
	concepts[5].m_params.push_back(m_p11_a);

	concepts[6].m_type = TParamUIConcept::POINT;
	concepts[6].m_label = "Bottom Left Src";
	concepts[6].m_params.push_back(m_p00_b);

	concepts[7].m_type = TParamUIConcept::POINT;
	concepts[7].m_label = "Bottom Right Src";
	concepts[7].m_params.push_back(m_p10_b);

	concepts[8].m_type = TParamUIConcept::POINT;
	concepts[8].m_label = "Top Left Src";
	concepts[8].m_params.push_back(m_p01_b);

	concepts[9].m_type = TParamUIConcept::POINT;
	concepts[9].m_label = "Top Right Src";
	concepts[9].m_params.push_back(m_p11_b);

	concepts[10].m_type = TParamUIConcept::POINT;
	concepts[10].m_label = "Bottom Left Dst";
	concepts[10].m_params.push_back(m_p00_a);

	concepts[11].m_type = TParamUIConcept::POINT;
	concepts[11].m_label = "Bottom Right Dst";
	concepts[11].m_params.push_back(m_p10_a);

	concepts[12].m_type = TParamUIConcept::POINT;
	concepts[12].m_label = "Top Left Dst";
	concepts[12].m_params.push_back(m_p01_a);

	concepts[13].m_type = TParamUIConcept::POINT;
	concepts[13].m_label = "Top Right Dst";
	concepts[13].m_params.push_back(m_p11_a);
}

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

namespace
{

template <typename PIX>
void doFade(TRasterPT<PIX> &ras, PIX col, double intensity)
{
	double maxChannelValueD = PIX::maxChannelValue;

	int j;
	ras->lock();
	for (j = 0; j < ras->getLy(); j++) {
		PIX *pix = ras->pixels(j);
		PIX *endPix = pix + ras->getLx();
		while (pix < endPix) {
			if (pix->m > 0) {
				double factor = pix->m / maxChannelValueD;
				int val;
				val = troundp(pix->r + intensity * (col.r * factor - pix->r));
				pix->r = (val > PIX::maxChannelValue) ? PIX::maxChannelValue : val;
				val = troundp(pix->g + intensity * (col.g * factor - pix->g));
				pix->g = (val > PIX::maxChannelValue) ? PIX::maxChannelValue : val;
				val = troundp(pix->b + intensity * (col.b * factor - pix->b));
				pix->b = (val > PIX::maxChannelValue) ? PIX::maxChannelValue : val;
			}
			++pix;
		}
	}
	ras->unlock();
}

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

template <typename PIX>
void doTransparency(TRasterPT<PIX> &ras, double downTransp, double upTransp, double y0, double y1)
{
	assert(y0 <= 0.0 && y1 >= ras->getLy() - 1);

	double den = y1 - y0;

	int j;
	ras->lock();
	for (j = 0; j < ras->getLy(); j++) {
		double transp = (1 - downTransp) + (downTransp - upTransp) * (j - y0) / den;
		PIX *pix = ras->pixels(j);
		PIX *endPix = pix + ras->getLx();
		while (pix < endPix) {
			if (pix->m > 0) {
				pix->r = troundp(pix->r * transp);
				pix->g = troundp(pix->g * transp);
				pix->b = troundp(pix->b * transp);
				pix->m = troundp(pix->m * transp);
			}
			++pix;
		}
	}
	ras->unlock();
}

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

template <typename PIX>
inline void filterPixel(PIX *bufin, int wrap, PIX *bufout, double blur, int coord, int maxVal)
{
	int rad, k, index;
	double coeff, weight;
	double valr = 0.0, valg = 0.0, valb = 0.0, valm = 0.0;

	rad = tceil(blur);
	coeff = 1.0 / rad;

	valr = bufin[0].r;
	valg = bufin[0].g;
	valb = bufin[0].b;
	valm = bufin[0].m;

	for (k = 1, index = wrap, weight = (1 - coeff); k < rad; k++, index += wrap, weight -= coeff) {
		if (k + coord < maxVal) {
			if (-k + coord >= 0) {
				valr += (bufin[index].r + bufin[-index].r) * weight;
				valg += (bufin[index].g + bufin[-index].g) * weight;
				valb += (bufin[index].b + bufin[-index].b) * weight;
				valm += (bufin[index].m + bufin[-index].m) * weight;
			} else {
				valr += bufin[index].r * weight;
				valg += bufin[index].g * weight;
				valb += bufin[index].b * weight;
				valm += bufin[index].m * weight;
			}
		} else if (-k + coord >= 0) {
			if (k + coord < maxVal) {
				valr += (bufin[index].r + bufin[-index].r) * weight;
				valg += (bufin[index].g + bufin[-index].g) * weight;
				valb += (bufin[index].b + bufin[-index].b) * weight;
				valm += (bufin[index].m + bufin[-index].m) * weight;
			} else {
				valr += bufin[-index].r * weight;
				valg += bufin[-index].g * weight;
				valb += bufin[-index].b * weight;
				valm += bufin[-index].m * weight;
			}
		}
	}

	valr *= coeff;
	valg *= coeff;
	valb *= coeff;
	valm *= coeff;

	bufout->r = troundp(valr);
	bufout->g = troundp(valg);
	bufout->b = troundp(valb);
	bufout->m = troundp(valm);
}

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

template <typename PIX>
void doBlur(TRasterPT<PIX> &r,
			double blur0, double blur1,
			double transp0, double transp1,
			double y0, double y1)
{
	int lx, ly, wrap;
	int i, j, brad, blur_max;
	PIX *bufin, *bufout, *pixin, *pixout, *row;
	double cur_blur, actual_blur, blur_incr, cur_transp, actual_transp, transp_incr;

	lx = r->getLx();
	ly = r->getLy();
	wrap = r->getWrap();
	blur_max = (int)std::max(blur0, blur1);
	brad = tceil(blur_max);

	//assert(y0 <= brad && y1 >= r->getLy()-1 - 2 * brad + y0);

	double den = y1 - y0;

	blur_incr = (blur1 - blur0) / den;
	transp_incr = (transp0 - transp1) / den;

	row = new PIX[std::max(lx, ly)];
	r->lock();
	bufin = r->pixels(0);
	for (i = 0, cur_blur = blur0 + (blur1 - blur0) * (i - y0) / den, 0.0, actual_blur = std::max(cur_blur, 0.0);
		 i < ly;
		 i++, bufin += wrap, cur_blur += blur_incr, actual_blur = std::max(cur_blur, 0.0)) {
		pixin = bufin;
		for (j = 0; j < lx; j++, pixin++) {
			if (cur_blur > 1.0)
				filterPixel(pixin, 1, row + j, actual_blur, j, lx);
			else
				row[j] = *pixin;
		}
		for (j = 0; j < lx; j++)
			bufin[j] = row[j];
	}

	bufin = r->pixels(0);
	bufout = r->pixels(0);
	for (i = 0; i < lx; i++, bufin++, bufout++) {
		pixin = bufin;
		pixout = bufout;

		for (j = 0, cur_blur = blur0 + (blur1 - blur0) * (j - y0) / den, actual_blur = std::max(cur_blur, 0.0);
			 j < ly;
			 j++, pixin += wrap, cur_blur += blur_incr, actual_blur = std::max(cur_blur, 0.0)) {
			if (cur_blur > 1.0)
				filterPixel(pixin, wrap, row + j, actual_blur, j, ly);
			else
				row[j] = *pixin;
		}

		if (transp0 > 0 || transp1 > 0)
			for (j = 0, cur_transp = 1 - transp0 + (transp0 - transp1) * (j - y0) / den, actual_transp = std::max(cur_transp, 0.0);
				 j < ly;
				 j++, pixout += wrap, cur_transp += transp_incr, actual_transp = std::max(cur_transp, 0.0)) {
				pixout->r = troundp(row[j].r * actual_transp);
				pixout->g = troundp(row[j].g * actual_transp);
				pixout->b = troundp(row[j].b * actual_transp);
				pixout->m = troundp(row[j].m * actual_transp);
			}
		else
			for (j = 0; j < ly; j++, pixout += wrap) {
				pixout->r = row[j].r;
				pixout->g = row[j].g;
				pixout->b = row[j].b;
				pixout->m = row[j].m;
			}
	}
	r->unlock();
	delete[] row;
}
}

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

void FreeDistortBaseFx::doDryCompute(TRectD &rect, double frame, const TRenderSettings &info)
{
	if (!m_input.isConnected())
		return;

	if (m_deactivate->getValue()) {
		m_input->dryCompute(rect, frame, info);
		return;
	}

	TRectD rectOnInput;
	TRenderSettings infoOnInput;
	TRectD inBBox;

	safeTransform(frame, 0, rect, info, rectOnInput, infoOnInput, inBBox);

	rectOnInput *= inBBox;

	if (!myIsEmpty(rectOnInput))
		m_input->dryCompute(rectOnInput, frame, infoOnInput);
}

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

void FreeDistortBaseFx::doCompute(TTile &tile, double frame, const TRenderSettings &ri)
{
	if (!m_input.isConnected())
		return;

	//Upon deactivation, this fx does nothing.
	if (m_deactivate->getValue()) {
		m_input->compute(tile, frame, ri);
		return;
	}

	//Get the source quad
	TPointD p00_b = m_p00_b->getValue(frame);
	TPointD p10_b = m_p10_b->getValue(frame);
	TPointD p01_b = m_p01_b->getValue(frame);
	TPointD p11_b = m_p11_b->getValue(frame);

	//Get destination quad
	TPointD p00_a = m_p00_a->getValue(frame);
	TPointD p10_a = m_p10_a->getValue(frame);
	TPointD p01_a = m_p01_a->getValue(frame);
	TPointD p11_a = m_p11_a->getValue(frame);

	if (m_isCastShadow) {
		//Shadows are mirrored
		tswap(p00_a, p01_a);
		tswap(p10_a, p11_a);
	}

	//Get requested tile's geometry
	TRasterP tileRas(tile.getRaster());
	TRectD tileRect(convert(tileRas->getBounds()) + tile.m_pos);

	//Call transform to get the minimal rectOnInput
	TRectD inRect;
	TRenderSettings riNew;
	TRectD inBBox;

	safeTransform(frame, 0, tileRect, ri, inRect, riNew, inBBox);

	//Intersect with the bbox
	inRect *= inBBox;

	if (myIsEmpty(inRect))
		return;

	double scale = ri.m_affine.a11;

	double downBlur = m_downBlur->getValue(frame) * scale;
	double upBlur = m_upBlur->getValue(frame) * scale;
	int brad = tceil(std::max(downBlur, upBlur));

	inRect = inRect.enlarge(brad);

	TDimension inRectSize(tceil(inRect.getLx()), tceil(inRect.getLy()));

	TTile inTile;
	m_input->allocateAndCompute(inTile, inRect.getP00(), inRectSize, tileRas, frame, riNew);

	TPointD inTilePosRi = inTile.m_pos;

	//Update quads by the scale factors
	p00_b = riNew.m_affine * p00_b;
	p10_b = riNew.m_affine * p10_b;
	p01_b = riNew.m_affine * p01_b;
	p11_b = riNew.m_affine * p11_b;

	p00_a = ri.m_affine * p00_a;
	p10_a = ri.m_affine * p10_a;
	p01_a = ri.m_affine * p01_a;
	p11_a = ri.m_affine * p11_a;

	PerspectiveDistorter perpDistorter(
		p00_b - inTile.m_pos, p10_b - inTile.m_pos, p01_b - inTile.m_pos, p11_b - inTile.m_pos,
		p00_a, p10_a, p01_a, p11_a);

	BilinearDistorter bilDistorter(
		p00_b - inTile.m_pos, p10_b - inTile.m_pos, p01_b - inTile.m_pos, p11_b - inTile.m_pos,
		p00_a, p10_a, p01_a, p11_a);

	TQuadDistorter *distorter;
	if (m_distortType->getValue() == PERSPECTIVE)
		distorter = &perpDistorter;
	else if (m_distortType->getValue() == BILINEAR)
		distorter = &bilDistorter;
	else
		assert(0);

	if (m_isCastShadow) {
		TRaster32P ras32 = inTile.getRaster();
		TRaster64P ras64 = inTile.getRaster();

		if (ras32) {
			if (m_fade->getValue(frame) > 0)
				doFade(ras32, m_color->getValue(frame), m_fade->getValue(frame) / 100.0);
			if (brad > 0)
				doBlur(ras32, upBlur, downBlur,
					   m_upTransp->getValue(frame) / 100.0, m_downTransp->getValue(frame) / 100.0,
					   inBBox.y0 - inTile.m_pos.y, inBBox.y1 - inTile.m_pos.y);
			else if (m_upTransp->getValue(frame) > 0 || m_downTransp->getValue(frame) > 0)
				doTransparency(ras32, m_upTransp->getValue(frame) / 100.0, m_downTransp->getValue(frame) / 100.0,
							   inBBox.y0 - inTile.m_pos.y, inBBox.y1 - inTile.m_pos.y);
		} else if (ras64) {
			if (m_fade->getValue(frame) > 0)
				doFade(ras64, toPixel64(m_color->getValue(frame)), m_fade->getValue(frame) / 100.0);
			if (brad > 0)
				doBlur(ras64, upBlur, downBlur,
					   m_upTransp->getValue(frame) / 100.0, m_downTransp->getValue(frame) / 100.0,
					   inBBox.y0 - inTile.m_pos.y, inBBox.y1 - inTile.m_pos.y);
			else if (m_upTransp->getValue(frame) > 0 || m_downTransp->getValue(frame) > 0)
				doTransparency(ras64, m_upTransp->getValue(frame) / 100.0, m_downTransp->getValue(frame) / 100.0,
							   inBBox.y0 - inTile.m_pos.y, inBBox.y1 - inTile.m_pos.y);
		} else
			assert(false);
	}

	distort(tileRas, inTile.getRaster(), *distorter, convert(tile.m_pos), TRop::Bilinear);
}

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

int FreeDistortBaseFx::getMemoryRequirement(const TRectD &rect, double frame, const TRenderSettings &info)
{
	if (!m_input.isConnected())
		return 0;

	TRectD inRect;
	TRenderSettings riNew;
	TRectD inBBox;
	safeTransform(frame, 0, rect, info, inRect, riNew, inBBox);
	inRect *= inBBox;

	return TRasterFx::memorySize(inRect, riNew.m_bpp);
}

FX_PLUGIN_IDENTIFIER(FreeDistortFx, "freeDistortFx");
FX_PLUGIN_IDENTIFIER(CastShadowFx, "castShadowFx");

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