Fix bugs in previous commit that caused FTBFS in synfig and ETL FTBFS with older...
[synfig.git] / synfig-core / tags / synfig_0_61_07_rc1 / src / modules / lyr_std / sphere_distort.cpp
1 /* === S Y N F I G ========================================================= */
2 /*!     \file sphere_distort.cpp
3 **      \brief Sphere Distort File
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,_("Spherize"));
73 SYNFIG_LAYER_SET_CATEGORY(Layer_SphereDistort,_("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                         return context.accelerated_render(surface,quality,renddesc,cb);
354                 }
355
356                 //synfig::warning("Spherize: Bounding box accept");
357         }
358
359         //Ok, so we overlap some... now expand the window for rendering
360         RendDesc r = renddesc;
361         Surface background;
362         Real pw = renddesc.get_pw(),ph = renddesc.get_ph();
363
364         int nl=0,nt=0,nr=0,nb=0, nw=0,nh=0;
365         Point tl = renddesc.get_tl(), br = renddesc.get_br();
366
367         {
368                 //must enlarge window by pixel coordinates so go!
369
370                 //need to figure out closest and farthest point and distort THOSE
371
372                 Point origin[4] = {tl,tl,br,br};
373                 Vector v[4] = {Vector(0,br[1]-tl[1]),
374                                            Vector(br[0]-tl[0],0),
375                                            Vector(0,tl[1]-br[1]),
376                                            Vector(tl[0]-br[0],0)};
377
378                 Point close(0,0);
379                 Real t = 0;
380                 Rect    expandr(tl,br);
381
382                 //expandr.set_point(tl[0],tl[1]);
383                 //expandr.expand(br[0],br[1]);
384
385                 //synfig::warning("Spherize: Loop through lines and stuff");
386                 for(int i=0; i<4; ++i)
387                 {
388                         //synfig::warning("Spherize:    %d", i);
389                         Vector p_o = center-origin[i];
390
391                         //project onto left line
392                         t = (p_o*v[i])/v[i].mag_squared();
393
394                         //clamp
395                         if(t < 0) t = 0; if(t > 1) t = 1;
396
397                         close = origin[i] + v[i]*t;
398
399                         //now get transforms and expand the rectangle to accomodate
400                         Point p = sphtrans(close,center,radius,percent,type);
401                         expandr.expand(p[0],p[1]);
402                         p = sphtrans(origin[i],center,radius,percent,type);
403                         expandr.expand(p[0],p[1]);
404                         p = sphtrans(origin[i]+v[i],center,radius,percent,type);
405                         expandr.expand(p[0],p[1]);
406                 }
407
408                 /*synfig::warning("Spherize: Bounding box (%f,%f)-(%f,%f)",
409                                                         expandr.minx,expandr.miny,expandr.maxx,expandr.maxy);*/
410
411                 //now that we have the bouding rectangle of ALL the pixels (should be...)
412                 //order it so that it's in the same orientation as the tl,br pair
413
414                 //synfig::warning("Spherize: Organize like tl,br");
415                 Point ntl(0,0),nbr(0,0);
416
417                 //sort x
418                 if(tl[0] < br[0])
419                 {
420                         ntl[0] = expandr.minx;
421                         nbr[0] = expandr.maxx;
422                 }
423                 else
424                 {
425                         ntl[0] = expandr.maxx;
426                         nbr[0] = expandr.minx;
427                 }
428
429                 //sort y
430                 if(tl[1] < br[1])
431                 {
432                         ntl[1] = expandr.miny;
433                         nbr[1] = expandr.maxy;
434                 }
435                 else
436                 {
437                         ntl[1] = expandr.maxy;
438                         nbr[1] = expandr.miny;
439                 }
440
441                 //now expand the window as needed
442                 Vector temp = ntl-tl;
443
444                 //pixel offset
445                 nl = (int)(temp[0]/pw)-1;
446                 nt = (int)(temp[1]/ph)-1;
447
448                 temp = nbr - br;
449                 nr = (int)(temp[0]/pw)+1;
450                 nb = (int)(temp[1]/ph)+1;
451
452                 nw = renddesc.get_w() + nr - nl;
453                 nh = renddesc.get_h() + nb - nt;
454
455                 //synfig::warning("Spherize: Setting subwindow (%d,%d) (%d,%d) (%d,%d)",nl,nt,nr,nb,nw,nh);
456                 r.set_subwindow(nl,nt,nw,nh);
457
458                 /*r = renddesc;
459                 nw = r.get_w(), nh = r.get_h();
460                 nl = 0, nt = 0;*/
461         }
462
463         //synfig::warning("Spherize: render background");
464         if(!context.accelerated_render(&background,quality,r,cb))
465         {
466                 synfig::warning("SphereDistort: Layer below failed");
467                 return false;
468         }
469
470         //now distort and check to make sure we aren't overshooting our bounds here
471         int w = renddesc.get_w(), h = renddesc.get_h();
472         surface->set_wh(w,h);
473
474         Point sample = tl, sub = tl, trans(0,0);
475         float xs = 0,ys = 0;
476         int y=0,x=0;
477         Real invpw = 1/pw, invph = 1/ph;
478         Surface::pen    p = surface->begin();
479
480         Point rtl = r.get_tl();
481
482         //synfig::warning("Spherize: About to transform");
483
484         for(y = 0; y < h; ++y, sample[1] += ph, p.inc_y())
485         {
486                 sub = sample;
487                 for(x = 0; x < w; ++x, sub[0] += pw, p.inc_x())
488                 {
489                         bool clipped;
490                         trans=sphtrans(sub,center,radius,percent,type,clipped);
491                         if(clip && clipped)
492                         {
493                                 p.put_value(Color::alpha());
494                                 continue;
495                         }
496
497                         xs = (trans[0]-rtl[0])*invpw;
498                         ys = (trans[1]-rtl[1])*invph;
499
500                         if(!(xs >= 0 && xs < nw && ys >= 0 && ys < nh))
501                         {
502                                 //synfig::warning("Spherize: we failed to account for %f,%f",xs,ys);
503                                 p.put_value(context.get_color(trans));//Color::alpha());
504                                 continue;
505                         }
506
507                         //sample at that pixel location based on the quality
508                         if(quality <= 4)        // cubic
509                                 p.put_value(background.cubic_sample(xs,ys));
510                         else if(quality <= 5) // cosine
511                                 p.put_value(background.cosine_sample(xs,ys));
512                         else if(quality <= 6) // linear
513                                 p.put_value(background.linear_sample(xs,ys));
514                         else                            // nearest
515                                 p.put_value(background[round_to_int(ys)][round_to_int(xs)]);
516                 }
517                 p.dec_x(w);
518         }
519
520         return true;
521 }
522 #endif
523
524 class synfig::Spherize_Trans : public synfig::Transform
525 {
526         etl::handle<const Layer_SphereDistort> layer;
527 public:
528         Spherize_Trans(const Layer_SphereDistort* x):Transform(x->get_guid()),layer(x) { }
529
530         synfig::Vector perform(const synfig::Vector& x)const
531         {
532                 return sphtrans(x,layer->center,layer->radius,-layer->percent,layer->type);
533         }
534
535         synfig::Vector unperform(const synfig::Vector& x)const
536         {
537                 return sphtrans(x,layer->center,layer->radius,layer->percent,layer->type);
538         }
539 };
540
541 etl::handle<Transform>
542 Layer_SphereDistort::get_transform()const
543 {
544         return new Spherize_Trans(this);
545 }
546
547 Rect
548 Layer_SphereDistort::get_bounding_rect()const
549 {
550         Rect bounds(Rect::full_plane());
551         switch(type)
552         {
553                 case TYPE_NORMAL:
554                         bounds=Rect(center[0]+radius, center[1]+radius,
555                                                 center[0]-radius, center[1]-radius);
556                         break;
557                 case TYPE_DISTH:
558                         bounds = Rect::vertical_strip(center[0]-radius, center[0]+radius);
559                         break;
560                 case TYPE_DISTV:
561                         bounds = Rect::horizontal_strip(center[1]-radius, center[1]+radius);
562                         break;
563                 default:
564                         break;
565         }
566
567         return bounds;
568 }