/* === S Y N F I G ========================================================= */
/*!	\file sphere_distort.cpp
**	\brief Implementation of the "Spherize" layer
**
**	$Id$
**
**	\legal
**	Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
**	Copyright (c) 2007, 2008 Chris Moore
**	Copyright (c) 2011 Carlos López
**
**	This package is free software; you can redistribute it and/or
**	modify it under the terms of the GNU General Public License as
**	published by the Free Software Foundation; either version 2 of
**	the License, or (at your option) any later version.
**
**	This package is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**	General Public License for more details.
**	\endlegal
*/
/* ========================================================================= */

/* === H E A D E R S ======================================================= */

#ifdef USING_PCH
#	include "pch.h"
#else
#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#include "sphere_distort.h"

#include <synfig/localization.h>
#include <synfig/general.h>

#include <synfig/string.h>
#include <synfig/time.h>
#include <synfig/context.h>
#include <synfig/paramdesc.h>
#include <synfig/renddesc.h>
#include <synfig/surface.h>
#include <synfig/value.h>
#include <synfig/valuenode.h>
#include <synfig/transform.h>
#include <synfig/cairo_renddesc.h>

#include <synfig/curve_helper.h>

#endif

/* === U S I N G =========================================================== */

using namespace std;
using namespace etl;
using namespace synfig;
using namespace modules;
using namespace lyr_std;

/* === M A C R O S ========================================================= */

#ifndef PI
const double PI = 3.14159265;
#endif

enum
{
	TYPE_NORMAL = 0,
	TYPE_DISTH = 1, //axe the horizontal axis
	TYPE_DISTV = 2, //axe the vertical axis
	N_TYPES
};

/* === G L O B A L S ======================================================= */

SYNFIG_LAYER_INIT(Layer_SphereDistort);
SYNFIG_LAYER_SET_NAME(Layer_SphereDistort,"spherize");
SYNFIG_LAYER_SET_LOCAL_NAME(Layer_SphereDistort,N_("Spherize"));
SYNFIG_LAYER_SET_CATEGORY(Layer_SphereDistort,N_("Distortions"));
SYNFIG_LAYER_SET_VERSION(Layer_SphereDistort,"0.2");
SYNFIG_LAYER_SET_CVS_ID(Layer_SphereDistort,"$Id$");

/* === P R O C E D U R E S ================================================= */

/* === M E T H O D S ======================================================= */

/* === E N T R Y P O I N T ================================================= */

Layer_SphereDistort::Layer_SphereDistort():
param_center(ValueBase(Vector(0,0))),
param_radius(ValueBase(double(1))),
param_amount(ValueBase(double(1))),
param_type(ValueBase(int(TYPE_NORMAL))),
param_clip(ValueBase(false))
{
	SET_INTERPOLATION_DEFAULTS();
	SET_STATIC_DEFAULTS();
}


bool
Layer_SphereDistort::set_param(const String & param, const ValueBase &value)
{
	IMPORT_VALUE_PLUS(param_center,sync());
	IMPORT_VALUE_PLUS(param_radius,sync());
	IMPORT_VALUE(param_type);
	IMPORT_VALUE(param_amount);
	IMPORT_VALUE(param_clip);

	if(param=="percent" && param_amount.get_type()==value.get_type())
		return set_param("amount", value);

	return Layer::set_param(param,value);
}

ValueBase
Layer_SphereDistort::get_param(const String &param)const
{
	EXPORT_VALUE(param_center);
	EXPORT_VALUE(param_radius);
	EXPORT_VALUE(param_type);
	EXPORT_VALUE(param_amount);
	EXPORT_VALUE(param_clip);
	if(param=="percent")
		return get_param("amount");

	EXPORT_NAME();
	EXPORT_VERSION();

	return Layer::get_param(param);
}

void
Layer_SphereDistort::sync()
{
// Remove me?
}

