1 /* === S Y N F I G ========================================================= */
2 /*! \file sphere_distort.cpp
3 ** \brief Implementation of the "Spherize" layer
8 ** Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
9 ** Copyright (c) 2007, 2008 Chris Moore
11 ** This package is free software; you can redistribute it and/or
12 ** modify it under the terms of the GNU General Public License as
13 ** published by the Free Software Foundation; either version 2 of
14 ** the License, or (at your option) any later version.
16 ** This package is distributed in the hope that it will be useful,
17 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 ** General Public License for more details.
22 /* ========================================================================= */
24 /* === H E A D E R S ======================================================= */
33 #include "sphere_distort.h"
34 #include <synfig/string.h>
35 #include <synfig/time.h>
36 #include <synfig/context.h>
37 #include <synfig/paramdesc.h>
38 #include <synfig/renddesc.h>
39 #include <synfig/surface.h>
40 #include <synfig/value.h>
41 #include <synfig/valuenode.h>
42 #include <synfig/transform.h>
44 #include <synfig/curve_helper.h>
48 /* === U S I N G =========================================================== */
52 using namespace synfig;
54 /* === M A C R O S ========================================================= */
57 const double PI = 3.14159265;
63 TYPE_DISTH = 1, //axe the horizontal axis
64 TYPE_DISTV = 2, //axe the vertical axis
68 /* === G L O B A L S ======================================================= */
70 SYNFIG_LAYER_INIT(Layer_SphereDistort);
71 SYNFIG_LAYER_SET_NAME(Layer_SphereDistort,"spherize");
72 SYNFIG_LAYER_SET_LOCAL_NAME(Layer_SphereDistort,N_("Spherize"));
73 SYNFIG_LAYER_SET_CATEGORY(Layer_SphereDistort,N_("Distortions"));
74 SYNFIG_LAYER_SET_VERSION(Layer_SphereDistort,"0.2");
75 SYNFIG_LAYER_SET_CVS_ID(Layer_SphereDistort,"$Id$");
77 /* === P R O C E D U R E S ================================================= */
79 /* === M E T H O D S ======================================================= */
81 /* === E N T R Y P O I N T ================================================= */
83 Layer_SphereDistort::Layer_SphereDistort()
94 Layer_SphereDistort::set_param(const String & param, const ValueBase &value)
96 IMPORT_PLUS(center,sync());
97 IMPORT_PLUS(radius,sync());
99 IMPORT_AS(percent,"amount");
104 if(dynamic_param_list().count("percent"))
106 connect_dynamic_param("amount",dynamic_param_list().find("percent")->second);
107 disconnect_dynamic_param("percent");
108 synfig::warning("Layer_SphereDistort::::set_param(): Updated valuenode connection to use the new \"amount\" parameter.");
111 synfig::warning("Layer_SphereDistort::::set_param(): The parameter \"segment_list\" is deprecated. Use \"bline\" instead.");
118 Layer_SphereDistort::get_param(const String ¶m)const
123 EXPORT_AS(percent,"amount");
133 Layer_SphereDistort::sync()
138 Layer_SphereDistort::get_param_vocab()const
142 ret.push_back(ParamDesc("center")
143 .set_local_name(_("Position"))
146 ret.push_back(ParamDesc("radius")
147 .set_local_name(_("Radius"))
148 .set_origin("center")
152 ret.push_back(ParamDesc("amount")
153 .set_local_name(_("Amount"))
154 .set_is_distance(false)
157 ret.push_back(ParamDesc("clip")
158 .set_local_name(_("Clip"))
161 ret.push_back(ParamDesc("type")
162 .set_local_name(_("Distort Type"))
163 .set_description(_("The direction of the distortion"))
165 .add_enum_value(TYPE_NORMAL,"normal",_("Spherize"))
166 .add_enum_value(TYPE_DISTH,"honly",_("Vertical Bar"))
167 .add_enum_value(TYPE_DISTV,"vonly",_("Horizontal Bar"))
174 Spherical Distortion: maps an image onto a ellipsoid of some sort
176 so the image coordinate (i.e. distance away from the center)
177 will determine how things get mapped
179 so with the radius and position the mapping would go as follows
181 r = (pos - center) / radius clamped to [-1,1]
183 if it's outside of that range then it's not distorted
184 but if it's inside of that range then it goes as follows
186 angle = r * pi/2 (-pi/2,pi/2)
188 newr = cos(angle)*radius
190 the inverse of this is (which is actually what we'd be transforming it from
195 inline float spherify(float f)
197 if(f > -1 && f < 1 && f!=0)
198 return sinf(f*(PI/2));
202 inline float unspherify(float f)
204 if(f > -1 && f < 1 && f!=0)
205 return asin(f)/(PI/2);
209 Point sphtrans(const Point &p, const Point ¢er, const float &radius,
210 const Real &percent, int type, bool& clipped)
212 const Vector v = (p - center) / radius;
215 const float t = percent;
219 if(type == TYPE_NORMAL)
221 const float m = v.mag();
224 if(m <= -1 || m >= 1)
234 lerp = (t*unspherify(m) + (1-t)*m);
237 lerp = ((1+t)*m - t*spherify(m));
240 const float d = lerp*radius;
241 newp = center + v*(d/m);
244 else if(type == TYPE_DISTH)
247 if(v[0] <= -1 || v[0] >= 1)
257 lerp = (t*unspherify(v[0]) + (1-t)*v[0]);
260 lerp = ((1+t)*v[0] - t*spherify(v[0]));
263 newp[0] = center[0] + lerp*radius;
266 else if(type == TYPE_DISTV)
269 if(v[1] <= -1 || v[1] >= 1)
280 lerp = (t*unspherify(v[1]) + (1-t)*v[1]);
283 lerp = ((1+t)*v[1] - t*spherify(v[1]));
286 newp[1] = center[1] + lerp*radius;
292 inline Point sphtrans(const Point &p, const Point ¢er, const Real &radius,
293 const Real &percent, int type)
296 return sphtrans(p, center, radius, percent, type, tmp);
299 synfig::Layer::Handle
300 Layer_SphereDistort::hit_check(synfig::Context context, const synfig::Point &pos)const
303 Point point(sphtrans(pos,center,radius,percent,type,clipped));
306 return context.hit_check(point);
310 Layer_SphereDistort::get_color(Context context, const Point &pos)const
313 Point point(sphtrans(pos,center,radius,percent,type,clipped));
315 return Color::alpha();
316 return context.get_color(point);
320 bool Layer_SphereDistort::accelerated_render(Context context,Surface *surface,int quality, const RendDesc &renddesc, ProgressCallback *cb)const
322 /* Things to consider:
323 1) Block expansion for distortion (ouch... quality level??)
324 2) Bounding box clipping
325 3) Super sampling for better visual quality (based on the quality level?)
326 4) Interpolation type for sampling (based on quality level?)
328 //things to defer until after
329 super sampling, non-linear interpolation
332 //bounding box reject
336 sphr.set_point(center[0]-radius,center[1]-radius);
337 sphr.expand(center[0]+radius,center[1]+radius);
339 //get the bounding box of the transform
342 //and the bounding box of the rendering
343 windr.set_point(renddesc.get_tl()[0],renddesc.get_tl()[1]);
344 windr.expand(renddesc.get_br()[0],renddesc.get_br()[1]);
346 //test bounding boxes for collision
347 if( (type == TYPE_NORMAL && !intersect(sphr,windr)) ||
348 (type == TYPE_DISTH && (sphr.minx >= windr.maxx || windr.minx >= sphr.maxx)) ||
349 (type == TYPE_DISTV && (sphr.miny >= windr.maxy || windr.miny >= sphr.maxy)) )
351 //synfig::warning("Spherize: Bounding box reject");
354 surface->set_wh(renddesc.get_w(), renddesc.get_h());
359 return context.accelerated_render(surface,quality,renddesc,cb);
362 //synfig::warning("Spherize: Bounding box accept");
365 //Ok, so we overlap some... now expand the window for rendering
366 RendDesc r = renddesc;
368 Real pw = renddesc.get_pw(),ph = renddesc.get_ph();
370 int nl=0,nt=0,nr=0,nb=0, nw=0,nh=0;
371 Point tl = renddesc.get_tl(), br = renddesc.get_br();
374 //must enlarge window by pixel coordinates so go!
376 //need to figure out closest and farthest point and distort THOSE
378 Point origin[4] = {tl,tl,br,br};
379 Vector v[4] = {Vector(0,br[1]-tl[1]),
380 Vector(br[0]-tl[0],0),
381 Vector(0,tl[1]-br[1]),
382 Vector(tl[0]-br[0],0)};
388 //expandr.set_point(tl[0],tl[1]);
389 //expandr.expand(br[0],br[1]);
391 //synfig::warning("Spherize: Loop through lines and stuff");
392 for(int i=0; i<4; ++i)
394 //synfig::warning("Spherize: %d", i);
395 Vector p_o = center-origin[i];
397 //project onto left line
398 t = (p_o*v[i])/v[i].mag_squared();
401 if(t < 0) t = 0; if(t > 1) t = 1;
403 close = origin[i] + v[i]*t;
405 //now get transforms and expand the rectangle to accommodate
406 Point p = sphtrans(close,center,radius,percent,type);
407 expandr.expand(p[0],p[1]);
408 p = sphtrans(origin[i],center,radius,percent,type);
409 expandr.expand(p[0],p[1]);
410 p = sphtrans(origin[i]+v[i],center,radius,percent,type);
411 expandr.expand(p[0],p[1]);
414 /*synfig::warning("Spherize: Bounding box (%f,%f)-(%f,%f)",
415 expandr.minx,expandr.miny,expandr.maxx,expandr.maxy);*/
417 //now that we have the bounding rectangle of ALL the pixels (should be...)
418 //order it so that it's in the same orientation as the tl,br pair
420 //synfig::warning("Spherize: Organize like tl,br");
421 Point ntl(0,0),nbr(0,0);
426 ntl[0] = expandr.minx;
427 nbr[0] = expandr.maxx;
431 ntl[0] = expandr.maxx;
432 nbr[0] = expandr.minx;
438 ntl[1] = expandr.miny;
439 nbr[1] = expandr.maxy;
443 ntl[1] = expandr.maxy;
444 nbr[1] = expandr.miny;
447 //now expand the window as needed
448 Vector temp = ntl-tl;
451 nl = (int)(temp[0]/pw)-1;
452 nt = (int)(temp[1]/ph)-1;
455 nr = (int)(temp[0]/pw)+1;
456 nb = (int)(temp[1]/ph)+1;
458 nw = renddesc.get_w() + nr - nl;
459 nh = renddesc.get_h() + nb - nt;
461 //synfig::warning("Spherize: Setting subwindow (%d,%d) (%d,%d) (%d,%d)",nl,nt,nr,nb,nw,nh);
462 r.set_subwindow(nl,nt,nw,nh);
465 nw = r.get_w(), nh = r.get_h();
469 //synfig::warning("Spherize: render background");
470 if(!context.accelerated_render(&background,quality,r,cb))
472 synfig::warning("SphereDistort: Layer below failed");
476 //now distort and check to make sure we aren't overshooting our bounds here
477 int w = renddesc.get_w(), h = renddesc.get_h();
478 surface->set_wh(w,h);
480 Point sample = tl, sub = tl, trans(0,0);
483 Real invpw = 1/pw, invph = 1/ph;
484 Surface::pen p = surface->begin();
486 Point rtl = r.get_tl();
488 //synfig::warning("Spherize: About to transform");
490 for(y = 0; y < h; ++y, sample[1] += ph, p.inc_y())
493 for(x = 0; x < w; ++x, sub[0] += pw, p.inc_x())
496 trans=sphtrans(sub,center,radius,percent,type,clipped);
499 p.put_value(Color::alpha());
503 xs = (trans[0]-rtl[0])*invpw;
504 ys = (trans[1]-rtl[1])*invph;
506 if(!(xs >= 0 && xs < nw && ys >= 0 && ys < nh))
508 //synfig::warning("Spherize: we failed to account for %f,%f",xs,ys);
509 p.put_value(context.get_color(trans));//Color::alpha());
513 //sample at that pixel location based on the quality
514 if(quality <= 4) // cubic
515 p.put_value(background.cubic_sample(xs,ys));
516 else if(quality <= 5) // cosine
517 p.put_value(background.cosine_sample(xs,ys));
518 else if(quality <= 6) // linear
519 p.put_value(background.linear_sample(xs,ys));
521 p.put_value(background[round_to_int(ys)][round_to_int(xs)]);
530 class synfig::Spherize_Trans : public synfig::Transform
532 etl::handle<const Layer_SphereDistort> layer;
534 Spherize_Trans(const Layer_SphereDistort* x):Transform(x->get_guid()),layer(x) { }
536 synfig::Vector perform(const synfig::Vector& x)const
538 return sphtrans(x,layer->center,layer->radius,-layer->percent,layer->type);
541 synfig::Vector unperform(const synfig::Vector& x)const
543 return sphtrans(x,layer->center,layer->radius,layer->percent,layer->type);
547 etl::handle<Transform>
548 Layer_SphereDistort::get_transform()const
550 return new Spherize_Trans(this);
554 Layer_SphereDistort::get_bounding_rect()const
556 Rect bounds(Rect::full_plane());
564 bounds=Rect(center[0]+radius, center[1]+radius,
565 center[0]-radius, center[1]-radius);
568 bounds = Rect::vertical_strip(center[0]-radius, center[0]+radius);
571 bounds = Rect::horizontal_strip(center[1]-radius, center[1]+radius);