marked stable
[synfig.git] / synfig-core / tags / stable / src / modules / mod_gradient / curvegradient.cpp
1 /*! ========================================================================
2 ** Sinfg
3 ** Template File
4 ** $Id: curvegradient.cpp,v 1.2 2005/01/13 06:48:39 darco Exp $
5 **
6 ** Copyright (c) 2002 Robert B. Quattlebaum Jr.
7 **
8 ** This software and associated documentation
9 ** are CONFIDENTIAL and PROPRIETARY property of
10 ** the above-mentioned copyright holder.
11 **
12 ** You may not copy, print, publish, or in any
13 ** other way distribute this software without
14 ** a prior written agreement with
15 ** the copyright holder.
16 **
17 ** === N O T E S ===========================================================
18 **
19 ** ========================================================================= */
20
21 /* === H E A D E R S ======================================================= */
22
23 #ifdef USING_PCH
24 #       include "pch.h"
25 #else
26 #ifdef HAVE_CONFIG_H
27 #       include <config.h>
28 #endif
29
30 #include "curvegradient.h"
31
32 #include <sinfg/string.h>
33 #include <sinfg/time.h>
34 #include <sinfg/context.h>
35 #include <sinfg/paramdesc.h>
36 #include <sinfg/renddesc.h>
37 #include <sinfg/surface.h>
38 #include <sinfg/value.h>
39 #include <sinfg/valuenode.h>
40 #include <ETL/bezier>
41 #include <ETL/hermite>
42 #include <ETL/calculus>
43
44 #endif
45
46 /* === M A C R O S ========================================================= */
47
48 /* === G L O B A L S ======================================================= */
49
50 SINFG_LAYER_INIT(CurveGradient);
51 SINFG_LAYER_SET_NAME(CurveGradient,"curve_gradient");
52 SINFG_LAYER_SET_LOCAL_NAME(CurveGradient,_("Curve Gradient"));
53 SINFG_LAYER_SET_CATEGORY(CurveGradient,_("Gradients"));
54 SINFG_LAYER_SET_VERSION(CurveGradient,"0.0");
55 SINFG_LAYER_SET_CVS_ID(CurveGradient,"$Id: curvegradient.cpp,v 1.2 2005/01/13 06:48:39 darco Exp $");
56
57 /* === P R O C E D U R E S ================================================= */
58
59 inline float calculate_distance(const sinfg::BLinePoint& a,const sinfg::BLinePoint& b)
60 {
61 #if 1
62         const Point& c1(a.get_vertex());
63         const Point c2(a.get_vertex()+a.get_tangent2()/3);
64         const Point c3(b.get_vertex()-b.get_tangent1()/3);
65         const Point& c4(b.get_vertex());
66         return (c1-c2).mag()+(c2-c3).mag()+(c3-c4).mag();
67 #else
68 #endif
69 }
70
71 inline float calculate_distance(const std::vector<sinfg::BLinePoint>& bline)
72 {
73         std::vector<sinfg::BLinePoint>::const_iterator iter,next,ret;
74         std::vector<sinfg::BLinePoint>::const_iterator end(bline.end());
75         
76         float dist(0);
77         
78         next=bline.begin();
79         
80         //if(loop)
81         //      iter=--bline.end();
82         //else
83                 iter=next++;
84         
85         for(;next!=end;iter=next++)
86         {
87                 // Setup the curve
88                 etl::hermite<Vector> curve(
89                         iter->get_vertex(),
90                         next->get_vertex(),
91                         iter->get_tangent2(),
92                         next->get_tangent1()
93                 );
94
95 //              dist+=calculate_distance(*iter,*next);
96                 dist+=curve.length();
97         }
98
99         return dist;
100 }
101
102 std::vector<sinfg::BLinePoint>::const_iterator
103 find_closest(const std::vector<sinfg::BLinePoint>& bline,const Point& p,bool loop=false,float *bline_dist_ret=0)
104 {
105         std::vector<sinfg::BLinePoint>::const_iterator iter,next,ret;
106         std::vector<sinfg::BLinePoint>::const_iterator end(bline.end());
107         
108         ret=bline.end();
109         float dist(100000000000.0);
110         
111         next=bline.begin();
112
113         float best_bline_dist(0);
114         float best_bline_len(0);
115         float total_bline_dist(0);
116         etl::hermite<Vector> best_curve;
117         
118         if(loop)
119                 iter=--bline.end();
120         else
121                 iter=next++;
122
123         Point bp;
124         
125         for(;next!=end;iter=next++)
126         {
127                 // Setup the curve
128                 etl::hermite<Vector> curve(
129                         iter->get_vertex(),
130                         next->get_vertex(),
131                         iter->get_tangent2(),
132                         next->get_tangent1()
133                 );
134
135                 /*
136                 const float t(curve.find_closest(p,6,0.01,0.99));
137                 bp=curve(t);if((bp-p).mag_squared()<dist) { ret=iter; dist=(bp-p).mag_squared(); ret_t=t; }             
138                 */
139
140                 float thisdist(0);
141                 float len(0);
142                 if(bline_dist_ret)
143                 {
144                         //len=calculate_distance(*iter,*next);
145                         len=curve.length();
146                 }
147                 
148 #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; }
149
150                 POINT_CHECK(0.0001);
151                 POINT_CHECK((1.0/6.0));
152                 POINT_CHECK((2.0/6.0));
153                 POINT_CHECK((3.0/6.0));
154                 POINT_CHECK((4.0/6.0));
155                 POINT_CHECK((5.0/6.0));
156                 POINT_CHECK(0.9999);
157
158                 total_bline_dist+=len;
159         }
160
161         if(bline_dist_ret)
162         {
163                 *bline_dist_ret=best_bline_dist+best_curve.find_distance(0,best_curve.find_closest(p));
164 //              *bline_dist_ret=best_bline_dist+best_curve.find_closest(p)*best_bline_len;
165         }
166         
167         return ret;
168 }
169
170 /* === M E T H O D S ======================================================= */
171
172 inline void
173 CurveGradient::sync()
174 {
175         diff=(p2-p1);
176         const Real mag(diff.inv_mag());
177         diff*=mag*mag;
178
179         curve_length_=calculate_distance(bline);
180 }
181
182
183 CurveGradient::CurveGradient():
184         p1(1,1),
185         p2(-1,-1),
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<sinfg::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                         Color pool(gradient(dist,supersample*0.5).premult_alpha()*(1.0-(dist-supersample*0.5)));
347                         pool+=gradient((dist+supersample*0.5)-1.0,supersample*0.5).premult_alpha()*((dist+supersample*0.5)-1.0);
348                         return pool.demult_alpha();
349                 }
350                 if(dist-supersample*0.5<0.0)
351                 {
352                         Color pool(gradient(dist,supersample*0.5).premult_alpha()*(dist+supersample*0.5));
353                         pool+=gradient(1.0-(dist-supersample*0.5),supersample*0.5).premult_alpha()*(-(dist-supersample*0.5));
354                         return pool.demult_alpha();
355                 }
356         }
357         return gradient(dist,supersample);
358 }
359
360 float
361 CurveGradient::calc_supersample(const sinfg::Point &x, float pw,float ph)const
362 {
363 //      return pw/(p2-p1).mag();
364         return pw;
365 }
366
367 sinfg::Layer::Handle
368 CurveGradient::hit_check(sinfg::Context context, const sinfg::Point &point)const
369 {
370         if(get_blend_method()==Color::BLEND_STRAIGHT && get_amount()>=0.5)
371                 return const_cast<CurveGradient*>(this);
372         if(get_amount()==0.0)
373                 return context.hit_check(point);
374         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)
375                 return const_cast<CurveGradient*>(this);
376         return context.hit_check(point);
377 }
378
379 bool
380 CurveGradient::set_param(const String & param, const ValueBase &value)
381 {
382         if(param=="p1" && value.same_as(p1))
383         {
384                 p1=value.get(p1);
385                 sync();
386                 return true;
387         }
388         if(param=="p2" && value.same_as(p2))
389         {
390                 p2=value.get(p2);
391                 sync();
392                 return true;
393         }
394         //IMPORT(p1);
395         //IMPORT(p2);
396         
397         
398         IMPORT(offset);
399         IMPORT(perpendicular);
400
401         if(param=="bline" && value.get_type()==ValueBase::TYPE_LIST)
402         {
403                 bline=value;
404                 bline_loop=value.get_loop();
405                 sync();
406
407                 return true;
408         }
409
410         IMPORT(width);
411         IMPORT(gradient);
412         IMPORT(loop);
413         IMPORT(zigzag);
414         return Layer_Composite::set_param(param,value); 
415 }
416
417 ValueBase
418 CurveGradient::get_param(const String & param)const
419 {
420         EXPORT(offset);
421         EXPORT(bline);
422         EXPORT(p1);
423         EXPORT(p2);
424         EXPORT(gradient);
425         EXPORT(loop);
426         EXPORT(zigzag);
427         EXPORT(width);
428         EXPORT(perpendicular);
429         
430         EXPORT_NAME();
431         EXPORT_VERSION();
432                 
433         return Layer_Composite::get_param(param);       
434 }
435
436 Layer::Vocab
437 CurveGradient::get_param_vocab()const
438 {
439         Layer::Vocab ret(Layer_Composite::get_param_vocab());
440
441                 ret.push_back(ParamDesc("offset")
442                 .set_local_name(_("Offset"))
443         );
444
445         ret.push_back(ParamDesc("width")
446                 .set_is_distance()
447                 .set_local_name(_("Width"))
448         );
449
450         ret.push_back(ParamDesc("bline")
451                 .set_local_name(_("Vertices"))
452                 .set_origin("offset")
453                 .set_scalar("width")
454                 .set_description(_("A list of BLine Points"))
455         );
456
457
458         ret.push_back(ParamDesc("gradient")
459                 .set_local_name(_("Gradient"))
460         );
461         ret.push_back(ParamDesc("loop")
462                 .set_local_name(_("Loop"))
463         );
464         ret.push_back(ParamDesc("zigzag")
465                 .set_local_name(_("ZigZag"))
466         );
467         ret.push_back(ParamDesc("perpendicular")
468                 .set_local_name(_("Perpendicular"))
469         );
470         
471         return ret;
472 }
473
474 Color
475 CurveGradient::get_color(Context context, const Point &point)const
476 {
477         const Color color(color_func(point,0));
478
479         if(get_amount()==1.0 && get_blend_method()==Color::BLEND_STRAIGHT)
480                 return color;
481         else
482                 return Color::blend(color,context.get_color(point),get_amount(),get_blend_method());
483 }
484
485 bool
486 CurveGradient::accelerated_render(Context context,Surface *surface,int quality, const RendDesc &renddesc, ProgressCallback *cb)const
487 {
488         SuperCallback supercb(cb,0,9500,10000);
489
490         if(get_amount()==1.0 && get_blend_method()==Color::BLEND_STRAIGHT)
491         {
492                 surface->set_wh(renddesc.get_w(),renddesc.get_h());
493         }
494         else
495         {
496                 if(!context.accelerated_render(surface,quality,renddesc,&supercb))
497                         return false;
498                 if(get_amount()==0)
499                         return true;
500         }
501
502                 
503         int x,y;
504
505         Surface::pen pen(surface->begin());
506         const Real pw(renddesc.get_pw()),ph(renddesc.get_ph());
507         Point pos;
508         Point tl(renddesc.get_tl());
509         const int w(surface->get_w());
510         const int h(surface->get_h());
511         
512         if(get_amount()==1.0 && get_blend_method()==Color::BLEND_STRAIGHT)
513         {
514                 for(y=0,pos[1]=tl[1];y<h;y++,pen.inc_y(),pen.dec_x(x),pos[1]+=ph)
515                         for(x=0,pos[0]=tl[0];x<w;x++,pen.inc_x(),pos[0]+=pw)
516                                 pen.put_value(color_func(pos,quality,calc_supersample(pos,pw,ph)));
517         }
518         else
519         {
520                 for(y=0,pos[1]=tl[1];y<h;y++,pen.inc_y(),pen.dec_x(x),pos[1]+=ph)
521                         for(x=0,pos[0]=tl[0];x<w;x++,pen.inc_x(),pos[0]+=pw)
522                                 pen.put_value(Color::blend(color_func(pos,quality,calc_supersample(pos,pw,ph)),pen.get_value(),get_amount(),get_blend_method()));
523         }
524
525         // Mark our progress as finished
526         if(cb && !cb->amount_complete(10000,10000))
527                 return false;
528
529         return true;
530 }