Set the description of some parameters
[synfig.git] / synfig-core / 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, 2008 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 :center(0,0),
85 radius(1),
86 percent(1.0),
87 type(TYPE_NORMAL),
88 clip(false)
89 {
90         Layer::Vocab voc(get_param_vocab());
91         Layer::fill_static(voc);
92 }
93
94
95 bool
96 Layer_SphereDistort::set_param(const String & param, const ValueBase &value)
97 {
98         IMPORT_PLUS(center,sync());
99         IMPORT_PLUS(radius,sync());
100         IMPORT(type);
101         IMPORT_AS(percent,"amount");
102         IMPORT(clip);
103
104         if(param=="percent")
105         {
106                 if(dynamic_param_list().count("percent"))
107                 {
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.");
111                 }
112                 else
113                         synfig::warning("Layer_SphereDistort::::set_param(): The parameter \"segment_list\" is deprecated. Use \"bline\" instead.");
114         }
115
116         return false;
117 }
118
119 ValueBase
120 Layer_SphereDistort::get_param(const String &param)const
121 {
122         EXPORT(center);
123         EXPORT(radius);
124         EXPORT(type);
125         EXPORT_AS(percent,"amount");
126         EXPORT(clip);
127
128         EXPORT_NAME();
129         EXPORT_VERSION();
130
131         return ValueBase();
132 }
133
134 void
135 Layer_SphereDistort::sync()
136 {
137 }
138
139 Layer::Vocab
140 Layer_SphereDistort::get_param_vocab()const
141 {
142         Layer::Vocab ret;
143
144         ret.push_back(ParamDesc("center")
145                 .set_local_name(_("Position"))
146                 .set_description(_("Where the sphere distortion is centered"))
147         );
148
149         ret.push_back(ParamDesc("radius")
150                 .set_local_name(_("Radius"))
151                 .set_origin("center")
152                 .set_is_distance()
153                 .set_description(_("The size of the sphere distortion"))
154         );
155
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)"))
160         );
161
162         ret.push_back(ParamDesc("clip")
163                 .set_local_name(_("Clip"))
164                 .set_description(_("When cheked, the area outside the Radius are not distorted"))
165         );
166
167         ret.push_back(ParamDesc("type")
168                 .set_local_name(_("Distort Type"))
169                 .set_description(_("The direction of the distortion"))
170                 .set_hint("enum")
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"))
174         );
175
176         return ret;
177 }
178
179 /*
180         Spherical Distortion: maps an image onto a ellipsoid of some sort
181
182         so the image coordinate (i.e. distance away from the center)
183         will determine how things get mapped
184
185         so with the radius and position the mapping would go as follows
186
187         r = (pos - center) / radius     clamped to [-1,1]
188
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
191
192         angle = r * pi/2 (-pi/2,pi/2)
193
194         newr = cos(angle)*radius
195
196         the inverse of this is (which is actually what we'd be transforming it from
197
198
199 */
200
201 inline float spherify(float f)
202 {
203         if(f > -1 && f < 1 && f!=0)
204                 return sinf(f*(PI/2));
205         else return f;
206 }
207
208 inline float unspherify(float f)
209 {
210         if(f > -1 && f < 1 && f!=0)
211                 return asin(f)/(PI/2);
212         else return f;
213 }
214
215 Point sphtrans(const Point &p, const Point &center, const float &radius,
216                                                                                         const Real &percent, int type, bool& clipped)
217 {
218         const Vector v = (p - center) / radius;
219
220         Point newp = p;
221         const float t = percent;
222
223         clipped=false;
224
225         if(type == TYPE_NORMAL)
226         {
227                 const float m = v.mag();
228                 float lerp(0);
229
230                 if(m <= -1 || m >= 1)
231                 {
232                         clipped=true;
233                         return newp;
234                 }else
235                 if(m==0)
236                         return newp;
237                 else
238                 if(t > 0)
239                 {
240                         lerp = (t*unspherify(m) + (1-t)*m);
241                 }else if(t < 0)
242                 {
243                         lerp = ((1+t)*m - t*spherify(m));
244                 }else lerp = m;
245
246                 const float d = lerp*radius;
247                 newp = center + v*(d/m);
248         }
249
250         else if(type == TYPE_DISTH)
251         {
252                 float lerp(0);
253                 if(v[0] <= -1 || v[0] >= 1)
254                 {
255                         clipped=true;
256                         return newp;
257                 }else
258                 if(v[0]==0)
259                         return newp;
260                 else
261                 if(t > 0)
262                 {
263                         lerp = (t*unspherify(v[0]) + (1-t)*v[0]);
264                 }else if(t < 0)
265                 {
266                         lerp = ((1+t)*v[0] - t*spherify(v[0]));
267                 }else lerp = v[0];
268
269                 newp[0] = center[0] + lerp*radius;
270         }
271
272         else if(type == TYPE_DISTV)
273         {
274                 float lerp(0);
275                 if(v[1] <= -1 || v[1] >= 1)
276                 {
277                         clipped=true;
278                         return newp;
279                 }
280                 else
281                 if(v[1]==0)
282                         return newp;
283                 else
284                 if(t > 0)
285                 {
286                         lerp = (t*unspherify(v[1]) + (1-t)*v[1]);
287                 }else if(t < 0)
288                 {
289                         lerp = ((1+t)*v[1] - t*spherify(v[1]));
290                 }else lerp = v[1];
291
292                 newp[1] = center[1] + lerp*radius;
293         }
294
295         return newp;
296 }
297
298 inline Point sphtrans(const Point &p, const Point &center, const Real &radius,
299                                                                                         const Real &percent, int type)
300 {
301         bool tmp;
302         return sphtrans(p, center, radius, percent, type, tmp);
303 }
304
305 synfig::Layer::Handle
306 Layer_SphereDistort::hit_check(synfig::Context context, const synfig::Point &pos)const
307 {
308         bool clipped;
309         Point point(sphtrans(pos,center,radius,percent,type,clipped));
310         if(clip && clipped)
311                 return 0;
312         return context.hit_check(point);
313 }
314
315 Color
316 Layer_SphereDistort::get_color(Context context, const Point &pos)const
317 {
318         bool clipped;
319         Point point(sphtrans(pos,center,radius,percent,type,clipped));
320         if(clip && clipped)
321                 return Color::alpha();
322         return context.get_color(point);
323 }
324
325 #if 1
326 bool Layer_SphereDistort::accelerated_render(Context context,Surface *surface,int quality, const RendDesc &renddesc, ProgressCallback *cb)const
327 {
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?)
333
334                 //things to defer until after
335                 super sampling, non-linear interpolation
336         */
337
338         //bounding box reject
339         {
340                 Rect    sphr;
341
342                 sphr.set_point(center[0]-radius,center[1]-radius);
343                 sphr.expand(center[0]+radius,center[1]+radius);
344
345                 //get the bounding box of the transform
346                 Rect    windr;
347
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]);
351
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)) )
356                 {
357                         //synfig::warning("Spherize: Bounding box reject");
358                         if (clip)
359                         {
360                                 surface->set_wh(renddesc.get_w(), renddesc.get_h());
361                                 surface->clear();
362                                 return true;
363                         }
364                         else
365                                 return context.accelerated_render(surface,quality,renddesc,cb);
366                 }
367
368                 //synfig::warning("Spherize: Bounding box accept");
369         }
370
371         //Ok, so we overlap some... now expand the window for rendering
372         RendDesc r = renddesc;
373         Surface background;
374         Real pw = renddesc.get_pw(),ph = renddesc.get_ph();
375
376         int nl=0,nt=0,nr=0,nb=0, nw=0,nh=0;
377         Point tl = renddesc.get_tl(), br = renddesc.get_br();
378
379         {
380                 //must enlarge window by pixel coordinates so go!
381
382                 //need to figure out closest and farthest point and distort THOSE
383
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)};
389
390                 Point close(0,0);
391                 Real t = 0;
392                 Rect    expandr(tl,br);
393
394                 //expandr.set_point(tl[0],tl[1]);
395                 //expandr.expand(br[0],br[1]);
396
397                 //synfig::warning("Spherize: Loop through lines and stuff");
398                 for(int i=0; i<4; ++i)
399                 {
400                         //synfig::warning("Spherize:    %d", i);
401                         Vector p_o = center-origin[i];
402
403                         //project onto left line
404                         t = (p_o*v[i])/v[i].mag_squared();
405
406                         //clamp
407                         if(t < 0) t = 0; if(t > 1) t = 1;
408
409                         close = origin[i] + v[i]*t;
410
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]);
418                 }
419
420                 /*synfig::warning("Spherize: Bounding box (%f,%f)-(%f,%f)",
421                                                         expandr.minx,expandr.miny,expandr.maxx,expandr.maxy);*/
422
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
425
426                 //synfig::warning("Spherize: Organize like tl,br");
427                 Point ntl(0,0),nbr(0,0);
428
429                 //sort x
430                 if(tl[0] < br[0])
431                 {
432                         ntl[0] = expandr.minx;
433                         nbr[0] = expandr.maxx;
434                 }
435                 else
436                 {
437                         ntl[0] = expandr.maxx;
438                         nbr[0] = expandr.minx;
439                 }
440
441                 //sort y
442                 if(tl[1] < br[1])
443                 {
444                         ntl[1] = expandr.miny;
445                         nbr[1] = expandr.maxy;
446                 }
447                 else
448                 {
449                         ntl[1] = expandr.maxy;
450                         nbr[1] = expandr.miny;
451                 }
452
453                 //now expand the window as needed
454                 Vector temp = ntl-tl;
455
456                 //pixel offset
457                 nl = (int)(temp[0]/pw)-1;
458                 nt = (int)(temp[1]/ph)-1;
459
460                 temp = nbr - br;
461                 nr = (int)(temp[0]/pw)+1;
462                 nb = (int)(temp[1]/ph)+1;
463
464                 nw = renddesc.get_w() + nr - nl;
465                 nh = renddesc.get_h() + nb - nt;
466
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);
469
470                 /*r = renddesc;
471                 nw = r.get_w(), nh = r.get_h();
472                 nl = 0, nt = 0;*/
473         }
474
475         //synfig::warning("Spherize: render background");
476         if(!context.accelerated_render(&background,quality,r,cb))
477         {
478                 synfig::warning("SphereDistort: Layer below failed");
479                 return false;
480         }
481
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);
485
486         Point sample = tl, sub = tl, trans(0,0);
487         float xs = 0,ys = 0;
488         int y=0,x=0;
489         Real invpw = 1/pw, invph = 1/ph;
490         Surface::pen    p = surface->begin();
491
492         Point rtl = r.get_tl();
493
494         //synfig::warning("Spherize: About to transform");
495
496         for(y = 0; y < h; ++y, sample[1] += ph, p.inc_y())
497         {
498                 sub = sample;
499                 for(x = 0; x < w; ++x, sub[0] += pw, p.inc_x())
500                 {
501                         bool clipped;
502                         trans=sphtrans(sub,center,radius,percent,type,clipped);
503                         if(clip && clipped)
504                         {
505                                 p.put_value(Color::alpha());
506                                 continue;
507                         }
508
509                         xs = (trans[0]-rtl[0])*invpw;
510                         ys = (trans[1]-rtl[1])*invph;
511
512                         if(!(xs >= 0 && xs < nw && ys >= 0 && ys < nh))
513                         {
514                                 //synfig::warning("Spherize: we failed to account for %f,%f",xs,ys);
515                                 p.put_value(context.get_color(trans));//Color::alpha());
516                                 continue;
517                         }
518
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));
526                         else                            // nearest
527                                 p.put_value(background[round_to_int(ys)][round_to_int(xs)]);
528                 }
529                 p.dec_x(w);
530         }
531
532         return true;
533 }
534 #endif
535
536 class synfig::Spherize_Trans : public synfig::Transform
537 {
538         etl::handle<const Layer_SphereDistort> layer;
539 public:
540         Spherize_Trans(const Layer_SphereDistort* x):Transform(x->get_guid()),layer(x) { }
541
542         synfig::Vector perform(const synfig::Vector& x)const
543         {
544                 return sphtrans(x,layer->center,layer->radius,-layer->percent,layer->type);
545         }
546
547         synfig::Vector unperform(const synfig::Vector& x)const
548         {
549                 return sphtrans(x,layer->center,layer->radius,layer->percent,layer->type);
550         }
551 };
552
553 etl::handle<Transform>
554 Layer_SphereDistort::get_transform()const
555 {
556         return new Spherize_Trans(this);
557 }
558
559 Rect
560 Layer_SphereDistort::get_bounding_rect()const
561 {
562         Rect bounds(Rect::full_plane());
563
564         if (clip)
565                 return bounds;
566
567         switch(type)
568         {
569                 case TYPE_NORMAL:
570                         bounds=Rect(center[0]+radius, center[1]+radius,
571                                                 center[0]-radius, center[1]-radius);
572                         break;
573                 case TYPE_DISTH:
574                         bounds = Rect::vertical_strip(center[0]-radius, center[0]+radius);
575                         break;
576                 case TYPE_DISTV:
577                         bounds = Rect::horizontal_strip(center[1]-radius, center[1]+radius);
578                         break;
579                 default:
580                         break;
581         }
582
583         return bounds;
584 }