Fixed indentation.
[synfig.git] / synfig-core / trunk / src / modules / mod_gradient / curvegradient.cpp
1 /* === S Y N F I G ========================================================= */
2 /*!     \file curvegradient.cpp
3 **      \brief Template Header
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 ** === N O T E S ===========================================================
22 **
23 ** ========================================================================= */
24
25 /* === H E A D E R S ======================================================= */
26
27 #ifdef USING_PCH
28 #       include "pch.h"
29 #else
30 #ifdef HAVE_CONFIG_H
31 #       include <config.h>
32 #endif
33
34 #include "curvegradient.h"
35
36 #include <synfig/string.h>
37 #include <synfig/time.h>
38 #include <synfig/context.h>
39 #include <synfig/paramdesc.h>
40 #include <synfig/renddesc.h>
41 #include <synfig/surface.h>
42 #include <synfig/value.h>
43 #include <synfig/valuenode.h>
44 #include <ETL/bezier>
45 #include <ETL/hermite>
46 #include <ETL/calculus>
47
48 #endif
49
50 /* === M A C R O S ========================================================= */
51
52 /* === G L O B A L S ======================================================= */
53
54 SYNFIG_LAYER_INIT(CurveGradient);
55 SYNFIG_LAYER_SET_NAME(CurveGradient,"curve_gradient");
56 SYNFIG_LAYER_SET_LOCAL_NAME(CurveGradient,_("Curve Gradient"));
57 SYNFIG_LAYER_SET_CATEGORY(CurveGradient,_("Gradients"));
58 SYNFIG_LAYER_SET_VERSION(CurveGradient,"0.0");
59 SYNFIG_LAYER_SET_CVS_ID(CurveGradient,"$Id$");
60
61 /* === P R O C E D U R E S ================================================= */
62
63 inline float calculate_distance(const synfig::BLinePoint& a,const synfig::BLinePoint& b)
64 {
65 #if 1
66         const Point& c1(a.get_vertex());
67         const Point c2(a.get_vertex()+a.get_tangent2()/3);
68         const Point c3(b.get_vertex()-b.get_tangent1()/3);
69         const Point& c4(b.get_vertex());
70         return (c1-c2).mag()+(c2-c3).mag()+(c3-c4).mag();
71 #else
72 #endif
73 }
74
75 inline float calculate_distance(const std::vector<synfig::BLinePoint>& bline)
76 {
77         std::vector<synfig::BLinePoint>::const_iterator iter,next,ret;
78         std::vector<synfig::BLinePoint>::const_iterator end(bline.end());
79
80         float dist(0);
81
82         if (bline.empty()) return dist;
83
84         next=bline.begin();
85
86         //if(loop)
87         //      iter=--bline.end();
88         //else
89         iter=next++;
90
91         for(;next!=end;iter=next++)
92         {
93                 // Setup the curve
94                 etl::hermite<Vector> curve(
95                         iter->get_vertex(),
96                         next->get_vertex(),
97                         iter->get_tangent2(),
98                         next->get_tangent1());
99
100 //              dist+=calculate_distance(*iter,*next);
101                 dist+=curve.length();
102         }
103
104         return dist;
105 }
106
107 std::vector<synfig::BLinePoint>::const_iterator
108 find_closest(const std::vector<synfig::BLinePoint>& bline,const Point& p,bool loop=false,float *bline_dist_ret=0)
109 {
110         std::vector<synfig::BLinePoint>::const_iterator iter,next,ret;
111         std::vector<synfig::BLinePoint>::const_iterator end(bline.end());
112
113         ret=bline.end();
114         float dist(100000000000.0);
115
116         next=bline.begin();
117
118         float best_bline_dist(0);
119         float best_bline_len(0);
120         float total_bline_dist(0);
121         etl::hermite<Vector> best_curve;
122
123         if(loop)
124                 iter=--bline.end();
125         else
126                 iter=next++;
127
128         Point bp;
129
130         for(;next!=end;iter=next++)
131         {
132                 // Setup the curve
133                 etl::hermite<Vector> curve(
134                         iter->get_vertex(),
135                         next->get_vertex(),
136                         iter->get_tangent2(),
137                         next->get_tangent1());
138
139                 /*
140                 const float t(curve.find_closest(p,6,0.01,0.99));
141                 bp=curve(t);if((bp-p).mag_squared()<dist) { ret=iter; dist=(bp-p).mag_squared(); ret_t=t; }
142                 */
143
144                 float thisdist(0);
145                 float len(0);
146                 if(bline_dist_ret)
147                 {
148                         //len=calculate_distance(*iter,*next);
149                         len=curve.length();
150                 }
151
152 #define POINT_CHECK(x) bp=curve(x);     thisdist=(bp-p).mag_squared(); if(thisdist<dist) { ret=iter; dist=thisdist; best_bline_dist=total_bline_dist; best_bline_len=len; best_curve=curve; }
153
154                 POINT_CHECK(0.0001);
155                 POINT_CHECK((1.0/6.0));
156                 POINT_CHECK((2.0/6.0));
157                 POINT_CHECK((3.0/6.0));
158                 POINT_CHECK((4.0/6.0));
159                 POINT_CHECK((5.0/6.0));
160                 POINT_CHECK(0.9999);
161
162                 total_bline_dist+=len;
163         }
164
165         if(bline_dist_ret)
166         {
167                 *bline_dist_ret=best_bline_dist+best_curve.find_distance(0,best_curve.find_closest(p));
168 //              *bline_dist_ret=best_bline_dist+best_curve.find_closest(p)*best_bline_len;
169         }
170
171         return ret;
172 }
173
174 /* === M E T H O D S ======================================================= */
175
176 inline void
177 CurveGradient::sync()
178 {
179         curve_length_=calculate_distance(bline);
180 }
181
182
183 CurveGradient::CurveGradient():
184         offset(0,0),
185         width(0.25),
186         gradient(Color::black(), Color::white()),
187         loop(false),
188         zigzag(false),
189         perpendicular(false)
190 {
191         bline.push_back(BLinePoint());
192         bline.push_back(BLinePoint());
193         bline.push_back(BLinePoint());
194         bline[0].set_vertex(Point(0,1));
195         bline[1].set_vertex(Point(0,-1));
196         bline[2].set_vertex(Point(1,0));
197         bline[0].set_tangent(bline[1].get_vertex()-bline[2].get_vertex()*0.5f);
198         bline[1].set_tangent(bline[2].get_vertex()-bline[0].get_vertex()*0.5f);
199         bline[2].set_tangent(bline[0].get_vertex()-bline[1].get_vertex()*0.5f);
200         bline[0].set_width(1.0f);
201         bline[1].set_width(1.0f);
202         bline[2].set_width(1.0f);
203         bline_loop=true;
204
205         sync();
206 }
207
208 inline Color
209 CurveGradient::color_func(const Point &point_, int quality, float supersample)const
210 {
211         Vector tangent;
212         Vector diff;
213         Point p1;
214         Real thickness;
215         Real dist;
216
217         float perp_dist;
218
219         if(bline.size()==0)
220                 return Color::alpha();
221         else if(bline.size()==1)
222         {
223                 tangent=bline.front().get_tangent1();
224                 p1=bline.front().get_vertex();
225                 thickness=bline.front().get_width();
226         }
227         else
228         {
229                 Point point(point_-offset);
230
231                 std::vector<synfig::BLinePoint>::const_iterator iter,next;
232
233                 // Figure out the BLinePoints we will be using,
234                 // Taking into account looping.
235                 if(perpendicular)
236                 {
237                         next=find_closest(bline,point,bline_loop,&perp_dist);
238                         perp_dist/=curve_length_;
239                 }
240                 else
241                 {
242                         next=find_closest(bline,point,bline_loop);
243                 }
244
245                 iter=next++;
246                 if(next==bline.end()) next=bline.begin();
247
248                 // Setup the curve
249                 etl::hermite<Vector> curve(
250                         iter->get_vertex(),
251                         next->get_vertex(),
252                         iter->get_tangent2(),
253                         next->get_tangent1()
254                         );
255
256                 // Setup the derivative function
257                 etl::derivative<etl::hermite<Vector> > deriv(curve);
258
259                 int search_iterations(7);
260
261                 /*if(quality==0)search_iterations=8;
262                   else if(quality<=2)search_iterations=10;
263                   else if(quality<=4)search_iterations=8;
264                 */
265                 if(!perpendicular)
266                 {
267                         if(quality<=6)search_iterations=7;
268                         else if(quality<=7)search_iterations=6;
269                         else if(quality<=8)search_iterations=5;
270                         else search_iterations=4;
271                 }
272                 else
273                 {
274                         if(quality>7)
275                                 search_iterations=4;
276                 }
277
278                 // Figure out the closest point on the curve
279                 const float t(curve.find_closest(point,search_iterations));
280
281
282                 // Calculate our values
283                 p1=curve(t);
284                 tangent=deriv(t).norm();
285
286                 if(perpendicular)
287                 {
288                         tangent*=curve_length_;
289                         p1-=tangent*perp_dist;
290                         tangent=-tangent.perp();
291                 }
292                 else
293                 {
294                         thickness=(next->get_width()-iter->get_width())*t+iter->get_width();
295                 }
296                 //}
297         }
298
299         if(!perpendicular)
300         {
301                 diff=tangent.perp()*thickness*width;
302                 p1-=diff*0.5;
303                 const Real mag(diff.inv_mag());
304                 supersample=supersample*mag;
305                 diff*=mag*mag;
306                 dist=((point_-offset)*diff-p1*diff);
307         }
308         else
309         {
310                 if(quality>7)
311                 {
312                         dist=perp_dist;
313 /*                      diff=tangent.perp();
314                         const Real mag(diff.inv_mag());
315                         supersample=supersample*mag;
316 */
317                         supersample=0;
318                 }
319                 else
320                 {
321                         diff=tangent.perp();
322                         //p1-=diff*0.5;
323                         const Real mag(diff.inv_mag());
324                         supersample=supersample*mag;
325                         diff*=mag*mag;
326                         dist=((point_-offset)*diff-p1*diff);
327                 }
328         }
329
330         if(loop)
331                 dist-=floor(dist);
332
333         if(zigzag)
334         {
335                 dist*=2.0;
336                 supersample*=2.0;
337                 if(dist>1)dist=2.0-dist;
338         }
339
340         if(loop)
341         {
342                 if(dist+supersample*0.5>1.0)
343                 {
344                         float  left(supersample*0.5-(dist-1.0));
345                         float right(supersample*0.5+(dist-1.0));
346                         Color pool(gradient(1.0-(left*0.5),left).premult_alpha()*left/supersample);
347                         if (zigzag) pool+=gradient(1.0-right*0.5,right).premult_alpha()*right/supersample;
348                         else            pool+=gradient(right*0.5,right).premult_alpha()*right/supersample;
349                         return pool.demult_alpha();
350                 }
351                 if(dist-supersample*0.5<0.0)
352                 {
353                         float  left(supersample*0.5-dist);
354                         float right(supersample*0.5+dist);
355                         Color pool(gradient(right*0.5,right).premult_alpha()*right/supersample);
356                         if (zigzag) pool+=gradient(left*0.5,left).premult_alpha()*left/supersample;
357                         else            pool+=gradient(1.0-left*0.5,left).premult_alpha()*left/supersample;
358                         return pool.demult_alpha();
359                 }
360         }
361         return gradient(dist,supersample);
362 }
363
364 float
365 CurveGradient::calc_supersample(const synfig::Point &x, float pw,float ph)const
366 {
367         return pw;
368 }
369
370 synfig::Layer::Handle
371 CurveGradient::hit_check(synfig::Context context, const synfig::Point &point)const
372 {
373         if(get_blend_method()==Color::BLEND_STRAIGHT && get_amount()>=0.5)
374                 return const_cast<CurveGradient*>(this);
375         if(get_amount()==0.0)
376                 return context.hit_check(point);
377         if((get_blend_method()==Color::BLEND_STRAIGHT || get_blend_method()==Color::BLEND_COMPOSITE|| get_blend_method()==Color::BLEND_ONTO) && color_func(point).get_a()>0.5)
378                 return const_cast<CurveGradient*>(this);
379         return context.hit_check(point);
380 }
381
382 bool
383 CurveGradient::set_param(const String & param, const ValueBase &value)
384 {
385
386
387         IMPORT(offset);
388         IMPORT(perpendicular);
389
390         if(param=="bline" && value.get_type()==ValueBase::TYPE_LIST)
391         {
392                 bline=value;
393                 bline_loop=value.get_loop();
394                 sync();
395
396                 return true;
397         }
398
399         IMPORT(width);
400         IMPORT(gradient);
401         IMPORT(loop);
402         IMPORT(zigzag);
403         return Layer_Composite::set_param(param,value);
404 }
405
406 ValueBase
407 CurveGradient::get_param(const String & param)const
408 {
409         EXPORT(offset);
410         EXPORT(bline);
411         EXPORT(gradient);
412         EXPORT(loop);
413         EXPORT(zigzag);
414         EXPORT(width);
415         EXPORT(perpendicular);
416
417         EXPORT_NAME();
418         EXPORT_VERSION();
419
420         return Layer_Composite::get_param(param);
421 }
422
423 Layer::Vocab
424 CurveGradient::get_param_vocab()const
425 {
426         Layer::Vocab ret(Layer_Composite::get_param_vocab());
427
428         ret.push_back(ParamDesc("offset")
429                                   .set_local_name(_("Offset")));
430
431         ret.push_back(ParamDesc("width")
432                                   .set_is_distance()
433                                   .set_local_name(_("Width")));
434
435         ret.push_back(ParamDesc("bline")
436                                   .set_local_name(_("Vertices"))
437                                   .set_origin("offset")
438                                   .set_scalar("width")
439                                   .set_description(_("A list of BLine Points")));
440
441         ret.push_back(ParamDesc("gradient")
442                                   .set_local_name(_("Gradient")));
443         ret.push_back(ParamDesc("loop")
444                                   .set_local_name(_("Loop")));
445         ret.push_back(ParamDesc("zigzag")
446                                   .set_local_name(_("ZigZag")));
447         ret.push_back(ParamDesc("perpendicular")
448                                   .set_local_name(_("Perpendicular")));
449
450         return ret;
451 }
452
453 Color
454 CurveGradient::get_color(Context context, const Point &point)const
455 {
456         const Color color(color_func(point,0));
457
458         if(get_amount()==1.0 && get_blend_method()==Color::BLEND_STRAIGHT)
459                 return color;
460         else
461                 return Color::blend(color,context.get_color(point),get_amount(),get_blend_method());
462 }
463
464 bool
465 CurveGradient::accelerated_render(Context context,Surface *surface,int quality, const RendDesc &renddesc, ProgressCallback *cb)const
466 {
467         SuperCallback supercb(cb,0,9500,10000);
468
469         if(get_amount()==1.0 && get_blend_method()==Color::BLEND_STRAIGHT)
470         {
471                 surface->set_wh(renddesc.get_w(),renddesc.get_h());
472         }
473         else
474         {
475                 if(!context.accelerated_render(surface,quality,renddesc,&supercb))
476                         return false;
477                 if(get_amount()==0)
478                         return true;
479         }
480
481
482         int x,y;
483
484         Surface::pen pen(surface->begin());
485         const Real pw(renddesc.get_pw()),ph(renddesc.get_ph());
486         Point pos;
487         Point tl(renddesc.get_tl());
488         const int w(surface->get_w());
489         const int h(surface->get_h());
490
491         if(get_amount()==1.0 && get_blend_method()==Color::BLEND_STRAIGHT)
492         {
493                 for(y=0,pos[1]=tl[1];y<h;y++,pen.inc_y(),pen.dec_x(x),pos[1]+=ph)
494                         for(x=0,pos[0]=tl[0];x<w;x++,pen.inc_x(),pos[0]+=pw)
495                                 pen.put_value(color_func(pos,quality,calc_supersample(pos,pw,ph)));
496         }
497         else
498         {
499                 for(y=0,pos[1]=tl[1];y<h;y++,pen.inc_y(),pen.dec_x(x),pos[1]+=ph)
500                         for(x=0,pos[0]=tl[0];x<w;x++,pen.inc_x(),pos[0]+=pw)
501                                 pen.put_value(Color::blend(color_func(pos,quality,calc_supersample(pos,pw,ph)),pen.get_value(),get_amount(),get_blend_method()));
502         }
503
504         // Mark our progress as finished
505         if(cb && !cb->amount_complete(10000,10000))
506                 return false;
507
508         return true;
509 }