5786a46196151b3d9e17eb45192300f702f9a523
[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         );
147
148         ret.push_back(ParamDesc("radius")
149                 .set_local_name(_("Radius"))
150                 .set_origin("center")
151                 .set_is_distance()
152         );
153
154         ret.push_back(ParamDesc("amount")
155                 .set_local_name(_("Amount"))
156                 .set_is_distance(false)
157         );
158
159         ret.push_back(ParamDesc("clip")
160                 .set_local_name(_("Clip"))
161         );
162
163         ret.push_back(ParamDesc("type")
164                 .set_local_name(_("Distort Type"))
165                 .set_description(_("The direction of the distortion"))
166                 .set_hint("enum")
167                 .add_enum_value(TYPE_NORMAL,"normal",_("Spherize"))
168                 .add_enum_value(TYPE_DISTH,"honly",_("Vertical Bar"))
169                 .add_enum_value(TYPE_DISTV,"vonly",_("Horizontal Bar"))
170         );
171
172         return ret;
173 }
174
175 /*
176         Spherical Distortion: maps an image onto a ellipsoid of some sort
177
178         so the image coordinate (i.e. distance away from the center)
179         will determine how things get mapped
180
181         so with the radius and position the mapping would go as follows
182
183         r = (pos - center) / radius     clamped to [-1,1]
184
185         if it's outside of that range then it's not distorted
186         but if it's inside of that range then it goes as follows
187
188         angle = r * pi/2 (-pi/2,pi/2)
189
190         newr = cos(angle)*radius
191
192         the inverse of this is (which is actually what we'd be transforming it from
193
194
195 */
196
197 inline float spherify(float f)
198 {
199         if(f > -1 && f < 1 && f!=0)
200                 return sinf(f*(PI/2));
201         else return f;
202 }
203
204 inline float unspherify(float f)
205 {
206         if(f > -1 && f < 1 && f!=0)
207                 return asin(f)/(PI/2);
208         else return f;
209 }
210
211 Point sphtrans(const Point &p, const Point &center, const float &radius,
212                                                                                         const Real &percent, int type, bool& clipped)
213 {
214         const Vector v = (p - center) / radius;
215
216         Point newp = p;
217         const float t = percent;
218
219         clipped=false;
220
221         if(type == TYPE_NORMAL)
222         {
223                 const float m = v.mag();
224                 float lerp(0);
225
226                 if(m <= -1 || m >= 1)
227                 {
228                         clipped=true;
229                         return newp;
230                 }else
231                 if(m==0)
232                         return newp;
233                 else
234                 if(t > 0)
235                 {
236                         lerp = (t*unspherify(m) + (1-t)*m);
237                 }else if(t < 0)
238                 {
239                         lerp = ((1+t)*m - t*spherify(m));
240                 }else lerp = m;
241
242                 const float d = lerp*radius;
243                 newp = center + v*(d/m);
244         }
245
246         else if(type == TYPE_DISTH)
247         {
248                 float lerp(0);
249                 if(v[0] <= -1 || v[0] >= 1)
250                 {
251                         clipped=true;
252                         return newp;
253                 }else
254                 if(v[0]==0)
255                         return newp;
256                 else
257                 if(t > 0)
258                 {
259                         lerp = (t*unspherify(v[0]) + (1-t)*v[0]);
260                 }else if(t < 0)
261                 {
262                         lerp = ((1+t)*v[0] - t*spherify(v[0]));
263                 }else lerp = v[0];
264
265                 newp[0] = center[0] + lerp*radius;
266         }
267
268         else if(type == TYPE_DISTV)
269         {
270                 float lerp(0);
271                 if(v[1] <= -1 || v[1] >= 1)
272                 {
273                         clipped=true;
274                         return newp;
275                 }
276                 else
277                 if(v[1]==0)
278                         return newp;
279                 else
280                 if(t > 0)
281                 {
282                         lerp = (t*unspherify(v[1]) + (1-t)*v[1]);
283                 }else if(t < 0)
284                 {
285                         lerp = ((1+t)*v[1] - t*spherify(v[1]));
286                 }else lerp = v[1];
287
288                 newp[1] = center[1] + lerp*radius;
289         }
290
291         return newp;
292 }
293
294 inline Point sphtrans(const Point &p, const Point &center, const Real &radius,
295                                                                                         const Real &percent, int type)
296 {
297         bool tmp;
298         return sphtrans(p, center, radius, percent, type, tmp);
299 }
300
301 synfig::Layer::Handle
302 Layer_SphereDistort::hit_check(synfig::Context context, const synfig::Point &pos)const
303 {
304         bool clipped;
305         Point point(sphtrans(pos,center,radius,percent,type,clipped));
306         if(clip && clipped)
307                 return 0;
308         return context.hit_check(point);
309 }
310
311 Color
312 Layer_SphereDistort::get_color(Context context, const Point &pos)const
313 {
314         bool clipped;
315         Point point(sphtrans(pos,center,radius,percent,type,clipped));
316         if(clip && clipped)
317                 return Color::alpha();
318         return context.get_color(point);
319 }
320
321 #if 1
322 bool Layer_SphereDistort::accelerated_render(Context context,Surface *surface,int quality, const RendDesc &renddesc, ProgressCallback *cb)const
323 {
324         /*      Things to consider:
325                 1) Block expansion for distortion (ouch... quality level??)
326                 2) Bounding box clipping
327                 3) Super sampling for better visual quality (based on the quality level?)
328                 4) Interpolation type for sampling (based on quality level?)
329
330                 //things to defer until after
331                 super sampling, non-linear interpolation
332         */
333
334         //bounding box reject
335         {
336                 Rect    sphr;
337
338                 sphr.set_point(center[0]-radius,center[1]-radius);
339                 sphr.expand(center[0]+radius,center[1]+radius);
340
341                 //get the bounding box of the transform
342                 Rect    windr;
343
344                 //and the bounding box of the rendering
345                 windr.set_point(renddesc.get_tl()[0],renddesc.get_tl()[1]);
346                 windr.expand(renddesc.get_br()[0],renddesc.get_br()[1]);
347
348                 //test bounding boxes for collision
349                 if( (type == TYPE_NORMAL && !intersect(sphr,windr)) ||
350                         (type == TYPE_DISTH && (sphr.minx >= windr.maxx || windr.minx >= sphr.maxx)) ||
351                         (type == TYPE_DISTV && (sphr.miny >= windr.maxy || windr.miny >= sphr.maxy)) )
352                 {
353                         //synfig::warning("Spherize: Bounding box reject");
354                         if (clip)
355                         {
356                                 surface->set_wh(renddesc.get_w(), renddesc.get_h());
357                                 surface->clear();
358                                 return true;
359                         }
360                         else
361                                 return context.accelerated_render(surface,quality,renddesc,cb);
362                 }
363
364                 //synfig::warning("Spherize: Bounding box accept");
365         }
366
367         //Ok, so we overlap some... now expand the window for rendering
368         RendDesc r = renddesc;
369         Surface background;
370         Real pw = renddesc.get_pw(),ph = renddesc.get_ph();
371
372         int nl=0,nt=0,nr=0,nb=0, nw=0,nh=0;
373         Point tl = renddesc.get_tl(), br = renddesc.get_br();
374
375         {
376                 //must enlarge window by pixel coordinates so go!
377
378                 //need to figure out closest and farthest point and distort THOSE
379
380                 Point origin[4] = {tl,tl,br,br};
381                 Vector v[4] = {Vector(0,br[1]-tl[1]),
382                                            Vector(br[0]-tl[0],0),
383                                            Vector(0,tl[1]-br[1]),
384                                            Vector(tl[0]-br[0],0)};
385
386                 Point close(0,0);
387                 Real t = 0;
388                 Rect    expandr(tl,br);
389
390                 //expandr.set_point(tl[0],tl[1]);
391                 //expandr.expand(br[0],br[1]);
392
393                 //synfig::warning("Spherize: Loop through lines and stuff");
394                 for(int i=0; i<4; ++i)
395                 {
396                         //synfig::warning("Spherize:    %d", i);
397                         Vector p_o = center-origin[i];
398
399                         //project onto left line
400                         t = (p_o*v[i])/v[i].mag_squared();
401
402                         //clamp
403                         if(t < 0) t = 0; if(t > 1) t = 1;
404
405                         close = origin[i] + v[i]*t;
406
407                         //now get transforms and expand the rectangle to accommodate
408                         Point p = sphtrans(close,center,radius,percent,type);
409                         expandr.expand(p[0],p[1]);
410                         p = sphtrans(origin[i],center,radius,percent,type);
411                         expandr.expand(p[0],p[1]);
412                         p = sphtrans(origin[i]+v[i],center,radius,percent,type);
413                         expandr.expand(p[0],p[1]);
414                 }
415
416                 /*synfig::warning("Spherize: Bounding box (%f,%f)-(%f,%f)",
417                                                         expandr.minx,expandr.miny,expandr.maxx,expandr.maxy);*/
418
419                 //now that we have the bounding rectangle of ALL the pixels (should be...)
420                 //order it so that it's in the same orientation as the tl,br pair
421
422                 //synfig::warning("Spherize: Organize like tl,br");
423                 Point ntl(0,0),nbr(0,0);
424
425                 //sort x
426                 if(tl[0] < br[0])
427                 {
428                         ntl[0] = expandr.minx;
429                         nbr[0] = expandr.maxx;
430                 }
431                 else
432                 {
433                         ntl[0] = expandr.maxx;
434                         nbr[0] = expandr.minx;
435                 }
436
437                 //sort y
438                 if(tl[1] < br[1])
439                 {
440                         ntl[1] = expandr.miny;
441                         nbr[1] = expandr.maxy;
442                 }
443                 else
444                 {
445                         ntl[1] = expandr.maxy;
446                         nbr[1] = expandr.miny;
447                 }
448
449                 //now expand the window as needed
450                 Vector temp = ntl-tl;
451
452                 //pixel offset
453                 nl = (int)(temp[0]/pw)-1;
454                 nt = (int)(temp[1]/ph)-1;
455
456                 temp = nbr - br;
457                 nr = (int)(temp[0]/pw)+1;
458                 nb = (int)(temp[1]/ph)+1;
459
460                 nw = renddesc.get_w() + nr - nl;
461                 nh = renddesc.get_h() + nb - nt;
462
463                 //synfig::warning("Spherize: Setting subwindow (%d,%d) (%d,%d) (%d,%d)",nl,nt,nr,nb,nw,nh);
464                 r.set_subwindow(nl,nt,nw,nh);
465
466                 /*r = renddesc;
467                 nw = r.get_w(), nh = r.get_h();
468                 nl = 0, nt = 0;*/
469         }
470
471         //synfig::warning("Spherize: render background");
472         if(!context.accelerated_render(&background,quality,r,cb))
473         {
474                 synfig::warning("SphereDistort: Layer below failed");
475                 return false;
476         }
477
478         //now distort and check to make sure we aren't overshooting our bounds here
479         int w = renddesc.get_w(), h = renddesc.get_h();
480         surface->set_wh(w,h);
481
482         Point sample = tl, sub = tl, trans(0,0);
483         float xs = 0,ys = 0;
484         int y=0,x=0;
485         Real invpw = 1/pw, invph = 1/ph;
486         Surface::pen    p = surface->begin();
487
488         Point rtl = r.get_tl();
489
490         //synfig::warning("Spherize: About to transform");
491
492         for(y = 0; y < h; ++y, sample[1] += ph, p.inc_y())
493         {
494                 sub = sample;
495                 for(x = 0; x < w; ++x, sub[0] += pw, p.inc_x())
496                 {
497                         bool clipped;
498                         trans=sphtrans(sub,center,radius,percent,type,clipped);
499                         if(clip && clipped)
500                         {
501                                 p.put_value(Color::alpha());
502                                 continue;
503                         }
504
505                         xs = (trans[0]-rtl[0])*invpw;
506                         ys = (trans[1]-rtl[1])*invph;
507
508                         if(!(xs >= 0 && xs < nw && ys >= 0 && ys < nh))
509                         {
510                                 //synfig::warning("Spherize: we failed to account for %f,%f",xs,ys);
511                                 p.put_value(context.get_color(trans));//Color::alpha());
512                                 continue;
513                         }
514
515                         //sample at that pixel location based on the quality
516                         if(quality <= 4)        // cubic
517                                 p.put_value(background.cubic_sample(xs,ys));
518                         else if(quality <= 5) // cosine
519                                 p.put_value(background.cosine_sample(xs,ys));
520                         else if(quality <= 6) // linear
521                                 p.put_value(background.linear_sample(xs,ys));
522                         else                            // nearest
523                                 p.put_value(background[round_to_int(ys)][round_to_int(xs)]);
524                 }
525                 p.dec_x(w);
526         }
527
528         return true;
529 }
530 #endif
531
532 class synfig::Spherize_Trans : public synfig::Transform
533 {
534         etl::handle<const Layer_SphereDistort> layer;
535 public:
536         Spherize_Trans(const Layer_SphereDistort* x):Transform(x->get_guid()),layer(x) { }
537
538         synfig::Vector perform(const synfig::Vector& x)const
539         {
540                 return sphtrans(x,layer->center,layer->radius,-layer->percent,layer->type);
541         }
542
543         synfig::Vector unperform(const synfig::Vector& x)const
544         {
545                 return sphtrans(x,layer->center,layer->radius,layer->percent,layer->type);
546         }
547 };
548
549 etl::handle<Transform>
550 Layer_SphereDistort::get_transform()const
551 {
552         return new Spherize_Trans(this);
553 }
554
555 Rect
556 Layer_SphereDistort::get_bounding_rect()const
557 {
558         Rect bounds(Rect::full_plane());
559
560         if (clip)
561                 return bounds;
562
563         switch(type)
564         {
565                 case TYPE_NORMAL:
566                         bounds=Rect(center[0]+radius, center[1]+radius,
567                                                 center[0]-radius, center[1]-radius);
568                         break;
569                 case TYPE_DISTH:
570                         bounds = Rect::vertical_strip(center[0]-radius, center[0]+radius);
571                         break;
572                 case TYPE_DISTV:
573                         bounds = Rect::horizontal_strip(center[1]-radius, center[1]+radius);
574                         break;
575                 default:
576                         break;
577         }
578
579         return bounds;
580 }