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"))
146 .set_description(_("Where the sphere distortion is centered"))
149 ret.push_back(ParamDesc("radius")
150 .set_local_name(_("Radius"))
151 .set_origin("center")
153 .set_description(_("The size of the sphere distortion"))
156 ret.push_back(ParamDesc("amount")
157 .set_local_name(_("Amount"))
158 .set_is_distance(false)
159 .set_description(_("The distortion intensity (negative values inverts effect)"))
162 ret.push_back(ParamDesc("clip")
163 .set_local_name(_("Clip"))
164 .set_description(_("When cheked, the area outside the Radius are not distorted"))
167 ret.push_back(ParamDesc("type")
168 .set_local_name(_("Distort Type"))
169 .set_description(_("The direction of the distortion"))
171 .add_enum_value(TYPE_NORMAL,"normal",_("Spherize"))
172 .add_enum_value(TYPE_DISTH,"honly",_("Vertical Bar"))
173 .add_enum_value(TYPE_DISTV,"vonly",_("Horizontal Bar"))
180 Spherical Distortion: maps an image onto a ellipsoid of some sort
182 so the image coordinate (i.e. distance away from the center)
183 will determine how things get mapped
185 so with the radius and position the mapping would go as follows
187 r = (pos - center) / radius clamped to [-1,1]
189 if it's outside of that range then it's not distorted
190 but if it's inside of that range then it goes as follows
192 angle = r * pi/2 (-pi/2,pi/2)
194 newr = cos(angle)*radius
196 the inverse of this is (which is actually what we'd be transforming it from
201 inline float spherify(float f)
203 if(f > -1 && f < 1 && f!=0)
204 return sinf(f*(PI/2));
208 inline float unspherify(float f)
210 if(f > -1 && f < 1 && f!=0)
211 return asin(f)/(PI/2);
215 Point sphtrans(const Point &p, const Point ¢er, const float &radius,
216 const Real &percent, int type, bool& clipped)
218 const Vector v = (p - center) / radius;
221 const float t = percent;
225 if(type == TYPE_NORMAL)
227 const float m = v.mag();
230 if(m <= -1 || m >= 1)
240 lerp = (t*unspherify(m) + (1-t)*m);
243 lerp = ((1+t)*m - t*spherify(m));
246 const float d = lerp*radius;
247 newp = center + v*(d/m);
250 else if(type == TYPE_DISTH)
253 if(v[0] <= -1 || v[0] >= 1)
263 lerp = (t*unspherify(v[0]) + (1-t)*v[0]);
266 lerp = ((1+t)*v[0] - t*spherify(v[0]));
269 newp[0] = center[0] + lerp*radius;
272 else if(type == TYPE_DISTV)
275 if(v[1] <= -1 || v[1] >= 1)
286 lerp = (t*unspherify(v[1]) + (1-t)*v[1]);
289 lerp = ((1+t)*v[1] - t*spherify(v[1]));
292 newp[1] = center[1] + lerp*radius;
298 inline Point sphtrans(const Point &p, const Point ¢er, const Real &radius,
299 const Real &percent, int type)
302 return sphtrans(p, center, radius, percent, type, tmp);
305 synfig::Layer::Handle
306 Layer_SphereDistort::hit_check(synfig::Context context, const synfig::Point &pos)const
309 Point point(sphtrans(pos,center,radius,percent,type,clipped));
312 return context.hit_check(point);
316 Layer_SphereDistort::get_color(Context context, const Point &pos)const
319 Point point(sphtrans(pos,center,radius,percent,type,clipped));
321 return Color::alpha();
322 return context.get_color(point);
326 bool Layer_SphereDistort::accelerated_render(Context context,Surface *surface,int quality, const RendDesc &renddesc, ProgressCallback *cb)const
328 /* Things to consider:
329 1) Block expansion for distortion (ouch... quality level??)
330 2) Bounding box clipping
331 3) Super sampling for better visual quality (based on the quality level?)
332 4) Interpolation type for sampling (based on quality level?)
334 //things to defer until after
335 super sampling, non-linear interpolation
338 //bounding box reject
342 sphr.set_point(center[0]-radius,center[1]-radius);
343 sphr.expand(center[0]+radius,center[1]+radius);
345 //get the bounding box of the transform
348 //and the bounding box of the rendering
349 windr.set_point(renddesc.get_tl()[0],renddesc.get_tl()[1]);
350 windr.expand(renddesc.get_br()[0],renddesc.get_br()[1]);
352 //test bounding boxes for collision
353 if( (type == TYPE_NORMAL && !intersect(sphr,windr)) ||
354 (type == TYPE_DISTH && (sphr.minx >= windr.maxx || windr.minx >= sphr.maxx)) ||
355 (type == TYPE_DISTV && (sphr.miny >= windr.maxy || windr.miny >= sphr.maxy)) )
357 //synfig::warning("Spherize: Bounding box reject");
360 surface->set_wh(renddesc.get_w(), renddesc.get_h());
365 return context.accelerated_render(surface,quality,renddesc,cb);
368 //synfig::warning("Spherize: Bounding box accept");
371 //Ok, so we overlap some... now expand the window for rendering
372 RendDesc r = renddesc;
374 Real pw = renddesc.get_pw(),ph = renddesc.get_ph();
376 int nl=0,nt=0,nr=0,nb=0, nw=0,nh=0;
377 Point tl = renddesc.get_tl(), br = renddesc.get_br();
380 //must enlarge window by pixel coordinates so go!
382 //need to figure out closest and farthest point and distort THOSE
384 Point origin[4] = {tl,tl,br,br};
385 Vector v[4] = {Vector(0,br[1]-tl[1]),
386 Vector(br[0]-tl[0],0),
387 Vector(0,tl[1]-br[1]),
388 Vector(tl[0]-br[0],0)};
394 //expandr.set_point(tl[0],tl[1]);
395 //expandr.expand(br[0],br[1]);
397 //synfig::warning("Spherize: Loop through lines and stuff");
398 for(int i=0; i<4; ++i)
400 //synfig::warning("Spherize: %d", i);
401 Vector p_o = center-origin[i];
403 //project onto left line
404 t = (p_o*v[i])/v[i].mag_squared();
407 if(t < 0) t = 0; if(t > 1) t = 1;
409 close = origin[i] + v[i]*t;
411 //now get transforms and expand the rectangle to accommodate
412 Point p = sphtrans(close,center,radius,percent,type);
413 expandr.expand(p[0],p[1]);
414 p = sphtrans(origin[i],center,radius,percent,type);
415 expandr.expand(p[0],p[1]);
416 p = sphtrans(origin[i]+v[i],center,radius,percent,type);
417 expandr.expand(p[0],p[1]);
420 /*synfig::warning("Spherize: Bounding box (%f,%f)-(%f,%f)",
421 expandr.minx,expandr.miny,expandr.maxx,expandr.maxy);*/
423 //now that we have the bounding rectangle of ALL the pixels (should be...)
424 //order it so that it's in the same orientation as the tl,br pair
426 //synfig::warning("Spherize: Organize like tl,br");
427 Point ntl(0,0),nbr(0,0);
432 ntl[0] = expandr.minx;
433 nbr[0] = expandr.maxx;
437 ntl[0] = expandr.maxx;
438 nbr[0] = expandr.minx;
444 ntl[1] = expandr.miny;
445 nbr[1] = expandr.maxy;
449 ntl[1] = expandr.maxy;
450 nbr[1] = expandr.miny;
453 //now expand the window as needed
454 Vector temp = ntl-tl;
457 nl = (int)(temp[0]/pw)-1;
458 nt = (int)(temp[1]/ph)-1;
461 nr = (int)(temp[0]/pw)+1;
462 nb = (int)(temp[1]/ph)+1;
464 nw = renddesc.get_w() + nr - nl;
465 nh = renddesc.get_h() + nb - nt;
467 //synfig::warning("Spherize: Setting subwindow (%d,%d) (%d,%d) (%d,%d)",nl,nt,nr,nb,nw,nh);
468 r.set_subwindow(nl,nt,nw,nh);
471 nw = r.get_w(), nh = r.get_h();
475 //synfig::warning("Spherize: render background");
476 if(!context.accelerated_render(&background,quality,r,cb))
478 synfig::warning("SphereDistort: Layer below failed");
482 //now distort and check to make sure we aren't overshooting our bounds here
483 int w = renddesc.get_w(), h = renddesc.get_h();
484 surface->set_wh(w,h);
486 Point sample = tl, sub = tl, trans(0,0);
489 Real invpw = 1/pw, invph = 1/ph;
490 Surface::pen p = surface->begin();
492 Point rtl = r.get_tl();
494 //synfig::warning("Spherize: About to transform");
496 for(y = 0; y < h; ++y, sample[1] += ph, p.inc_y())
499 for(x = 0; x < w; ++x, sub[0] += pw, p.inc_x())
502 trans=sphtrans(sub,center,radius,percent,type,clipped);
505 p.put_value(Color::alpha());
509 xs = (trans[0]-rtl[0])*invpw;
510 ys = (trans[1]-rtl[1])*invph;
512 if(!(xs >= 0 && xs < nw && ys >= 0 && ys < nh))
514 //synfig::warning("Spherize: we failed to account for %f,%f",xs,ys);
515 p.put_value(context.get_color(trans));//Color::alpha());
519 //sample at that pixel location based on the quality
520 if(quality <= 4) // cubic
521 p.put_value(background.cubic_sample(xs,ys));
522 else if(quality <= 5) // cosine
523 p.put_value(background.cosine_sample(xs,ys));
524 else if(quality <= 6) // linear
525 p.put_value(background.linear_sample(xs,ys));
527 p.put_value(background[round_to_int(ys)][round_to_int(xs)]);
536 class synfig::Spherize_Trans : public synfig::Transform
538 etl::handle<const Layer_SphereDistort> layer;
540 Spherize_Trans(const Layer_SphereDistort* x):Transform(x->get_guid()),layer(x) { }
542 synfig::Vector perform(const synfig::Vector& x)const
544 return sphtrans(x,layer->center,layer->radius,-layer->percent,layer->type);
547 synfig::Vector unperform(const synfig::Vector& x)const
549 return sphtrans(x,layer->center,layer->radius,layer->percent,layer->type);
553 etl::handle<Transform>
554 Layer_SphereDistort::get_transform()const
556 return new Spherize_Trans(this);
560 Layer_SphereDistort::get_bounding_rect()const
562 Rect bounds(Rect::full_plane());
570 bounds=Rect(center[0]+radius, center[1]+radius,
571 center[0]-radius, center[1]-radius);
574 bounds = Rect::vertical_strip(center[0]-radius, center[0]+radius);
577 bounds = Rect::horizontal_strip(center[1]-radius, center[1]+radius);