1 /* === S Y N F I G ========================================================= */
2 /*! \file sphere_distort.cpp
3 ** \brief Sphere Distort File
8 ** Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
10 ** This package is free software; you can redistribute it and/or
11 ** modify it under the terms of the GNU General Public License as
12 ** published by the Free Software Foundation; either version 2 of
13 ** the License, or (at your option) any later version.
15 ** This package is distributed in the hope that it will be useful,
16 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 ** General Public License for more details.
21 /* ========================================================================= */
23 /* === H E A D E R S ======================================================= */
32 #include "sphere_distort.h"
33 #include <synfig/string.h>
34 #include <synfig/time.h>
35 #include <synfig/context.h>
36 #include <synfig/paramdesc.h>
37 #include <synfig/renddesc.h>
38 #include <synfig/surface.h>
39 #include <synfig/value.h>
40 #include <synfig/valuenode.h>
41 #include <synfig/transform.h>
43 #include <synfig/curve_helper.h>
47 /* === U S I N G =========================================================== */
51 using namespace synfig;
53 /* === M A C R O S ========================================================= */
56 const double PI = 3.14159265;
62 TYPE_DISTH = 1, //axe the horizontal axis
63 TYPE_DISTV = 2, //axe the vertical axis
67 /* === G L O B A L S ======================================================= */
69 SYNFIG_LAYER_INIT(Layer_SphereDistort);
70 SYNFIG_LAYER_SET_NAME(Layer_SphereDistort,"spherize");
71 SYNFIG_LAYER_SET_LOCAL_NAME(Layer_SphereDistort,_("Spherize"));
72 SYNFIG_LAYER_SET_CATEGORY(Layer_SphereDistort,_("Distortions"));
73 SYNFIG_LAYER_SET_VERSION(Layer_SphereDistort,"0.2");
74 SYNFIG_LAYER_SET_CVS_ID(Layer_SphereDistort,"$Id$");
76 /* === P R O C E D U R E S ================================================= */
78 /* === M E T H O D S ======================================================= */
80 /* === E N T R Y P O I N T ================================================= */
82 Layer_SphereDistort::Layer_SphereDistort()
83 :Layer_Composite(1.0,Color::BLEND_STRAIGHT),
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");
352 return context.accelerated_render(surface,quality,renddesc,cb);
355 //synfig::warning("Spherize: Bounding box accept");
358 //Ok, so we overlap some... now expand the window for rendering
359 RendDesc r = renddesc;
361 Real pw = renddesc.get_pw(),ph = renddesc.get_ph();
363 int nl=0,nt=0,nr=0,nb=0, nw=0,nh=0;
364 Point tl = renddesc.get_tl(), br = renddesc.get_br();
367 //must enlarge window by pixel coordinates so go!
369 //need to figure out closest and farthest point and distort THOSE
371 Point origin[4] = {tl,tl,br,br};
372 Vector v[4] = {Vector(0,br[1]-tl[1]),
373 Vector(br[0]-tl[0],0),
374 Vector(0,tl[1]-br[1]),
375 Vector(tl[0]-br[0],0)};
381 //expandr.set_point(tl[0],tl[1]);
382 //expandr.expand(br[0],br[1]);
384 //synfig::warning("Spherize: Loop through lines and stuff");
385 for(int i=0; i<4; ++i)
387 //synfig::warning("Spherize: %d", i);
388 Vector p_o = center-origin[i];
390 //project onto left line
391 t = (p_o*v[i])/v[i].mag_squared();
394 if(t < 0) t = 0; if(t > 1) t = 1;
396 close = origin[i] + v[i]*t;
398 //now get transforms and expand the rectangle to accomodate
399 Point p = sphtrans(close,center,radius,percent,type);
400 expandr.expand(p[0],p[1]);
401 p = sphtrans(origin[i],center,radius,percent,type);
402 expandr.expand(p[0],p[1]);
403 p = sphtrans(origin[i]+v[i],center,radius,percent,type);
404 expandr.expand(p[0],p[1]);
407 /*synfig::warning("Spherize: Bounding box (%f,%f)-(%f,%f)",
408 expandr.minx,expandr.miny,expandr.maxx,expandr.maxy);*/
410 //now that we have the bouding rectangle of ALL the pixels (should be...)
411 //order it so that it's in the same orientation as the tl,br pair
413 //synfig::warning("Spherize: Organize like tl,br");
414 Point ntl(0,0),nbr(0,0);
419 ntl[0] = expandr.minx;
420 nbr[0] = expandr.maxx;
424 ntl[0] = expandr.maxx;
425 nbr[0] = expandr.minx;
431 ntl[1] = expandr.miny;
432 nbr[1] = expandr.maxy;
436 ntl[1] = expandr.maxy;
437 nbr[1] = expandr.miny;
440 //now expand the window as needed
441 Vector temp = ntl-tl;
444 nl = (int)(temp[0]/pw)-1;
445 nt = (int)(temp[1]/ph)-1;
448 nr = (int)(temp[0]/pw)+1;
449 nb = (int)(temp[1]/ph)+1;
451 nw = renddesc.get_w() + nr - nl;
452 nh = renddesc.get_h() + nb - nt;
454 //synfig::warning("Spherize: Setting subwindow (%d,%d) (%d,%d) (%d,%d)",nl,nt,nr,nb,nw,nh);
455 r.set_subwindow(nl,nt,nw,nh);
458 nw = r.get_w(), nh = r.get_h();
462 //synfig::warning("Spherize: render background");
463 if(!context.accelerated_render(&background,quality,r,cb))
465 synfig::warning("SphereDistort: Layer below failed");
469 //now distort and check to make sure we aren't overshooting our bounds here
470 int w = renddesc.get_w(), h = renddesc.get_h();
471 surface->set_wh(w,h);
473 Point sample = tl, sub = tl, trans(0,0);
476 Real invpw = 1/pw, invph = 1/ph;
477 Surface::pen p = surface->begin();
479 Point rtl = r.get_tl();
481 //synfig::warning("Spherize: About to transform");
483 for(y = 0; y < h; ++y, sample[1] += ph, p.inc_y())
486 for(x = 0; x < w; ++x, sub[0] += pw, p.inc_x())
489 trans=sphtrans(sub,center,radius,percent,type,clipped);
492 p.put_value(Color::alpha());
496 xs = (trans[0]-rtl[0])*invpw;
497 ys = (trans[1]-rtl[1])*invph;
499 if(!(xs >= 0 && xs < nw && ys >= 0 && ys < nh))
501 //synfig::warning("Spherize: we failed to account for %f,%f",xs,ys);
502 p.put_value(context.get_color(trans));//Color::alpha());
506 //sample at that pixel location based on the quality
507 if(quality <= 4) //cubic
509 p.put_value(background.cubic_sample(xs,ys));
510 }else if(quality <= 5) //cosine
512 p.put_value(background.cosine_sample(xs,ys));
513 }else if(quality <= 6) //linear
515 p.put_value(background.linear_sample(xs,ys));
518 p.put_value(background[round_to_int(ys)][round_to_int(xs)]);
528 class synfig::Spherize_Trans : public synfig::Transform
530 etl::handle<const Layer_SphereDistort> layer;
532 Spherize_Trans(const Layer_SphereDistort* x):Transform(x->get_guid()),layer(x) { }
534 synfig::Vector perform(const synfig::Vector& x)const
536 return sphtrans(x,layer->center,layer->radius,-layer->percent,layer->type);
539 synfig::Vector unperform(const synfig::Vector& x)const
541 return sphtrans(x,layer->center,layer->radius,layer->percent,layer->type);
545 etl::handle<Transform>
546 Layer_SphereDistort::get_transform()const
548 return new Spherize_Trans(this);
552 Layer_SphereDistort::get_bounding_rect()const
554 Rect bounds(Rect::full_plane());