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