Layer::Vocab
Layer_SphereDistort::get_param_vocab()const
{
	Layer::Vocab ret;

	ret.push_back(ParamDesc("center")
		.set_local_name(_("Position"))
		.set_description(_("Where the sphere distortion is centered"))
	);

	ret.push_back(ParamDesc("radius")
		.set_local_name(_("Radius"))
		.set_origin("center")
		.set_is_distance()
		.set_description(_("The size of the sphere distortion"))
	);

	ret.push_back(ParamDesc("amount")
		.set_local_name(_("Amount"))
		.set_is_distance(false)
		.set_description(_("The distortion intensity (negative values inverts effect)"))
	);

	ret.push_back(ParamDesc("clip")
		.set_local_name(_("Clip"))
		.set_description(_("When cheked, the area outside the Radius are not distorted"))
	);

	ret.push_back(ParamDesc("type")
		.set_local_name(_("Distort Type"))
		.set_description(_("The direction of the distortion"))
		.set_hint("enum")
		.add_enum_value(TYPE_NORMAL,"normal",_("Spherize"))
		.add_enum_value(TYPE_DISTH,"honly",_("Vertical Bar"))
		.add_enum_value(TYPE_DISTV,"vonly",_("Horizontal Bar"))
	);

	return ret;
}

/*
	Spherical Distortion: maps an image onto a ellipsoid of some sort

	so the image coordinate (i.e. distance away from the center)
	will determine how things get mapped

	so with the radius and position the mapping would go as follows

	r = (pos - center) / radius	clamped to [-1,1]

	if it's outside of that range then it's not distorted
	but if it's inside of that range then it goes as follows

	angle = r * pi/2 (-pi/2,pi/2)

	newr = cos(angle)*radius

	the inverse of this is (which is actually what we'd be transforming it from


*/

inline float spherify(float f)
{
	if(f > -1 && f < 1 && f!=0)
		return sinf(f*(PI/2));
	else return f;
}

inline float unspherify(float f)
{
	if(f > -1 && f < 1 && f!=0)
		return asin(f)/(PI/2);
	else return f;
}

Point sphtrans(const Point &p, const Point &center, const float &radius,
											const Real &percent, int type, bool& clipped)
{
	const Vector v = (p - center) / radius;

	Point newp = p;
	const float t = percent;

	clipped=false;

	if(type == TYPE_NORMAL)
	{
		const float m = v.mag();
		float lerp(0);

		if(m <= -1 || m >= 1)
		{
			clipped=true;
			return newp;
		}else
		if(m==0)
			return newp;
		else
		if(t > 0)
		{
			lerp = (t*unspherify(m) + (1-t)*m);
		}else if(t < 0)
		{
			lerp = ((1+t)*m - t*spherify(m));
		}else lerp = m;

		const float d = lerp*radius;
		newp = center + v*(d/m);
	}

	else if(type == TYPE_DISTH)
	{
		float lerp(0);
		if(v[0] <= -1 || v[0] >= 1)
		{
			clipped=true;
			return newp;
		}else
		if(v[0]==0)
			return newp;
		else
		if(t > 0)
		{
			lerp = (t*unspherify(v[0]) + (1-t)*v[0]);
		}else if(t < 0)
		{
			lerp = ((1+t)*v[0] - t*spherify(v[0]));
		}else lerp = v[0];

		newp[0] = center[0] + lerp*radius;
	}

	else if(type == TYPE_DISTV)
	{
		float lerp(0);
		if(v[1] <= -1 || v[1] >= 1)
		{
			clipped=true;
			return newp;
		}
		else
		if(v[1]==0)
			return newp;
		else
		if(t > 0)
		{
			lerp = (t*unspherify(v[1]) + (1-t)*v[1]);
		}else if(t < 0)
		{
			lerp = ((1+t)*v[1] - t*spherify(v[1]));
		}else lerp = v[1];

		newp[1] = center[1] + lerp*radius;
	}

	return newp;
}

inline Point sphtrans(const Point &p, const Point &center, const Real &radius,
											const Real &percent, int type)
{
	bool tmp;
	return sphtrans(p, center, radius, percent, type, tmp);
}

Layer::Handle
Layer_SphereDistort::hit_check(Context context, const Point &pos)const
{
	Vector center=param_center.get(Vector());
	double radius=param_radius.get(double());
	double percent=param_amount.get(double());
	int type=param_type.get(int());
	bool clip=param_clip.get(bool());
	
	bool clipped;
	Point point(sphtrans(pos,center,radius,percent,type,clipped));
	if(clip && clipped)
		return 0;
	return context.hit_check(point);
}

Color
Layer_SphereDistort::get_color(Context context, const Point &pos)const
{
	Vector center=param_center.get(Vector());
	double radius=param_radius.get(double());
	double percent=param_amount.get(double());
	int type=param_type.get(int());
	bool clip=param_clip.get(bool());

	bool clipped;
	Point point(sphtrans(pos,center,radius,percent,type,clipped));
	if(clip && clipped)
		return Color::alpha();
	return context.get_color(point);
}

