Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
lpe-patternalongpath.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>
4 *
5 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
6 */
7
8#include <algorithm>
9#include <cmath>
10#include <vector>
12
13#include "preferences.h"
14
17#include "object/sp-shape.h"
18#include "ui/knot/knot-holder.h"
20
21// TODO due to internal breakage in glibmm headers, this must be last:
22#include <glibmm/i18n.h>
23
24/* Theory in e-mail from J.F. Barraud
25Let B be the skeleton path, and P the pattern (the path to be deformed).
26
27P is a map t --> P(t) = ( x(t), y(t) ).
28B is a map t --> B(t) = ( a(t), b(t) ).
29
30The first step is to re-parametrize B by its arc length: this is the parametrization in which a point p on B is located by its distance s from start. One obtains a new map s --> U(s) = (a'(s),b'(s)), that still describes the same path B, but where the distance along B from start to
31U(s) is s itself.
32
33We also need a unit normal to the path. This can be obtained by computing a unit tangent vector, and rotate it by 90�. Call this normal vector N(s).
34
35The basic deformation associated to B is then given by:
36
37 (x,y) --> U(x)+y*N(x)
38
39(i.e. we go for distance x along the path, and then for distance y along the normal)
40
41Of course this formula needs some minor adaptations (as is it depends on the absolute position of P for instance, so a little translation is needed
42first) but I think we can first forget about them.
43*/
44
45namespace Inkscape {
46namespace LivePathEffect {
47
48namespace WPAP {
49 class KnotHolderEntityWidthPatternAlongPath : public LPEKnotHolderEntity {
50 public:
51 KnotHolderEntityWidthPatternAlongPath(LPEPatternAlongPath * effect) : LPEKnotHolderEntity(effect) {}
52 ~KnotHolderEntityWidthPatternAlongPath() override
53 {
54 LPEPatternAlongPath *lpe = dynamic_cast<LPEPatternAlongPath *> (_effect);
55 lpe->_knotholder = nullptr;
56 }
57 void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state) override;
58 Geom::Point knot_get() const override;
59 };
60} // WPAP
61
63 // clang-format off
64 {PAPCT_SINGLE, N_("Single"), "single"},
65 {PAPCT_SINGLE_STRETCHED, N_("Single, stretched"), "single_stretched"},
66 {PAPCT_REPEATED, N_("Repeated"), "repeated"},
67 {PAPCT_REPEATED_STRETCHED, N_("Repeated, stretched"), "repeated_stretched"}
68 // clang-format on
69};
71
73 Effect(lpeobject),
74 pattern(_("Pattern source:"), _("Path to put along the skeleton path"), "pattern", &wr, this, "M0,0 L1,0"),
75 original_height(0.0),
76 prop_scale(_("_Width:"), _("Width of the pattern"), "prop_scale", &wr, this, 1.0),
77 copytype(_("Pattern copies:"), _("How many pattern copies to place along the skeleton path"),
78 "copytype", PAPCopyTypeConverter, &wr, this, PAPCT_SINGLE_STRETCHED),
79 scale_y_rel(_("Wid_th in units of length"),
80 _("Scale the width of the pattern in units of its length"),
81 "scale_y_rel", &wr, this, false),
82 spacing(_("Spa_cing:"),
83 // xgettext:no-c-format
84 _("Space between copies of the pattern. Negative values allowed, but are limited to -90% of pattern width."),
85 "spacing", &wr, this, 0),
86 normal_offset(_("No_rmal offset:"), "", "normal_offset", &wr, this, 0),
87 tang_offset(_("Tan_gential offset:"), "", "tang_offset", &wr, this, 0),
88 prop_units(_("Offsets in _unit of pattern size"),
89 _("Spacing, tangential and normal offset are expressed as a ratio of width/height"),
90 "prop_units", &wr, this, false),
91 vertical_pattern(_("Pattern is _vertical"), _("Rotate pattern 90 deg before applying"),
92 "vertical_pattern", &wr, this, false),
93 hide_knot(_("Hide width knot"), _("Hide width knot"),"hide_knot", &wr, this, false),
94 fuse_tolerance(_("_Fuse nearby ends:"), _("Fuse ends closer than this number. 0 means don't fuse."),
95 "fuse_tolerance", &wr, this, 0)
96{
110 _knotholder = nullptr;
112}
113
120
121bool
123{
124 if (!is_load || is_applied) {
125 return false;
126 }
127 pattern.reload();
128 return false;
129}
130
132{
134 pattern.param_transform_multiply(postmul, false);
135 }
136}
137
138void
140{
141 // get the pattern bounding box
143 if (bbox) {
144 original_height = (*bbox)[Geom::Y].max() - (*bbox)[Geom::Y].min();
145 }
146 if (is_load) {
147 pattern.reload();
148 }
149 if (_knotholder && !_knotholder->entity.empty()) {
150 if (hide_knot) {
152 _knotholder->entity.front()->knot->hide();
153 } else {
154 _knotholder->entity.front()->knot->show();
155 }
157 }
158}
159
162{
163 using namespace Geom;
164
165 // Don't allow empty path parameter:
166 if ( pattern.get_pathvector().empty() ) {
167 return pwd2_in;
168 }
169
170/* Much credit should go to jfb and mgsloan of lib2geom development for the code below! */
171 Piecewise<D2<SBasis> > output;
172 std::vector<Geom::Piecewise<Geom::D2<Geom::SBasis> > > pre_output;
173
179 OptInterval pattBndsX = bounds_exact(x0);
180 OptInterval pattBndsY = bounds_exact(y0);
181 if (pattBndsX && pattBndsY) {
182 x0 -= pattBndsX->min();
183 y0 -= pattBndsY->middle();
184
185 double xspace = spacing;
186 double noffset = normal_offset;
187 double toffset = tang_offset;
188 if (prop_units.get_value()){
189 xspace *= pattBndsX->extent();
190 noffset *= pattBndsY->extent();
191 toffset *= pattBndsX->extent();
192 }
193
194 //Prevent more than 90% overlap...
195 if (xspace < -pattBndsX->extent() * 0.9) {
196 xspace = -pattBndsX->extent() * 0.9;
197 }
198 //TODO: dynamical update of parameter ranges?
199 //if (prop_units.get_value()){
200 // spacing.param_set_range(-.9, Geom::infinity());
201 // }else{
202 // spacing.param_set_range(-pattBndsX.extent()*.9, Geom::infinity());
203 // }
204
205 y0 += noffset;
206
207 std::vector<Geom::Piecewise<Geom::D2<Geom::SBasis> > > paths_in;
208 paths_in = split_at_discontinuities(pwd2_in);
209
210 for (auto path_i : paths_in){
211 Piecewise<SBasis> x = x0;
212 Piecewise<SBasis> y = y0;
213 Piecewise<D2<SBasis> > uskeleton = arc_length_parametrization(path_i,2, 0.1);
214 uskeleton = remove_short_cuts(uskeleton, 0.01);
215 Piecewise<D2<SBasis> > n = rot90(derivative(uskeleton));
216 if (Geom::are_near(pwd2_in[0].at0(),pwd2_in[pwd2_in.size()-1].at1(), 0.01)) {
217 n = force_continuity(remove_short_cuts(n, 0.1), 0.01);
218 } else {
219 n = force_continuity(remove_short_cuts(n, 0.1));
220 }
221 int nbCopies = 0;
222 double scaling = 1;
223 switch(type) {
224 case PAPCT_REPEATED:
225 nbCopies = static_cast<int>(floor((uskeleton.domain().extent() - toffset + xspace)/(pattBndsX->extent()+xspace)));
226 pattBndsX = Interval(pattBndsX->min(),pattBndsX->max()+xspace);
227 break;
228
229 case PAPCT_SINGLE:
230 nbCopies = (toffset + pattBndsX->extent() < uskeleton.domain().extent()) ? 1 : 0;
231 break;
232
234 nbCopies = 1;
235 scaling = (uskeleton.domain().extent() - toffset)/pattBndsX->extent();
236 break;
237
239 // if uskeleton is closed:
240 if (are_near(path_i.segs.front().at0(), path_i.segs.back().at1())){
241 nbCopies = std::max(1, static_cast<int>(std::floor((uskeleton.domain().extent() - toffset)/(pattBndsX->extent()+xspace))));
242 pattBndsX = Interval(pattBndsX->min(),pattBndsX->max()+xspace);
243 scaling = (uskeleton.domain().extent() - toffset)/(((double)nbCopies)*pattBndsX->extent());
244 // if not closed: no space at the end
245 }else{
246 nbCopies = std::max(1, static_cast<int>(std::floor((uskeleton.domain().extent() - toffset + xspace)/(pattBndsX->extent()+xspace))));
247 pattBndsX = Interval(pattBndsX->min(),pattBndsX->max()+xspace);
248 scaling = (uskeleton.domain().extent() - toffset)/(((double)nbCopies)*pattBndsX->extent() - xspace);
249 }
250 break;
251
252 default:
253 return pwd2_in;
254 };
255
256 //Ceil to 6 decimals
257 scaling = ceil(scaling * 1000000) / 1000000;
258 double pattWidth = pattBndsX->extent() * scaling;
259
260 x *= scaling;
261 if ( scale_y_rel.get_value() ) {
262 y *= prop_scale * scaling;
263 } else {
264 y *= prop_scale;
265 }
266 x += toffset;
267
268 double offs = 0;
269 for (int i=0; i<nbCopies; i++){
270 if (fuse_tolerance > 0){
271 Geom::Piecewise<Geom::D2<Geom::SBasis> > output_piece = compose(uskeleton,x+offs)+y*compose(n,x+offs);
272 std::vector<Geom::Piecewise<Geom::D2<Geom::SBasis> > > splited_output_piece = split_at_discontinuities(output_piece);
273 pre_output.insert(pre_output.end(), splited_output_piece.begin(), splited_output_piece.end() );
274 }else{
275 output.concat(compose(uskeleton,x+offs)+y*compose(n,x+offs));
276 }
277 offs+=pattWidth;
278 }
279 }
280 if (fuse_tolerance > 0){
281 pre_output = fuse_nearby_ends(pre_output, fuse_tolerance);
282 for (const auto & i : pre_output){
283 output.concat(i);
284 }
285 }
286 return output;
287 } else {
288 return pwd2_in;
289 }
290}
291
292void
293LPEPatternAlongPath::addCanvasIndicators(SPLPEItem const */*lpeitem*/, std::vector<Geom::PathVector> &hp_vec)
294{
295 hp_vec.push_back(helper_path);
296}
297
298
299void
301{
302 _knotholder = knotholder;
304 knot_entity->create(nullptr, item, knotholder, Inkscape::CANVAS_ITEM_CTRL_TYPE_LPE, "LPE:PatternAlongPath",
305 _("Change the width"));
306 _knotholder->add(knot_entity);
307 if (hide_knot) {
308 knot_entity->knot->hide();
309 knot_entity->update_knot();
310 }
311}
312
313namespace WPAP {
314
315void
316KnotHolderEntityWidthPatternAlongPath::knot_set(Geom::Point const &p, Geom::Point const& /*origin*/, guint state)
317{
318 LPEPatternAlongPath *lpe = dynamic_cast<LPEPatternAlongPath *> (_effect);
319
320 Geom::Point const s = snap_knot_position(p, state);
321 SPShape const *sp_shape = cast<SPShape>(cast<SPLPEItem>(item));
322 if (sp_shape && lpe->original_height) {
323 if (auto c = sp_shape->curveForEdit()) {
324 auto curve_before = *c;
325 Geom::Path const &path_in = curve_before.front();
326 Geom::Point ptA = path_in.pointAt(Geom::PathTime(0, 0.0));
327 Geom::Point B = path_in.pointAt(Geom::PathTime(1, 0.0));
328 Geom::Curve const *first_curve = &path_in.curveAt(Geom::PathTime(0, 0.0));
329 Geom::CubicBezier const *cubic = dynamic_cast<Geom::CubicBezier const *>(&*first_curve);
330 Geom::Ray ray(ptA, B);
331 if (cubic) {
332 ray.setPoints(ptA, (*cubic)[1]);
333 }
334 ray.setAngle(ray.angle() + Geom::rad_from_deg(90));
335 Geom::Point knot_pos = this->knot->pos * item->i2dt_affine().inverse();
336 Geom::Coord nearest_to_ray = ray.nearestTime(knot_pos);
337 if(nearest_to_ray == 0){
338 lpe->prop_scale.param_set_value(-Geom::distance(s , ptA)/(lpe->original_height/2.0));
339 } else {
341 }
342 }
343 if (!lpe->original_height) {
345 }
347 prefs->setDouble("/live_effects/skeletal/width", lpe->prop_scale);
348 }
349 sp_lpe_item_update_patheffect (cast<SPLPEItem>(item), false, true);
350}
351
353KnotHolderEntityWidthPatternAlongPath::knot_get() const
354{
355 LPEPatternAlongPath *lpe = dynamic_cast<LPEPatternAlongPath *> (_effect);
356 if (auto const sp_shape = cast<SPShape>(cast<SPLPEItem>(item))) {
357 if (auto c = sp_shape->curveForEdit()) {
358 auto curve_before = *c;
359 Geom::Path const &path_in = curve_before.front();
360 Geom::Point ptA = path_in.pointAt(Geom::PathTime(0, 0.0));
361 Geom::Point B = path_in.pointAt(Geom::PathTime(1, 0.0));
362 Geom::Curve const *first_curve = &path_in.curveAt(Geom::PathTime(0, 0.0));
363 Geom::CubicBezier const *cubic = dynamic_cast<Geom::CubicBezier const *>(&*first_curve);
364 Geom::Ray ray(ptA, B);
365 if (cubic) {
366 ray.setPoints(ptA, (*cubic)[1]);
367 }
368 ray.setAngle(ray.angle() + Geom::rad_from_deg(90));
369 Geom::Point result_point = Geom::Point::polar(ray.angle(), (lpe->original_height/2.0) * lpe->prop_scale) + ptA;
370 lpe->helper_path.clear();
371 if (!lpe->hide_knot) {
372 Geom::Path hp(result_point);
373 hp.appendNew<Geom::LineSegment>(ptA);
374 lpe->helper_path.push_back(hp);
375 hp.clear();
376 }
377 return result_point;
378 }
379 }
380 return Geom::Point();
381}
382} // namespace WPAP
383} // namespace LivePathEffect
384} /* namespace Inkscape */
385
386/*
387 Local Variables:
388 mode:c++
389 c-file-style:"stroustrup"
390 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
391 indent-tabs-mode:nil
392 fill-column:99
393 End:
394*/
395// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
Point origin
Definition aa.cpp:227
Conversion between Bezier control points and SBasis curves.
3x3 matrix representing an affine transformation.
Definition affine.h:70
Affine inverse() const
Compute the inverse matrix.
Definition affine.cpp:388
Bezier curve with compile-time specified order.
Abstract continuous curve on a plane defined on [0,1].
Definition curve.h:78
Adaptor that creates 2D functions from 1D ones.
Definition d2.h:55
constexpr C extent() const
Range of real numbers that is never empty.
Definition interval.h:59
Range of real numbers that can be empty.
Definition interval.h:199
Axis-aligned rectangle that can be empty.
Definition rect.h:203
OptRect boundsFast() const
void clear()
Remove all paths from the vector.
Definition pathvector.h:195
bool empty() const
Check whether the vector contains any paths.
Definition pathvector.h:145
Sequence of contiguous curves, aka spline.
Definition path.h:353
Point pointAt(Coord t) const
Get the point at the specified time value.
Definition path.cpp:449
Curve const & front() const
Access the first curve in the path.
Definition path.h:443
Curve const & curveAt(Coord t, Coord *rest=NULL) const
Get the curve at the specified time value.
Definition path.cpp:440
Function defined as discrete pieces.
Definition piecewise.h:71
Interval domain() const
Definition piecewise.h:215
void concat(const Piecewise< T > &other)
Definition piecewise.h:235
Two-dimensional point that doubles as a vector.
Definition point.h:66
static Point polar(Coord angle, Coord radius)
Construct a point from its polar coordinates.
Definition point.h:88
Straight ray from a specific point to infinity.
Definition ray.h:53
void registerParameter(Parameter *param)
Definition effect.cpp:1704
void addKnotHolderEntities(KnotHolder *knotholder, SPItem *item) override
void doBeforeEffect(SPLPEItem const *lpeitem) override
Is performed each time before the effect is updated.
void transform_multiply(Geom::Affine const &postmul, bool set) override
Overridden function to apply transforms for example to powerstroke, jointtype or tapperstroke.
Geom::Piecewise< Geom::D2< Geom::SBasis > > doEffect_pwd2(Geom::Piecewise< Geom::D2< Geom::SBasis > > const &pwd2_in) override
bool doOnOpen(SPLPEItem const *lpeitem) override
Is performed on load document or revert If the item is fixed legacy return true.
void addCanvasIndicators(SPLPEItem const *, std::vector< Geom::PathVector > &hp_vec) override
Add possible canvas indicators (i.e., helperpaths other than the original path) to hp_vec This functi...
LPEPatternAlongPath(LivePathEffectObject *lpeobject)
Geom::PathVector const & get_pathvector() const
Definition path.cpp:99
Geom::Piecewise< Geom::D2< Geom::SBasis > > const & get_pwd2()
Definition path.cpp:105
void param_transform_multiply(Geom::Affine const &postmul, bool set) override
Definition path.cpp:323
Geom::Affine get_relative_affine()
Definition path.cpp:86
void param_set_digits(unsigned digits)
void param_set_increments(double step, double page)
Preference storage class.
Definition preferences.h:61
static Preferences * get()
Access the singleton Preferences object.
void setDouble(Glib::ustring const &pref_path, double value)
Set a floating point value.
Simplified management of enumerations of svg items with UI labels.
Definition enums.h:42
KnotHolderEntity definition.
void create(SPDesktop *desktop, SPItem *item, KnotHolder *parent, Inkscape::CanvasItemCtrlType type=Inkscape::CANVAS_ITEM_CTRL_TYPE_DEFAULT, Glib::ustring const &name="unknown", char const *tip="", uint32_t color=0xffffff00)
virtual void update_knot()
void update_knots()
std::list< KnotHolderEntity * > entity
Definition knot-holder.h:85
void add(KnotHolderEntity *e)
Inkscape::LivePathEffect::Effect * _effect
Base class for visual SVG elements.
Definition sp-item.h:109
Geom::Affine i2dt_affine() const
Returns the transformation from item to desktop coords.
Definition sp-item.cpp:1829
void hide()
Hide knot on its canvas.
Definition knot.cpp:327
bool pathEffectsEnabled() const
bool optimizeTransforms()
returns false when LPE write unoptimiced
Base class for shapes, including <path> element.
Definition sp-shape.h:38
Geom::PathVector const * curveForEdit() const
Return a borrowed pointer of the curve for edit.
Definition sp-shape.cpp:983
double c[8][4]
double Coord
Floating point type used to store coordinates.
Definition coord.h:76
@ Y
Definition coord.h:48
auto floor(Geom::Rect const &rect)
Definition geom.h:131
SPItem * item
Various utility functions.
Definition affine.h:22
D2< Piecewise< SBasis > > make_cuts_independent(Piecewise< D2< SBasis > > const &a)
Definition d2-sbasis.cpp:75
OptInterval bounds_exact(Bezier const &b)
Definition bezier.cpp:310
Angle distance(Angle const &a, Angle const &b)
Definition angle.h:163
std::vector< Geom::Piecewise< Geom::D2< Geom::SBasis > > > split_at_discontinuities(Geom::Piecewise< Geom::D2< Geom::SBasis > > const &pwsbin, double tol=.0001)
std::vector< Piecewise< D2< SBasis > > > fuse_nearby_ends(std::vector< Piecewise< D2< SBasis > > > const &f, double tol=0)
D2< T > compose(D2< T > const &a, T const &b)
Definition d2.h:405
Bezier derivative(Bezier const &a)
Definition bezier.cpp:282
Piecewise< D2< SBasis > > arc_length_parametrization(D2< SBasis > const &M, unsigned order=3, double tol=.01)
Piecewise< D2< SBasis > > force_continuity(Piecewise< D2< SBasis > > const &f, double tol=0, bool closed=false)
bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON)
D2< T > rot90(D2< T > const &a)
Definition d2.h:397
static const Util::EnumData< PAPCopyType > PAPCopyTypeData[PAPCT_END]
static const Util::EnumDataConverter< PAPCopyType > PAPCopyTypeConverter(PAPCopyTypeData, PAPCT_END)
Helper class to stream background task notifications as a series of messages.
@ CANVAS_ITEM_CTRL_TYPE_LPE
Singleton class to access the preferences file in a convenient way.
void sp_lpe_item_update_patheffect(SPLPEItem *lpeitem, bool wholetree, bool write, bool with_satellites)
Calls any registered handlers for the update_patheffect action.
Generalized time value in the path.
Definition path.h:139
Simplified management of enumerations of svg items with UI labels.
Definition enums.h:27