1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /** \file
3  * LPE Curve Stitching implementation, used as an example for a base starting class
4  * when implementing new LivePathEffects.
5  *
6  */
7 /*
8  * Authors:
9  *   Johan Engelen
10  *
11  * Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>
12  *
13  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
14  */
15 
16 #include "ui/widget/scalar.h"
17 #include "live_effects/lpe-curvestitch.h"
18 
19 #include "object/sp-path.h"
20 
21 #include "svg/svg.h"
22 #include "xml/repr.h"
23 
24 #include <2geom/bezier-to-sbasis.h>
25 
26 // TODO due to internal breakage in glibmm headers, this must be last:
27 #include <glibmm/i18n.h>
28 
29 
30 namespace Inkscape {
31 namespace LivePathEffect {
32 
33 using namespace Geom;
34 
LPECurveStitch(LivePathEffectObject * lpeobject)35 LPECurveStitch::LPECurveStitch(LivePathEffectObject *lpeobject) :
36     Effect(lpeobject),
37     strokepath(_("Stitch path:"), _("The path that will be used as stitch."), "strokepath", &wr, this, "M0,0 L1,0"),
38     nrofpaths(_("N_umber of paths:"), _("The number of paths that will be generated."), "count", &wr, this, 5),
39     startpoint_edge_variation(_("Sta_rt edge variance:"), _("The amount of random jitter to move the start points of the stitches inside & outside the guide path"), "startpoint_edge_variation", &wr, this, 0),
40     startpoint_spacing_variation(_("Sta_rt spacing variance:"), _("The amount of random shifting to move the start points of the stitches back & forth along the guide path"), "startpoint_spacing_variation", &wr, this, 0),
41     endpoint_edge_variation(_("End ed_ge variance:"), _("The amount of randomness that moves the end points of the stitches inside & outside the guide path"), "endpoint_edge_variation", &wr, this, 0),
42     endpoint_spacing_variation(_("End spa_cing variance:"), _("The amount of random shifting to move the end points of the stitches back & forth along the guide path"), "endpoint_spacing_variation", &wr, this, 0),
43     prop_scale(_("Scale _width:"), _("Scale the width of the stitch path"), "prop_scale", &wr, this, 1),
44     scale_y_rel(_("Scale _width relative to length"), _("Scale the width of the stitch path relative to its length"), "scale_y_rel", &wr, this, false)
45 {
46     registerParameter(&nrofpaths);
47     registerParameter(&startpoint_edge_variation);
48     registerParameter(&startpoint_spacing_variation);
49     registerParameter(&endpoint_edge_variation);
50     registerParameter(&endpoint_spacing_variation);
51     registerParameter(&strokepath );
52     registerParameter(&prop_scale);
53     registerParameter(&scale_y_rel);
54 
55     nrofpaths.param_make_integer();
56     nrofpaths.param_set_range(2, Geom::infinity());
57 
58     prop_scale.param_set_digits(3);
59     prop_scale.param_set_increments(0.01, 0.10);
60     transformed = false;
61 }
62 
63 LPECurveStitch::~LPECurveStitch()
64 = default;
65 
66 Geom::PathVector
doEffect_path(Geom::PathVector const & path_in)67 LPECurveStitch::doEffect_path (Geom::PathVector const & path_in)
68 {
69     if (path_in.size() >= 2) {
70         startpoint_edge_variation.resetRandomizer();
71         endpoint_edge_variation.resetRandomizer();
72         startpoint_spacing_variation.resetRandomizer();
73         endpoint_spacing_variation.resetRandomizer();
74 
75         D2<Piecewise<SBasis> > stroke = make_cuts_independent(strokepath.get_pwd2());
76         OptInterval bndsStroke = bounds_exact(stroke[0]);
77         OptInterval bndsStrokeY = bounds_exact(stroke[1]);
78         if (!bndsStroke && !bndsStrokeY) {
79             return path_in;
80         }
81         gdouble scaling = bndsStroke->max() - bndsStroke->min();
82         Point stroke_origin(bndsStroke->min(), (bndsStrokeY->max()+bndsStrokeY->min())/2);
83 
84         Geom::PathVector path_out;
85 
86         // do this for all permutations (ii,jj) if there are more than 2 paths? realllly cool!
87         for (unsigned ii = 0   ; ii < path_in.size() - 1; ii++)
88         for (unsigned jj = ii+1; jj < path_in.size(); jj++)
89         {
90             Piecewise<D2<SBasis> > A = arc_length_parametrization(Piecewise<D2<SBasis> >(path_in[ii].toPwSb()),2,.1);
91             Piecewise<D2<SBasis> > B = arc_length_parametrization(Piecewise<D2<SBasis> >(path_in[jj].toPwSb()),2,.1);
92             Interval bndsA = A.domain();
93             Interval bndsB = B.domain();
94             gdouble incrementA = (bndsA.max()-bndsA.min()) / (nrofpaths-1);
95             gdouble incrementB = (bndsB.max()-bndsB.min()) / (nrofpaths-1);
96             gdouble tA = bndsA.min();
97             gdouble tB = bndsB.min();
98             gdouble tAclean = tA; // the tA without spacing_variation
99             gdouble tBclean = tB; // the tB without spacing_variation
100 
101             for (int i = 0; i < nrofpaths; i++) {
102                 Point start = A(tA);
103                 Point end = B(tB);
104                 if (startpoint_edge_variation.get_value() != 0)
105                     start = start + (startpoint_edge_variation - startpoint_edge_variation.get_value()/2) * (end - start);
106                 if (endpoint_edge_variation.get_value() != 0)
107                     end = end + (endpoint_edge_variation - endpoint_edge_variation.get_value()/2)* (end - start);
108 
109                 if (!Geom::are_near(start,end)) {
110                     gdouble scaling_y = 1.0;
111                     if (scale_y_rel.get_value() || transformed) {
112                         scaling_y = (L2(end-start)/scaling)*prop_scale;
113                         transformed = false;
114                     } else {
115                         scaling_y = prop_scale;
116                     }
117 
118                     Affine transform;
119                     transform.setXAxis( (end-start) / scaling );
120                     transform.setYAxis( rot90(unit_vector(end-start)) * scaling_y);
121                     transform.setTranslation( start );
122                     Piecewise<D2<SBasis> > pwd2_out = (strokepath.get_pwd2()-stroke_origin) * transform;
123 
124                     // add stuff to one big pw<d2<sbasis> > and then outside the loop convert to path?
125                     // No: this way, the separate result paths are kept separate which might come in handy some time!
126                     Geom::PathVector result = Geom::path_from_piecewise(pwd2_out, LPE_CONVERSION_TOLERANCE);
127                     path_out.push_back(result[0]);
128                 }
129                 gdouble svA = startpoint_spacing_variation - startpoint_spacing_variation.get_value()/2;
130                 gdouble svB = endpoint_spacing_variation - endpoint_spacing_variation.get_value()/2;
131                 tAclean += incrementA;
132                 tBclean += incrementB;
133                 tA = tAclean + incrementA * svA;
134                 tB = tBclean + incrementB * svB;
135                 if (tA > bndsA.max())
136                     tA = bndsA.max();
137                 if (tB > bndsB.max())
138                     tB = bndsB.max();
139             }
140         }
141 
142         return path_out;
143     } else {
144         return path_in;
145     }
146 }
147 
148 void
resetDefaults(SPItem const * item)149 LPECurveStitch::resetDefaults(SPItem const* item)
150 {
151     Effect::resetDefaults(item);
152 
153     if (!SP_IS_PATH(item)) return;
154 
155     using namespace Geom;
156 
157     // set the stroke path to run horizontally in the middle of the bounding box of the original path
158 
159     // calculate bounding box:  (isn't there a simpler way?)
160     Piecewise<D2<SBasis> > pwd2;
161     Geom::PathVector temppath = sp_svg_read_pathv( item->getRepr()->attribute("inkscape:original-d"));
162     for (const auto & i : temppath) {
163         pwd2.concat( i.toPwSb() );
164     }
165     D2<Piecewise<SBasis> > d2pw = make_cuts_independent(pwd2);
166     OptInterval bndsX = bounds_exact(d2pw[0]);
167     OptInterval bndsY = bounds_exact(d2pw[1]);
168     if (bndsX && bndsY) {
169         Point start(bndsX->min(), (bndsY->max()+bndsY->min())/2);
170         Point end(bndsX->max(), (bndsY->max()+bndsY->min())/2);
171         if ( !Geom::are_near(start,end) ) {
172             Geom::Path path;
173             path.start( start );
174             path.appendNew<Geom::LineSegment>( end );
175             strokepath.set_new_value( path.toPwSb(), true );
176         } else {
177             // bounding box is too small to make decent path. set to default default. :-)
178             strokepath.param_set_and_write_default();
179         }
180     } else {
181         // bounding box is non-existent. set to default default. :-)
182         strokepath.param_set_and_write_default();
183     }
184 }
185 
186 } //namespace LivePathEffect
187 } /* namespace Inkscape */
188 
189 /*
190   Local Variables:
191   mode:c++
192   c-file-style:"stroustrup"
193   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
194   indent-tabs-mode:nil
195   fill-column:99
196   End:
197 */
198 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
199