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