From 69ae98cb453849c6d32a1c7ea8bc057fb13deea3 Mon Sep 17 00:00:00 2001 From: "Liam P. White" Date: Wed, 25 Jun 2014 11:32:51 -0400 Subject: 1. make it compile (bzr r13341.5.1) --- src/live_effects/lpe-perspective_path.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/live_effects') diff --git a/src/live_effects/lpe-perspective_path.cpp b/src/live_effects/lpe-perspective_path.cpp index c255f8665..a7748c5a8 100644 --- a/src/live_effects/lpe-perspective_path.cpp +++ b/src/live_effects/lpe-perspective_path.cpp @@ -60,7 +60,7 @@ LPEPerspectivePath::LPEPerspectivePath(LivePathEffectObject *lpeobject) : concatenate_before_pwd2 = true; // don't split the path into its subpaths _provides_knotholder_entities = true; - Persp3D *persp = persp3d_document_first_persp(inkscape_active_document()); + Persp3D *persp = persp3d_document_first_persp(SP_ACTIVE_DOCUMENT); Proj::TransfMat3x4 pmat = persp->perspective_impl->tmat; pmat.copy_tmat(tmat); } -- cgit v1.2.3 From 45f373f3319b598d8e0222fb48e9d3a4760b2044 Mon Sep 17 00:00:00 2001 From: "Liam P. White" Date: Fri, 27 Jun 2014 15:23:06 -0400 Subject: 5. Refactoring of Application class: make copy/assignment operators private, disallow pointers to Application (bzr r13341.5.9) --- src/live_effects/effect.cpp | 2 +- src/live_effects/lpe-bspline.cpp | 4 ++-- src/live_effects/lpe-envelope-perspective.cpp | 2 +- src/live_effects/lpe-lattice2.cpp | 2 +- src/live_effects/parameter/text.cpp | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) (limited to 'src/live_effects') diff --git a/src/live_effects/effect.cpp b/src/live_effects/effect.cpp index d0a168a14..e622ca420 100644 --- a/src/live_effects/effect.cpp +++ b/src/live_effects/effect.cpp @@ -356,7 +356,7 @@ void Effect::doAcceptPathPreparations(SPLPEItem *lpeitem) { // switch to pen context - SPDesktop *desktop = inkscape_active_desktop(); // TODO: Is there a better method to find the item's desktop? + SPDesktop *desktop = INKSCAPE.active_desktop(); // TODO: Is there a better method to find the item's desktop? if (!tools_isactive(desktop, TOOLS_FREEHAND_PEN)) { tools_switch(desktop, TOOLS_FREEHAND_PEN); } diff --git a/src/live_effects/lpe-bspline.cpp b/src/live_effects/lpe-bspline.cpp index b19b697c0..247496167 100644 --- a/src/live_effects/lpe-bspline.cpp +++ b/src/live_effects/lpe-bspline.cpp @@ -392,7 +392,7 @@ void LPEBSpline::toMakeCusp(Gtk::Widget *widgWeight) { void LPEBSpline::toWeight() { changeWeight(weight); } void LPEBSpline::changeWeight(double weightValue) { - SPDesktop *desktop = inkscape_active_desktop(); + SPDesktop *desktop = INKSCAPE.active_desktop(); Inkscape::Selection *selection = sp_desktop_selection(desktop); GSList *items = (GSList *)selection->itemList(); SPItem *item = (SPItem *)g_slist_nth(items, 0)->data; @@ -432,7 +432,7 @@ bool LPEBSpline::nodeIsSelected(Geom::Point nodePoint) { void LPEBSpline::doBSplineFromWidget(SPCurve *curve, double weightValue) { using Geom::X; using Geom::Y; - SPDesktop *desktop = inkscape_active_desktop(); + SPDesktop *desktop = INKSCAPE.active_desktop(); if (INK_IS_NODE_TOOL(desktop->event_context)) { Inkscape::UI::Tools::NodeTool *nt = INK_NODE_TOOL(desktop->event_context); Inkscape::UI::ControlPointSelection::Set &selection = diff --git a/src/live_effects/lpe-envelope-perspective.cpp b/src/live_effects/lpe-envelope-perspective.cpp index bfd6e56d7..a16ed7ab5 100644 --- a/src/live_effects/lpe-envelope-perspective.cpp +++ b/src/live_effects/lpe-envelope-perspective.cpp @@ -352,7 +352,7 @@ LPEEnvelopePerspective::resetGrid() Down_Left_Point.param_set_and_write_default(); //todo:this hack is only to reposition the knots on reser grid button //Better update path effect in LPEITEM - SPDesktop * desktop = inkscape_active_desktop(); + SPDesktop * desktop = INKSCAPE.active_desktop(); tools_switch(desktop, TOOLS_SELECT); tools_switch(desktop, TOOLS_NODES); } diff --git a/src/live_effects/lpe-lattice2.cpp b/src/live_effects/lpe-lattice2.cpp index db609c9e1..13759f126 100644 --- a/src/live_effects/lpe-lattice2.cpp +++ b/src/live_effects/lpe-lattice2.cpp @@ -391,7 +391,7 @@ LPELattice2::resetGrid() grid_point32x33x34x35.param_set_and_write_default(); //todo:this hack is only to reposition the knots on reser grid button //Better update path effect in LPEITEM - SPDesktop * desktop = inkscape_active_desktop(); + SPDesktop * desktop = INKSCAPE.active_desktop(); tools_switch(desktop, TOOLS_SELECT); tools_switch(desktop, TOOLS_NODES); } diff --git a/src/live_effects/parameter/text.cpp b/src/live_effects/parameter/text.cpp index fcb2fc9fc..88fc27abd 100644 --- a/src/live_effects/parameter/text.cpp +++ b/src/live_effects/parameter/text.cpp @@ -32,7 +32,7 @@ TextParam::TextParam( const Glib::ustring& label, const Glib::ustring& tip, value(default_value), defvalue(default_value) { - SPDesktop *desktop = inkscape_active_desktop(); // FIXME: we shouldn't use this! + SPDesktop *desktop = INKSCAPE.active_desktop(); // FIXME: we shouldn't use this! canvas_text = (SPCanvasText *) sp_canvastext_new(sp_desktop_tempgroup(desktop), desktop, Geom::Point(0,0), ""); sp_canvastext_set_text (canvas_text, default_value.c_str()); sp_canvastext_set_coords (canvas_text, 0, 0); -- cgit v1.2.3 From fa9bd6393f316dab9303569b28f6b5d179fedd61 Mon Sep 17 00:00:00 2001 From: "Liam P. White" Date: Sat, 27 Sep 2014 10:17:45 -0400 Subject: Update to experimental r13565 (bzr r13341.5.16) --- src/live_effects/CMakeLists.txt | 13 + src/live_effects/Makefile_insert | 16 + src/live_effects/effect-enum.h | 7 + src/live_effects/effect.cpp | 77 +- src/live_effects/effect.h | 11 + src/live_effects/lpe-attach-path.cpp | 198 +++++ src/live_effects/lpe-attach-path.h | 52 ++ src/live_effects/lpe-bounding-box.cpp | 67 ++ src/live_effects/lpe-bounding-box.h | 37 + src/live_effects/lpe-bspline.cpp | 394 +++------- src/live_effects/lpe-copy_rotate.cpp | 1 + src/live_effects/lpe-ellipse_5pts.cpp | 214 ++++++ src/live_effects/lpe-ellipse_5pts.h | 50 ++ src/live_effects/lpe-fill-between-many.cpp | 78 ++ src/live_effects/lpe-fill-between-many.h | 36 + src/live_effects/lpe-fill-between-strokes.cpp | 116 +++ src/live_effects/lpe-fill-between-strokes.h | 38 + src/live_effects/lpe-fillet-chamfer.cpp | 163 ++--- src/live_effects/lpe-fillet-chamfer.h | 10 +- src/live_effects/lpe-interpolate_points.cpp | 2 +- src/live_effects/lpe-jointype.cpp | 190 +++++ src/live_effects/lpe-jointype.h | 57 ++ src/live_effects/lpe-knot.cpp | 5 + src/live_effects/lpe-knot.h | 3 +- src/live_effects/lpe-powerstroke-interpolators.h | 40 + src/live_effects/lpe-powerstroke.cpp | 100 ++- src/live_effects/lpe-powerstroke.h | 2 + src/live_effects/lpe-roughen.cpp | 595 +++++++-------- src/live_effects/lpe-roughen.h | 64 +- src/live_effects/lpe-tangent_to_curve.cpp | 19 +- src/live_effects/lpe-tangent_to_curve.h | 1 - src/live_effects/lpe-taperstroke.cpp | 632 ++++++++++++++++ src/live_effects/lpe-taperstroke.h | 72 ++ src/live_effects/lpeobject-reference.h | 6 +- src/live_effects/lpeobject.h | 4 +- src/live_effects/parameter/Makefile_insert | 4 + .../parameter/filletchamferpointarray.cpp | 263 ++++++- .../parameter/filletchamferpointarray.h | 8 +- src/live_effects/parameter/originalpatharray.cpp | 487 +++++++++++++ src/live_effects/parameter/originalpatharray.h | 122 ++++ .../parameter/powerstrokepointarray.cpp | 43 +- src/live_effects/parameter/powerstrokepointarray.h | 23 +- src/live_effects/parameter/transformedpoint.cpp | 182 +++++ src/live_effects/parameter/transformedpoint.h | 82 +++ src/live_effects/pathoutlineprovider.cpp | 803 +++++++++++++++++++++ src/live_effects/pathoutlineprovider.h | 55 ++ 46 files changed, 4643 insertions(+), 799 deletions(-) create mode 100644 src/live_effects/lpe-attach-path.cpp create mode 100644 src/live_effects/lpe-attach-path.h create mode 100644 src/live_effects/lpe-bounding-box.cpp create mode 100644 src/live_effects/lpe-bounding-box.h create mode 100644 src/live_effects/lpe-ellipse_5pts.cpp create mode 100644 src/live_effects/lpe-ellipse_5pts.h create mode 100644 src/live_effects/lpe-fill-between-many.cpp create mode 100644 src/live_effects/lpe-fill-between-many.h create mode 100644 src/live_effects/lpe-fill-between-strokes.cpp create mode 100644 src/live_effects/lpe-fill-between-strokes.h create mode 100644 src/live_effects/lpe-jointype.cpp create mode 100644 src/live_effects/lpe-jointype.h create mode 100644 src/live_effects/lpe-taperstroke.cpp create mode 100644 src/live_effects/lpe-taperstroke.h create mode 100644 src/live_effects/parameter/originalpatharray.cpp create mode 100644 src/live_effects/parameter/originalpatharray.h create mode 100644 src/live_effects/parameter/transformedpoint.cpp create mode 100644 src/live_effects/parameter/transformedpoint.h create mode 100644 src/live_effects/pathoutlineprovider.cpp create mode 100644 src/live_effects/pathoutlineprovider.h (limited to 'src/live_effects') diff --git a/src/live_effects/CMakeLists.txt b/src/live_effects/CMakeLists.txt index d126aca9f..30c2b2f41 100644 --- a/src/live_effects/CMakeLists.txt +++ b/src/live_effects/CMakeLists.txt @@ -2,8 +2,10 @@ set(live_effects_SRC effect.cpp lpe-angle_bisector.cpp + lpe-attach-path.cpp lpe-bendpath.cpp lpe-boolops.cpp + lpe-bounding-box.cpp lpe-circle_3pts.cpp lpe-circle_with_radius.cpp lpe-clone-original.cpp @@ -11,9 +13,12 @@ set(live_effects_SRC lpe-copy_rotate.cpp lpe-curvestitch.cpp lpe-dynastroke.cpp + lpe-ellipse-5pts.cpp lpe-envelope.cpp lpe-envelope-perspective.cpp lpe-extrude.cpp + lpe-fill-between-many.cpp + lpe-fill-between-strokes.cpp lpe-fillet-chamfer.cpp lpe-gears.cpp lpe-interpolate.cpp @@ -55,11 +60,13 @@ set(live_effects_SRC parameter/parameter.cpp parameter/path.cpp parameter/originalpath.cpp + parameter/originalpatharray.cpp parameter/path-reference.cpp parameter/point.cpp parameter/powerstrokepointarray.cpp parameter/random.cpp parameter/text.cpp + paramter/transformedpoint.cpp parameter/togglebutton.cpp parameter/unit.cpp parameter/vector.cpp @@ -70,8 +77,10 @@ set(live_effects_SRC effect-enum.h effect.h lpe-angle_bisector.h + lpe-attach-path.h lpe-bendpath.h lpe-boolops.h + lpe-bounding-box.h lpe-circle_3pts.h lpe-circle_with_radius.h lpe-clone-original.h @@ -79,8 +88,11 @@ set(live_effects_SRC lpe-copy_rotate.h lpe-curvestitch.h lpe-dynastroke.h + lpe-ellipse-5pts.h lpe-envelope.h lpe-extrude.h + lpe-fill-between-many.h + lpe-fill-between-strokes.h lpe-fillet-chamfer.h lpe-gears.h lpe-interpolate.h @@ -125,6 +137,7 @@ set(live_effects_SRC parameter/path-reference.h parameter/path.h parameter/originalpath.h + parameter/originalpatharray.h parameter/point.h parameter/powerstrokepointarray.h parameter/random.h diff --git a/src/live_effects/Makefile_insert b/src/live_effects/Makefile_insert index 1b8f587e1..f18dcdef0 100644 --- a/src/live_effects/Makefile_insert +++ b/src/live_effects/Makefile_insert @@ -97,5 +97,21 @@ ink_common_sources += \ live_effects/lpe-path_length.h \ live_effects/lpe-line_segment.cpp \ live_effects/lpe-line_segment.h \ + live_effects/lpe-bounding-box.cpp \ + live_effects/lpe-bounding-box.h \ + live_effects/lpe-attach-path.cpp \ + live_effects/lpe-attach-path.h \ + live_effects/lpe-fill-between-strokes.cpp \ + live_effects/lpe-fill-between-strokes.h \ + live_effects/lpe-fill-between-many.cpp \ + live_effects/lpe-fill-between-many.h \ + live_effects/lpe-ellipse_5pts.cpp \ + live_effects/lpe-ellipse_5pts.h \ + live_effects/pathoutlineprovider.cpp \ + live_effects/pathoutlineprovider.h \ + live_effects/lpe-jointype.cpp \ + live_effects/lpe-jointype.h \ + live_effects/lpe-taperstroke.cpp \ + live_effects/lpe-taperstroke.h \ live_effects/lpe-envelope-perspective.cpp \ live_effects/lpe-envelope-perspective.h diff --git a/src/live_effects/effect-enum.h b/src/live_effects/effect-enum.h index 30dbf4092..c53f1a5b9 100644 --- a/src/live_effects/effect-enum.h +++ b/src/live_effects/effect-enum.h @@ -56,6 +56,13 @@ enum EffectType { EXTRUDE, POWERSTROKE, CLONE_ORIGINAL, + ATTACH_PATH, + FILL_BETWEEN_STROKES, + FILL_BETWEEN_MANY, + ELLIPSE_5PTS, + BOUNDING_BOX, + JOIN_TYPE, + TAPER_STROKE, ENVELOPE_PERSPECTIVE, FILLET_CHAMFER, INVALID_LPE // This must be last (I made it such that it is not needed anymore I think..., Don't trust on it being last. - johan) diff --git a/src/live_effects/effect.cpp b/src/live_effects/effect.cpp index b012e3ab1..87562ddc4 100644 --- a/src/live_effects/effect.cpp +++ b/src/live_effects/effect.cpp @@ -5,6 +5,8 @@ * Released under GNU GPL, read the file 'COPYING' for more information */ +//#define LPE_ENABLE_TEST_EFFECTS //uncomment for toy effects + #ifdef HAVE_CONFIG_H # include "config.h" #endif @@ -19,7 +21,6 @@ #include "live_effects/lpe-rough-hatches.h" #include "live_effects/lpe-dynastroke.h" #include "live_effects/lpe-test-doEffect-stack.h" -#include "live_effects/lpe-bspline.h" #include "live_effects/lpe-gears.h" #include "live_effects/lpe-curvestitch.h" #include "live_effects/lpe-circle_with_radius.h" @@ -51,6 +52,14 @@ #include "live_effects/lpe-extrude.h" #include "live_effects/lpe-powerstroke.h" #include "live_effects/lpe-clone-original.h" +#include "live_effects/lpe-bspline.h" +#include "live_effects/lpe-attach-path.h" +#include "live_effects/lpe-fill-between-strokes.h" +#include "live_effects/lpe-fill-between-many.h" +#include "live_effects/lpe-ellipse_5pts.h" +#include "live_effects/lpe-bounding-box.h" +#include "live_effects/lpe-jointype.h" +#include "live_effects/lpe-taperstroke.h" #include "live_effects/lpe-envelope-perspective.h" #include "live_effects/lpe-fillet-chamfer.h" @@ -132,10 +141,18 @@ const Util::EnumData LPETypeData[] = { {SHOW_HANDLES, N_("Show handles"), "show_handles"}, {ROUGHEN, N_("Roughen"), "roughen"}, {BSPLINE, N_("BSpline"), "bspline"}, - {SIMPLIFY, N_("Simplify"), "simplify"}, - {LATTICE2, N_("Lattice Deformation 2"), "lattice2"}, - // TRANSLATORS: "Envelope Perspective" should be equivalent to "perspective transformation" - {ENVELOPE_PERSPECTIVE, N_("Envelope Perspective"), "envelope-perspective"}, + {JOIN_TYPE, N_("Join type"), "join_type"}, + {TAPER_STROKE, N_("Taper stroke"), "taper_stroke"}, +/* Ponyscape */ + {ATTACH_PATH, N_("Attach path"), "attach_path"}, + {FILL_BETWEEN_STROKES, N_("Fill between strokes"), "fill_between_strokes"}, + {FILL_BETWEEN_MANY, N_("Fill between many"), "fill_between_many"}, + {ELLIPSE_5PTS, N_("Ellipse by 5 points"), "ellipse_5pts"}, + {BOUNDING_BOX, N_("Bounding Box"), "bounding_box"}, +/* 0.91 */ + {SIMPLIFY, N_("Simplify"), "simplify"}, + {LATTICE2, N_("Lattice Deformation 2"), "lattice2"}, + {ENVELOPE_PERSPECTIVE, N_("Envelope-Perspective"), "envelope-perspective"}, {FILLET_CHAMFER, N_("Fillet/Chamfer"), "fillet-chamfer"}, {INTERPOLATE_POINTS, N_("Interpolate points"), "interpolate_points"}, }; @@ -267,6 +284,27 @@ Effect::New(EffectType lpenr, LivePathEffectObject *lpeobj) case CLONE_ORIGINAL: neweffect = static_cast ( new LPECloneOriginal(lpeobj) ); break; + case ATTACH_PATH: + neweffect = static_cast ( new LPEAttachPath(lpeobj) ); + break; + case FILL_BETWEEN_STROKES: + neweffect = static_cast ( new LPEFillBetweenStrokes(lpeobj) ); + break; + case FILL_BETWEEN_MANY: + neweffect = static_cast ( new LPEFillBetweenMany(lpeobj) ); + break; + case ELLIPSE_5PTS: + neweffect = static_cast ( new LPEEllipse5Pts(lpeobj) ); + break; + case BOUNDING_BOX: + neweffect = static_cast ( new LPEBoundingBox(lpeobj) ); + break; + case JOIN_TYPE: + neweffect = static_cast ( new LPEJoinType(lpeobj) ); + break; + case TAPER_STROKE: + neweffect = static_cast ( new LPETaperStroke(lpeobj) ); + break; case SIMPLIFY: neweffect = static_cast ( new LPESimplify(lpeobj) ); break; @@ -286,7 +324,7 @@ Effect::New(EffectType lpenr, LivePathEffectObject *lpeobj) neweffect = static_cast ( new LPEShowHandles(lpeobj) ); break; default: - g_warning("LivePathEffect::Effect::New called with invalid patheffect type (%d)", lpenr); + g_warning("LivePathEffect::Effect::New called with invalid patheffect type (%d)", lpenr); neweffect = NULL; break; } @@ -369,6 +407,33 @@ Effect::doBeforeEffect (SPLPEItem const*/*lpeitem*/) //Do nothing for simple effects } +void Effect::doAfterEffect (SPLPEItem const* /*lpeitem*/) +{ +} + +void Effect::doOnRemove (SPLPEItem const* /*lpeitem*/) +{ +} + +//secret impl methods (shhhh!) +void Effect::doOnApply_impl(SPLPEItem const* lpeitem) +{ + sp_lpe_item = const_cast(lpeitem); + /*sp_curve = SP_SHAPE(sp_lpe_item)->getCurve(); + pathvector_before_effect = sp_curve->get_pathvector();*/ + doOnApply(lpeitem); +} + +void Effect::doBeforeEffect_impl(SPLPEItem const* lpeitem) +{ + sp_lpe_item = const_cast(lpeitem); + //printf("(SPLPEITEM*) %p\n", sp_lpe_item); + sp_curve = SP_SHAPE(sp_lpe_item)->getCurve(); + pathvector_before_effect = sp_curve->get_pathvector(); + + doBeforeEffect(lpeitem); +} + /** * Effects can have a parameter path set before they are applied by accepting a nonzero number of * mouse clicks. This method activates the pen context, which waits for the specified number of diff --git a/src/live_effects/effect.h b/src/live_effects/effect.h index 940770616..a486e8491 100644 --- a/src/live_effects/effect.h +++ b/src/live_effects/effect.h @@ -53,8 +53,16 @@ public: EffectType effectType() const; + //basically, to get this method called before the derived classes, a bit + //of indirection is needed. We first call these methods, then the below. + void doOnApply_impl(SPLPEItem const* lpeitem); + void doBeforeEffect_impl(SPLPEItem const* lpeitem); + virtual void doOnApply (SPLPEItem const* lpeitem); virtual void doBeforeEffect (SPLPEItem const* lpeitem); + + virtual void doAfterEffect (SPLPEItem const* lpeitem); + virtual void doOnRemove (SPLPEItem const* lpeitem); void writeParamsToSVG(); @@ -147,6 +155,9 @@ protected: // instead of normally 'splitting' the path into continuous pwd2 paths and calling doEffect_pwd2 for each. bool concatenate_before_pwd2; + SPLPEItem * sp_lpe_item; // these get stored in doBeforeEffect_impl, and derived classes may do as they please with them. + SPCurve * sp_curve; + std::vector pathvector_before_effect; private: bool provides_own_flash_paths; // if true, the standard flash path is suppressed diff --git a/src/live_effects/lpe-attach-path.cpp b/src/live_effects/lpe-attach-path.cpp new file mode 100644 index 000000000..768c66ee2 --- /dev/null +++ b/src/live_effects/lpe-attach-path.cpp @@ -0,0 +1,198 @@ +/* + * Copyright (C) Theodore Janeczko 2012 + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include + +#include "live_effects/lpe-attach-path.h" + +#include "display/curve.h" +#include "sp-item.h" +#include "2geom/path.h" +#include "sp-shape.h" +#include "sp-text.h" +#include "2geom/bezier-curve.h" +#include "2geom/path-sink.h" +#include "parameter/parameter.h" +#include "live_effects/parameter/point.h" +#include "parameter/originalpath.h" +#include "2geom/affine.h" + +namespace Inkscape { +namespace LivePathEffect { + +LPEAttachPath::LPEAttachPath(LivePathEffectObject *lpeobject) : + Effect(lpeobject), + start_path(_("Start path:"), _("Path to attach to the start of this path"), "startpath", &wr, this), + start_path_position(_("Start path position:"), _("Position to attach path start to"), "startposition", &wr, this, 0.0), + start_path_curve_start(_("Start path curve start:"), _("Starting curve"), "startcurvestart", &wr, this, Geom::Point(20,0)/*, true*/), + start_path_curve_end(_("Start path curve end:"), _("Ending curve"), "startcurveend", &wr, this, Geom::Point(20,0)/*, true*/), + end_path(_("End path:"), _("Path to attach to the end of this path"), "endpath", &wr, this), + end_path_position(_("End path position:"), _("Position to attach path end to"), "endposition", &wr, this, 0.0), + end_path_curve_start(_("End path curve start:"), _("Starting curve"), "endcurvestart", &wr, this, Geom::Point(20,0)/*, true*/), + end_path_curve_end(_("End path curve end:"), _("Ending curve"), "endcurveend", &wr, this, Geom::Point(20,0)/*, true*/) +{ + registerParameter(&start_path); + registerParameter(&start_path_position); + registerParameter(&start_path_curve_start); + registerParameter(&start_path_curve_end); + + registerParameter(&end_path); + registerParameter(&end_path_position); + registerParameter(&end_path_curve_start); + registerParameter(&end_path_curve_end); + + //perceived_path = true; + show_orig_path = true; + curve_start_previous_origin = start_path_curve_end.getOrigin(); + curve_end_previous_origin = end_path_curve_end.getOrigin(); +} + +LPEAttachPath::~LPEAttachPath() +{ + +} + +void LPEAttachPath::resetDefaults(SPItem const * /*item*/) +{ + curve_start_previous_origin = start_path_curve_end.getOrigin(); + curve_end_previous_origin = end_path_curve_end.getOrigin(); +} + +void LPEAttachPath::doEffect (SPCurve * curve) +{ + std::vector this_pathv = curve->get_pathvector(); + if (sp_lpe_item && !this_pathv.empty()) { + Geom::Path p = Geom::Path(this_pathv.front().initialPoint()); + + bool set_start_end = start_path_curve_end.getOrigin() != curve_start_previous_origin; + bool set_end_end = end_path_curve_end.getOrigin() != curve_end_previous_origin; + + if (start_path.linksToPath()) { + + std::vector linked_pathv = start_path.get_pathvector(); + Geom::Affine linkedtransform = start_path.getObject()->getRelativeTransform(sp_lpe_item); + + if ( !linked_pathv.empty() ) + { + Geom::Path transformedpath = linked_pathv.front() * linkedtransform; + start_path_curve_start.setOrigin(this_pathv.front().initialPoint()); + + std::vector derivs = this_pathv.front().front().pointAndDerivatives(0, 3); + + for (unsigned deriv_n = 1; deriv_n < derivs.size(); deriv_n++) { + Geom::Coord length = derivs[deriv_n].length(); + if ( ! Geom::are_near(length, 0) ) { + if (set_start_end) { + start_path_position.param_set_value(transformedpath.nearestPoint(start_path_curve_end.getOrigin())); + } + + if (start_path_position > transformedpath.size()) { + start_path_position.param_set_value(transformedpath.size()); + } else if (start_path_position < 0) { + start_path_position.param_set_value(0); + } + const Geom::Curve *c = start_path_position >= transformedpath.size() ? &transformedpath.back() : &transformedpath.at_index((int)start_path_position); + + std::vector derivs_2 = c->pointAndDerivatives(start_path_position >= transformedpath.size() ? 1 : (start_path_position - (int)start_path_position), 3); + for (unsigned deriv_n_2 = 1; deriv_n_2 < derivs_2.size(); deriv_n_2++) { + Geom::Coord length_2 = derivs[deriv_n_2].length(); + if ( ! Geom::are_near(length_2, 0) ) { + start_path_curve_end.setOrigin(derivs_2[0]); + curve_start_previous_origin = start_path_curve_end.getOrigin(); + + double startangle = atan2(start_path_curve_start.getVector().y(), start_path_curve_start.getVector().x()); + double endangle = atan2(start_path_curve_end.getVector().y(), start_path_curve_end.getVector().x()); + double startderiv = atan2(derivs[deriv_n].y(), derivs[deriv_n].x()); + double endderiv = atan2(derivs_2[deriv_n_2].y(), derivs_2[deriv_n_2].x()); + Geom::Point pt1 = Geom::Point(start_path_curve_start.getVector().length() * cos(startangle + startderiv), start_path_curve_start.getVector().length() * sin(startangle + startderiv)); + Geom::Point pt2 = Geom::Point(start_path_curve_end.getVector().length() * cos(endangle + endderiv), start_path_curve_end.getVector().length() * sin(endangle + endderiv)); + p = Geom::Path(derivs_2[0]); + p.appendNew(-pt2 + derivs_2[0], -pt1 + this_pathv.front().initialPoint(), this_pathv.front().initialPoint()); + break; + + } + } + break; + } + } + } + } + + p.append(this_pathv.front()); + + if (end_path.linksToPath()) { + + std::vector linked_pathv = end_path.get_pathvector(); + Geom::Affine linkedtransform = end_path.getObject()->getRelativeTransform(sp_lpe_item); + + if ( !linked_pathv.empty() ) + { + Geom::Path transformedpath = linked_pathv.front() * linkedtransform; + Geom::Curve * last_seg_reverse = this_pathv.front().back().reverse(); + + end_path_curve_start.setOrigin(last_seg_reverse->initialPoint()); + + std::vector derivs = last_seg_reverse->pointAndDerivatives(0, 3); + for (unsigned deriv_n = 1; deriv_n < derivs.size(); deriv_n++) { + Geom::Coord length = derivs[deriv_n].length(); + if ( ! Geom::are_near(length, 0) ) { + if (set_end_end) { + end_path_position.param_set_value(transformedpath.nearestPoint(end_path_curve_end.getOrigin())); + } + + if (end_path_position > transformedpath.size()) { + end_path_position.param_set_value(transformedpath.size()); + } else if (end_path_position < 0) { + end_path_position.param_set_value(0); + } + const Geom::Curve *c = end_path_position >= transformedpath.size() ? &transformedpath.back() : &transformedpath.at_index((int)end_path_position); + + std::vector derivs_2 = c->pointAndDerivatives(end_path_position >= transformedpath.size() ? 1 : (end_path_position - (int)end_path_position), 3); + for (unsigned deriv_n_2 = 1; deriv_n_2 < derivs_2.size(); deriv_n_2++) { + Geom::Coord length_2 = derivs[deriv_n_2].length(); + if ( ! Geom::are_near(length_2, 0) ) { + + end_path_curve_end.setOrigin(derivs_2[0]); + curve_end_previous_origin = end_path_curve_end.getOrigin(); + + double startangle = atan2(end_path_curve_start.getVector().y(), end_path_curve_start.getVector().x()); + double endangle = atan2(end_path_curve_end.getVector().y(), end_path_curve_end.getVector().x()); + double startderiv = atan2(derivs[deriv_n].y(), derivs[deriv_n].x()); + double endderiv = atan2(derivs_2[deriv_n_2].y(), derivs_2[deriv_n_2].x()); + Geom::Point pt1 = Geom::Point(end_path_curve_start.getVector().length() * cos(startangle + startderiv), end_path_curve_start.getVector().length() * sin(startangle + startderiv)); + Geom::Point pt2 = Geom::Point(end_path_curve_end.getVector().length() * cos(endangle + endderiv), end_path_curve_end.getVector().length() * sin(endangle + endderiv)); + p.appendNew(-pt1 + this_pathv.front().finalPoint(), -pt2 + derivs_2[0], derivs_2[0]); + + break; + + } + } + break; + } + } + delete last_seg_reverse; + } + } + Geom::PathVector outvector; + outvector.push_back(p); + curve->set_pathvector(outvector); + } +} + +} // 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 : diff --git a/src/live_effects/lpe-attach-path.h b/src/live_effects/lpe-attach-path.h new file mode 100644 index 000000000..1d6c590d1 --- /dev/null +++ b/src/live_effects/lpe-attach-path.h @@ -0,0 +1,52 @@ +#ifndef INKSCAPE_LPE_ATTACH_PATH_H +#define INKSCAPE_LPE_ATTACH_PATH_H + +/* + * Inkscape::LPEAttachPath + * + * Copyright (C) Ted Janeczko 2012 + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "live_effects/effect.h" +#include "live_effects/parameter/parameter.h" +#include "live_effects/parameter/point.h" +#include "live_effects/parameter/originalpath.h" +#include "live_effects/parameter/vector.h" +#include "live_effects/parameter/bool.h" +#include "live_effects/parameter/transformedpoint.h" + +namespace Inkscape { +namespace LivePathEffect { + +class LPEAttachPath : public Effect { +public: + LPEAttachPath(LivePathEffectObject *lpeobject); + virtual ~LPEAttachPath(); + + virtual void doEffect (SPCurve * curve); + virtual void resetDefaults(SPItem const * item); + +private: + LPEAttachPath(const LPEAttachPath&); + LPEAttachPath& operator=(const LPEAttachPath&); + + Geom::Point curve_start_previous_origin; + Geom::Point curve_end_previous_origin; + + OriginalPathParam start_path; + ScalarParam start_path_position; + TransformedPointParam start_path_curve_start; + VectorParam start_path_curve_end; + + OriginalPathParam end_path; + ScalarParam end_path_position; + TransformedPointParam end_path_curve_start; + VectorParam end_path_curve_end; +}; + +}; //namespace LivePathEffect +}; //namespace Inkscape + +#endif diff --git a/src/live_effects/lpe-bounding-box.cpp b/src/live_effects/lpe-bounding-box.cpp new file mode 100644 index 000000000..bafd5e70e --- /dev/null +++ b/src/live_effects/lpe-bounding-box.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) Theodore Janeczko 2012 + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include + +#include "live_effects/lpe-bounding-box.h" + +#include "display/curve.h" +#include "sp-item.h" +#include "2geom/path.h" +#include "sp-shape.h" +#include "sp-text.h" +#include "2geom/bezier-curve.h" +#include "lpe-bounding-box.h" + +namespace Inkscape { +namespace LivePathEffect { + +LPEBoundingBox::LPEBoundingBox(LivePathEffectObject *lpeobject) : + Effect(lpeobject), + linked_path(_("Linked path:"), _("Path from which to take the original path data"), "linkedpath", &wr, this), + visual_bounds(_("Visual Bounds"), _("Uses the visual bounding box"), "visualbounds", &wr, this) +{ + registerParameter( dynamic_cast(&linked_path) ); + registerParameter( dynamic_cast(&visual_bounds) ); + //perceived_path = true; +} + +LPEBoundingBox::~LPEBoundingBox() +{ + +} + +void LPEBoundingBox::doEffect (SPCurve * curve) +{ + if (curve) { + if ( linked_path.linksToPath() && linked_path.getObject() ) { + SPItem * item = linked_path.getObject(); + Geom::OptRect bbox = visual_bounds.get_value() ? item->visualBounds() : item->geometricBounds(); + Geom::Path p(Geom::Point(bbox->left(), bbox->top())); + p.appendNew(Geom::Point(bbox->right(), bbox->top())); + p.appendNew(Geom::Point(bbox->right(), bbox->bottom())); + p.appendNew(Geom::Point(bbox->left(), bbox->bottom())); + p.appendNew(Geom::Point(bbox->left(), bbox->top())); + std::vector out; + out.push_back(p); + curve->set_pathvector(out); + } + } +} + +} // 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 : diff --git a/src/live_effects/lpe-bounding-box.h b/src/live_effects/lpe-bounding-box.h new file mode 100644 index 000000000..d028a20ac --- /dev/null +++ b/src/live_effects/lpe-bounding-box.h @@ -0,0 +1,37 @@ +#ifndef INKSCAPE_LPE_BOUNDING_BOX_H +#define INKSCAPE_LPE_BOUNDING_BOX_H + +/* + * Inkscape::LPEFillBetweenStrokes + * + * Copyright (C) Theodore Janeczko 2012 + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "live_effects/effect.h" +#include "live_effects/parameter/originalpath.h" + +namespace Inkscape { +namespace LivePathEffect { + +class LPEBoundingBox : public Effect { +public: + LPEBoundingBox(LivePathEffectObject *lpeobject); + virtual ~LPEBoundingBox(); + + virtual void doEffect (SPCurve * curve); + +private: + OriginalPathParam linked_path; + BoolParam visual_bounds; + +private: + LPEBoundingBox(const LPEBoundingBox&); + LPEBoundingBox& operator=(const LPEBoundingBox&); +}; + +}; //namespace LivePathEffect +}; //namespace Inkscape + +#endif diff --git a/src/live_effects/lpe-bspline.cpp b/src/live_effects/lpe-bspline.cpp index 3a6dca795..d45afb0d9 100644 --- a/src/live_effects/lpe-bspline.cpp +++ b/src/live_effects/lpe-bspline.cpp @@ -52,19 +52,24 @@ using Inkscape::DocumentUndo; namespace Inkscape { namespace LivePathEffect { +const double handleCubicGap = 0.01; +const double noPower = 0.0; +const double defaultStartPower = 0.3334; +const double defaultEndPower = 0.6667; + LPEBSpline::LPEBSpline(LivePathEffectObject *lpeobject) : Effect(lpeobject), steps(_("Steps whith CTRL:"), _("Change number of steps whith CTRL pressed"), "steps", &wr, this, 2), ignoreCusp(_("Ignore cusp nodes"), _("Change ignoring cusp nodes"), "ignoreCusp", &wr, this, true), onlySelected(_("Change only selected nodes"), _("Change only selected nodes"), "onlySelected", &wr, this, false), - weight(_("Change weight:"), _("Change weight of the effect"), "weight", &wr, this, 0.3334) + weight(_("Change weight:"), _("Change weight of the effect"), "weight", &wr, this, defaultStartPower) { registerParameter(&weight); registerParameter(&steps); registerParameter(&ignoreCusp); registerParameter(&onlySelected); - weight.param_set_range(0.0000, 1); + weight.param_set_range(noPower, 1); weight.param_set_increments(0.1, 0.1); weight.param_set_digits(4); @@ -97,192 +102,136 @@ void LPEBSpline::createAndApply(const char *name, SPDocument *doc, SPItem *item) void LPEBSpline::doEffect(SPCurve *curve) { - if (curve->get_segment_count() < 1) + if (curve->get_segment_count() < 1){ return; - // Make copy of old path as it is changed during processing + } + Geom::PathVector const original_pathv = curve->get_pathvector(); curve->reset(); - //Recorremos todos los paths a los que queremos aplicar el efecto, hasta el - //penúltimo for (Geom::PathVector::const_iterator path_it = original_pathv.begin(); path_it != original_pathv.end(); ++path_it) { - //Si está vacío... if (path_it->empty()) continue; - //Itreadores - Geom::Path::const_iterator curve_it1 = path_it->begin(); // incoming curve - Geom::Path::const_iterator curve_it2 = - ++(path_it->begin()); // outgoing curve - Geom::Path::const_iterator curve_endit = - path_it->end_default(); // this determines when the loop has to stop - //Creamos las lineas rectas que unen todos los puntos del trazado y donde se - //calcularán - //los puntos clave para los manejadores. - //Esto hace que la curva BSpline no pierda su condición aunque se trasladen - //dichos manejadores + 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(); SPCurve *nCurve = new SPCurve(); Geom::Point previousNode(0, 0); Geom::Point node(0, 0); Geom::Point pointAt1(0, 0); Geom::Point pointAt2(0, 0); Geom::Point nextPointAt1(0, 0); - Geom::Point nextPointAt2(0, 0); - Geom::Point nextPointAt3(0, 0); Geom::D2 SBasisIn; Geom::D2 SBasisOut; Geom::D2 SBasisHelper; Geom::CubicBezier const *cubic = NULL; if (path_it->closed()) { - // if the path is closed, maybe we have to stop a bit earlier because the - // closing line segment has zerolength. - const Geom::Curve &closingline = - path_it->back_closed(); // the closing line segment is always of type - // Geom::LineSegment. + const Geom::Curve &closingline = path_it->back_closed(); 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! curve_endit = path_it->end_open(); } } - //Si la curva está cerrada calculamos el punto donde - //deveria estar el nodo BSpline de cierre/inicio de la curva - //en posible caso de que se cierre con una linea recta creando un nodo - //BSPline nCurve->moveto(curve_it1->initialPoint()); - //Recorremos todos los segmentos menos el último - while (curve_it2 != curve_endit) { - //previousPointAt3 = pointAt3; - //Calculamos los puntos que dividirían en tres segmentos iguales el path - //recto de entrada y de salida + while (curve_it1 != curve_endit) { SPCurve *in = new SPCurve(); in->moveto(curve_it1->initialPoint()); in->lineto(curve_it1->finalPoint()); cubic = dynamic_cast(&*curve_it1); if (cubic) { SBasisIn = in->first_segment()->toSBasis(); - pointAt1 = SBasisIn.valueAt( - Geom::nearest_point((*cubic)[1], *in->first_segment())); - pointAt2 = SBasisIn.valueAt( - Geom::nearest_point((*cubic)[2], *in->first_segment())); + if(are_near((*cubic)[1],(*cubic)[0]) && !are_near((*cubic)[2],(*cubic)[3])) { + pointAt1 = SBasisIn.valueAt(defaultStartPower); + } else { + pointAt1 = SBasisIn.valueAt(Geom::nearest_point((*cubic)[1], *in->first_segment())); + } + if(are_near((*cubic)[2],(*cubic)[3]) && !are_near((*cubic)[1],(*cubic)[0])) { + pointAt2 = SBasisIn.valueAt(defaultEndPower); + } else { + pointAt2 = SBasisIn.valueAt(Geom::nearest_point((*cubic)[2], *in->first_segment())); + } } else { pointAt1 = in->first_segment()->initialPoint(); pointAt2 = in->first_segment()->finalPoint(); } in->reset(); delete in; - //Y hacemos lo propio con el path de salida - //nextPointAt0 = curveOut.valueAt(0); - SPCurve *out = new SPCurve(); - out->moveto(curve_it2->initialPoint()); - out->lineto(curve_it2->finalPoint()); - cubic = dynamic_cast(&*curve_it2); - if (cubic) { - SBasisOut = out->first_segment()->toSBasis(); - nextPointAt1 = SBasisOut.valueAt( - Geom::nearest_point((*cubic)[1], *out->first_segment())); - nextPointAt2 = SBasisOut.valueAt( - Geom::nearest_point((*cubic)[2], *out->first_segment())); - ; - nextPointAt3 = out->first_segment()->finalPoint(); + if ( curve_it2 != curve_endit ) { + SPCurve *out = new SPCurve(); + out->moveto(curve_it2->initialPoint()); + out->lineto(curve_it2->finalPoint()); + cubic = dynamic_cast(&*curve_it2); + if (cubic) { + SBasisOut = out->first_segment()->toSBasis(); + if(are_near((*cubic)[1],(*cubic)[0]) && !are_near((*cubic)[2],(*cubic)[3])) { + nextPointAt1 = SBasisIn.valueAt(defaultStartPower); + } else { + nextPointAt1 = SBasisOut.valueAt(Geom::nearest_point((*cubic)[1], *out->first_segment())); + } + } else { + nextPointAt1 = out->first_segment()->initialPoint(); + } + out->reset(); + delete out; + } + Geom::Point startNode = path_it->begin()->initialPoint(); + if (path_it->closed() && curve_it2 == curve_endit) { + SPCurve *start = new SPCurve(); + start->moveto(path_it->begin()->initialPoint()); + start->lineto(path_it->begin()->finalPoint()); + Geom::D2 SBasisStart = start->first_segment()->toSBasis(); + SPCurve *lineHelper = new SPCurve(); + cubic = dynamic_cast(&*path_it->begin()); + if (cubic) { + lineHelper->moveto(SBasisStart.valueAt( + Geom::nearest_point((*cubic)[1], *start->first_segment()))); + } else { + lineHelper->moveto(start->first_segment()->initialPoint()); + } + start->reset(); + delete start; + + SPCurve *end = new SPCurve(); + end->moveto(curve_it1->initialPoint()); + end->lineto(curve_it1->finalPoint()); + Geom::D2 SBasisEnd = end->first_segment()->toSBasis(); + cubic = dynamic_cast(&*curve_it1); + if (cubic) { + lineHelper->lineto(SBasisEnd.valueAt( + Geom::nearest_point((*cubic)[2], *end->first_segment()))); + } else { + lineHelper->lineto(end->first_segment()->finalPoint()); + } + end->reset(); + delete end; + SBasisHelper = lineHelper->first_segment()->toSBasis(); + lineHelper->reset(); + delete lineHelper; + startNode = SBasisHelper.valueAt(0.5); + nCurve->curveto(pointAt1, pointAt2, startNode); + nCurve->move_endpoints(startNode, startNode); + } else if ( curve_it2 == curve_endit) { + nCurve->curveto(pointAt1, pointAt2, curve_it1->finalPoint()); + nCurve->move_endpoints(path_it->begin()->initialPoint(), curve_it1->finalPoint()); } else { - nextPointAt1 = out->first_segment()->initialPoint(); - nextPointAt2 = out->first_segment()->finalPoint(); - nextPointAt3 = out->first_segment()->finalPoint(); + SPCurve *lineHelper = new SPCurve(); + lineHelper->moveto(pointAt2); + lineHelper->lineto(nextPointAt1); + SBasisHelper = lineHelper->first_segment()->toSBasis(); + lineHelper->reset(); + delete lineHelper; + previousNode = node; + node = SBasisHelper.valueAt(0.5); + Geom::CubicBezier const *cubic2 = dynamic_cast(&*curve_it1); + if((cubic && are_near((*cubic)[0],(*cubic)[1])) || (cubic2 && are_near((*cubic2)[2],(*cubic2)[3]))) { + node = curve_it1->finalPoint(); + } + nCurve->curveto(pointAt1, pointAt2, node); } - out->reset(); - delete out; - //La curva BSpline se forma calculando el centro del segmanto de unión - //de el punto situado en las 2/3 partes de el segmento de entrada - //con el punto situado en la posición 1/3 del segmento de salida - //Estos dos puntos ademas estan posicionados en el lugas correspondiente - //de - //los manejadores de la curva - SPCurve *lineHelper = new SPCurve(); - lineHelper->moveto(pointAt2); - lineHelper->lineto(nextPointAt1); - SBasisHelper = lineHelper->first_segment()->toSBasis(); - lineHelper->reset(); - delete lineHelper; - //almacenamos el punto del anterior bucle -o el de cierre- que nos hara de - //principio de curva - previousNode = node; - //Y este hará de final de curva - node = SBasisHelper.valueAt(0.5); - nCurve->curveto(pointAt1, pointAt2, node); - //aumentamos los valores para el siguiente paso en el bucle ++curve_it1; ++curve_it2; } - SPCurve *out = new SPCurve(); - out->moveto(curve_it1->initialPoint()); - out->lineto(curve_it1->finalPoint()); - cubic = dynamic_cast(&*curve_it1); - if (cubic) { - SBasisOut = out->first_segment()->toSBasis(); - nextPointAt1 = SBasisOut.valueAt(Geom::nearest_point((*cubic)[1], *out->first_segment())); - nextPointAt2 = SBasisOut.valueAt(Geom::nearest_point((*cubic)[2], *out->first_segment())); - nextPointAt3 = out->first_segment()->finalPoint(); - } else { - nextPointAt1 = out->first_segment()->initialPoint(); - nextPointAt2 = out->first_segment()->finalPoint(); - nextPointAt3 = out->first_segment()->finalPoint(); - } - out->reset(); - delete out; - //Si está cerrada la curva, la cerramos sobre el valor guardado - //previamente - //Si no finalizamos en el punto final - Geom::Point startNode = path_it->begin()->initialPoint(); - if (path_it->closed()) { - SPCurve *start = new SPCurve(); - start->moveto(path_it->begin()->initialPoint()); - start->lineto(path_it->begin()->finalPoint()); - Geom::D2 SBasisStart = start->first_segment()->toSBasis(); - SPCurve *lineHelper = new SPCurve(); - cubic = dynamic_cast(&*path_it->begin()); - if (cubic) { - lineHelper->moveto(SBasisStart.valueAt( - Geom::nearest_point((*cubic)[1], *start->first_segment()))); - } else { - lineHelper->moveto(start->first_segment()->initialPoint()); - } - start->reset(); - delete start; - - SPCurve *end = new SPCurve(); - end->moveto(curve_it1->initialPoint()); - end->lineto(curve_it1->finalPoint()); - Geom::D2 SBasisEnd = end->first_segment()->toSBasis(); - - cubic = dynamic_cast(&*curve_it1); - if (cubic) { - lineHelper->lineto(SBasisEnd.valueAt( - Geom::nearest_point((*cubic)[2], *end->first_segment()))); - } else { - lineHelper->lineto(end->first_segment()->finalPoint()); - } - end->reset(); - delete end; - SBasisHelper = lineHelper->first_segment()->toSBasis(); - lineHelper->reset(); - delete lineHelper; - startNode = SBasisHelper.valueAt(0.5); - nCurve->curveto(nextPointAt1, nextPointAt2, startNode); - nCurve->move_endpoints(startNode, startNode); - } else { - SPCurve *start = new SPCurve(); - start->moveto(path_it->begin()->initialPoint()); - start->lineto(path_it->begin()->finalPoint()); - startNode = start->first_segment()->initialPoint(); - start->reset(); - delete start; - nCurve->curveto(nextPointAt1, nextPointAt2, nextPointAt3); - nCurve->move_endpoints(startNode, nextPointAt3); - } //y cerramos la curva if (path_it->closed()) { nCurve->closepath_current(); @@ -349,22 +298,22 @@ Gtk::Widget *LPEBSpline::newWidget() void LPEBSpline::toDefaultWeight(Gtk::Widget *widgWeight) { - weight.param_set_value(0.3334); - changeWeight(0.3334); + weight.param_set_value(defaultStartPower); + changeWeight(defaultStartPower); Gtk::HBox * scalarParameter = dynamic_cast(widgWeight); std::vector< Gtk::Widget* > childList = scalarParameter->get_children(); Gtk::Entry* entryWidg = dynamic_cast(childList[1]); - entryWidg->set_text("0.3334"); + entryWidg->set_text("defaultStartPower"); } void LPEBSpline::toMakeCusp(Gtk::Widget *widgWeight) { - weight.param_set_value(0.0000); - changeWeight(0.0000); + weight.param_set_value(noPower); + changeWeight(noPower); Gtk::HBox * scalarParameter = dynamic_cast(widgWeight); std::vector< Gtk::Widget* > childList = scalarParameter->get_children(); Gtk::Entry* entryWidg = dynamic_cast(childList[1]); - entryWidg->set_text("0.0000"); + entryWidg->set_text("noPower"); } void LPEBSpline::toWeight() @@ -402,7 +351,7 @@ bool LPEBSpline::nodeIsSelected(Geom::Point nodePoint) for (std::vector::iterator i = points.begin(); i != points.end(); ++i) { Geom::Point p = *i; - if (Geom::are_near(p, nodePoint, 0.0001)) { + if (Geom::are_near(p, nodePoint, handleCubicGap)) { return true; } else { } @@ -463,10 +412,6 @@ void LPEBSpline::doBSplineFromWidget(SPCurve *curve, double weightValue) Geom::Point pointAt1(0, 0); Geom::Point pointAt2(0, 0); Geom::Point pointAt3(0, 0); - Geom::Point nextPointAt0(0, 0); - Geom::Point nextPointAt1(0, 0); - Geom::Point nextPointAt2(0, 0); - Geom::Point nextPointAt3(0, 0); Geom::D2 SBasisIn; Geom::D2 SBasisOut; Geom::CubicBezier const *cubic = NULL; @@ -490,7 +435,7 @@ void LPEBSpline::doBSplineFromWidget(SPCurve *curve, double weightValue) //BSPline nCurve->moveto(curve_it1->initialPoint()); //Recorremos todos los segmentos menos el último - while (curve_it2 != curve_endit) { + while (curve_it1 != curve_endit) { //previousPointAt3 = pointAt3; //Calculamos los puntos que dividirían en tres segmentos iguales el path //recto de entrada y de salida @@ -505,33 +450,33 @@ void LPEBSpline::doBSplineFromWidget(SPCurve *curve, double weightValue) if (cubic) { if (!ignoreCusp || !Geom::are_near((*cubic)[1], pointAt0)) { pointAt1 = SBasisIn.valueAt(weightValue); - if (weightValue != 0.0000) { + if (weightValue != noPower) { pointAt1 = - Geom::Point(pointAt1[X] + 0.0001, pointAt1[Y] + 0.0001); + Geom::Point(pointAt1[X] + handleCubicGap, pointAt1[Y] + handleCubicGap); } } else { pointAt1 = in->first_segment()->initialPoint(); } if (!ignoreCusp || !Geom::are_near((*cubic)[2], pointAt3)) { pointAt2 = SBasisIn.valueAt(1 - weightValue); - if (weightValue != 0.0000) { + if (weightValue != noPower) { pointAt2 = - Geom::Point(pointAt2[X] + 0.0001, pointAt2[Y] + 0.0001); + Geom::Point(pointAt2[X] + handleCubicGap, pointAt2[Y] + handleCubicGap); } } else { pointAt2 = in->first_segment()->finalPoint(); } } else { - if (!ignoreCusp && weightValue != 0.0000) { + if (!ignoreCusp && weightValue != noPower) { pointAt1 = SBasisIn.valueAt(weightValue); - if (weightValue != 0.0000) { + if (weightValue != noPower) { pointAt1 = - Geom::Point(pointAt1[X] + 0.0001, pointAt1[Y] + 0.0001); + Geom::Point(pointAt1[X] + handleCubicGap, pointAt1[Y] + handleCubicGap); } pointAt2 = SBasisIn.valueAt(1 - weightValue); - if (weightValue != 0.0000) { + if (weightValue != noPower) { pointAt2 = - Geom::Point(pointAt2[X] + 0.0001, pointAt2[Y] + 0.0001); + Geom::Point(pointAt2[X] + handleCubicGap, pointAt2[Y] + handleCubicGap); } } else { pointAt1 = in->first_segment()->initialPoint(); @@ -543,9 +488,9 @@ void LPEBSpline::doBSplineFromWidget(SPCurve *curve, double weightValue) if (!ignoreCusp || !Geom::are_near((*cubic)[1], pointAt0)) { if (nodeIsSelected(pointAt0)) { pointAt1 = SBasisIn.valueAt(weightValue); - if (weightValue != 0.0000) { + if (weightValue != noPower) { pointAt1 = - Geom::Point(pointAt1[X] + 0.0001, pointAt1[Y] + 0.0001); + Geom::Point(pointAt1[X] + handleCubicGap, pointAt1[Y] + handleCubicGap); } } else { pointAt1 = (*cubic)[1]; @@ -556,9 +501,9 @@ void LPEBSpline::doBSplineFromWidget(SPCurve *curve, double weightValue) if (!ignoreCusp || !Geom::are_near((*cubic)[2], pointAt3)) { if (nodeIsSelected(pointAt3)) { pointAt2 = SBasisIn.valueAt(1 - weightValue); - if (weightValue != 0.0000) { + if (weightValue != noPower) { pointAt2 = - Geom::Point(pointAt2[X] + 0.0001, pointAt2[Y] + 0.0001); + Geom::Point(pointAt2[X] + handleCubicGap, pointAt2[Y] + handleCubicGap); } } else { pointAt2 = (*cubic)[2]; @@ -567,18 +512,18 @@ void LPEBSpline::doBSplineFromWidget(SPCurve *curve, double weightValue) pointAt2 = in->first_segment()->finalPoint(); } } else { - if (!ignoreCusp && weightValue != 0.000) { + if (!ignoreCusp && weightValue != noPower) { if (nodeIsSelected(pointAt0)) { pointAt1 = SBasisIn.valueAt(weightValue); pointAt1 = - Geom::Point(pointAt1[X] + 0.0001, pointAt1[Y] + 0.0001); + Geom::Point(pointAt1[X] + handleCubicGap, pointAt1[Y] + handleCubicGap); } else { pointAt1 = in->first_segment()->initialPoint(); } if (nodeIsSelected(pointAt3)) { pointAt2 = SBasisIn.valueAt(weightValue); pointAt2 = - Geom::Point(pointAt2[X] + 0.0001, pointAt2[Y] + 0.0001); + Geom::Point(pointAt2[X] + handleCubicGap, pointAt2[Y] + handleCubicGap); } else { pointAt2 = in->first_segment()->finalPoint(); } @@ -601,110 +546,11 @@ void LPEBSpline::doBSplineFromWidget(SPCurve *curve, double weightValue) ++curve_it1; ++curve_it2; } - SPCurve *out = new SPCurve(); - out->moveto(curve_it1->initialPoint()); - out->lineto(curve_it1->finalPoint()); - SBasisOut = out->first_segment()->toSBasis(); - nextPointAt0 = out->first_segment()->initialPoint(); - nextPointAt3 = out->first_segment()->finalPoint(); - cubic = dynamic_cast(&*curve_it1); - if (!onlySelected) { - if (cubic) { - if (!ignoreCusp || !Geom::are_near((*cubic)[1], nextPointAt0)) { - nextPointAt1 = SBasisOut.valueAt(weightValue); - if (weightValue != 0.0000) { - nextPointAt1 = - Geom::Point(nextPointAt1[X] + 0.0001, nextPointAt1[Y] + 0.0001); - } - } else { - nextPointAt1 = out->first_segment()->initialPoint(); - } - if (!ignoreCusp || !Geom::are_near((*cubic)[2], nextPointAt3)) { - nextPointAt2 = SBasisOut.valueAt(1 - weightValue); - if (weightValue != 0.0000) { - nextPointAt2 = - Geom::Point(nextPointAt2[X] + 0.0001, nextPointAt2[Y] + 0.0001); - } - } else { - nextPointAt2 = out->first_segment()->finalPoint(); - } - } else { - if (!ignoreCusp && weightValue != 0.0000) { - nextPointAt1 = SBasisOut.valueAt(weightValue); - nextPointAt1 = - Geom::Point(nextPointAt1[X] + 0.0001, nextPointAt1[Y] + 0.0001); - nextPointAt2 = SBasisOut.valueAt(1 - weightValue); - nextPointAt2 = - Geom::Point(nextPointAt2[X] + 0.0001, nextPointAt2[Y] + 0.0001); - } else { - nextPointAt1 = out->first_segment()->initialPoint(); - nextPointAt2 = out->first_segment()->finalPoint(); - } - } - } else { - if (cubic) { - if (!ignoreCusp || !Geom::are_near((*cubic)[1], nextPointAt0)) { - if (nodeIsSelected(nextPointAt0)) { - nextPointAt1 = SBasisOut.valueAt(weightValue); - if (weightValue != 0.0000) { - nextPointAt1 = Geom::Point(nextPointAt1[X] + 0.0001, - nextPointAt1[Y] + 0.0001); - } - } else { - nextPointAt1 = (*cubic)[1]; - } - } else { - nextPointAt1 = out->first_segment()->initialPoint(); - } - if (!ignoreCusp || !Geom::are_near((*cubic)[2], nextPointAt3)) { - if (nodeIsSelected(nextPointAt3)) { - nextPointAt2 = SBasisOut.valueAt(1 - weightValue); - if (weightValue != 0.0000) { - nextPointAt2 = Geom::Point(nextPointAt2[X] + 0.0001, - nextPointAt2[Y] + 0.0001); - } - } else { - nextPointAt2 = (*cubic)[2]; - } - } else { - nextPointAt2 = out->first_segment()->finalPoint(); - } - } else { - if (!ignoreCusp && weightValue != 0.0000) { - if (nodeIsSelected(nextPointAt0)) { - nextPointAt1 = SBasisOut.valueAt(weightValue); - nextPointAt1 = - Geom::Point(nextPointAt1[X] + 0.0001, nextPointAt1[Y] + 0.0001); - } else { - nextPointAt1 = out->first_segment()->initialPoint(); - } - if (nodeIsSelected(nextPointAt3)) { - nextPointAt2 = SBasisOut.valueAt(weightValue); - nextPointAt2 = - Geom::Point(nextPointAt2[X] + 0.0001, nextPointAt2[Y] + 0.0001); - } else { - nextPointAt2 = out->first_segment()->finalPoint(); - } - } else { - nextPointAt1 = out->first_segment()->initialPoint(); - nextPointAt2 = out->first_segment()->finalPoint(); - } - } - } - out->reset(); - delete out; - //Aberiguamos la ultima parte de la curva correspondiente al último - //segmento - //Y hacemos lo propio con el path de salida - //nextPointAt0 = curveOut.valueAt(0); if (path_it->closed()) { - nCurve->curveto(nextPointAt1, nextPointAt2, - path_it->begin()->initialPoint()); nCurve->move_endpoints(path_it->begin()->initialPoint(), path_it->begin()->initialPoint()); } else { - nCurve->curveto(nextPointAt1, nextPointAt2, nextPointAt3); - nCurve->move_endpoints(path_it->begin()->initialPoint(), nextPointAt3); + nCurve->move_endpoints(path_it->begin()->initialPoint(), pointAt3); } //y cerramos la curva if (path_it->closed()) { diff --git a/src/live_effects/lpe-copy_rotate.cpp b/src/live_effects/lpe-copy_rotate.cpp index 65bbcdad1..e466093d3 100644 --- a/src/live_effects/lpe-copy_rotate.cpp +++ b/src/live_effects/lpe-copy_rotate.cpp @@ -12,6 +12,7 @@ */ #include +#include #include "live_effects/lpe-copy_rotate.h" #include "sp-shape.h" diff --git a/src/live_effects/lpe-ellipse_5pts.cpp b/src/live_effects/lpe-ellipse_5pts.cpp new file mode 100644 index 000000000..b0a5919fe --- /dev/null +++ b/src/live_effects/lpe-ellipse_5pts.cpp @@ -0,0 +1,214 @@ +/** \file + * LPE "Ellipse through 5 points" implementation + */ + +/* + * Authors: + * Theodore Janeczko + * + * Copyright (C) Theodore Janeczko 2012 + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "live_effects/lpe-ellipse_5pts.h" + +// You might need to include other 2geom files. You can add them here: +#include +#include <2geom/path.h> +#include <2geom/circle.h> +#include <2geom/ellipse.h> +#include <2geom/path-sink.h> +#include "inkscape.h" +#include "desktop.h" +#include "message-stack.h" + +namespace Inkscape { +namespace LivePathEffect { + +LPEEllipse5Pts::LPEEllipse5Pts(LivePathEffectObject *lpeobject) : + Effect(lpeobject) +{ + //perceived_path = true; +} + +LPEEllipse5Pts::~LPEEllipse5Pts() +{ +} + +static double _det3(double (*mat)[3]) +{ + for (int i = 0; i < 2; i++) + { + for (int j = i + 1; j < 3; j++) + { + for (int k = i + 1; k < 3; k++) + { + mat[j][k] = (mat[j][k] * mat[i][i] - mat[j][i] * mat[i][k]); + if (i) mat[j][k] /= mat[i-1][i-1]; + } + } + } + return mat[2][2]; +} +static double _det5(double (*mat)[5]) +{ + for (int i = 0; i < 4; i++) + { + for (int j = i + 1; j < 5; j++) + { + for (int k = i + 1; k < 5; k++) + { + mat[j][k] = (mat[j][k] * mat[i][i] - mat[j][i] * mat[i][k]); + if (i) mat[j][k] /= mat[i-1][i-1]; + } + } + } + return mat[4][4]; +} + +std::vector +LPEEllipse5Pts::doEffect_path (std::vector const & path_in) +{ + std::vector path_out = std::vector(); + + if (path_in[0].size() < 4) { + + SP_ACTIVE_DESKTOP->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Five points required for constructing an ellipse")); + return path_in; + } + // we assume that the path has >= 3 nodes + Geom::Point A = path_in[0].initialPoint(); + Geom::Point B = path_in[0].pointAt(1); + Geom::Point C = path_in[0].pointAt(2); + Geom::Point D = path_in[0].pointAt(3); + Geom::Point E = path_in[0].pointAt(4); + + using namespace Geom; + + double rowmajor_matrix[5][6] = + { + {A.x()*A.x(), A.x()*A.y(), A.y()*A.y(), A.x(), A.y(), 1}, + {B.x()*B.x(), B.x()*B.y(), B.y()*B.y(), B.x(), B.y(), 1}, + {C.x()*C.x(), C.x()*C.y(), C.y()*C.y(), C.x(), C.y(), 1}, + {D.x()*D.x(), D.x()*D.y(), D.y()*D.y(), D.x(), D.y(), 1}, + {E.x()*E.x(), E.x()*E.y(), E.y()*E.y(), E.x(), E.y(), 1} + }; + + double mat_a[5][5] = + { + {rowmajor_matrix[0][1], rowmajor_matrix[1][1], rowmajor_matrix[2][1], rowmajor_matrix[3][1], rowmajor_matrix[4][1]}, + {rowmajor_matrix[0][2], rowmajor_matrix[1][2], rowmajor_matrix[2][2], rowmajor_matrix[3][2], rowmajor_matrix[4][2]}, + {rowmajor_matrix[0][3], rowmajor_matrix[1][3], rowmajor_matrix[2][3], rowmajor_matrix[3][3], rowmajor_matrix[4][3]}, + {rowmajor_matrix[0][4], rowmajor_matrix[1][4], rowmajor_matrix[2][4], rowmajor_matrix[3][4], rowmajor_matrix[4][4]}, + {rowmajor_matrix[0][5], rowmajor_matrix[1][5], rowmajor_matrix[2][5], rowmajor_matrix[3][5], rowmajor_matrix[4][5]} + }; + double mat_b[5][5] = + { + {rowmajor_matrix[0][0], rowmajor_matrix[1][0], rowmajor_matrix[2][0], rowmajor_matrix[3][0], rowmajor_matrix[4][0]}, + {rowmajor_matrix[0][2], rowmajor_matrix[1][2], rowmajor_matrix[2][2], rowmajor_matrix[3][2], rowmajor_matrix[4][2]}, + {rowmajor_matrix[0][3], rowmajor_matrix[1][3], rowmajor_matrix[2][3], rowmajor_matrix[3][3], rowmajor_matrix[4][3]}, + {rowmajor_matrix[0][4], rowmajor_matrix[1][4], rowmajor_matrix[2][4], rowmajor_matrix[3][4], rowmajor_matrix[4][4]}, + {rowmajor_matrix[0][5], rowmajor_matrix[1][5], rowmajor_matrix[2][5], rowmajor_matrix[3][5], rowmajor_matrix[4][5]} + }; + double mat_c[5][5] = + { + {rowmajor_matrix[0][0], rowmajor_matrix[1][0], rowmajor_matrix[2][0], rowmajor_matrix[3][0], rowmajor_matrix[4][0]}, + {rowmajor_matrix[0][1], rowmajor_matrix[1][1], rowmajor_matrix[2][1], rowmajor_matrix[3][1], rowmajor_matrix[4][1]}, + {rowmajor_matrix[0][3], rowmajor_matrix[1][3], rowmajor_matrix[2][3], rowmajor_matrix[3][3], rowmajor_matrix[4][3]}, + {rowmajor_matrix[0][4], rowmajor_matrix[1][4], rowmajor_matrix[2][4], rowmajor_matrix[3][4], rowmajor_matrix[4][4]}, + {rowmajor_matrix[0][5], rowmajor_matrix[1][5], rowmajor_matrix[2][5], rowmajor_matrix[3][5], rowmajor_matrix[4][5]} + }; + double mat_d[5][5] = + { + {rowmajor_matrix[0][0], rowmajor_matrix[1][0], rowmajor_matrix[2][0], rowmajor_matrix[3][0], rowmajor_matrix[4][0]}, + {rowmajor_matrix[0][1], rowmajor_matrix[1][1], rowmajor_matrix[2][1], rowmajor_matrix[3][1], rowmajor_matrix[4][1]}, + {rowmajor_matrix[0][2], rowmajor_matrix[1][2], rowmajor_matrix[2][2], rowmajor_matrix[3][2], rowmajor_matrix[4][2]}, + {rowmajor_matrix[0][4], rowmajor_matrix[1][4], rowmajor_matrix[2][4], rowmajor_matrix[3][4], rowmajor_matrix[4][4]}, + {rowmajor_matrix[0][5], rowmajor_matrix[1][5], rowmajor_matrix[2][5], rowmajor_matrix[3][5], rowmajor_matrix[4][5]} + }; + double mat_e[5][5] = + { + {rowmajor_matrix[0][0], rowmajor_matrix[1][0], rowmajor_matrix[2][0], rowmajor_matrix[3][0], rowmajor_matrix[4][0]}, + {rowmajor_matrix[0][1], rowmajor_matrix[1][1], rowmajor_matrix[2][1], rowmajor_matrix[3][1], rowmajor_matrix[4][1]}, + {rowmajor_matrix[0][2], rowmajor_matrix[1][2], rowmajor_matrix[2][2], rowmajor_matrix[3][2], rowmajor_matrix[4][2]}, + {rowmajor_matrix[0][3], rowmajor_matrix[1][3], rowmajor_matrix[2][3], rowmajor_matrix[3][3], rowmajor_matrix[4][3]}, + {rowmajor_matrix[0][5], rowmajor_matrix[1][5], rowmajor_matrix[2][5], rowmajor_matrix[3][5], rowmajor_matrix[4][5]} + }; + double mat_f[5][5] = + { + {rowmajor_matrix[0][0], rowmajor_matrix[1][0], rowmajor_matrix[2][0], rowmajor_matrix[3][0], rowmajor_matrix[4][0]}, + {rowmajor_matrix[0][1], rowmajor_matrix[1][1], rowmajor_matrix[2][1], rowmajor_matrix[3][1], rowmajor_matrix[4][1]}, + {rowmajor_matrix[0][2], rowmajor_matrix[1][2], rowmajor_matrix[2][2], rowmajor_matrix[3][2], rowmajor_matrix[4][2]}, + {rowmajor_matrix[0][3], rowmajor_matrix[1][3], rowmajor_matrix[2][3], rowmajor_matrix[3][3], rowmajor_matrix[4][3]}, + {rowmajor_matrix[0][4], rowmajor_matrix[1][4], rowmajor_matrix[2][4], rowmajor_matrix[3][4], rowmajor_matrix[4][4]} + }; + + double a1 = _det5(mat_a); + double b1 = -_det5(mat_b); + double c1 = _det5(mat_c); + double d1 = -_det5(mat_d); + double e1 = _det5(mat_e); + double f1 = -_det5(mat_f); + + double mat_check[][3] = + { + {a1, b1/2, d1/2}, + {b1/2, c1, e1/2}, + {d1/2, e1/2, f1} + }; + + if (_det3(mat_check) == 0 || a1*c1 - b1*b1/4 <= 0) { + SP_ACTIVE_DESKTOP->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("No ellipse found for specified points")); + return path_in; + } + + Geom::Ellipse el(a1, b1, c1, d1, e1, f1); + + double s, e; + double x0, y0, x1, y1, x2, y2, x3, y3; + double len; + + // figure out if we have a slice, guarding against rounding errors + + Path p(Geom::Point(cos(0), sin(0))); + + double end = 2 * M_PI; + for (s = 0; s < end; s += M_PI_2) { + e = s + M_PI_2; + if (e > end) + e = end; + len = 4*tan((e - s)/4)/3; + x0 = cos(s); + y0 = sin(s); + x1 = x0 + len * cos(s + M_PI_2); + y1 = y0 + len * sin(s + M_PI_2); + x3 = cos(e); + y3 = sin(e); + x2 = x3 + len * cos(e - M_PI_2); + y2 = y3 + len * sin(e - M_PI_2); + p.appendNew(Geom::Point(x1,y1), Geom::Point(x2,y2), Geom::Point(x3,y3)); + } + + Geom::Affine aff = Geom::Scale(el.ray(Geom::X), el.ray(Geom::Y)) * Geom::Rotate(el.rot_angle()) * Geom::Translate(el.center()); + + path_out.push_back(p * aff); + + return path_out; +} + +/* ######################## */ + +} //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 : diff --git a/src/live_effects/lpe-ellipse_5pts.h b/src/live_effects/lpe-ellipse_5pts.h new file mode 100644 index 000000000..d3b1fccfa --- /dev/null +++ b/src/live_effects/lpe-ellipse_5pts.h @@ -0,0 +1,50 @@ +#ifndef INKSCAPE_LPE_ELLIPSE_5PTS_H +#define INKSCAPE_LPE_ELLIPSE_5PTS_H + +/** \file + * LPE "Ellipse through 5 points" implementation + */ + +/* + * Authors: + * Theodore Janeczko + * + * Copyright (C) Theodore Janeczko 2012 + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "live_effects/effect.h" +#include "live_effects/parameter/parameter.h" +#include "live_effects/parameter/point.h" + +namespace Inkscape { +namespace LivePathEffect { + +class LPEEllipse5Pts : public Effect { +public: + LPEEllipse5Pts(LivePathEffectObject *lpeobject); + virtual ~LPEEllipse5Pts(); + + virtual std::vector doEffect_path (std::vector const & path_in); + +private: + LPEEllipse5Pts(const LPEEllipse5Pts&); + LPEEllipse5Pts& operator=(const LPEEllipse5Pts&); +}; + +} //namespace LivePathEffect +} //namespace Inkscape + +#endif + +/* + 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 : diff --git a/src/live_effects/lpe-fill-between-many.cpp b/src/live_effects/lpe-fill-between-many.cpp new file mode 100644 index 000000000..7cf354044 --- /dev/null +++ b/src/live_effects/lpe-fill-between-many.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (C) Theodore Janeczko 2012 + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include + +#include "live_effects/lpe-fill-between-many.h" + +#include "display/curve.h" +#include "sp-item.h" +#include "2geom/path.h" +#include "sp-shape.h" +#include "sp-text.h" +#include "2geom/bezier-curve.h" + +#include + +namespace Inkscape { +namespace LivePathEffect { + +LPEFillBetweenMany::LPEFillBetweenMany(LivePathEffectObject *lpeobject) : + Effect(lpeobject), + linked_paths(_("Linked path:"), _("Paths from which to take the original path data"), "linkedpaths", &wr, this) +{ + registerParameter( dynamic_cast(&linked_paths) ); + //perceived_path = true; +} + +LPEFillBetweenMany::~LPEFillBetweenMany() +{ + +} + +void LPEFillBetweenMany::doEffect (SPCurve * curve) +{ + std::vector res_pathv; + SPItem * firstObj = NULL; + for (std::vector::iterator iter = linked_paths._vector.begin(); iter != linked_paths._vector.end(); iter++) { + SPObject *obj; + if ((*iter)->ref.isAttached() && (obj = (*iter)->ref.getObject()) && SP_IS_ITEM(obj) && !(*iter)->_pathvector.empty()) { + Geom::Path linked_path; + if ((*iter)->reversed) { + linked_path = (*iter)->_pathvector.front().reverse(); + } else { + linked_path = (*iter)->_pathvector.front(); + } + + if (!res_pathv.empty()) { + linked_path = linked_path * SP_ITEM(obj)->getRelativeTransform(firstObj); + res_pathv.front().appendNew(linked_path.initialPoint()); + res_pathv.front().append(linked_path); + } else { + firstObj = SP_ITEM(obj); + res_pathv.push_back(linked_path); + } + } + } + if (!res_pathv.empty()) { + res_pathv.front().close(); + } + curve->set_pathvector(res_pathv); +} + +} // 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 : diff --git a/src/live_effects/lpe-fill-between-many.h b/src/live_effects/lpe-fill-between-many.h new file mode 100644 index 000000000..99ee8b15f --- /dev/null +++ b/src/live_effects/lpe-fill-between-many.h @@ -0,0 +1,36 @@ +#ifndef INKSCAPE_LPE_FILL_BETWEEN_MANY_H +#define INKSCAPE_LPE_FILL_BETWEEN_MANY_H + +/* + * Inkscape::LPEFillBetweenStrokes + * + * Copyright (C) Theodore Janeczko 2012 + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "live_effects/effect.h" +#include "live_effects/parameter/originalpatharray.h" + +namespace Inkscape { +namespace LivePathEffect { + +class LPEFillBetweenMany : public Effect { +public: + LPEFillBetweenMany(LivePathEffectObject *lpeobject); + virtual ~LPEFillBetweenMany(); + + virtual void doEffect (SPCurve * curve); + +private: + OriginalPathArrayParam linked_paths; + +private: + LPEFillBetweenMany(const LPEFillBetweenMany&); + LPEFillBetweenMany& operator=(const LPEFillBetweenMany&); +}; + +}; //namespace LivePathEffect +}; //namespace Inkscape + +#endif diff --git a/src/live_effects/lpe-fill-between-strokes.cpp b/src/live_effects/lpe-fill-between-strokes.cpp new file mode 100644 index 000000000..e72979ed0 --- /dev/null +++ b/src/live_effects/lpe-fill-between-strokes.cpp @@ -0,0 +1,116 @@ +/* + * Copyright (C) Theodore Janeczko 2012 + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include + +#include "live_effects/lpe-fill-between-strokes.h" + +#include "display/curve.h" +#include "sp-item.h" +#include "2geom/path.h" +#include "sp-shape.h" +#include "sp-text.h" +#include "2geom/bezier-curve.h" + +namespace Inkscape { +namespace LivePathEffect { + +LPEFillBetweenStrokes::LPEFillBetweenStrokes(LivePathEffectObject *lpeobject) : + Effect(lpeobject), + linked_path(_("Linked path:"), _("Path from which to take the original path data"), "linkedpath", &wr, this), + second_path(_("Second path:"), _("Second path from which to take the original path data"), "secondpath", &wr, this), + reverse_second(_("Reverse Second"), _("Reverses the second path order"), "reversesecond", &wr, this) +{ + registerParameter( dynamic_cast(&linked_path) ); + registerParameter( dynamic_cast(&second_path) ); + registerParameter( dynamic_cast(&reverse_second) ); + //perceived_path = true; +} + +LPEFillBetweenStrokes::~LPEFillBetweenStrokes() +{ + +} + +void LPEFillBetweenStrokes::doEffect (SPCurve * curve) +{ + if (curve) { + if ( linked_path.linksToPath() && second_path.linksToPath() && linked_path.getObject() && second_path.getObject() ) { + std::vector linked_pathv = linked_path.get_pathvector(); + std::vector second_pathv = second_path.get_pathvector(); + std::vector result_linked_pathv; + std::vector result_second_pathv; + Geom::Affine second_transform = second_path.getObject()->getRelativeTransform(linked_path.getObject()); + + for (std::vector::iterator iter = linked_pathv.begin(); iter != linked_pathv.end(); ++iter) + { + result_linked_pathv.push_back((*iter)); + } + for (std::vector::iterator iter = second_pathv.begin(); iter != second_pathv.end(); ++iter) + { + result_second_pathv.push_back((*iter) * second_transform); + } + + if ( !result_linked_pathv.empty() && !result_second_pathv.empty() && !result_linked_pathv.front().closed() ) { + if (reverse_second.get_value()) + { + result_linked_pathv.front().appendNew(result_second_pathv.front().finalPoint()); + result_linked_pathv.front().append(result_second_pathv.front().reverse()); + } + else + { + result_linked_pathv.front().appendNew(result_second_pathv.front().initialPoint()); + result_linked_pathv.front().append(result_second_pathv.front()); + } + curve->set_pathvector(result_linked_pathv); + } + else if ( !result_linked_pathv.empty() ) { + curve->set_pathvector(result_linked_pathv); + } + else if ( !result_second_pathv.empty() ) { + curve->set_pathvector(result_second_pathv); + } + } + else if ( linked_path.linksToPath() && linked_path.getObject() ) { + std::vector linked_pathv = linked_path.get_pathvector(); + std::vector result_pathv; + + for (std::vector::iterator iter = linked_pathv.begin(); iter != linked_pathv.end(); ++iter) + { + result_pathv.push_back((*iter)); + } + if ( !result_pathv.empty() ) { + curve->set_pathvector(result_pathv); + } + } + else if ( second_path.linksToPath() && second_path.getObject() ) { + std::vector second_pathv = second_path.get_pathvector(); + std::vector result_pathv; + + for (std::vector::iterator iter = second_pathv.begin(); iter != second_pathv.end(); ++iter) + { + result_pathv.push_back((*iter)); + } + if ( !result_pathv.empty() ) { + curve->set_pathvector(result_pathv); + } + } + } +} + +} // 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 : diff --git a/src/live_effects/lpe-fill-between-strokes.h b/src/live_effects/lpe-fill-between-strokes.h new file mode 100644 index 000000000..ec57b1852 --- /dev/null +++ b/src/live_effects/lpe-fill-between-strokes.h @@ -0,0 +1,38 @@ +#ifndef INKSCAPE_LPE_FILL_BETWEEN_STROKES_H +#define INKSCAPE_LPE_FILL_BETWEEN_STROKES_H + +/* + * Inkscape::LPEFillBetweenStrokes + * + * Copyright (C) Theodore Janeczko 2012 + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "live_effects/effect.h" +#include "live_effects/parameter/originalpath.h" + +namespace Inkscape { +namespace LivePathEffect { + +class LPEFillBetweenStrokes : public Effect { +public: + LPEFillBetweenStrokes(LivePathEffectObject *lpeobject); + virtual ~LPEFillBetweenStrokes(); + + virtual void doEffect (SPCurve * curve); + +private: + OriginalPathParam linked_path; + OriginalPathParam second_path; + BoolParam reverse_second; + +private: + LPEFillBetweenStrokes(const LPEFillBetweenStrokes&); + LPEFillBetweenStrokes& operator=(const LPEFillBetweenStrokes&); +}; + +}; //namespace LivePathEffect +}; //namespace Inkscape + +#endif diff --git a/src/live_effects/lpe-fillet-chamfer.cpp b/src/live_effects/lpe-fillet-chamfer.cpp index bfb2c3ba4..857e9d9cc 100644 --- a/src/live_effects/lpe-fillet-chamfer.cpp +++ b/src/live_effects/lpe-fillet-chamfer.cpp @@ -22,6 +22,9 @@ #include "desktop.h" #include "display/curve.h" #include "helper/geom-nodetype.h" +#include "helper/geom-curves.h" +#include "helper/geom.h" + #include "live_effects/parameter/filletchamferpointarray.h" // for programmatically updating knots @@ -49,6 +52,8 @@ LPEFilletChamfer::LPEFilletChamfer(LivePathEffectObject *lpeobject) : ignore_radius_0(_("Ignore 0 radius knots"), _("Ignore 0 radius knots"), "ignore_radius_0", &wr, this, false), only_selected(_("Change only selected nodes"), _("Change only selected nodes"), "only_selected", &wr, this, false), flexible(_("Flexible radius size (%)"), _("Flexible radius size (%)"), "flexible", &wr, this, false), + force_arcs(_("Use arcs in cubic curves"), _("Use arcs in cubic curves"), "force_arcs", &wr, this, false), + use_knot_distance(_("Use knots distance instead radius"), _("Use knots distance instead radius"), "use_knot_distance", &wr, this, false), unit(_("Unit"), _("Unit"), "unit", &wr, this), radius(_("Radius (unit or %)"), _("Radius, in unit or %"), "radius", &wr, this, 0.), helper_size(_("Helper size with direction"), _("Helper size with direction"), "helper_size", &wr, this, 0) @@ -58,9 +63,11 @@ LPEFilletChamfer::LPEFilletChamfer(LivePathEffectObject *lpeobject) : registerParameter(&radius); registerParameter(&helper_size); registerParameter(&flexible); + registerParameter(&use_knot_distance); registerParameter(&ignore_radius_0); registerParameter(&only_selected); registerParameter(&hide_knots); + registerParameter(&force_arcs); radius.param_set_range(0., infinity()); radius.param_set_increments(1, 1); @@ -270,10 +277,10 @@ void LPEFilletChamfer::doUpdateFillet(std::vector const& original_pa } std::vector filletChamferData = fillet_chamfer_values.data(); std::vector result; + std::vector original_pathv_processed = pathv_to_linear_and_cubic_beziers(original_pathv); int counter = 0; - for (PathVector::const_iterator path_it = original_pathv.begin(); - path_it != original_pathv.end(); ++path_it) { - int pathCounter = 0; + for (PathVector::const_iterator path_it = original_pathv_processed.begin(); + path_it != original_pathv_processed.end(); ++path_it) { if (path_it->empty()) continue; @@ -289,6 +296,9 @@ void LPEFilletChamfer::doUpdateFillet(std::vector const& original_pa double powerend = 0; while (curve_it1 != curve_endit) { powerend = power; + if (power < 0 && !use_knot_distance) { + powerend = fillet_chamfer_values.rad_to_len(counter,powerend); + } if (power > 0) { powerend = counter + (power / 100); } @@ -306,7 +316,6 @@ void LPEFilletChamfer::doUpdateFillet(std::vector const& original_pa ++curve_it1; ++curve_it2; counter++; - pathCounter++; } } fillet_chamfer_values.param_set_and_write_new_value(result); @@ -334,8 +343,9 @@ void LPEFilletChamfer::doChangeType(std::vector const& original_path } std::vector filletChamferData = fillet_chamfer_values.data(); std::vector result; + std::vector original_pathv_processed = pathv_to_linear_and_cubic_beziers(original_pathv); int counter = 0; - for (PathVector::const_iterator path_it = original_pathv.begin(); path_it != original_pathv.end(); ++path_it) { + for (PathVector::const_iterator path_it = original_pathv_processed.begin(); path_it != original_pathv_processed.end(); ++path_it) { int pathCounter = 0; if (path_it->empty()) continue; @@ -378,7 +388,7 @@ void LPEFilletChamfer::doOnApply(SPLPEItem const *lpeItem) { if (SP_IS_SHAPE(lpeItem)) { std::vector point; - PathVector const &original_pathv = SP_SHAPE(lpeItem)->_curve->get_pathvector(); + PathVector const &original_pathv = pathv_to_linear_and_cubic_beziers(SP_SHAPE(lpeItem)->_curve->get_pathvector()); Piecewise > pwd2_in = paths_to_pw(original_pathv); for (PathVector::const_iterator path_it = original_pathv.begin(); path_it != original_pathv.end(); ++path_it) { if (path_it->empty()) @@ -401,10 +411,15 @@ void LPEFilletChamfer::doOnApply(SPLPEItem const *lpeItem) } int counter = 0; while (curve_it1 != curve_endit) { + std::pair positions = fillet_chamfer_values.get_positions(counter, original_pathv); Geom::NodeType nodetype; - if (counter == 0) { + if (positions.second == 0) { if (path_it->closed()) { - nodetype = get_nodetype(path_it->back_default(), *curve_it1); + Piecewise > u; + u.push_cut(0); + u.push(pwd2_in[fillet_chamfer_values.last_index(counter, original_pathv)], 1); + Geom::Curve const * A = path_from_piecewise(u, 0.1)[0][0].duplicate(); + nodetype = get_nodetype(*A, *curve_it1); } else { nodetype = NODE_NONE; } @@ -433,12 +448,15 @@ void LPEFilletChamfer::doBeforeEffect(SPLPEItem const *lpeItem) { if (SP_IS_SHAPE(lpeItem)) { fillet_chamfer_values.set_helper_size(helper_size); + fillet_chamfer_values.set_use_distance(use_knot_distance); fillet_chamfer_values.set_unit(unit.get_abbreviation()); - SPCurve *c = SP_IS_PATH(lpeItem) ? static_cast(lpeItem)->get_original_curve() : SP_SHAPE(lpeItem)->getCurve(); + SPCurve *c = SP_IS_PATH(lpeItem) ? static_cast(lpeItem) + ->get_original_curve() + : SP_SHAPE(lpeItem)->getCurve(); std::vector filletChamferData = fillet_chamfer_values.data(); if (!filletChamferData.empty() && getKnotsNumber(c) != (int) filletChamferData.size()) { - PathVector const original_pathv = c->get_pathvector(); + PathVector const original_pathv = pathv_to_linear_and_cubic_beziers(c->get_pathvector()); Piecewise > pwd2_in = paths_to_pw(original_pathv); fillet_chamfer_values.recalculate_controlpoints_for_new_pwd2(pwd2_in); } @@ -450,7 +468,7 @@ void LPEFilletChamfer::doBeforeEffect(SPLPEItem const *lpeItem) int LPEFilletChamfer::getKnotsNumber(SPCurve const *c) { int nKnots = c->nodes_in_path(); - PathVector const pv = c->get_pathvector(); + PathVector const pv = pathv_to_linear_and_cubic_beziers(c->get_pathvector()); for (std::vector::const_iterator path_it = pv.begin(); path_it != pv.end(); ++path_it) { if (!(*path_it).closed()) { @@ -464,8 +482,7 @@ void LPEFilletChamfer::adjustForNewPath(std::vector const &path_in) { if (!path_in.empty()) { - fillet_chamfer_values.recalculate_controlpoints_for_new_pwd2(path_in[0] - .toPwSb()); + fillet_chamfer_values.recalculate_controlpoints_for_new_pwd2(pathv_to_linear_and_cubic_beziers(path_in)[0].toPwSb()); } } @@ -473,17 +490,17 @@ std::vector LPEFilletChamfer::doEffect_path(std::vector const &path_in) { std::vector pathvector_out; - Piecewise > pwd2_in = paths_to_pw(path_in); + Piecewise > pwd2_in = paths_to_pw(pathv_to_linear_and_cubic_beziers(path_in)); pwd2_in = remove_short_cuts(pwd2_in, .01); Piecewise > der = derivative(pwd2_in); Piecewise > n = rot90(unitVector(der)); fillet_chamfer_values.set_pwd2(pwd2_in, n); std::vector filletChamferData = fillet_chamfer_values.data(); unsigned int counter = 0; - //from http://launchpadlibrarian.net/12692602/rcp.svg const double K = (4.0 / 3.0) * (sqrt(2.0) - 1.0); - for (PathVector::const_iterator path_it = path_in.begin(); - path_it != path_in.end(); ++path_it) { + std::vector path_in_processed = pathv_to_linear_and_cubic_beziers(path_in); + for (PathVector::const_iterator path_it = path_in_processed.begin(); + path_it != path_in_processed.end(); ++path_it) { if (path_it->empty()) continue; Geom::Path path_out; @@ -504,81 +521,24 @@ LPEFilletChamfer::doEffect_path(std::vector const &path_in) } unsigned int counterCurves = 0; while (curve_it1 != curve_endit) { - Coord it1_length = (*curve_it1).length(tolerance); - double time_it1, time_it2, time_it1_B, intpart; - time_it1 = modf( - fillet_chamfer_values.to_time(counter, filletChamferData[counter][X]), - &intpart); - if (filletChamferData[counter][Y] == 0) { - time_it1 = 0; - } - if (path_it->closed() && curve_it2 == curve_endit) { - time_it2 = modf(fillet_chamfer_values.to_time( - counter - counterCurves, - filletChamferData[counter - counterCurves][X]), - &intpart); - } else if (!path_it->closed() && curve_it2 == curve_endit){ - time_it2 = 0; - } else { - time_it2 = modf(fillet_chamfer_values.to_time( - counter + 1, filletChamferData[counter + 1][X]), - &intpart); - } - double resultLenght = 0; - time_it1_B = 1; - if (path_it->closed() && curve_it2 == curve_endit) { - resultLenght = - it1_length + fillet_chamfer_values.to_len( - counter - counterCurves, - filletChamferData[counter - counterCurves][X]); - } else if (!path_it->closed() && curve_it2 == curve_endit){ - resultLenght = 0; - } else { - resultLenght = - it1_length + fillet_chamfer_values.to_len( - counter + 1, filletChamferData[counter + 1][X]); - } - if (resultLenght > 0 && time_it2 != 0) { - time_it1_B = modf(fillet_chamfer_values.to_time(counter, -resultLenght), - &intpart); - } else { - if (time_it2 == 0) { - time_it1_B = 1; - } else { - time_it1_B = gapHelper; - } - } - if (path_it->closed() && curve_it2 == curve_endit && - filletChamferData[counter - counterCurves][Y] == 0) { - time_it1_B = 1; - time_it2 = 0; - } else if (path_it->size() > counterCurves + 1 && - filletChamferData[counter + 1][Y] == 0) { - time_it1_B = 1; - time_it2 = 0; + Curve *curve_it2Fixed = (*path_it->begin()).duplicate(); + if(!path_it->closed() || curve_it2 != curve_endit){ + curve_it2Fixed = (*curve_it2).duplicate(); } - if (time_it1_B < time_it1) { - time_it1_B = time_it1 + gapHelper; - } - Curve *knotCurve1 = curve_it1->portion(time_it1, time_it1_B); + bool last = curve_it2 == curve_endit; + std::vector times = fillet_chamfer_values.get_times(counter, path_in, last); + Curve *knotCurve1 = curve_it1->portion(times[0], times[1]); if (counterCurves > 0) { knotCurve1->setInitial(path_out.finalPoint()); } else { - path_out.start((*curve_it1).pointAt(time_it1)); - } - Curve *knotCurve2 = (*path_it).front().portion(time_it2, 1); - if (curve_it2 != curve_endit) { - knotCurve2 = (*curve_it2).portion(time_it2, 1); + path_out.start((*curve_it1).pointAt(times[0])); } + Curve *knotCurve2 = curve_it2Fixed->portion(times[2], 1); Point startArcPoint = knotCurve1->finalPoint(); - Point endArcPoint = (*path_it).front().pointAt(time_it2); - if (curve_it2 != curve_endit) { - endArcPoint = (*curve_it2).pointAt(time_it2); - } + Point endArcPoint = curve_it2Fixed->pointAt(times[2]); double k1 = distance(startArcPoint, curve_it1->finalPoint()) * K; double k2 = distance(endArcPoint, curve_it1->finalPoint()) * K; - Geom::CubicBezier const *cubic1 = - dynamic_cast(&*knotCurve1); + Geom::CubicBezier const *cubic1 = dynamic_cast(&*knotCurve1); Ray ray1(startArcPoint, curve_it1->finalPoint()); if (cubic1) { ray1.setPoints((*cubic1)[2], startArcPoint); @@ -591,8 +551,7 @@ LPEFilletChamfer::doEffect_path(std::vector const &path_in) ray2.setPoints(endArcPoint, (*cubic2)[1]); } Point handle2 = endArcPoint - Point::polar(ray2.angle(),k2); - bool ccwToggle = cross(curve_it1->finalPoint() - startArcPoint, - endArcPoint - startArcPoint) < 0; + bool ccwToggle = cross(curve_it1->finalPoint() - startArcPoint, endArcPoint - startArcPoint) < 0; double angle = angle_between(ray1, ray2, ccwToggle); double handleAngle = ray1.angle() - angle; if (ccwToggle) { @@ -604,14 +563,22 @@ LPEFilletChamfer::doEffect_path(std::vector const &path_in) handleAngle = ray2.angle() - angle; } Point inverseHandle2 = endArcPoint - Point::polar(handleAngle,k2); - if (time_it1_B != 1) { - if (time_it1_B != gapHelper && time_it1_B != time_it1 + gapHelper) { + //straigth lines arc based + Line const x_line(Geom::Point(0,0),Geom::Point(1,0)); + Line const angled_line(startArcPoint,endArcPoint); + double angleArc = Geom::angle_between( x_line,angled_line); + double radius = Geom::distance(startArcPoint,middle_point(startArcPoint,endArcPoint))/sin(angle/2.0); + Coord rx = radius; + Coord ry = rx; + + if (times[1] != 1) { + if (times[1] != gapHelper && times[1] != times[0] + gapHelper) { path_out.append(*knotCurve1); } int type = 0; - if(path_it->closed() && curve_it2 == curve_endit){ + if(path_it->closed() && last){ type = abs(filletChamferData[counter - counterCurves][Y]); - } else if (!path_it->closed() && curve_it2 == curve_endit){ + } else if (!path_it->closed() && last){ //0 } else { type = abs(filletChamferData[counter + 1][Y]); @@ -624,15 +591,23 @@ LPEFilletChamfer::doEffect_path(std::vector const &path_in) } path_out.appendNew(endArcPoint); } else if (type == 2) { - path_out.appendNew(inverseHandle1, inverseHandle2, - endArcPoint); + if((is_straight_curve(*curve_it1) && is_straight_curve(*curve_it2Fixed)) || force_arcs){ + ccwToggle = ccwToggle?0:1; + path_out.appendNew(rx, ry, angleArc, 0, ccwToggle, endArcPoint); + }else{ + path_out.appendNew(inverseHandle1, inverseHandle2, endArcPoint); + } } else { - path_out.appendNew(handle1, handle2, endArcPoint); + if((is_straight_curve(*curve_it1) && is_straight_curve(*curve_it2Fixed)) || force_arcs){ + path_out.appendNew(rx, ry, angleArc, 0, ccwToggle, endArcPoint); + } else { + path_out.appendNew(handle1, handle2, endArcPoint); + } } } else { path_out.append(*knotCurve1); } - if (path_it->closed() && curve_it2 == curve_endit) { + if (path_it->closed() && last) { path_out.close(); } ++curve_it1; diff --git a/src/live_effects/lpe-fillet-chamfer.h b/src/live_effects/lpe-fillet-chamfer.h index 420425650..49b407b71 100644 --- a/src/live_effects/lpe-fillet-chamfer.h +++ b/src/live_effects/lpe-fillet-chamfer.h @@ -15,7 +15,13 @@ * Released under GNU GPL, read the file 'COPYING' for more information */ -#include +#if HAVE_CONFIG_H +# include "config.h" +#endif + +#if defined(GLIBMM_DISABLE_DEPRECATED) && defined(HAVE_GLIBMM_THREADS_H) +# include +#endif #include "live_effects/parameter/enum.h" #include "live_effects/parameter/bool.h" @@ -61,6 +67,8 @@ private: BoolParam ignore_radius_0; BoolParam only_selected; BoolParam flexible; + BoolParam force_arcs; + BoolParam use_knot_distance; UnitParam unit; ScalarParam radius; ScalarParam helper_size; diff --git a/src/live_effects/lpe-interpolate_points.cpp b/src/live_effects/lpe-interpolate_points.cpp index 865b46ca7..4ac139752 100644 --- a/src/live_effects/lpe-interpolate_points.cpp +++ b/src/live_effects/lpe-interpolate_points.cpp @@ -40,7 +40,7 @@ LPEInterpolatePoints::LPEInterpolatePoints(LivePathEffectObject *lpeobject) { show_orig_path = false; - registerParameter( dynamic_cast(&interpolator_type) ); + registerParameter( &interpolator_type ); } LPEInterpolatePoints::~LPEInterpolatePoints() diff --git a/src/live_effects/lpe-jointype.cpp b/src/live_effects/lpe-jointype.cpp new file mode 100644 index 000000000..bf2526986 --- /dev/null +++ b/src/live_effects/lpe-jointype.cpp @@ -0,0 +1,190 @@ +/* Authors: +* +* Liam P White +* +* Copyright (C) 2014 Authors +* +* Released under GNU GPL v2, read the file 'COPYING' for more information +*/ + +#include "live_effects/parameter/enum.h" +#include "live_effects/pathoutlineprovider.h" + +#include "sp-shape.h" +#include "style.h" +#include "xml/repr.h" +#include "sp-paint-server.h" +#include "svg/svg-color.h" +#include "desktop-style.h" +#include "svg/css-ostringstream.h" +#include "display/curve.h" + +#include <2geom/path.h> +#include <2geom/svg-elliptical-arc.h> + +#include "lpe-jointype.h" + +namespace Inkscape { +namespace LivePathEffect { + +static const Util::EnumData JoinTypeData[] = { + {LINEJOIN_STRAIGHT, N_("Beveled"), "bevel"}, + {LINEJOIN_ROUND, N_("Rounded"), "round"}, + {LINEJOIN_POINTY, N_("Miter"), "miter"}, + {LINEJOIN_REFLECTED, N_("Reflected"), "extrapolated"}, + {LINEJOIN_EXTRAPOLATED, N_("Extrapolated arc"), "extrp_arc"} +}; + +static const Util::EnumData CapTypeData[] = { + {BUTT_STRAIGHT, N_("Butt"), "butt"}, + {BUTT_ROUND, N_("Rounded"), "round"}, + {BUTT_SQUARE, N_("Square"), "square"}, + {BUTT_POINTY, N_("Peak"), "peak"}, + {BUTT_LEANED, N_("Leaned"), "leaned"} +}; + +static const Util::EnumDataConverter CapTypeConverter(CapTypeData, sizeof(CapTypeData)/sizeof(*CapTypeData)); +static const Util::EnumDataConverter JoinTypeConverter(JoinTypeData, sizeof(JoinTypeData)/sizeof(*JoinTypeData)); + +LPEJoinType::LPEJoinType(LivePathEffectObject *lpeobject) : + Effect(lpeobject), + line_width(_("Line width"), _("Thickness of the stroke"), "line_width", &wr, this, 1.), + linecap_type(_("Line cap"), _("The end shape of the stroke"), "linecap_type", CapTypeConverter, &wr, this, butt_straight), + linejoin_type(_("Join:"), _("Determines the shape of the path's corners"), "linejoin_type", JoinTypeConverter, &wr, this, LINEJOIN_EXTRAPOLATED), + start_lean(_("Start path lean"), _("Start path lean"), "start_lean", &wr, this, 0.), + end_lean(_("End path lean"), _("End path lean"), "end_lean", &wr, this, 0.), + miter_limit(_("Miter limit:"), _("Maximum length of the miter join (in units of stroke width)"), "miter_limit", &wr, this, 100.), + attempt_force_join(_("Force miter"), _("Overrides the miter limit and forces a join."), "attempt_force_join", &wr, this, true) +{ + show_orig_path = true; + registerParameter(&linecap_type); + registerParameter(&line_width); + registerParameter(&linejoin_type); + registerParameter(&start_lean); + registerParameter(&end_lean); + registerParameter(&miter_limit); + registerParameter(&attempt_force_join); + was_initialized = false; + start_lean.param_set_range(-1,1); + start_lean.param_set_increments(0.1, 0.1); + start_lean.param_set_digits(4); + end_lean.param_set_range(-1,1); + end_lean.param_set_increments(0.1, 0.1); + end_lean.param_set_digits(4); +} + +LPEJoinType::~LPEJoinType() +{ +} + +//from LPEPowerStroke -- sets fill if stroke color because we will +//be converting to a fill to make the new join. + +void LPEJoinType::doOnApply(SPLPEItem const* lpeitem) +{ + if (SP_IS_SHAPE(lpeitem)) { + SPLPEItem* item = const_cast(lpeitem); + double width = (lpeitem && lpeitem->style) ? lpeitem->style->stroke_width.computed : 1.; + + SPCSSAttr *css = sp_repr_css_attr_new (); + if (true) { + if (lpeitem->style->stroke.isPaintserver()) { + SPPaintServer * server = lpeitem->style->getStrokePaintServer(); + if (server) { + Glib::ustring str; + str += "url(#"; + str += server->getId(); + str += ")"; + sp_repr_css_set_property (css, "fill", str.c_str()); + } + } else if (lpeitem->style->stroke.isColor()) { + gchar c[64]; + sp_svg_write_color (c, sizeof(c), lpeitem->style->stroke.value.color.toRGBA32(SP_SCALE24_TO_FLOAT(lpeitem->style->stroke_opacity.value))); + sp_repr_css_set_property (css, "fill", c); + } else { + sp_repr_css_set_property (css, "fill", "none"); + } + } else { + sp_repr_css_unset_property (css, "fill"); + } + + sp_repr_css_set_property(css, "stroke", "none"); + + sp_desktop_apply_css_recursive(item, css, true); + sp_repr_css_attr_unref (css); + if (!was_initialized) + { + was_initialized = true; + line_width.param_set_value(width); + } + } else { + g_warning("LPE Join Type can only be applied to paths (not groups)."); + } +} + +//from LPEPowerStroke -- sets stroke color from existing fill color + +void LPEJoinType::doOnRemove(SPLPEItem const* lpeitem) +{ + + if (SP_IS_SHAPE(lpeitem)) { + SPLPEItem *item = const_cast(lpeitem); + + SPCSSAttr *css = sp_repr_css_attr_new (); + if (true) { + if (lpeitem->style->fill.isPaintserver()) { + SPPaintServer * server = lpeitem->style->getFillPaintServer(); + if (server) { + Glib::ustring str; + str += "url(#"; + str += server->getId(); + str += ")"; + sp_repr_css_set_property (css, "stroke", str.c_str()); + } + } else if (lpeitem->style->fill.isColor()) { + gchar c[64]; + sp_svg_write_color (c, sizeof(c), lpeitem->style->stroke.value.color.toRGBA32(SP_SCALE24_TO_FLOAT(lpeitem->style->stroke_opacity.value))); + sp_repr_css_set_property (css, "stroke", c); + } else { + sp_repr_css_set_property (css, "stroke", "none"); + } + } else { + sp_repr_css_unset_property (css, "stroke"); + } + + Inkscape::CSSOStringStream os; + os << fabs(line_width); + sp_repr_css_set_property (css, "stroke-width", os.str().c_str()); + + sp_repr_css_set_property(css, "fill", "none"); + + sp_desktop_apply_css_recursive(item, css, true); + sp_repr_css_attr_unref (css); + item->updateRepr(); + } +} + +// NOTE: I originally had all the outliner functions defined in here, but they were actually useful +// enough for other LPEs so I moved them all into pathoutlineprovider.cpp. The code here is just a +// wrapper around it. +std::vector LPEJoinType::doEffect_path(std::vector const & path_in) +{ + return Outline::PathVectorOutline(path_in, line_width, static_cast(linecap_type.get_value()), + static_cast(linejoin_type.get_value()), + (attempt_force_join ? std::numeric_limits::max() : miter_limit), + start_lean/2 ,end_lean/2); +} + +} // 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:fileencoding=utf-8 : diff --git a/src/live_effects/lpe-jointype.h b/src/live_effects/lpe-jointype.h new file mode 100644 index 000000000..73705666d --- /dev/null +++ b/src/live_effects/lpe-jointype.h @@ -0,0 +1,57 @@ +/* Authors: + * Liam P White + * + * Copyright (C) 2014 Authors + * + * Released under GNU GPL v2, read the file COPYING for more information + */ + +#ifndef INKSCAPE_LPE_JOINTYPE_H +#define INKSCAPE_LPE_JOINTYPE_H + +#include "live_effects/effect.h" +#include "live_effects/parameter/parameter.h" +#include "live_effects/parameter/point.h" +#include "live_effects/parameter/enum.h" + +namespace Inkscape { +namespace LivePathEffect { + +class LPEJoinType : public Effect { +public: + LPEJoinType(LivePathEffectObject *lpeobject); + virtual ~LPEJoinType(); + + virtual void doOnApply(SPLPEItem const* lpeitem); + virtual void doOnRemove(SPLPEItem const* lpeitem); + virtual std::vector doEffect_path (std::vector const & path_in); + +private: + LPEJoinType(const LPEJoinType&); + LPEJoinType& operator=(const LPEJoinType&); + + ScalarParam line_width; + EnumParam linecap_type; + EnumParam linejoin_type; + ScalarParam start_lean; + ScalarParam end_lean; + ScalarParam miter_limit; + BoolParam attempt_force_join; + bool was_initialized; +}; + +} //namespace LivePathEffect +} //namespace Inkscape + +#endif + +/* + 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:fileencoding=utf-8 : diff --git a/src/live_effects/lpe-knot.cpp b/src/live_effects/lpe-knot.cpp index cac3a9347..3876aa24b 100644 --- a/src/live_effects/lpe-knot.cpp +++ b/src/live_effects/lpe-knot.cpp @@ -22,6 +22,7 @@ #include "knotholder.h" #include +#include #include <2geom/sbasis-to-bezier.h> #include <2geom/sbasis.h> @@ -536,6 +537,10 @@ LPEKnot::doBeforeEffect (SPLPEItem const* lpeitem) { using namespace Geom; original_bbox(lpeitem); + + if (SP_IS_PATH(lpeitem)) { + supplied_path = SP_PATH(lpeitem)->getCurve()->get_pathvector(); + } gpaths.clear(); gstroke_widths.clear(); diff --git a/src/live_effects/lpe-knot.h b/src/live_effects/lpe-knot.h index f926bf085..080f32de2 100644 --- a/src/live_effects/lpe-knot.h +++ b/src/live_effects/lpe-knot.h @@ -64,7 +64,8 @@ public: void addKnotHolderEntities(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item); protected: - virtual void addCanvasIndicators(SPLPEItem const *lpeitem, std::vector &hp_vec); + virtual void addCanvasIndicators(SPLPEItem const *lpeitem, std::vector &hp_vec); + std::vector supplied_path; //for knotholder business private: void updateSwitcher(); diff --git a/src/live_effects/lpe-powerstroke-interpolators.h b/src/live_effects/lpe-powerstroke-interpolators.h index 986bd3544..3cde0b4b3 100644 --- a/src/live_effects/lpe-powerstroke-interpolators.h +++ b/src/live_effects/lpe-powerstroke-interpolators.h @@ -28,6 +28,7 @@ enum InterpolatorType { INTERP_CUBICBEZIER, INTERP_CUBICBEZIER_JOHAN, INTERP_SPIRO, + INTERP_CUBICBEZIER_SMOOTH, INTERP_CENTRIPETAL_CATMULLROM }; @@ -134,6 +135,43 @@ private: CubicBezierJohan& operator=(const CubicBezierJohan&); }; +/// @todo invent name for this class +class CubicBezierSmooth : public Interpolator { +public: + CubicBezierSmooth(double beta = 0.2) { + _beta = beta; + }; + virtual ~CubicBezierSmooth() {}; + + virtual Path interpolateToPath(std::vector const &points) const { + Path fit; + fit.start(points.at(0)); + unsigned int num_points = points.size(); + for (unsigned int i = 1; i < num_points; ++i) { + Point p0 = points.at(i-1); + Point p1 = points.at(i); + Point dx = Point(p1[X] - p0[X], 0); + if (i == 1) { + fit.appendNew(p0, p1-0.75*dx, p1); + } else if (i == points.size() - 1) { + fit.appendNew(p0+0.75*dx, p1, p1); + } else { + fit.appendNew(p0+_beta*dx, p1-_beta*dx, p1); + } + } + return fit; + }; + + void setBeta(double beta) { + _beta = beta; + } + + double _beta; + +private: + CubicBezierSmooth(const CubicBezierSmooth&); + CubicBezierSmooth& operator=(const CubicBezierSmooth&); +}; class SpiroInterpolator : public Interpolator { public: @@ -261,6 +299,8 @@ Interpolator::create(InterpolatorType type) { return new Geom::Interpolate::CubicBezierJohan(); case INTERP_SPIRO: return new Geom::Interpolate::SpiroInterpolator(); + case INTERP_CUBICBEZIER_SMOOTH: + return new Geom::Interpolate::CubicBezierSmooth(); case INTERP_CENTRIPETAL_CATMULLROM: return new Geom::Interpolate::CentripetalCatmullRomInterpolator(); default: diff --git a/src/live_effects/lpe-powerstroke.cpp b/src/live_effects/lpe-powerstroke.cpp index 5bfe88ed1..b1951d978 100644 --- a/src/live_effects/lpe-powerstroke.cpp +++ b/src/live_effects/lpe-powerstroke.cpp @@ -15,6 +15,11 @@ #include "sp-shape.h" #include "style.h" +#include "xml/repr.h" +#include "sp-paint-server.h" +#include "svg/svg-color.h" +#include "desktop-style.h" +#include "svg/css-ostringstream.h" #include "display/curve.h" #include <2geom/path.h> @@ -185,6 +190,7 @@ namespace Inkscape { namespace LivePathEffect { static const Util::EnumData InterpolatorTypeData[] = { + {Geom::Interpolate::INTERP_CUBICBEZIER_SMOOTH, N_("CubicBezierSmooth"), "CubicBezierSmooth"}, {Geom::Interpolate::INTERP_LINEAR , N_("Linear"), "Linear"}, {Geom::Interpolate::INTERP_CUBICBEZIER , N_("CubicBezierFit"), "CubicBezierFit"}, {Geom::Interpolate::INTERP_CUBICBEZIER_JOHAN , N_("CubicBezierJohan"), "CubicBezierJohan"}, @@ -223,9 +229,7 @@ static const Util::EnumData LineJoinTypeData[] = { {LINEJOIN_EXTRP_MITER, N_("Extrapolated"), "extrapolated"}, {LINEJOIN_MITER, N_("Miter"), "miter"}, {LINEJOIN_SPIRO, N_("Spiro"), "spiro"}, -#ifdef LPE_ENABLE_TEST_EFFECTS {LINEJOIN_EXTRP_MITER_ARC, N_("Extrapolated arc"), "extrp_arc"}, -#endif }; static const Util::EnumDataConverter LineJoinTypeConverter(LineJoinTypeData, sizeof(LineJoinTypeData)/sizeof(*LineJoinTypeData)); @@ -233,12 +237,12 @@ LPEPowerStroke::LPEPowerStroke(LivePathEffectObject *lpeobject) : Effect(lpeobject), offset_points(_("Offset points"), _("Offset points"), "offset_points", &wr, this), sort_points(_("Sort points"), _("Sort offset points according to their time value along the curve"), "sort_points", &wr, this, true), - interpolator_type(_("Interpolator type:"), _("Determines which kind of interpolator will be used to interpolate between stroke width along the path"), "interpolator_type", InterpolatorTypeConverter, &wr, this, Geom::Interpolate::INTERP_CUBICBEZIER_JOHAN), + interpolator_type(_("Interpolator type:"), _("Determines which kind of interpolator will be used to interpolate between stroke width along the path"), "interpolator_type", InterpolatorTypeConverter, &wr, this, Geom::Interpolate::INTERP_CUBICBEZIER), interpolator_beta(_("Smoothness:"), _("Sets the smoothness for the CubicBezierJohan interpolator; 0 = linear interpolation, 1 = smooth"), "interpolator_beta", &wr, this, 0.2), - start_linecap_type(_("Start cap:"), _("Determines the shape of the path's start"), "start_linecap_type", LineCapTypeConverter, &wr, this, LINECAP_ROUND), - linejoin_type(_("Join:"), _("Determines the shape of the path's corners"), "linejoin_type", LineJoinTypeConverter, &wr, this, LINEJOIN_ROUND), + start_linecap_type(_("Start cap:"), _("Determines the shape of the path's start"), "start_linecap_type", LineCapTypeConverter, &wr, this, LINECAP_BUTT), + linejoin_type(_("Join:"), _("Determines the shape of the path's corners"), "linejoin_type", LineJoinTypeConverter, &wr, this, LINEJOIN_EXTRP_MITER_ARC), miter_limit(_("Miter limit:"), _("Maximum length of the miter (in units of stroke width)"), "miter_limit", &wr, this, 4.), - end_linecap_type(_("End cap:"), _("Determines the shape of the path's end"), "end_linecap_type", LineCapTypeConverter, &wr, this, LINECAP_ROUND) + end_linecap_type(_("End cap:"), _("Determines the shape of the path's end"), "end_linecap_type", LineCapTypeConverter, &wr, this, LINECAP_BUTT) { show_orig_path = true; @@ -267,20 +271,52 @@ void LPEPowerStroke::doOnApply(SPLPEItem const* lpeitem) { if (SP_IS_SHAPE(lpeitem)) { + SPLPEItem* item = const_cast(lpeitem); std::vector points; Geom::PathVector const &pathv = SP_SHAPE(lpeitem)->_curve->get_pathvector(); - double width = (lpeitem && lpeitem->style) ? lpeitem->style->stroke_width.computed : 1.; + double width = (lpeitem && lpeitem->style) ? lpeitem->style->stroke_width.computed / 2 : 1.; + + SPCSSAttr *css = sp_repr_css_attr_new (); + if (true) { + if (lpeitem->style->stroke.isPaintserver()) { + SPPaintServer * server = lpeitem->style->getStrokePaintServer(); + if (server) { + Glib::ustring str; + str += "url(#"; + str += server->getId(); + str += ")"; + sp_repr_css_set_property (css, "fill", str.c_str()); + } + } else if (lpeitem->style->stroke.isColor()) { + gchar c[64]; + sp_svg_write_color (c, sizeof(c), lpeitem->style->stroke.value.color.toRGBA32(SP_SCALE24_TO_FLOAT(lpeitem->style->stroke_opacity.value))); + sp_repr_css_set_property (css, "fill", c); + } else { + sp_repr_css_set_property (css, "fill", "none"); + } + } else { + sp_repr_css_unset_property (css, "fill"); + } + + sp_repr_css_set_property(css, "stroke", "none"); + + sp_desktop_apply_css_recursive(item, css, true); + sp_repr_css_attr_unref (css); + + item->updateRepr(); if (pathv.empty()) { - points.push_back( Geom::Point(0.,width) ); + points.push_back( Geom::Point(0.2,width) ); points.push_back( Geom::Point(0.5,width) ); - points.push_back( Geom::Point(1.,width) ); + points.push_back( Geom::Point(0.8,width) ); } else { Geom::Path const &path = pathv.front(); Geom::Path::size_type const size = path.size_default(); - points.push_back( Geom::Point(0.,width) ); + if (!path.closed()) { + points.push_back( Geom::Point(0.2,width) ); + } points.push_back( Geom::Point(0.5*size,width) ); if (!path.closed()) { - points.push_back( Geom::Point(size,width) ); + points.push_back( Geom::Point(size - 0.2,width) ); } } offset_points.param_set_and_write_new_value(points); @@ -289,6 +325,45 @@ LPEPowerStroke::doOnApply(SPLPEItem const* lpeitem) } } +void LPEPowerStroke::doOnRemove(SPLPEItem const* lpeitem) +{ + if (SP_IS_SHAPE(lpeitem)) { + SPLPEItem *item = const_cast(lpeitem); + SPCSSAttr *css = sp_repr_css_attr_new (); + if (true) { + if (lpeitem->style->fill.isPaintserver()) { + SPPaintServer * server = lpeitem->style->getFillPaintServer(); + if (server) { + Glib::ustring str; + str += "url(#"; + str += server->getId(); + str += ")"; + sp_repr_css_set_property (css, "stroke", str.c_str()); + } + } else if (lpeitem->style->fill.isColor()) { + gchar c[64]; + sp_svg_write_color (c, sizeof(c), lpeitem->style->stroke.value.color.toRGBA32(SP_SCALE24_TO_FLOAT(lpeitem->style->stroke_opacity.value))); + sp_repr_css_set_property (css, "stroke", c); + } else { + sp_repr_css_set_property (css, "stroke", "none"); + } + } else { + sp_repr_css_unset_property (css, "stroke"); + } + + Inkscape::CSSOStringStream os; + os << offset_points.median_width() * 2; + sp_repr_css_set_property (css, "stroke-width", os.str().c_str()); + + sp_repr_css_set_property(css, "fill", "none"); + + sp_desktop_apply_css_recursive(item, css, true); + sp_repr_css_attr_unref (css); + + item->updateRepr(); + } +} + void LPEPowerStroke::adjustForNewPath(std::vector const & path_in) { @@ -588,6 +663,9 @@ LPEPowerStroke::doEffect_path (std::vector const & path_in) if (Geom::Interpolate::CubicBezierJohan *johan = dynamic_cast(interpolator)) { johan->setBeta(interpolator_beta); } + if (Geom::Interpolate::CubicBezierSmooth *smooth = dynamic_cast(interpolator)) { + smooth->setBeta(interpolator_beta); + } Geom::Path strokepath = interpolator->interpolateToPath(ts); delete interpolator; diff --git a/src/live_effects/lpe-powerstroke.h b/src/live_effects/lpe-powerstroke.h index 7bc736820..a773434aa 100644 --- a/src/live_effects/lpe-powerstroke.h +++ b/src/live_effects/lpe-powerstroke.h @@ -25,9 +25,11 @@ public: LPEPowerStroke(LivePathEffectObject *lpeobject); virtual ~LPEPowerStroke(); + virtual std::vector doEffect_path (std::vector const & path_in); virtual void doOnApply(SPLPEItem const* lpeitem); + virtual void doOnRemove(SPLPEItem const* lpeitem); // methods called by path-manipulator upon edits void adjustForNewPath(std::vector const & path_in); diff --git a/src/live_effects/lpe-roughen.cpp b/src/live_effects/lpe-roughen.cpp index 5f6acd6f4..b9d575e06 100644 --- a/src/live_effects/lpe-roughen.cpp +++ b/src/live_effects/lpe-roughen.cpp @@ -1,10 +1,21 @@ -/* +/** + * @file + * Roughen LPE implementation. Creates roughen paths. + */ +/* Authors: + * Jabier Arraiza Cenoz + * + * Thanks to all people involved specialy to Josh Andler for the idea and to the + * original extensions authors. + * + * Copyright (C) 2014 Authors + * * Released under GNU GPL, read the file 'COPYING' for more information */ -#include "live_effects/lpe-roughen.h" -#include +#include +#include "live_effects/lpe-roughen.h" #include "display/curve.h" #include "live_effects/parameter/parameter.h" #include "helper/geom.h" @@ -15,375 +26,299 @@ namespace Inkscape { namespace LivePathEffect { static const Util::EnumData DivisionMethodData[DM_END] = { - { DM_SEGMENTS, N_("By number of segments"), "segments" }, - { DM_SIZE, N_("By max. segment size"), "size" } + { DM_SEGMENTS, N_("By number of segments"), "segments" }, + { DM_SIZE, N_("By max. segment size"), "size" } }; static const Util::EnumDataConverter - DMConverter(DivisionMethodData, DM_END); +DMConverter(DivisionMethodData, DM_END); LPERoughen::LPERoughen(LivePathEffectObject *lpeobject) : Effect(lpeobject), // initialise your parameters here: - unit(_("Unit:"), _("Unit"), "unit", &wr, this), - method(_("Method:"), _("Division method"), "method", DMConverter, &wr, + unit(_("Unit"), _("Unit"), "unit", &wr, this), + method(_("Method"), _("Division method"), "method", DMConverter, &wr, this, DM_SEGMENTS), - maxSegmentSize(_("Max. segment size:"), _("Max. segment size"), + maxSegmentSize(_("Max. segment size"), _("Max. segment size"), "maxSegmentSize", &wr, this, 10.), - segments(_("Number of segments:"), _("Number of segments"), "segments", + segments(_("Number of segments"), _("Number of segments"), "segments", &wr, this, 2), - displaceX(_("Max. displacement in X:"), _("Max. displacement in X"), + displaceX(_("Max. displacement in X"), _("Max. displacement in X"), "displaceX", &wr, this, 10.), - displaceY(_("Max. displacement in Y:"), _("Max. displacement in Y"), + displaceY(_("Max. displacement in Y"), _("Max. displacement in Y"), "displaceY", &wr, this, 10.), shiftNodes(_("Shift nodes"), _("Shift nodes"), "shiftNodes", &wr, this, true), shiftNodeHandles(_("Shift node handles"), _("Shift node handles"), - "shiftNodeHandles", &wr, this, true) { - registerParameter(dynamic_cast(&unit)); - registerParameter(dynamic_cast(&method)); - registerParameter(dynamic_cast(&maxSegmentSize)); - registerParameter(dynamic_cast(&segments)); - registerParameter(dynamic_cast(&displaceX)); - registerParameter(dynamic_cast(&displaceY)); - registerParameter(dynamic_cast(&shiftNodes)); - registerParameter(dynamic_cast(&shiftNodeHandles)); - displaceX.param_set_range(0., Geom::infinity()); - displaceY.param_set_range(0., Geom::infinity()); - maxSegmentSize.param_set_range(0., Geom::infinity()); - maxSegmentSize.param_set_increments(1, 1); - maxSegmentSize.param_set_digits(1); - segments.param_set_range(1, Geom::infinity()); - segments.param_set_increments(1, 1); - segments.param_set_digits(0); + "shiftNodeHandles", &wr, this, true) +{ + registerParameter(&unit); + registerParameter(&method); + registerParameter(&maxSegmentSize); + registerParameter(&segments); + registerParameter(&displaceX); + registerParameter(&displaceY); + registerParameter(&shiftNodes); + registerParameter(&shiftNodeHandles); + displaceX.param_set_range(0., Geom::infinity()); + displaceY.param_set_range(0., Geom::infinity()); + maxSegmentSize.param_set_range(0., Geom::infinity()); + maxSegmentSize.param_set_increments(1, 1); + maxSegmentSize.param_set_digits(1); + segments.param_set_range(1, Geom::infinity()); + segments.param_set_increments(1, 1); + segments.param_set_digits(0); } LPERoughen::~LPERoughen() {} -void LPERoughen::doBeforeEffect(SPLPEItem const */*lpeitem*/) { - displaceX.resetRandomizer(); - displaceY.resetRandomizer(); - srand(1); +void LPERoughen::doBeforeEffect(SPLPEItem const */*lpeitem*/) +{ + displaceX.resetRandomizer(); + displaceY.resetRandomizer(); + srand(1); } -Gtk::Widget *LPERoughen::newWidget() { - // use manage here, because after deletion of Effect object, others might - // still be pointing to this widget. - 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::iterator it = param_vector.begin(); - while (it != param_vector.end()) { - if ((*it)->widget_is_visible) { - Parameter *param = *it; - Gtk::Widget *widg = dynamic_cast(param->param_newWidget()); - if (param->param_key == "unit") { - Gtk::Label *unitLabel = Gtk::manage(new Gtk::Label( - Glib::ustring(_("Roughen unit")), Gtk::ALIGN_START)); - unitLabel->set_use_markup(true); - vbox->pack_start(*unitLabel, false, false, 2); -#if WITH_GTKMM_3_0 - vbox->pack_start(*Gtk::manage(new Gtk::Separator()), -#else - vbox->pack_start(*Gtk::manage(new Gtk::HSeparator()), -#endif - Gtk::PACK_EXPAND_WIDGET); - } - if (param->param_key == "method") { - Gtk::Label *methodLabel = Gtk::manage(new Gtk::Label( - Glib::ustring(_("Add nodes Subdivide each segment")), - Gtk::ALIGN_START)); - methodLabel->set_use_markup(true); - vbox->pack_start(*methodLabel, false, false, 2); -#if WITH_GTKMM_3_0 - vbox->pack_start(*Gtk::manage(new Gtk::Separator()), -#else - vbox->pack_start(*Gtk::manage(new Gtk::HSeparator()), -#endif - Gtk::PACK_EXPAND_WIDGET); - } - if (param->param_key == "displaceX") { - Gtk::Label *displaceXLabel = Gtk::manage(new Gtk::Label( - Glib::ustring(_("Jitter nodes Move nodes/handles")), - Gtk::ALIGN_START)); - displaceXLabel->set_use_markup(true); - vbox->pack_start(*displaceXLabel, false, false, 2); -#if WITH_GTKMM_3_0 - vbox->pack_start(*Gtk::manage(new Gtk::Separator()), -#else - vbox->pack_start(*Gtk::manage(new Gtk::HSeparator()), -#endif - Gtk::PACK_EXPAND_WIDGET); - } - 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); +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::iterator it = param_vector.begin(); + while (it != param_vector.end()) { + if ((*it)->widget_is_visible) { + Parameter *param = *it; + Gtk::Widget *widg = dynamic_cast(param->param_newWidget()); + if (param->param_key == "unit") { + Gtk::Label *unitLabel = Gtk::manage(new Gtk::Label( + Glib::ustring(_("Roughen unit")), Gtk::ALIGN_START)); + unitLabel->set_use_markup(true); + vbox->pack_start(*unitLabel, false, false, 2); + vbox->pack_start(*Gtk::manage(new Gtk::HSeparator()), + Gtk::PACK_EXPAND_WIDGET); + } + if (param->param_key == "method") { + Gtk::Label *methodLabel = Gtk::manage(new Gtk::Label( + Glib::ustring(_("Add nodes Subdivide each segment")), + Gtk::ALIGN_START)); + methodLabel->set_use_markup(true); + vbox->pack_start(*methodLabel, false, false, 2); + vbox->pack_start(*Gtk::manage(new Gtk::HSeparator()), + Gtk::PACK_EXPAND_WIDGET); + } + if (param->param_key == "displaceX") { + Gtk::Label *displaceXLabel = Gtk::manage(new Gtk::Label( + Glib::ustring(_("Jitter nodes Move nodes/handles")), + Gtk::ALIGN_START)); + displaceXLabel->set_use_markup(true); + vbox->pack_start(*displaceXLabel, false, false, 2); + vbox->pack_start(*Gtk::manage(new Gtk::HSeparator()), + Gtk::PACK_EXPAND_WIDGET); + } + 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); + } + } } - } - } - ++it; - } - return dynamic_cast(vbox); + ++it; + } + return dynamic_cast(vbox); } -double LPERoughen::sign(double randNumber) { - if (rand() % 100 < 49) { - randNumber *= -1.; - } - return randNumber; +double LPERoughen::sign(double randNumber) +{ + if (rand() % 100 < 49) { + randNumber *= -1.; + } + return randNumber; } -Geom::Point LPERoughen::randomize() { - double displaceXParsed = Inkscape::Util::Quantity::convert( - displaceX, unit.get_abbreviation(), "px"); - double displaceYParsed = Inkscape::Util::Quantity::convert( - displaceY, unit.get_abbreviation(), "px"); - //maybe is better divide this point by 2... - Geom::Point output = - Geom::Point(sign(displaceXParsed), sign(displaceYParsed)); - return output; -} +Geom::Point LPERoughen::randomize() +{ + double displaceXParsed = Inkscape::Util::Quantity::convert( + displaceX, unit.get_abbreviation(), "px"); + double displaceYParsed = Inkscape::Util::Quantity::convert( + displaceY, unit.get_abbreviation(), "px"); -void LPERoughen::doEffect(SPCurve *curve) { - Geom::PathVector const original_pathv = - pathv_to_linear_and_cubic_beziers(curve->get_pathvector()); - curve->reset(); + Geom::Point output = Geom::Point(sign(displaceXParsed), sign(displaceYParsed)); + return output; +} - //Recorremos todos los paths a los que queremos aplicar el efecto, hasta el - //penúltimo - for (Geom::PathVector::const_iterator path_it = original_pathv.begin(); - path_it != original_pathv.end(); ++path_it) { - //Si está vacío... - if (path_it->empty()) - continue; - //Itreadores +void LPERoughen::doEffect(SPCurve *curve) +{ + Geom::PathVector const original_pathv = + pathv_to_linear_and_cubic_beziers(curve->get_pathvector()); + curve->reset(); + for (Geom::PathVector::const_iterator path_it = original_pathv.begin(); + path_it != original_pathv.end(); ++path_it) { + if (path_it->empty()) + continue; - Geom::Path::const_iterator curve_it1 = path_it->begin(); // incoming curve - Geom::Path::const_iterator curve_it2 = - ++(path_it->begin()); // outgoing curve - Geom::Path::const_iterator curve_endit = - path_it->end_default(); // this determines when the loop has to stop - //Creamos las lineas rectas que unen todos los puntos del trazado y donde se - //calcularán - //los puntos clave para los manejadores. - //Esto hace que la curva BSpline no pierda su condición aunque se trasladen - //dichos manejadores - SPCurve *nCurve = new SPCurve(); - if (path_it->closed()) { - // if the path is closed, maybe we have to stop a bit earlier because the - // closing line segment has zerolength. - const Geom::Curve &closingline = - path_it->back_closed(); // 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! - curve_endit = path_it->end_open(); - } - } - //Si la curva está cerrada calculamos el punto donde - //deveria estar el nodo BSpline de cierre/inicio de la curva - //en posible caso de que se cierre con una linea recta creando un nodo - //BSPline - Geom::Point initialMove(0, 0); - if (shiftNodes) { - initialMove = randomize(); - } - Geom::Point initialPoint = curve_it1->initialPoint() + initialMove; - nCurve->moveto(initialPoint); - Geom::Point A0(0, 0); - Geom::Point A1(0, 0); - Geom::Point A2(0, 0); - Geom::Point A3(0, 0); - while (curve_it2 != curve_endit) { - //aumentamos los valores para el siguiente paso en el bucle - //Recorremos todos los segmentos menos el último - Geom::CubicBezier const *cubic = NULL; - A0 = curve_it1->initialPoint(); - A1 = curve_it1->initialPoint(); - A2 = curve_it1->finalPoint(); - A3 = curve_it1->finalPoint(); - cubic = dynamic_cast(&*curve_it1); - if (cubic) { - A1 = (*cubic)[1]; - if (shiftNodes) { - A1 = (*cubic)[1] + initialMove; + 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(); + SPCurve *nCurve = new SPCurve(); + if (path_it->closed()) { + const Geom::Curve &closingline = + path_it->back_closed(); + if (are_near(closingline.initialPoint(), closingline.finalPoint())) { + curve_endit = path_it->end_open(); + } } - A2 = (*cubic)[2]; - nCurve->curveto(A1, A2, A3); - } else { - nCurve->lineto(A3); - } - double length = Inkscape::Util::Quantity::convert( - curve_it1->length(0.001), "px", unit.get_abbreviation()); - unsigned int splits = 0; - if (method == DM_SEGMENTS) { - splits = segments; - } else { - splits = ceil(length / maxSegmentSize); - } - for (unsigned int t = splits; t >= 1; t--) { - if (t == 1 && splits != 1) { - continue; + Geom::Point initialMove(0, 0); + if (shiftNodes) { + initialMove = randomize(); } - const SPCurve *tmp; - if (splits == 1) { - tmp = jitter(nCurve->last_segment()); - } else { - tmp = addNodesAndJitter(nCurve->last_segment(), 1. / t); + Geom::Point initialPoint = curve_it1->initialPoint() + initialMove; + nCurve->moveto(initialPoint); + Geom::Point A0(0, 0); + Geom::Point A1(0, 0); + Geom::Point A2(0, 0); + Geom::Point A3(0, 0); + while (curve_it1 != curve_endit) { + Geom::CubicBezier const *cubic = NULL; + A0 = curve_it1->initialPoint(); + A1 = curve_it1->initialPoint(); + A2 = curve_it1->finalPoint(); + A3 = curve_it1->finalPoint(); + cubic = dynamic_cast(&*curve_it1); + if (cubic) { + A1 = (*cubic)[1]; + if (shiftNodes) { + A1 = (*cubic)[1] + initialMove; + } + A2 = (*cubic)[2]; + nCurve->curveto(A1, A2, A3); + } else { + nCurve->lineto(A3); + } + double length = Inkscape::Util::Quantity::convert( + curve_it1->length(0.001), "px", unit.get_abbreviation()); + std::size_t splits = 0; + if (method == DM_SEGMENTS) { + splits = segments; + } else { + splits = ceil(length / maxSegmentSize); + } + for (unsigned int t = splits; t >= 1; t--) { + if (t == 1 && splits != 1) { + continue; + } + const SPCurve *tmp; + if (splits == 1) { + tmp = jitter(nCurve->last_segment()); + } else { + tmp = addNodesAndJitter(nCurve->last_segment(), 1. / t); + } + if (nCurve->get_segment_count() > 1) { + nCurve->backspace(); + nCurve->append_continuous(tmp, 0.001); + } else { + nCurve = tmp->copy(); + } + delete tmp; + } + ++curve_it1; + if(curve_it2 != curve_endit) { + ++curve_it2; + } } - if (nCurve->get_segment_count() > 1) { - nCurve->backspace(); - nCurve->append_continuous(tmp, 0.001); - } else { - nCurve = tmp->copy(); + if (path_it->closed()) { + nCurve->closepath_current(); } - delete tmp; - } - ++curve_it1; - ++curve_it2; + curve->append(nCurve, false); + nCurve->reset(); + delete nCurve; } - Geom::CubicBezier const *cubic = NULL; - A0 = curve_it1->initialPoint(); - A1 = curve_it1->initialPoint(); - A2 = curve_it1->finalPoint(); - A3 = curve_it1->finalPoint(); - cubic = dynamic_cast(&*curve_it1); - if (cubic) { - A1 = (*cubic)[1]; - A2 = (*cubic)[2]; - if (path_it->closed()) { - A2 = A2 + initialMove; - A3 = initialPoint; - } - nCurve->curveto(A1, A2, A3); - } else { - if (path_it->closed()) { - A3 = initialPoint; - } - nCurve->lineto(A3); +} + +SPCurve *LPERoughen::addNodesAndJitter(const Geom::Curve *A, double t) +{ + SPCurve *out = new SPCurve(); + Geom::CubicBezier const *cubic = dynamic_cast(&*A); + Geom::Point A1(0, 0); + Geom::Point A2(0, 0); + Geom::Point A3(0, 0); + Geom::Point B1(0, 0); + Geom::Point B2(0, 0); + Geom::Point B3(0, 0); + if (shiftNodes) { + A3 = randomize(); + B3 = randomize(); } - double length = Inkscape::Util::Quantity::convert( - curve_it1->length(0.001), "px", unit.get_abbreviation()); - unsigned int splits = 0; - if (method == DM_SEGMENTS) { - splits = segments; + if (shiftNodeHandles) { + A1 = randomize(); + A2 = randomize(); + B1 = randomize(); + B2 = randomize(); } else { - splits = ceil(length / maxSegmentSize); + A2 = A3; + B1 = A3; + B2 = B3; } - for (unsigned int t = splits; t >= 1; t--) { - if (t == 1 && splits != 1) { - continue; - } - const SPCurve *tmp; - if (splits == 1) { - tmp = jitter(nCurve->last_segment()); - } else { - tmp = addNodesAndJitter(nCurve->last_segment(), 1. / t); - } - if (nCurve->get_segment_count() > 1) { - nCurve->backspace(); - nCurve->append_continuous(tmp, 0.001); - } else { - nCurve = tmp->copy(); - } - delete tmp; - } - //y cerramos la curva - if (path_it->closed()) { - nCurve->closepath_current(); + if (cubic) { + std::pair div = cubic->subdivide(t); + std::vector seg1 = div.first.points(), + seg2 = div.second.points(); + out->moveto(seg1[0]); + out->curveto(seg1[1] + A1, seg1[2] + A2, seg1[3] + A3); + out->curveto(seg2[1] + B1, seg2[2], seg2[3]); + } else if (shiftNodeHandles) { + out->moveto(A->initialPoint()); + out->curveto(A->pointAt(t / 3) + A1, A->pointAt((t / 3) * 2) + A2, + A->pointAt(t) + A3); + out->curveto(A->pointAt(t + (t / 3)) + B1, A->pointAt(t + ((t / 3) * 2)), + A->finalPoint()); + } else { + out->moveto(A->initialPoint()); + out->lineto(A->pointAt(t) + A3); + out->lineto(A->finalPoint()); } - curve->append(nCurve, false); - nCurve->reset(); - delete nCurve; - } -} - -SPCurve *LPERoughen::addNodesAndJitter(const Geom::Curve *A, double t) { - SPCurve *out = new SPCurve(); - Geom::CubicBezier const *cubic = dynamic_cast(&*A); - Geom::Point A1(0, 0); - Geom::Point A2(0, 0); - Geom::Point A3(0, 0); - Geom::Point B1(0, 0); - Geom::Point B2(0, 0); - Geom::Point B3(0, 0); - if (shiftNodes) { - A3 = randomize(); - B3 = randomize(); - } - if (shiftNodeHandles) { - A1 = randomize(); - A2 = randomize(); - B1 = randomize(); - B2 = randomize(); - } else { - A2 = A3; - B1 = A3; - B2 = B3; - } - if (cubic) { - std::pair div = cubic->subdivide(t); - std::vector seg1 = div.first.points(), - seg2 = div.second.points(); - out->moveto(seg1[0]); - out->curveto(seg1[1] + A1, seg1[2] + A2, seg1[3] + A3); - out->curveto(seg2[1] + B1, seg2[2], seg2[3]); - } else if (shiftNodeHandles) { - out->moveto(A->initialPoint()); - out->curveto(A->pointAt(t / 3) + A1, A->pointAt((t / 3) * 2) + A2, - A->pointAt(t) + A3); - out->curveto(A->pointAt(t + (t / 3)) + B1, A->pointAt(t + ((t / 3) * 2)), - A->finalPoint()); - } else { - out->moveto(A->initialPoint()); - out->lineto(A->pointAt(t) + A3); - out->lineto(A->finalPoint()); - } - return out; + return out; } -SPCurve *LPERoughen::jitter(const Geom::Curve *A) { - SPCurve *out = new SPCurve(); - Geom::CubicBezier const *cubic = dynamic_cast(&*A); - Geom::Point A1(0, 0); - Geom::Point A2(0, 0); - Geom::Point A3(0, 0); - if (shiftNodes) { - A3 = randomize(); - } - if (shiftNodeHandles) { - A1 = randomize(); - A2 = randomize(); - } else { - A2 = A3; - } - if (cubic) { - out->moveto((*cubic)[0]); - out->curveto((*cubic)[1] + A1, (*cubic)[2] + A2, (*cubic)[3] + A3); - } else if (shiftNodeHandles) { - out->moveto(A->initialPoint()); - out->curveto(A->pointAt(0.3333) + A1, A->pointAt(0.6666) + A2, - A->finalPoint() + A3); - } else { - out->moveto(A->initialPoint()); - out->lineto(A->finalPoint() + A3); - } - return out; +SPCurve *LPERoughen::jitter(const Geom::Curve *A) +{ + SPCurve *out = new SPCurve(); + Geom::CubicBezier const *cubic = dynamic_cast(&*A); + Geom::Point A1(0, 0); + Geom::Point A2(0, 0); + Geom::Point A3(0, 0); + if (shiftNodes) { + A3 = randomize(); + } + if (shiftNodeHandles) { + A1 = randomize(); + A2 = randomize(); + } else { + A2 = A3; + } + if (cubic) { + out->moveto((*cubic)[0]); + out->curveto((*cubic)[1] + A1, (*cubic)[2] + A2, (*cubic)[3] + A3); + } else if (shiftNodeHandles) { + out->moveto(A->initialPoint()); + out->curveto(A->pointAt(0.3333) + A1, A->pointAt(0.6666) + A2, + A->finalPoint() + A3); + } else { + out->moveto(A->initialPoint()); + out->lineto(A->finalPoint() + A3); + } + return out; } -Geom::Point LPERoughen::tpoint(Geom::Point A, Geom::Point B, double t) { - using Geom::X; - using Geom::Y; - return Geom::Point(A[X] + t * (B[X] - A[X]), A[Y] + t * (B[Y] - A[Y])); +Geom::Point LPERoughen::tpoint(Geom::Point A, Geom::Point B, double t) +{ + using Geom::X; + using Geom::Y; + return Geom::Point(A[X] + t * (B[X] - A[X]), A[Y] + t * (B[Y] - A[Y])); } }; //namespace LivePathEffect diff --git a/src/live_effects/lpe-roughen.h b/src/live_effects/lpe-roughen.h index cc7784dc0..7e99b47bf 100644 --- a/src/live_effects/lpe-roughen.h +++ b/src/live_effects/lpe-roughen.h @@ -1,11 +1,17 @@ -#ifndef INKSCAPE_LPE_ROUGHEN_H -#define INKSCAPE_LPE_ROUGHEN_H - -/* - * Inkscape::LPERoughen +/** @file + * @brief Roughen LPE effect, see lpe-roughen.cpp. + */ +/* Authors: + * Jabier Arraiza Cenoz + * + * Copyright (C) 2014 Authors * * Released under GNU GPL, read the file 'COPYING' for more information */ + +#ifndef INKSCAPE_LPE_ROUGHEN_H +#define INKSCAPE_LPE_ROUGHEN_H + #include "live_effects/parameter/enum.h" #include "live_effects/effect.h" #include "live_effects/parameter/parameter.h" @@ -18,37 +24,37 @@ namespace Inkscape { namespace LivePathEffect { enum DivisionMethod { - DM_SEGMENTS, - DM_SIZE, - DM_END + DM_SEGMENTS, + DM_SIZE, + DM_END }; class LPERoughen : public Effect { public: - LPERoughen(LivePathEffectObject *lpeobject); - virtual ~LPERoughen(); - - virtual void doEffect(SPCurve *curve); - virtual double sign(double randNumber); - virtual Geom::Point randomize(); - virtual void doBeforeEffect(SPLPEItem const */*lpeitem*/); - virtual SPCurve *addNodesAndJitter(const Geom::Curve *A, double t); - virtual SPCurve *jitter(const Geom::Curve *A); - virtual Geom::Point tpoint(Geom::Point A, Geom::Point B, double t = 0.5); - virtual Gtk::Widget *newWidget(); + LPERoughen(LivePathEffectObject *lpeobject); + virtual ~LPERoughen(); + + virtual void doEffect(SPCurve *curve); + virtual double sign(double randNumber); + virtual Geom::Point randomize(); + virtual void doBeforeEffect(SPLPEItem const */*lpeitem*/); + virtual SPCurve *addNodesAndJitter(const Geom::Curve *A, double t); + virtual SPCurve *jitter(const Geom::Curve *A); + virtual Geom::Point tpoint(Geom::Point A, Geom::Point B, double t = 0.5); + virtual Gtk::Widget *newWidget(); private: - UnitParam unit; - EnumParam method; - ScalarParam maxSegmentSize; - ScalarParam segments; - RandomParam displaceX; - RandomParam displaceY; - BoolParam shiftNodes; - BoolParam shiftNodeHandles; - LPERoughen(const LPERoughen &); - LPERoughen &operator=(const LPERoughen &); + UnitParam unit; + EnumParam method; + ScalarParam maxSegmentSize; + ScalarParam segments; + RandomParam displaceX; + RandomParam displaceY; + BoolParam shiftNodes; + BoolParam shiftNodeHandles; + LPERoughen(const LPERoughen &); + LPERoughen &operator=(const LPERoughen &); }; diff --git a/src/live_effects/lpe-tangent_to_curve.cpp b/src/live_effects/lpe-tangent_to_curve.cpp index dbebdf7fb..bce4876af 100644 --- a/src/live_effects/lpe-tangent_to_curve.cpp +++ b/src/live_effects/lpe-tangent_to_curve.cpp @@ -16,8 +16,6 @@ #include #include "live_effects/lpe-tangent_to_curve.h" -// FIXME: The following are only needed to convert the path's SPCurve* to pwd2. -// There must be a more convenient way to achieve this. #include "sp-path.h" #include "display/curve.h" @@ -108,13 +106,13 @@ LPETangentToCurve::addKnotHolderEntities(KnotHolder *knotholder, SPDesktop *desk { KnotHolderEntity *e = new TtC::KnotHolderEntityLeftEnd(this); e->create( desktop, item, knotholder, Inkscape::CTRL_TYPE_UNKNOWN, - _("Adjust the \"left\" end of the tangent") ); + _("Adjust the left end of the tangent") ); knotholder->add(e); } { KnotHolderEntity *e = new TtC::KnotHolderEntityRightEnd(this); e->create( desktop, item, knotholder, Inkscape::CTRL_TYPE_UNKNOWN, - _("Adjust the \"right\" end of the tangent") ); + _("Adjust the right end of the tangent") ); knotholder->add(e); } }; @@ -130,14 +128,13 @@ KnotHolderEntityAttachPt::knot_set(Geom::Point const &p, Geom::Point const &/*or Geom::Point const s = snap_knot_position(p, state); - // FIXME: There must be a better way of converting the path's SPCurve* to pwd2. - SPCurve *curve = SP_PATH(item)->get_curve_for_edit(); - Geom::PathVector pathv = curve->get_pathvector(); - Piecewise > pwd2; - for (unsigned int i=0; i < pathv.size(); i++) { - pwd2.concat(pathv[i].toPwSb()); + if ( !SP_IS_SHAPE(lpe->sp_lpe_item) ) { + //lpe->t_attach.param_set_value(0); + g_warning("LPEItem is not a path! %s:%d\n", __FILE__, __LINE__); + return; } - + Piecewise > pwd2 = paths_to_pw( lpe->pathvector_before_effect ); + double t0 = nearest_point(s, pwd2); lpe->t_attach.param_set_value(t0); diff --git a/src/live_effects/lpe-tangent_to_curve.h b/src/live_effects/lpe-tangent_to_curve.h index 309afc14b..8e44c01d1 100644 --- a/src/live_effects/lpe-tangent_to_curve.h +++ b/src/live_effects/lpe-tangent_to_curve.h @@ -34,7 +34,6 @@ class LPETangentToCurve : public Effect { public: LPETangentToCurve(LivePathEffectObject *lpeobject); virtual ~LPETangentToCurve(); - virtual Geom::Piecewise > doEffect_pwd2 (Geom::Piecewise > const & pwd2_in); diff --git a/src/live_effects/lpe-taperstroke.cpp b/src/live_effects/lpe-taperstroke.cpp new file mode 100644 index 000000000..b595b8d55 --- /dev/null +++ b/src/live_effects/lpe-taperstroke.cpp @@ -0,0 +1,632 @@ +/** + * @file + * Taper Stroke path effect, provided as an alternative to Power Strokes + * for otherwise constant-width paths. + * + * Authors: + * Liam P White + * + * Copyright (C) 2014 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "live_effects/lpe-taperstroke.h" + +#include <2geom/path.h> +#include <2geom/shape.h> +#include <2geom/path.h> +#include <2geom/circle.h> +#include <2geom/sbasis-to-bezier.h> + +#include "pathoutlineprovider.h" +#include "display/curve.h" +#include "sp-shape.h" +#include "style.h" +#include "xml/repr.h" +#include "sp-paint-server.h" +#include "svg/svg-color.h" +#include "desktop-style.h" +#include "svg/css-ostringstream.h" +#include "svg/svg.h" + +#include "knot-holder-entity.h" +#include "knotholder.h" + +#include + +template +inline bool withinRange(T value, T low, T high) { + return (value > low && value < high); +} + +namespace Inkscape { +namespace LivePathEffect { + +namespace TpS { + class KnotHolderEntityAttachBegin : public LPEKnotHolderEntity { + public: + KnotHolderEntityAttachBegin(LPETaperStroke * effect) : LPEKnotHolderEntity(effect) {} + virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state); + virtual Geom::Point knot_get() const; + }; + + class KnotHolderEntityAttachEnd : public LPEKnotHolderEntity { + public: + KnotHolderEntityAttachEnd(LPETaperStroke * effect) : LPEKnotHolderEntity(effect) {} + virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state); + virtual Geom::Point knot_get() const; + }; +} // TpS + +static const Util::EnumData JoinType[] = { + {LINEJOIN_STRAIGHT, N_("Beveled"), "bevel"}, + {LINEJOIN_ROUND, N_("Rounded"), "round"}, + {LINEJOIN_REFLECTED, N_("Reflected"), "reflected"}, + {LINEJOIN_POINTY, N_("Miter"), "miter"}, + {LINEJOIN_EXTRAPOLATED, N_("Extrapolated"), "extrapolated"} +}; + +static const Util::EnumDataConverter JoinTypeConverter(JoinType, sizeof (JoinType)/sizeof(*JoinType)); + +LPETaperStroke::LPETaperStroke(LivePathEffectObject *lpeobject) : + Effect(lpeobject), + line_width(_("Stroke width"), _("The (non-tapered) width of the path"), "stroke_width", &wr, this, 1.), + attach_start(_("Start offset"), _("Taper distance from path start"), "attach_start", &wr, this, 0.2), + attach_end(_("End offset"), _("The ending position of the taper"), "end_offset", &wr, this, 0.2), + smoothing(_("Taper smoothing"), _("Amount of smoothing to apply to the tapers"), "smoothing", &wr, this, 0.5), + join_type(_("Join type"), _("Join type for non-smooth nodes"), "jointype", JoinTypeConverter, &wr, this, LINEJOIN_EXTRAPOLATED), + miter_limit(_("Miter limit"), _("Limit for miter joins"), "miter_limit", &wr, this, 100.) +{ + show_orig_path = true; + _provides_knotholder_entities = true; + + attach_start.param_set_digits(3); + attach_end.param_set_digits(3); + + registerParameter(&line_width); + registerParameter(&attach_start); + registerParameter(&attach_end); + registerParameter(&smoothing); + registerParameter(&join_type); + registerParameter(&miter_limit); +} + +// from LPEPowerStroke -- sets fill if stroke color because we will +// be converting to a fill to make the new join. + +void LPETaperStroke::doOnApply(SPLPEItem const* lpeitem) +{ + if (SP_IS_SHAPE(lpeitem)) { + SPLPEItem* item = const_cast(lpeitem); + double width = (lpeitem && lpeitem->style) ? lpeitem->style->stroke_width.computed : 1.; + + SPCSSAttr *css = sp_repr_css_attr_new (); + if (true) { + if (lpeitem->style->stroke.isPaintserver()) { + SPPaintServer * server = lpeitem->style->getStrokePaintServer(); + if (server) { + Glib::ustring str; + str += "url(#"; + str += server->getId(); + str += ")"; + sp_repr_css_set_property (css, "fill", str.c_str()); + } + } else if (lpeitem->style->stroke.isColor()) { + gchar c[64]; + sp_svg_write_color (c, sizeof(c), lpeitem->style->stroke.value.color.toRGBA32(SP_SCALE24_TO_FLOAT(lpeitem->style->stroke_opacity.value))); + sp_repr_css_set_property (css, "fill", c); + } else { + sp_repr_css_set_property (css, "fill", "none"); + } + } else { + sp_repr_css_unset_property (css, "fill"); + } + + sp_repr_css_set_property(css, "stroke", "none"); + + sp_desktop_apply_css_recursive(item, css, true); + sp_repr_css_attr_unref (css); + + line_width.param_set_value(width); + } else { + printf("WARNING: It only makes sense to apply Join Type to paths (not groups).\n"); + } +} + +// from LPEPowerStroke -- sets stroke color from existing fill color + +void LPETaperStroke::doOnRemove(SPLPEItem const* lpeitem) +{ + if (SP_IS_SHAPE(lpeitem)) { + SPLPEItem *item = const_cast(lpeitem); + + SPCSSAttr *css = sp_repr_css_attr_new (); + if (true) { + if (lpeitem->style->fill.isPaintserver()) { + SPPaintServer * server = lpeitem->style->getFillPaintServer(); + if (server) { + Glib::ustring str; + str += "url(#"; + str += server->getId(); + str += ")"; + sp_repr_css_set_property (css, "stroke", str.c_str()); + } + } else if (lpeitem->style->fill.isColor()) { + gchar c[64]; + sp_svg_write_color (c, sizeof(c), lpeitem->style->stroke.value.color.toRGBA32(SP_SCALE24_TO_FLOAT(lpeitem->style->stroke_opacity.value))); + sp_repr_css_set_property (css, "stroke", c); + } else { + sp_repr_css_set_property (css, "stroke", "none"); + } + } else { + sp_repr_css_unset_property (css, "stroke"); + } + + Inkscape::CSSOStringStream os; + os << fabs(line_width); + sp_repr_css_set_property (css, "stroke-width", os.str().c_str()); + + sp_repr_css_set_property(css, "fill", "none"); + + sp_desktop_apply_css_recursive(item, css, true); + sp_repr_css_attr_unref (css); + } +} + +using Geom::Piecewise; +using Geom::D2; +using Geom::SBasis; +// leave Geom::Path + +Geom::Path return_at_first_cusp(Geom::Path const & path_in, double /*smooth_tolerance*/ = 0.05) { + return Geom::split_at_cusps(path_in)[0]; +} + +Piecewise > stretch_along(Piecewise > pwd2_in, Geom::Path pattern, double width); + +// references to pointers +void subdivideCurve(Geom::Curve * curve_in, Geom::Coord t, Geom::Curve *& val_first, Geom::Curve *& val_second); + +// actual effect + +Geom::PathVector LPETaperStroke::doEffect_path(Geom::PathVector const& path_in) +{ + Geom::Path first_cusp = return_at_first_cusp(path_in[0]); + Geom::Path last_cusp = return_at_first_cusp(path_in[0].reverse()); + + bool zeroStart = false; // [distance from start taper knot -> start of path] == 0 + bool zeroEnd = false; // [distance from end taper knot -> end of path] == 0 + bool metInMiddle = false; // knots are touching + + // there is a pretty good chance that people will try to drag the knots + // on top of each other, so block it + + unsigned size = path_in[0].size(); + if (size == first_cusp.size()) { + // check to see if the knots were dragged over each other + // if so, reset the end offset, but still allow the start offset. + if ( attach_start >= (size - attach_end) ) { + attach_end.param_set_value( size - attach_start ); + metInMiddle = true; + } + } + + if (attach_start == size - attach_end) { + metInMiddle = true; + } + if (attach_end == size - attach_start) { + metInMiddle = true; + } + + // don't let it be integer (TODO this is stupid!) + { + if (double(unsigned(attach_start)) == attach_start) { + attach_start.param_set_value(attach_start - 0.00001); + } + if (double(unsigned(attach_end)) == attach_end) { + attach_end.param_set_value(attach_end - 0.00001); + } + } + + unsigned allowed_start = first_cusp.size(); + unsigned allowed_end = last_cusp.size(); + + // don't let the knots be farther than they are allowed to be + { + if ((unsigned)attach_start >= allowed_start) { + attach_start.param_set_value((double)allowed_start - 0.00001); + } + if ((unsigned)attach_end >= allowed_end) { + attach_end.param_set_value((double)allowed_end - 0.00001); + } + } + + // don't let it be zero (this is stupid too!) + if (attach_start < 0.0000001 || withinRange(double(attach_start), 0.00000001, 0.000001)) { + attach_start.param_set_value( 0.0000001 ); + zeroStart = true; + } + if (attach_end < 0.0000001 || withinRange(double(attach_end), 0.00000001, 0.000001)) { + attach_end.param_set_value( 0.0000001 ); + zeroEnd = true; + } + + // Path::operator () means get point at time t + start_attach_point = first_cusp(attach_start); + end_attach_point = last_cusp(attach_end); + Geom::PathVector pathv_out; + + // the following function just splits it up into three pieces. + pathv_out = doEffect_simplePath(path_in); + + // now for the actual tapering. the stretch_along method (stolen from PaP) is used to accomplish this + + Geom::PathVector real_pathv; + Geom::Path real_path; + Geom::PathVector pat_vec; + Piecewise > pwd2; + Geom::Path throwaway_path; + + if (!zeroStart) { + // Construct the pattern + std::stringstream pat_str; + pat_str.imbue(std::locale::classic()); + pat_str << "M 1,0 C " << 1 - (double)smoothing << ",0 0,0.5 0,0.5 0,0.5 " << 1 - (double)smoothing << ",1 1,1"; + + pat_vec = sp_svg_read_pathv(pat_str.str().c_str()); + pwd2.concat(stretch_along(pathv_out[0].toPwSb(), pat_vec[0], -fabs(line_width))); + throwaway_path = Geom::path_from_piecewise(pwd2, LPE_CONVERSION_TOLERANCE)[0]; + + real_path.append(throwaway_path); + } + + // if this condition happens to evaluate false, i.e. there was no space for a path to be drawn, it is simply skipped. + // although this seems obvious, it can probably lead to bugs. + if (!metInMiddle) { + // append the outside outline of the path (goes with the direction of the path) + throwaway_path = Outline::PathOutsideOutline(pathv_out[1], -fabs(line_width), static_cast(join_type.get_value()), miter_limit); + if (!zeroStart && real_path.size() >= 1 && throwaway_path.size() >= 1) { + if (!Geom::are_near(real_path.finalPoint(), throwaway_path.initialPoint())) { + real_path.appendNew(throwaway_path.initialPoint()); + } else { + real_path.setFinal(throwaway_path.initialPoint()); + } + } + real_path.append(throwaway_path); + } + + if (!zeroEnd) { + // append the ending taper + std::stringstream pat_str_1; + pat_str_1.imbue(std::locale::classic()); + pat_str_1 << "M 0,1 C " << (double)smoothing << ",1 1,0.5 1,0.5 1,0.5 " << double(smoothing) << ",0 0,0"; + pat_vec = sp_svg_read_pathv(pat_str_1.str().c_str()); + + pwd2 = Piecewise >(); + pwd2.concat(stretch_along(pathv_out[2].toPwSb(), pat_vec[0], -fabs(line_width))); + + throwaway_path = Geom::path_from_piecewise(pwd2, LPE_CONVERSION_TOLERANCE)[0]; + if (!Geom::are_near(real_path.finalPoint(), throwaway_path.initialPoint()) && real_path.size() >= 1) { + real_path.appendNew(throwaway_path.initialPoint()); + } else { + real_path.setFinal(throwaway_path.initialPoint()); + } + real_path.append(throwaway_path); + } + + if (!metInMiddle) { + // append the inside outline of the path (against direction) + throwaway_path = Outline::PathOutsideOutline(pathv_out[1].reverse(), -fabs(line_width), static_cast(join_type.get_value()), miter_limit); + + if (!Geom::are_near(real_path.finalPoint(), throwaway_path.initialPoint()) && real_path.size() >= 1) { + real_path.appendNew(throwaway_path.initialPoint()); + } else { + real_path.setFinal(throwaway_path.initialPoint()); + } + real_path.append(throwaway_path); + } + + if (!Geom::are_near(real_path.finalPoint(), real_path.initialPoint())) { + real_path.appendNew(real_path.initialPoint()); + } else { + real_path.setFinal(real_path.initialPoint()); + } + real_path.close(); + + real_pathv.push_back(real_path); + + return real_pathv; +} + +/** + * @return Always returns a PathVector with three elements. + * + * The positions of the effect knots are accessed to determine + * where exactly the input path should be split. + */ +Geom::PathVector LPETaperStroke::doEffect_simplePath(Geom::PathVector const & path_in) +{ + size_t size = path_in[0].size(); + + unsigned loc = (unsigned)attach_start; + Geom::Curve * curve_start = path_in[0] [loc].duplicate(); + + std::vector pathv_out; + Geom::Path path_out = Geom::Path(); + + Geom::Path trimmed_start = Geom::Path(); + Geom::Path trimmed_end = Geom::Path(); + + for (size_t i = 0; i < loc; ++i) { + trimmed_start.append(path_in[0] [i]); + } + + Geom::Curve * temp; + subdivideCurve(curve_start, attach_start - loc, temp, curve_start); + trimmed_start.append(*temp); + if (temp) delete temp; temp = 0; + + // special case: path is one segment long + // special case: what if the two knots occupy the same segment? + if ((size == 1) || ( size - unsigned(attach_end) - 1 == loc )) { + + // If you look into it, I don't actually think there is a working way to do this + // with only point math. So we use nearest_point instead. + Geom::Coord t = Geom::nearest_point(end_attach_point, *curve_start); + + // it is just a dumb segment + // we have to do some shifting here because the value changed when we reduced the length + // of the previous segment. + + subdivideCurve(curve_start, t, curve_start, temp); + trimmed_end.append(*temp); + if (temp) delete temp; temp = 0; + + for (size_t j = (size - attach_end) + 1; j < size; ++j) { + trimmed_end.append(path_in[0] [j]); + } + + path_out.append(*curve_start); + pathv_out.push_back(trimmed_start); + pathv_out.push_back(path_out); + pathv_out.push_back(trimmed_end); + return pathv_out; + } + + pathv_out.push_back(trimmed_start); + + // append almost all of the rest of the path, ignore the curves that the knot is past (we'll get to it in a minute) + path_out.append(*curve_start); + + for (size_t k = loc + 1; k < (size - unsigned(attach_end)) - 1; ++k) { + path_out.append(path_in[0] [k]); + } + + // deal with the last segment in a very similar fashion to the first + loc = size - attach_end; + + Geom::Curve * curve_end = path_in[0] [loc].duplicate(); + + Geom::Coord t = Geom::nearest_point(end_attach_point, *curve_end); + + subdivideCurve(curve_end, t, curve_end, temp); + trimmed_end.append(*temp); + if (temp) delete temp; temp = 0; + + for (size_t j = (size - attach_end) + 1; j < size; ++j) { + trimmed_end.append(path_in[0] [j]); + } + + path_out.append(*curve_end); + pathv_out.push_back(path_out); + + pathv_out.push_back(trimmed_end); + + if (curve_end) delete curve_end; + if (curve_start) delete curve_start; + return pathv_out; +} + + +/** + * Most of the below function is verbatim from Pattern Along Path. However, it needed a little + * tweaking to get it to work right in this case. Also, large portions of the effect have been + * stripped out as I deemed them unnecessary for the relative simplicity of this effect. + */ +Piecewise > stretch_along(Piecewise > pwd2_in, Geom::Path pattern, double prop_scale) +{ + using namespace Geom; + + // Don't allow empty path parameter: + if ( pattern.empty() ) { + return pwd2_in; + } + + /* Much credit should go to jfb and mgsloan of lib2geom development for the code below! */ + Piecewise > output; + std::vector > > pre_output; + + D2 > patternd2 = make_cuts_independent(pattern.toPwSb()); + Piecewise x0 = Piecewise(patternd2[0]); + Piecewise y0 = Piecewise(patternd2[1]); + OptInterval pattBndsX = bounds_exact(x0); + OptInterval pattBndsY = bounds_exact(y0); + if (pattBndsX && pattBndsY) { + x0 -= pattBndsX->min(); + y0 -= pattBndsY->middle(); + + double xspace = 0; + double noffset = 0; + double toffset = 0; + // Prevent more than 90% overlap... + if (xspace < -pattBndsX->extent()*.9) { + xspace = -pattBndsX->extent()*.9; + } + + y0+=noffset; + + std::vector > > paths_in; + paths_in = split_at_discontinuities(pwd2_in); + + for (unsigned idx = 0; idx < paths_in.size(); idx++) { + Piecewise > path_i = paths_in[idx]; + Piecewise x = x0; + Piecewise y = y0; + Piecewise > uskeleton = arc_length_parametrization(path_i,2,.1); + uskeleton = remove_short_cuts(uskeleton,.01); + Piecewise > n = rot90(derivative(uskeleton)); + n = force_continuity(remove_short_cuts(n,.1)); + + int nbCopies = 0; + double scaling = 1; + nbCopies = 1; + scaling = (uskeleton.domain().extent() - toffset)/pattBndsX->extent(); + + double pattWidth = pattBndsX->extent() * scaling; + + if (scaling != 1.0) { + x*=scaling; + } + if ( false ) { + y*=(scaling*prop_scale); + } else { + if (prop_scale != 1.0) y *= prop_scale; + } + x += toffset; + + double offs = 0; + for (int i=0; i > output_piece = compose(uskeleton,x+offs)+y*compose(n,x+offs); + std::vector > > splited_output_piece = split_at_discontinuities(output_piece); + pre_output.insert(pre_output.end(), splited_output_piece.begin(), splited_output_piece.end() ); + } else { + output.concat(compose(uskeleton,x+offs)+y*compose(n,x+offs)); + } + offs+=pattWidth; + } + } + return output; + } else { + return pwd2_in; + } +} + +void subdivideCurve(Geom::Curve * curve_in, Geom::Coord t, Geom::Curve *& val_first, Geom::Curve *& val_second) +{ + if (Geom::LineSegment* linear = dynamic_cast(curve_in)) { + // special case for line segments + std::pair seg_pair = linear->subdivide(t); + val_first = seg_pair.first.duplicate(); + val_second = seg_pair.second.duplicate(); + } else { + // all other cases: + Geom::CubicBezier cubic = Geom::sbasis_to_cubicbezier(curve_in->toSBasis()); + std::pair cubic_pair = cubic.subdivide(t); + val_first = cubic_pair.first.duplicate(); + val_second = cubic_pair.second.duplicate(); + } +} + + +void LPETaperStroke::addKnotHolderEntities(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item) +{ + KnotHolderEntity *e = new TpS::KnotHolderEntityAttachBegin(this); + e->create(desktop, item, knotholder, Inkscape::CTRL_TYPE_UNKNOWN, _("Start point of the taper"), SP_KNOT_SHAPE_CIRCLE); + knotholder->add(e); + + KnotHolderEntity *f = new TpS::KnotHolderEntityAttachEnd(this); + f->create(desktop, item, knotholder, Inkscape::CTRL_TYPE_UNKNOWN, _("End point of the taper"), SP_KNOT_SHAPE_CIRCLE); + knotholder->add(f); +} + +namespace TpS { + +void KnotHolderEntityAttachBegin::knot_set(Geom::Point const &p, Geom::Point const&/*origin*/, guint state) +{ + using namespace Geom; + + LPETaperStroke* lpe = dynamic_cast(_effect); + + Geom::Point const s = snap_knot_position(p, state); + + if (!SP_IS_SHAPE(lpe->sp_lpe_item)) { + printf("WARNING: LPEItem is not a path!\n"); + return; + } + + SPCurve* curve; + if (!(curve = SP_SHAPE(lpe->sp_lpe_item)->getCurve())) { + // oops + return; + } + // in case you are wondering, the above are simply sanity checks. we never want to actually + // use that object. + + Geom::PathVector pathv = lpe->pathvector_before_effect; + + Piecewise > pwd2; + Geom::Path p_in = return_at_first_cusp(pathv[0]); + pwd2.concat(p_in.toPwSb()); + + double t0 = nearest_point(s, pwd2); + lpe->attach_start.param_set_value(t0); + + // FIXME: this should not directly ask for updating the item. It should write to SVG, which triggers updating. + sp_lpe_item_update_patheffect(SP_LPE_ITEM(item), false, true); +} +void KnotHolderEntityAttachEnd::knot_set(Geom::Point const &p, Geom::Point const& /*origin*/, guint state) +{ + using namespace Geom; + + LPETaperStroke* lpe = dynamic_cast(_effect); + + Geom::Point const s = snap_knot_position(p, state); + + if (!SP_IS_SHAPE(lpe->sp_lpe_item) ) { + printf("WARNING: LPEItem is not a path!\n"); + return; + } + + SPCurve* curve; + if ( !(curve = SP_SHAPE(lpe->sp_lpe_item)->getCurve()) ) { + // oops + return; + } + Geom::PathVector pathv = lpe->pathvector_before_effect; + Geom::Path p_in = return_at_first_cusp(pathv[0].reverse()); + Piecewise > pwd2 = p_in.toPwSb(); + + double t0 = nearest_point(s, pwd2); + lpe->attach_end.param_set_value(t0); + + sp_lpe_item_update_patheffect (SP_LPE_ITEM(item), false, true); +} + +Geom::Point KnotHolderEntityAttachBegin::knot_get() const +{ + LPETaperStroke const * lpe = dynamic_cast (_effect); + return lpe->start_attach_point; +} + +Geom::Point KnotHolderEntityAttachEnd::knot_get() const +{ + LPETaperStroke const * lpe = dynamic_cast (_effect); + return lpe->end_attach_point; +} + +} // namespace TpS +} // 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:fileencoding=utf-8 : diff --git a/src/live_effects/lpe-taperstroke.h b/src/live_effects/lpe-taperstroke.h new file mode 100644 index 000000000..88604486e --- /dev/null +++ b/src/live_effects/lpe-taperstroke.h @@ -0,0 +1,72 @@ +/** @file + * @brief Taper Stroke path effect (meant as a replacement for using Power Strokes for tapering) + */ +/* Authors: + * Liam P White + * Copyright (C) 2014 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef INKSCAPE_LPE_TAPERSTROKE_H +#define INKSCAPE_LPE_TAPERSTROKE_H + +#include "live_effects/parameter/enum.h" +#include "live_effects/effect.h" +#include "live_effects/parameter/parameter.h" +#include "live_effects/parameter/vector.h" + +namespace Inkscape { +namespace LivePathEffect { + +namespace TpS { +// we need a separate namespace to avoid clashes with other LPEs +class KnotHolderEntityAttachBegin; +class KnotHolderEntityAttachEnd; +} + +class LPETaperStroke : public Effect { +public: + LPETaperStroke(LivePathEffectObject *lpeobject); + virtual ~LPETaperStroke() {} + + virtual void doOnApply(SPLPEItem const* lpeitem); + virtual void doOnRemove(SPLPEItem const* lpeitem); + + virtual Geom::PathVector doEffect_path (Geom::PathVector const& path_in); + Geom::PathVector doEffect_simplePath(Geom::PathVector const& path_in); + + virtual void addKnotHolderEntities(KnotHolder * knotholder, SPDesktop * desktop, SPItem * item); + + friend class TpS::KnotHolderEntityAttachBegin; + friend class TpS::KnotHolderEntityAttachEnd; +private: + ScalarParam line_width; + ScalarParam attach_start; + ScalarParam attach_end; + ScalarParam smoothing; + EnumParam join_type; + ScalarParam miter_limit; + + Geom::Point start_attach_point; + Geom::Point end_attach_point; + + LPETaperStroke(const LPETaperStroke&); + LPETaperStroke& operator=(const LPETaperStroke&); +}; + +} //namespace LivePathEffect +} //namespace Inkscape + +#endif + +/* + 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:fileencoding=utf-8 : diff --git a/src/live_effects/lpeobject-reference.h b/src/live_effects/lpeobject-reference.h index b1ba1ee4e..374e715ec 100644 --- a/src/live_effects/lpeobject-reference.h +++ b/src/live_effects/lpeobject-reference.h @@ -9,10 +9,10 @@ * Released under GNU GPL, read the file 'COPYING' for more information. */ -#include -#include #include +#include "uri-references.h" + namespace Inkscape { namespace XML { class Node; @@ -33,7 +33,7 @@ public: SPObject *owner; // concerning the LPEObject that is refered to: - gchar *lpeobject_href; + char *lpeobject_href; Inkscape::XML::Node *lpeobject_repr; LivePathEffectObject *lpeobject; diff --git a/src/live_effects/lpeobject.h b/src/live_effects/lpeobject.h index 9700024fe..2e62707e3 100644 --- a/src/live_effects/lpeobject.h +++ b/src/live_effects/lpeobject.h @@ -47,9 +47,9 @@ protected: virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); virtual void release(); - virtual void set(unsigned int key, const gchar* value); + virtual void set(unsigned int key, char const* value); - virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, guint flags); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, unsigned int flags); }; #endif diff --git a/src/live_effects/parameter/Makefile_insert b/src/live_effects/parameter/Makefile_insert index 1137ef34b..f990f41c7 100644 --- a/src/live_effects/parameter/Makefile_insert +++ b/src/live_effects/parameter/Makefile_insert @@ -20,12 +20,16 @@ ink_common_sources += \ live_effects/parameter/path.h \ live_effects/parameter/originalpath.cpp \ live_effects/parameter/originalpath.h \ + live_effects/parameter/originalpatharray.cpp \ + live_effects/parameter/originalpatharray.h \ live_effects/parameter/powerstrokepointarray.cpp \ live_effects/parameter/powerstrokepointarray.h \ live_effects/parameter/filletchamferpointarray.cpp \ live_effects/parameter/filletchamferpointarray.h \ live_effects/parameter/text.cpp \ live_effects/parameter/text.h \ + live_effects/parameter/transformedpoint.cpp \ + live_effects/parameter/transformedpoint.h \ live_effects/parameter/togglebutton.cpp \ live_effects/parameter/togglebutton.h \ live_effects/parameter/unit.cpp \ diff --git a/src/live_effects/parameter/filletchamferpointarray.cpp b/src/live_effects/parameter/filletchamferpointarray.cpp index a89a6279b..bb00ef045 100644 --- a/src/live_effects/parameter/filletchamferpointarray.cpp +++ b/src/live_effects/parameter/filletchamferpointarray.cpp @@ -8,12 +8,20 @@ * Released under GNU GPL, read the file 'COPYING' for more information */ +#include + #include "ui/dialog/lpe-fillet-chamfer-properties.h" #include "live_effects/parameter/filletchamferpointarray.h" + #include <2geom/piecewise.h> #include <2geom/sbasis-to-bezier.h> #include <2geom/sbasis-geometric.h> +#include <2geom/svg-elliptical-arc.h> +#include <2geom/line.h> +#include <2geom/path-intersection.h> +#include "ui/dialog/lpe-fillet-chamfer-properties.h" +#include "live_effects/parameter/filletchamferpointarray.h" #include "live_effects/effect.h" #include "svg/svg.h" #include "svg/stringstream.h" @@ -32,6 +40,7 @@ // this has to be included last. #include + using namespace Geom; namespace Inkscape { @@ -165,11 +174,12 @@ void FilletChamferPointArrayParam::recalculate_controlpoints_for_new_pwd2( } } if (last_pathv.size() > pathv.size() || - (last_pathv[counterPaths].size() > counter - offset && + (last_pathv.size() > counterPaths && + last_pathv[counterPaths].size() > counter - offset && !are_near(curve_it1->initialPoint(), last_pathv[counterPaths][counter - offset].initialPoint(), 0.1))) { - if (last_pathv.size() > counterPaths && curve_it2 == curve_endit) { + if ( curve_it2 == curve_endit) { if (last_pathv[counterPaths].size() < pathv[counterPaths].size()) { offset = abs(last_pathv[counterPaths].size() - pathv[counterPaths].size()); @@ -332,10 +342,10 @@ void FilletChamferPointArrayParam::recalculate_knots( result.push_back(Point(xPos, 0)); } ++curve_it1; + counter++; if (curve_it2 != curve_endit) { ++curve_it2; } - counter++; counterCurves++; } } @@ -359,6 +369,11 @@ void FilletChamferPointArrayParam::set_helper_size(int hs) helper_size = hs; } +void FilletChamferPointArrayParam::set_use_distance(bool use_knot_distance ) +{ + use_distance = use_knot_distance; +} + void FilletChamferPointArrayParam::set_unit(const gchar *abbr) { unit = abbr; @@ -398,6 +413,205 @@ void FilletChamferPointArrayParam::addCanvasIndicators( hp_vec.push_back(hp); } +double FilletChamferPointArrayParam::rad_to_len(int index, double rad) +{ + double len = 0; + std::vector subpaths = path_from_piecewise(last_pwd2, 0.1); + std::pair positions = get_positions(index, subpaths); + D2 A = last_pwd2[last_index(index, subpaths)]; + if(positions.second != 0){ + A = last_pwd2[index-1]; + }else{ + if(!subpaths[positions.first].closed()){ + return len; + } + } + D2 B = last_pwd2[index]; + Piecewise > offset_curve0 = Piecewise >(A)+rot90(unitVector(derivative(A)))*(rad); + Piecewise > offset_curve1 = Piecewise >(B)+rot90(unitVector(derivative(B)))*(rad); + Geom::Path p0 = path_from_piecewise(offset_curve0, 0.1)[0]; + Geom::Path p1 = path_from_piecewise(offset_curve1, 0.1)[0]; + Geom::Crossings cs = Geom::crossings(p0, p1); + if(cs.size() > 0){ + Point cp =p0(cs[0].ta); + double p0pt = nearest_point(cp, B); + len = time_to_len(index,p0pt); + } else { + if(rad < 0){ + len = rad_to_len(index, rad * -1); + } + } + return len; +} + +double FilletChamferPointArrayParam::len_to_rad(int index, double len) +{ + double rad = 0; + double tmp_len = _vector[index][X]; + _vector[index] = Geom::Point(len,_vector[index][Y]); + std::vector subpaths = path_from_piecewise(last_pwd2, 0.1); + std::pair positions = get_positions(index, subpaths); + Piecewise > u; + u.push_cut(0); + u.push(last_pwd2[last_index(index, subpaths)], 1); + Geom::Curve * A = path_from_piecewise(u, 0.1)[0][0].duplicate(); + Geom::Curve * B = subpaths[positions.first][positions.second].duplicate(); + std::vector times; + if(positions.second != 0){ + A = subpaths[positions.first][positions.second-1].duplicate(); + times = get_times(index-1, subpaths, false); + }else{ + if(!subpaths[positions.first].closed()){ + return rad; + } + times = get_times(last_index(index, subpaths), subpaths, true); + } + _vector[index] = Geom::Point(tmp_len,_vector[index][Y]); + Geom::Point startArcPoint = A->toSBasis().valueAt(times[1]); + Geom::Point endArcPoint = B->toSBasis().valueAt(times[2]); + Curve *knotCurve1 = A->portion(times[0], times[1]); + Curve *knotCurve2 = B->portion(times[2], 1); + Geom::CubicBezier const *cubic1 = dynamic_cast(&*knotCurve1); + Ray ray1(startArcPoint, A->finalPoint()); + if (cubic1) { + ray1.setPoints((*cubic1)[2], startArcPoint); + } + Geom::CubicBezier const *cubic2 = dynamic_cast(&*knotCurve2); + Ray ray2(B->initialPoint(), endArcPoint); + if (cubic2) { + ray2.setPoints(endArcPoint, (*cubic2)[1]); + } + bool ccwToggle = cross(A->finalPoint() - startArcPoint, endArcPoint - startArcPoint) < 0; + double distanceArc = Geom::distance(startArcPoint,middle_point(startArcPoint,endArcPoint)); + double angleBetween = angle_between(ray1, ray2, ccwToggle); + rad = distanceArc/sin(angleBetween/2.0); + return rad * -1; +} + +std::vector FilletChamferPointArrayParam::get_times(int index, std::vector subpaths, bool last) +{ + const double tolerance = 0.001; + const double gapHelper = 0.00001; + std::pair positions = get_positions(index, subpaths); + Curve *curve_it1; + curve_it1 = subpaths[positions.first][positions.second].duplicate(); + Coord it1_length = (*curve_it1).length(tolerance); + double time_it1, time_it2, time_it1_B, intpart; + time_it1 = modf(to_time(index, _vector[index][X]), &intpart); + if (_vector[index][Y] == 0) { + time_it1 = 0; + } + double resultLenght = 0; + time_it1_B = 1; + if (subpaths[positions.first].closed() && last) { + time_it2 = modf(to_time(index - positions.second , _vector[index - positions.second ][X]), &intpart); + resultLenght = it1_length + to_len(index - positions.second, _vector[index - positions.second ][X]); + } else if (!subpaths[positions.first].closed() && last){ + time_it2 = 0; + resultLenght = 0; + } else { + time_it2 = modf(to_time(index + 1, _vector[index + 1][X]), &intpart); + resultLenght = it1_length + to_len( index + 1, _vector[index + 1][X]); + } + if (resultLenght > 0 && time_it2 != 0) { + time_it1_B = modf(to_time(index, -resultLenght), &intpart); + } else { + if (time_it2 == 0) { + time_it1_B = 1; + } else { + time_it1_B = gapHelper; + } + } + + if ((subpaths[positions.first].closed() && last && _vector[index - positions.second][Y] == 0) || (subpaths[positions.first].size() > positions.second + 1 && _vector[index + 1][Y] == 0)) { + time_it1_B = 1; + time_it2 = 0; + } + if (time_it1_B < time_it1) { + time_it1_B = time_it1 + gapHelper; + } + std::vector out; + out.push_back(time_it1); + out.push_back(time_it1_B); + out.push_back(time_it2); + return out; +} + +std::pair FilletChamferPointArrayParam::get_positions(int index, std::vector subpaths) +{ + int counter = -1; + std::size_t first = 0; + std::size_t second = 0; + for (PathVector::const_iterator path_it = subpaths.begin(); path_it != subpaths.end(); ++path_it) { + if (path_it->empty()) + continue; + Geom::Path::const_iterator curve_it1 = path_it->begin(); + Geom::Path::const_iterator curve_endit = path_it->end_default(); + if (path_it->closed()) { + const Geom::Curve &closingline = path_it->back_closed(); + // 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! + curve_endit = path_it->end_open(); + } + } + first++; + second = 0; + while (curve_it1 != curve_endit) { + counter++; + second++; + if(counter == index){ + break; + } + ++curve_it1; + } + if(counter == index){ + break; + } + } + first--; + second--; + std::pair out(first, second); + return out; +} + +int FilletChamferPointArrayParam::last_index(int index, std::vector subpaths) +{ + int counter = -1; + bool inSubpath = false; + for (PathVector::const_iterator path_it = subpaths.begin(); path_it != subpaths.end(); ++path_it) { + if (path_it->empty()) + continue; + Geom::Path::const_iterator curve_it1 = path_it->begin(); + Geom::Path::const_iterator curve_endit = path_it->end_default(); + if (path_it->closed()) { + const Geom::Curve &closingline = path_it->back_closed(); + if (are_near(closingline.initialPoint(), closingline.finalPoint())) { + curve_endit = path_it->end_open(); + } + } + while (curve_it1 != curve_endit) { + counter++; + if(counter == index){ + inSubpath = true; + } + ++curve_it1; + } + if(inSubpath){ + break; + } + } + if(!inSubpath){ + counter = -1; + } + return counter; +} + + double FilletChamferPointArrayParam::len_to_time(int index, double len) { double t = 0; @@ -405,7 +619,7 @@ double FilletChamferPointArrayParam::len_to_time(int index, double len) if (len != 0) { if (last_pwd2[index][0].degreesOfFreedom() != 2) { Piecewise > u; - u.push_cut(0); + u.push_cut(0); u.push(last_pwd2[index], 1); std::vector t_roots = roots(arcLengthSb(u) - std::abs(len)); if (t_roots.size() > 0) { @@ -586,34 +800,34 @@ void FilletChamferPointArrayParamKnotHolderEntity::knot_click(guint state) this->knot->tip = g_strdup(tip); this->knot->show(); } - } else if ((state & GDK_MOD1_MASK) || (state & GDK_SHIFT_MASK)) { - Geom::Point offset = Geom::Point(_pparam->_vector.at(_index).x(), - _pparam->_vector.at(_index).y()); + } else if (state & GDK_SHIFT_MASK) { + double xModified = _pparam->_vector.at(_index).x(); + if(xModified < 0 && !_pparam->use_distance){ + xModified = _pparam->len_to_rad(_index, _pparam->_vector.at(_index).x()); + } + std::vector subpaths = path_from_piecewise(_pparam->last_pwd2, 0.1); + std::pair positions = _pparam->get_positions(_index, subpaths); + D2 A = _pparam->last_pwd2[_pparam->last_index(_index, subpaths)]; + if(positions.second != 0){ + A = _pparam->last_pwd2[_index-1]; + } + D2 B = _pparam->last_pwd2[_index]; + bool aprox = (A[0].degreesOfFreedom() != 2 || B[0].degreesOfFreedom() != 2) && !_pparam->use_distance?true:false; + Geom::Point offset = Geom::Point(xModified, _pparam->_vector.at(_index).y()); Inkscape::UI::Dialogs::FilletChamferPropertiesDialog::showDialog( - this->desktop, offset, this, _pparam->unit); + this->desktop, offset, this, _pparam->unit, _pparam->use_distance, aprox); } } -void -FilletChamferPointArrayParamKnotHolderEntity::knot_doubleclicked(guint state) -{ - //todo: fill the double click dialog whith this parameters in the added file - //src/ui/dialog/lpe-fillet-chamfer-properties.cpp(.h) - //My idea for when have enought time is: - //label whith radius in percent - //label whith radius in size -maybe handle units- - //entry whith actual radius -in flexible % mode or fixed -?px- - //2 radio options to switch entry from fixed or flexible, also update the - //entry - //Checkbox or two radios to swith fillet or chamfer - -} - void FilletChamferPointArrayParamKnotHolderEntity::knot_set_offset( Geom::Point offset) { - _pparam->_vector.at(_index) = Geom::Point(offset.x(), offset.y()); + double xModified = offset.x(); + if(xModified < 0 && !_pparam->use_distance){ + xModified = _pparam->rad_to_len(_index, offset.x()); + } + _pparam->_vector.at(_index) = Geom::Point(xModified, offset.y()); this->parent_holder->knot_ungrabbed_handler(this->knot, 0); } @@ -644,7 +858,6 @@ void FilletChamferPointArrayParam::addKnotHolderEntities(KnotHolder *knotholder, "Shift+Click open dialog, " "Ctrl+Alt+Click reset"); } - FilletChamferPointArrayParamKnotHolderEntity *e = new FilletChamferPointArrayParamKnotHolderEntity(this, i); e->create(desktop, item, knotholder, Inkscape::CTRL_TYPE_UNKNOWN, _(tip), diff --git a/src/live_effects/parameter/filletchamferpointarray.h b/src/live_effects/parameter/filletchamferpointarray.h index 93d6e9f4c..a1fa698ae 100644 --- a/src/live_effects/parameter/filletchamferpointarray.h +++ b/src/live_effects/parameter/filletchamferpointarray.h @@ -43,9 +43,15 @@ public: guint32 color); virtual double to_time(int index, double A); virtual double to_len(int index, double A); + virtual double rad_to_len(int index, double rad); + virtual double len_to_rad(int index, double len); virtual double len_to_time(int index, double len); virtual double time_to_len(int index, double time); + virtual std::pair get_positions(int index, std::vector subpaths); + virtual int last_index(int index, std::vector subpaths); + std::vector get_times(int index, std::vector subpaths, bool last); virtual void set_helper_size(int hs); + virtual void set_use_distance(bool use_knot_distance); virtual void set_unit(const gchar *abbr); virtual void addCanvasIndicators(SPLPEItem const *lpeitem, std::vector &hp_vec); @@ -79,6 +85,7 @@ private: SPKnotModeType knot_mode; guint32 knot_color; int helper_size; + bool use_distance; const gchar *unit; Geom::PathVector hp; @@ -96,7 +103,6 @@ public: guint state); virtual Geom::Point knot_get() const; virtual void knot_click(guint state); - virtual void knot_doubleclicked(guint state); virtual void knot_set_offset(Geom::Point offset); /*Checks whether the index falls within the size of the parameter's vector*/ diff --git a/src/live_effects/parameter/originalpatharray.cpp b/src/live_effects/parameter/originalpatharray.cpp new file mode 100644 index 000000000..7706dbdf8 --- /dev/null +++ b/src/live_effects/parameter/originalpatharray.cpp @@ -0,0 +1,487 @@ +/* + * Copyright (C) Theodore Janeczko 2012 + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#if GLIBMM_DISABLE_DEPRECATED && HAVE_GLIBMM_THREADS_H +#include +#endif + +#include "live_effects/parameter/originalpatharray.h" + +#include +#include +#include +#include +#include + +#include + +#include "inkscape.h" +#include "icon-size.h" +#include "widgets/icon.h" +#include "ui/clipboard.h" +#include "svg/svg.h" +#include "svg/stringstream.h" +#include "originalpath.h" +#include "uri.h" +#include "display/curve.h" + +#include <2geom/coord.h> +#include <2geom/point.h> +#include "sp-shape.h" +#include "sp-text.h" +#include "live_effects/effect.h" + +#include "verbs.h" +#include "document-undo.h" +#include "document.h" + +namespace Inkscape { + +namespace LivePathEffect { + +class OriginalPathArrayParam::ModelColumns : public Gtk::TreeModel::ColumnRecord +{ +public: + + ModelColumns() + { + add(_colObject); + add(_colLabel); + add(_colReverse); + } + virtual ~ModelColumns() {} + + Gtk::TreeModelColumn _colObject; + Gtk::TreeModelColumn _colLabel; + Gtk::TreeModelColumn _colReverse; +}; + +OriginalPathArrayParam::OriginalPathArrayParam( const Glib::ustring& label, + const Glib::ustring& tip, + const Glib::ustring& key, + Inkscape::UI::Widget::Registry* wr, + Effect* effect ) +: Parameter(label, tip, key, wr, effect), + _vector(), + _tree(), + _text_renderer(), + _toggle_renderer(), + _scroller() +{ + _model = new ModelColumns(); + _store = Gtk::TreeStore::create(*_model); + _tree.set_model(_store); + + _tree.set_reorderable(true); + _tree.enable_model_drag_dest (Gdk::ACTION_MOVE); + + _text_renderer = manage(new Gtk::CellRendererText()); + int nameColNum = _tree.append_column(_("Name"), *_text_renderer) - 1; + _name_column = _tree.get_column(nameColNum); + _name_column->add_attribute(_text_renderer->property_text(), _model->_colLabel); + + _tree.set_expander_column( *_tree.get_column(nameColNum) ); + _tree.set_search_column(_model->_colLabel); + + Gtk::CellRendererToggle * _toggle_renderer = manage(new Gtk::CellRendererToggle()); + int toggleColNum = _tree.append_column(_("Reverse"), *_toggle_renderer) - 1; + Gtk::TreeViewColumn* col = _tree.get_column(toggleColNum); + _toggle_renderer->set_activatable(true); + _toggle_renderer->signal_toggled().connect(sigc::mem_fun(*this, &OriginalPathArrayParam::on_reverse_toggled)); + col->add_attribute(_toggle_renderer->property_active(), _model->_colReverse); + + //quick little hack -- newer versions of gtk gave the item zero space allotment + _scroller.set_size_request(-1, 120); + + _scroller.add(_tree); + _scroller.set_policy( Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC ); + //_scroller.set_shadow_type(Gtk::SHADOW_IN); + + oncanvas_editable = true; + +} + +OriginalPathArrayParam::~OriginalPathArrayParam() +{ + while (!_vector.empty()) { + PathAndDirection *w = _vector.back(); + _vector.pop_back(); + unlink(w); + delete w; + } + delete _model; +} + +void OriginalPathArrayParam::on_reverse_toggled(const Glib::ustring& path) +{ + Gtk::TreeModel::iterator iter = _store->get_iter(path); + Gtk::TreeModel::Row row = *iter; + PathAndDirection *w = row[_model->_colObject]; + row[_model->_colReverse] = !row[_model->_colReverse]; + w->reversed = row[_model->_colReverse]; + + gchar * full = param_getSVGValue(); + param_write_to_repr(full); + g_free(full); + DocumentUndo::done(param_effect->getSPDoc(), SP_VERB_DIALOG_LIVE_PATH_EFFECT, + _("Link path parameter to path")); +} + +void OriginalPathArrayParam::param_set_default() +{ + +} + +Gtk::Widget* OriginalPathArrayParam::param_newWidget() +{ + Gtk::VBox* vbox = Gtk::manage(new Gtk::VBox()); + Gtk::HBox* hbox = Gtk::manage(new Gtk::HBox()); + + vbox->pack_start(_scroller, Gtk::PACK_EXPAND_WIDGET); + + + { // Paste path to link button + Gtk::Widget *pIcon = Gtk::manage( sp_icon_get_icon( GTK_STOCK_PASTE, Inkscape::ICON_SIZE_BUTTON) ); + Gtk::Button *pButton = Gtk::manage(new Gtk::Button()); + pButton->set_relief(Gtk::RELIEF_NONE); + pIcon->show(); + pButton->add(*pIcon); + pButton->show(); + pButton->signal_clicked().connect(sigc::mem_fun(*this, &OriginalPathArrayParam::on_link_button_click)); + hbox->pack_start(*pButton, Gtk::PACK_SHRINK); + pButton->set_tooltip_text(_("Link to path")); + } + + { // Remove linked path + Gtk::Widget *pIcon = Gtk::manage( sp_icon_get_icon( GTK_STOCK_REMOVE, Inkscape::ICON_SIZE_BUTTON) ); + Gtk::Button *pButton = Gtk::manage(new Gtk::Button()); + pButton->set_relief(Gtk::RELIEF_NONE); + pIcon->show(); + pButton->add(*pIcon); + pButton->show(); + pButton->signal_clicked().connect(sigc::mem_fun(*this, &OriginalPathArrayParam::on_remove_button_click)); + hbox->pack_start(*pButton, Gtk::PACK_SHRINK); + pButton->set_tooltip_text(_("Remove Path")); + } + + { // Move Down + Gtk::Widget *pIcon = Gtk::manage( sp_icon_get_icon( GTK_STOCK_GO_DOWN, Inkscape::ICON_SIZE_BUTTON) ); + Gtk::Button *pButton = Gtk::manage(new Gtk::Button()); + pButton->set_relief(Gtk::RELIEF_NONE); + pIcon->show(); + pButton->add(*pIcon); + pButton->show(); + pButton->signal_clicked().connect(sigc::mem_fun(*this, &OriginalPathArrayParam::on_down_button_click)); + hbox->pack_end(*pButton, Gtk::PACK_SHRINK); + pButton->set_tooltip_text(_("Move Down")); + } + + { // Move Down + Gtk::Widget *pIcon = Gtk::manage( sp_icon_get_icon( GTK_STOCK_GO_UP, Inkscape::ICON_SIZE_BUTTON) ); + Gtk::Button *pButton = Gtk::manage(new Gtk::Button()); + pButton->set_relief(Gtk::RELIEF_NONE); + pIcon->show(); + pButton->add(*pIcon); + pButton->show(); + pButton->signal_clicked().connect(sigc::mem_fun(*this, &OriginalPathArrayParam::on_up_button_click)); + hbox->pack_end(*pButton, Gtk::PACK_SHRINK); + pButton->set_tooltip_text(_("Move Up")); + } + + vbox->pack_end(*hbox, Gtk::PACK_SHRINK); + + vbox->show_all_children(true); + + return vbox; +} + +bool OriginalPathArrayParam::_selectIndex(const Gtk::TreeIter& iter, int* i) +{ + if ((*i)-- <= 0) { + _tree.get_selection()->select(iter); + return true; + } + return false; +} + +void OriginalPathArrayParam::on_up_button_click() +{ + Gtk::TreeModel::iterator iter = _tree.get_selection()->get_selected(); + if (iter) { + Gtk::TreeModel::Row row = *iter; + + int i = -1; + std::vector::iterator piter = _vector.begin(); + for (std::vector::iterator iter = _vector.begin(); iter != _vector.end(); piter = iter, i++, iter++) { + if (*iter == row[_model->_colObject]) { + _vector.erase(iter); + _vector.insert(piter, row[_model->_colObject]); + break; + } + } + + gchar * full = param_getSVGValue(); + param_write_to_repr(full); + g_free(full); + + DocumentUndo::done(param_effect->getSPDoc(), SP_VERB_DIALOG_LIVE_PATH_EFFECT, + _("Move path up")); + + _store->foreach_iter(sigc::bind(sigc::mem_fun(*this, &OriginalPathArrayParam::_selectIndex), &i)); + } +} + +void OriginalPathArrayParam::on_down_button_click() +{ + Gtk::TreeModel::iterator iter = _tree.get_selection()->get_selected(); + if (iter) { + Gtk::TreeModel::Row row = *iter; + + int i = 0; + for (std::vector::iterator iter = _vector.begin(); iter != _vector.end(); i++, iter++) { + if (*iter == row[_model->_colObject]) { + std::vector::iterator niter = _vector.erase(iter); + if (niter != _vector.end()) { + niter++; + i++; + } + _vector.insert(niter, row[_model->_colObject]); + break; + } + } + + gchar * full = param_getSVGValue(); + param_write_to_repr(full); + g_free(full); + + DocumentUndo::done(param_effect->getSPDoc(), SP_VERB_DIALOG_LIVE_PATH_EFFECT, + _("Move path down")); + + _store->foreach_iter(sigc::bind(sigc::mem_fun(*this, &OriginalPathArrayParam::_selectIndex), &i)); + } +} + +void OriginalPathArrayParam::on_remove_button_click() +{ + Gtk::TreeModel::iterator iter = _tree.get_selection()->get_selected(); + if (iter) { + Gtk::TreeModel::Row row = *iter; + remove_link(row[_model->_colObject]); + + gchar * full = param_getSVGValue(); + param_write_to_repr(full); + g_free(full); + + DocumentUndo::done(param_effect->getSPDoc(), SP_VERB_DIALOG_LIVE_PATH_EFFECT, + _("Remove path")); + } + +} + +void +OriginalPathArrayParam::on_link_button_click() +{ + Inkscape::UI::ClipboardManager *cm = Inkscape::UI::ClipboardManager::get(); + Glib::ustring pathid = cm->getShapeOrTextObjectId(SP_ACTIVE_DESKTOP); + + if (pathid == "") { + return; + } + // add '#' at start to make it an uri. + pathid.insert(pathid.begin(), '#'); + + Inkscape::SVGOStringStream os; + bool foundOne = false; + for (std::vector::const_iterator iter = _vector.begin(); iter != _vector.end(); iter++) { + if (foundOne) { + os << "|"; + } else { + foundOne = true; + } + os << (*iter)->href << "," << ((*iter)->reversed ? "1" : "0"); + } + + if (foundOne) { + os << "|"; + } + + os << pathid.c_str() << ",0"; + + param_write_to_repr(os.str().c_str()); + DocumentUndo::done(param_effect->getSPDoc(), SP_VERB_DIALOG_LIVE_PATH_EFFECT, + _("Link path parameter to path")); +} + +void OriginalPathArrayParam::unlink(PathAndDirection* to) +{ + to->linked_modified_connection.disconnect(); + to->linked_delete_connection.disconnect(); + to->ref.detach(); + to->_pathvector = Geom::PathVector(); + if (to->href) { + g_free(to->href); + to->href = NULL; + } +} + +void OriginalPathArrayParam::remove_link(PathAndDirection* to) +{ + unlink(to); + for (std::vector::iterator iter = _vector.begin(); iter != _vector.end(); iter++) { + if (*iter == to) { + PathAndDirection *w = *iter; + _vector.erase(iter); + delete w; + return; + } + } +} + +void OriginalPathArrayParam::linked_delete(SPObject */*deleted*/, PathAndDirection* /*to*/) +{ + //remove_link(to); + + gchar * full = param_getSVGValue(); + param_write_to_repr(full); + g_free(full); +} + +bool OriginalPathArrayParam::_updateLink(const Gtk::TreeIter& iter, PathAndDirection* pd) +{ + Gtk::TreeModel::Row row = *iter; + if (row[_model->_colObject] == pd) { + SPObject *obj = pd->ref.getObject(); + row[_model->_colLabel] = obj && obj->getId() ? ( obj->label() ? obj->label() : obj->getId() ) : pd->href; + return true; + } + return false; +} + +void OriginalPathArrayParam::linked_changed(SPObject */*old_obj*/, SPObject *new_obj, PathAndDirection* to) +{ + to->linked_delete_connection.disconnect(); + to->linked_modified_connection.disconnect(); + to->linked_transformed_connection.disconnect(); + + if (new_obj && SP_IS_ITEM(new_obj)) { + to->linked_delete_connection = new_obj->connectDelete(sigc::bind(sigc::mem_fun(*this, &OriginalPathArrayParam::linked_delete), to)); + to->linked_modified_connection = new_obj->connectModified(sigc::bind(sigc::mem_fun(*this, &OriginalPathArrayParam::linked_modified), to)); + to->linked_transformed_connection = SP_ITEM(new_obj)->connectTransformed(sigc::bind(sigc::mem_fun(*this, &OriginalPathArrayParam::linked_transformed), to)); + + linked_modified(new_obj, SP_OBJECT_MODIFIED_FLAG, to); + } else { + to->_pathvector = Geom::PathVector(); + SP_OBJECT(param_effect->getLPEObj())->requestModified(SP_OBJECT_MODIFIED_FLAG); + _store->foreach_iter(sigc::bind(sigc::mem_fun(*this, &OriginalPathArrayParam::_updateLink), to)); + } +} + +void OriginalPathArrayParam::setPathVector(SPObject *linked_obj, guint /*flags*/, PathAndDirection* to) +{ + if (!to) { + return; + } + SPCurve *curve = NULL; + if (SP_IS_SHAPE(linked_obj)) { + curve = SP_SHAPE(linked_obj)->getCurveBeforeLPE(); + } + if (SP_IS_TEXT(linked_obj)) { + curve = SP_TEXT(linked_obj)->getNormalizedBpath(); + } + + if (curve == NULL) { + // curve invalid, set empty pathvector + to->_pathvector = Geom::PathVector(); + } else { + to->_pathvector = curve->get_pathvector(); + curve->unref(); + } +} + +void OriginalPathArrayParam::linked_modified(SPObject *linked_obj, guint flags, PathAndDirection* to) +{ + if (!to) { + return; + } + setPathVector(linked_obj, flags, to); + SP_OBJECT(param_effect->getLPEObj())->requestModified(SP_OBJECT_MODIFIED_FLAG); + _store->foreach_iter(sigc::bind(sigc::mem_fun(*this, &OriginalPathArrayParam::_updateLink), to)); +} + +bool OriginalPathArrayParam::param_readSVGValue(const gchar* strvalue) +{ + if (strvalue) { + while (!_vector.empty()) { + PathAndDirection *w = _vector.back(); + unlink(w); + _vector.pop_back(); + delete w; + } + _store->clear(); + + gchar ** strarray = g_strsplit(strvalue, "|", 0); + for (gchar ** iter = strarray; *iter != NULL; iter++) { + if ((*iter)[0] == '#') { + gchar ** substrarray = g_strsplit(*iter, ",", 0); + PathAndDirection* w = new PathAndDirection((SPObject *)param_effect->getLPEObj()); + w->href = g_strdup(*substrarray); + w->reversed = *(substrarray+1) != NULL && (*(substrarray+1))[0] == '1'; + + w->linked_changed_connection = w->ref.changedSignal().connect(sigc::bind(sigc::mem_fun(*this, &OriginalPathArrayParam::linked_changed), w)); + w->ref.attach(URI(w->href)); + + _vector.push_back(w); + + Gtk::TreeModel::iterator iter = _store->append(); + Gtk::TreeModel::Row row = *iter; + SPObject *obj = w->ref.getObject(); + + row[_model->_colObject] = w; + row[_model->_colLabel] = obj ? ( obj->label() ? obj->label() : obj->getId() ) : w->href; + row[_model->_colReverse] = w->reversed; + g_strfreev (substrarray); + } + } + g_strfreev (strarray); + return true; + } + return false; +} + +gchar * OriginalPathArrayParam::param_getSVGValue() const +{ + Inkscape::SVGOStringStream os; + bool foundOne = false; + for (std::vector::const_iterator iter = _vector.begin(); iter != _vector.end(); iter++) { + if (foundOne) { + os << "|"; + } else { + foundOne = true; + } + os << (*iter)->href << "," << ((*iter)->reversed ? "1" : "0"); + } + gchar * str = g_strdup(os.str().c_str()); + return str; +} + +} /* 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 : diff --git a/src/live_effects/parameter/originalpatharray.h b/src/live_effects/parameter/originalpatharray.h new file mode 100644 index 000000000..6c792613f --- /dev/null +++ b/src/live_effects/parameter/originalpatharray.h @@ -0,0 +1,122 @@ +#ifndef INKSCAPE_LIVEPATHEFFECT_PARAMETER_ORIGINALPATHARRAY_H +#define INKSCAPE_LIVEPATHEFFECT_PARAMETER_ORIGINALPATHARRAY_H + +/* + * Inkscape::LivePathEffectParameters + * + * Copyright (C) Theodore Janeczko 2012 + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include + +#include +#include +#include +#include + +#include "live_effects/parameter/parameter.h" +#include "live_effects/parameter/path-reference.h" + +#include "svg/svg.h" +#include "svg/stringstream.h" +#include "path-reference.h" +#include "sp-object.h" + +namespace Inkscape { + +namespace LivePathEffect { + +class PathAndDirection { +public: + PathAndDirection(SPObject *owner) + : href(NULL), + ref(owner), + _pathvector(Geom::PathVector()), + reversed(false) + { + + } + gchar *href; + URIReference ref; + std::vector _pathvector; + bool reversed; + + sigc::connection linked_changed_connection; + sigc::connection linked_delete_connection; + sigc::connection linked_modified_connection; + sigc::connection linked_transformed_connection; +}; + +class OriginalPathArrayParam : public Parameter { +public: + class ModelColumns; + + OriginalPathArrayParam( const Glib::ustring& label, + const Glib::ustring& tip, + const Glib::ustring& key, + Inkscape::UI::Widget::Registry* wr, + Effect* effect); + + virtual ~OriginalPathArrayParam(); + + virtual Gtk::Widget * param_newWidget(); + virtual bool param_readSVGValue(const gchar * strvalue); + virtual gchar * param_getSVGValue() const; + virtual void param_set_default(); + + /** Disable the canvas indicators of parent class by overriding this method */ + virtual void param_editOncanvas(SPItem * /*item*/, SPDesktop * /*dt*/) {}; + /** Disable the canvas indicators of parent class by overriding this method */ + virtual void addCanvasIndicators(SPLPEItem const* /*lpeitem*/, std::vector & /*hp_vec*/) {}; + + std::vector _vector; + +protected: + bool _updateLink(const Gtk::TreeIter& iter, PathAndDirection* pd); + bool _selectIndex(const Gtk::TreeIter& iter, int* i); + void unlink(PathAndDirection* to); + void remove_link(PathAndDirection* to); + void setPathVector(SPObject *linked_obj, guint flags, PathAndDirection* to); + + void linked_changed(SPObject *old_obj, SPObject *new_obj, PathAndDirection* to); + void linked_modified(SPObject *linked_obj, guint flags, PathAndDirection* to); + void linked_transformed(Geom::Affine const *, SPItem *, PathAndDirection*) {} + void linked_delete(SPObject *deleted, PathAndDirection* to); + + ModelColumns *_model; + Glib::RefPtr _store; + Gtk::TreeView _tree; + Gtk::CellRendererText *_text_renderer; + Gtk::CellRendererToggle *_toggle_renderer; + Gtk::TreeView::Column *_name_column; + Gtk::ScrolledWindow _scroller; + + void on_link_button_click(); + void on_remove_button_click(); + void on_up_button_click(); + void on_down_button_click(); + void on_reverse_toggled(const Glib::ustring& path); + +private: + OriginalPathArrayParam(const OriginalPathArrayParam&); + OriginalPathArrayParam& operator=(const OriginalPathArrayParam&); +}; + +} //namespace LivePathEffect + +} //namespace Inkscape + +#endif + +/* + 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 : diff --git a/src/live_effects/parameter/powerstrokepointarray.cpp b/src/live_effects/parameter/powerstrokepointarray.cpp index 647986da6..427be8065 100644 --- a/src/live_effects/parameter/powerstrokepointarray.cpp +++ b/src/live_effects/parameter/powerstrokepointarray.cpp @@ -4,8 +4,7 @@ * Released under GNU GPL, read the file 'COPYING' for more information */ -#include - +#include "ui/dialog/lpe-powerstroke-properties.h" #include "live_effects/parameter/powerstrokepointarray.h" #include "live_effects/effect.h" @@ -21,6 +20,8 @@ #include "desktop.h" #include "live_effects/lpeobject.h" +#include + namespace Inkscape { namespace LivePathEffect { @@ -102,6 +103,23 @@ PowerStrokePointArrayParam::recalculate_controlpoints_for_new_pwd2(Geom::Piecewi } } +float PowerStrokePointArrayParam::median_width() +{ + size_t size = _vector.size(); + if (size > 0) + { + if (size % 2 == 0) + { + return (_vector[size / 2 - 1].y() + _vector[size / 2].y()) / 2; + } + else + { + return _vector[size / 2].y(); + } + } + return 1; +} + void PowerStrokePointArrayParam::set_pwd2(Geom::Piecewise > const & pwd2_in, Geom::Piecewise > const & pwd2_normal_in) { @@ -117,7 +135,7 @@ PowerStrokePointArrayParam::set_oncanvas_looks(SPKnotShapeType shape, SPKnotMode knot_mode = mode; knot_color = color; } - +/* class PowerStrokePointArrayParamKnotHolderEntity : public KnotHolderEntity { public: PowerStrokePointArrayParamKnotHolderEntity(PowerStrokePointArrayParam *p, unsigned int index); @@ -127,7 +145,7 @@ public: virtual Geom::Point knot_get() const; virtual void knot_click(guint state); - /** Checks whether the index falls within the size of the parameter's vector */ + // Checks whether the index falls within the size of the parameter's vector bool valid_index(unsigned int index) const { return (_pparam->_vector.size() > index); }; @@ -135,7 +153,7 @@ public: private: PowerStrokePointArrayParam *_pparam; unsigned int _index; -}; +};*/ PowerStrokePointArrayParamKnotHolderEntity::PowerStrokePointArrayParamKnotHolderEntity(PowerStrokePointArrayParam *p, unsigned int index) : _pparam(p), @@ -184,6 +202,12 @@ PowerStrokePointArrayParamKnotHolderEntity::knot_get() const return canvas_point; } +void PowerStrokePointArrayParamKnotHolderEntity::knot_set_offset(Geom::Point offset) +{ + _pparam->_vector.at(_index) = Geom::Point(offset.x(), offset.y() / 2); + this->parent_holder->knot_ungrabbed_handler(this->knot, 0); +} + void PowerStrokePointArrayParamKnotHolderEntity::knot_click(guint state) { @@ -226,10 +250,15 @@ PowerStrokePointArrayParamKnotHolderEntity::knot_click(guint state) // add knot to knotholder PowerStrokePointArrayParamKnotHolderEntity *e = new PowerStrokePointArrayParamKnotHolderEntity(_pparam, _index+1); e->create( this->desktop, this->item, parent_holder, Inkscape::CTRL_TYPE_UNKNOWN, - _("Stroke width control point: drag to alter the stroke width. Ctrl+click adds a control point, Ctrl+Alt+click deletes it."), + _("Stroke width control point: drag to alter the stroke width. Ctrl+click adds a control point, Ctrl+Alt+click deletes it, Shift+click launches width dialog."), _pparam->knot_shape, _pparam->knot_mode, _pparam->knot_color); parent_holder->add(e); } + } + else if ((state & GDK_MOD1_MASK) || (state & GDK_SHIFT_MASK)) + { + Geom::Point offset = Geom::Point(_pparam->_vector.at(_index).x(), _pparam->_vector.at(_index).y() * 2); + Inkscape::UI::Dialogs::PowerstrokePropertiesDialog::showDialog(this->desktop, offset, this); } } @@ -238,7 +267,7 @@ void PowerStrokePointArrayParam::addKnotHolderEntities(KnotHolder *knotholder, S for (unsigned int i = 0; i < _vector.size(); ++i) { PowerStrokePointArrayParamKnotHolderEntity *e = new PowerStrokePointArrayParamKnotHolderEntity(this, i); e->create( desktop, item, knotholder, Inkscape::CTRL_TYPE_UNKNOWN, - _("Stroke width control point: drag to alter the stroke width. Ctrl+click adds a control point, Ctrl+Alt+click deletes it."), + _("Stroke width control point: drag to alter the stroke width. Ctrl+click adds a control point, Ctrl+Alt+click deletes it, Shift+click launches width dialog."), knot_shape, knot_mode, knot_color); knotholder->add(e); } diff --git a/src/live_effects/parameter/powerstrokepointarray.h b/src/live_effects/parameter/powerstrokepointarray.h index e1fa440f2..911bbc82d 100644 --- a/src/live_effects/parameter/powerstrokepointarray.h +++ b/src/live_effects/parameter/powerstrokepointarray.h @@ -20,8 +20,6 @@ namespace Inkscape { namespace LivePathEffect { -class PowerStrokePointArrayParamKnotHolderEntity; - class PowerStrokePointArrayParam : public ArrayParam { public: PowerStrokePointArrayParam( const Glib::ustring& label, @@ -37,6 +35,8 @@ public: void set_oncanvas_looks(SPKnotShapeType shape, SPKnotModeType mode, guint32 color); + float median_width(); + virtual bool providesKnotHolderEntities() const { return true; } virtual void addKnotHolderEntities(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item); @@ -60,6 +60,25 @@ private: Geom::Piecewise > last_pwd2_normal; }; +class PowerStrokePointArrayParamKnotHolderEntity : public KnotHolderEntity { +public: + PowerStrokePointArrayParamKnotHolderEntity(PowerStrokePointArrayParam *p, unsigned int index); + virtual ~PowerStrokePointArrayParamKnotHolderEntity() {} + + virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state); + virtual Geom::Point knot_get() const; + virtual void knot_set_offset(Geom::Point offset); + virtual void knot_click(guint state); + + /** Checks whether the index falls within the size of the parameter's vector */ + bool valid_index(unsigned int index) const { + return (_pparam->_vector.size() > index); + }; + +private: + PowerStrokePointArrayParam *_pparam; + unsigned int _index; +}; } //namespace LivePathEffect diff --git a/src/live_effects/parameter/transformedpoint.cpp b/src/live_effects/parameter/transformedpoint.cpp new file mode 100644 index 000000000..0d03432c3 --- /dev/null +++ b/src/live_effects/parameter/transformedpoint.cpp @@ -0,0 +1,182 @@ +/* + * Copyright (C) Theodore Janeczko 2012 + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "ui/widget/registered-widget.h" +#include "live_effects/parameter/transformedpoint.h" +#include "sp-lpe-item.h" +#include "knotholder.h" +#include "svg/svg.h" +#include "svg/stringstream.h" + +#include "live_effects/effect.h" +#include "desktop.h" +#include "verbs.h" + +#include + +namespace Inkscape { + +namespace LivePathEffect { + +TransformedPointParam::TransformedPointParam( const Glib::ustring& label, const Glib::ustring& tip, + const Glib::ustring& key, Inkscape::UI::Widget::Registry* wr, + Effect* effect, Geom::Point default_vector, + bool dontTransform) + : Parameter(label, tip, key, wr, effect), + defvalue(default_vector), + origin(0.,0.), + vector(default_vector), + noTransform(dontTransform) +{ + vec_knot_shape = SP_KNOT_SHAPE_DIAMOND; + vec_knot_mode = SP_KNOT_MODE_XOR; + vec_knot_color = 0xffffb500; +} + +TransformedPointParam::~TransformedPointParam() +{ + +} + +void +TransformedPointParam::param_set_default() +{ + setOrigin(Geom::Point(0.,0.)); + setVector(defvalue); +} + +bool +TransformedPointParam::param_readSVGValue(const gchar * strvalue) +{ + gchar ** strarray = g_strsplit(strvalue, ",", 4); + if (!strarray) { + return false; + } + double val[4]; + unsigned int i = 0; + while (i < 4 && strarray[i]) { + if (sp_svg_number_read_d(strarray[i], &val[i]) != 0) { + i++; + } else { + break; + } + } + g_strfreev (strarray); + if (i == 4) { + setOrigin( Geom::Point(val[0], val[1]) ); + setVector( Geom::Point(val[2], val[3]) ); + return true; + } + return false; +} + +gchar * +TransformedPointParam::param_getSVGValue() const +{ + Inkscape::SVGOStringStream os; + os << origin << " , " << vector; + gchar * str = g_strdup(os.str().c_str()); + return str; +} + +Gtk::Widget * +TransformedPointParam::param_newWidget() +{ + Inkscape::UI::Widget::RegisteredVector * pointwdg = Gtk::manage( + new Inkscape::UI::Widget::RegisteredVector( param_label, + param_tooltip, + param_key, + *param_wr, + param_effect->getRepr(), + param_effect->getSPDoc() ) ); + pointwdg->setPolarCoords(); + pointwdg->setValue( vector, origin ); + pointwdg->clearProgrammatically(); + pointwdg->set_undo_parameters(SP_VERB_DIALOG_LIVE_PATH_EFFECT, _("Change vector parameter")); + + Gtk::HBox * hbox = Gtk::manage( new Gtk::HBox() ); + static_cast(hbox)->pack_start(*pointwdg, true, true); + static_cast(hbox)->show_all_children(); + + return dynamic_cast (hbox); +} + +void +TransformedPointParam::set_and_write_new_values(Geom::Point const &new_origin, Geom::Point const &new_vector) +{ + setValues(new_origin, new_vector); + gchar * str = param_getSVGValue(); + param_write_to_repr(str); + g_free(str); +} + +void +TransformedPointParam::param_transform_multiply(Geom::Affine const& postmul, bool /*set*/) +{ + if (!noTransform) { + set_and_write_new_values( origin * postmul, vector * postmul.withoutTranslation() ); + } +} + + +void +TransformedPointParam::set_vector_oncanvas_looks(SPKnotShapeType shape, SPKnotModeType mode, guint32 color) +{ + vec_knot_shape = shape; + vec_knot_mode = mode; + vec_knot_color = color; +} + +void +TransformedPointParam::set_oncanvas_color(guint32 color) +{ + vec_knot_color = color; +} + +class TransformedPointParamKnotHolderEntity_Vector : public KnotHolderEntity { +public: + TransformedPointParamKnotHolderEntity_Vector(TransformedPointParam *p) : param(p) { } + virtual ~TransformedPointParamKnotHolderEntity_Vector() {} + + virtual void knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint /*state*/) { + Geom::Point const s = p - param->origin; + /// @todo implement angle snapping when holding CTRL + param->setVector(s); + sp_lpe_item_update_patheffect(SP_LPE_ITEM(item), false, false); + }; + virtual Geom::Point knot_get() const{ + return param->origin + param->vector; + }; + virtual void knot_click(guint /*state*/){ + g_print ("This is the vector handle associated to parameter '%s'\n", param->param_key.c_str()); + }; + +private: + TransformedPointParam *param; +}; + +void +TransformedPointParam::addKnotHolderEntities(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item) +{ + TransformedPointParamKnotHolderEntity_Vector *vector_e = new TransformedPointParamKnotHolderEntity_Vector(this); + vector_e->create(desktop, item, knotholder, Inkscape::CTRL_TYPE_UNKNOWN, handleTip(), vec_knot_shape, vec_knot_mode, vec_knot_color); + knotholder->add(vector_e); +} + +} /* 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 : diff --git a/src/live_effects/parameter/transformedpoint.h b/src/live_effects/parameter/transformedpoint.h new file mode 100644 index 000000000..c96bedb53 --- /dev/null +++ b/src/live_effects/parameter/transformedpoint.h @@ -0,0 +1,82 @@ +#ifndef INKSCAPE_LIVEPATHEFFECT_PARAMETER_TRANSFORMED_POINT_H +#define INKSCAPE_LIVEPATHEFFECT_PARAMETER_TRANSFORMED_POINT_H + +/* + * Inkscape::LivePathEffectParameters + * + * Copyright (C) Theodore Janeczko 2012 + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include <2geom/point.h> + +#include "live_effects/parameter/parameter.h" + +#include "knot-holder-entity.h" + +namespace Inkscape { + +namespace LivePathEffect { + + +class TransformedPointParam : public Parameter { +public: + TransformedPointParam( const Glib::ustring& label, + const Glib::ustring& tip, + const Glib::ustring& key, + Inkscape::UI::Widget::Registry* wr, + Effect* effect, + Geom::Point default_vector = Geom::Point(1,0), + bool dontTransform = false); + virtual ~TransformedPointParam(); + + virtual Gtk::Widget * param_newWidget(); + inline const gchar *handleTip() const { return param_tooltip.c_str(); } + + virtual bool param_readSVGValue(const gchar * strvalue); + virtual gchar * param_getSVGValue() const; + + Geom::Point getVector() const { return vector; }; + Geom::Point getOrigin() const { return origin; }; + void setValues(Geom::Point const &new_origin, Geom::Point const &new_vector) { setVector(new_vector); setOrigin(new_origin); }; + void setVector(Geom::Point const &new_vector) { vector = new_vector; }; + void setOrigin(Geom::Point const &new_origin) { origin = new_origin; }; + virtual void param_set_default(); + + void set_and_write_new_values(Geom::Point const &new_origin, Geom::Point const &new_vector); + + virtual void param_transform_multiply(Geom::Affine const &postmul, bool set); + + void set_vector_oncanvas_looks(SPKnotShapeType shape, SPKnotModeType mode, guint32 color); + void set_oncanvas_color(guint32 color); + + virtual bool providesKnotHolderEntities() const { return true; } + virtual void addKnotHolderEntities(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item); + +private: + TransformedPointParam(const TransformedPointParam&); + TransformedPointParam& operator=(const TransformedPointParam&); + + Geom::Point defvalue; + + Geom::Point origin; + Geom::Point vector; + + bool noTransform; + + /// The looks of the vector and origin knots oncanvas + SPKnotShapeType vec_knot_shape; + SPKnotModeType vec_knot_mode; + guint32 vec_knot_color; + + friend class TransformedPointParamKnotHolderEntity_Vector; +}; + + +} //namespace LivePathEffect + +} //namespace Inkscape + +#endif diff --git a/src/live_effects/pathoutlineprovider.cpp b/src/live_effects/pathoutlineprovider.cpp new file mode 100644 index 000000000..21a0fb809 --- /dev/null +++ b/src/live_effects/pathoutlineprovider.cpp @@ -0,0 +1,803 @@ +/* Author: + * Liam P. White + * + * Copyright (C) 2014 Author + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include <2geom/angle.h> +#include <2geom/path.h> +#include <2geom/circle.h> +#include <2geom/sbasis-to-bezier.h> +#include <2geom/shape.h> +#include <2geom/transforms.h> +#include <2geom/path-sink.h> +#include + +#include "pathoutlineprovider.h" +#include "livarot/path-description.h" +#include "helper/geom-nodetype.h" +#include "svg/svg.h" + +namespace Geom { +/** +* Refer to: Weisstein, Eric W. "Circle-Circle Intersection." + From MathWorld--A Wolfram Web Resource. + http://mathworld.wolfram.com/Circle-CircleIntersection.html +* +* @return 0 if no intersection +* @return 1 if one circle is contained in the other +* @return 2 if intersections are found (they are written to p0 and p1) +*/ +static int circle_circle_intersection(Circle const &circle0, Circle const &circle1, Point & p0, Point & p1) +{ + Point X0 = circle0.center(); + double r0 = circle0.ray(); + Point X1 = circle1.center(); + double r1 = circle1.ray(); + + /* dx and dy are the vertical and horizontal distances between + * the circle centers. + */ + Point D = X1 - X0; + + /* Determine the straight-line distance between the centers. */ + double d = L2(D); + + /* Check for solvability. */ + if (d > (r0 + r1)) { + /* no solution. circles do not intersect. */ + return 0; + } + if (d <= fabs(r0 - r1)) { + /* no solution. one circle is contained in the other */ + return 1; + } + + /* 'point 2' is the point where the line through the circle + * intersection points crosses the line between the circle + * centers. + */ + + /* Determine the distance from point 0 to point 2. */ + double a = ((r0*r0) - (r1*r1) + (d*d)) / (2.0 * d) ; + + /* Determine the coordinates of point 2. */ + Point p2 = X0 + D * (a/d); + + /* Determine the distance from point 2 to either of the + * intersection points. + */ + double h = std::sqrt((r0*r0) - (a*a)); + + /* Now determine the offsets of the intersection points from + * point 2. + */ + Point r = (h/d)*rot90(D); + + /* Determine the absolute intersection points. */ + p0 = p2 + r; + p1 = p2 - r; + + return 2; +} +/** +* Find circle that touches inside of the curve, with radius matching the curvature, at time value \c t. +* Because this method internally uses unitTangentAt, t should be smaller than 1.0 (see unitTangentAt). +*/ +static Circle touching_circle( D2 const &curve, double t, double tol=0.01 ) +{ + D2 dM=derivative(curve); + if ( are_near(L2sq(dM(t)),0.) ) { + dM=derivative(dM); + } + if ( are_near(L2sq(dM(t)),0.) ) { // try second time + dM=derivative(dM); + } + Piecewise > unitv = unitVector(dM,tol); + Piecewise dMlength = dot(Piecewise >(dM),unitv); + Piecewise k = cross(derivative(unitv),unitv); + k = divide(k,dMlength,tol,3); + double curv = k(t); // note that this value is signed + + Geom::Point normal = unitTangentAt(curve, t).cw(); + double radius = 1/curv; + Geom::Point center = curve(t) + radius*normal; + return Geom::Circle(center, fabs(radius)); +} + +std::vector split_at_cusps(const Geom::Path& in) +{ + PathVector out = PathVector(); + Path temp = Path(); + + for (unsigned i = 0; i < in.size(); i++) { + temp.append(in[i]); + if ( get_nodetype(in[i], in[i + 1]) != Geom::NODE_SMOOTH ) { + out.push_back(temp); + temp = Path(); + } + } + if (temp.size() > 0) { + out.push_back(temp); + } + return out; +} + +Geom::CubicBezier sbasis_to_cubicbezier(Geom::D2 const & sbasis_in) +{ + std::vector temp; + sbasis_to_bezier(temp, sbasis_in, 4); + return Geom::CubicBezier( temp ); +} + +static boost::optional intersection_point(Geom::Point const & origin_a, Geom::Point const & vector_a, Geom::Point const & origin_b, Geom::Point const & vector_b) +{ + Geom::Coord denom = cross(vector_b, vector_a); + if (!Geom::are_near(denom,0.)) { + Geom::Coord t = (cross(origin_a,vector_b) + cross(vector_b,origin_b)) / denom; + return origin_a + t * vector_a; + } + return boost::none; +} + +} // namespace Geom + +namespace Outline { + +typedef Geom::D2 D2SB; +typedef Geom::Piecewise PWD2; + +// UTILITY + +unsigned bezierOrder (const Geom::Curve* curve_in) +{ + using namespace Geom; + if ( const BezierCurve* bz = dynamic_cast(curve_in) ) { + return bz->order(); + } + return 0; +} + +/** + * @return true if the angle formed by the curves and their handles is greater than 180 degrees clockwise, otherwise false. + */ +bool outside_angle (const Geom::Curve& cbc1, const Geom::Curve& cbc2) +{ + Geom::Point start_point; + Geom::Point cross_point = cbc1.finalPoint(); + Geom::Point end_point; + + if (cross_point != cbc2.initialPoint()) { + printf("WARNING: Non-contiguous path in Outline::outside_angle()"); + return false; + } + + Geom::CubicBezier cubicBezier = Geom::sbasis_to_cubicbezier(cbc1.toSBasis()); + start_point = cubicBezier [2]; + + /* + * Because the node editor does not yet support true quadratics, paths are converted to + * cubic beziers in the node tool with degenerate handles on one side. + */ + + if (are_near(start_point, cross_point, 0.0000001)) { + start_point = cubicBezier [1]; + } + cubicBezier = Geom::sbasis_to_cubicbezier(cbc2.toSBasis()); + end_point = cubicBezier [1]; + if (are_near(end_point, cross_point, 0.0000001)) { + end_point = cubicBezier [2]; + } + + // got our three points, now let's see what their clockwise angle is + + // Definition of a Graham scan + + /******************************************************************** + # Three points are a counter-clockwise turn if ccw > 0, clockwise if + # ccw < 0, and collinear if ccw = 0 because ccw is a determinant that + # gives the signed area of the triangle formed by p1, p2 and p3. + function ccw(p1, p2, p3): + return (p2.x - p1.x)*(p3.y - p1.y) - (p2.y - p1.y)*(p3.x - p1.x) + *********************************************************************/ + + double ccw = ( (cross_point.x() - start_point.x()) * (end_point.y() - start_point.y()) ) - + ( (cross_point.y() - start_point.y()) * (end_point.x() - start_point.x()) ); + return ccw > 0; +} + +// LINE JOINS + +typedef Geom::BezierCurveN<1u> BezierLine; + +/** + * Removes the crossings on an interior join. + * @param path_builder Contains the incoming segment; result is appended to this + * @param outgoing The outgoing segment + */ +void joinInside(Geom::Path& path_builder, Geom::Curve const& outgoing) +{ + Geom::Curve const& incoming = path_builder.back(); + + // Using Geom::crossings to find intersections between two curves + Geom::Crossings cross = Geom::crossings(incoming, outgoing); + if (!cross.empty()) { + // Crossings found, create the join + Geom::CubicBezier cubic = Geom::sbasis_to_cubicbezier(incoming.toSBasis()); + cubic = cubic.subdivide(cross[0].ta).first; + // erase the last segment, as we're going to overwrite it now + path_builder.erase_last(); + path_builder.append(cubic, Geom::Path::STITCH_DISCONTINUOUS); + + cubic = Geom::sbasis_to_cubicbezier(outgoing.toSBasis()); + cubic = cubic.subdivide(cross[0].tb).second; + path_builder.append(cubic, Geom::Path::STITCH_DISCONTINUOUS); + } else { + // No crossings occurred, or Geom::crossings() failed; default to bevel + if (Geom::are_near(incoming.finalPoint(), outgoing.initialPoint())) { + path_builder.appendNew(outgoing.initialPoint()); + } else { + path_builder.setFinal(outgoing.initialPoint()); + } + } +} + +/** + * Try to create a miter join. Falls back to bevel if no miter can be created. + * @param path_builder Path to append curves to; back() is the incoming curve + * @param outgoing Outgoing curve. + * @param miter_limit When mitering, don't exceed this length + * @param line_width The thickness of the line. + */ +void miter_curves(Geom::Path& path_builder, Geom::Curve const& outgoing, double miter_limit, double line_width) +{ + using namespace Geom; + Curve const& incoming = path_builder.back(); + Point tang1 = unitTangentAt(Geom::reverse(incoming.toSBasis()), 0.); + Point tang2 = unitTangentAt(outgoing.toSBasis(), 0); + + boost::optional p = intersection_point (incoming.finalPoint(), tang1, outgoing.initialPoint(), tang2); + if (p) { + // check size of miter + Point point_on_path = incoming.finalPoint() - rot90(tang1) * line_width; + Coord len = distance(*p, point_on_path); + if (len <= miter_limit) { + // miter OK + path_builder.appendNew(*p); + } + } + path_builder.appendNew(outgoing.initialPoint()); +} + +/** + * Smoothly extrapolate curves along a circular route. Falls back to miter if necessary. + * @param path_builder Path to append curves to; back() is the incoming curve + * @param outgoing Outgoing curve. + * @param miter_limit When mitering, don't exceed this length + * @param line_width The thickness of the line. Used for miter fallback. + */ +void extrapolate_curves(Geom::Path& path_builder, Geom::Curve const& outgoing, double miter_limit, double line_width) +{ + Geom::Curve const& incoming = path_builder.back(); + Geom::Point endPt = outgoing.initialPoint(); + + // The method used when extrapolating curves fails to work when either side of the join to be extrapolated + // is a line segment. When this situation is encountered, fall back to a regular miter join. + bool lineProblem = (dynamic_cast(&incoming)) || (dynamic_cast(&outgoing)); + if (lineProblem == false) { + // Geom::Point tang1 = Geom::unitTangentAt(Geom::reverse(incoming.toSBasis()), 0.); + Geom::Point tang2 = Geom::unitTangentAt(outgoing.toSBasis(), 0); + + Geom::Circle circle1 = Geom::touching_circle(Geom::reverse(incoming.toSBasis()), 0.); + Geom::Circle circle2 = Geom::touching_circle(outgoing.toSBasis(), 0); + + Geom::Point points[2]; + int solutions = Geom::circle_circle_intersection(circle1, circle2, points[0], points[1]); + if (solutions == 2) { + Geom::Point sol(0,0); + if ( dot(tang2,points[0]-endPt) > 0 ) { + // points[0] is bad, choose points[1] + sol = points[1]; + } else if ( dot(tang2,points[1]-endPt) > 0 ) { // points[0] could be good, now check points[1] + // points[1] is bad, choose points[0] + sol = points[0]; + } else { + // both points are good, choose nearest + sol = ( distanceSq(endPt, points[0]) < distanceSq(endPt, points[1]) ) ? points[0] : points[1]; + } + + Geom::EllipticalArc *arc0 = circle1.arc(incoming.finalPoint(), 0.5*(incoming.finalPoint()+sol), sol, true); + Geom::EllipticalArc *arc1 = circle2.arc(sol, 0.5*(sol+endPt), endPt, true); + try { + if (arc0) { + path_builder.append (arc0->toSBasis()); + delete arc0; + arc0 = NULL; + } else { + throw std::exception(); + } + + if (arc1) { + path_builder.append (arc1->toSBasis()); + delete arc1; + arc1 = NULL; + } else { + throw std::exception(); + } + + } catch (std::exception const & ex) { + printf("WARNING: Error extrapolating line join: %s\n", ex.what()); + path_builder.appendNew(endPt); + } + } else { + // 1 or no solutions found, default to miter + miter_curves(path_builder, outgoing, miter_limit, line_width); + } + } else { + // Line segments exist + miter_curves(path_builder, outgoing, miter_limit, line_width); + } +} + +/** + * Extrapolate curves by reflecting them along the line that would be given by beveling the join. + * @param path_builder Path to append curves to; back() is the incoming curve + * @param outgoing Outgoing curve. + * @param miter_limit When mitering, don't exceed this length + * @param line_width The thickness of the line. Used for miter fallback. + */ +void reflect_curves(Geom::Path& path_builder, Geom::Curve const& outgoing, double miter_limit, double line_width) +{ + using namespace Geom; + Curve const& incoming = path_builder.back(); + // On the outside, we'll take the incoming curve, the outgoing curve, and + // reflect them over the line formed by taking the unit tangent vector at times + // 0 and 1, respectively, rotated by 90 degrees. + Crossings cross; + + // reflect curves along the line that would be given by beveling the join + Point tang1 = unitTangentAt(reverse(incoming.toSBasis()), 0.); + D2SB newcurve1 = incoming.toSBasis() * reflection(-rot90(tang1), incoming.finalPoint()); + CubicBezier bzr1 = sbasis_to_cubicbezier(reverse(newcurve1)); + + Point tang2 = Geom::unitTangentAt(outgoing.toSBasis(), 0.); + D2SB newcurve2 = outgoing.toSBasis() * reflection(-rot90(tang2), outgoing.initialPoint()); + CubicBezier bzr2 = sbasis_to_cubicbezier(reverse(newcurve2)); + + cross = crossings(bzr1, bzr2); + if (cross.empty()) { + // paths don't cross, fall back to miter + miter_curves(path_builder, outgoing, miter_limit, line_width); + } else { + // reflected join + std::pair sub1 = bzr1.subdivide(cross[0].ta); + std::pair sub2 = bzr2.subdivide(cross[0].tb); + + // TODO it seems as if a bug in 2geom sometimes doesn't catch the first + // crossing of paths, but the second instead; but only sometimes. + path_builder.appendNew (sub1.first[1], sub1.first[2], sub2.second[0]); + path_builder.appendNew (sub2.second[1], sub2.second[2], outgoing.initialPoint()); + } +} + +// Ideal function pointer we want to pass +typedef void JoinFunc(Geom::Path& /*path_builder*/, Geom::Curve const& /*outgoing*/, double /*miter_limit*/, double /*line_width*/); + +/** + * Helper function for repeated logic in outlineHalf. + */ +static void outlineHelper(Geom::Path& path_builder, Geom::PathVector* path_vec, bool outside, double width, double miter, JoinFunc func) +{ + Geom::Curve * cbc2 = path_vec->front()[0].duplicate(); + + if (outside) { + func(path_builder, *cbc2, miter, width); + } else { + joinInside(path_builder, *cbc2); + } + + // store it + Geom::Path temp_path = path_vec->front(); + if (!outside) { + // erase the first segment since the inside join code already appended it + temp_path.erase(temp_path.begin()); + } + + if (temp_path.initialPoint() != path_builder.finalPoint()) { + temp_path.setInitial(path_builder.finalPoint()); + } + + path_builder.append(temp_path); + + delete cbc2; +} + +/** + * Offsets exactly one half of a bezier spline (path). + * @param path_in The input path to use. (To create the other side use path_in.reverse() ) + * @param line_width the line width to use (usually you want to divide this by 2) + * @param miter_limit the miter parameter + * @param func Join function to apply at each join. + */ + +Geom::Path outlineHalf(const Geom::Path& path_in, double line_width, double miter_limit, JoinFunc func) +{ + // NOTE: it is important to notice the distinction between a Geom::Path and a livarot ::Path here! + // if you do not see "Geom::" there is a different function set! + + Geom::PathVector pv = split_at_cusps(path_in); + + ::Path to_outline; + ::Path outlined_result; + + Geom::Path path_builder = Geom::Path(); // the path to store the result in + Geom::PathVector* path_vec; // needed because livarot returns a pointer (TODO make this not a pointer) + + // Do two curves at a time for efficiency, since the join function needs to know the outgoing curve as well + const size_t k = pv.size(); + for (size_t u = 0; u < k; u += 2) { + to_outline = Path(); + outlined_result = Path(); + + to_outline.LoadPath(pv[u], Geom::identity(), false, false); + to_outline.OutsideOutline(&outlined_result, line_width / 2, join_straight, butt_straight, 10); + // now a curve has been outside outlined and loaded into outlined_result + + // get the Geom::Path + path_vec = outlined_result.MakePathVector(); + + // on the first run through, there is no join + if (u == 0) { + path_builder.start(path_vec->front().initialPoint()); + path_builder.append(path_vec->front()); + } else { + outlineHelper(path_builder, path_vec, outside_angle(pv[u-1][pv[u-1].size()-1], pv[u][0]), line_width, miter_limit, func); + } + + // outline the next segment, but don't store it yet + if (path_vec) + delete path_vec; + path_vec = NULL; + + // odd number of paths + if (u < k - 1) { + outlined_result = Path(); + to_outline = Path(); + + to_outline.LoadPath(pv[u+1], Geom::Affine(), false, false); + to_outline.OutsideOutline(&outlined_result, line_width / 2, join_straight, butt_straight, 10); + + path_vec = outlined_result.MakePathVector(); + outlineHelper(path_builder, path_vec, outside_angle(pv[u][pv[u].size()-1], pv[u+1][0]), line_width, miter_limit, func); + + if (path_vec) + delete path_vec; + path_vec = NULL; + } + } + + if (path_in.closed()) { + Geom::Curve * cbc1; + Geom::Curve * cbc2; + + if ( path_in[path_in.size()].isDegenerate() ) { + // handle case for last segment curved + outlined_result = Path(); + to_outline = Path(); + + Geom::Path oneCurve; oneCurve.append(path_in[0]); + + to_outline.LoadPath(oneCurve, Geom::Affine(), false, false); + to_outline.OutsideOutline(&outlined_result, line_width / 2, join_straight, butt_straight, 10); + + path_vec = outlined_result.MakePathVector(); + + cbc1 = path_builder[path_builder.size() - 1].duplicate(); + cbc2 = path_vec->front()[0].duplicate(); + + delete path_vec; + } else { + // handle case for last segment straight + // since the path doesn't actually give us access to it, we'll do it ourselves + outlined_result = Path(); + to_outline = Path(); + + Geom::Path oneCurve; oneCurve.append(Geom::LineSegment(path_in.finalPoint(), path_in.initialPoint())); + + to_outline.LoadPath(oneCurve, Geom::Affine(), false, false); + to_outline.OutsideOutline(&outlined_result, line_width / 2, join_straight, butt_straight, 10); + + path_vec = outlined_result.MakePathVector(); + + cbc1 = path_builder[path_builder.size() - 1].duplicate(); + cbc2 = (*path_vec)[0] [0].duplicate(); + + outlineHelper(path_builder, path_vec, outside_angle(path_in[path_in.size()-1], oneCurve[0]), line_width, miter_limit, func); + + delete cbc1; + cbc1 = cbc2->duplicate(); + delete path_vec; + + oneCurve = Geom::Path(); oneCurve.append(path_in[0]); + + to_outline.LoadPath(oneCurve, Geom::Affine(), false, false); + to_outline.OutsideOutline(&outlined_result, line_width / 2, join_straight, butt_straight, 10); + + path_vec = outlined_result.MakePathVector(); + delete cbc2; cbc2 = (*path_vec)[0] [0].duplicate(); + delete path_vec; + } + + Geom::Path temporary; + temporary.append(*cbc1); + + Geom::Curve const & prev_curve = path_in[path_in.size()].isDegenerate() ? path_in[path_in.size() - 1] : path_in[path_in.size()]; + Geom::Path isStraight; + isStraight.append(prev_curve); + isStraight.append(path_in[0]); + // does closing path require a join? + if (Geom::split_at_cusps(isStraight).size() > 1) { + bool outside = outside_angle(prev_curve, path_in[0]); + if (outside) { + func(temporary, *cbc2, miter_limit, line_width); + } else { + joinInside(temporary, *cbc2); + path_builder.erase(path_builder.begin()); + } + + // extract the appended curves + path_builder.erase_last(); + if (Geom::are_near(path_builder.finalPoint(), temporary.initialPoint())) { + path_builder.setFinal(temporary.initialPoint()); + } else { + path_builder.appendNew(temporary.initialPoint()); + } + path_builder.append(temporary); + } else { + // closing path does not require a join + path_builder.setFinal(path_builder.initialPoint()); + } + path_builder.close(); + + if (cbc1) delete cbc1; + if (cbc2) delete cbc2; + } + + return path_builder; +} + +Geom::PathVector outlinePath(const Geom::PathVector& path_in, double line_width, LineJoinType join, ButtTypeMod butt, double miter_lim, bool extrapolate, double start_lean, double end_lean) +{ + Geom::PathVector path_out; + + unsigned pv_size = path_in.size(); + for (unsigned i = 0; i < pv_size; i++) { + + if (path_in[i].size() > 1) { + Geom::Path with_direction; + Geom::Path against_direction; + + with_direction = Outline::outlineHalf(path_in[i], -line_width, miter_lim, extrapolate ? extrapolate_curves : reflect_curves); + against_direction = Outline::outlineHalf(path_in[i].reverse(), -line_width, miter_lim, extrapolate ? extrapolate_curves : reflect_curves); + + Geom::PathBuilder pb; + + pb.moveTo(with_direction.initialPoint()); + pb.append(with_direction); + + //add in our line caps + if (!path_in[i].closed()) { + switch (butt) { + case BUTT_STRAIGHT: + pb.lineTo(against_direction.initialPoint()); + break; + case BUTT_ROUND: + pb.arcTo((-line_width) / 2, (-line_width) / 2, 0., true, true, against_direction.initialPoint() ); + break; + case BUTT_POINTY: { + Geom::Point end_deriv = -Geom::unitTangentAt(Geom::reverse(path_in[i].back().toSBasis()), 0.); + double radius = 0.5 * Geom::distance(with_direction.finalPoint(), against_direction.initialPoint()); + Geom::Point midpoint = 0.5 * (with_direction.finalPoint() + against_direction.initialPoint()) + radius*end_deriv; + pb.lineTo(midpoint); + pb.lineTo(against_direction.initialPoint()); + break; + } + case BUTT_SQUARE: { + Geom::Point end_deriv = -Geom::unitTangentAt(Geom::reverse(path_in[i].back().toSBasis()), 0.); + double radius = 0.5 * Geom::distance(with_direction.finalPoint(), against_direction.initialPoint()); + pb.lineTo(with_direction.finalPoint() + radius*end_deriv); + pb.lineTo(against_direction.initialPoint() + radius*end_deriv); + pb.lineTo(against_direction.initialPoint()); + break; + } + case BUTT_LEANED: { + Geom::Point end_deriv = -Geom::unitTangentAt(Geom::reverse(path_in[i].back().toSBasis()), 0.); + double maxRadius = (end_lean+0.5) * Geom::distance(with_direction.finalPoint(), against_direction.initialPoint()); + double minRadius = ((end_lean*-1)+0.5) * Geom::distance(with_direction.finalPoint(), against_direction.initialPoint()); + pb.lineTo(with_direction.finalPoint() + maxRadius*end_deriv); + pb.lineTo(against_direction.initialPoint() + minRadius*end_deriv); + pb.lineTo(against_direction.initialPoint()); + break; + } + } + } else { + pb.moveTo(against_direction.initialPoint()); + } + + pb.append(against_direction); + + //cap (if necessary) + if (!path_in[i].closed()) { + switch (butt) { + case BUTT_STRAIGHT: + pb.lineTo(with_direction.initialPoint()); + break; + case BUTT_ROUND: + pb.arcTo((-line_width) / 2, (-line_width) / 2, 0., true, true, with_direction.initialPoint() ); + break; + case BUTT_POINTY: { + Geom::Point end_deriv = -Geom::unitTangentAt(path_in[i].front().toSBasis(), 0.); + double radius = 0.5 * Geom::distance(against_direction.finalPoint(), with_direction.initialPoint()); + Geom::Point midpoint = 0.5 * (against_direction.finalPoint() + with_direction.initialPoint()) + radius*end_deriv; + pb.lineTo(midpoint); + pb.lineTo(with_direction.initialPoint()); + break; + } + case BUTT_SQUARE: { + Geom::Point end_deriv = -Geom::unitTangentAt(path_in[i].front().toSBasis(), 0.); + double radius = 0.5 * Geom::distance(against_direction.finalPoint(), with_direction.initialPoint()); + pb.lineTo(against_direction.finalPoint() + radius*end_deriv); + pb.lineTo(with_direction.initialPoint() + radius*end_deriv); + pb.lineTo(with_direction.initialPoint()); + break; + } + case BUTT_LEANED: { + Geom::Point end_deriv = -Geom::unitTangentAt(path_in[i].front().toSBasis(), 0.); + double maxRadius = (start_lean+0.5) * Geom::distance(against_direction.finalPoint(), with_direction.initialPoint()); + double minRadius = ((start_lean*-1)+0.5) * Geom::distance(against_direction.finalPoint(), with_direction.initialPoint()); + pb.lineTo(against_direction.finalPoint() + minRadius*end_deriv); + pb.lineTo(with_direction.initialPoint() + maxRadius*end_deriv); + pb.lineTo(with_direction.initialPoint()); + break; + } + } + } + pb.flush(); + path_out.push_back(pb.peek()[0]); + if (path_in[i].closed()) { + path_out.push_back(pb.peek()[1]); + } + } else { + Path p = Path(); + Path outlinepath = Path(); + ButtType original_butt; + switch (butt) { + case BUTT_STRAIGHT: + original_butt = butt_straight; + break; + case BUTT_ROUND: + original_butt = butt_round; + break; + case butt_pointy: { + original_butt = butt_pointy; + break; + } + case BUTT_SQUARE: { + original_butt = butt_square; + break; + } + case BUTT_LEANED: { + original_butt = butt_straight; + break; + } + } + p.LoadPath(path_in[i], Geom::Affine(), false, false); + p.Outline(&outlinepath, line_width / 2, static_cast(join), original_butt, miter_lim); + Geom::PathVector *pv_p = outlinepath.MakePathVector(); + //somewhat hack-ish + path_out.push_back( (*pv_p)[0].reverse() ); + if (pv_p) delete pv_p; + } + } + return path_out; +} + +#define miter_lim fabs(line_width * miter_limit) + +Geom::PathVector PathVectorOutline(Geom::PathVector const & path_in, double line_width, ButtTypeMod linecap_type, LineJoinType linejoin_type, double miter_limit, double start_lean, double end_lean) +{ + std::vector path_out = std::vector(); + if (path_in.empty()) { + return path_out; + } + Path p = Path(); + Path outlinepath = Path(); + for (unsigned i = 0; i < path_in.size(); i++) { + p.LoadPath(path_in[i], Geom::Affine(), false, ( (i==0) ? false : true)); + } + + // magic! + ButtType original_butt; + switch (linecap_type) { + case BUTT_STRAIGHT: + original_butt = butt_straight; + break; + case BUTT_ROUND: + original_butt = butt_round; + break; + case butt_pointy: { + original_butt = butt_pointy; + break; + } + case BUTT_SQUARE: { + original_butt = butt_square; + break; + } + case BUTT_LEANED: { + original_butt = butt_straight; + break; + } + } + if (linejoin_type <= LINEJOIN_POINTY) { + p.Outline(&outlinepath, line_width / 2, static_cast(linejoin_type), + original_butt, miter_lim); + // fix memory leak + std::vector *pv_p = outlinepath.MakePathVector(); + path_out = *pv_p; + delete pv_p; + + } else if (linejoin_type == LINEJOIN_REFLECTED) { + // reflected arc join + path_out = outlinePath(path_in, line_width, static_cast(linejoin_type), + linecap_type , miter_lim, false, start_lean, end_lean); + + } else if (linejoin_type == LINEJOIN_EXTRAPOLATED) { + // extrapolated arc join + path_out = outlinePath(path_in, line_width, LINEJOIN_STRAIGHT, linecap_type, miter_lim, true, start_lean, end_lean); + } + + return path_out; +} + +Geom::Path PathOutsideOutline(Geom::Path const & path_in, double line_width, LineJoinType linejoin_type, double miter_limit) +{ + + Geom::Path path_out; + + if (linejoin_type <= LINEJOIN_POINTY || path_in.size() <= 1) { + + Geom::PathVector * pathvec; + + Path path_tangent = Path(); + Path path_outline = Path(); + path_outline.LoadPath(path_in, Geom::Affine(), false, false); + path_outline.OutsideOutline(&path_tangent, line_width / 2, static_cast(linejoin_type), butt_straight, miter_lim); + + pathvec = path_tangent.MakePathVector(); + path_out = pathvec->front(); + delete pathvec; + return path_out; + } else if (linejoin_type == LINEJOIN_REFLECTED) { + path_out = outlineHalf(path_in, line_width, miter_lim, reflect_curves); + return path_out; + } else if (linejoin_type == LINEJOIN_EXTRAPOLATED) { + path_out = outlineHalf(path_in, line_width, miter_lim, extrapolate_curves); + return path_out; + } + return path_out; +} + +} // namespace Outline + +/* + 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:encoding=utf-8 : diff --git a/src/live_effects/pathoutlineprovider.h b/src/live_effects/pathoutlineprovider.h new file mode 100644 index 000000000..c17584be2 --- /dev/null +++ b/src/live_effects/pathoutlineprovider.h @@ -0,0 +1,55 @@ +#ifndef SEEN_PATH_OUTLINE_H +#define SEEN_PATH_OUTLINE_H + +/* Author: + * Liam P. White + * + * Copyright (C) 2014 Author + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include + +enum LineJoinType { + LINEJOIN_STRAIGHT, + LINEJOIN_ROUND, + LINEJOIN_POINTY, + LINEJOIN_REFLECTED, + LINEJOIN_EXTRAPOLATED +}; +enum ButtTypeMod { + BUTT_STRAIGHT, + BUTT_ROUND, + BUTT_SQUARE, + BUTT_POINTY, + BUTT_LEANED +}; + +namespace Geom +{ + Geom::CubicBezier sbasis_to_cubicbezier(Geom::D2 const & sbasis_in); + std::vector split_at_cusps(const Geom::Path& in); +} + +namespace Outline +{ + unsigned bezierOrder (const Geom::Curve* curve_in); + std::vector PathVectorOutline(std::vector const & path_in, double line_width, ButtTypeMod linecap_type, + LineJoinType linejoin_type, double miter_limit, double start_lean = 0, double end_lean = 0); + Geom::Path PathOutsideOutline(Geom::Path const & path_in, double line_width, LineJoinType linejoin_type, double miter_limit); +} + +#endif // SEEN_PATH_OUTLINE_H + +/* + 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:fileencoding=utf-8 : -- cgit v1.2.3