Implement accelerated_render for the Curve Warp layer. It can now warp text and...
[synfig.git] / synfig-core / trunk / src / modules / lyr_std / curvewarp.cpp
1 /* === S Y N F I G ========================================================= */
2 /*!     \file curvewarp.cpp
3 **      \brief Implementation of the "Curve Warp" 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 "curvewarp.h"
36
37 #include <synfig/context.h>
38 #include <synfig/paramdesc.h>
39 #include <synfig/surface.h>
40 #include <synfig/valuenode.h>
41 #include <ETL/calculus>
42
43 #endif
44
45 /* === M A C R O S ========================================================= */
46
47 #define FAKE_TANGENT_STEP 0.000001
48
49 /* === G L O B A L S ======================================================= */
50
51 SYNFIG_LAYER_INIT(CurveWarp);
52 SYNFIG_LAYER_SET_NAME(CurveWarp,"curve_warp");
53 SYNFIG_LAYER_SET_LOCAL_NAME(CurveWarp,N_("Curve Warp"));
54 SYNFIG_LAYER_SET_CATEGORY(CurveWarp,N_("Distortions"));
55 SYNFIG_LAYER_SET_VERSION(CurveWarp,"0.0");
56 SYNFIG_LAYER_SET_CVS_ID(CurveWarp,"$Id$");
57
58 /* === P R O C E D U R E S ================================================= */
59
60 inline float calculate_distance(const std::vector<synfig::BLinePoint>& bline)
61 {
62         std::vector<synfig::BLinePoint>::const_iterator iter,next,ret;
63         std::vector<synfig::BLinePoint>::const_iterator end(bline.end());
64
65         float dist(0);
66
67         if (bline.empty()) return dist;
68
69         next=bline.begin();
70         iter=next++;
71
72         for(;next!=end;iter=next++)
73         {
74                 // Setup the curve
75                 etl::hermite<Vector> curve(iter->get_vertex(), next->get_vertex(), iter->get_tangent2(), next->get_tangent1());
76                 dist+=curve.length();
77         }
78
79         return dist;
80 }
81
82 std::vector<synfig::BLinePoint>::const_iterator
83 find_closest_to_bline(bool fast, const std::vector<synfig::BLinePoint>& bline,const Point& p,float& t, float& len, bool& extreme)
84 {
85         std::vector<synfig::BLinePoint>::const_iterator iter,next,ret;
86         std::vector<synfig::BLinePoint>::const_iterator end(bline.end());
87
88         ret=bline.end();
89         float dist(100000000000.0);
90         next=bline.begin();
91         float best_pos(0), best_len(0);
92         etl::hermite<Vector> best_curve;
93         iter=next++;
94         Point bp;
95         float total_len(0);
96         bool first = true, last = false;
97         extreme = false;
98
99         for(;next!=end;iter=next++)
100         {
101                 // Setup the curve
102                 etl::hermite<Vector> curve(iter->get_vertex(), next->get_vertex(), iter->get_tangent2(), next->get_tangent1());
103                 float thisdist(0);
104                 last = false;
105
106                 if (fast)
107                 {
108 #define POINT_CHECK(x) bp=curve(x);     thisdist=(bp-p).mag_squared(); if(thisdist<dist) { extreme = (first&&x<0.01); ret=iter; best_len = total_len; dist=thisdist; best_curve=curve; last=true; best_pos=x;}
109                         POINT_CHECK(0.0001);  POINT_CHECK((1.0/6)); POINT_CHECK((2.0/6)); POINT_CHECK((3.0/6));
110                         POINT_CHECK((4.0/6)); POINT_CHECK((5.0/6)); POINT_CHECK(0.9999);
111                 }
112                 else
113                 {
114                         float pos = curve.find_closest(fast, p);
115                         thisdist=(curve(pos)-p).mag_squared();
116                         if(thisdist<dist)
117                         {
118                                 extreme = (first && pos == 0);
119                                 ret=iter;
120                                 dist=thisdist;
121                                 best_pos = pos;
122                                 best_curve = curve;
123                                 best_len = total_len;
124                                 last = true;
125                         }
126                 }
127                 total_len += curve.length();
128                 first = false;
129         }
130
131         t = best_pos;
132         if (fast)
133         {
134                 len = best_len + best_curve.find_distance(0,best_curve.find_closest(fast, p));
135                 if (last && t > .99) extreme = true;
136         }
137         else
138         {
139                 len = best_len + best_curve.find_distance(0,best_pos);
140                 if (last && t == 1) extreme = true;
141         }
142         return ret;
143 }
144
145 /* === M E T H O D S ======================================================= */
146
147 inline void
148 CurveWarp::sync()
149 {
150         curve_length_=calculate_distance(bline);
151         perp_ = (end_point - start_point).perp().norm();
152 }
153
154 CurveWarp::CurveWarp():
155         origin(0,0),
156         perp_width(1),
157         start_point(-2.5,-0.5),
158         end_point(2.5,-0.3),
159         fast(true)
160 {
161         bline.push_back(BLinePoint());
162         bline.push_back(BLinePoint());
163         bline[0].set_vertex(Point(-2.5,0));
164         bline[1].set_vertex(Point( 2.5,0));
165         bline[0].set_tangent(Point(1,  0.1));
166         bline[1].set_tangent(Point(1, -0.1));
167         bline[0].set_width(1.0f);
168         bline[1].set_width(1.0f);
169
170         sync();
171 }
172
173 inline Point
174 CurveWarp::transform(const Point &point_, int quality, float supersample)const
175 {
176         Vector tangent;
177         Vector diff;
178         Point p1;
179         Real thickness;
180         Real dist;
181         bool edge_case = false;
182         float len(0);
183         bool extreme;
184         float t;
185
186         if(bline.size()==0)
187                 return Point();
188         else if(bline.size()==1)
189         {
190                 tangent=bline.front().get_tangent1();
191                 p1=bline.front().get_vertex();
192                 thickness=bline.front().get_width();
193                 t = 0.5;
194                 extreme = false;
195         }
196         else
197         {
198                 Point point(point_-origin);
199
200                 std::vector<synfig::BLinePoint>::const_iterator iter,next;
201
202                 // Figure out the BLinePoint we will be using,
203                 next=find_closest_to_bline(fast,bline,point,t,len,extreme);
204
205                 iter=next++;
206                 if(next==bline.end()) next=bline.begin();
207
208                 // Setup the curve
209                 etl::hermite<Vector> curve(iter->get_vertex(), next->get_vertex(), iter->get_tangent2(), next->get_tangent1());
210
211                 // Setup the derivative function
212                 etl::derivative<etl::hermite<Vector> > deriv(curve);
213
214                 int search_iterations(7);
215
216                 if(quality<=6)search_iterations=7;
217                 else if(quality<=7)search_iterations=6;
218                 else if(quality<=8)search_iterations=5;
219                 else search_iterations=4;
220
221                 // Figure out the closest point on the curve
222                 if (fast) t = curve.find_closest(fast, point,search_iterations);
223
224                 // Calculate our values
225                 p1=curve(t);                     // the closest point on the curve
226                 tangent=deriv(t);                // the tangent at that point
227
228                 // if the point we're nearest to is at either end of the
229                 // bline, our distance from the curve is the distance from the
230                 // point on the curve.  we need to know which side of the
231                 // curve we're on, so find the average of the two tangents at
232                 // this point
233                 if (t<0.00001 || t>0.99999)
234                 {
235                         bool zero_tangent = (tangent[0] == 0 && tangent[1] == 0);
236
237                         if (t<0.5)
238                         {
239                                 if (iter->get_split_tangent_flag() || zero_tangent)
240                                 {
241                                         // fake the current tangent if we need to
242                                         if (zero_tangent) tangent = curve(FAKE_TANGENT_STEP) - curve(0);
243
244                                         // calculate the other tangent
245                                         Vector other_tangent(iter->get_tangent1());
246                                         if (other_tangent[0] == 0 && other_tangent[1] == 0)
247                                         {
248                                                 // find the previous blinepoint
249                                                 std::vector<synfig::BLinePoint>::const_iterator prev;
250                                                 if (iter != bline.begin()) (prev = iter)--;
251                                                 else prev = iter;
252
253                                                 etl::hermite<Vector> other_curve(prev->get_vertex(), iter->get_vertex(), prev->get_tangent2(), iter->get_tangent1());
254                                                 other_tangent = other_curve(1) - other_curve(1-FAKE_TANGENT_STEP);
255                                         }
256
257                                         // normalise and sum the two tangents
258                                         tangent=(other_tangent.norm()+tangent.norm());
259                                         edge_case=true;
260                                 }
261                         }
262                         else
263                         {
264                                 if (next->get_split_tangent_flag() || zero_tangent)
265                                 {
266                                         // fake the current tangent if we need to
267                                         if (zero_tangent) tangent = curve(1) - curve(1-FAKE_TANGENT_STEP);
268
269                                         // calculate the other tangent
270                                         Vector other_tangent(next->get_tangent2());
271                                         if (other_tangent[0] == 0 && other_tangent[1] == 0)
272                                         {
273                                                 // find the next blinepoint
274                                                 std::vector<synfig::BLinePoint>::const_iterator next2(next);
275                                                 if (++next2 == bline.end())
276                                                         next2 = next;
277
278                                                 etl::hermite<Vector> other_curve(next->get_vertex(), next2->get_vertex(), next->get_tangent2(), next2->get_tangent1());
279                                                 other_tangent = other_curve(FAKE_TANGENT_STEP) - other_curve(0);
280                                         }
281
282                                         // normalise and sum the two tangents
283                                         tangent=(other_tangent.norm()+tangent.norm());
284                                         edge_case=true;
285                                 }
286                         }
287                 }
288                 tangent = tangent.norm();
289
290                 // the width of the bline at the closest point on the curve
291                 thickness=(next->get_width()-iter->get_width())*t+iter->get_width();
292         }
293
294         if (extreme)
295         {
296                 Vector tangent;
297
298                 if (t < 0.5)
299                 {
300                         std::vector<synfig::BLinePoint>::const_iterator iter(bline.begin());
301                         tangent = iter->get_tangent1().norm();
302                         len = 0;
303                 }
304                 else
305                 {
306                         std::vector<synfig::BLinePoint>::const_iterator iter(--bline.end());
307                         tangent = iter->get_tangent2().norm();
308                         len = curve_length_;
309                 }
310                 diff = tangent.perp()*thickness*perp_width;
311                 len += (point_-origin - p1)*tangent;
312         }
313         else if (edge_case)
314         {
315                 diff=(p1-(point_-origin));
316                 if(diff*tangent.perp()<0) diff=-diff;
317                 diff=diff.norm()*thickness*perp_width;
318         }
319         else
320                 diff=tangent.perp()*thickness*perp_width;
321
322         const Real mag(diff.inv_mag());
323         supersample=supersample*mag;
324         diff*=mag*mag;
325         dist=(point_-origin - p1)*diff;
326         len /= curve_length_;
327         return (start_point + (end_point - start_point) * len) + perp_ * dist;
328 }
329
330 float
331 CurveWarp::calc_supersample(const synfig::Point &/*x*/, float pw,float /*ph*/)const
332 {
333         return pw;
334 }
335
336 synfig::Layer::Handle
337 CurveWarp::hit_check(synfig::Context context, const synfig::Point &point)const
338 {
339         return context.hit_check(transform(point));
340 }
341
342 bool
343 CurveWarp::set_param(const String & param, const ValueBase &value)
344 {
345         IMPORT(origin);
346         IMPORT(start_point);
347         IMPORT(end_point);
348         IMPORT(fast);
349         IMPORT(perp_width);
350
351         if(param=="bline" && value.get_type()==ValueBase::TYPE_LIST)
352         {
353                 bline=value;
354                 sync();
355
356                 return true;
357         }
358
359         IMPORT_AS(origin,"offset");
360
361         return false;
362 }
363
364 ValueBase
365 CurveWarp::get_param(const String & param)const
366 {
367         EXPORT(origin);
368         EXPORT(start_point);
369         EXPORT(end_point);
370         EXPORT(bline);
371         EXPORT(fast);
372         EXPORT(perp_width);
373
374         EXPORT_NAME();
375         EXPORT_VERSION();
376
377         return ValueBase();
378 }
379
380 Layer::Vocab
381 CurveWarp::get_param_vocab()const
382 {
383         Layer::Vocab ret;
384
385         ret.push_back(ParamDesc("origin")
386                                   .set_local_name(_("Origin")));
387
388         ret.push_back(ParamDesc("perp_width")
389                                   .set_local_name(_("Width"))
390                                   .set_origin("start_point"));
391
392         ret.push_back(ParamDesc("start_point")
393                                   .set_local_name(_("Start Point"))
394                                   .set_connect("end_point"));
395
396         ret.push_back(ParamDesc("end_point")
397                                   .set_local_name(_("End Point")));
398
399         ret.push_back(ParamDesc("bline")
400                                   .set_local_name(_("Vertices"))
401                                   .set_origin("origin")
402                                   .set_hint("width")
403                                   .set_description(_("A list of BLine Points")));
404
405         ret.push_back(ParamDesc("fast")
406                                   .set_local_name(_("Fast")));
407
408         return ret;
409 }
410
411 Color
412 CurveWarp::get_color(Context context, const Point &point)const
413 {
414         return context.get_color(transform(point));
415 }
416
417 bool
418 CurveWarp::accelerated_render(Context context,Surface *surface,int quality, const RendDesc &renddesc, ProgressCallback *cb)const
419 {
420         SuperCallback stageone(cb,0,9000,10000);
421         SuperCallback stagetwo(cb,9000,10000,10000);
422
423         int x,y;
424
425         const Real pw(renddesc.get_pw()),ph(renddesc.get_ph());
426         Point tl(renddesc.get_tl());
427         Point br(renddesc.get_br());
428         const int w(renddesc.get_w());
429         const int h(renddesc.get_h());
430
431         // find a bounding rectangle for the context we need to render
432         // todo: find a better way of doing this - this way doesn't work
433         Rect src_rect(transform(tl));
434         Point pos1, pos2;
435
436         // look along the top and bottom edges
437         pos1[0] = pos2[0] = tl[0]; pos1[1] = tl[1]; pos2[1] = br[1];
438         for (x = 0; x < w; x++, pos1[0] += pw, pos2[0] += pw)
439         {
440                 src_rect.expand(transform(pos1));
441                 src_rect.expand(transform(pos2));
442         }
443
444         // look along the left and right edges
445         pos1[0] = tl[0]; pos2[0] = br[0]; pos1[1] = pos2[1] = tl[1];
446         for (y = 0; y < h; y++, pos1[1] += ph, pos2[1] += ph)
447         {
448                 src_rect.expand(transform(pos1));
449                 src_rect.expand(transform(pos2));
450         }
451
452         // look at each blinepoint
453         std::vector<synfig::BLinePoint>::const_iterator iter;
454         for (iter=bline.begin(); iter!=bline.end(); iter++)
455                 src_rect.expand(transform(iter->get_vertex()+origin));
456
457         Point src_tl(src_rect.get_min());
458         Point src_br(src_rect.get_max());
459         int src_w = w;                          // todo: what should we use for src_w
460         int src_h = h;                          //       and src_h?
461         Real src_pw((src_br[0] - src_tl[0]) / src_w);
462         Real src_ph((src_br[1] - src_tl[1]) / src_h);
463
464         // this is an attempt to remove artifacts around tile edges - the
465         // cubic interpolation uses at most 2 pixels either side of the
466         // target pixel, so add an extra 2 pixels around the tile on all
467         // sides
468         src_tl -= (Point(src_pw,src_ph)*2);
469         src_br += (Point(src_pw,src_ph)*2);
470         src_w += 4;
471         src_h += 4;
472         src_pw = (src_br[0] - src_tl[0]) / src_w;
473         src_ph = (src_br[1] - src_tl[1]) / src_h;
474
475         // set up a renddesc for the context to render
476         RendDesc src_desc(renddesc);
477         src_desc.clear_flags();
478         src_desc.set_tl(src_tl);
479         src_desc.set_br(src_br);
480         src_desc.set_wh(src_w, src_h);
481
482         // render the context onto a new surface
483         Surface source;
484         source.set_wh(src_w,src_h);
485         if(!context.accelerated_render(&source,quality,src_desc,&stageone))
486                 return false;
487
488         float u,v;
489         Point pos, tmp;
490
491         surface->set_wh(w,h);
492         surface->clear();
493
494         if(quality<=4)                          // CUBIC
495                 for(y=0,pos[1]=tl[1];y<h;y++,pos[1]+=ph)
496                 {
497                         for(x=0,pos[0]=tl[0];x<w;x++,pos[0]+=pw)
498                         {
499                                 tmp=transform(pos);
500                                 u=(tmp[0]-src_tl[0])/src_pw;
501                                 v=(tmp[1]-src_tl[1])/src_ph;
502                                 if(u<0 || v<0 || u>=src_w || v>=src_h || isnan(u) || isnan(v))
503                                         (*surface)[y][x]=context.get_color(tmp);
504                                 else
505                                         (*surface)[y][x]=source.cubic_sample(u,v);
506                         }
507                         if((y&31)==0 && cb && !stagetwo.amount_complete(y,h)) return false;
508                 }
509         else if (quality<=6)            // INTERPOLATION_LINEAR
510                 for(y=0,pos[1]=tl[1];y<h;y++,pos[1]+=ph)
511                 {
512                         for(x=0,pos[0]=tl[0];x<w;x++,pos[0]+=pw)
513                         {
514                                 tmp=transform(pos);
515                                 u=(tmp[0]-src_tl[0])/src_pw;
516                                 v=(tmp[1]-src_tl[1])/src_ph;
517                                 if(u<0 || v<0 || u>=src_w || v>=src_h || isnan(u) || isnan(v))
518                                         (*surface)[y][x]=context.get_color(tmp);
519                                 else
520                                         (*surface)[y][x]=source.linear_sample(u,v);
521                         }
522                         if((y&31)==0 && cb && !stagetwo.amount_complete(y,h)) return false;
523                 }
524         else                                            // NEAREST_NEIGHBOR
525                 for(y=0,pos[1]=tl[1];y<h;y++,pos[1]+=ph)
526                 {
527                         for(x=0,pos[0]=tl[0];x<w;x++,pos[0]+=pw)
528                         {
529                                 tmp=transform(pos);
530                                 u=(tmp[0]-src_tl[0])/src_pw;
531                                 v=(tmp[1]-src_tl[1])/src_ph;
532                                 if(u<0 || v<0 || u>=src_w || v>=src_h || isnan(u) || isnan(v))
533                                         (*surface)[y][x]=context.get_color(tmp);
534                                 else
535                                         (*surface)[y][x]=source[floor_to_int(v)][floor_to_int(u)];
536                         }
537                         if((y&31)==0 && cb && !stagetwo.amount_complete(y,h)) return false;
538                 }
539
540         // Mark our progress as finished
541         if(cb && !cb->amount_complete(10000,10000))
542                 return false;
543
544         return true;
545 }