1 #define INKSCAPE_LPE_DYNASTROKE_CPP
2 /** \file
3 * LPE <dynastroke> implementation
4 */
5 /*
6 * Authors:
7 * JF Barraud
8 *
9 * Copyright (C) JF Barraud 2007 <jf.barraud@gmail.com>
10 *
11 * Released under GNU GPL, read the file 'COPYING' for more information
12 */
14 #include "live_effects/lpe-dynastroke.h"
15 #include "display/curve.h"
16 //# include <libnr/n-art-bpath.h>
18 #include <2geom/path.h>
19 #include <2geom/sbasis.h>
20 #include <2geom/sbasis-geometric.h>
21 #include <2geom/bezier-to-sbasis.h>
22 #include <2geom/sbasis-to-bezier.h>
23 #include <2geom/d2.h>
24 #include <2geom/d2-sbasis.h>
25 #include <2geom/sbasis-math.h>
26 #include <2geom/piecewise.h>
28 namespace Inkscape {
29 namespace LivePathEffect {
31 //----------------------------------------------------------
32 //--- TODO: Test this and move to 2Geom --------------------
33 //----------------------------------------------------------
35 static
36 std::vector<double>
37 find_corners (Geom::Piecewise<Geom::D2<Geom::SBasis> > const &m){
38 using namespace Geom;
39 std::vector<double> output = std::vector<double>();
40 Piecewise<D2<SBasis> > v = derivative(m);
41 for (unsigned i=0; i<m.size()-1; i++){
42 if ( m.segs[i].at1() == m.segs[i+1].at0() &&
43 v.segs[i].at1() != v.segs[i+1].at0()){
44 output.push_back(m.cuts[i+1]);
45 }
46 }
47 return output;
48 }
50 //----------------------------------------------------------
51 //----------------------------------------------------------
52 //----------------------------------------------------------
54 //TODO: growfor/fadefor can be expressed in unit of width.
55 //TODO: make round/sharp end choices independant for start and end.
56 //TODO: define more styles like in calligtool.
57 //TODO: allow fancy ends.
59 static const Util::EnumData<DynastrokeMethod> DynastrokeMethodData[DSM_END] = {
60 {DSM_ELLIPTIC_PEN, N_("Elliptic Pen"), "elliptic_pen"},
61 {DSM_THICKTHIN_FAST, N_("Thick-Thin strokes (fast)"), "thickthin_fast"},
62 {DSM_THICKTHIN_SLOW, N_("Thick-Thin strokes (slow)"), "thickthin_slow"}
63 };
64 static const Util::EnumDataConverter<DynastrokeMethod> DSMethodConverter(DynastrokeMethodData, DSM_END);
66 static const Util::EnumData<DynastrokeCappingType> DynastrokeCappingTypeData[DSCT_END] = {
67 {DSCT_SHARP, N_("Sharp"), "sharp"},
68 {DSCT_ROUND, N_("Round"), "round"},
69 };
70 static const Util::EnumDataConverter<DynastrokeCappingType> DSCTConverter(DynastrokeCappingTypeData, DSCT_END);
72 LPEDynastroke::LPEDynastroke(LivePathEffectObject *lpeobject) :
73 Effect(lpeobject),
74 // initialise your parameters here:
75 method(_("Method"), _("Choose pen type"), "method", DSMethodConverter, &wr, this, DSM_THICKTHIN_FAST),
76 width(_("Pen width"), _("Maximal stroke width"), "width", &wr, this, 25),
77 roundness(_("Pen roundness"), _("Min/Max width ratio"), "roundness", &wr, this, .2),
78 angle(_("angle"), _("direction of thickest strokes (opposite = thinest)"), "angle", &wr, this, 45),
79 // modulo_pi(_("modulo pi"), _("Give forward and backward moves in one direction the same thickness "), "modulo_pi", &wr, this, false),
80 start_cap(_("Start"), _("Choose start capping type"), "start_cap", DSCTConverter, &wr, this, DSCT_SHARP),
81 end_cap(_("End"), _("Choose end capping type"), "end_cap", DSCTConverter, &wr, this, DSCT_SHARP),
82 growfor(_("Grow for"), _("Make the stroke thiner near it's start"), "growfor", &wr, this, 100),
83 fadefor(_("Fade for"), _("Make the stroke thiner near it's end"), "fadefor", &wr, this, 100),
84 round_ends(_("Round ends"), _("Strokes end with a round end"), "round_ends", &wr, this, false),
85 capping(_("Capping"), _("left capping"), "capping", &wr, this, "M 100,5 C 50,5 0,0 0,0 0,0 50,-5 100,-5")
86 {
88 registerParameter( dynamic_cast<Parameter *>(& method) );
89 registerParameter( dynamic_cast<Parameter *>(& width) );
90 registerParameter( dynamic_cast<Parameter *>(& roundness) );
91 registerParameter( dynamic_cast<Parameter *>(& angle) );
92 //registerParameter( dynamic_cast<Parameter *>(& modulo_pi) );
93 registerParameter( dynamic_cast<Parameter *>(& start_cap) );
94 registerParameter( dynamic_cast<Parameter *>(& growfor) );
95 registerParameter( dynamic_cast<Parameter *>(& end_cap) );
96 registerParameter( dynamic_cast<Parameter *>(& fadefor) );
97 registerParameter( dynamic_cast<Parameter *>(& round_ends) );
98 registerParameter( dynamic_cast<Parameter *>(& capping) );
100 width.param_set_range(0, NR_HUGE);
101 roundness.param_set_range(0.01, 1);
102 angle.param_set_range(-360, 360);
103 growfor.param_set_range(0, NR_HUGE);
104 fadefor.param_set_range(0, NR_HUGE);
106 show_orig_path = true;
107 }
109 LPEDynastroke::~LPEDynastroke()
110 {
112 }
114 Geom::Piecewise<Geom::D2<Geom::SBasis> >
115 LPEDynastroke::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
116 {
117 using namespace Geom;
119 std::cout<<"do effect: debut\n";
121 Piecewise<D2<SBasis> > output;
122 Piecewise<D2<SBasis> > m = pwd2_in;
123 Piecewise<D2<SBasis> > v = derivative(m);;
124 Piecewise<D2<SBasis> > n = unitVector(v);
125 n = rot90(n);
126 Piecewise<D2<SBasis> > n1,n2;
128 // for (unsigned i=0; i<n.size(); i++){
129 // std::cout<<n[i][X]<<"\n";
130 // }
131 // return m + unitVector(v);
133 #if 0
134 Piecewise<SBasis> k = curvature(m);
135 OptInterval mag = bounds_exact(k);
136 //TODO test if mag is non empty...
137 k = (k-mag->min())*width/mag->extent() + (roundness*width);
138 Piecewise<D2<SBasis> > left = m + k*n;
139 Piecewise<D2<SBasis> > right = m - k*n;
140 right = compose(right,Linear(right.cuts.back(),right.cuts.front()));
141 D2<SBasis> line;
142 line[X] = Linear(left.lastValue()[X],right.firstValue()[X]);
143 line[Y] = Linear(left.lastValue()[Y],right.firstValue()[Y]);
144 output = left;
145 output.concat(Piecewise<D2<SBasis> >(line));
146 output.concat(right);
147 line[X] = Linear(right.lastValue()[X],left.firstValue()[X]);
148 line[Y] = Linear(right.lastValue()[Y],left.firstValue()[Y]);
149 output.concat(Piecewise<D2<SBasis> >(line));
150 return output;
151 #else
153 double angle_rad = angle*M_PI/180.;//TODO: revert orientation?...
154 Piecewise<SBasis> w;
156 std::vector<double> corners = find_corners(m);
158 DynastrokeMethod stroke_method = method.get_value();
159 if (roundness==1.) {
160 std::cout<<"round pen.\n";
161 n1 = n*double(width);
162 n2 =-n1;
163 }else{
164 switch(stroke_method) {
165 case DSM_ELLIPTIC_PEN:{
166 std::cout<<"ellptic pen\n";
167 //FIXME: roundness=0???
168 double c = cos(angle_rad), s = sin(angle_rad);
169 Matrix rot,slant;
170 rot = Matrix(c, -s, s, c, 0, 0 );
171 slant = Matrix(double(width)*roundness, 0, 0, double(width), 0, 0 );
172 Piecewise<D2<SBasis> > nn = unitVector(v * ( rot * slant ) );
173 slant = Matrix( 0,-roundness, 1, 0, 0, 0 );
174 rot = Matrix(-s, -c, c, -s, 0, 0 );
175 nn = nn * (slant * rot );
177 n1 = nn*double(width);
178 n2 =-n1;
179 break;
180 }
181 case DSM_THICKTHIN_FAST:{
182 std::cout<<"fast thick thin pen\n";
183 D2<Piecewise<SBasis> > n_xy = make_cuts_independent(n);
184 w = n_xy[X]*sin(angle_rad) - n_xy[Y]*cos(angle_rad);
185 w = w * ((1 - roundness)*width/2.) + ((1 + roundness)*width/2.);
186 n1 = w*n;
187 n2 = -n1;
188 break;
189 }
190 case DSM_THICKTHIN_SLOW:{
191 std::cout<<"slow thick thin pen\n";
192 D2<Piecewise<SBasis> > n_xy = make_cuts_independent(n);
193 w = n_xy[X]*cos(angle_rad)+ n_xy[Y]*sin(angle_rad);
194 w = w * ((1 - roundness)*width/2.) + ((1 + roundness)*width/2.);
195 //->Slower and less stable, but more accurate .
196 // General formula: n1 = w*u with ||u||=1 and u.v = -dw/dt
197 Piecewise<SBasis> dw = derivative(w);
198 Piecewise<SBasis> ncomp = sqrt(dot(v,v)-dw*dw,.1,3);
199 //FIXME: is force continuity usefull? compatible with corners?
200 std::cout<<"ici\n";
201 n1 = -dw*v + ncomp*rot90(v);
202 n1 = w*force_continuity(unitVector(n1),.1);
203 n2 = -dw*v - ncomp*rot90(v);
204 n2 = w*force_continuity(unitVector(n2),.1);
205 std::cout<<"ici2\n";
206 break;
207 }
208 default:{
209 n1 = n*double(width);
210 n2 = n1*(-.5);
211 break;
212 }
213 }//case
214 }//if/else
216 //
217 //TODO: insert relevant stitch at each corner!!
218 //
220 Piecewise<D2<SBasis> > left, right;
221 if ( m.segs.front().at0() == m.segs.back().at1()){
222 // if closed:
223 std::cout<<"closed input.\n";
224 left = m + n1;//+ n;
225 right = m + n2;//- n;
226 } else {
227 //if not closed, shape the ends:
228 //TODO: allow fancy ends...
229 std::cout<<"shaping the ends\n";
230 double grow_length = growfor;// * width;
231 double fade_length = fadefor;// * width;
232 Piecewise<SBasis > s = arcLengthSb(m);
233 double totlength = s.segs.back().at1();
235 //scale factor for a sharp start
236 SBasis join = SBasis(2,Linear(0,1));
237 join[1] = Linear(1,1);
238 Piecewise<SBasis > factor_in = Piecewise<SBasis >(join);
239 factor_in.cuts[1]=grow_length;
240 if (grow_length < totlength){
241 factor_in.concat(Piecewise<SBasis >(Linear(1)));
242 factor_in.cuts[2]=totlength;
243 }
244 std::cout<<"shaping the ends ici\n";
245 //scale factor for a sharp end
246 join[0] = Linear(1,0);
247 join[1] = Linear(1,1);
248 Piecewise<SBasis > factor_out;
249 if (fade_length < totlength){
250 factor_out = Piecewise<SBasis >(Linear(1));
251 factor_out.cuts[1] = totlength-fade_length;
252 factor_out.concat(Piecewise<SBasis >(join));
253 factor_out.cuts[2] = totlength;
254 }else{
255 factor_out = Piecewise<SBasis >(join);
256 factor_out.setDomain(Interval(totlength-fade_length,totlength));
257 }
258 std::cout<<"shaping the ends ici ici\n";
260 Piecewise<SBasis > factor = factor_in*factor_out;
261 n1 = compose(factor,s)*n1;
262 n2 = compose(factor,s)*n2;
264 left = m + n1;
265 right = m + n2;
266 std::cout<<"shaping the ends ici ici ici\n";
268 if (start_cap.get_value() == DSCT_ROUND){
269 std::cout<<"shaping round start\n";
270 SBasis tau(2,Linear(0));
271 tau[1] = Linear(-1,0);
272 Piecewise<SBasis > hbump;
273 hbump.concat(Piecewise<SBasis >(tau*grow_length));
274 hbump.concat(Piecewise<SBasis >(Linear(0)));
275 hbump.cuts[0]=0;
276 hbump.cuts[1]=fmin(grow_length,totlength*grow_length/(grow_length+fade_length));
277 hbump.cuts[2]=totlength;
278 hbump = compose(hbump,s);
280 left += - hbump * rot90(n);
281 right += - hbump * rot90(n);
282 }
283 if (end_cap.get_value() == DSCT_ROUND){
284 std::cout<<"shaping round end\n";
285 SBasis tau(2,Linear(0));
286 tau[1] = Linear(0,1);
287 Piecewise<SBasis > hbump;
288 hbump.concat(Piecewise<SBasis >(Linear(0)));
289 hbump.concat(Piecewise<SBasis >(tau*fade_length));
290 hbump.cuts[0]=0;
291 hbump.cuts[1]=fmax(totlength-fade_length, totlength*grow_length/(grow_length+fade_length));
292 hbump.cuts[2]=totlength;
293 hbump = compose(hbump,s);
295 left += - hbump * rot90(n);
296 right += - hbump * rot90(n);
297 }
298 }
300 left = force_continuity(left);
301 right = force_continuity(right);
303 std::cout<<"gathering result: left";
304 output = left;
305 std::cout<<" + reverse(right)";
306 output.concat(reverse(right));
307 std::cout<<". done\n";
309 //-----------
310 return output;
311 #endif
312 }
315 /* ######################## */
317 } //namespace LivePathEffect (setq default-directory "c:/Documents And Settings/jf/Mes Documents/InkscapeSVN")
318 } /* namespace Inkscape */
320 /*
321 Local Variables:
322 mode:c++
323 c-file-style:"stroustrup"
324 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
325 indent-tabs-mode:nil
326 fill-column:99
327 End:
328 */
329 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :