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