lpe-roughen.cpp 24.4 KB
Newer Older
Max Gaukler's avatar
Max Gaukler committed
1
// SPDX-License-Identifier: GPL-2.0-or-later
2
3
4
5
6
7
8
/**
 * @file
 * Roughen LPE implementation. Creates roughen paths.
 */
/* Authors:
 *   Jabier Arraiza Cenoz <jabier.arraiza@marker.es>
 *
luz.paz's avatar
luz.paz committed
9
 * Thanks to all people involved specially to Josh Andler for the idea and to the
10
11
12
13
 * original extensions authors.
 *
 * Copyright (C) 2014 Authors
 *
Max Gaukler's avatar
Max Gaukler committed
14
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
Jabier Arraiza's avatar
Jabier Arraiza committed
15
16
 */

17
#include "live_effects/lpe-roughen.h"
18
19
#include "display/curve.h"
#include "helper/geom.h"
20
21
#include <boost/functional/hash.hpp>
#include <gtkmm.h>
Jabiertxof's avatar
Jabiertxof committed
22

Jabier Arraiza's avatar
Jabier Arraiza committed
23
// TODO due to internal breakage in glibmm headers, this must be last:
Jabiertxof's avatar
Jabiertxof committed
24
#include <glibmm/i18n.h>
Jabier Arraiza's avatar
Jabier Arraiza committed
25
26
27
28

