Fix bugs in previous commit that caused FTBFS in synfig and ETL FTBFS with older...
[synfig.git] / synfig-core / tags / stable / src / modules / mod_gradient / curvegradient.cpp
1 /* === S Y N F I G ========================================================= */
2 /*!     \file curvegradient.cpp
3 **      \brief Implementation of the "Curve Gradient" 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 "curvegradient.h"
36
37 #include <synfig/string.h>
38 #include <synfig/time.h>
39 #include <synfig/context.h>
40 #include <synfig/paramdesc.h>
41 #include <synfig/renddesc.h>
42 #include <synfig/surface.h>
43 #include <synfig/value.h>
44 #include <synfig/valuenode.h>
45 #include <ETL/bezier>
46 #include <ETL/hermite>
47 #include <ETL/calculus>
48
49 #endif
50
51 /* === M A C R O S ========================================================= */
52
53 /* === G L O B A L S ======================================================= */
54
55 SYNFIG_LAYER_INIT(CurveGradient);
56 SYNFIG_LAYER_SET_NAME(CurveGradient,"curve_gradient");
57 SYNFIG_LAYER_SET_LOCAL_NAME(CurveGradient,N_("Curve Gradient"));
58 SYNFIG_LAYER_SET_CATEGORY(CurveGradient,N_("Gradients"));
59 SYNFIG_LAYER_SET_VERSION(CurveGradient,"0.0");
60 SYNFIG_LAYER_SET_CVS_ID(CurveGradient,"$Id$");
61
62 /* === P R O C E D U R E S ================================================= */
63
64 inline float calculate_distance(const synfig::BLinePoint& a,const synfig::BLinePoint& b)
65 {
66 #if 1
67         const Point& c1(a.get_vertex());
68         const Point c2(a.get_vertex()+a.get_tangent2()/3);
69         const Point c3(b.get_vertex()-b.get_tangent1()/3);
70         const Point& c4(b.get_vertex());
71         return (c1-c2).mag()+(c2-c3).mag()+(c3-c4).mag();
72 #else
73 #endif
74 }
75
76 inline float calculate_distance(const std::vector<synfig::BLinePoint>& bline, bool bline_loop)
77 {
78         std::vector<synfig::BLinePoint>::const_iterator iter,next,ret;
79         std::vector<synfig::BLinePoint>::const_iterator end(bline.end());
80
81         float dist(0);
82
83         if (bline.empty()) return dist;
84
85         next=bline.begin();
86
87         if(bline_loop)
88                 iter=--bline.end();
89         else
90                 iter=next++;
91
92         for(;next!=end;iter=next++)
93         {
94                 // Setup the curve
95                 etl::hermite<Vector> curve(
96                         iter->get_vertex(),
97                         next->get_vertex(),
98                         iter->get_tangent2(),
99                         next->get_tangent1());
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(bool fast, const std::vector<synfig::BLinePoint>& bline,const Point& p,float& t,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         float best_pos(0);
123         etl::hermite<Vector> best_curve;
124
125         if(loop)
126                 iter=--bline.end();
127         else
128                 iter=next++;
129
130         Point bp;
131
132         for(;next!=end;iter=next++)
133         {
134                 // Setup the curve
135                 etl::hermite<Vector> curve(
136                         iter->get_vertex(),
137                         next->get_vertex(),
138                         iter->get_tangent2(),
139                         next->get_tangent1());
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                 if (fast)
155                 {
156 #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; }
157                         POINT_CHECK(0.0001);
158                         POINT_CHECK((1.0/6.0));
159                         POINT_CHECK((2.0/6.0));
160                         POINT_CHECK((3.0/6.0));
161                         POINT_CHECK((4.0/6.0));
162                         POINT_CHECK((5.0/6.0));
163                         POINT_CHECK(0.9999);
164                 }
165                 else
166                 {
167                         float pos = curve.find_closest(fast, p);
168                         thisdist=(curve(pos)-p).mag_squared();
169                         if(thisdist<dist)
170                         {
171                                 ret=iter;
172                                 dist=thisdist;
173                                 best_bline_dist=total_bline_dist;
174                                 best_bline_len=len;
175                                 best_curve=curve;
176                                 best_pos = pos;
177                         }
178                 }
179
180                 total_bline_dist+=len;
181         }
182
183         t = best_pos;
184
185         if(bline_dist_ret)
186         {
187                 //! \todo is this a redundant call to find_closest()?
188                 // note bline_dist_ret is null except when 'perpendicular' is true
189                 *bline_dist_ret=best_bline_dist+best_curve.find_distance(0,best_curve.find_closest(fast, p));
190 //              *bline_dist_ret=best_bline_dist+best_curve.find_closest(fast, p)*best_bline_len;
191         }
192
193         return ret;
194 }
195
196 /* === M E T H O D S ======================================================= */
197
198 inline void
199 CurveGradient::sync()
200 {
201         curve_length_=calculate_distance(bline, bline_loop);
202 }
203
204
205 CurveGradient::CurveGradient():
206         origin(0,0),
207         width(0.25),
208         gradient(Color::black(), Color::white()),
209         loop(false),
210         zigzag(false),
211         perpendicular(false),
212         fast(true)
213 {
214         bline.push_back(BLinePoint());
215         bline.push_back(BLinePoint());
216         bline.push_back(BLinePoint());
217         bline[0].set_vertex(Point(0,1));
218         bline[1].set_vertex(Point(0,-1));
219         bline[2].set_vertex(Point(1,0));
220         bline[0].set_tangent(bline[1].get_vertex()-bline[2].get_vertex()*0.5f);
221         bline[1].set_tangent(bline[2].get_vertex()-bline[0].get_vertex()*0.5f);
222         bline[2].set_tangent(bline[0].get_vertex()-bline[1].get_vertex()*0.5f);
223         bline[0].set_width(1.0f);
224         bline[1].set_width(1.0f);
225         bline[2].set_width(1.0f);
226         bline_loop=true;
227
228         sync();
229 }
230
231 inline Color
232 CurveGradient::color_func(const Point &point_, int quality, float supersample)const
233 {
234         Vector tangent;
235         Vector diff;
236         Point p1;
237         Real thickness;
238         Real dist;
239
240         float perp_dist;
241         bool edge_case = false;
242
243         if(bline.size()==0)
244                 return Color::alpha();
245         else if(bline.size()==1)
246         {
247                 tangent=bline.front().get_tangent1();
248                 p1=bline.front().get_vertex();
249                 thickness=bline.front().get_width();
250         }
251         else
252         {
253                 float t;
254                 Point point(point_-origin);
255
256                 std::vector<synfig::BLinePoint>::const_iterator iter,next;
257
258                 // Figure out the BLinePoints we will be using,
259                 // Taking into account looping.
260                 if(perpendicular)
261                 {
262                         next=find_closest(fast,bline,point,t,bline_loop,&perp_dist);
263                         perp_dist/=curve_length_;
264                 }
265                 else                                    // not perpendicular
266                 {
267                         next=find_closest(fast,bline,point,t,bline_loop);
268                 }
269
270                 iter=next++;
271                 if(next==bline.end()) next=bline.begin();
272
273                 // Setup the curve
274                 etl::hermite<Vector> curve(
275                         iter->get_vertex(),
276                         next->get_vertex(),
277                         iter->get_tangent2(),
278                         next->get_tangent1()
279                         );
280
281                 // Setup the derivative function
282                 etl::derivative<etl::hermite<Vector> > deriv(curve);
283
284                 int search_iterations(7);
285
286                 /*if(quality==0)search_iterations=8;
287                   else if(quality<=2)search_iterations=10;
288                   else if(quality<=4)search_iterations=8;
289                 */
290                 if(perpendicular)
291                 {
292                         if(quality>7)
293                                 search_iterations=4;
294                 }
295                 else                                    // not perpendicular
296                 {
297                         if(quality<=6)search_iterations=7;
298                         else if(quality<=7)search_iterations=6;
299                         else if(quality<=8)search_iterations=5;
300                         else search_iterations=4;
301                 }
302
303                 // Figure out the closest point on the curve
304                 if (fast)
305                         t = curve.find_closest(fast, point,search_iterations);
306
307                 // Calculate our values
308                 p1=curve(t);                     // the closest point on the curve
309                 tangent=deriv(t).norm(); // the unit tangent at that point
310
311                 // if the point we're nearest to is at either end of the
312                 // bline, our distance from the curve is the distance from the
313                 // point on the curve.  we need to know which side of the
314                 // curve we're on, so find the average of the two tangents at
315                 // this point
316                 if (t<0.00001 || t>0.99999)
317                 {
318                         if (t<0.5)
319                         {
320                                 if (iter->get_split_tangent_flag())
321                                 {
322                                         tangent=(iter->get_tangent1().norm()+tangent).norm();
323                                         edge_case=true;
324                                 }
325                         }
326                         else
327                         {
328                                 if (next->get_split_tangent_flag())
329                                 {
330                                         tangent=(next->get_tangent2().norm()+tangent).norm();
331                                         edge_case=true;
332                                 }
333                         }
334                 }
335
336                 if(perpendicular)
337                 {
338                         tangent*=curve_length_;
339                         p1-=tangent*perp_dist;
340                         tangent=-tangent.perp();
341                 }
342                 else                                    // not perpendicular
343                         // the width of the bline at the closest point on the curve
344                         thickness=(next->get_width()-iter->get_width())*t+iter->get_width();
345         }
346
347         if(perpendicular)
348         {
349                 if(quality>7)
350                 {
351                         dist=perp_dist;
352 /*                      diff=tangent.perp();
353                         const Real mag(diff.inv_mag());
354                         supersample=supersample*mag;
355 */
356                         supersample=0;
357                 }
358                 else
359                 {
360                         diff=tangent.perp();
361                         //p1-=diff*0.5;
362                         const Real mag(diff.inv_mag());
363                         supersample=supersample*mag;
364                         diff*=mag*mag;
365                         dist=((point_-origin)*diff-p1*diff);
366                 }
367         }
368         else                                            // not perpendicular
369         {
370                 if (edge_case)
371                 {
372                         diff=(p1-(point_-origin));
373                         if(diff*tangent.perp()<0) diff=-diff;
374                         diff=diff.norm()*thickness*width;
375                 }
376                 else
377                         diff=tangent.perp()*thickness*width;
378
379                 p1-=diff*0.5;
380                 const Real mag(diff.inv_mag());
381                 supersample=supersample*mag;
382                 diff*=mag*mag;
383                 dist=((point_-origin)*diff-p1*diff);
384         }
385
386         if(loop)
387                 dist-=floor(dist);
388
389         if(zigzag)
390         {
391                 dist*=2.0;
392                 supersample*=2.0;
393                 if(dist>1)dist=2.0-dist;
394         }
395
396         if(loop)
397         {
398                 if(dist+supersample*0.5>1.0)
399                 {
400                         float  left(supersample*0.5-(dist-1.0));
401                         float right(supersample*0.5+(dist-1.0));
402                         Color pool(gradient(1.0-(left*0.5),left).premult_alpha()*left/supersample);
403                         if (zigzag) pool+=gradient(1.0-right*0.5,right).premult_alpha()*right/supersample;
404                         else            pool+=gradient(right*0.5,right).premult_alpha()*right/supersample;
405                         return pool.demult_alpha();
406                 }
407                 if(dist-supersample*0.5<0.0)
408                 {
409                         float  left(supersample*0.5-dist);
410                         float right(supersample*0.5+dist);
411                         Color pool(gradient(right*0.5,right).premult_alpha()*right/supersample);
412                         if (zigzag) pool+=gradient(left*0.5,left).premult_alpha()*left/supersample;
413                         else            pool+=gradient(1.0-left*0.5,left).premult_alpha()*left/supersample;
414                         return pool.demult_alpha();
415                 }
416         }
417         return gradient(dist,supersample);
418 }
419
420 float
421 CurveGradient::calc_supersample(const synfig::Point &/*x*/, float pw,float /*ph*/)const
422 {
423         return pw;
424 }
425
426 synfig::Layer::Handle
427 CurveGradient::hit_check(synfig::Context context, const synfig::Point &point)const
428 {
429         if(get_blend_method()==Color::BLEND_STRAIGHT && get_amount()>=0.5)
430                 return const_cast<CurveGradient*>(this);
431         if(get_amount()==0.0)
432                 return context.hit_check(point);
433         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)
434                 return const_cast<CurveGradient*>(this);
435         return context.hit_check(point);
436 }
437
438 bool
439 CurveGradient::set_param(const String & param, const ValueBase &value)
440 {
441
442
443         IMPORT(origin);
444         IMPORT(perpendicular);
445         IMPORT(fast);
446
447         if(param=="bline" && value.get_type()==ValueBase::TYPE_LIST)
448         {
449                 bline=value;
450                 bline_loop=value.get_loop();
451                 sync();
452
453                 return true;
454         }
455
456         IMPORT(width);
457         IMPORT(gradient);
458         IMPORT(loop);
459         IMPORT(zigzag);
460
461         IMPORT_AS(origin,"offset");
462
463         return Layer_Composite::set_param(param,value);
464 }
465
466 ValueBase
467 CurveGradient::get_param(const String & param)const
468 {
469         EXPORT(origin);
470         EXPORT(bline);
471         EXPORT(gradient);
472         EXPORT(loop);
473         EXPORT(zigzag);
474         EXPORT(width);
475         EXPORT(perpendicular);
476         EXPORT(fast);
477
478         EXPORT_NAME();
479         EXPORT_VERSION();
480
481         return Layer_Composite::get_param(param);
482 }
483
484 Layer::Vocab
485 CurveGradient::get_param_vocab()const
486 {
487         Layer::Vocab ret(Layer_Composite::get_param_vocab());
488
489         ret.push_back(ParamDesc("origin")
490                                   .set_local_name(_("Origin")));
491
492         ret.push_back(ParamDesc("width")
493                                   .set_is_distance()
494                                   .set_local_name(_("Width")));
495
496         ret.push_back(ParamDesc("bline")
497                                   .set_local_name(_("Vertices"))
498                                   .set_origin("origin")
499                                   .set_hint("width")
500                                   .set_description(_("A list of BLine Points")));
501
502         ret.push_back(ParamDesc("gradient")
503                                   .set_local_name(_("Gradient")));
504         ret.push_back(ParamDesc("loop")
505                                   .set_local_name(_("Loop")));
506         ret.push_back(ParamDesc("zigzag")
507                                   .set_local_name(_("ZigZag")));
508         ret.push_back(ParamDesc("perpendicular")
509                                   .set_local_name(_("Perpendicular")));
510         ret.push_back(ParamDesc("fast")
511                                   .set_local_name(_("Fast")));
512
513         return ret;
514 }
515
516 Color
517 CurveGradient::get_color(Context context, const Point &point)const
518 {
519         const Color color(color_func(point,0));
520
521         if(get_amount()==1.0 && get_blend_method()==Color::BLEND_STRAIGHT)
522                 return color;
523         else
524                 return Color::blend(color,context.get_color(point),get_amount(),get_blend_method());
525 }
526
527 bool
528 CurveGradient::accelerated_render(Context context,Surface *surface,int quality, const RendDesc &renddesc, ProgressCallback *cb)const
529 {
530         SuperCallback supercb(cb,0,9500,10000);
531
532         if(get_amount()==1.0 && get_blend_method()==Color::BLEND_STRAIGHT)
533         {
534                 surface->set_wh(renddesc.get_w(),renddesc.get_h());
535         }
536         else
537         {
538                 if(!context.accelerated_render(surface,quality,renddesc,&supercb))
539                         return false;
540                 if(get_amount()==0)
541                         return true;
542         }
543
544
545         int x,y;
546
547         Surface::pen pen(surface->begin());
548         const Real pw(renddesc.get_pw()),ph(renddesc.get_ph());
549         Point pos;
550         Point tl(renddesc.get_tl());
551         const int w(surface->get_w());
552         const int h(surface->get_h());
553
554         if(get_amount()==1.0 && get_blend_method()==Color::BLEND_STRAIGHT)
555         {
556                 for(y=0,pos[1]=tl[1];y<h;y++,pen.inc_y(),pen.dec_x(x),pos[1]+=ph)
557                         for(x=0,pos[0]=tl[0];x<w;x++,pen.inc_x(),pos[0]+=pw)
558                                 pen.put_value(color_func(pos,quality,calc_supersample(pos,pw,ph)));
559         }
560         else
561         {
562                 for(y=0,pos[1]=tl[1];y<h;y++,pen.inc_y(),pen.dec_x(x),pos[1]+=ph)
563                         for(x=0,pos[0]=tl[0];x<w;x++,pen.inc_x(),pos[0]+=pw)
564                                 pen.put_value(Color::blend(color_func(pos,quality,calc_supersample(pos,pw,ph)),pen.get_value(),get_amount(),get_blend_method()));
565         }
566
567         // Mark our progress as finished
568         if(cb && !cb->amount_complete(10000,10000))
569                 return false;
570
571         return true;
572 }