Added missing $Id$ keyword to the header.
[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         diff=(p2-p1);
182         const Real mag(diff.inv_mag());
183         diff*=mag*mag;
184
185         curve_length_=calculate_distance(bline);
186 }
187
188
189 CurveGradient::CurveGradient():
190         p1(1,1),
191         p2(-1,-1),
192         offset(0,0),
193         width(0.25),
194         gradient(Color::black(), Color::white()),
195         loop(false),
196         zigzag(false),
197         perpendicular(false)
198 {
199         bline.push_back(BLinePoint());
200         bline.push_back(BLinePoint());
201         bline.push_back(BLinePoint());
202         bline[0].set_vertex(Point(0,1));
203         bline[1].set_vertex(Point(0,-1));
204         bline[2].set_vertex(Point(1,0));
205         bline[0].set_tangent(bline[1].get_vertex()-bline[2].get_vertex()*0.5f);
206         bline[1].set_tangent(bline[2].get_vertex()-bline[0].get_vertex()*0.5f);
207         bline[2].set_tangent(bline[0].get_vertex()-bline[1].get_vertex()*0.5f);
208         bline[0].set_width(1.0f);
209         bline[1].set_width(1.0f);
210         bline[2].set_width(1.0f);
211         bline_loop=true;
212
213         sync();
214 }
215
216 inline Color
217 CurveGradient::color_func(const Point &point_, int quality, float supersample)const
218 {
219         Vector tangent;
220         Vector diff;
221         Point p1;
222         Real thickness;
223         Real dist;
224
225         float perp_dist;
226
227         if(bline.size()==0)
228                 return Color::alpha();
229         else if(bline.size()==1)
230         {
231                 tangent=bline.front().get_tangent1();
232                 p1=bline.front().get_vertex();
233                 thickness=bline.front().get_width();
234         }
235         else
236         {
237                 Point point(point_-offset);
238
239                 std::vector<synfig::BLinePoint>::const_iterator iter,next;
240
241                 // Figure out the BLinePoints we will be using,
242                 // Taking into account looping.
243                 if(perpendicular)
244                 {
245                         next=find_closest(bline,point,bline_loop,&perp_dist);
246                         perp_dist/=curve_length_;
247                 }
248                 else
249                 {
250                         next=find_closest(bline,point,bline_loop);
251                 }
252                         iter=next++;
253                         if(next==bline.end()) next=bline.begin();
254
255                         // Setup the curve
256                         etl::hermite<Vector> curve(
257                                 iter->get_vertex(),
258                                 next->get_vertex(),
259                                 iter->get_tangent2(),
260                                 next->get_tangent1()
261                         );
262
263                         // Setup the derivative function
264                         etl::derivative<etl::hermite<Vector> > deriv(curve);
265
266                         int search_iterations(7);
267
268                         /*if(quality==0)search_iterations=8;
269                         else if(quality<=2)search_iterations=10;
270                         else if(quality<=4)search_iterations=8;
271                         */
272                         if(!perpendicular)
273                         {
274                                 if(quality<=6)search_iterations=7;
275                                 else if(quality<=7)search_iterations=6;
276                                 else if(quality<=8)search_iterations=5;
277                                 else search_iterations=4;
278                         }
279                         else
280                         {
281                                 if(quality>7)
282                                         search_iterations=4;
283                         }
284
285                         // Figure out the closest point on the curve
286                         const float t(curve.find_closest(point,search_iterations));
287
288
289                         // Calculate our values
290                         p1=curve(t);
291                         tangent=deriv(t).norm();
292
293                         if(perpendicular)
294                         {
295                                 tangent*=curve_length_;
296                                 p1-=tangent*perp_dist;
297                                 tangent=-tangent.perp();
298                         }
299                         else
300                         {
301                                 thickness=(next->get_width()-iter->get_width())*t+iter->get_width();
302                         }
303                 //}
304         }
305
306
307         if(!perpendicular)
308         {
309                         diff=tangent.perp()*thickness*width;
310                         p1-=diff*0.5;
311                         const Real mag(diff.inv_mag());
312                         supersample=supersample*mag;
313                         diff*=mag*mag;
314                         dist=((point_-offset)*diff-p1*diff);
315         }
316         else
317         {
318                 if(quality>7)
319                 {
320                         dist=perp_dist;
321 /*                      diff=tangent.perp();
322                         const Real mag(diff.inv_mag());
323                         supersample=supersample*mag;
324 */
325                         supersample=0;
326                 }
327                 else
328                 {
329                 diff=tangent.perp();
330                 //p1-=diff*0.5;
331                 const Real mag(diff.inv_mag());
332                 supersample=supersample*mag;
333                 diff*=mag*mag;
334                 dist=((point_-offset)*diff-p1*diff);
335                 }
336         }
337
338         if(loop)
339                 dist-=floor(dist);
340
341         if(zigzag)
342         {
343                 dist*=2.0;
344                 supersample*=2.0;
345                 if(dist>1)dist=2.0-dist;
346         }
347
348         if(loop)
349         {
350                 if(dist+supersample*0.5>1.0)
351                 {
352                         float  left(supersample*0.5-(dist-1.0));
353                         float right(supersample*0.5+(dist-1.0));
354                         Color pool(gradient(1.0-(left*0.5),left).premult_alpha()*left/supersample);
355                         if (zigzag) pool+=gradient(1.0-right*0.5,right).premult_alpha()*right/supersample;
356                         else            pool+=gradient(right*0.5,right).premult_alpha()*right/supersample;
357                         return pool.demult_alpha();
358                 }
359                 if(dist-supersample*0.5<0.0)
360                 {
361                         float  left(supersample*0.5-dist);
362                         float right(supersample*0.5+dist);
363                         Color pool(gradient(right*0.5,right).premult_alpha()*right/supersample);
364                         if (zigzag) pool+=gradient(left*0.5,left).premult_alpha()*left/supersample;
365                         else            pool+=gradient(1.0-left*0.5,left).premult_alpha()*left/supersample;
366                         return pool.demult_alpha();
367                 }
368         }
369         return gradient(dist,supersample);
370 }
371
372 float
373 CurveGradient::calc_supersample(const synfig::Point &x, float pw,float ph)const
374 {
375 //      return pw/(p2-p1).mag();
376         return pw;
377 }
378
379 synfig::Layer::Handle
380 CurveGradient::hit_check(synfig::Context context, const synfig::Point &point)const
381 {
382         if(get_blend_method()==Color::BLEND_STRAIGHT && get_amount()>=0.5)
383                 return const_cast<CurveGradient*>(this);
384         if(get_amount()==0.0)
385                 return context.hit_check(point);
386         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)
387                 return const_cast<CurveGradient*>(this);
388         return context.hit_check(point);
389 }
390
391 bool
392 CurveGradient::set_param(const String & param, const ValueBase &value)
393 {
394         if(param=="p1" && value.same_as(p1))
395         {
396                 p1=value.get(p1);
397                 sync();
398                 return true;
399         }
400         if(param=="p2" && value.same_as(p2))
401         {
402                 p2=value.get(p2);
403                 sync();
404                 return true;
405         }
406         //IMPORT(p1);
407         //IMPORT(p2);
408
409
410         IMPORT(offset);
411         IMPORT(perpendicular);
412
413         if(param=="bline" && value.get_type()==ValueBase::TYPE_LIST)
414         {
415                 bline=value;
416                 bline_loop=value.get_loop();
417                 sync();
418
419                 return true;
420         }
421
422         IMPORT(width);
423         IMPORT(gradient);
424         IMPORT(loop);
425         IMPORT(zigzag);
426         return Layer_Composite::set_param(param,value);
427 }
428
429 ValueBase
430 CurveGradient::get_param(const String & param)const
431 {
432         EXPORT(offset);
433         EXPORT(bline);
434         EXPORT(p1);
435         EXPORT(p2);
436         EXPORT(gradient);
437         EXPORT(loop);
438         EXPORT(zigzag);
439         EXPORT(width);
440         EXPORT(perpendicular);
441
442         EXPORT_NAME();
443         EXPORT_VERSION();
444
445         return Layer_Composite::get_param(param);
446 }
447
448 Layer::Vocab
449 CurveGradient::get_param_vocab()const
450 {
451         Layer::Vocab ret(Layer_Composite::get_param_vocab());
452
453                 ret.push_back(ParamDesc("offset")
454                 .set_local_name(_("Offset"))
455         );
456
457         ret.push_back(ParamDesc("width")
458                 .set_is_distance()
459                 .set_local_name(_("Width"))
460         );
461
462         ret.push_back(ParamDesc("bline")
463                 .set_local_name(_("Vertices"))
464                 .set_origin("offset")
465                 .set_scalar("width")
466                 .set_description(_("A list of BLine Points"))
467         );
468
469
470         ret.push_back(ParamDesc("gradient")
471                 .set_local_name(_("Gradient"))
472         );
473         ret.push_back(ParamDesc("loop")
474                 .set_local_name(_("Loop"))
475         );
476         ret.push_back(ParamDesc("zigzag")
477                 .set_local_name(_("ZigZag"))
478         );
479         ret.push_back(ParamDesc("perpendicular")
480                 .set_local_name(_("Perpendicular"))
481         );
482
483         return ret;
484 }
485
486 Color
487 CurveGradient::get_color(Context context, const Point &point)const
488 {
489         const Color color(color_func(point,0));
490
491         if(get_amount()==1.0 && get_blend_method()==Color::BLEND_STRAIGHT)
492                 return color;
493         else
494                 return Color::blend(color,context.get_color(point),get_amount(),get_blend_method());
495 }
496
497 bool
498 CurveGradient::accelerated_render(Context context,Surface *surface,int quality, const RendDesc &renddesc, ProgressCallback *cb)const
499 {
500         SuperCallback supercb(cb,0,9500,10000);
501
502         if(get_amount()==1.0 && get_blend_method()==Color::BLEND_STRAIGHT)
503         {
504                 surface->set_wh(renddesc.get_w(),renddesc.get_h());
505         }
506         else
507         {
508                 if(!context.accelerated_render(surface,quality,renddesc,&supercb))
509                         return false;
510                 if(get_amount()==0)
511                         return true;
512         }
513
514
515         int x,y;
516
517         Surface::pen pen(surface->begin());
518         const Real pw(renddesc.get_pw()),ph(renddesc.get_ph());
519         Point pos;
520         Point tl(renddesc.get_tl());
521         const int w(surface->get_w());
522         const int h(surface->get_h());
523
524         if(get_amount()==1.0 && get_blend_method()==Color::BLEND_STRAIGHT)
525         {
526                 for(y=0,pos[1]=tl[1];y<h;y++,pen.inc_y(),pen.dec_x(x),pos[1]+=ph)
527                         for(x=0,pos[0]=tl[0];x<w;x++,pen.inc_x(),pos[0]+=pw)
528                                 pen.put_value(color_func(pos,quality,calc_supersample(pos,pw,ph)));
529         }
530         else
531         {
532                 for(y=0,pos[1]=tl[1];y<h;y++,pen.inc_y(),pen.dec_x(x),pos[1]+=ph)
533                         for(x=0,pos[0]=tl[0];x<w;x++,pen.inc_x(),pos[0]+=pw)
534                                 pen.put_value(Color::blend(color_func(pos,quality,calc_supersample(pos,pw,ph)),pen.get_value(),get_amount(),get_blend_method()));
535         }
536
537         // Mark our progress as finished
538         if(cb && !cb->amount_complete(10000,10000))
539                 return false;
540
541         return true;
542 }