Use map of static values indexed by parameter name instead of the macros and the...
[synfig.git] / synfig-core / src / modules / lyr_std / curvewarp.cpp
1 /* === S Y N F I G ========================================================= */
2 /*!     \file curvewarp.cpp
3 **      \brief Implementation of the "Curve Warp" 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 ** === N O T E S ===========================================================
23 **
24 ** ========================================================================= */
25
26 /* === H E A D E R S ======================================================= */
27
28 #ifdef USING_PCH
29 #       include "pch.h"
30 #else
31 #ifdef HAVE_CONFIG_H
32 #       include <config.h>
33 #endif
34
35 #include "curvewarp.h"
36
37 #include <synfig/context.h>
38 #include <synfig/paramdesc.h>
39 #include <synfig/surface.h>
40 #include <synfig/valuenode.h>
41 #include <ETL/calculus>
42
43 #endif
44
45 /* === M A C R O S ========================================================= */
46
47 #define FAKE_TANGENT_STEP 0.000001
48 #define TOO_THIN 0.01
49
50 /* === G L O B A L S ======================================================= */
51
52 SYNFIG_LAYER_INIT(CurveWarp);
53 SYNFIG_LAYER_SET_NAME(CurveWarp,"curve_warp");
54 SYNFIG_LAYER_SET_LOCAL_NAME(CurveWarp,N_("Curve Warp"));
55 SYNFIG_LAYER_SET_CATEGORY(CurveWarp,N_("Distortions"));
56 SYNFIG_LAYER_SET_VERSION(CurveWarp,"0.0");
57 SYNFIG_LAYER_SET_CVS_ID(CurveWarp,"$Id$");
58
59 /* === P R O C E D U R E S ================================================= */
60
61 inline float calculate_distance(const std::vector<synfig::BLinePoint>& bline)
62 {
63         std::vector<synfig::BLinePoint>::const_iterator iter,next,ret;
64         std::vector<synfig::BLinePoint>::const_iterator end(bline.end());
65
66         float dist(0);
67
68         if (bline.empty()) return dist;
69
70         next=bline.begin();
71         iter=next++;
72
73         for(;next!=end;iter=next++)
74         {
75                 // Setup the curve
76                 etl::hermite<Vector> curve(iter->get_vertex(), next->get_vertex(), iter->get_tangent2(), next->get_tangent1());
77                 dist+=curve.length();
78         }
79
80         return dist;
81 }
82
83 std::vector<synfig::BLinePoint>::const_iterator
84 find_closest_to_bline(bool fast, const std::vector<synfig::BLinePoint>& bline,const Point& p,float& t, float& len, bool& extreme)
85 {
86         std::vector<synfig::BLinePoint>::const_iterator iter,next,ret;
87         std::vector<synfig::BLinePoint>::const_iterator end(bline.end());
88
89         ret=bline.end();
90         float dist(100000000000.0);
91         next=bline.begin();
92         float best_pos(0), best_len(0);
93         etl::hermite<Vector> best_curve;
94         iter=next++;
95         Point bp;
96         float total_len(0);
97         bool first = true, last = false;
98         extreme = false;
99
100         for(;next!=end;iter=next++)
101         {
102                 // Setup the curve
103                 etl::hermite<Vector> curve(iter->get_vertex(), next->get_vertex(), iter->get_tangent2(), next->get_tangent1());
104                 float thisdist(0);
105                 last = false;
106
107                 if (fast)
108                 {
109 #define POINT_CHECK(x) bp=curve(x);     thisdist=(bp-p).mag_squared(); if(thisdist<dist) { extreme = (first&&x<0.01); ret=iter; best_len = total_len; dist=thisdist; best_curve=curve; last=true; best_pos=x;}
110                         POINT_CHECK(0.0001);  POINT_CHECK((1.0/6)); POINT_CHECK((2.0/6)); POINT_CHECK((3.0/6));
111                         POINT_CHECK((4.0/6)); POINT_CHECK((5.0/6)); POINT_CHECK(0.9999);
112                 }
113                 else
114                 {
115                         float pos = curve.find_closest(fast, p);
116                         thisdist=(curve(pos)-p).mag_squared();
117                         if(thisdist<dist)
118                         {
119                                 extreme = (first && pos == 0);
120                                 ret=iter;
121                                 dist=thisdist;
122                                 best_pos = pos;
123                                 best_curve = curve;
124                                 best_len = total_len;
125                                 last = true;
126                         }
127                 }
128                 total_len += curve.length();
129                 first = false;
130         }
131
132         t = best_pos;
133         if (fast)
134         {
135                 len = best_len + best_curve.find_distance(0,best_curve.find_closest(fast, p));
136                 if (last && t > .99) extreme = true;
137         }
138         else
139         {
140                 len = best_len + best_curve.find_distance(0,best_pos);
141                 if (last && t == 1) extreme = true;
142         }
143         return ret;
144 }
145
146 /* === M E T H O D S ======================================================= */
147
148 inline void
149 CurveWarp::sync()
150 {
151         curve_length_=calculate_distance(bline);
152         perp_ = (end_point - start_point).perp().norm();
153 }
154
155 CurveWarp::CurveWarp():
156         origin(0,0),
157         perp_width(1),
158         start_point(-2.5,-0.5),
159         end_point(2.5,-0.3),
160         fast(true)
161 {
162         bline.push_back(BLinePoint());
163         bline.push_back(BLinePoint());
164         bline[0].set_vertex(Point(-2.5,0));
165         bline[1].set_vertex(Point( 2.5,0));
166         bline[0].set_tangent(Point(1,  0.1));
167         bline[1].set_tangent(Point(1, -0.1));
168         bline[0].set_width(1.0f);
169         bline[1].set_width(1.0f);
170
171         sync();
172         Layer::Vocab voc(get_param_vocab());
173         Layer::fill_static(voc);
174 }
175
176 inline Point
177 CurveWarp::transform(const Point &point_, Real *dist, Real *along, int quality)const
178 {
179         Vector tangent;
180         Vector diff;
181         Point p1;
182         Real thickness;
183         bool edge_case = false;
184         float len(0);
185         bool extreme;
186         float t;
187
188         if(bline.size()==0)
189                 return Point();
190         else if(bline.size()==1)
191         {
192                 tangent=bline.front().get_tangent1();
193                 p1=bline.front().get_vertex();
194                 thickness=bline.front().get_width();
195                 t = 0.5;
196                 extreme = false;
197         }
198         else
199         {
200                 Point point(point_-origin);
201
202                 std::vector<synfig::BLinePoint>::const_iterator iter,next;
203
204                 // Figure out the BLinePoint we will be using,
205                 next=find_closest_to_bline(fast,bline,point,t,len,extreme);
206
207                 iter=next++;
208                 if(next==bline.end()) next=bline.begin();
209
210                 // Setup the curve
211                 etl::hermite<Vector> curve(iter->get_vertex(), next->get_vertex(), iter->get_tangent2(), next->get_tangent1());
212
213                 // Setup the derivative function
214                 etl::derivative<etl::hermite<Vector> > deriv(curve);
215
216                 int search_iterations(7);
217
218                 if(quality<=6)search_iterations=7;
219                 else if(quality<=7)search_iterations=6;
220                 else if(quality<=8)search_iterations=5;
221                 else search_iterations=4;
222
223                 // Figure out the closest point on the curve
224                 if (fast) t = curve.find_closest(fast, point,search_iterations);
225
226                 // Calculate our values
227                 p1=curve(t);                     // the closest point on the curve
228                 tangent=deriv(t);                // the tangent at that point
229
230                 // if the point we're nearest to is at either end of the
231                 // bline, our distance from the curve is the distance from the
232                 // point on the curve.  we need to know which side of the
233                 // curve we're on, so find the average of the two tangents at
234                 // this point
235                 if (t<0.00001 || t>0.99999)
236                 {
237                         bool zero_tangent = (tangent[0] == 0 && tangent[1] == 0);
238
239                         if (t<0.5)
240                         {
241                                 if (iter->get_split_tangent_flag() || zero_tangent)
242                                 {
243                                         // fake the current tangent if we need to
244                                         if (zero_tangent) tangent = curve(FAKE_TANGENT_STEP) - curve(0);
245
246                                         // calculate the other tangent
247                                         Vector other_tangent(iter->get_tangent1());
248                                         if (other_tangent[0] == 0 && other_tangent[1] == 0)
249                                         {
250                                                 // find the previous blinepoint
251                                                 std::vector<synfig::BLinePoint>::const_iterator prev;
252                                                 if (iter != bline.begin()) (prev = iter)--;
253                                                 else prev = iter;
254
255                                                 etl::hermite<Vector> other_curve(prev->get_vertex(), iter->get_vertex(), prev->get_tangent2(), iter->get_tangent1());
256                                                 other_tangent = other_curve(1) - other_curve(1-FAKE_TANGENT_STEP);
257                                         }
258
259                                         // normalise and sum the two tangents
260                                         tangent=(other_tangent.norm()+tangent.norm());
261                                         edge_case=true;
262                                 }
263                         }
264                         else
265                         {
266                                 if (next->get_split_tangent_flag() || zero_tangent)
267                                 {
268                                         // fake the current tangent if we need to
269                                         if (zero_tangent) tangent = curve(1) - curve(1-FAKE_TANGENT_STEP);
270
271                                         // calculate the other tangent
272                                         Vector other_tangent(next->get_tangent2());
273                                         if (other_tangent[0] == 0 && other_tangent[1] == 0)
274                                         {
275                                                 // find the next blinepoint
276                                                 std::vector<synfig::BLinePoint>::const_iterator next2(next);
277                                                 if (++next2 == bline.end())
278                                                         next2 = next;
279
280                                                 etl::hermite<Vector> other_curve(next->get_vertex(), next2->get_vertex(), next->get_tangent2(), next2->get_tangent1());
281                                                 other_tangent = other_curve(FAKE_TANGENT_STEP) - other_curve(0);
282                                         }
283
284                                         // normalise and sum the two tangents
285                                         tangent=(other_tangent.norm()+tangent.norm());
286                                         edge_case=true;
287                                 }
288                         }
289                 }
290                 tangent = tangent.norm();
291
292                 // the width of the bline at the closest point on the curve
293                 thickness=(next->get_width()-iter->get_width())*t+iter->get_width();
294         }
295
296         if (thickness < TOO_THIN && thickness > -TOO_THIN)
297         {
298                 if (thickness > 0) thickness = TOO_THIN;
299                 else thickness = -TOO_THIN;
300         }
301
302         if (extreme)
303         {
304                 Vector tangent;
305
306                 if (t < 0.5)
307                 {
308                         std::vector<synfig::BLinePoint>::const_iterator iter(bline.begin());
309                         tangent = iter->get_tangent1().norm();
310                         len = 0;
311                 }
312                 else
313                 {
314                         std::vector<synfig::BLinePoint>::const_iterator iter(--bline.end());
315                         tangent = iter->get_tangent2().norm();
316                         len = curve_length_;
317                 }
318                 len += (point_-origin - p1)*tangent;
319                 diff = tangent.perp();
320         }
321         else if (edge_case)
322         {
323                 diff=(p1-(point_-origin));
324                 if(diff*tangent.perp()<0) diff=-diff;
325                 diff=diff.norm();
326         }
327         else
328                 diff=tangent.perp();
329
330         // diff is a unit vector perpendicular to the bline
331         const Real unscaled_distance((point_-origin - p1)*diff);
332         if (dist) *dist = unscaled_distance;
333         if (along) *along = len;
334         return ((start_point + (end_point - start_point) * len / curve_length_) +
335                         perp_ * unscaled_distance/(thickness*perp_width));
336 }
337
338 synfig::Layer::Handle
339 CurveWarp::hit_check(synfig::Context context, const synfig::Point &point)const
340 {
341         return context.hit_check(transform(point));
342 }
343
344 bool
345 CurveWarp::set_param(const String & param, const ValueBase &value)
346 {
347         IMPORT(origin);
348         IMPORT(start_point);
349         IMPORT(end_point);
350         IMPORT(fast);
351         IMPORT(perp_width);
352
353         if(param=="bline" && value.get_type()==ValueBase::TYPE_LIST)
354         {
355                 bline=value;
356                 sync();
357
358                 return true;
359         }
360
361         IMPORT_AS(origin,"offset");
362
363         return false;
364 }
365
366 ValueBase
367 CurveWarp::get_param(const String & param)const
368 {
369         EXPORT(origin);
370         EXPORT(start_point);
371         EXPORT(end_point);
372         EXPORT(bline);
373         EXPORT(fast);
374         EXPORT(perp_width);
375
376         EXPORT_NAME();
377         EXPORT_VERSION();
378
379         return ValueBase();
380 }
381
382 bool
383 CurveWarp::set_param_static(const String &param, const bool x)
384 {
385         return Layer::set_param_static(param, x);
386 }
387
388 bool
389 CurveWarp::get_param_static(const String &param)const
390 {
391         return Layer::get_param_static(param);
392 }
393
394 Layer::Vocab
395 CurveWarp::get_param_vocab()const
396 {
397         Layer::Vocab ret;
398
399         ret.push_back(ParamDesc("origin")
400                                   .set_local_name(_("Origin")));
401
402         ret.push_back(ParamDesc("perp_width")
403                                   .set_local_name(_("Width"))
404                                   .set_origin("start_point"));
405
406         ret.push_back(ParamDesc("start_point")
407                                   .set_local_name(_("Start Point"))
408                                   .set_connect("end_point"));
409
410         ret.push_back(ParamDesc("end_point")
411                                   .set_local_name(_("End Point")));
412
413         ret.push_back(ParamDesc("bline")
414                                   .set_local_name(_("Vertices"))
415                                   .set_origin("origin")
416                                   .set_hint("width")
417                                   .set_description(_("A list of BLine Points")));
418
419         ret.push_back(ParamDesc("fast")
420                                   .set_local_name(_("Fast")));
421
422         return ret;
423 }
424
425 Color
426 CurveWarp::get_color(Context context, const Point &point)const
427 {
428         return context.get_color(transform(point));
429 }
430
431 bool
432 CurveWarp::accelerated_render(Context context,Surface *surface,int quality, const RendDesc &renddesc, ProgressCallback *cb)const
433 {
434         SuperCallback stageone(cb,0,9000,10000);
435         SuperCallback stagetwo(cb,9000,10000,10000);
436
437         int x,y;
438
439         const Real pw(renddesc.get_pw()),ph(renddesc.get_ph());
440         Point tl(renddesc.get_tl());
441         Point br(renddesc.get_br());
442         const int w(renddesc.get_w());
443         const int h(renddesc.get_h());
444
445         // find a bounding rectangle for the context we need to render
446         // todo: find a better way of doing this - this way doesn't work
447         Rect src_rect(transform(tl));
448         Point pos1, pos2;
449         Real dist, along;
450         Real min_dist(999999), max_dist(-999999), min_along(999999), max_along(-999999);
451
452 #define UPDATE_DIST \
453         if (dist < min_dist) min_dist = dist; \
454         if (dist > max_dist) max_dist = dist; \
455         if (along < min_along) min_along = along; \
456         if (along > max_along) max_along = along
457
458         // look along the top and bottom edges
459         pos1[0] = pos2[0] = tl[0]; pos1[1] = tl[1]; pos2[1] = br[1];
460         for (x = 0; x < w; x++, pos1[0] += pw, pos2[0] += pw)
461         {
462                 src_rect.expand(transform(pos1, &dist, &along)); UPDATE_DIST;
463                 src_rect.expand(transform(pos2, &dist, &along)); UPDATE_DIST;
464         }
465
466         // look along the left and right edges
467         pos1[0] = tl[0]; pos2[0] = br[0]; pos1[1] = pos2[1] = tl[1];
468         for (y = 0; y < h; y++, pos1[1] += ph, pos2[1] += ph)
469         {
470                 src_rect.expand(transform(pos1, &dist, &along)); UPDATE_DIST;
471                 src_rect.expand(transform(pos2, &dist, &along)); UPDATE_DIST;
472         }
473
474         // look along the diagonals
475         const int max_wh(std::max(w,h));
476         const Real inc_x((br[0]-tl[0])/max_wh),inc_y((br[1]-tl[1])/max_wh);
477         pos1[0] = pos2[0] = tl[0]; pos1[1] = tl[1]; pos2[1] = br[1];
478         for (x = 0; x < max_wh; x++, pos1[0] += inc_x, pos2[0] = pos1[0], pos1[1]+=inc_y, pos2[1]-=inc_y)
479         {
480                 src_rect.expand(transform(pos1, &dist, &along)); UPDATE_DIST;
481                 src_rect.expand(transform(pos2, &dist, &along)); UPDATE_DIST;
482         }
483
484 #if 0
485         // look at each blinepoint
486         std::vector<synfig::BLinePoint>::const_iterator iter;
487         for (iter=bline.begin(); iter!=bline.end(); iter++)
488                 src_rect.expand(transform(iter->get_vertex()+origin, &dist, &along)); UPDATE_DIST;
489 #endif
490
491         Point src_tl(src_rect.get_min());
492         Point src_br(src_rect.get_max());
493
494         Vector ab((end_point - start_point).norm());
495         Angle::tan ab_angle(ab[1], ab[0]);
496
497         Real used_length = max_along - min_along;
498         Real render_width = max_dist - min_dist;
499
500         int src_w = (abs(used_length*Angle::cos(ab_angle).get()) +
501                                  abs(render_width*Angle::sin(ab_angle).get())) / abs(pw);
502         int src_h = (abs(used_length*Angle::sin(ab_angle).get()) +
503                                  abs(render_width*Angle::cos(ab_angle).get())) / abs(ph);
504
505         Real src_pw((src_br[0] - src_tl[0]) / src_w);
506         Real src_ph((src_br[1] - src_tl[1]) / src_h);
507
508         if (src_pw > abs(pw))
509         {
510                 src_w = int((src_br[0] - src_tl[0]) / abs(pw));
511                 src_pw = (src_br[0] - src_tl[0]) / src_w;
512         }
513
514         if (src_ph > abs(ph))
515         {
516                 src_h = int((src_br[1] - src_tl[1]) / abs(ph));
517                 src_ph = (src_br[1] - src_tl[1]) / src_h;
518         }
519
520 #define MAXPIX 10000
521         if (src_w > MAXPIX) src_w = MAXPIX;
522         if (src_h > MAXPIX) src_h = MAXPIX;
523
524         // this is an attempt to remove artifacts around tile edges - the
525         // cubic interpolation uses at most 2 pixels either side of the
526         // target pixel, so add an extra 2 pixels around the tile on all
527         // sides
528         src_tl -= (Point(src_pw,src_ph)*2);
529         src_br += (Point(src_pw,src_ph)*2);
530         src_w += 4;
531         src_h += 4;
532         src_pw = (src_br[0] - src_tl[0]) / src_w;
533         src_ph = (src_br[1] - src_tl[1]) / src_h;
534
535         // set up a renddesc for the context to render
536         RendDesc src_desc(renddesc);
537         src_desc.clear_flags();
538         src_desc.set_tl(src_tl);
539         src_desc.set_br(src_br);
540         src_desc.set_wh(src_w, src_h);
541
542         // render the context onto a new surface
543         Surface source;
544         source.set_wh(src_w,src_h);
545         if(!context.accelerated_render(&source,quality,src_desc,&stageone))
546                 return false;
547
548         float u,v;
549         Point pos, tmp;
550
551         surface->set_wh(w,h);
552         surface->clear();
553
554         if(quality<=4)                          // CUBIC
555                 for(y=0,pos[1]=tl[1];y<h;y++,pos[1]+=ph)
556                 {
557                         for(x=0,pos[0]=tl[0];x<w;x++,pos[0]+=pw)
558                         {
559                                 tmp=transform(pos);
560                                 u=(tmp[0]-src_tl[0])/src_pw;
561                                 v=(tmp[1]-src_tl[1])/src_ph;
562                                 if(u<0 || v<0 || u>=src_w || v>=src_h || isnan(u) || isnan(v))
563                                         (*surface)[y][x]=context.get_color(tmp);
564                                 else
565                                         (*surface)[y][x]=source.cubic_sample(u,v);
566                         }
567                         if((y&31)==0 && cb && !stagetwo.amount_complete(y,h)) return false;
568                 }
569         else if (quality<=6)            // INTERPOLATION_LINEAR
570                 for(y=0,pos[1]=tl[1];y<h;y++,pos[1]+=ph)
571                 {
572                         for(x=0,pos[0]=tl[0];x<w;x++,pos[0]+=pw)
573                         {
574                                 tmp=transform(pos);
575                                 u=(tmp[0]-src_tl[0])/src_pw;
576                                 v=(tmp[1]-src_tl[1])/src_ph;
577                                 if(u<0 || v<0 || u>=src_w || v>=src_h || isnan(u) || isnan(v))
578                                         (*surface)[y][x]=context.get_color(tmp);
579                                 else
580                                         (*surface)[y][x]=source.linear_sample(u,v);
581                         }
582                         if((y&31)==0 && cb && !stagetwo.amount_complete(y,h)) return false;
583                 }
584         else                                            // NEAREST_NEIGHBOR
585                 for(y=0,pos[1]=tl[1];y<h;y++,pos[1]+=ph)
586                 {
587                         for(x=0,pos[0]=tl[0];x<w;x++,pos[0]+=pw)
588                         {
589                                 tmp=transform(pos);
590                                 u=(tmp[0]-src_tl[0])/src_pw;
591                                 v=(tmp[1]-src_tl[1])/src_ph;
592                                 if(u<0 || v<0 || u>=src_w || v>=src_h || isnan(u) || isnan(v))
593                                         (*surface)[y][x]=context.get_color(tmp);
594                                 else
595                                         (*surface)[y][x]=source[floor_to_int(v)][floor_to_int(u)];
596                         }
597                         if((y&31)==0 && cb && !stagetwo.amount_complete(y,h)) return false;
598                 }
599
600         // Mark our progress as finished
601         if(cb && !cb->amount_complete(10000,10000))
602                 return false;
603
604         return true;
605 }