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,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()
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");
355 surface->set_wh(renddesc.get_w(), renddesc.get_h());
360 return context.accelerated_render(surface,quality,renddesc,cb);
363 //synfig::warning("Spherize: Bounding box accept");
366 //Ok, so we overlap some... now expand the window for rendering
367 RendDesc r = renddesc;
369 Real pw = renddesc.get_pw(),ph = renddesc.get_ph();
371 int nl=0,nt=0,nr=0,nb=0, nw=0,nh=0;
372 Point tl = renddesc.get_tl(), br = renddesc.get_br();
375 //must enlarge window by pixel coordinates so go!
377 //need to figure out closest and farthest point and distort THOSE
379 Point origin[4] = {tl,tl,br,br};
380 Vector v[4] = {Vector(0,br[1]-tl[1]),
381 Vector(br[0]-tl[0],0),
382 Vector(0,tl[1]-br[1]),
383 Vector(tl[0]-br[0],0)};
389 //expandr.set_point(tl[0],tl[1]);
390 //expandr.expand(br[0],br[1]);
392 //synfig::warning("Spherize: Loop through lines and stuff");
393 for(int i=0; i<4; ++i)
395 //synfig::warning("Spherize: %d", i);
396 Vector p_o = center-origin[i];
398 //project onto left line
399 t = (p_o*v[i])/v[i].mag_squared();
402 if(t < 0) t = 0; if(t > 1) t = 1;
404 close = origin[i] + v[i]*t;
406 //now get transforms and expand the rectangle to accommodate
407 Point p = sphtrans(close,center,radius,percent,type);
408 expandr.expand(p[0],p[1]);
409 p = sphtrans(origin[i],center,radius,percent,type);
410 expandr.expand(p[0],p[1]);
411 p = sphtrans(origin[i]+v[i],center,radius,percent,type);
412 expandr.expand(p[0],p[1]);
415 /*synfig::warning("Spherize: Bounding box (%f,%f)-(%f,%f)",
416 expandr.minx,expandr.miny,expandr.maxx,expandr.maxy);*/
418 //now that we have the bounding rectangle of ALL the pixels (should be...)
419 //order it so that it's in the same orientation as the tl,br pair
421 //synfig::warning("Spherize: Organize like tl,br");
422 Point ntl(0,0),nbr(0,0);
427 ntl[0] = expandr.minx;
428 nbr[0] = expandr.maxx;
432 ntl[0] = expandr.maxx;
433 nbr[0] = expandr.minx;
439 ntl[1] = expandr.miny;
440 nbr[1] = expandr.maxy;
444 ntl[1] = expandr.maxy;
445 nbr[1] = expandr.miny;
448 //now expand the window as needed
449 Vector temp = ntl-tl;
452 nl = (int)(temp[0]/pw)-1;
453 nt = (int)(temp[1]/ph)-1;
456 nr = (int)(temp[0]/pw)+1;
457 nb = (int)(temp[1]/ph)+1;
459 nw = renddesc.get_w() + nr - nl;
460 nh = renddesc.get_h() + nb - nt;
462 //synfig::warning("Spherize: Setting subwindow (%d,%d) (%d,%d) (%d,%d)",nl,nt,nr,nb,nw,nh);
463 r.set_subwindow(nl,nt,nw,nh);
466 nw = r.get_w(), nh = r.get_h();
470 //synfig::warning("Spherize: render background");
471 if(!context.accelerated_render(&background,quality,r,cb))
473 synfig::warning("SphereDistort: Layer below failed");
477 //now distort and check to make sure we aren't overshooting our bounds here
478 int w = renddesc.get_w(), h = renddesc.get_h();
479 surface->set_wh(w,h);
481 Point sample = tl, sub = tl, trans(0,0);
484 Real invpw = 1/pw, invph = 1/ph;
485 Surface::pen p = surface->begin();
487 Point rtl = r.get_tl();
489 //synfig::warning("Spherize: About to transform");
491 for(y = 0; y < h; ++y, sample[1] += ph, p.inc_y())
494 for(x = 0; x < w; ++x, sub[0] += pw, p.inc_x())
497 trans=sphtrans(sub,center,radius,percent,type,clipped);
500 p.put_value(Color::alpha());
504 xs = (trans[0]-rtl[0])*invpw;
505 ys = (trans[1]-rtl[1])*invph;
507 if(!(xs >= 0 && xs < nw && ys >= 0 && ys < nh))
509 //synfig::warning("Spherize: we failed to account for %f,%f",xs,ys);
510 p.put_value(context.get_color(trans));//Color::alpha());
514 //sample at that pixel location based on the quality
515 if(quality <= 4) // cubic
516 p.put_value(background.cubic_sample(xs,ys));
517 else if(quality <= 5) // cosine
518 p.put_value(background.cosine_sample(xs,ys));
519 else if(quality <= 6) // linear
520 p.put_value(background.linear_sample(xs,ys));
522 p.put_value(background[round_to_int(ys)][round_to_int(xs)]);
531 class synfig::Spherize_Trans : public synfig::Transform
533 etl::handle<const Layer_SphereDistort> layer;
535 Spherize_Trans(const Layer_SphereDistort* x):Transform(x->get_guid()),layer(x) { }
537 synfig::Vector perform(const synfig::Vector& x)const
539 return sphtrans(x,layer->center,layer->radius,-layer->percent,layer->type);
542 synfig::Vector unperform(const synfig::Vector& x)const
544 return sphtrans(x,layer->center,layer->radius,layer->percent,layer->type);
548 etl::handle<Transform>
549 Layer_SphereDistort::get_transform()const
551 return new Spherize_Trans(this);
555 Layer_SphereDistort::get_bounding_rect()const
557 Rect bounds(Rect::full_plane());
565 bounds=Rect(center[0]+radius, center[1]+radius,
566 center[0]-radius, center[1]-radius);
569 bounds = Rect::vertical_strip(center[0]-radius, center[0]+radius);
572 bounds = Rect::horizontal_strip(center[1]-radius, center[1]+radius);