1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Copyright (C) Steren Giannini 2008 <steren.giannini@gmail.com>
4  *
5  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
6  */
7 
8 #include "live_effects/lpe-envelope.h"
9 #include "display/curve.h"
10 // TODO due to internal breakage in glibmm headers, this must be last:
11 #include <glibmm/i18n.h>
12 
13 namespace Inkscape {
14 namespace LivePathEffect {
15 
LPEEnvelope(LivePathEffectObject * lpeobject)16 LPEEnvelope::LPEEnvelope(LivePathEffectObject *lpeobject) :
17     Effect(lpeobject),
18     bend_path1(_("Top bend path:"), _("Top path along which to bend the original path"), "bendpath1", &wr, this, "M0,0 L1,0"),
19     bend_path2(_("Right bend path:"), _("Right path along which to bend the original path"), "bendpath2", &wr, this, "M0,0 L1,0"),
20     bend_path3(_("Bottom bend path:"), _("Bottom path along which to bend the original path"), "bendpath3", &wr, this, "M0,0 L1,0"),
21     bend_path4(_("Left bend path:"), _("Left path along which to bend the original path"), "bendpath4", &wr, this, "M0,0 L1,0"),
22     xx(_("_Enable left &amp; right paths"), _("Enable the left and right deformation paths"), "xx", &wr, this, true),
23     yy(_("_Enable top &amp; bottom paths"), _("Enable the top and bottom deformation paths"), "yy", &wr, this, true)
24 {
25     registerParameter(&yy);
26     registerParameter(&xx);
27     registerParameter(&bend_path1);
28     registerParameter(&bend_path2);
29     registerParameter(&bend_path3);
30     registerParameter(&bend_path4);
31     concatenate_before_pwd2 = true;
32     apply_to_clippath_and_mask = true;
33 }
34 
35 LPEEnvelope::~LPEEnvelope()
36 = default;
37 
transform_multiply(Geom::Affine const & postmul,bool)38 void LPEEnvelope::transform_multiply(Geom::Affine const &postmul, bool /*set*/)
39 {
40     if (sp_lpe_item && sp_lpe_item->pathEffectsEnabled() && sp_lpe_item->optimizeTransforms()) {
41         bend_path1.param_transform_multiply(postmul, false);
42         bend_path2.param_transform_multiply(postmul, false);
43         bend_path3.param_transform_multiply(postmul, false);
44         bend_path4.param_transform_multiply(postmul, false);
45     }
46 }
47 
48 void
doBeforeEffect(SPLPEItem const * lpeitem)49 LPEEnvelope::doBeforeEffect (SPLPEItem const* lpeitem)
50 {
51     // get the item bounding box
52     original_bbox(lpeitem, false, true);
53 }
54 
55 Geom::Piecewise<Geom::D2<Geom::SBasis> >
doEffect_pwd2(Geom::Piecewise<Geom::D2<Geom::SBasis>> const & pwd2_in)56 LPEEnvelope::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
57 {
58 
59     if(!xx.get_value() && !yy.get_value())
60     {
61         return pwd2_in;
62     }
63 
64     using namespace Geom;
65 
66     // Don't allow empty path parameters:
67     if ( bend_path1.get_pathvector().empty()
68          || bend_path2.get_pathvector().empty()
69          || bend_path3.get_pathvector().empty()
70          || bend_path4.get_pathvector().empty() )
71     {
72         return pwd2_in;
73     }
74 
75     /*
76     The code below is inspired from the Bend Path code developed by jfb and mgsloan
77     Please, read it before trying to understand this one
78     */
79 
80     Piecewise<D2<SBasis> > uskeleton1 = arc_length_parametrization(bend_path1.get_pwd2(),2,.1);
81     uskeleton1 = remove_short_cuts(uskeleton1,.01);
82     Piecewise<D2<SBasis> > n1 = rot90(derivative(uskeleton1));
83     n1 = force_continuity(remove_short_cuts(n1,.1));
84 
85     Piecewise<D2<SBasis> > uskeleton2 = arc_length_parametrization(bend_path2.get_pwd2(),2,.1);
86     uskeleton2 = remove_short_cuts(uskeleton2,.01);
87     Piecewise<D2<SBasis> > n2 = rot90(derivative(uskeleton2));
88     n2 = force_continuity(remove_short_cuts(n2,.1));
89 
90     Piecewise<D2<SBasis> > uskeleton3 = arc_length_parametrization(bend_path3.get_pwd2(),2,.1);
91     uskeleton3 = remove_short_cuts(uskeleton3,.01);
92     Piecewise<D2<SBasis> > n3 = rot90(derivative(uskeleton3));
93     n3 = force_continuity(remove_short_cuts(n3,.1));
94 
95     Piecewise<D2<SBasis> > uskeleton4 = arc_length_parametrization(bend_path4.get_pwd2(),2,.1);
96     uskeleton4 = remove_short_cuts(uskeleton4,.01);
97     Piecewise<D2<SBasis> > n4 = rot90(derivative(uskeleton4));
98     n4 = force_continuity(remove_short_cuts(n4,.1));
99 
100 
101     D2<Piecewise<SBasis> > patternd2 = make_cuts_independent(pwd2_in);
102     Piecewise<SBasis> x = Piecewise<SBasis>(patternd2[0]);
103     Piecewise<SBasis> y = Piecewise<SBasis>(patternd2[1]);
104 
105     /*The *1.001 is a hack to avoid a small bug : path at x=0 and y=0 don't work well. */
106     x-= boundingbox_X.min()*1.001;
107     y-= boundingbox_Y.min()*1.001;
108 
109     Piecewise<SBasis> x1 = x ;
110     Piecewise<SBasis> y1 = y ;
111 
112     Piecewise<SBasis> x2 = x ;
113     Piecewise<SBasis> y2 = y ;
114     x2 -= boundingbox_X.extent();
115 
116     Piecewise<SBasis> x3 = x ;
117     Piecewise<SBasis> y3 = y ;
118     y3 -= boundingbox_Y.extent();
119 
120     Piecewise<SBasis> x4 = x ;
121     Piecewise<SBasis> y4 = y ;
122 
123 
124     /*Scaling to the Bend Path length*/
125     double scaling1 = uskeleton1.cuts.back()/boundingbox_X.extent();
126     if (scaling1 != 1.0) {
127         x1*=scaling1;
128     }
129 
130     double scaling2 = uskeleton2.cuts.back()/boundingbox_Y.extent();
131     if (scaling2 != 1.0) {
132         y2*=scaling2;
133     }
134 
135     double scaling3 = uskeleton3.cuts.back()/boundingbox_X.extent();
136     if (scaling3 != 1.0) {
137         x3*=scaling3;
138     }
139 
140     double scaling4 = uskeleton4.cuts.back()/boundingbox_Y.extent();
141     if (scaling4 != 1.0) {
142         y4*=scaling4;
143     }
144 
145 
146 
147     Piecewise<SBasis> xbis = x;
148     Piecewise<SBasis> ybis = y;
149     xbis *= -1.0;
150     xbis += boundingbox_X.extent();
151     ybis *= -1.0;
152     ybis += boundingbox_Y.extent();
153     /* This is important : y + ybis = constant  and x +xbis = constant */
154 
155     Piecewise<D2<SBasis> > output;
156     Piecewise<D2<SBasis> > output1;
157     Piecewise<D2<SBasis> > output2;
158     Piecewise<D2<SBasis> > output_x;
159     Piecewise<D2<SBasis> > output_y;
160 
161     /*
162     output_y : Deformation by Up and Down Bend Paths
163     We use weighting : The closer a point is to a Band Path, the more it will be affected by this Bend Path.
164     This is done by the line "ybis*Derformation1 + y*Deformation2"
165     The result is a mix between the 2 deformed paths
166     */
167     output_y =  ybis*(compose((uskeleton1),x1) + y1*compose(n1,x1) )
168             +    y*(compose((uskeleton3),x3) + y3*compose(n3,x3) );
169     output_y /= (boundingbox_Y.extent());
170     if(!xx.get_value() && yy.get_value())
171     {
172             return output_y;
173     }
174 
175     /*output_x : Deformation by Left and Right Bend Paths*/
176     output_x =    x*(compose((uskeleton2),y2) + -x2*compose(n2,y2) )
177             + xbis*(compose((uskeleton4),y4) + -x4*compose(n4,y4) );
178     output_x /= (boundingbox_X.extent());
179     if(xx.get_value() && !yy.get_value())
180     {
181             return output_x;
182     }
183 
184     /*output : Deformation by Up, Left, Right and Down Bend Paths*/
185     if(xx.get_value() && yy.get_value())
186     {
187         Piecewise<SBasis> xsqr = x*xbis; /* xsqr = x * (BBox_X - x) */
188         Piecewise<SBasis> ysqr = y*ybis; /* xsqr = y * (BBox_Y - y) */
189         Piecewise<SBasis> xsqrbis = xsqr;
190         Piecewise<SBasis> ysqrbis = ysqr;
191         xsqrbis *= -1;
192         xsqrbis += boundingbox_X.extent()*boundingbox_X.extent()/4.;
193         ysqrbis *= -1;
194         ysqrbis += boundingbox_Y.extent()*boundingbox_Y.extent()/4.;
195         /*This is important : xsqr + xsqrbis = constant*/
196 
197 
198         /*
199         Here we mix the last two results : output_x and output_y
200         output1 : The more a point is close to Up and Down, the less it will be affected by output_x.
201         (This is done with the polynomial function)
202         output2 : The more a point is close to Left and Right, the less it will be affected by output_y.
203         output : we do the mean between output1 and output2 for all points.
204         */
205         output1 =  (ysqrbis*output_y) + (ysqr*output_x);
206         output1 /= (boundingbox_Y.extent()*boundingbox_Y.extent()/4.);
207 
208         output2 =  (xsqrbis*output_x) + (xsqr*output_y);
209         output2 /= (boundingbox_X.extent()*boundingbox_X.extent()/4.);
210 
211         output = output1 + output2;
212         output /= 2.;
213 
214         return output;
215         /*Of course, the result is not perfect, but on a graphical point of view, this is sufficient.*/
216 
217     }
218 
219     // do nothing when xx and yy are both false
220     return pwd2_in;
221 }
222 
223 void
resetDefaults(SPItem const * item)224 LPEEnvelope::resetDefaults(SPItem const* item)
225 {
226     Effect::resetDefaults(item);
227 
228     original_bbox(SP_LPE_ITEM(item), false, true);
229 
230     Geom::Point Up_Left(boundingbox_X.min(), boundingbox_Y.min());
231     Geom::Point Up_Right(boundingbox_X.max(), boundingbox_Y.min());
232     Geom::Point Down_Left(boundingbox_X.min(), boundingbox_Y.max());
233     Geom::Point Down_Right(boundingbox_X.max(), boundingbox_Y.max());
234 
235     Geom::Path path1;
236     path1.start( Up_Left );
237     path1.appendNew<Geom::LineSegment>( Up_Right );
238     bend_path1.set_new_value( path1.toPwSb(), true );
239 
240     Geom::Path path2;
241     path2.start( Up_Right );
242     path2.appendNew<Geom::LineSegment>( Down_Right );
243     bend_path2.set_new_value( path2.toPwSb(), true );
244 
245     Geom::Path path3;
246     path3.start( Down_Left );
247     path3.appendNew<Geom::LineSegment>( Down_Right );
248     bend_path3.set_new_value( path3.toPwSb(), true );
249 
250     Geom::Path path4;
251     path4.start( Up_Left );
252     path4.appendNew<Geom::LineSegment>( Down_Left );
253     bend_path4.set_new_value( path4.toPwSb(), true );
254 }
255 
256 
257 } // namespace LivePathEffect
258 } /* namespace Inkscape */
259