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()
90 Layer::Vocab voc(get_param_vocab());
91 Layer::fill_static(voc);
96 Layer_SphereDistort::set_param(const String & param, const ValueBase &value)
98 IMPORT_PLUS(center,sync());
99 IMPORT_PLUS(radius,sync());
101 IMPORT_AS(percent,"amount");
106 if(dynamic_param_list().count("percent"))
108 connect_dynamic_param("amount",dynamic_param_list().find("percent")->second);
109 disconnect_dynamic_param("percent");
110 synfig::warning("Layer_SphereDistort::::set_param(): Updated valuenode connection to use the new \"amount\" parameter.");
113 synfig::warning("Layer_SphereDistort::::set_param(): The parameter \"segment_list\" is deprecated. Use \"bline\" instead.");
120 Layer_SphereDistort::get_param(const String ¶m)const
125 EXPORT_AS(percent,"amount");
135 Layer_SphereDistort::sync()
140 Layer_SphereDistort::get_param_vocab()const
144 ret.push_back(ParamDesc("center")
145 .set_local_name(_("Position"))
148 ret.push_back(ParamDesc("radius")
149 .set_local_name(_("Radius"))
150 .set_origin("center")
154 ret.push_back(ParamDesc("amount")
155 .set_local_name(_("Amount"))
156 .set_is_distance(false)
159 ret.push_back(ParamDesc("clip")
160 .set_local_name(_("Clip"))
163 ret.push_back(ParamDesc("type")
164 .set_local_name(_("Distort Type"))
165 .set_description(_("The direction of the distortion"))
167 .add_enum_value(TYPE_NORMAL,"normal",_("Spherize"))
168 .add_enum_value(TYPE_DISTH,"honly",_("Vertical Bar"))
169 .add_enum_value(TYPE_DISTV,"vonly",_("Horizontal Bar"))
176 Spherical Distortion: maps an image onto a ellipsoid of some sort
178 so the image coordinate (i.e. distance away from the center)
179 will determine how things get mapped
181 so with the radius and position the mapping would go as follows
183 r = (pos - center) / radius clamped to [-1,1]
185 if it's outside of that range then it's not distorted
186 but if it's inside of that range then it goes as follows
188 angle = r * pi/2 (-pi/2,pi/2)
190 newr = cos(angle)*radius
192 the inverse of this is (which is actually what we'd be transforming it from
197 inline float spherify(float f)
199 if(f > -1 && f < 1 && f!=0)
200 return sinf(f*(PI/2));
204 inline float unspherify(float f)
206 if(f > -1 && f < 1 && f!=0)
207 return asin(f)/(PI/2);
211 Point sphtrans(const Point &p, const Point ¢er, const float &radius,
212 const Real &percent, int type, bool& clipped)
214 const Vector v = (p - center) / radius;
217 const float t = percent;
221 if(type == TYPE_NORMAL)
223 const float m = v.mag();
226 if(m <= -1 || m >= 1)
236 lerp = (t*unspherify(m) + (1-t)*m);
239 lerp = ((1+t)*m - t*spherify(m));
242 const float d = lerp*radius;
243 newp = center + v*(d/m);
246 else if(type == TYPE_DISTH)
249 if(v[0] <= -1 || v[0] >= 1)
259 lerp = (t*unspherify(v[0]) + (1-t)*v[0]);
262 lerp = ((1+t)*v[0] - t*spherify(v[0]));
265 newp[0] = center[0] + lerp*radius;
268 else if(type == TYPE_DISTV)
271 if(v[1] <= -1 || v[1] >= 1)
282 lerp = (t*unspherify(v[1]) + (1-t)*v[1]);
285 lerp = ((1+t)*v[1] - t*spherify(v[1]));
288 newp[1] = center[1] + lerp*radius;
294 inline Point sphtrans(const Point &p, const Point ¢er, const Real &radius,
295 const Real &percent, int type)
298 return sphtrans(p, center, radius, percent, type, tmp);
301 synfig::Layer::Handle
302 Layer_SphereDistort::hit_check(synfig::Context context, const synfig::Point &pos)const
305 Point point(sphtrans(pos,center,radius,percent,type,clipped));
308 return context.hit_check(point);
312 Layer_SphereDistort::get_color(Context context, const Point &pos)const
315 Point point(sphtrans(pos,center,radius,percent,type,clipped));
317 return Color::alpha();
318 return context.get_color(point);
322 bool Layer_SphereDistort::accelerated_render(Context context,Surface *surface,int quality, const RendDesc &renddesc, ProgressCallback *cb)const
324 /* Things to consider:
325 1) Block expansion for distortion (ouch... quality level??)
326 2) Bounding box clipping
327 3) Super sampling for better visual quality (based on the quality level?)
328 4) Interpolation type for sampling (based on quality level?)
330 //things to defer until after
331 super sampling, non-linear interpolation
334 //bounding box reject
338 sphr.set_point(center[0]-radius,center[1]-radius);
339 sphr.expand(center[0]+radius,center[1]+radius);
341 //get the bounding box of the transform
344 //and the bounding box of the rendering
345 windr.set_point(renddesc.get_tl()[0],renddesc.get_tl()[1]);
346 windr.expand(renddesc.get_br()[0],renddesc.get_br()[1]);
348 //test bounding boxes for collision
349 if( (type == TYPE_NORMAL && !intersect(sphr,windr)) ||
350 (type == TYPE_DISTH && (sphr.minx >= windr.maxx || windr.minx >= sphr.maxx)) ||
351 (type == TYPE_DISTV && (sphr.miny >= windr.maxy || windr.miny >= sphr.maxy)) )
353 //synfig::warning("Spherize: Bounding box reject");
356 surface->set_wh(renddesc.get_w(), renddesc.get_h());
361 return context.accelerated_render(surface,quality,renddesc,cb);
364 //synfig::warning("Spherize: Bounding box accept");
367 //Ok, so we overlap some... now expand the window for rendering
368 RendDesc r = renddesc;
370 Real pw = renddesc.get_pw(),ph = renddesc.get_ph();
372 int nl=0,nt=0,nr=0,nb=0, nw=0,nh=0;
373 Point tl = renddesc.get_tl(), br = renddesc.get_br();
376 //must enlarge window by pixel coordinates so go!
378 //need to figure out closest and farthest point and distort THOSE
380 Point origin[4] = {tl,tl,br,br};
381 Vector v[4] = {Vector(0,br[1]-tl[1]),
382 Vector(br[0]-tl[0],0),
383 Vector(0,tl[1]-br[1]),
384 Vector(tl[0]-br[0],0)};
390 //expandr.set_point(tl[0],tl[1]);
391 //expandr.expand(br[0],br[1]);
393 //synfig::warning("Spherize: Loop through lines and stuff");
394 for(int i=0; i<4; ++i)
396 //synfig::warning("Spherize: %d", i);
397 Vector p_o = center-origin[i];
399 //project onto left line
400 t = (p_o*v[i])/v[i].mag_squared();
403 if(t < 0) t = 0; if(t > 1) t = 1;
405 close = origin[i] + v[i]*t;
407 //now get transforms and expand the rectangle to accommodate
408 Point p = sphtrans(close,center,radius,percent,type);
409 expandr.expand(p[0],p[1]);
410 p = sphtrans(origin[i],center,radius,percent,type);
411 expandr.expand(p[0],p[1]);
412 p = sphtrans(origin[i]+v[i],center,radius,percent,type);
413 expandr.expand(p[0],p[1]);
416 /*synfig::warning("Spherize: Bounding box (%f,%f)-(%f,%f)",
417 expandr.minx,expandr.miny,expandr.maxx,expandr.maxy);*/
419 //now that we have the bounding rectangle of ALL the pixels (should be...)
420 //order it so that it's in the same orientation as the tl,br pair
422 //synfig::warning("Spherize: Organize like tl,br");
423 Point ntl(0,0),nbr(0,0);
428 ntl[0] = expandr.minx;
429 nbr[0] = expandr.maxx;
433 ntl[0] = expandr.maxx;
434 nbr[0] = expandr.minx;
440 ntl[1] = expandr.miny;
441 nbr[1] = expandr.maxy;
445 ntl[1] = expandr.maxy;
446 nbr[1] = expandr.miny;
449 //now expand the window as needed
450 Vector temp = ntl-tl;
453 nl = (int)(temp[0]/pw)-1;
454 nt = (int)(temp[1]/ph)-1;
457 nr = (int)(temp[0]/pw)+1;
458 nb = (int)(temp[1]/ph)+1;
460 nw = renddesc.get_w() + nr - nl;
461 nh = renddesc.get_h() + nb - nt;
463 //synfig::warning("Spherize: Setting subwindow (%d,%d) (%d,%d) (%d,%d)",nl,nt,nr,nb,nw,nh);
464 r.set_subwindow(nl,nt,nw,nh);
467 nw = r.get_w(), nh = r.get_h();
471 //synfig::warning("Spherize: render background");
472 if(!context.accelerated_render(&background,quality,r,cb))
474 synfig::warning("SphereDistort: Layer below failed");
478 //now distort and check to make sure we aren't overshooting our bounds here
479 int w = renddesc.get_w(), h = renddesc.get_h();
480 surface->set_wh(w,h);
482 Point sample = tl, sub = tl, trans(0,0);
485 Real invpw = 1/pw, invph = 1/ph;
486 Surface::pen p = surface->begin();
488 Point rtl = r.get_tl();
490 //synfig::warning("Spherize: About to transform");
492 for(y = 0; y < h; ++y, sample[1] += ph, p.inc_y())
495 for(x = 0; x < w; ++x, sub[0] += pw, p.inc_x())
498 trans=sphtrans(sub,center,radius,percent,type,clipped);
501 p.put_value(Color::alpha());
505 xs = (trans[0]-rtl[0])*invpw;
506 ys = (trans[1]-rtl[1])*invph;
508 if(!(xs >= 0 && xs < nw && ys >= 0 && ys < nh))
510 //synfig::warning("Spherize: we failed to account for %f,%f",xs,ys);
511 p.put_value(context.get_color(trans));//Color::alpha());
515 //sample at that pixel location based on the quality
516 if(quality <= 4) // cubic
517 p.put_value(background.cubic_sample(xs,ys));
518 else if(quality <= 5) // cosine
519 p.put_value(background.cosine_sample(xs,ys));
520 else if(quality <= 6) // linear
521 p.put_value(background.linear_sample(xs,ys));
523 p.put_value(background[round_to_int(ys)][round_to_int(xs)]);
532 class synfig::Spherize_Trans : public synfig::Transform
534 etl::handle<const Layer_SphereDistort> layer;
536 Spherize_Trans(const Layer_SphereDistort* x):Transform(x->get_guid()),layer(x) { }
538 synfig::Vector perform(const synfig::Vector& x)const
540 return sphtrans(x,layer->center,layer->radius,-layer->percent,layer->type);
543 synfig::Vector unperform(const synfig::Vector& x)const
545 return sphtrans(x,layer->center,layer->radius,layer->percent,layer->type);
549 etl::handle<Transform>
550 Layer_SphereDistort::get_transform()const
552 return new Spherize_Trans(this);
556 Layer_SphereDistort::get_bounding_rect()const
558 Rect bounds(Rect::full_plane());
566 bounds=Rect(center[0]+radius, center[1]+radius,
567 center[0]-radius, center[1]-radius);
570 bounds = Rect::vertical_strip(center[0]-radius, center[0]+radius);
573 bounds = Rect::horizontal_strip(center[1]-radius, center[1]+radius);