marked stable
[synfig.git] / synfig-core / tags / stable / src / modules / lyr_std / sphere_distort.cpp
1 /* === S I N F G =========================================================== */
2 /*!     \file sphere_distort.cpp
3 **      \brief Sphere Distort File
4 **
5 **      $Id: sphere_distort.cpp,v 1.2 2005/01/24 05:00:18 darco Exp $
6 **
7 **      \legal
8 **      Copyright (c) 2002 Robert B. Quattlebaum Jr.
9 **
10 **      This software and associated documentation
11 **      are CONFIDENTIAL and PROPRIETARY property of
12 **      the above-mentioned copyright holder.
13 **
14 **      You may not copy, print, publish, or in any
15 **      other way distribute this software without
16 **      a prior written agreement with
17 **      the copyright holder.
18 **      \endlegal
19 */
20 /* ========================================================================= */
21
22 /* === H E A D E R S ======================================================= */
23
24 #ifdef USING_PCH
25 #       include "pch.h"
26 #else
27 #ifdef HAVE_CONFIG_H
28 #       include <config.h>
29 #endif
30
31 #include "sphere_distort.h"
32 #include <sinfg/string.h>
33 #include <sinfg/time.h>
34 #include <sinfg/context.h>
35 #include <sinfg/paramdesc.h>
36 #include <sinfg/renddesc.h>
37 #include <sinfg/surface.h>
38 #include <sinfg/value.h>
39 #include <sinfg/valuenode.h>
40 #include <sinfg/transform.h>
41
42 #include <sinfg/curve_helper.h>
43
44 #endif
45
46 /* === U S I N G =========================================================== */
47
48 using namespace std;
49 using namespace etl;
50 using namespace sinfg;
51
52 /* === M A C R O S ========================================================= */
53
54 #ifndef PI
55 const double PI = 3.14159265;
56 #endif
57
58 enum
59 {
60         TYPE_NORMAL = 0,
61         TYPE_DISTH = 1, //axe the horizontal axis
62         TYPE_DISTV = 2, //axe the vertical axis
63         N_TYPES
64 };
65
66 /* === G L O B A L S ======================================================= */
67
68 SINFG_LAYER_INIT(Layer_SphereDistort);
69 SINFG_LAYER_SET_NAME(Layer_SphereDistort,"spherize");
70 SINFG_LAYER_SET_LOCAL_NAME(Layer_SphereDistort,_("Spherize"));
71 SINFG_LAYER_SET_CATEGORY(Layer_SphereDistort,_("Distortions"));
72 SINFG_LAYER_SET_VERSION(Layer_SphereDistort,"0.2");
73 SINFG_LAYER_SET_CVS_ID(Layer_SphereDistort,"$Id: sphere_distort.cpp,v 1.2 2005/01/24 05:00:18 darco Exp $");
74
75 /* === P R O C E D U R E S ================================================= */
76
77 /* === M E T H O D S ======================================================= */
78
79 /* === E N T R Y P O I N T ================================================= */
80
81 Layer_SphereDistort::Layer_SphereDistort()
82 :Layer_Composite(1.0,Color::BLEND_STRAIGHT),
83 center(0,0),
84 radius(1),
85 percent(1,1),
86 type(TYPE_NORMAL),
87 clip(false)
88 {
89 }
90
91         
92 bool
93 Layer_SphereDistort::set_param(const String & param, const ValueBase &value)
94 {
95         IMPORT_PLUS(center,sync());
96         IMPORT_PLUS(radius,sync());
97         IMPORT(type);
98         IMPORT_AS(percent,"amount");
99         IMPORT(clip);
100
101         if(param=="percent")
102         {
103                 if(dynamic_param_list().count("percent"))
104                 {
105                         connect_dynamic_param("amount",dynamic_param_list().find("percent")->second);
106                         disconnect_dynamic_param("percent");
107                         sinfg::warning("Layer_SphereDistort::::set_param(): Updated valuenode connection to use the new \"amount\" parameter.");
108                 }
109                 else
110                         sinfg::warning("Layer_SphereDistort::::set_param(): The parameter \"segment_list\" is deprecated. Use \"bline\" instead.");
111         }
112         
113         return false;   
114 }
115
116 ValueBase
117 Layer_SphereDistort::get_param(const String &param)const
118 {
119         EXPORT(center);
120         EXPORT(radius);
121         EXPORT(type);
122         EXPORT_AS(percent,"amount");
123         EXPORT(clip);
124         
125         EXPORT_NAME();
126         EXPORT_VERSION();
127                 
128         return ValueBase();
129 }
130
131 void
132 Layer_SphereDistort::sync()
133 {
134 }
135
136 Layer::Vocab
137 Layer_SphereDistort::get_param_vocab()const
138 {
139         Layer::Vocab ret;
140         
141         ret.push_back(ParamDesc("center")
142                 .set_local_name(_("Position"))
143         );
144         
145         ret.push_back(ParamDesc("radius")
146                 .set_local_name(_("Radius"))
147                 .set_origin("center")
148                 .set_is_distance()
149         );
150         
151         ret.push_back(ParamDesc("amount")
152                 .set_local_name(_("Amount"))
153                 .set_is_distance(false)
154         );
155
156         ret.push_back(ParamDesc("clip")
157                 .set_local_name(_("Clip"))
158         );
159         
160         ret.push_back(ParamDesc("type")
161                 .set_local_name(_("Distort Type"))
162                 .set_description(_("The direction of the distortion"))
163                 .set_hint("enum")
164                 .add_enum_value(TYPE_NORMAL,"normal",_("Spherize"))
165                 .add_enum_value(TYPE_DISTH,"honly",_("Vertical Bar"))
166                 .add_enum_value(TYPE_DISTV,"vonly",_("Horizontal Bar"))
167         );
168         
169         return ret;
170 }
171
172 /*
173         Spherical Distortion: maps an image onto a ellipsoid of some sort
174         
175         so the image coordinate (i.e. distance away from the center) 
176         will determine how things get mapped
177         
178         so with the radius and position the mapping would go as follows
179         
180         r = (pos - center) / radius     clamped to [-1,1]
181         
182         if it's outside of that range then it's not distorted
183         but if it's inside of that range then it goes as follows
184         
185         angle = r * pi/2 (-pi/2,pi/2)
186         
187         newr = cos(angle)*radius
188         
189         the inverse of this is (which is actually what we'd be transforming it from
190
191         
192 */
193
194 inline float spherify(float f)
195 {
196         if(f > -1 && f < 1 && f!=0)
197                 return sinf(f*(PI/2));
198         else return f;
199 }
200
201 inline float unspherify(float f)
202 {
203         if(f > -1 && f < 1 && f!=0)
204                 return asin(f)/(PI/2);
205         else return f;
206 }
207
208 Point sphtrans(const Point &p, const Point &center, const float &radius, 
209                                                                                         const Real &percent, int type, bool& clipped)
210 {
211         const Vector v = (p - center) / radius;
212         
213         Point newp = p;
214         const float t = percent;
215         
216         clipped=false;
217         
218         if(type == TYPE_NORMAL)
219         {
220                 const float m = v.mag();
221                 float lerp(0);
222                 
223                 if(m <= -1 || m >= 1)
224                 {
225                         clipped=true;
226                         return newp;
227                 }else
228                 if(m==0)
229                         return newp;
230                 else
231                 if(t > 0)
232                 {
233                         lerp = (t*unspherify(m) + (1-t)*m);
234                 }else if(t < 0)
235                 {                       
236                         lerp = ((1+t)*m - t*spherify(m));
237                 }else lerp = m;
238                 
239                 const float d = lerp*radius;
240                 newp = center + v*(d/m);
241         }
242         
243         else if(type == TYPE_DISTH)
244         {
245                 float lerp(0);
246                 if(v[0] <= -1 || v[0] >= 1)
247                 {
248                         clipped=true;
249                         return newp;
250                 }else
251                 if(v[0]==0)
252                         return newp;
253                 else
254                 if(t > 0)
255                 {
256                         lerp = (t*unspherify(v[0]) + (1-t)*v[0]);
257                 }else if(t < 0)
258                 {
259                         lerp = ((1+t)*v[0] - t*spherify(v[0]));
260                 }else lerp = v[0];
261                 
262                 newp[0] = center[0] + lerp*radius;
263         }
264         
265         else if(type == TYPE_DISTV)
266         {
267                 float lerp(0);
268                 if(v[1] <= -1 || v[1] >= 1)
269                 {
270                         clipped=true;
271                         return newp;
272                 }
273                 else
274                 if(v[1]==0)
275                         return newp;
276                 else
277                 if(t > 0)
278                 {
279                         lerp = (t*unspherify(v[1]) + (1-t)*v[1]);
280                 }else if(t < 0)
281                 {
282                         lerp = ((1+t)*v[1] - t*spherify(v[1]));
283                 }else lerp = v[1];
284                 
285                 newp[1] = center[1] + lerp*radius;
286         }
287         
288         return newp;
289 }
290
291 inline Point sphtrans(const Point &p, const Point &center, const Real &radius, 
292                                                                                         const Real &percent, int type)
293 {
294         bool tmp;
295         return sphtrans(p, center, radius, percent, type, tmp);
296 }
297
298 sinfg::Layer::Handle
299 Layer_SphereDistort::hit_check(sinfg::Context context, const sinfg::Point &pos)const
300 {
301         bool clipped;
302         Point point(sphtrans(pos,center,radius,percent,type,clipped));
303         if(clip && clipped)
304                 return 0;
305         return context.hit_check(point);
306 }
307
308 Color
309 Layer_SphereDistort::get_color(Context context, const Point &pos)const
310 {
311         bool clipped;
312         Point point(sphtrans(pos,center,radius,percent,type,clipped));
313         if(clip && clipped)
314                 return Color::alpha();
315         return context.get_color(point);
316 }
317
318 #if 1
319 bool Layer_SphereDistort::accelerated_render(Context context,Surface *surface,int quality, const RendDesc &renddesc, ProgressCallback *cb)const
320 {
321         /*      Things to consider:
322                 1) Block expansion for distortion (ouch... quality level??)
323                 2) Bounding box clipping
324                 3) Super sampling for better visual quality (based on the quality level?)
325                 4) Interpolation type for sampling (based on quality level?)
326         
327                 //things to defer until after
328                 super sampling, non-linear interpolation
329         */
330         
331         //bounding box reject
332         {
333                 Rect    sphr;
334                 
335                 sphr.set_point(center[0]-radius,center[1]-radius);
336                 sphr.expand(center[0]+radius,center[1]+radius);
337
338                 //get the bounding box of the transform         
339                 Rect    windr;
340                 
341                 //and the bounding box of the rendering         
342                 windr.set_point(renddesc.get_tl()[0],renddesc.get_tl()[1]);
343                 windr.expand(renddesc.get_br()[0],renddesc.get_br()[1]);
344                 
345                 //test bounding boxes for collision
346                 if( (type == TYPE_NORMAL && !intersect(sphr,windr)) ||
347                         (type == TYPE_DISTH && (sphr.minx >= windr.maxx || windr.minx >= sphr.maxx)) ||
348                         (type == TYPE_DISTV && (sphr.miny >= windr.maxy || windr.miny >= sphr.maxy)) )
349                 {
350                         //sinfg::warning("Spherize: Bounding box reject");
351                         return context.accelerated_render(surface,quality,renddesc,cb);
352                 }
353                 
354                 //sinfg::warning("Spherize: Bounding box accept");
355         }
356         
357         //Ok, so we overlap some... now expand the window for rendering
358         RendDesc r = renddesc;
359         Surface background;
360         Real pw = renddesc.get_pw(),ph = renddesc.get_ph();
361         
362         int nl=0,nt=0,nr=0,nb=0, nw=0,nh=0;
363         Point tl = renddesc.get_tl(), br = renddesc.get_br();
364         
365         {               
366                 //must enlarge window by pixel coordinates so go!
367                 
368                 //need to figure out closest and farthest point and distort THOSE
369                 
370                 Point origin[4] = {tl,tl,br,br};
371                 Vector v[4] = {Vector(0,br[1]-tl[1]),
372                                            Vector(br[0]-tl[0],0),
373                                            Vector(0,tl[1]-br[1]),
374                                            Vector(tl[0]-br[0],0)};
375                 
376                 Point close(0,0);
377                 Real t = 0;
378                 Rect    expandr(tl,br);
379                                            
380                 //expandr.set_point(tl[0],tl[1]);
381                 //expandr.expand(br[0],br[1]);
382
383                 //sinfg::warning("Spherize: Loop through lines and stuff");                                        
384                 for(int i=0; i<4; ++i)
385                 {
386                         //sinfg::warning("Spherize:     %d", i);
387                         Vector p_o = center-origin[i];
388                         
389                         //project onto left line
390                         t = (p_o*v[i])/v[i].mag_squared();
391                         
392                         //clamp
393                         if(t < 0) t = 0; if(t > 1) t = 1;
394                         
395                         close = origin[i] + v[i]*t;
396                         
397                         //now get transforms and expand the rectangle to accomodate
398                         Point p = sphtrans(close,center,radius,percent,type);
399                         expandr.expand(p[0],p[1]);
400                         p = sphtrans(origin[i],center,radius,percent,type);
401                         expandr.expand(p[0],p[1]);
402                         p = sphtrans(origin[i]+v[i],center,radius,percent,type);
403                         expandr.expand(p[0],p[1]);
404                 }
405                 
406                 /*sinfg::warning("Spherize: Bounding box (%f,%f)-(%f,%f)",
407                                                         expandr.minx,expandr.miny,expandr.maxx,expandr.maxy);*/
408                 
409                 //now that we have the bouding rectangle of ALL the pixels (should be...)
410                 //order it so that it's in the same orientation as the tl,br pair
411
412                 //sinfg::warning("Spherize: Organize like tl,br");
413                 Point ntl(0,0),nbr(0,0);
414                 
415                 //sort x
416                 if(tl[0] < br[0])
417                 { 
418                         ntl[0] = expandr.minx;
419                         nbr[0] = expandr.maxx;
420                 }
421                 else
422                 {
423                         ntl[0] = expandr.maxx;
424                         nbr[0] = expandr.minx;                  
425                 }
426                 
427                 //sort y
428                 if(tl[1] < br[1])
429                 { 
430                         ntl[1] = expandr.miny;
431                         nbr[1] = expandr.maxy;
432                 }
433                 else
434                 {
435                         ntl[1] = expandr.maxy;
436                         nbr[1] = expandr.miny;                  
437                 }
438                 
439                 //now expand the window as needed
440                 Vector temp = ntl-tl;
441                 
442                 //pixel offset
443                 nl = (int)(temp[0]/pw)-1;
444                 nt = (int)(temp[1]/ph)-1;
445                 
446                 temp = nbr - br;
447                 nr = (int)(temp[0]/pw)+1;
448                 nb = (int)(temp[1]/ph)+1;
449                 
450                 nw = renddesc.get_w() + nr - nl;
451                 nh = renddesc.get_h() + nb - nt;
452                 
453                 //sinfg::warning("Spherize: Setting subwindow (%d,%d) (%d,%d) (%d,%d)",nl,nt,nr,nb,nw,nh);              
454                 r.set_subwindow(nl,nt,nw,nh);
455                 
456                 /*r = renddesc;
457                 nw = r.get_w(), nh = r.get_h();
458                 nl = 0, nt = 0;*/
459         }
460         
461         //sinfg::warning("Spherize: render background");
462         if(!context.accelerated_render(&background,quality,r,cb))
463         {
464                 sinfg::warning("SphereDistort: Layer below failed");
465                 return false;
466         }
467         
468         //now distort and check to make sure we aren't overshooting our bounds here
469         int w = renddesc.get_w(), h = renddesc.get_h();
470         surface->set_wh(w,h);
471                 
472         Point sample = tl, sub = tl, trans(0,0);
473         float xs = 0,ys = 0;
474         int y=0,x=0;
475         Real invpw = 1/pw, invph = 1/ph;
476         Surface::pen    p = surface->begin();
477         
478         Point rtl = r.get_tl();
479         
480         //sinfg::warning("Spherize: About to transform");
481         
482         for(y = 0; y < h; ++y, sample[1] += ph, p.inc_y())
483         {
484                 sub = sample;
485                 for(x = 0; x < w; ++x, sub[0] += pw, p.inc_x())
486                 {
487                         bool clipped;
488                         trans=sphtrans(sub,center,radius,percent,type,clipped);
489                         if(clip && clipped)
490                         {
491                                 p.put_value(Color::alpha());
492                                 continue;
493                         }
494
495                         xs = (trans[0]-rtl[0])*invpw;
496                         ys = (trans[1]-rtl[1])*invph;
497                         
498                         if(!(xs >= 0 && xs < nw && ys >= 0 && ys < nh))
499                         {
500                                 //sinfg::warning("Spherize: we failed to account for %f,%f",xs,ys);
501                                 p.put_value(context.get_color(trans));//Color::alpha());
502                                 continue;
503                         }
504                         
505                         //sample at that pixel location based on the quality            
506                         if(quality <= 4) //cubic
507                         {
508                                 p.put_value(background.cubic_sample(xs,ys));
509                         }else if(quality <= 5) //cosine
510                         {
511                                 p.put_value(background.cosine_sample(xs,ys));
512                         }else if(quality <= 6) //linear
513                         {
514                                 p.put_value(background.linear_sample(xs,ys));
515                         }else //nearest
516                         {
517                                 p.put_value(background[round_to_int(ys)][round_to_int(xs)]);
518                         }
519                 }
520                 p.dec_x(w);
521         }
522         
523         return true;    
524 }
525 #endif
526
527 class  Spherize_Trans : public Transform
528 {
529         etl::handle<const Layer_SphereDistort> layer;
530 public:
531         Spherize_Trans(const Layer_SphereDistort* x):Transform(x->get_guid()),layer(x) { }
532         
533         sinfg::Vector perform(const sinfg::Vector& x)const
534         {
535                 return sphtrans(x,layer->center,layer->radius,-layer->percent,layer->type);
536         }
537         
538         sinfg::Vector unperform(const sinfg::Vector& x)const
539         {
540                 return sphtrans(x,layer->center,layer->radius,layer->percent,layer->type);
541         }
542 };
543
544 etl::handle<Transform>
545 Layer_SphereDistort::get_transform()const
546 {
547         return new Spherize_Trans(this);
548 }
549
550 Rect
551 Layer_SphereDistort::get_bounding_rect()const
552 {
553         Rect bounds(Rect::full_plane());
554         switch(type)
555         {
556                 case TYPE_NORMAL:
557                         bounds=Rect(
558                                 center[0]+(radius),
559                                 center[1]+(radius),
560                                 center[0]-(radius),
561                                 center[1]-(radius)
562                         );
563                         break;
564                 case TYPE_DISTH:
565                         break;
566                 case TYPE_DISTV:
567                         break;
568                 default:
569                         break;
570         }
571         
572         return bounds;
573 }
574