namespace Inkscape {
namespace LivePathEffect {

29
30
31
static const Util::EnumData<DivisionMethod> DivisionMethodData[DM_END] = { { DM_SEGMENTS, N_("By number of segments"), "segments" }, 
                                                                           { DM_SIZE, N_("By max. segment size"), "size" } };
static const Util::EnumDataConverter<DivisionMethod> DMConverter(DivisionMethodData, DM_END);
Jabier Arraiza's avatar
Jabier Arraiza committed
32

33
34
35
36
37
static const Util::EnumData<HandlesMethod> HandlesMethodData[HM_END] = { { HM_ALONG_NODES, N_("Along nodes"), "along" },
                                                                         { HM_RAND, N_("Rand"), "rand" },
                                                                         { HM_RETRACT, N_("Retract"), "retract" },
                                                                         { HM_SMOOTH, N_("Smooth"), "smooth" } };
static const Util::EnumDataConverter<HandlesMethod> HMConverter(HandlesMethodData, HM_END);
38

Jabier Arraiza's avatar
Jabier Arraiza committed
39
LPERoughen::LPERoughen(LivePathEffectObject *lpeobject)
40
41
42
43
44
45
46
47
48
49
50
51
52
    : Effect(lpeobject)
    , method(_("Method"), _("Division method"), "method", DMConverter, &wr, this, DM_SIZE)
    , max_segment_size(_("Max. segment size"), _("Max. segment size"), "max_segment_size", &wr, this, 10)
    , segments(_("Number of segments"), _("Number of segments"), "segments", &wr, this, 2)
    , displace_x(_("Max. displacement in X"), _("Max. displacement in X"), "displace_x", &wr, this, 10.)
    , displace_y(_("Max. displacement in Y"), _("Max. displacement in Y"), "displace_y", &wr, this, 10.)
    , global_randomize(_("Global randomize"), _("Global randomize"), "global_randomize", &wr, this, 1.)
    , handles(_("Handles"), _("Handles options"), "handles", HMConverter, &wr, this, HM_ALONG_NODES)
    , shift_nodes(_("Shift nodes"), _("Shift nodes"), "shift_nodes", &wr, this, true)
    , fixed_displacement(_("Fixed displacement"), _("Fixed displacement, 1/3 of segment length"), "fixed_displacement",
                         &wr, this, false)
    , spray_tool_friendly(_("Spray Tool friendly"), _("For use with spray tool in copy mode"), "spray_tool_friendly",
                          &wr, this, false)
53
54
{
    registerParameter(&method);
55
    registerParameter(&max_segment_size);
56
    registerParameter(&segments);
57
58
59
    registerParameter(&displace_x);
    registerParameter(&displace_y);
    registerParameter(&global_randomize);
60
    registerParameter(&handles);
61
    registerParameter(&shift_nodes);
62
    registerParameter(&fixed_displacement);
63
    registerParameter(&spray_tool_friendly);
64
65
66
67
68
    displace_x.param_set_range(0., Geom::infinity());
    displace_y.param_set_range(0., Geom::infinity());
    global_randomize.param_set_range(0., Geom::infinity());
    max_segment_size.param_set_range(0., Geom::infinity());
    max_segment_size.param_set_increments(1, 1);
69
    max_segment_size.param_set_digits(3);
70
71
72
    segments.param_set_range(1, Geom::infinity());
    segments.param_set_increments(1, 1);
    segments.param_set_digits(0);
73
    seed = 0;
Jabier Arraiza's avatar
Jabier Arraiza committed
74
    apply_to_clippath_and_mask = true;
Jabier Arraiza's avatar
Jabier Arraiza committed
75
76
}

77
LPERoughen::~LPERoughen() = default;
Jabier Arraiza's avatar
Jabier Arraiza committed
78

79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
void LPERoughen::doOnApply(SPLPEItem const *lpeitem)
{
    Geom::OptRect bbox = lpeitem->bounds(SPItem::GEOMETRIC_BBOX);
    if (bbox) {
        std::vector<Parameter *>::iterator it = param_vector.begin();
        Inkscape::Preferences *prefs = Inkscape::Preferences::get();
        while (it != param_vector.end()) {
            Parameter *param = *it;
            const gchar *key = param->param_key.c_str();
            Glib::ustring pref_path = (Glib::ustring) "/live_effects/" +
                                      (Glib::ustring)LPETypeConverter.get_key(effectType()).c_str() +
                                      (Glib::ustring) "/" + (Glib::ustring)key;


            bool valid = prefs->getEntry(pref_path).isValid();
            Glib::ustring displace_x_str = Glib::ustring::format((*bbox).width() / 100.0);
            Glib::ustring displace_y_str = Glib::ustring::format((*bbox).height() / 100.0);
96
            Glib::ustring max_segment_size_str = Glib::ustring::format(std::min((*bbox).height(), (*bbox).width()) / 100.0);
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
            if (!valid) {
                if (strcmp(key, "method") == 0) {
                    param->param_readSVGValue("size");
                } else if (strcmp(key, "max_segment_size") == 0) {
                    param->param_readSVGValue(max_segment_size_str.c_str());
                } else if (strcmp(key, "displace_x") == 0) {
                    param->param_readSVGValue(displace_x_str.c_str());
                } else if (strcmp(key, "displace_y") == 0) {
                    param->param_readSVGValue(displace_y_str.c_str());
                } else if (strcmp(key, "handles") == 0) {
                    param->param_readSVGValue("along");
                } else if (strcmp(key, "shift_nodes") == 0) {
                    param->param_readSVGValue("true");
                } else if (strcmp(key, "fixed_displacement") == 0) {
                    param->param_readSVGValue("true");
                } else if (strcmp(key, "spray_tool_friendly") == 0) {
                    param->param_readSVGValue("true");
                }
            }
            ++it;
        }
    }
}

121
void LPERoughen::doBeforeEffect(SPLPEItem const *lpeitem)
122
{
123
    if (spray_tool_friendly && seed == 0 && SP_OBJECT(lpeitem)->getId()) {
124
125
126
127
        std::string id_item(SP_OBJECT(lpeitem)->getId());
        long seed = static_cast<long>(boost::hash_value(id_item));
        global_randomize.param_set_value(global_randomize.get_value(), seed);
    }
128
129
    displace_x.resetRandomizer();
    displace_y.resetRandomizer();
130
    global_randomize.resetRandomizer();
131
    srand(1);
Jabier Arraiza's avatar
Jabier Arraiza committed
132
133
}

134
135
136
137
138
139
140
141
142
143
144
145
Gtk::Widget *LPERoughen::newWidget()
{
    Gtk::VBox *vbox = Gtk::manage(new Gtk::VBox(Effect::newWidget()));
    vbox->set_border_width(5);
    vbox->set_homogeneous(false);
    vbox->set_spacing(2);
    std::vector<Parameter *>::iterator it = param_vector.begin();
    while (it != param_vector.end()) {
        if ((*it)->widget_is_visible) {
            Parameter *param = *it;
            Gtk::Widget *widg = dynamic_cast<Gtk::Widget *>(param->param_newWidget());
            if (param->param_key == "method") {
146
147
                Gtk::Label *method_label = Gtk::manage(
                    new Gtk::Label(Glib::ustring(_("<b>Add nodes</b> Subdivide each segment")), Gtk::ALIGN_START));
148
149
                method_label->set_use_markup(true);
                vbox->pack_start(*method_label, false, false, 2);
Alex Valavanis's avatar
Alex Valavanis committed
150
                vbox->pack_start(*Gtk::manage(new Gtk::Separator(Gtk::ORIENTATION_HORIZONTAL)),
151
152
                                 Gtk::PACK_EXPAND_WIDGET);
            }
153
            if (param->param_key == "displace_x") {
154
155
                Gtk::Label *displace_x_label = Gtk::manage(
                    new Gtk::Label(Glib::ustring(_("<b>Jitter nodes</b> Move nodes/handles")), Gtk::ALIGN_START));
156
157
                displace_x_label->set_use_markup(true);
                vbox->pack_start(*displace_x_label, false, false, 2);
Alex Valavanis's avatar
Alex Valavanis committed
158
                vbox->pack_start(*Gtk::manage(new Gtk::Separator(Gtk::ORIENTATION_HORIZONTAL)),
159
160
                                 Gtk::PACK_EXPAND_WIDGET);
            }
161
162
            if (param->param_key == "global_randomize") {
                Gtk::Label *global_rand = Gtk::manage(new Gtk::Label(
163
                    Glib::ustring(_("<b>Extra roughen</b> Add an extra layer of rough")), Gtk::ALIGN_START));
164
165
                global_rand->set_use_markup(true);
                vbox->pack_start(*global_rand, false, false, 2);
Alex Valavanis's avatar
Alex Valavanis committed
166
                vbox->pack_start(*Gtk::manage(new Gtk::Separator(Gtk::ORIENTATION_HORIZONTAL)),
167
168
                                 Gtk::PACK_EXPAND_WIDGET);
            }
169
            if (param->param_key == "handles") {
170
171
                Gtk::Label *options = Gtk::manage(
                    new Gtk::Label(Glib::ustring(_("<b>Options</b> Modify options to rough")), Gtk::ALIGN_START));
172
173
                options->set_use_markup(true);
                vbox->pack_start(*options, false, false, 2);
Alex Valavanis's avatar
Alex Valavanis committed
174
                vbox->pack_start(*Gtk::manage(new Gtk::Separator(Gtk::ORIENTATION_HORIZONTAL)),
175
176
                                 Gtk::PACK_EXPAND_WIDGET);
            }
177
178
179
180
181
182
183
184
185
186
            Glib::ustring *tip = param->param_getTooltip();
            if (widg) {
                vbox->pack_start(*widg, true, true, 2);
                if (tip) {
                    widg->set_tooltip_text(*tip);
                } else {
                    widg->set_tooltip_text("");
                    widg->set_has_tooltip(false);
                }
            }
Jabier Arraiza's avatar
Jabier Arraiza committed
187
        }
188
189
        ++it;
    }
190
    if (Gtk::Widget *widg = defaultParamSet()) {
Jabier Arraiza's avatar
Jabier Arraiza committed
191
192
        vbox->pack_start(*widg, true, true, 2);
    }
193
    return dynamic_cast<Gtk::Widget *>(vbox);
Jabier Arraiza's avatar
Jabier Arraiza committed
194
195
}

196
double LPERoughen::sign(double random_number)
197
198
{
    if (rand() % 100 < 49) {
199
        random_number *= -1.;
200
    }
201
    return random_number;
Jabier Arraiza's avatar
Jabier Arraiza committed
202
203
}

Yuri Chornoivan's avatar
Yuri Chornoivan committed
204
Geom::Point LPERoughen::randomize(double max_length, bool is_node)
205
{
206
207
    double factor = 1.0 / 3.0;
    if (is_node) {
208
        factor = 1.0;
209
    }
210
211
212
    double displace_x_parsed = displace_x * global_randomize * factor;
    double displace_y_parsed = displace_y * global_randomize * factor;
    Geom::Point output = Geom::Point(sign(displace_x_parsed), sign(displace_y_parsed));
213
214
215
    if (fixed_displacement) {
        Geom::Ray ray(Geom::Point(0, 0), output);
        output = Geom::Point::polar(ray.angle(), max_length);
216
    }
217
218
    return output;
}
Jabier Arraiza's avatar
Jabier Arraiza committed
219

220
221
void LPERoughen::doEffect(SPCurve *curve)
{
222
    Geom::PathVector const original_pathv = pathv_to_linear_and_cubic_beziers(curve->get_pathvector());
223
    curve->reset();
224
    for (const auto &path_it : original_pathv) {
Marc Jeanmougin's avatar
Marc Jeanmougin committed
225
        if (path_it.empty())
226
            continue;
Jabier Arraiza's avatar
Jabier Arraiza committed
227

Marc Jeanmougin's avatar
Marc Jeanmougin committed
228
229
230
        Geom::Path::const_iterator curve_it1 = path_it.begin();
        Geom::Path::const_iterator curve_it2 = ++(path_it.begin());
        Geom::Path::const_iterator curve_endit = path_it.end_default();
231
        SPCurve *nCurve = new SPCurve();
Jabier Arraiza's avatar
Jabier Arraiza committed
232
        Geom::Point prev(0, 0);
233
        Geom::Point last_move(0, 0);
234
        nCurve->moveto(curve_it1->initialPoint());
Marc Jeanmougin's avatar
Marc Jeanmougin committed
235
236
        if (path_it.closed()) {
          const Geom::Curve &closingline = path_it.back_closed(); 
237
238
239
240
241
242
243
          // the closing line segment is always of type 
          // Geom::LineSegment.
          if (are_near(closingline.initialPoint(), closingline.finalPoint())) {
            // closingline.isDegenerate() did not work, because it only checks for
            // *exact* zero length, which goes wrong for relative coordinates and
            // rounding errors...
            // the closing line segment has zero-length. So stop before that one!
Marc Jeanmougin's avatar
Marc Jeanmougin committed
244
            curve_endit = path_it.end_open();
245
246
          }
        }
247
        while (curve_it1 != curve_endit) {
248
            Geom::CubicBezier const *cubic = nullptr;
249
250
            cubic = dynamic_cast<Geom::CubicBezier const *>(&*curve_it1);
            if (cubic) {
251
                nCurve->curveto((*cubic)[1] + last_move, (*cubic)[2], curve_it1->finalPoint());
252
            } else {
253
                nCurve->lineto(curve_it1->finalPoint());
254
            }
255
            last_move = Geom::Point(0, 0);
256
            double length = curve_it1->length(0.01);
257
258
259
260
            std::size_t splits = 0;
            if (method == DM_SEGMENTS) {
                splits = segments;
            } else {
261
                splits = ceil(length / max_segment_size);
262
            }
Jabier Arraiza's avatar
Jabier Arraiza committed
263
264
            Geom::Curve const * original = nCurve->last_segment()->duplicate() ;
            for (unsigned int t = 1; t <= splits; t++) {
265
                if (t == splits && splits != 1) {
266
267
                    continue;
                }
Jabier Arraiza's avatar
Jabier Arraiza committed
268
                SPCurve const * tmp;
269
                if (splits == 1) {
270
                    tmp = jitter(nCurve->last_segment(), prev, last_move);
271
                } else {
Jabier Arraiza's avatar
Jabier Arraiza committed
272
                    bool last = false;
273
                    if (t == splits - 1) {
Jabier Arraiza's avatar
Jabier Arraiza committed
274
275
276
                        last = true;
                    }
                    double time = Geom::nearest_time(original->pointAt((1. / (double)splits) * t), *nCurve->last_segment());
277
                    tmp = addNodesAndJitter(nCurve->last_segment(), prev, last_move, time, last);
278
279
                }
                if (nCurve->get_segment_count() > 1) {
280
                    nCurve->backspace();
281
282
283
284
285
286
287
                    nCurve->append_continuous(tmp, 0.001);
                } else {
                    nCurve = tmp->copy();
                }
                delete tmp;
            }
            ++curve_it1;
288
            ++curve_it2;
Jabier Arraiza's avatar
Jabier Arraiza committed
289
        }
Marc Jeanmougin's avatar
Marc Jeanmougin committed
290
        if (path_it.closed()) {
291
            if (handles == HM_SMOOTH && curve_it1 == curve_endit) {
Jabier Arraiza's avatar
Jabier Arraiza committed
292
293
294
295
                SPCurve *out = new SPCurve();
                nCurve = nCurve->create_reverse();
                Geom::CubicBezier const *cubic_start = dynamic_cast<Geom::CubicBezier const *>(nCurve->first_segment());
                Geom::CubicBezier const *cubic = dynamic_cast<Geom::CubicBezier const *>(nCurve->last_segment());
296
297
                Geom::Point oposite = nCurve->first_segment()->pointAt(1.0 / 3.0);
                if (cubic_start) {
Jabier Arraiza's avatar
Jabier Arraiza committed
298
299
                    Geom::Ray ray((*cubic_start)[1], (*cubic_start)[0]);
                    double dist = Geom::distance((*cubic_start)[1], (*cubic_start)[0]);
300
                    oposite = Geom::Point::polar(ray.angle(), dist) + (*cubic_start)[0];
Jabier Arraiza's avatar
Jabier Arraiza committed
301
                }
302
                if (cubic) {
Jabier Arraiza's avatar
Jabier Arraiza committed
303
304
305
306
307
308
309
310
                    out->moveto((*cubic)[0]);
                    out->curveto((*cubic)[1], oposite, (*cubic)[3]);
                } else {
                    out->moveto(nCurve->last_segment()->initialPoint());
                    out->curveto(nCurve->last_segment()->initialPoint(), oposite, nCurve->last_segment()->finalPoint());
                }
                nCurve->backspace();
                nCurve->append_continuous(out, 0.001);
311
                nCurve = nCurve->create_reverse();
Jabier Arraiza's avatar
Jabier Arraiza committed
312
            }
313
            if (handles == HM_ALONG_NODES && curve_it1 == curve_endit) {
314
315
316
                SPCurve *out = new SPCurve();
                nCurve = nCurve->create_reverse();
                Geom::CubicBezier const *cubic = dynamic_cast<Geom::CubicBezier const *>(nCurve->last_segment());
317
                if (cubic) {
318
                    out->moveto((*cubic)[0]);
319
320
                    out->curveto((*cubic)[1], (*cubic)[2] - ((*cubic)[3] - nCurve->first_segment()->initialPoint()),
                                 (*cubic)[3]);
321
322
                    nCurve->backspace();
                    nCurve->append_continuous(out, 0.001);
323
                }
324
325
                nCurve = nCurve->create_reverse();
            }
326
            nCurve->move_endpoints(nCurve->last_segment()->finalPoint(), nCurve->last_segment()->finalPoint());
327
            nCurve->closepath_current();
Jabier Arraiza's avatar
Jabier Arraiza committed
328
        }
329
330
331
        curve->append(nCurve, false);
        nCurve->reset();
        delete nCurve;
Jabier Arraiza's avatar
Jabier Arraiza committed
332
    }
333
334
}

335
336
SPCurve const * LPERoughen::addNodesAndJitter(Geom::Curve const *A, Geom::Point &prev, Geom::Point &last_move, double t,
                                             bool last)
337
338
339
{
    SPCurve *out = new SPCurve();
    Geom::CubicBezier const *cubic = dynamic_cast<Geom::CubicBezier const *>(&*A);
340
    double max_length = Geom::distance(A->initialPoint(), A->pointAt(t)) / 3.0;
341
342
343
    Geom::Point point_a1(0, 0);
    Geom::Point point_a2(0, 0);
    Geom::Point point_a3(0, 0);
344
345
346
347
    Geom::Point point_b1(0, 0);
    Geom::Point point_b2(0, 0);
    Geom::Point point_b3(0, 0);
    if (shift_nodes) {
Yuri Chornoivan's avatar
Yuri Chornoivan committed
348
        point_a3 = randomize(max_length, true);
349
        if (last) {
Yuri Chornoivan's avatar
Yuri Chornoivan committed
350
            point_b3 = randomize(max_length, true);
351
        }
Jabier Arraiza's avatar
Jabier Arraiza committed
352
    }
353
    if (handles == HM_RAND || handles == HM_SMOOTH) {
Yuri Chornoivan's avatar
Yuri Chornoivan committed
354
355
356
        point_a1 = randomize(max_length);
        point_a2 = randomize(max_length);
        point_b1 = randomize(max_length);
357
        if (last) {
Yuri Chornoivan's avatar
Yuri Chornoivan committed
358
            point_b2 = randomize(max_length);
359
360
361
362
        }
    } else {
        point_a2 = point_a3;
        point_b1 = point_a3;
363
        if (last) {
364
            point_b2 = point_b3;
Jabier Arraiza's avatar
Jabier Arraiza committed
365
        }
Jabier Arraiza's avatar
Jabier Arraiza committed
366
    }
367
368
    if (handles == HM_SMOOTH) {
        if (cubic) {
369
            std::pair<Geom::CubicBezier, Geom::CubicBezier> div = cubic->subdivide(t);
370
            std::vector<Geom::Point> seg1 = div.first.controlPoints(), seg2 = div.second.controlPoints();
Jabier Arraiza's avatar
Jabier Arraiza committed
371
            Geom::Ray ray(seg1[3] + point_a3, seg2[1] + point_a3);
Yuri Chornoivan's avatar
Yuri Chornoivan committed
372
            double length = max_length;
373
            if (!fixed_displacement) {
Yuri Chornoivan's avatar
Yuri Chornoivan committed
374
                length = Geom::distance(seg1[3] + point_a3, seg2[1] + point_a3);
Jabier Arraiza's avatar
Jabier Arraiza committed
375
            }
376
377
378
            point_b1 = seg1[3] + point_a3 + Geom::Point::polar(ray.angle(), length);
            point_b2 = seg2[2];
            point_b3 = seg2[3] + point_b3;
379
            point_a3 = seg1[3] + point_a3;
380
381
382
            ray.setPoints(prev, A->initialPoint());
            point_a1 = A->initialPoint() + Geom::Point::polar(ray.angle(), max_length);
            if (last) {
383
384
                Geom::Path b2(point_b3);
                b2.appendNew<Geom::LineSegment>(point_a3);
Yuri Chornoivan's avatar
Yuri Chornoivan committed
385
                length = max_length;
Jabier Arraiza's avatar
Jabier Arraiza committed
386
                ray.setPoints(point_b3, point_b2);
387
388
                if (!fixed_displacement) {
                    length = Geom::distance(b2.pointAt(1.0 / 3.0), point_b3);
Jabier Arraiza's avatar
Jabier Arraiza committed
389
                }
390
                point_b2 = point_b3 + Geom::Point::polar(ray.angle(), length);
391
392
            }
            ray.setPoints(point_b1, point_a3);
393
394
            point_a2 = point_a3 + Geom::Point::polar(ray.angle(), max_length);
            if (last) {
395
396
397
398
399
                prev = point_b2;
            } else {
                prev = point_a2;
            }
            out->moveto(seg1[0]);
400
            out->curveto(point_a1, point_a2, point_a3);
401
402
            out->curveto(point_b1, point_b2, point_b3);
        } else {
403
            Geom::Ray ray(A->pointAt(t) + point_a3, A->pointAt(t + (t / 3)));
Yuri Chornoivan's avatar
Yuri Chornoivan committed
404
            double length = max_length;
405
406
            if (!fixed_displacement) {
                length = Geom::distance(A->pointAt(t) + point_a3, A->pointAt(t + (t / 3)));
Jabier Arraiza's avatar
Jabier Arraiza committed
407
            }
408
409
410
            point_b1 = A->pointAt(t) + point_a3 + Geom::Point::polar(ray.angle(), length);
            point_b2 = A->pointAt(t + ((t / 3) * 2));
            point_b3 = A->finalPoint() + point_b3;
411
            point_a3 = A->pointAt(t) + point_a3;
412
413
414
            ray.setPoints(prev, A->initialPoint());
            point_a1 = A->initialPoint() + Geom::Point::polar(ray.angle(), max_length);
            if (prev == Geom::Point(0, 0)) {
Yuri Chornoivan's avatar
Yuri Chornoivan committed
415
                point_a1 = randomize(max_length);
416
            }
417
            if (last) {
418
419
                Geom::Path b2(point_b3);
                b2.appendNew<Geom::LineSegment>(point_a3);
Yuri Chornoivan's avatar
Yuri Chornoivan committed
420
                length = max_length;
Jabier Arraiza's avatar
Jabier Arraiza committed
421
                ray.setPoints(point_b3, point_b2);
422
423
                if (!fixed_displacement) {
                    length = Geom::distance(b2.pointAt(1.0 / 3.0), point_b3);
Jabier Arraiza's avatar
Jabier Arraiza committed
424
                }
425
                point_b2 = point_b3 + Geom::Point::polar(ray.angle(), length);
426
427
            }
            ray.setPoints(point_b1, point_a3);
428
429
            point_a2 = point_a3 + Geom::Point::polar(ray.angle(), max_length);
            if (last) {
430
431
432
433
434
                prev = point_b2;
            } else {
                prev = point_a2;
            }
            out->moveto(A->initialPoint());
435
            out->curveto(point_a1, point_a2, point_a3);
436
437
            out->curveto(point_b1, point_b2, point_b3);
        }
438
    } else if (handles == HM_RETRACT) {
439
440
        out->moveto(A->initialPoint());
        out->lineto(A->pointAt(t) + point_a3);
441
        if (cubic && !last) {
442
443
444
445
446
447
            std::pair<Geom::CubicBezier, Geom::CubicBezier> div = cubic->subdivide(t);
            std::vector<Geom::Point> seg2 = div.second.controlPoints();
            out->curveto(seg2[1], seg2[2], seg2[3]);
        } else {
            out->lineto(A->finalPoint() + point_b3);
        }
448
    } else if (handles == HM_ALONG_NODES) {
449
450
        if (cubic) {
            std::pair<Geom::CubicBezier, Geom::CubicBezier> div = cubic->subdivide(t);
451
            std::vector<Geom::Point> seg1 = div.first.controlPoints(), seg2 = div.second.controlPoints();
452
            out->moveto(seg1[0]);
453
454
            out->curveto(seg1[1] + last_move, seg1[2] + point_a3, seg1[3] + point_a3);
            last_move = point_a3;
455
            if (last) {
456
457
                last_move = point_b3;
            }
458
            out->curveto(seg2[1] + point_a3, seg2[2] + point_b3, seg2[3] + point_b3);
459
        } else {
460
461
462
463
            out->moveto(A->initialPoint());
            out->lineto(A->pointAt(t) + point_a3);
            out->lineto(A->finalPoint() + point_b3);
        }
464
    } else if (handles == HM_RAND) {
465
466
        if (cubic) {
            std::pair<Geom::CubicBezier, Geom::CubicBezier> div = cubic->subdivide(t);
467
            std::vector<Geom::Point> seg1 = div.first.controlPoints(), seg2 = div.second.controlPoints();
468
            out->moveto(seg1[0]);
469
            out->curveto(seg1[1] + point_a1, seg1[2] + point_a2 + point_a3, seg1[3] + point_a3);
470
            out->curveto(seg2[1] + point_a3 + point_b1, seg2[2] + point_b2 + point_b3, seg2[3] + point_b3);
471
472
473
474
        } else {
            out->moveto(A->initialPoint());
            out->lineto(A->pointAt(t) + point_a3);
            out->lineto(A->finalPoint() + point_b3);
475
        }
Jabier Arraiza's avatar
Jabier Arraiza committed
476
    }
477
    return out;
Jabier Arraiza's avatar
Jabier Arraiza committed
478
479
}

480
SPCurve *LPERoughen::jitter(Geom::Curve const *A, Geom::Point &prev, Geom::Point &last_move)
481
482
483
{
    SPCurve *out = new SPCurve();
    Geom::CubicBezier const *cubic = dynamic_cast<Geom::CubicBezier const *>(&*A);
484
    double max_length = Geom::distance(A->initialPoint(), A->finalPoint()) / 3.0;
485
486
487
    Geom::Point point_a1(0, 0);
    Geom::Point point_a2(0, 0);
    Geom::Point point_a3(0, 0);
488
    if (shift_nodes) {
Yuri Chornoivan's avatar
Yuri Chornoivan committed
489
        point_a3 = randomize(max_length, true);
490
    }
491
    if (handles == HM_RAND || handles == HM_SMOOTH) {
Yuri Chornoivan's avatar
Yuri Chornoivan committed
492
493
        point_a1 = randomize(max_length);
        point_a2 = randomize(max_length);
494
    }
495
    if (handles == HM_SMOOTH) {
496
        if (cubic) {
497
498
499
500
            Geom::Ray ray(prev, A->initialPoint());
            point_a1 = Geom::Point::polar(ray.angle(), max_length);
            if (prev == Geom::Point(0, 0)) {
                point_a1 = A->pointAt(1.0 / 3.0) + randomize(max_length);
501
502
            }
            ray.setPoints((*cubic)[3] + point_a3, (*cubic)[2] + point_a3);
503
            point_a2 = randomize(max_length, ray.angle());
504
505
506
507
            prev = (*cubic)[2] + point_a2;
            out->moveto((*cubic)[0]);
            out->curveto((*cubic)[0] + point_a1, (*cubic)[2] + point_a2 + point_a3, (*cubic)[3] + point_a3);
        } else {
508
            Geom::Ray ray(prev, A->initialPoint());
Yuri Chornoivan's avatar
Yuri Chornoivan committed
509
            point_a1 = Geom::Point::polar(ray.angle(), max_length);
510
511
            if (prev == Geom::Point(0, 0)) {
                point_a1 = A->pointAt(1.0 / 3.0) + randomize(max_length);
512
            }
513
514
515
            ray.setPoints(A->finalPoint() + point_a3, A->pointAt((1.0 / 3.0) * 2) + point_a3);
            point_a2 = randomize(max_length, ray.angle());
            prev = A->pointAt((1.0 / 3.0) * 2) + point_a2 + point_a3;
516
            out->moveto(A->initialPoint());
517
518
            out->curveto(A->initialPoint() + point_a1, A->pointAt((1.0 / 3.0) * 2) + point_a2 + point_a3,
                         A->finalPoint() + point_a3);
519
        }
520
    } else if (handles == HM_RETRACT) {
521
        out->moveto(A->initialPoint());
522
523
        out->lineto(A->finalPoint() + point_a3);
    } else if (handles == HM_ALONG_NODES) {
524
        if (cubic) {
525
            out->moveto((*cubic)[0]);
526
527
528
            out->curveto((*cubic)[1] + last_move, (*cubic)[2] + point_a3, (*cubic)[3] + point_a3);
            last_move = point_a3;
        } else {
529
530
            out->moveto(A->initialPoint());
            out->lineto(A->finalPoint() + point_a3);
531
532
        }
    } else if (handles == HM_RAND) {
533
        out->moveto(A->initialPoint());
534
        out->curveto(A->pointAt(0.3333) + point_a1, A->pointAt(0.6666) + point_a2 + point_a3,
535
                     A->finalPoint() + point_a3);
536
537
    }
    return out;
Jabier Arraiza's avatar
Jabier Arraiza committed
538
539
}

540
Geom::Point LPERoughen::tPoint(Geom::Point A, Geom::Point B, double t)
541
542
543
544
{
    using Geom::X;
    using Geom::Y;
    return Geom::Point(A[X] + t * (B[X] - A[X]), A[Y] + t * (B[Y] - A[Y]));
Jabier Arraiza's avatar
Jabier Arraiza committed
545
546
}

547
}; // namespace LivePathEffect
Jabier Arraiza's avatar
Jabier Arraiza committed
548
549
550
551
552
553
554
555
556
557
558
559
}; /* namespace Inkscape */

/*
  Local Variables:
  mode:c++
  c-file-style:"stroustrup"
  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
  indent-tabs-mode:nil
  fill-column:99
  End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :