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