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