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
9 ** Copyright (c) 2007 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,_("Spherize"));
73 SYNFIG_LAYER_SET_CATEGORY(Layer_SphereDistort,_("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()
84 :Layer_Composite(1.0,Color::BLEND_STRAIGHT),
95 Layer_SphereDistort::set_param(const String & param, const ValueBase &value)
97 IMPORT_PLUS(center,sync());
98 IMPORT_PLUS(radius,sync());
100 IMPORT_AS(percent,"amount");
105 if(dynamic_param_list().count("percent"))
107 connect_dynamic_param("amount",dynamic_param_list().find("percent")->second);
108 disconnect_dynamic_param("percent");
109 synfig::warning("Layer_SphereDistort::::set_param(): Updated valuenode connection to use the new \"amount\" parameter.");
112 synfig::warning("Layer_SphereDistort::::set_param(): The parameter \"segment_list\" is deprecated. Use \"bline\" instead.");
119 Layer_SphereDistort::get_param(const String ¶m)const
124 EXPORT_AS(percent,"amount");
134 Layer_SphereDistort::sync()
139 Layer_SphereDistort::get_param_vocab()const
143 ret.push_back(ParamDesc("center")
144 .set_local_name(_("Position"))
147 ret.push_back(ParamDesc("radius")
148 .set_local_name(_("Radius"))
149 .set_origin("center")
153 ret.push_back(ParamDesc("amount")
154 .set_local_name(_("Amount"))
155 .set_is_distance(false)
158 ret.push_back(ParamDesc("clip")
159 .set_local_name(_("Clip"))
162 ret.push_back(ParamDesc("type")
163 .set_local_name(_("Distort Type"))
164 .set_description(_("The direction of the distortion"))
166 .add_enum_value(TYPE_NORMAL,"normal",_("Spherize"))
167 .add_enum_value(TYPE_DISTH,"honly",_("Vertical Bar"))
168 .add_enum_value(TYPE_DISTV,"vonly",_("Horizontal Bar"))
175 Spherical Distortion: maps an image onto a ellipsoid of some sort
177 so the image coordinate (i.e. distance away from the center)
178 will determine how things get mapped
180 so with the radius and position the mapping would go as follows
182 r = (pos - center) / radius clamped to [-1,1]
184 if it's outside of that range then it's not distorted
185 but if it's inside of that range then it goes as follows
187 angle = r * pi/2 (-pi/2,pi/2)
189 newr = cos(angle)*radius
191 the inverse of this is (which is actually what we'd be transforming it from
196 inline float spherify(float f)
198 if(f > -1 && f < 1 && f!=0)
199 return sinf(f*(PI/2));
203 inline float unspherify(float f)
205 if(f > -1 && f < 1 && f!=0)
206 return asin(f)/(PI/2);
210 Point sphtrans(const Point &p, const Point ¢er, const float &radius,
211 const Real &percent, int type, bool& clipped)
213 const Vector v = (p - center) / radius;
216 const float t = percent;
220 if(type == TYPE_NORMAL)
222 const float m = v.mag();
225 if(m <= -1 || m >= 1)
235 lerp = (t*unspherify(m) + (1-t)*m);
238 lerp = ((1+t)*m - t*spherify(m));
241 const float d = lerp*radius;
242 newp = center + v*(d/m);
245 else if(type == TYPE_DISTH)
248 if(v[0] <= -1 || v[0] >= 1)
258 lerp = (t*unspherify(v[0]) + (1-t)*v[0]);
261 lerp = ((1+t)*v[0] - t*spherify(v[0]));
264 newp[0] = center[0] + lerp*radius;
267 else if(type == TYPE_DISTV)
270 if(v[1] <= -1 || v[1] >= 1)
281 lerp = (t*unspherify(v[1]) + (1-t)*v[1]);
284 lerp = ((1+t)*v[1] - t*spherify(v[1]));
287 newp[1] = center[1] + lerp*radius;
293 inline Point sphtrans(const Point &p, const Point ¢er, const Real &radius,
294 const Real &percent, int type)
297 return sphtrans(p, center, radius, percent, type, tmp);
300 synfig::Layer::Handle
301 Layer_SphereDistort::hit_check(synfig::Context context, const synfig::Point &pos)const
304 Point point(sphtrans(pos,center,radius,percent,type,clipped));
307 return context.hit_check(point);
311 Layer_SphereDistort::get_color(Context context, const Point &pos)const
314 Point point(sphtrans(pos,center,radius,percent,type,clipped));
316 return Color::alpha();
317 return context.get_color(point);
321 bool Layer_SphereDistort::accelerated_render(Context context,Surface *surface,int quality, const RendDesc &renddesc, ProgressCallback *cb)const
323 /* Things to consider:
324 1) Block expansion for distortion (ouch... quality level??)
325 2) Bounding box clipping
326 3) Super sampling for better visual quality (based on the quality level?)
327 4) Interpolation type for sampling (based on quality level?)
329 //things to defer until after
330 super sampling, non-linear interpolation
333 //bounding box reject
337 sphr.set_point(center[0]-radius,center[1]-radius);
338 sphr.expand(center[0]+radius,center[1]+radius);
340 //get the bounding box of the transform
343 //and the bounding box of the rendering
344 windr.set_point(renddesc.get_tl()[0],renddesc.get_tl()[1]);
345 windr.expand(renddesc.get_br()[0],renddesc.get_br()[1]);
347 //test bounding boxes for collision
348 if( (type == TYPE_NORMAL && !intersect(sphr,windr)) ||
349 (type == TYPE_DISTH && (sphr.minx >= windr.maxx || windr.minx >= sphr.maxx)) ||
350 (type == TYPE_DISTV && (sphr.miny >= windr.maxy || windr.miny >= sphr.maxy)) )
352 //synfig::warning("Spherize: Bounding box reject");
353 return context.accelerated_render(surface,quality,renddesc,cb);
356 //synfig::warning("Spherize: Bounding box accept");
359 //Ok, so we overlap some... now expand the window for rendering
360 RendDesc r = renddesc;
362 Real pw = renddesc.get_pw(),ph = renddesc.get_ph();
364 int nl=0,nt=0,nr=0,nb=0, nw=0,nh=0;
365 Point tl = renddesc.get_tl(), br = renddesc.get_br();
368 //must enlarge window by pixel coordinates so go!
370 //need to figure out closest and farthest point and distort THOSE
372 Point origin[4] = {tl,tl,br,br};
373 Vector v[4] = {Vector(0,br[1]-tl[1]),
374 Vector(br[0]-tl[0],0),
375 Vector(0,tl[1]-br[1]),
376 Vector(tl[0]-br[0],0)};
382 //expandr.set_point(tl[0],tl[1]);
383 //expandr.expand(br[0],br[1]);
385 //synfig::warning("Spherize: Loop through lines and stuff");
386 for(int i=0; i<4; ++i)
388 //synfig::warning("Spherize: %d", i);
389 Vector p_o = center-origin[i];
391 //project onto left line
392 t = (p_o*v[i])/v[i].mag_squared();
395 if(t < 0) t = 0; if(t > 1) t = 1;
397 close = origin[i] + v[i]*t;
399 //now get transforms and expand the rectangle to accomodate
400 Point p = sphtrans(close,center,radius,percent,type);
401 expandr.expand(p[0],p[1]);
402 p = sphtrans(origin[i],center,radius,percent,type);
403 expandr.expand(p[0],p[1]);
404 p = sphtrans(origin[i]+v[i],center,radius,percent,type);
405 expandr.expand(p[0],p[1]);
408 /*synfig::warning("Spherize: Bounding box (%f,%f)-(%f,%f)",
409 expandr.minx,expandr.miny,expandr.maxx,expandr.maxy);*/
411 //now that we have the bouding rectangle of ALL the pixels (should be...)
412 //order it so that it's in the same orientation as the tl,br pair
414 //synfig::warning("Spherize: Organize like tl,br");
415 Point ntl(0,0),nbr(0,0);
420 ntl[0] = expandr.minx;
421 nbr[0] = expandr.maxx;
425 ntl[0] = expandr.maxx;
426 nbr[0] = expandr.minx;
432 ntl[1] = expandr.miny;
433 nbr[1] = expandr.maxy;
437 ntl[1] = expandr.maxy;
438 nbr[1] = expandr.miny;
441 //now expand the window as needed
442 Vector temp = ntl-tl;
445 nl = (int)(temp[0]/pw)-1;
446 nt = (int)(temp[1]/ph)-1;
449 nr = (int)(temp[0]/pw)+1;
450 nb = (int)(temp[1]/ph)+1;
452 nw = renddesc.get_w() + nr - nl;
453 nh = renddesc.get_h() + nb - nt;
455 //synfig::warning("Spherize: Setting subwindow (%d,%d) (%d,%d) (%d,%d)",nl,nt,nr,nb,nw,nh);
456 r.set_subwindow(nl,nt,nw,nh);
459 nw = r.get_w(), nh = r.get_h();
463 //synfig::warning("Spherize: render background");
464 if(!context.accelerated_render(&background,quality,r,cb))
466 synfig::warning("SphereDistort: Layer below failed");
470 //now distort and check to make sure we aren't overshooting our bounds here
471 int w = renddesc.get_w(), h = renddesc.get_h();
472 surface->set_wh(w,h);
474 Point sample = tl, sub = tl, trans(0,0);
477 Real invpw = 1/pw, invph = 1/ph;
478 Surface::pen p = surface->begin();
480 Point rtl = r.get_tl();
482 //synfig::warning("Spherize: About to transform");
484 for(y = 0; y < h; ++y, sample[1] += ph, p.inc_y())
487 for(x = 0; x < w; ++x, sub[0] += pw, p.inc_x())
490 trans=sphtrans(sub,center,radius,percent,type,clipped);
493 p.put_value(Color::alpha());
497 xs = (trans[0]-rtl[0])*invpw;
498 ys = (trans[1]-rtl[1])*invph;
500 if(!(xs >= 0 && xs < nw && ys >= 0 && ys < nh))
502 //synfig::warning("Spherize: we failed to account for %f,%f",xs,ys);
503 p.put_value(context.get_color(trans));//Color::alpha());
507 //sample at that pixel location based on the quality
508 if(quality <= 4) // cubic
509 p.put_value(background.cubic_sample(xs,ys));
510 else if(quality <= 5) // cosine
511 p.put_value(background.cosine_sample(xs,ys));
512 else if(quality <= 6) // linear
513 p.put_value(background.linear_sample(xs,ys));
515 p.put_value(background[round_to_int(ys)][round_to_int(xs)]);
524 class synfig::Spherize_Trans : public synfig::Transform
526 etl::handle<const Layer_SphereDistort> layer;
528 Spherize_Trans(const Layer_SphereDistort* x):Transform(x->get_guid()),layer(x) { }
530 synfig::Vector perform(const synfig::Vector& x)const
532 return sphtrans(x,layer->center,layer->radius,-layer->percent,layer->type);
535 synfig::Vector unperform(const synfig::Vector& x)const
537 return sphtrans(x,layer->center,layer->radius,layer->percent,layer->type);
541 etl::handle<Transform>
542 Layer_SphereDistort::get_transform()const
544 return new Spherize_Trans(this);
548 Layer_SphereDistort::get_bounding_rect()const
550 Rect bounds(Rect::full_plane());
554 bounds=Rect(center[0]+radius, center[1]+radius,
555 center[0]-radius, center[1]-radius);
558 bounds = Rect::vertical_strip(center[0]-radius, center[0]+radius);
561 bounds = Rect::horizontal_strip(center[1]-radius, center[1]+radius);