Deleting all the verticies from a Curve Gradient used to cause a crash. This stops...
[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 **      \legal
6 **      Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
7 **
8 **      This package is free software; you can redistribute it and/or
9 **      modify it under the terms of the GNU General Public License as
10 **      published by the Free Software Foundation; either version 2 of
11 **      the License, or (at your option) any later version.
12 **
13 **      This package is distributed in the hope that it will be useful,
14 **      but WITHOUT ANY WARRANTY; without even the implied warranty of
15 **      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 **      General Public License for more details.
17 **      \endlegal
18 **
19 ** === N O T E S ===========================================================
20 **
21 ** ========================================================================= */
22
23 /* === H E A D E R S ======================================================= */
24
25 #ifdef USING_PCH
26 #       include "pch.h"
27 #else
28 #ifdef HAVE_CONFIG_H
29 #       include <config.h>
30 #endif
31
32 #include "curvegradient.h"
33
34 #include <synfig/string.h>
35 #include <synfig/time.h>
36 #include <synfig/context.h>
37 #include <synfig/paramdesc.h>
38 #include <synfig/renddesc.h>
39 #include <synfig/surface.h>
40 #include <synfig/value.h>
41 #include <synfig/valuenode.h>
42 #include <ETL/bezier>
43 #include <ETL/hermite>
44 #include <ETL/calculus>
45
46 #endif
47
48 /* === M A C R O S ========================================================= */
49
50 /* === G L O B A L S ======================================================= */
51
52 SYNFIG_LAYER_INIT(CurveGradient);
53 SYNFIG_LAYER_SET_NAME(CurveGradient,"curve_gradient");
54 SYNFIG_LAYER_SET_LOCAL_NAME(CurveGradient,_("Curve Gradient"));
55 SYNFIG_LAYER_SET_CATEGORY(CurveGradient,_("Gradients"));
56 SYNFIG_LAYER_SET_VERSION(CurveGradient,"0.0");
57 SYNFIG_LAYER_SET_CVS_ID(CurveGradient,"$Id$");
58
59 /* === P R O C E D U R E S ================================================= */
60
61 inline float calculate_distance(const synfig::BLinePoint& a,const synfig::BLinePoint& b)
62 {
63 #if 1
64         const Point& c1(a.get_vertex());
65         const Point c2(a.get_vertex()+a.get_tangent2()/3);
66         const Point c3(b.get_vertex()-b.get_tangent1()/3);
67         const Point& c4(b.get_vertex());
68         return (c1-c2).mag()+(c2-c3).mag()+(c3-c4).mag();
69 #else
70 #endif
71 }
72
73 inline float calculate_distance(const std::vector<synfig::BLinePoint>& bline)
74 {
75         std::vector<synfig::BLinePoint>::const_iterator iter,next,ret;
76         std::vector<synfig::BLinePoint>::const_iterator end(bline.end());
77
78         float dist(0);
79
80         if (bline.empty()) return dist;
81
82         next=bline.begin();
83
84         //if(loop)
85         //      iter=--bline.end();
86         //else
87                 iter=next++;
88
89         for(;next!=end;iter=next++)
90         {
91                 // Setup the curve
92                 etl::hermite<Vector> curve(
93                         iter->get_vertex(),
94                         next->get_vertex(),
95                         iter->get_tangent2(),
96                         next->get_tangent1()
97                 );
98
99 //              dist+=calculate_distance(*iter,*next);
100                 dist+=curve.length();
101         }
102
103         return dist;
104 }
105
106 std::vector<synfig::BLinePoint>::const_iterator
107 find_closest(const std::vector<synfig::BLinePoint>& bline,const Point& p,bool loop=false,float *bline_dist_ret=0)
108 {
109         std::vector<synfig::BLinePoint>::const_iterator iter,next,ret;
110         std::vector<synfig::BLinePoint>::const_iterator end(bline.end());
111
112         ret=bline.end();
113         float dist(100000000000.0);
114
115         next=bline.begin();
116
117         float best_bline_dist(0);
118         float best_bline_len(0);
119         float total_bline_dist(0);
120         etl::hermite<Vector> best_curve;
121
122         if(loop)
123                 iter=--bline.end();
124         else
125                 iter=next++;
126
127         Point bp;
128
129         for(;next!=end;iter=next++)
130         {
131                 // Setup the curve
132                 etl::hermite<Vector> curve(
133                         iter->get_vertex(),
134                         next->get_vertex(),
135                         iter->get_tangent2(),
136                         next->get_tangent1()
137                 );
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         diff=(p2-p1);
180         const Real mag(diff.inv_mag());
181         diff*=mag*mag;
182
183         curve_length_=calculate_distance(bline);
184 }
185
186
187 CurveGradient::CurveGradient():
188         p1(1,1),
189         p2(-1,-1),
190         offset(0,0),
191         width(0.25),
192         gradient(Color::black(), Color::white()),
193         loop(false),
194         zigzag(false),
195         perpendicular(false)
196 {
197         bline.push_back(BLinePoint());
198         bline.push_back(BLinePoint());
199         bline.push_back(BLinePoint());
200         bline[0].set_vertex(Point(0,1));
201         bline[1].set_vertex(Point(0,-1));
202         bline[2].set_vertex(Point(1,0));
203         bline[0].set_tangent(bline[1].get_vertex()-bline[2].get_vertex()*0.5f);
204         bline[1].set_tangent(bline[2].get_vertex()-bline[0].get_vertex()*0.5f);
205         bline[2].set_tangent(bline[0].get_vertex()-bline[1].get_vertex()*0.5f);
206         bline[0].set_width(1.0f);
207         bline[1].set_width(1.0f);
208         bline[2].set_width(1.0f);
209         bline_loop=true;
210
211         sync();
212 }
213
214 inline Color
215 CurveGradient::color_func(const Point &point_, int quality, float supersample)const
216 {
217         Vector tangent;
218         Vector diff;
219         Point p1;
220         Real thickness;
221         Real dist;
222
223         float perp_dist;
224
225         if(bline.size()==0)
226                 return Color::alpha();
227         else if(bline.size()==1)
228         {
229                 tangent=bline.front().get_tangent1();
230                 p1=bline.front().get_vertex();
231                 thickness=bline.front().get_width();
232         }
233         else
234         {
235                 Point point(point_-offset);
236
237                 std::vector<synfig::BLinePoint>::const_iterator iter,next;
238
239                 // Figure out the BLinePoints we will be using,
240                 // Taking into account looping.
241                 if(perpendicular)
242                 {
243                         next=find_closest(bline,point,bline_loop,&perp_dist);
244                         perp_dist/=curve_length_;
245                 }
246                 else
247                 {
248                         next=find_closest(bline,point,bline_loop);
249                 }
250                         iter=next++;
251                         if(next==bline.end()) next=bline.begin();
252
253                         // Setup the curve
254                         etl::hermite<Vector> curve(
255                                 iter->get_vertex(),
256                                 next->get_vertex(),
257                                 iter->get_tangent2(),
258                                 next->get_tangent1()
259                         );
260
261                         // Setup the derivative function
262                         etl::derivative<etl::hermite<Vector> > deriv(curve);
263
264                         int search_iterations(7);
265
266                         /*if(quality==0)search_iterations=8;
267                         else if(quality<=2)search_iterations=10;
268                         else if(quality<=4)search_iterations=8;
269                         */
270                         if(!perpendicular)
271                         {
272                                 if(quality<=6)search_iterations=7;
273                                 else if(quality<=7)search_iterations=6;
274                                 else if(quality<=8)search_iterations=5;
275                                 else search_iterations=4;
276                         }
277                         else
278                         {
279                                 if(quality>7)
280                                         search_iterations=4;
281                         }
282
283                         // Figure out the closest point on the curve
284                         const float t(curve.find_closest(point,search_iterations));
285
286
287                         // Calculate our values
288                         p1=curve(t);
289                         tangent=deriv(t).norm();
290
291                         if(perpendicular)
292                         {
293                                 tangent*=curve_length_;
294                                 p1-=tangent*perp_dist;
295                                 tangent=-tangent.perp();
296                         }
297                         else
298                         {
299                                 thickness=(next->get_width()-iter->get_width())*t+iter->get_width();
300                         }
301                 //}
302         }
303
304
305         if(!perpendicular)
306         {
307                         diff=tangent.perp()*thickness*width;
308                         p1-=diff*0.5;
309                         const Real mag(diff.inv_mag());
310                         supersample=supersample*mag;
311                         diff*=mag*mag;
312                         dist=((point_-offset)*diff-p1*diff);
313         }
314         else
315         {
316                 if(quality>7)
317                 {
318                         dist=perp_dist;
319 /*                      diff=tangent.perp();
320                         const Real mag(diff.inv_mag());
321                         supersample=supersample*mag;
322 */
323                         supersample=0;
324                 }
325                 else
326                 {
327                 diff=tangent.perp();
328                 //p1-=diff*0.5;
329                 const Real mag(diff.inv_mag());
330                 supersample=supersample*mag;
331                 diff*=mag*mag;
332                 dist=((point_-offset)*diff-p1*diff);
333                 }
334         }
335
336         if(loop)
337                 dist-=floor(dist);
338
339         if(zigzag)
340         {
341                 dist*=2.0;
342                 supersample*=2.0;
343                 if(dist>1)dist=2.0-dist;
344         }
345
346         if(loop)
347         {
348                 if(dist+supersample*0.5>1.0)
349                 {
350                         float  left(supersample*0.5-(dist-1.0));
351                         float right(supersample*0.5+(dist-1.0));
352                         Color pool(gradient(1.0-(left*0.5),left).premult_alpha()*left/supersample);
353                         if (zigzag) pool+=gradient(1.0-right*0.5,right).premult_alpha()*right/supersample;
354                         else            pool+=gradient(right*0.5,right).premult_alpha()*right/supersample;
355                         return pool.demult_alpha();
356                 }
357                 if(dist-supersample*0.5<0.0)
358                 {
359                         float  left(supersample*0.5-dist);
360                         float right(supersample*0.5+dist);
361                         Color pool(gradient(right*0.5,right).premult_alpha()*right/supersample);
362                         if (zigzag) pool+=gradient(left*0.5,left).premult_alpha()*left/supersample;
363                         else            pool+=gradient(1.0-left*0.5,left).premult_alpha()*left/supersample;
364                         return pool.demult_alpha();
365                 }
366         }
367         return gradient(dist,supersample);
368 }
369
370 float
371 CurveGradient::calc_supersample(const synfig::Point &x, float pw,float ph)const
372 {
373 //      return pw/(p2-p1).mag();
374         return pw;
375 }
376
377 synfig::Layer::Handle
378 CurveGradient::hit_check(synfig::Context context, const synfig::Point &point)const
379 {
380         if(get_blend_method()==Color::BLEND_STRAIGHT && get_amount()>=0.5)
381                 return const_cast<CurveGradient*>(this);
382         if(get_amount()==0.0)
383                 return context.hit_check(point);
384         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)
385                 return const_cast<CurveGradient*>(this);
386         return context.hit_check(point);
387 }
388
389 bool
390 CurveGradient::set_param(const String & param, const ValueBase &value)
391 {
392         if(param=="p1" && value.same_as(p1))
393         {
394                 p1=value.get(p1);
395                 sync();
396                 return true;
397         }
398         if(param=="p2" && value.same_as(p2))
399         {
400                 p2=value.get(p2);
401                 sync();
402                 return true;
403         }
404         //IMPORT(p1);
405         //IMPORT(p2);
406
407
408         IMPORT(offset);
409         IMPORT(perpendicular);
410
411         if(param=="bline" && value.get_type()==ValueBase::TYPE_LIST)
412         {
413                 bline=value;
414                 bline_loop=value.get_loop();
415                 sync();
416
417                 return true;
418         }
419
420         IMPORT(width);
421         IMPORT(gradient);
422         IMPORT(loop);
423         IMPORT(zigzag);
424         return Layer_Composite::set_param(param,value);
425 }
426
427 ValueBase
428 CurveGradient::get_param(const String & param)const
429 {
430         EXPORT(offset);
431         EXPORT(bline);
432         EXPORT(p1);
433         EXPORT(p2);
434         EXPORT(gradient);
435         EXPORT(loop);
436         EXPORT(zigzag);
437         EXPORT(width);
438         EXPORT(perpendicular);
439
440         EXPORT_NAME();
441         EXPORT_VERSION();
442
443         return Layer_Composite::get_param(param);
444 }
445
446 Layer::Vocab
447 CurveGradient::get_param_vocab()const
448 {
449         Layer::Vocab ret(Layer_Composite::get_param_vocab());
450
451                 ret.push_back(ParamDesc("offset")
452                 .set_local_name(_("Offset"))
453         );
454
455         ret.push_back(ParamDesc("width")
456                 .set_is_distance()
457                 .set_local_name(_("Width"))
458         );
459
460         ret.push_back(ParamDesc("bline")
461                 .set_local_name(_("Vertices"))
462                 .set_origin("offset")
463                 .set_scalar("width")
464                 .set_description(_("A list of BLine Points"))
465         );
466
467
468         ret.push_back(ParamDesc("gradient")
469                 .set_local_name(_("Gradient"))
470         );
471         ret.push_back(ParamDesc("loop")
472                 .set_local_name(_("Loop"))
473         );
474         ret.push_back(ParamDesc("zigzag")
475                 .set_local_name(_("ZigZag"))
476         );
477         ret.push_back(ParamDesc("perpendicular")
478                 .set_local_name(_("Perpendicular"))
479         );
480
481         return ret;
482 }
483
484 Color
485 CurveGradient::get_color(Context context, const Point &point)const
486 {
487         const Color color(color_func(point,0));
488
489         if(get_amount()==1.0 && get_blend_method()==Color::BLEND_STRAIGHT)
490                 return color;
491         else
492                 return Color::blend(color,context.get_color(point),get_amount(),get_blend_method());
493 }
494
495 bool
496 CurveGradient::accelerated_render(Context context,Surface *surface,int quality, const RendDesc &renddesc, ProgressCallback *cb)const
497 {
498         SuperCallback supercb(cb,0,9500,10000);
499
500         if(get_amount()==1.0 && get_blend_method()==Color::BLEND_STRAIGHT)
501         {
502                 surface->set_wh(renddesc.get_w(),renddesc.get_h());
503         }
504         else
505         {
506                 if(!context.accelerated_render(surface,quality,renddesc,&supercb))
507                         return false;
508                 if(get_amount()==0)
509                         return true;
510         }
511
512
513         int x,y;
514
515         Surface::pen pen(surface->begin());
516         const Real pw(renddesc.get_pw()),ph(renddesc.get_ph());
517         Point pos;
518         Point tl(renddesc.get_tl());
519         const int w(surface->get_w());
520         const int h(surface->get_h());
521
522         if(get_amount()==1.0 && get_blend_method()==Color::BLEND_STRAIGHT)
523         {
524                 for(y=0,pos[1]=tl[1];y<h;y++,pen.inc_y(),pen.dec_x(x),pos[1]+=ph)
525                         for(x=0,pos[0]=tl[0];x<w;x++,pen.inc_x(),pos[0]+=pw)
526                                 pen.put_value(color_func(pos,quality,calc_supersample(pos,pw,ph)));
527         }
528         else
529         {
530                 for(y=0,pos[1]=tl[1];y<h;y++,pen.inc_y(),pen.dec_x(x),pos[1]+=ph)
531                         for(x=0,pos[0]=tl[0];x<w;x++,pen.inc_x(),pos[0]+=pw)
532                                 pen.put_value(Color::blend(color_func(pos,quality,calc_supersample(pos,pw,ph)),pen.get_value(),get_amount(),get_blend_method()));
533         }
534
535         // Mark our progress as finished
536         if(cb && !cb->amount_complete(10000,10000))
537                 return false;
538
539         return true;
540 }