RendDesc
Layer_SphereDistort::get_sub_renddesc_vfunc(const RendDesc &renddesc) const
{
	RendDesc desc(renddesc);
	Real pw = desc.get_pw();
	Real ph = desc.get_ph();
	desc.set_tl(Vector(-10.0, -10.0));
	desc.set_br(Vector( 10.0,  10.0));
	desc.set_wh(
		(int)approximate_ceil(fabs((desc.get_br()[0] - desc.get_tl()[0])/pw)),
		(int)approximate_ceil(fabs((desc.get_br()[1] - desc.get_tl()[1])/ph)) );
	return desc;
}

#if 1
bool
Layer_SphereDistort::accelerated_render(Context context,Surface *surface,int quality, const RendDesc &renddesc, ProgressCallback *cb)const
{
	RENDER_TRANSFORMED_IF_NEED(__FILE__, __LINE__)

	/*	Things to consider:
		1) Block expansion for distortion (ouch... quality level??)
		2) Bounding box clipping
		3) Super sampling for better visual quality (based on the quality level?)
		4) Interpolation type for sampling (based on quality level?)

		//things to defer until after
		super sampling, non-linear interpolation
	*/

	//bounding box reject
	Vector center=param_center.get(Vector());
	double radius=param_radius.get(double());
	double percent=param_amount.get(double());
	int type=param_type.get(int());
	bool clip=param_clip.get(bool());
	{
		Rect	sphr;

		sphr.set_point(center[0]-radius,center[1]-radius);
		sphr.expand(center[0]+radius,center[1]+radius);

		//get the bounding box of the transform
		Rect	windr;

		//and the bounding box of the rendering
		windr.set_point(renddesc.get_tl()[0],renddesc.get_tl()[1]);
		windr.expand(renddesc.get_br()[0],renddesc.get_br()[1]);

		//test bounding boxes for collision
		if( (type == TYPE_NORMAL && !intersect(sphr,windr)) ||
			(type == TYPE_DISTH && (sphr.minx >= windr.maxx || windr.minx >= sphr.maxx)) ||
			(type == TYPE_DISTV && (sphr.miny >= windr.maxy || windr.miny >= sphr.maxy)) )
		{
			//warning("Spherize: Bounding box reject");
			if (clip)
			{
				surface->set_wh(renddesc.get_w(), renddesc.get_h());
				surface->clear();
				return true;
			}
			else
				return context.accelerated_render(surface,quality,renddesc,cb);
		}

		//warning("Spherize: Bounding box accept");
	}

	//Ok, so we overlap some... now expand the window for rendering
	RendDesc r = renddesc;
	Surface background;
	Real pw = renddesc.get_pw(),ph = renddesc.get_ph();

	int nl=0,nt=0,nr=0,nb=0, nw=0,nh=0;
	Point tl = renddesc.get_tl(), br = renddesc.get_br();

	{
		//must enlarge window by pixel coordinates so go!

		//need to figure out closest and farthest point and distort THOSE

		Point origin[4] = {tl,tl,br,br};
		Vector v[4] = {Vector(0,br[1]-tl[1]),
					   Vector(br[0]-tl[0],0),
					   Vector(0,tl[1]-br[1]),
					   Vector(tl[0]-br[0],0)};

		Point close(0,0);
		Real t = 0;
		Rect	expandr(tl,br);

		//expandr.set_point(tl[0],tl[1]);
		//expandr.expand(br[0],br[1]);

		//warning("Spherize: Loop through lines and stuff");
		for(int i=0; i<4; ++i)
		{
			//warning("Spherize: 	%d", i);
			Vector p_o = center-origin[i];

			//project onto left line
			t = (p_o*v[i])/v[i].mag_squared();

			//clamp
			if(t < 0) t = 0; if(t > 1) t = 1;

			close = origin[i] + v[i]*t;

			//now get transforms and expand the rectangle to accommodate
			Point p = sphtrans(close,center,radius,percent,type);
			expandr.expand(p[0],p[1]);
			p = sphtrans(origin[i],center,radius,percent,type);
			expandr.expand(p[0],p[1]);
			p = sphtrans(origin[i]+v[i],center,radius,percent,type);
			expandr.expand(p[0],p[1]);
		}

		/*warning("Spherize: Bounding box (%f,%f)-(%f,%f)",
							expandr.minx,expandr.miny,expandr.maxx,expandr.maxy);*/

		//now that we have the bounding rectangle of ALL the pixels (should be...)
		//order it so that it's in the same orientation as the tl,br pair

		//warning("Spherize: Organize like tl,br");
		Point ntl(0,0),nbr(0,0);

		//sort x
		if(tl[0] < br[0])
		{
			ntl[0] = expandr.minx;
			nbr[0] = expandr.maxx;
		}
		else
		{
			ntl[0] = expandr.maxx;
			nbr[0] = expandr.minx;
		}

		//sort y
		if(tl[1] < br[1])
		{
			ntl[1] = expandr.miny;
			nbr[1] = expandr.maxy;
		}
		else
		{
			ntl[1] = expandr.maxy;
			nbr[1] = expandr.miny;
		}

		//now expand the window as needed
		Vector temp = ntl-tl;

		//pixel offset
		nl = (int)(temp[0]/pw)-1;
		nt = (int)(temp[1]/ph)-1;

		temp = nbr - br;
		nr = (int)(temp[0]/pw)+1;
		nb = (int)(temp[1]/ph)+1;

		nw = renddesc.get_w() + nr - nl;
		nh = renddesc.get_h() + nb - nt;

		//warning("Spherize: Setting subwindow (%d,%d) (%d,%d) (%d,%d)",nl,nt,nr,nb,nw,nh);
		r.set_subwindow(nl,nt,nw,nh);

		/*r = renddesc;
		nw = r.get_w(), nh = r.get_h();
		nl = 0, nt = 0;*/
	}

	//warning("Spherize: render background");
	if(!context.accelerated_render(&background,quality,r,cb))
	{
		warning("SphereDistort: Layer below failed");
		return false;
	}

	//now distort and check to make sure we aren't overshooting our bounds here
	int w = renddesc.get_w(), h = renddesc.get_h();
	surface->set_wh(w,h);

	Point sample = tl, sub = tl, trans(0,0);
	float xs = 0,ys = 0;
	int y=0,x=0;
	Real invpw = 1/pw, invph = 1/ph;
	Surface::pen	p = surface->begin();

	Point rtl = r.get_tl();

	//warning("Spherize: About to transform");

	for(y = 0; y < h; ++y, sample[1] += ph, p.inc_y())
	{
		sub = sample;
		for(x = 0; x < w; ++x, sub[0] += pw, p.inc_x())
		{
			bool clipped;
			trans=sphtrans(sub,center,radius,percent,type,clipped);
			if(clip && clipped)
			{
				p.put_value(Color::alpha());
				continue;
			}

			xs = (trans[0]-rtl[0])*invpw;
			ys = (trans[1]-rtl[1])*invph;

			if(!(xs >= 0 && xs < nw && ys >= 0 && ys < nh))
			{
				//warning("Spherize: we failed to account for %f,%f",xs,ys);
				p.put_value(context.get_color(trans));//Color::alpha());
				continue;
			}

			//sample at that pixel location based on the quality
			if(quality <= 4)	// cubic
				p.put_value(background.cubic_sample(xs,ys));
			else if(quality <= 5) // cosine
				p.put_value(background.cosine_sample(xs,ys));
			else if(quality <= 6) // linear
				p.put_value(background.linear_sample(xs,ys));
			else				// nearest
				p.put_value(background[round_to_int(ys)][round_to_int(xs)]);
		}
		p.dec_x(w);
	}

	return true;
}

////////
bool
Layer_SphereDistort::accelerated_cairorender(Context context, cairo_t *cr, int quality, const RendDesc &renddesc_, ProgressCallback *cb)const
{
	/*	Things to consider:
	 1) Block expansion for distortion (ouch... quality level??)
	 2) Bounding box clipping
	 3) Super sampling for better visual quality (based on the quality level?)
	 4) Interpolation type for sampling (based on quality level?)
	 
	 //things to defer until after
	 super sampling, non-linear interpolation
	 */
	
	Vector center=param_center.get(Vector());
	double radius=param_radius.get(double());
	double percent=param_amount.get(double());
	int type=param_type.get(int());
	bool clip=param_clip.get(bool());

	RendDesc	renddesc(renddesc_);
	
	// Untransform the render desc
	if(!cairo_renddesc_untransform(cr, renddesc))
		return false;
	
	//bounding box reject
	{
		Rect	sphr;
		
		sphr.set_point(center[0]-radius,center[1]-radius);
		sphr.expand(center[0]+radius,center[1]+radius);
		
		//get the bounding box of the transform
		Rect	windr;
		
		//and the bounding box of the rendering
		windr.set_point(renddesc.get_tl()[0],renddesc.get_tl()[1]);
		windr.expand(renddesc.get_br()[0],renddesc.get_br()[1]);
		
		//test bounding boxes for collision
		if( (type == TYPE_NORMAL && !intersect(sphr,windr)) ||
		   (type == TYPE_DISTH && (sphr.minx >= windr.maxx || windr.minx >= sphr.maxx)) ||
		   (type == TYPE_DISTV && (sphr.miny >= windr.maxy || windr.miny >= sphr.maxy)) )
		{
			if (clip)
			{
				cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
				cairo_paint(cr);
				return true;
			}
			else
				return context.accelerated_cairorender(cr,quality,renddesc,cb);
		}
	}
	
	//Ok, so we overlap some... now expand the window for rendering
	RendDesc r = renddesc;
	cairo_surface_t* background, *result;
	int nl=0,nt=0,nr=0,nb=0, nw=0,nh=0;
	
	// grab the current renddesc interesting values
	const Real pw = renddesc.get_pw(),ph = renddesc.get_ph();
	const Point tl = renddesc.get_tl(), br = renddesc.get_br();
	const int w = renddesc.get_w(), h = renddesc.get_h();
	
	{
		//must enlarge window by pixel coordinates so go!
		
		//need to figure out closest and farthest point and distort THOSE
		
		Point origin[4] = {tl,tl,br,br};
		Vector v[4] = {Vector(0,br[1]-tl[1]),
			Vector(br[0]-tl[0],0),
			Vector(0,tl[1]-br[1]),
			Vector(tl[0]-br[0],0)};
		
		Point close(0,0);
		Real t = 0;
		Rect	expandr(tl,br);
		
		//expandr.set_point(tl[0],tl[1]);
		//expandr.expand(br[0],br[1]);
		
		for(int i=0; i<4; ++i)
		{
			Vector p_o = center-origin[i];
			
			//project onto left line
			t = (p_o*v[i])/v[i].mag_squared();
			
			//clamp
			if(t < 0) t = 0; if(t > 1) t = 1;
			
			close = origin[i] + v[i]*t;
			
			//now get transforms and expand the rectangle to accommodate
			Point p = sphtrans(close,center,radius,percent,type);
			expandr.expand(p[0],p[1]);
			p = sphtrans(origin[i],center,radius,percent,type);
			expandr.expand(p[0],p[1]);
			p = sphtrans(origin[i]+v[i],center,radius,percent,type);
			expandr.expand(p[0],p[1]);
		}
		
		//now that we have the bounding rectangle of ALL the pixels (should be...)
		//order it so that it's in the same orientation as the tl,br pair
		
		Point ntl(0,0),nbr(0,0);
		
		//sort x
		if(tl[0] < br[0])
		{
			ntl[0] = expandr.minx;
			nbr[0] = expandr.maxx;
		}
		else
		{
			ntl[0] = expandr.maxx;
			nbr[0] = expandr.minx;
		}
		
		//sort y
		if(tl[1] < br[1])
		{
			ntl[1] = expandr.miny;
			nbr[1] = expandr.maxy;
		}
		else
		{
			ntl[1] = expandr.maxy;
			nbr[1] = expandr.miny;
		}
		
		//now expand the window as needed
		Vector temp = ntl-tl;
		
		//pixel offset
		nl = (int)(temp[0]/pw)-1;
		nt = (int)(temp[1]/ph)-1;
		
		temp = nbr - br;
		nr = (int)(temp[0]/pw)+1;
		nb = (int)(temp[1]/ph)+1;
		
		nw = renddesc.get_w() + nr - nl;
		nh = renddesc.get_h() + nb - nt;
		
		r.set_subwindow(nl,nt,nw,nh);
	}
	// New values for expanded
	const double wpw =r.get_pw();
	const double wph =r.get_ph();
	const double wtlx=r.get_tl()[0];
	const double wtly=r.get_tl()[1];

	// now we know the sice of the needed background, create the cairo surface
	background=cairo_surface_create_similar(cairo_get_target(cr), CAIRO_CONTENT_COLOR_ALPHA, nw, nh);
	result=cairo_surface_create_similar(cairo_get_target(cr), CAIRO_CONTENT_COLOR_ALPHA, w, h);
	// render the background
	cairo_t* subcr=cairo_create(background);
	cairo_scale(subcr, 1/wpw, 1/wph);
	cairo_translate(subcr, -wtlx, -wtly);
	if(!context.accelerated_cairorender(subcr,quality,r,cb))
	{
		warning("Cairo SphereDistort: Layer below failed");
		return false;
	}
	cairo_destroy(subcr);
	//now distort and check to make sure we aren't overshooting our bounds here
	
	Point sample = tl, sub = tl, trans(0,0);
	float xs = 0,ys = 0;
	int y=0,x=0;
	Real invpw = 1/pw, invph = 1/ph;
	Point rtl = r.get_tl();
	
	CairoSurface cresult(result);
	if(!cresult.map_cairo_image())
	{
		warning("Sphere Distort: map cairo surface failed");
		return false;
	}
	CairoSurface cbackground(background);
	if(!cbackground.map_cairo_image())
	{
		warning("Sphere Distort: map cairo surface failed");
		return false;
	}
	
	for(y = 0; y < h; ++y, sample[1] += ph)
	{
		sub = sample;
		for(x = 0; x < w; ++x, sub[0] += pw)
		{
			bool clipped;
			trans=sphtrans(sub,center,radius,percent,type,clipped);
			if(clip && clipped)
			{
				cresult[y][x]=CairoColor::alpha();
				continue;
			}
			
			xs = (trans[0]-rtl[0])*invpw;
			ys = (trans[1]-rtl[1])*invph;
			
			if(!(xs >= 0 && xs < nw && ys >= 0 && ys < nh))
			{
				cresult[y][x]=context.get_cairocolor(trans).premult_alpha();
				continue;
			}
			
			//sample at that pixel location based on the quality
			if(quality <= 4)	// cubic
				cresult[y][x]=cbackground.cubic_sample_cooked(xs, ys).premult_alpha();
			else if(quality <= 5) // cosine
				cresult[y][x]=cbackground.cosine_sample_cooked(xs, ys).premult_alpha();
			else if(quality <= 6) // linear
				cresult[y][x]=cbackground.linear_sample_cooked(xs, ys).premult_alpha();
			else				// nearest
				cresult[y][x]=cbackground[round_to_int(ys)][round_to_int(xs)].premult_alpha();
		}
	}
	cresult.unmap_cairo_image();
	cbackground.unmap_cairo_image();
	
	cairo_surface_destroy(background);
	
	cairo_save(cr);
	
	cairo_translate(cr, tl[0], tl[1]);
	cairo_scale(cr, pw, ph);
	cairo_set_source_surface(cr, result, 0, 0);
	cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
	cairo_paint(cr);
	
	cairo_restore(cr);
	cairo_surface_destroy(result);
	
	return true;
}

#endif

class lyr_std::Spherize_Trans : public Transform
{
	etl::handle<const Layer_SphereDistort> layer;
public:
	Spherize_Trans(const Layer_SphereDistort* x):Transform(x->get_guid()),layer(x) { }

	Vector perform(const Vector& x)const
	{
		return sphtrans(x,layer->param_center.get(Vector()),layer->param_radius.get(double()),-layer->param_amount.get(double()),layer->param_type.get(int()));
	}

	Vector unperform(const Vector& x)const
	{
		return sphtrans(x,layer->param_center.get(Vector()),layer->param_radius.get(double()),-layer->param_amount.get(double()),layer->param_type.get(int()));
	}

	String get_string()const
	{
		return "spheredistort";
	}
};

etl::handle<Transform>
Layer_SphereDistort::get_transform()const
{
	return new Spherize_Trans(this);
}

Rect
Layer_SphereDistort::get_bounding_rect()const
{
	Vector center=param_center.get(Vector());
	double radius=param_radius.get(double());
	int type=param_type.get(int());
	bool clip=param_clip.get(bool());

	Rect bounds(Rect::full_plane());

	if (clip)
		return bounds;

	switch(type)
	{
		case TYPE_NORMAL:
			bounds=Rect(center[0]+radius, center[1]+radius,
						center[0]-radius, center[1]-radius);
			break;
		case TYPE_DISTH:
			bounds = Rect::vertical_strip(center[0]-radius, center[0]+radius);
			break;
		case TYPE_DISTV:
			bounds = Rect::horizontal_strip(center[1]-radius, center[1]+radius);
			break;
		default:
			break;
	}

	return bounds;
}
