+/* === 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
+**
+** 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/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/curve_helper.h>
+
+#endif
+
+/* === U S I N G =========================================================== */
+
+using namespace std;
+using namespace etl;
+using namespace synfig;
+
+/* === 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()
+:center(0,0),
+radius(1),
+percent(1.0),
+type(TYPE_NORMAL),
+clip(false)
+{
+}
+
+
+bool
+Layer_SphereDistort::set_param(const String & param, const ValueBase &value)
+{
+ IMPORT_PLUS(center,sync());
+ IMPORT_PLUS(radius,sync());
+ IMPORT(type);
+ IMPORT_AS(percent,"amount");
+ IMPORT(clip);
+
+ if(param=="percent")
+ {
+ if(dynamic_param_list().count("percent"))
+ {
+ connect_dynamic_param("amount",dynamic_param_list().find("percent")->second);
+ disconnect_dynamic_param("percent");
+ synfig::warning("Layer_SphereDistort::::set_param(): Updated valuenode connection to use the new \"amount\" parameter.");
+ }
+ else
+ synfig::warning("Layer_SphereDistort::::set_param(): The parameter \"segment_list\" is deprecated. Use \"bline\" instead.");
+ }
+
+ return false;
+}
+
+ValueBase
+Layer_SphereDistort::get_param(const String ¶m)const
+{
+ EXPORT(center);
+ EXPORT(radius);
+ EXPORT(type);
+ EXPORT_AS(percent,"amount");
+ EXPORT(clip);
+
+ EXPORT_NAME();
+ EXPORT_VERSION();
+
+ return ValueBase();
+}
+
+void
+Layer_SphereDistort::sync()
+{
+}
+
+Layer::Vocab
+Layer_SphereDistort::get_param_vocab()const
+{
+ Layer::Vocab ret;
+
+ ret.push_back(ParamDesc("center")
+ .set_local_name(_("Position"))
+ );
+
+ ret.push_back(ParamDesc("radius")
+ .set_local_name(_("Radius"))
+ .set_origin("center")
+ .set_is_distance()
+ );
+
+ ret.push_back(ParamDesc("amount")
+ .set_local_name(_("Amount"))
+ .set_is_distance(false)
+ );
+
+ ret.push_back(ParamDesc("clip")
+ .set_local_name(_("Clip"))
+ );
+
+ 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 ¢er, 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 ¢er, const Real &radius,
+ const Real &percent, int type)
+{
+ bool tmp;
+ return sphtrans(p, center, radius, percent, type, tmp);
+}
+
+synfig::Layer::Handle
+Layer_SphereDistort::hit_check(synfig::Context context, const synfig::Point &pos)const
+{
+ 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
+{
+ bool clipped;
+ Point point(sphtrans(pos,center,radius,percent,type,clipped));
+ if(clip && clipped)
+ return Color::alpha();
+ return context.get_color(point);
+}
+
+#if 1
+bool Layer_SphereDistort::accelerated_render(Context context,Surface *surface,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
+ */
+
+ //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)) )
+ {
+ //synfig::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);
+ }
+
+ //synfig::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]);
+
+ //synfig::warning("Spherize: Loop through lines and stuff");
+ for(int i=0; i<4; ++i)
+ {
+ //synfig::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]);
+ }
+
+ /*synfig::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
+
+ //synfig::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;
+
+ //synfig::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;*/
+ }
+
+ //synfig::warning("Spherize: render background");
+ if(!context.accelerated_render(&background,quality,r,cb))
+ {
+ synfig::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();
+
+ //synfig::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))
+ {
+ //synfig::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;
+}
+#endif
+
+class synfig::Spherize_Trans : public synfig::Transform
+{
+ etl::handle<const Layer_SphereDistort> layer;
+public:
+ Spherize_Trans(const Layer_SphereDistort* x):Transform(x->get_guid()),layer(x) { }
+
+ synfig::Vector perform(const synfig::Vector& x)const
+ {
+ return sphtrans(x,layer->center,layer->radius,-layer->percent,layer->type);
+ }
+
+ synfig::Vector unperform(const synfig::Vector& x)const
+ {
+ return sphtrans(x,layer->center,layer->radius,layer->percent,layer->type);
+ }
+};
+
+etl::handle<Transform>
+Layer_SphereDistort::get_transform()const
+{
+ return new Spherize_Trans(this);
+}
+
+Rect
+Layer_SphereDistort::get_bounding_rect()const
+{
+ 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;
+}