1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (C) JF Barraud 2007 <jf.barraud@gmail.com>
4 *
5 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
6 */
7
8 #include <vector>
9 #include "live_effects/lpe-vonkoch.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
16 void
param_setup_nodepath(Inkscape::NodePath::Path * np)17 VonKochPathParam::param_setup_nodepath(Inkscape::NodePath::Path *np)
18 {
19 PathParam::param_setup_nodepath(np);
20 //sp_nodepath_make_straight_path(np);
21 }
22
23 //FIXME: a path is used here instead of 2 points to work around path/point param incompatibility bug.
24 void
param_setup_nodepath(Inkscape::NodePath::Path * np)25 VonKochRefPathParam::param_setup_nodepath(Inkscape::NodePath::Path *np)
26 {
27 PathParam::param_setup_nodepath(np);
28 //sp_nodepath_make_straight_path(np);
29 }
30 bool
param_readSVGValue(const gchar * strvalue)31 VonKochRefPathParam::param_readSVGValue(const gchar * strvalue)
32 {
33 Geom::PathVector old = _pathvector;
34 bool res = PathParam::param_readSVGValue(strvalue);
35 if (res && _pathvector.size()==1 && _pathvector.front().size()==1){
36 return true;
37 }else{
38 _pathvector = old;
39 return false;
40 }
41 }
42
LPEVonKoch(LivePathEffectObject * lpeobject)43 LPEVonKoch::LPEVonKoch(LivePathEffectObject *lpeobject) :
44 Effect(lpeobject),
45 nbgenerations(_("N_r of generations:"), _("Depth of the recursion --- keep low!!"), "nbgenerations", &wr, this, 1),
46 generator(_("Generating path:"), _("Path whose segments define the iterated transforms"), "generator", &wr, this, "M0,0 L30,0 M0,10 L10,10 M 20,10 L30,10"),
47 similar_only(_("_Use uniform transforms only"), _("2 consecutive segments are used to reverse/preserve orientation only (otherwise, they define a general transform)."), "similar_only", &wr, this, false),
48 drawall(_("Dra_w all generations"), _("If unchecked, draw only the last generation"), "drawall", &wr, this, true),
49 //,draw_boxes(_("Display boxes"), _("Display boxes instead of paths only"), "draw_boxes", &wr, this, true)
50 ref_path(_("Reference segment:"), _("The reference segment. Defaults to the horizontal midline of the bbox."), "ref_path", &wr, this, "M0,0 L10,0"),
51 //refA(_("Ref Start"), _("Left side middle of the reference box"), "refA", &wr, this),
52 //refB(_("Ref End"), _("Right side middle of the reference box"), "refB", &wr, this),
53 //FIXME: a path is used here instead of 2 points to work around path/point param incompatibility bug.
54 maxComplexity(_("_Max complexity:"), _("Disable effect if the output is too complex"), "maxComplexity", &wr, this, 1000)
55 {
56 //FIXME: a path is used here instead of 2 points to work around path/point param incompatibility bug.
57 registerParameter(&ref_path);
58 //registerParameter(&refA) );
59 //registerParameter(&refB) );
60 registerParameter(&generator);
61 registerParameter(&similar_only);
62 registerParameter(&nbgenerations);
63 registerParameter(&drawall);
64 registerParameter(&maxComplexity);
65 //registerParameter(&draw_boxes) );
66 apply_to_clippath_and_mask = true;
67 nbgenerations.param_make_integer();
68 nbgenerations.param_set_range(0, std::numeric_limits<gint>::max());
69 maxComplexity.param_make_integer();
70 maxComplexity.param_set_range(0, std::numeric_limits<gint>::max());
71 }
72
73 LPEVonKoch::~LPEVonKoch()
74 = default;
75
76 Geom::PathVector
doEffect_path(Geom::PathVector const & path_in)77 LPEVonKoch::doEffect_path (Geom::PathVector const & path_in)
78 {
79 using namespace Geom;
80
81 Geom::PathVector generating_path = generator.get_pathvector();
82
83 if (generating_path.empty()) {
84 return path_in;
85 }
86
87 //Collect transform matrices.
88 Affine m0;
89 Geom::Path refpath = ref_path.get_pathvector().front();
90 Point A = refpath.pointAt(0);
91 Point B = refpath.pointAt(refpath.size());
92 Point u = B-A;
93 m0 = Affine(u[X], u[Y],-u[Y], u[X], A[X], A[Y]);
94
95 //FIXME: a path is used as ref instead of 2 points to work around path/point param incompatibility bug.
96 //Point u = refB-refA;
97 //m0 = Affine(u[X], u[Y],-u[Y], u[X], refA[X], refA[Y]);
98 m0 = m0.inverse();
99
100 std::vector<Affine> transforms;
101 for (const auto & i : generating_path){
102 Affine m;
103 if(i.size()==1){
104 Point p = i.pointAt(0);
105 Point u = i.pointAt(1)-p;
106 m = Affine(u[X], u[Y],-u[Y], u[X], p[X], p[Y]);
107 m = m0*m;
108 transforms.push_back(m);
109 }else if(i.size()>=2){
110 Point p = i.pointAt(1);
111 Point u = i.pointAt(2)-p;
112 Point v = p-i.pointAt(0);
113 if (similar_only.get_value()){
114 int sign = (u[X]*v[Y]-u[Y]*v[X]>=0?1:-1);
115 v[X] = -u[Y]*sign;
116 v[Y] = u[X]*sign;
117 }
118 m = Affine(u[X], u[Y],v[X], v[Y], p[X], p[Y]);
119 m = m0*m;
120 transforms.push_back(m);
121 }
122 }
123
124 if (transforms.empty()){
125 return path_in;
126 }
127
128 //Do nothing if the output is too complex...
129 int path_in_complexity = 0;
130 for (const auto & k : path_in){
131 path_in_complexity+=k.size();
132 }
133 double complexity = std::pow(transforms.size(), nbgenerations) * path_in_complexity;
134 if (drawall.get_value()){
135 int k = transforms.size();
136 if(k>1){
137 complexity = (std::pow(k,nbgenerations+1)-1)/(k-1)*path_in_complexity;
138 }else{
139 complexity = nbgenerations*k*path_in_complexity;
140 }
141 }
142 if (complexity > double(maxComplexity)){
143 g_warning("VonKoch lpe's output too complex. Effect bypassed.");
144 return path_in;
145 }
146
147 //Generate path:
148 Geom::PathVector pathi = path_in;
149 Geom::PathVector path_out = path_in;
150
151 for (unsigned i = 0; i<nbgenerations; i++){
152 if (drawall.get_value()){
153 path_out = path_in;
154 complexity = path_in_complexity;
155 }else{
156 path_out = Geom::PathVector();
157 complexity = 0;
158 }
159 for (const auto & transform : transforms){
160 for (unsigned k = 0; k<pathi.size() && complexity < maxComplexity; k++){
161 path_out.push_back(pathi[k]*transform);
162 complexity+=pathi[k].size();
163 }
164 }
165 pathi = path_out;
166 }
167 return path_out;
168 }
169
170
171 //Useful??
172 //void
173 //LPEVonKoch::addCanvasIndicators(SPLPEItem const */*lpeitem*/, std::vector<Geom::PathVector> &hp_vec)
174 /*{
175 using namespace Geom;
176 if (draw_boxes.get_value()){
177 double ratio = .5;
178 if (similar_only.get_value()) ratio = boundingbox_Y.extent()/boundingbox_X.extent()/2;
179
180 Point BB1,BB2,BB3,BB4,v;
181
182 //Draw the reference box (ref_path is supposed to consist in one line segment)
183 //FIXME: a path is used as ref instead of 2 points to work around path/point param incompatibility bug.
184 Geom::Path refpath = ref_path.get_pathvector().front();
185 if (refpath.size()==1){
186 BB1 = BB4 = refpath.front().pointAt(0);
187 BB2 = BB3 = refpath.front().pointAt(1);
188 v = rot90(BB2 - BB1)*ratio;
189 BB1 -= v;
190 BB2 -= v;
191 BB3 += v;
192 BB4 += v;
193 Geom::Path refbox(BB1);
194 refbox.appendNew<LineSegment>(BB2);
195 refbox.appendNew<LineSegment>(BB3);
196 refbox.appendNew<LineSegment>(BB4);
197 refbox.close();
198 PathVector refbox_as_vect;
199 refbox_as_vect.push_back(refbox);
200 hp_vec.push_back(refbox_as_vect);
201 }
202 //Draw the transformed boxes
203 Geom::PathVector generating_path = generator.get_pathvector();
204 for (unsigned i=0;i<generating_path.size(); i++){
205 if (generating_path[i].size()==0){
206 //Ooops! this should not happen.
207 }else if (generating_path[i].size()==1){
208 BB1 = BB4 = generating_path[i].pointAt(0);
209 BB2 = BB3 = generating_path[i].pointAt(1);
210 v = rot90(BB2 - BB1)*ratio;
211 }else{//Only tak the first 2 segments into account.
212 BB1 = BB4 = generating_path[i].pointAt(1);
213 BB2 = BB3 = generating_path[i].pointAt(2);
214 if(similar_only.get_value()){
215 v = rot90(BB2 - BB1)*ratio;
216 }else{
217 v = (generating_path[i].pointAt(0) - BB1)*ratio;
218 }
219 }
220 BB1 -= v;
221 BB2 -= v;
222 BB3 += v;
223 BB4 += v;
224 Geom::Path path(BB1);
225 path.appendNew<LineSegment>(BB2);
226 path.appendNew<LineSegment>(BB3);
227 path.appendNew<LineSegment>(BB4);
228 path.close();
229 PathVector pathv;
230 pathv.push_back(path);
231 hp_vec.push_back(pathv);
232 }
233 }
234 }
235 */
236
237 void
doBeforeEffect(SPLPEItem const * lpeitem)238 LPEVonKoch::doBeforeEffect (SPLPEItem const* lpeitem)
239 {
240 using namespace Geom;
241 original_bbox(lpeitem, false, true);
242
243 Geom::PathVector paths = ref_path.get_pathvector();
244 Geom::Point A,B;
245 if (paths.empty()||paths.front().size()==0){
246 //FIXME: a path is used as ref instead of 2 points to work around path/point param incompatibility bug.
247 //refA.param_setValue( Geom::Point(boundingbox_X.min(), boundingbox_Y.middle()) );
248 //refB.param_setValue( Geom::Point(boundingbox_X.max(), boundingbox_Y.middle()) );
249 A = Point(boundingbox_X.min(), boundingbox_Y.middle());
250 B = Point(boundingbox_X.max(), boundingbox_Y.middle());
251 }else{
252 A = paths.front().pointAt(0);
253 B = paths.front().pointAt(paths.front().size());
254 }
255 if (paths.size()!=1||paths.front().size()!=1){
256 Geom::Path tmp_path(A);
257 tmp_path.appendNew<LineSegment>(B);
258 Geom::PathVector tmp_pathv;
259 tmp_pathv.push_back(tmp_path);
260 ref_path.set_new_value(tmp_pathv,true);
261 }
262 }
263
264
265 void
resetDefaults(SPItem const * item)266 LPEVonKoch::resetDefaults(SPItem const* item)
267 {
268 Effect::resetDefaults(item);
269
270 using namespace Geom;
271 original_bbox(SP_LPE_ITEM(item), false, true);
272
273 Point A,B;
274 A[Geom::X] = boundingbox_X.min();
275 A[Geom::Y] = boundingbox_Y.middle();
276 B[Geom::X] = boundingbox_X.max();
277 B[Geom::Y] = boundingbox_Y.middle();
278
279 Geom::PathVector paths,refpaths;
280 Geom::Path path = Geom::Path(A);
281 path.appendNew<Geom::LineSegment>(B);
282
283 refpaths.push_back(path);
284 ref_path.set_new_value(refpaths, true);
285
286 paths.push_back(path * Affine(1./3,0,0,1./3, A[X]*2./3, A[Y]*2./3 + boundingbox_Y.extent()/2));
287 paths.push_back(path * Affine(1./3,0,0,1./3, B[X]*2./3, B[Y]*2./3 + boundingbox_Y.extent()/2));
288 generator.set_new_value(paths, true);
289
290 //FIXME: a path is used as ref instead of 2 points to work around path/point param incompatibility bug.
291 //refA[Geom::X] = boundingbox_X.min();
292 //refA[Geom::Y] = boundingbox_Y.middle();
293 //refB[Geom::X] = boundingbox_X.max();
294 //refB[Geom::Y] = boundingbox_Y.middle();
295 //Geom::PathVector paths;
296 //Geom::Path path = Geom::Path( (Point) refA);
297 //path.appendNew<Geom::LineSegment>( (Point) refB );
298 //paths.push_back(path * Affine(1./3,0,0,1./3, refA[X]*2./3, refA[Y]*2./3 + boundingbox_Y.extent()/2));
299 //paths.push_back(path * Affine(1./3,0,0,1./3, refB[X]*2./3, refB[Y]*2./3 + boundingbox_Y.extent()/2));
300 //paths.push_back(path);
301 //generator.set_new_value(paths, true);
302 }
303
304 } // namespace LivePathEffect
305 } /* namespace Inkscape */
306
307 /*
308 Local Variables:
309 mode:c++
310 c-file-style:"stroustrup"
311 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
312 indent-tabs-mode:nil
313 fill-column:99
314 End:
315 */
316 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
317