summaryrefslogtreecommitdiffstats
path: root/src/live_effects/lpe-curvestitch.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/live_effects/lpe-curvestitch.cpp')
-rw-r--r--src/live_effects/lpe-curvestitch.cpp215
1 files changed, 215 insertions, 0 deletions
diff --git a/src/live_effects/lpe-curvestitch.cpp b/src/live_effects/lpe-curvestitch.cpp
new file mode 100644
index 0000000..f1f74dd
--- /dev/null
+++ b/src/live_effects/lpe-curvestitch.cpp
@@ -0,0 +1,215 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ * LPE Curve Stitching implementation, used as an example for a base starting class
+ * when implementing new LivePathEffects.
+ *
+ */
+/*
+ * Authors:
+ * Johan Engelen
+ *
+ * Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "ui/widget/scalar.h"
+#include "live_effects/lpe-curvestitch.h"
+
+#include "object/sp-path.h"
+
+#include "svg/svg.h"
+#include "xml/repr.h"
+
+#include <2geom/bezier-to-sbasis.h>
+
+// TODO due to internal breakage in glibmm headers, this must be last:
+#include <glibmm/i18n.h>
+
+
+namespace Inkscape {
+namespace LivePathEffect {
+
+using namespace Geom;
+
+LPECurveStitch::LPECurveStitch(LivePathEffectObject *lpeobject) :
+ Effect(lpeobject),
+ strokepath(_("Stitch path:"), _("The path that will be used as stitch."), "strokepath", &wr, this, "M0,0 L1,0"),
+ nrofpaths(_("N_umber of paths:"), _("The number of paths that will be generated."), "count", &wr, this, 5),
+ 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),
+ 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),
+ 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),
+ 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),
+ prop_scale(_("Scale _width:"), _("Scale the width of the stitch path"), "prop_scale", &wr, this, 1),
+ 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)
+{
+ registerParameter(&nrofpaths);
+ registerParameter(&startpoint_edge_variation);
+ registerParameter(&startpoint_spacing_variation);
+ registerParameter(&endpoint_edge_variation);
+ registerParameter(&endpoint_spacing_variation);
+ registerParameter(&strokepath );
+ registerParameter(&prop_scale);
+ registerParameter(&scale_y_rel);
+
+ nrofpaths.param_make_integer();
+ nrofpaths.param_set_range(2, Geom::infinity());
+
+ prop_scale.param_set_digits(3);
+ prop_scale.param_set_increments(0.01, 0.10);
+ transformed = false;
+}
+
+LPECurveStitch::~LPECurveStitch()
+= default;
+
+bool
+LPECurveStitch::doOnOpen(SPLPEItem const *lpeitem)
+{
+ if (!is_load || is_applied) {
+ return false;
+ }
+ strokepath.reload();
+ return false;
+}
+
+
+Geom::PathVector
+LPECurveStitch::doEffect_path (Geom::PathVector const & path_in)
+{
+
+ if (is_load) {
+ strokepath.reload();
+ }
+
+ if (path_in.size() >= 2) {
+ startpoint_edge_variation.resetRandomizer();
+ endpoint_edge_variation.resetRandomizer();
+ startpoint_spacing_variation.resetRandomizer();
+ endpoint_spacing_variation.resetRandomizer();
+ Geom::Affine affine = strokepath.get_relative_affine().withoutTranslation();
+
+ D2<Piecewise<SBasis> > stroke = make_cuts_independent(strokepath.get_pwd2() * affine);
+ OptInterval bndsStroke = bounds_exact(stroke[0]);
+ OptInterval bndsStrokeY = bounds_exact(stroke[1]);
+ if (!bndsStroke && !bndsStrokeY) {
+ return path_in;
+ }
+ gdouble scaling = bndsStroke->max() - bndsStroke->min();
+ Point stroke_origin(bndsStroke->min(), (bndsStrokeY->max()+bndsStrokeY->min())/2);
+
+ Geom::PathVector path_out;
+
+ // do this for all permutations (ii,jj) if there are more than 2 paths? realllly cool!
+ for (unsigned ii = 0 ; ii < path_in.size() - 1; ii++)
+ for (unsigned jj = ii+1; jj < path_in.size(); jj++)
+ {
+ Piecewise<D2<SBasis> > A = arc_length_parametrization(Piecewise<D2<SBasis> >(path_in[ii].toPwSb()),2,.1);
+ Piecewise<D2<SBasis> > B = arc_length_parametrization(Piecewise<D2<SBasis> >(path_in[jj].toPwSb()),2,.1);
+ Interval bndsA = A.domain();
+ Interval bndsB = B.domain();
+ gdouble incrementA = (bndsA.max()-bndsA.min()) / (nrofpaths-1);
+ gdouble incrementB = (bndsB.max()-bndsB.min()) / (nrofpaths-1);
+ gdouble tA = bndsA.min();
+ gdouble tB = bndsB.min();
+ gdouble tAclean = tA; // the tA without spacing_variation
+ gdouble tBclean = tB; // the tB without spacing_variation
+
+ for (int i = 0; i < nrofpaths; i++) {
+ Point start = A(tA);
+ Point end = B(tB);
+ if (startpoint_edge_variation.get_value() != 0)
+ start = start + (startpoint_edge_variation - startpoint_edge_variation.get_value()/2) * (end - start);
+ if (endpoint_edge_variation.get_value() != 0)
+ end = end + (endpoint_edge_variation - endpoint_edge_variation.get_value()/2)* (end - start);
+
+ if (!Geom::are_near(start,end)) {
+ gdouble scaling_y = 1.0;
+ if (scale_y_rel.get_value() || transformed) {
+ scaling_y = (L2(end-start)/scaling)*prop_scale;
+ transformed = false;
+ } else {
+ scaling_y = prop_scale;
+ }
+
+ Affine transform;
+ transform.setXAxis( (end-start) / scaling );
+ transform.setYAxis( rot90(unit_vector(end-start)) * scaling_y);
+ transform.setTranslation( start );
+ Piecewise<D2<SBasis> > pwd2_out = (strokepath.get_pwd2()-stroke_origin) * transform;
+
+ // add stuff to one big pw<d2<sbasis> > and then outside the loop convert to path?
+ // No: this way, the separate result paths are kept separate which might come in handy some time!
+ Geom::PathVector result = Geom::path_from_piecewise(pwd2_out, LPE_CONVERSION_TOLERANCE);
+ path_out.push_back(result[0]);
+ }
+ gdouble svA = startpoint_spacing_variation - startpoint_spacing_variation.get_value()/2;
+ gdouble svB = endpoint_spacing_variation - endpoint_spacing_variation.get_value()/2;
+ tAclean += incrementA;
+ tBclean += incrementB;
+ tA = tAclean + incrementA * svA;
+ tB = tBclean + incrementB * svB;
+ if (tA > bndsA.max())
+ tA = bndsA.max();
+ if (tB > bndsB.max())
+ tB = bndsB.max();
+ }
+ }
+
+ return path_out;
+ } else {
+ return path_in;
+ }
+}
+
+void
+LPECurveStitch::resetDefaults(SPItem const* item)
+{
+ Effect::resetDefaults(item);
+
+ if (!SP_IS_PATH(item)) return;
+
+ using namespace Geom;
+
+ // set the stroke path to run horizontally in the middle of the bounding box of the original path
+
+ // calculate bounding box: (isn't there a simpler way?)
+ Piecewise<D2<SBasis> > pwd2;
+ Geom::PathVector temppath = sp_svg_read_pathv( item->getRepr()->attribute("inkscape:original-d"));
+ for (const auto & i : temppath) {
+ pwd2.concat( i.toPwSb() );
+ }
+ D2<Piecewise<SBasis> > d2pw = make_cuts_independent(pwd2);
+ OptInterval bndsX = bounds_exact(d2pw[0]);
+ OptInterval bndsY = bounds_exact(d2pw[1]);
+ if (bndsX && bndsY) {
+ Point start(bndsX->min(), (bndsY->max()+bndsY->min())/2);
+ Point end(bndsX->max(), (bndsY->max()+bndsY->min())/2);
+ if ( !Geom::are_near(start,end) ) {
+ Geom::Path path;
+ path.start( start );
+ path.appendNew<Geom::LineSegment>( end );
+ strokepath.set_new_value( path.toPwSb(), true );
+ } else {
+ // bounding box is too small to make decent path. set to default default. :-)
+ strokepath.param_set_and_write_default();
+ }
+ } else {
+ // bounding box is non-existent. set to default default. :-)
+ strokepath.param_set_and_write_default();
+ }
+}
+
+} //namespace LivePathEffect
+} /* 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 :