diff options
| author | Liam P. White <inkscapebrony@gmail.com> | 2014-10-29 22:40:05 +0000 |
|---|---|---|
| committer | Liam P. White <inkscapebrony@gmail.com> | 2014-10-29 22:40:05 +0000 |
| commit | e9943b70c7bf507b9639ecb0a421bcee7ce93e33 (patch) | |
| tree | 2d2fe7ee7a566e1ef1a5dcde18296d9f21188f35 /src/live_effects | |
| parent | i18n. Fixing untranslated strings. (diff) | |
| parent | Merge with trunk r13640 (diff) | |
| download | inkscape-e9943b70c7bf507b9639ecb0a421bcee7ce93e33.tar.gz inkscape-e9943b70c7bf507b9639ecb0a421bcee7ce93e33.zip | |
Merge experimental
(bzr r13641)
Diffstat (limited to 'src/live_effects')
73 files changed, 9290 insertions, 75 deletions
diff --git a/src/live_effects/CMakeLists.txt b/src/live_effects/CMakeLists.txt index a5f50a69d..c8a02c810 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,8 +13,12 @@ set(live_effects_SRC lpe-copy_rotate.cpp lpe-curvestitch.cpp lpe-dynastroke.cpp + lpe-ellipse-5pts.cpp lpe-envelope.cpp lpe-extrude.cpp + lpe-fill-between-many.cpp + lpe-fill-between-strokes.cpp + lpe-fillet-chamfer.cpp lpe-gears.cpp lpe-interpolate.cpp lpe-knot.cpp @@ -25,15 +31,20 @@ set(live_effects_SRC lpe-patternalongpath.cpp lpe-perp_bisector.cpp lpe-perspective_path.cpp + lpe-perspective-envelope.cpp lpe-powerstroke.cpp lpe-recursiveskeleton.cpp lpe-rough-hatches.cpp lpe-ruler.cpp + lpe-show_handles.cpp + lpe-simplify.cpp # lpe-skeleton.cpp lpe-sketch.cpp lpe-spiro.cpp + lpe-roughen.cpp lpe-tangent_to_curve.cpp lpe-test-doEffect-stack.cpp + lpe-bspline.cpp lpe-text_label.cpp lpe-vonkoch.cpp lpegroupbbox.cpp @@ -44,14 +55,18 @@ set(live_effects_SRC parameter/array.cpp parameter/bool.cpp + parameter/filletchamferpointarray.cpp 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 @@ -61,8 +76,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 @@ -70,8 +87,12 @@ 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 lpe-knot.h @@ -84,16 +105,21 @@ set(live_effects_SRC lpe-patternalongpath.h lpe-perp_bisector.h lpe-perspective_path.h + lpe-perspective-envelope.h lpe-powerstroke.h lpe-powerstroke-interpolators.h lpe-recursiveskeleton.h lpe-rough-hatches.h lpe-ruler.h + lpe-simplify.h + lpe-show_handles.h lpe-skeleton.h lpe-sketch.h lpe-spiro.h + lpe-roughen.h lpe-tangent_to_curve.h lpe-test-doEffect-stack.h + lpe-bspline.h lpe-text_label.h lpe-vonkoch.h lpegroupbbox.h @@ -104,15 +130,18 @@ set(live_effects_SRC parameter/array.h parameter/bool.h + parameter/filletchamferpointarray.h parameter/enum.h parameter/parameter.h parameter/path-reference.h parameter/path.h parameter/originalpath.h + parameter/originalpatharray.h parameter/point.h parameter/powerstrokepointarray.h parameter/random.h parameter/text.h + parameter/togglebutton.h parameter/unit.h parameter/vector.h diff --git a/src/live_effects/Makefile_insert b/src/live_effects/Makefile_insert index 9c3c171f2..8f0a3ac57 100644 --- a/src/live_effects/Makefile_insert +++ b/src/live_effects/Makefile_insert @@ -32,14 +32,28 @@ ink_common_sources += \ live_effects/lpe-curvestitch.h \ live_effects/lpe-constructgrid.cpp \ live_effects/lpe-constructgrid.h \ + live_effects/lpe-fillet-chamfer.cpp \ + live_effects/lpe-fillet-chamfer.h \ live_effects/lpe-gears.cpp \ live_effects/lpe-gears.h \ live_effects/lpe-interpolate.cpp \ live_effects/lpe-interpolate.h \ + live_effects/lpe-interpolate_points.cpp \ + live_effects/lpe-interpolate_points.h \ live_effects/lpe-test-doEffect-stack.cpp \ live_effects/lpe-test-doEffect-stack.h \ + live_effects/lpe-bspline.cpp \ + live_effects/lpe-bspline.h \ live_effects/lpe-lattice.cpp \ live_effects/lpe-lattice.h \ + live_effects/lpe-lattice2.cpp \ + live_effects/lpe-lattice2.h \ + live_effects/lpe-roughen.cpp \ + live_effects/lpe-roughen.h \ + live_effects/lpe-show_handles.cpp \ + live_effects/lpe-show_handles.h \ + live_effects/lpe-simplify.cpp \ + live_effects/lpe-simplify.h \ live_effects/lpe-envelope.cpp \ live_effects/lpe-envelope.h \ live_effects/lpe-spiro.cpp \ @@ -56,6 +70,8 @@ ink_common_sources += \ live_effects/lpe-circle_with_radius.h \ live_effects/lpe-perspective_path.cpp \ live_effects/lpe-perspective_path.h \ + live_effects/lpe-perspective-envelope.cpp \ + live_effects/lpe-perspective-envelope.h \ live_effects/lpe-mirror_symmetry.cpp \ live_effects/lpe-mirror_symmetry.h \ live_effects/lpe-circle_3pts.cpp \ @@ -82,4 +98,20 @@ ink_common_sources += \ live_effects/lpe-path_length.cpp \ live_effects/lpe-path_length.h \ live_effects/lpe-line_segment.cpp \ - live_effects/lpe-line_segment.h + 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 diff --git a/src/live_effects/effect-enum.h b/src/live_effects/effect-enum.h index 43af33b53..383eec19e 100644 --- a/src/live_effects/effect-enum.h +++ b/src/live_effects/effect-enum.h @@ -28,6 +28,10 @@ enum EffectType { PERSPECTIVE_PATH, SPIRO, LATTICE, + LATTICE2, + ROUGHEN, + SHOW_HANDLES, + SIMPLIFY, ENVELOPE, CONSTRUCT_GRID, PERP_BISECTOR, @@ -41,15 +45,26 @@ enum EffectType { RULER, BOOLOPS, INTERPOLATE, + INTERPOLATE_POINTS, TEXT_LABEL, PATH_LENGTH, LINE_SEGMENT, DOEFFECTSTACK_TEST, + BSPLINE, DYNASTROKE, RECURSIVE_SKELETON, EXTRUDE, POWERSTROKE, CLONE_ORIGINAL, + ATTACH_PATH, + FILL_BETWEEN_STROKES, + FILL_BETWEEN_MANY, + ELLIPSE_5PTS, + BOUNDING_BOX, + JOIN_TYPE, + TAPER_STROKE, + PERSPECTIVE_ENVELOPE, + 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 4c5e21194..1a64defd9 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 @@ -23,8 +25,13 @@ #include "live_effects/lpe-curvestitch.h" #include "live_effects/lpe-circle_with_radius.h" #include "live_effects/lpe-perspective_path.h" +#include "live_effects/lpe-perspective-envelope.h" #include "live_effects/lpe-spiro.h" #include "live_effects/lpe-lattice.h" +#include "live_effects/lpe-lattice2.h" +#include "live_effects/lpe-roughen.h" +#include "live_effects/lpe-show_handles.h" +#include "live_effects/lpe-simplify.h" #include "live_effects/lpe-envelope.h" #include "live_effects/lpe-constructgrid.h" #include "live_effects/lpe-perp_bisector.h" @@ -38,6 +45,7 @@ #include "live_effects/lpe-ruler.h" #include "live_effects/lpe-boolops.h" #include "live_effects/lpe-interpolate.h" +#include "live_effects/lpe-interpolate_points.h" #include "live_effects/lpe-text_label.h" #include "live_effects/lpe-path_length.h" #include "live_effects/lpe-line_segment.h" @@ -45,6 +53,15 @@ #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-fillet-chamfer.h" #include "xml/node-event-vector.h" #include "sp-object.h" @@ -57,7 +74,7 @@ #include "xml/document.h" #include <glibmm/i18n.h> #include "ui/tools/pen-tool.h" -#include "tools-switch.h" +#include "ui/tools-switch.h" #include "message-stack.h" #include "desktop.h" #include "knotholder.h" @@ -103,7 +120,7 @@ const Util::EnumData<EffectType> LPETypeData[] = { {TEXT_LABEL, N_("Text label"), "text_label"}, #endif /* 0.46 */ - {BEND_PATH, N_("Bend"), "bend_path"}, + {BEND_PATH, N_("Bend"), "bend_path"}, {GEARS, N_("Gears"), "gears"}, {PATTERN_ALONG_PATH, N_("Pattern Along Path"), "skeletal"}, // for historic reasons, this effect is called skeletal(strokes) in Inkscape:SVG {CURVE_STITCH, N_("Stitch Sub-Paths"), "curvestitching"}, @@ -117,9 +134,27 @@ const Util::EnumData<EffectType> LPETypeData[] = { {ROUGH_HATCHES, N_("Hatches (rough)"), "rough_hatches"}, {SKETCH, N_("Sketch"), "sketch"}, {RULER, N_("Ruler"), "ruler"}, -/* 0.49 */ - {POWERSTROKE, N_("Power stroke"), "powerstroke"}, - {CLONE_ORIGINAL, N_("Clone original path"), "clone_original"}, +/* 0.91 */ + {POWERSTROKE, N_("Power stroke"), "powerstroke"}, + {CLONE_ORIGINAL, N_("Clone original path"), "clone_original"}, +/* EXPERIMENTAL */ + {SHOW_HANDLES, N_("Show handles"), "show_handles"}, + {ROUGHEN, N_("Roughen"), "roughen"}, + {BSPLINE, N_("BSpline"), "bspline"}, + {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"}, + {PERSPECTIVE_ENVELOPE, N_("Perspective/Envelope"), "perspective-envelope"}, + {FILLET_CHAMFER, N_("Fillet/Chamfer"), "fillet-chamfer"}, + {INTERPOLATE_POINTS, N_("Interpolate points"), "interpolate_points"}, }; const Util::EnumDataConverter<EffectType> LPETypeConverter(LPETypeData, sizeof(LPETypeData)/sizeof(*LPETypeData)); @@ -216,6 +251,9 @@ Effect::New(EffectType lpenr, LivePathEffectObject *lpeobj) case INTERPOLATE: neweffect = static_cast<Effect*> ( new LPEInterpolate(lpeobj) ); break; + case INTERPOLATE_POINTS: + neweffect = static_cast<Effect*> ( new LPEInterpolatePoints(lpeobj) ); + break; case TEXT_LABEL: neweffect = static_cast<Effect*> ( new LPETextLabel(lpeobj) ); break; @@ -228,6 +266,9 @@ Effect::New(EffectType lpenr, LivePathEffectObject *lpeobj) case DOEFFECTSTACK_TEST: neweffect = static_cast<Effect*> ( new LPEdoEffectStackTest(lpeobj) ); break; + case BSPLINE: + neweffect = static_cast<Effect*> ( new LPEBSpline(lpeobj) ); + break; case DYNASTROKE: neweffect = static_cast<Effect*> ( new LPEDynastroke(lpeobj) ); break; @@ -243,8 +284,47 @@ Effect::New(EffectType lpenr, LivePathEffectObject *lpeobj) case CLONE_ORIGINAL: neweffect = static_cast<Effect*> ( new LPECloneOriginal(lpeobj) ); break; + case ATTACH_PATH: + neweffect = static_cast<Effect*> ( new LPEAttachPath(lpeobj) ); + break; + case FILL_BETWEEN_STROKES: + neweffect = static_cast<Effect*> ( new LPEFillBetweenStrokes(lpeobj) ); + break; + case FILL_BETWEEN_MANY: + neweffect = static_cast<Effect*> ( new LPEFillBetweenMany(lpeobj) ); + break; + case ELLIPSE_5PTS: + neweffect = static_cast<Effect*> ( new LPEEllipse5Pts(lpeobj) ); + break; + case BOUNDING_BOX: + neweffect = static_cast<Effect*> ( new LPEBoundingBox(lpeobj) ); + break; + case JOIN_TYPE: + neweffect = static_cast<Effect*> ( new LPEJoinType(lpeobj) ); + break; + case TAPER_STROKE: + neweffect = static_cast<Effect*> ( new LPETaperStroke(lpeobj) ); + break; + case SIMPLIFY: + neweffect = static_cast<Effect*> ( new LPESimplify(lpeobj) ); + break; + case LATTICE2: + neweffect = static_cast<Effect*> ( new LPELattice2(lpeobj) ); + break; + case PERSPECTIVE_ENVELOPE: + neweffect = static_cast<Effect*> ( new LPEPerspectiveEnvelope(lpeobj) ); + break; + case FILLET_CHAMFER: + neweffect = static_cast<Effect*> ( new LPEFilletChamfer(lpeobj) ); + break; + case ROUGHEN: + neweffect = static_cast<Effect*> ( new LPERoughen(lpeobj) ); + break; + case SHOW_HANDLES: + neweffect = static_cast<Effect*> ( 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; } @@ -327,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<SPLPEItem *>(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<SPLPEItem *>(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 @@ -494,11 +601,6 @@ Effect::getCanvasIndicators(SPLPEItem const* lpeitem) { std::vector<Geom::PathVector> hp_vec; - if (!SP_IS_SHAPE(lpeitem)) { -// g_print ("How to handle helperpaths for non-shapes?\n"); // non-shapes are for example SPGroups. - return hp_vec; - } - // add indicators provided by the effect itself addCanvasIndicators(lpeitem, hp_vec); diff --git a/src/live_effects/effect.h b/src/live_effects/effect.h index 1da9b4cc9..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(); @@ -101,6 +109,7 @@ public: Inkscape::XML::Node * getRepr(); SPDocument * getSPDoc(); LivePathEffectObject * getLPEObj() {return lpeobj;}; + LivePathEffectObject const * getLPEObj() const {return lpeobj;}; Parameter * getParameter(const char * key); void readallParameters(Inkscape::XML::Node const* repr); @@ -146,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<Geom::Path> 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 <flutterguy317@gmail.com> + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include <glibmm/i18n.h> +#include <math.h> + +#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<Geom::Path> 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<Geom::Path> 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<Geom::Point> 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<Geom::Point> 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<Geom::CubicBezier>(-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<Geom::Path> 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<Geom::Point> 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<Geom::Point> 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<Geom::CubicBezier>(-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 <flutterguy317@gmail.com> + * + * 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-bendpath.cpp b/src/live_effects/lpe-bendpath.cpp index eaf9fe4a6..33171b184 100644 --- a/src/live_effects/lpe-bendpath.cpp +++ b/src/live_effects/lpe-bendpath.cpp @@ -76,6 +76,9 @@ LPEBendPath::doBeforeEffect (SPLPEItem const* lpeitem) { // get the item bounding box original_bbox(lpeitem); + SPLPEItem * item = const_cast<SPLPEItem*>(lpeitem); + item->apply_to_clippath(item); + item->apply_to_mask(item); } Geom::Piecewise<Geom::D2<Geom::SBasis> > 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 <flutterguy317@gmail.com> + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include <glibmm/i18n.h> + +#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<Parameter *>(&linked_path) ); + registerParameter( dynamic_cast<Parameter *>(&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::LineSegment>(Geom::Point(bbox->right(), bbox->top())); + p.appendNew<Geom::LineSegment>(Geom::Point(bbox->right(), bbox->bottom())); + p.appendNew<Geom::LineSegment>(Geom::Point(bbox->left(), bbox->bottom())); + p.appendNew<Geom::LineSegment>(Geom::Point(bbox->left(), bbox->top())); + std::vector<Geom::Path> 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 <flutterguy317@gmail.com> + * + * 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 new file mode 100644 index 000000000..b68799d08 --- /dev/null +++ b/src/live_effects/lpe-bspline.cpp @@ -0,0 +1,663 @@ +/* + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <gtkmm.h> + +#if WITH_GLIBMM_2_32 +# include <glibmm/threads.h> +#endif + +#include <glib.h> +#include <glibmm/i18n.h> + + +#include "display/curve.h" +#include <2geom/bezier-curve.h> +#include <2geom/point.h> +#include "helper/geom-curves.h" +#include "live_effects/lpe-bspline.h" +#include "live_effects/lpeobject.h" +#include "live_effects/parameter/parameter.h" +#include "ui/widget/scalar.h" +#include "ui/tool/node.h" +#include "ui/tools/node-tool.h" +#include "ui/tool/control-point-selection.h" +#include "ui/tool/selectable-control-point.h" +#include "selection.h" +#include "xml/repr.h" +#include "svg/svg.h" +#include "sp-path.h" +#include "style.h" +#include "document-private.h" +#include "document.h" +#include "document-undo.h" +#include "desktop-handles.h" +#include "verbs.h" +#include "sp-lpe-item.h" +#include "sp-namedview.h" +#include "display/sp-canvas.h" +#include <typeinfo> +#include <vector> +#include "util/units.h" +// For handling un-continuous paths: +#include "message-stack.h" +#include "inkscape.h" +#include "desktop.h" + +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), + // initialise your parameters here: + //testpointA(_("Test Point A"), _("Test A"), "ptA", &wr, this, + //Geom::Point(100,100)), + 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), + showHelper(_("Show helper paths"), _("Show helper paths"), "showHelper", &wr, this, false), + weight(_("Change weight:"), _("Change weight of the effect"), "weight", &wr, this, defaultStartPower) +{ + registerParameter(dynamic_cast<Parameter *>(&weight)); + registerParameter(dynamic_cast<Parameter *>(&steps)); + registerParameter(dynamic_cast<Parameter *>(&ignoreCusp)); + registerParameter(dynamic_cast<Parameter *>(&onlySelected)); + registerParameter(dynamic_cast<Parameter *>(&showHelper)); + + weight.param_set_range(noPower, 1); + weight.param_set_increments(0.1, 0.1); + weight.param_set_digits(4); + + steps.param_set_range(1, 10); + steps.param_set_increments(1, 1); + steps.param_set_digits(0); +} + +LPEBSpline::~LPEBSpline() {} + +void LPEBSpline::doBeforeEffect (SPLPEItem const* lpeitem) +{ + if(!hp.empty()){ + hp.clear(); + } +} + + +void LPEBSpline::createAndApply(const char *name, SPDocument *doc, + SPItem *item) +{ + if (!SP_IS_SHAPE(item)) { + g_warning("LPE BSpline can only be applied to shapes (not groups)."); + } else { + // Path effect definition + Inkscape::XML::Document *xml_doc = doc->getReprDoc(); + Inkscape::XML::Node *repr = xml_doc->createElement("inkscape:path-effect"); + repr->setAttribute("effect", name); + + doc->getDefs()->getRepr() + ->addChild(repr, NULL); // adds to <defs> and assigns the 'id' attribute + const gchar *repr_id = repr->attribute("id"); + Inkscape::GC::release(repr); + + gchar *href = g_strdup_printf("#%s", repr_id); + SP_LPE_ITEM(item)->addPathEffect(href, true); + g_free(href); + } +} + +void LPEBSpline::doEffect(SPCurve *curve) +{ + + 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(); + double radiusHelperNodes = 6.0; + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (desktop){ + radiusHelperNodes /= SP_ACTIVE_DESKTOP->current_zoom(); + SPNamedView *nv = sp_desktop_namedview(desktop); + radiusHelperNodes = Inkscape::Util::Quantity::convert(radiusHelperNodes, "px", nv->doc_units->abbr); + } + 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(); + 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::D2<Geom::SBasis> SBasisIn; + Geom::D2<Geom::SBasis> SBasisOut; + Geom::D2<Geom::SBasis> 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. + 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_it1 != curve_endit) { + //previousPointAt3 = pointAt3; + //Calculamos los puntos que dividirÃan en tres segmentos iguales el path + //recto de entrada y de salida + SPCurve *in = new SPCurve(); + in->moveto(curve_it1->initialPoint()); + in->lineto(curve_it1->finalPoint()); + cubic = dynamic_cast<Geom::CubicBezier const *>(&*curve_it1); + if (cubic) { + SBasisIn = in->first_segment()->toSBasis(); + 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; + if ( curve_it2 != curve_endit ) { + SPCurve *out = new SPCurve(); + out->moveto(curve_it2->initialPoint()); + out->lineto(curve_it2->finalPoint()); + cubic = dynamic_cast<Geom::CubicBezier const *>(&*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; + } + 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<Geom::SBasis> SBasisStart = start->first_segment()->toSBasis(); + SPCurve *lineHelper = new SPCurve(); + cubic = dynamic_cast<Geom::CubicBezier const *>(&*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<Geom::SBasis> SBasisEnd = end->first_segment()->toSBasis(); + cubic = dynamic_cast<Geom::CubicBezier const *>(&*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; + node = SBasisHelper.valueAt(0.5); + nCurve->curveto(pointAt1, pointAt2, node); + nCurve->move_endpoints(node, node); + } else if ( curve_it2 == curve_endit) { + nCurve->curveto(pointAt1, pointAt2, curve_it1->finalPoint()); + nCurve->move_endpoints(path_it->begin()->initialPoint(), curve_it1->finalPoint()); + } else { + 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); + Geom::CubicBezier const *cubic2 = dynamic_cast<Geom::CubicBezier const *>(&*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); + } + if(!are_near(node,curve_it1->finalPoint()) && showHelper){ + drawHandle(node, radiusHelperNodes); + } + //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 + //aumentamos los valores para el siguiente paso en el bucle + ++curve_it1; + ++curve_it2; + } + //y cerramos la curva + if (path_it->closed()) { + nCurve->closepath_current(); + } + curve->append(nCurve, false); + nCurve->reset(); + delete nCurve; + } + if(showHelper){ + Geom::PathVector const pathv = curve->get_pathvector(); + hp.push_back(pathv[0]); + } +} + +void +LPEBSpline::drawHandle(Geom::Point p, double radiusHelperNodes) +{ + char const * svgd = "M 1,0.5 A 0.5,0.5 0 0 1 0.5,1 0.5,0.5 0 0 1 0,0.5 0.5,0.5 0 0 1 0.5,0 0.5,0.5 0 0 1 1,0.5 Z"; + Geom::PathVector pathv = sp_svg_read_pathv(svgd); + Geom::Affine aff = Geom::Affine(); + aff *= Geom::Scale(radiusHelperNodes); + pathv *= aff; + pathv += p - Geom::Point(0.5*radiusHelperNodes, 0.5*radiusHelperNodes); + hp.push_back(pathv[0]); +} + +void +LPEBSpline::addCanvasIndicators(SPLPEItem const */*lpeitem*/, std::vector<Geom::PathVector> &hp_vec) +{ + hp_vec.push_back(hp); +} + +Gtk::Widget *LPEBSpline::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); + std::vector<Parameter *>::iterator it = param_vector.begin(); + while (it != param_vector.end()) { + if ((*it)->widget_is_visible) { + Parameter *param = *it; + Gtk::Widget *widg = dynamic_cast<Gtk::Widget *>(param->param_newWidget()); + if (param->param_key == "weight") { + Gtk::HBox * buttons = Gtk::manage(new Gtk::HBox(true,0)); + Gtk::Button *defaultWeight = + Gtk::manage(new Gtk::Button(Glib::ustring(_("Default weight")))); + defaultWeight->signal_clicked() + .connect(sigc::bind<Gtk::Widget *>(sigc::mem_fun(*this, &LPEBSpline::toDefaultWeight), widg)); + buttons->pack_start(*defaultWeight, true, true, 2); + Gtk::Button *makeCusp = + Gtk::manage(new Gtk::Button(Glib::ustring(_("Make cusp")))); + makeCusp->signal_clicked() + .connect(sigc::bind<Gtk::Widget *>(sigc::mem_fun(*this, &LPEBSpline::toMakeCusp), widg)); + buttons->pack_start(*makeCusp, true, true, 2); + vbox->pack_start(*buttons, true, true, 2); + } + if (param->param_key == "weight" || param->param_key == "steps") { + Inkscape::UI::Widget::Scalar *widgRegistered = + Gtk::manage(dynamic_cast<Inkscape::UI::Widget::Scalar *>(widg)); + widgRegistered->signal_value_changed() + .connect(sigc::mem_fun(*this, &LPEBSpline::toWeight)); + widg = dynamic_cast<Gtk::Widget *>(widgRegistered); + if (widg) { + Gtk::HBox * scalarParameter = dynamic_cast<Gtk::HBox *>(widg); + std::vector< Gtk::Widget* > childList = scalarParameter->get_children(); + Gtk::Entry* entryWidg = dynamic_cast<Gtk::Entry *>(childList[1]); + entryWidg->set_width_chars(6); + } + } + if (param->param_key == "onlySelected") { + Gtk::CheckButton *widgRegistered = + Gtk::manage(dynamic_cast<Gtk::CheckButton *>(widg)); + widg = dynamic_cast<Gtk::Widget *>(widgRegistered); + } + if (param->param_key == "ignoreCusp") { + Gtk::CheckButton *widgRegistered = + Gtk::manage(dynamic_cast<Gtk::CheckButton *>(widg)); + widg = dynamic_cast<Gtk::Widget *>(widgRegistered); + } + 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<Gtk::Widget *>(vbox); +} + +void LPEBSpline::toDefaultWeight(Gtk::Widget *widgWeight) +{ + weight.param_set_value(defaultStartPower); + changeWeight(defaultStartPower); + Gtk::HBox * scalarParameter = dynamic_cast<Gtk::HBox *>(widgWeight); + std::vector< Gtk::Widget* > childList = scalarParameter->get_children(); + Gtk::Entry* entryWidg = dynamic_cast<Gtk::Entry *>(childList[1]); + entryWidg->set_text("defaultStartPower"); +} + +void LPEBSpline::toMakeCusp(Gtk::Widget *widgWeight) +{ + weight.param_set_value(noPower); + changeWeight(noPower); + Gtk::HBox * scalarParameter = dynamic_cast<Gtk::HBox *>(widgWeight); + std::vector< Gtk::Widget* > childList = scalarParameter->get_children(); + Gtk::Entry* entryWidg = dynamic_cast<Gtk::Entry *>(childList[1]); + entryWidg->set_text("noPower"); +} + +void LPEBSpline::toWeight() +{ + changeWeight(weight); +} + +void LPEBSpline::changeWeight(double weightValue) +{ + 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; + SPPath *path = SP_PATH(item); + SPCurve *curve = path->get_curve_for_edit(); + LPEBSpline::doBSplineFromWidget(curve, weightValue); + gchar *str = sp_svg_write_path(curve->get_pathvector()); + path->getRepr()->setAttribute("inkscape:original-d", str); + if (INK_IS_NODE_TOOL(desktop->event_context)) { + Inkscape::UI::Tools::NodeTool *nt = INK_NODE_TOOL(desktop->event_context); + nt->desktop->updateNow(); + } + g_free(str); + curve->unref(); + desktop->clearWaitingCursor(); + DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_LPE, + _("Modified the weight of the BSpline")); +} + +bool LPEBSpline::nodeIsSelected(Geom::Point nodePoint) +{ + using Geom::X; + using Geom::Y; + + if (points.size() > 0) { + for (std::vector<Geom::Point>::iterator i = points.begin(); + i != points.end(); ++i) { + Geom::Point p = *i; + if (Geom::are_near(p, nodePoint, handleCubicGap)) { + return true; + } else { + } + } + } + return false; +} + +void LPEBSpline::doBSplineFromWidget(SPCurve *curve, double weightValue) +{ + using Geom::X; + using Geom::Y; + 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 = + nt->_selected_nodes->allPoints(); + points.clear(); + std::vector<Geom::Point>::iterator pbegin; + for (Inkscape::UI::ControlPointSelection::Set::iterator i = + selection.begin(); + i != selection.end(); ++i) { + if ((*i)->selected()) { + Inkscape::UI::Node *n = dynamic_cast<Inkscape::UI::Node *>(*i); + pbegin = points.begin(); + points.insert(pbegin, desktop->doc2dt(n->position())); + } + } + } + //bool hasNodesSelected = LPEBspline::hasNodesSelected(); + 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 + SPCurve *nCurve = new SPCurve(); + Geom::Point pointAt0(0, 0); + Geom::Point pointAt1(0, 0); + Geom::Point pointAt2(0, 0); + Geom::Point pointAt3(0, 0); + Geom::D2<Geom::SBasis> SBasisIn; + Geom::D2<Geom::SBasis> SBasisOut; + 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. + 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_it1 != curve_endit) { + //previousPointAt3 = pointAt3; + //Calculamos los puntos que dividirÃan en tres segmentos iguales el path + //recto de entrada y de salida + SPCurve *in = new SPCurve(); + in->moveto(curve_it1->initialPoint()); + in->lineto(curve_it1->finalPoint()); + cubic = dynamic_cast<Geom::CubicBezier const *>(&*curve_it1); + pointAt0 = in->first_segment()->initialPoint(); + pointAt3 = in->first_segment()->finalPoint(); + SBasisIn = in->first_segment()->toSBasis(); + if (!onlySelected) { + if (cubic) { + if (!ignoreCusp || !Geom::are_near((*cubic)[1], pointAt0)) { + pointAt1 = SBasisIn.valueAt(weightValue); + if (weightValue != noPower) { + pointAt1 = + 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 != noPower) { + pointAt2 = + Geom::Point(pointAt2[X] + handleCubicGap, pointAt2[Y] + handleCubicGap); + } + } else { + pointAt2 = in->first_segment()->finalPoint(); + } + } else { + if (!ignoreCusp && weightValue != noPower) { + pointAt1 = SBasisIn.valueAt(weightValue); + if (weightValue != noPower) { + pointAt1 = + Geom::Point(pointAt1[X] + handleCubicGap, pointAt1[Y] + handleCubicGap); + } + pointAt2 = SBasisIn.valueAt(1 - weightValue); + if (weightValue != noPower) { + pointAt2 = + Geom::Point(pointAt2[X] + handleCubicGap, pointAt2[Y] + handleCubicGap); + } + } else { + pointAt1 = in->first_segment()->initialPoint(); + pointAt2 = in->first_segment()->finalPoint(); + } + } + } else { + if (cubic) { + if (!ignoreCusp || !Geom::are_near((*cubic)[1], pointAt0)) { + if (nodeIsSelected(pointAt0)) { + pointAt1 = SBasisIn.valueAt(weightValue); + if (weightValue != noPower) { + pointAt1 = + Geom::Point(pointAt1[X] + handleCubicGap, pointAt1[Y] + handleCubicGap); + } + } else { + pointAt1 = (*cubic)[1]; + } + } else { + pointAt1 = in->first_segment()->initialPoint(); + } + if (!ignoreCusp || !Geom::are_near((*cubic)[2], pointAt3)) { + if (nodeIsSelected(pointAt3)) { + pointAt2 = SBasisIn.valueAt(1 - weightValue); + if (weightValue != noPower) { + pointAt2 = + Geom::Point(pointAt2[X] + handleCubicGap, pointAt2[Y] + handleCubicGap); + } + } else { + pointAt2 = (*cubic)[2]; + } + } else { + pointAt2 = in->first_segment()->finalPoint(); + } + } else { + if (!ignoreCusp && weightValue != noPower) { + if (nodeIsSelected(pointAt0)) { + pointAt1 = SBasisIn.valueAt(weightValue); + pointAt1 = + 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] + handleCubicGap, pointAt2[Y] + handleCubicGap); + } else { + pointAt2 = in->first_segment()->finalPoint(); + } + } else { + pointAt1 = in->first_segment()->initialPoint(); + pointAt2 = in->first_segment()->finalPoint(); + } + } + } + in->reset(); + delete in; + //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 + nCurve->curveto(pointAt1, pointAt2, pointAt3); + //aumentamos los valores para el siguiente paso en el bucle + ++curve_it1; + ++curve_it2; + } + if (path_it->closed()) { + nCurve->move_endpoints(path_it->begin()->initialPoint(), + path_it->begin()->initialPoint()); + } else { + nCurve->move_endpoints(path_it->begin()->initialPoint(), pointAt3); + } + //y cerramos la curva + if (path_it->closed()) { + nCurve->closepath_current(); + } + curve->append(nCurve, false); + nCurve->reset(); + delete nCurve; + } +} + +}; //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-bspline.h b/src/live_effects/lpe-bspline.h new file mode 100644 index 000000000..169658b94 --- /dev/null +++ b/src/live_effects/lpe-bspline.h @@ -0,0 +1,56 @@ +#ifndef INKSCAPE_LPE_BSPLINE_H +#define INKSCAPE_LPE_BSPLINE_H + +/* + * Inkscape::LPEBSpline + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "live_effects/effect.h" +#include "live_effects/parameter/bool.h" +#include <vector> + +namespace Inkscape { +namespace LivePathEffect { + +class LPEBSpline : public Effect { +public: + LPEBSpline(LivePathEffectObject *lpeobject); + virtual ~LPEBSpline(); + + virtual void createAndApply(const char *name, SPDocument *doc, SPItem *item); + virtual LPEPathFlashType pathFlashType() const { + return SUPPRESS_FLASH; + } + virtual void doEffect(SPCurve *curve); + virtual void doBeforeEffect (SPLPEItem const* lpeitem); + void drawHandle(Geom::Point p, double radiusHelperNodes); + void addCanvasIndicators(SPLPEItem const */*lpeitem*/, std::vector<Geom::PathVector> &hp_vec); + virtual void doBSplineFromWidget(SPCurve *curve, double value); + virtual bool nodeIsSelected(Geom::Point nodePoint); + virtual Gtk::Widget *newWidget(); + virtual void changeWeight(double weightValue); + virtual void toDefaultWeight(Gtk::Widget *widgWeight); + virtual void toMakeCusp(Gtk::Widget *widgWeight); + virtual void toWeight(); + + // TODO make this private + ScalarParam steps; + +private: + std::vector<Geom::Point> points; + BoolParam ignoreCusp; + BoolParam onlySelected; + BoolParam showHelper; + ScalarParam weight; + Geom::PathVector hp; + + LPEBSpline(const LPEBSpline &); + LPEBSpline &operator=(const LPEBSpline &); + +}; + +} //namespace LivePathEffect +} //namespace Inkscape +#endif 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 <glibmm/i18n.h> +#include <gdk/gdk.h> #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 <flutterguy317@gmail.com> + * + * 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 <glibmm/i18n.h> +#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<Geom::Path> +LPEEllipse5Pts::doEffect_path (std::vector<Geom::Path> const & path_in) +{ + std::vector<Geom::Path> path_out = std::vector<Geom::Path>(); + + 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::CubicBezier>(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 <flutterguy317@gmail.com> + * + * 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<Geom::Path> doEffect_path (std::vector<Geom::Path> 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-envelope.cpp b/src/live_effects/lpe-envelope.cpp index 4f5623fba..05a672ae8 100644 --- a/src/live_effects/lpe-envelope.cpp +++ b/src/live_effects/lpe-envelope.cpp @@ -32,7 +32,7 @@ LPEEnvelope::LPEEnvelope(LivePathEffectObject *lpeobject) : bend_path2(_("Right bend path:"), _("Right path along which to bend the original path"), "bendpath2", &wr, this, "M0,0 L1,0"), bend_path3(_("Bottom bend path:"), _("Bottom path along which to bend the original path"), "bendpath3", &wr, this, "M0,0 L1,0"), bend_path4(_("Left bend path:"), _("Left path along which to bend the original path"), "bendpath4", &wr, this, "M0,0 L1,0"), - xx(_("E_nable left & right paths"), _("Enable the left and right deformation paths"), "xx", &wr, this, true), + xx(_("_Enable left & right paths"), _("Enable the left and right deformation paths"), "xx", &wr, this, true), yy(_("_Enable top & bottom paths"), _("Enable the top and bottom deformation paths"), "yy", &wr, this, true) { registerParameter( dynamic_cast<Parameter *>(&yy) ); @@ -54,6 +54,9 @@ LPEEnvelope::doBeforeEffect (SPLPEItem const* lpeitem) { // get the item bounding box original_bbox(lpeitem); + SPLPEItem * item = const_cast<SPLPEItem*>(lpeitem); + item->apply_to_clippath(item); + item->apply_to_mask(item); } Geom::Piecewise<Geom::D2<Geom::SBasis> > 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 <flutterguy317@gmail.com> + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include <gtkmm/box.h> + +#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 <glibmm/i18n.h> + +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<Parameter *>(&linked_paths) ); + //perceived_path = true; +} + +LPEFillBetweenMany::~LPEFillBetweenMany() +{ + +} + +void LPEFillBetweenMany::doEffect (SPCurve * curve) +{ + std::vector<Geom::Path> res_pathv; + SPItem * firstObj = NULL; + for (std::vector<PathAndDirection*>::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<Geom::LineSegment>(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 <flutterguy317@gmail.com> + * + * 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 <flutterguy317@gmail.com> + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include <glibmm/i18n.h> + +#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<Parameter *>(&linked_path) ); + registerParameter( dynamic_cast<Parameter *>(&second_path) ); + registerParameter( dynamic_cast<Parameter *>(&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<Geom::Path> linked_pathv = linked_path.get_pathvector(); + std::vector<Geom::Path> second_pathv = second_path.get_pathvector(); + std::vector<Geom::Path> result_linked_pathv; + std::vector<Geom::Path> result_second_pathv; + Geom::Affine second_transform = second_path.getObject()->getRelativeTransform(linked_path.getObject()); + + for (std::vector<Geom::Path>::iterator iter = linked_pathv.begin(); iter != linked_pathv.end(); ++iter) + { + result_linked_pathv.push_back((*iter)); + } + for (std::vector<Geom::Path>::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<Geom::LineSegment>(result_second_pathv.front().finalPoint()); + result_linked_pathv.front().append(result_second_pathv.front().reverse()); + } + else + { + result_linked_pathv.front().appendNew<Geom::LineSegment>(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<Geom::Path> linked_pathv = linked_path.get_pathvector(); + std::vector<Geom::Path> result_pathv; + + for (std::vector<Geom::Path>::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<Geom::Path> second_pathv = second_path.get_pathvector(); + std::vector<Geom::Path> result_pathv; + + for (std::vector<Geom::Path>::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 <flutterguy317@gmail.com> + * + * 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 new file mode 100644 index 000000000..f15bf9215 --- /dev/null +++ b/src/live_effects/lpe-fillet-chamfer.cpp @@ -0,0 +1,649 @@ +/* + * Author(s): + * Jabiertxo Arraiza Cenoz <jabier.arraiza@marker.es> + * + * Copyright (C) 2014 Author(s) + * + * Special thanks to Johan Engelen for the base of the effect -powerstroke- + * Also to ScislaC for point me to the idea + * Also su_v for his construvtive feedback and time + * Also to Mc- (IRC nick) for his important contribution to find real time + * values based on + * and finaly to Liam P. White for his big help on coding, that save me a lot of hours + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ +#include "live_effects/lpe-fillet-chamfer.h" + +#include <2geom/sbasis-to-bezier.h> +#include <2geom/svg-elliptical-arc.h> +#include <2geom/line.h> + +#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 +#include "selection.h" +#include "ui/tools-switch.h" +#include "ui/tool/control-point-selection.h" +#include "ui/tool/selectable-control-point.h" +#include "ui/tool/node.h" +#include "ui/tools/node-tool.h" + +// TODO due to internal breakage in glibmm headers, this must be last: +#include <glibmm/i18n.h> + +using namespace Geom; +namespace Inkscape { +namespace LivePathEffect { + +static const Util::EnumData<FilletMethod> FilletMethodData[FM_END] = { + { FM_AUTO, N_("Auto"), "auto" }, + { FM_ARC, N_("Force arc"), "arc" }, + { FM_BEZIER, N_("Force bezier"), "bezier" } +}; +static const Util::EnumDataConverter<FilletMethod> +FMConverter(FilletMethodData, FM_END); + +const double tolerance = 0.001; +const double gapHelper = 0.00001; + +LPEFilletChamfer::LPEFilletChamfer(LivePathEffectObject *lpeobject) : + Effect(lpeobject), + fillet_chamfer_values(_("Fillet point"), _("Fillet point"), "fillet_chamfer_values", &wr, this), + hide_knots(_("Hide knots"), _("Hide knots"), "hide_knots", &wr, this, false), + 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), + 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), + method(_("Method"), _("Fillets methods"), "method", FMConverter, &wr, this, FM_AUTO), + 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) +{ + registerParameter(&fillet_chamfer_values); + registerParameter(&unit); + registerParameter(&method); + registerParameter(&radius); + registerParameter(&helper_size); + registerParameter(&flexible); + registerParameter(&use_knot_distance); + registerParameter(&ignore_radius_0); + registerParameter(&only_selected); + registerParameter(&hide_knots); + + radius.param_set_range(0., infinity()); + radius.param_set_increments(1, 1); + radius.param_set_digits(4); + helper_size.param_set_range(0, infinity()); + helper_size.param_set_increments(5, 5); + helper_size.param_set_digits(0); +} + +LPEFilletChamfer::~LPEFilletChamfer() {} + +Gtk::Widget *LPEFilletChamfer::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<Parameter *>::iterator it = param_vector.begin(); + while (it != param_vector.end()) { + if ((*it)->widget_is_visible) { + Parameter *param = *it; + Gtk::Widget *widg = param->param_newWidget(); + if (param->param_key == "radius") { + Inkscape::UI::Widget::Scalar *widgRegistered = Gtk::manage(dynamic_cast<Inkscape::UI::Widget::Scalar *>(widg)); + widgRegistered->signal_value_changed().connect(sigc::mem_fun(*this, &LPEFilletChamfer::updateFillet)); + widg = widgRegistered; + if (widg) { + Gtk::HBox *scalarParameter = dynamic_cast<Gtk::HBox *>(widg); + std::vector<Gtk::Widget *> childList = scalarParameter->get_children(); + Gtk::Entry *entryWidg = dynamic_cast<Gtk::Entry *>(childList[1]); + entryWidg->set_width_chars(6); + } + } else if (param->param_key == "flexible") { + Gtk::CheckButton *widgRegistered = Gtk::manage(dynamic_cast<Gtk::CheckButton *>(widg)); + widgRegistered->signal_clicked().connect(sigc::mem_fun(*this, &LPEFilletChamfer::toggleFlexFixed)); + } else if (param->param_key == "helper_size") { + Inkscape::UI::Widget::Scalar *widgRegistered = Gtk::manage(dynamic_cast<Inkscape::UI::Widget::Scalar *>(widg)); + widgRegistered->signal_value_changed().connect(sigc::mem_fun(*this, &LPEFilletChamfer::refreshKnots)); + } else if (param->param_key == "hide_knots") { + Gtk::CheckButton *widgRegistered = Gtk::manage(dynamic_cast<Gtk::CheckButton *>(widg)); + widgRegistered->signal_clicked().connect(sigc::mem_fun(*this, &LPEFilletChamfer::toggleHide)); + } else if (param->param_key == "only_selected") { + Gtk::manage(widg); + } else if (param->param_key == "ignore_radius_0") { + Gtk::manage(widg); + } + + 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; + } + + Gtk::HBox *filletButtonsContainer = Gtk::manage(new Gtk::HBox(true, 0)); + Gtk::Button *fillet = Gtk::manage(new Gtk::Button(Glib::ustring(_("Fillet")))); + fillet->signal_clicked().connect(sigc::mem_fun(*this, &LPEFilletChamfer::fillet)); + + filletButtonsContainer->pack_start(*fillet, true, true, 2); + Gtk::Button *inverse = Gtk::manage(new Gtk::Button(Glib::ustring(_("Inverse fillet")))); + inverse->signal_clicked().connect(sigc::mem_fun(*this, &LPEFilletChamfer::inverse)); + filletButtonsContainer->pack_start(*inverse, true, true, 2); + + Gtk::HBox *chamferButtonsContainer = Gtk::manage(new Gtk::HBox(true, 0)); + Gtk::Button *chamfer = Gtk::manage(new Gtk::Button(Glib::ustring(_("Chamfer")))); + chamfer->signal_clicked().connect(sigc::mem_fun(*this, &LPEFilletChamfer::chamfer)); + + chamferButtonsContainer->pack_start(*chamfer, true, true, 2); + Gtk::Button *doubleChamfer = Gtk::manage(new Gtk::Button(Glib::ustring(_("Double chamfer")))); + doubleChamfer->signal_clicked().connect(sigc::mem_fun(*this, &LPEFilletChamfer::doubleChamfer)); + chamferButtonsContainer->pack_start(*doubleChamfer, true, true, 2); + + vbox->pack_start(*filletButtonsContainer, true, true, 2); + vbox->pack_start(*chamferButtonsContainer, true, true, 2); + + return vbox; +} + +void LPEFilletChamfer::toggleHide() +{ + std::vector<Point> filletChamferData = fillet_chamfer_values.data(); + std::vector<Geom::Point> result; + for (std::vector<Point>::const_iterator point_it = filletChamferData.begin(); + point_it != filletChamferData.end(); ++point_it) { + if (hide_knots) { + result.push_back(Point((*point_it)[X], abs((*point_it)[Y]) * -1)); + } else { + result.push_back(Point((*point_it)[X], abs((*point_it)[Y]))); + } + } + fillet_chamfer_values.param_set_and_write_new_value(result); + refreshKnots(); +} + +void LPEFilletChamfer::toggleFlexFixed() +{ + std::vector<Point> filletChamferData = fillet_chamfer_values.data(); + std::vector<Geom::Point> result; + unsigned int i = 0; + for (std::vector<Point>::const_iterator point_it = filletChamferData.begin(); + point_it != filletChamferData.end(); ++point_it) { + if (flexible) { + result.push_back(Point(fillet_chamfer_values.to_time(i, (*point_it)[X]), + (*point_it)[Y])); + } else { + result.push_back(Point(fillet_chamfer_values.to_len(i, (*point_it)[X]), + (*point_it)[Y])); + } + i++; + } + if (flexible) { + radius.param_set_range(0., 100); + radius.param_set_value(0); + } else { + radius.param_set_range(0., infinity()); + radius.param_set_value(0); + } + fillet_chamfer_values.param_set_and_write_new_value(result); +} + +void LPEFilletChamfer::updateFillet() +{ + double power = 0; + if (!flexible) { + power = Inkscape::Util::Quantity::convert(radius, unit.get_abbreviation(), "px") * -1; + } else { + power = radius; + } + Piecewise<D2<SBasis> > const &pwd2 = fillet_chamfer_values.get_pwd2(); + doUpdateFillet(path_from_piecewise(pwd2, tolerance), power); +} + +void LPEFilletChamfer::fillet() +{ + Piecewise<D2<SBasis> > const &pwd2 = fillet_chamfer_values.get_pwd2(); + doChangeType(path_from_piecewise(pwd2, tolerance), 1); +} + +void LPEFilletChamfer::inverse() +{ + Piecewise<D2<SBasis> > const &pwd2 = fillet_chamfer_values.get_pwd2(); + doChangeType(path_from_piecewise(pwd2, tolerance), 2); +} + +void LPEFilletChamfer::chamfer() +{ + Piecewise<D2<SBasis> > const &pwd2 = fillet_chamfer_values.get_pwd2(); + doChangeType(path_from_piecewise(pwd2, tolerance), 3); +} + +void LPEFilletChamfer::doubleChamfer() +{ + Piecewise<D2<SBasis> > const &pwd2 = fillet_chamfer_values.get_pwd2(); + doChangeType(path_from_piecewise(pwd2, tolerance), 4); +} + +void LPEFilletChamfer::refreshKnots() +{ + Piecewise<D2<SBasis> > const &pwd2 = fillet_chamfer_values.get_pwd2(); + fillet_chamfer_values.recalculate_knots(pwd2); + SPDesktop *desktop = inkscape_active_desktop(); + if (tools_isactive(desktop, TOOLS_NODES)) { + tools_switch(desktop, TOOLS_SELECT); + tools_switch(desktop, TOOLS_NODES); + } +} + +bool LPEFilletChamfer::nodeIsSelected(Geom::Point nodePoint, std::vector<Geom::Point> point) +{ + if (point.size() > 0) { + for (std::vector<Geom::Point>::iterator i = point.begin(); i != point.end(); + ++i) { + Geom::Point p = *i; + if (Geom::are_near(p, nodePoint, 2)) { + return true; + } + } + } + return false; +} +void LPEFilletChamfer::doUpdateFillet(std::vector<Geom::Path> const& original_pathv, double power) +{ + std::vector<Geom::Point> point; + SPDesktop *desktop = inkscape_active_desktop(); + if (INK_IS_NODE_TOOL(desktop->event_context)) { + Inkscape::UI::Tools::NodeTool *nodeTool = INK_NODE_TOOL(desktop->event_context); + Inkscape::UI::ControlPointSelection::Set &selection = nodeTool->_selected_nodes->allPoints(); + std::vector<Geom::Point>::iterator pBegin; + for (Inkscape::UI::ControlPointSelection::Set::iterator i = selection.begin(); i != selection.end(); ++i) { + if ((*i)->selected()) { + Inkscape::UI::Node *n = dynamic_cast<Inkscape::UI::Node *>(*i); + pBegin = point.begin(); + point.insert(pBegin, desktop->doc2dt(n->position())); + } + } + } + std::vector<Point> filletChamferData = fillet_chamfer_values.data(); + std::vector<Geom::Point> result; + std::vector<Geom::Path> original_pathv_processed = pathv_to_linear_and_cubic_beziers(original_pathv); + int counter = 0; + for (PathVector::const_iterator path_it = original_pathv_processed.begin(); + path_it != original_pathv_processed.end(); ++path_it) { + if (path_it->empty()) + continue; + + 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(); + if (path_it->closed() && path_it->back_closed().isDegenerate()) { + const Curve &closingline = path_it->back_closed(); + if (are_near(closingline.initialPoint(), closingline.finalPoint())) { + curve_endit = path_it->end_open(); + } + } + 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); + } + if (ignore_radius_0 && (filletChamferData[counter][X] == 0 || + filletChamferData[counter][X] == counter)) { + powerend = filletChamferData[counter][X]; + } + if (filletChamferData[counter][Y] == 0) { + powerend = filletChamferData[counter][X]; + } + if (only_selected && !nodeIsSelected(curve_it1->initialPoint(), point)) { + powerend = filletChamferData[counter][X]; + } + result.push_back(Point(powerend, filletChamferData[counter][Y])); + ++curve_it1; + ++curve_it2; + counter++; + } + } + fillet_chamfer_values.param_set_and_write_new_value(result); +} + +void LPEFilletChamfer::doChangeType(std::vector<Geom::Path> const& original_pathv, int type) +{ + std::vector<Geom::Point> point; + SPDesktop *desktop = inkscape_active_desktop(); + if (INK_IS_NODE_TOOL(desktop->event_context)) { + Inkscape::UI::Tools::NodeTool *nodeTool = + INK_NODE_TOOL(desktop->event_context); + Inkscape::UI::ControlPointSelection::Set &selection = + nodeTool->_selected_nodes->allPoints(); + std::vector<Geom::Point>::iterator pBegin; + for (Inkscape::UI::ControlPointSelection::Set::iterator i = + selection.begin(); + i != selection.end(); ++i) { + if ((*i)->selected()) { + Inkscape::UI::Node *n = dynamic_cast<Inkscape::UI::Node *>(*i); + pBegin = point.begin(); + point.insert(pBegin, desktop->doc2dt(n->position())); + } + } + } + std::vector<Point> filletChamferData = fillet_chamfer_values.data(); + std::vector<Geom::Point> result; + std::vector<Geom::Path> original_pathv_processed = pathv_to_linear_and_cubic_beziers(original_pathv); + int counter = 0; + 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; + + 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(); + if (path_it->closed() && path_it->back_closed().isDegenerate()) { + const Curve &closingline = path_it->back_closed(); + if (are_near(closingline.initialPoint(), closingline.finalPoint())) { + curve_endit = path_it->end_open(); + } + } + while (curve_it1 != curve_endit) { + bool toggle = true; + if (filletChamferData[counter][Y] == 0 || + (ignore_radius_0 && (filletChamferData[counter][X] == 0 || + filletChamferData[counter][X] == counter)) || + (only_selected && + !nodeIsSelected(curve_it1->initialPoint(), point))) { + toggle = false; + } + if (toggle) { + result.push_back(Point(filletChamferData[counter][X], type)); + } else { + result.push_back(filletChamferData[counter]); + } + ++curve_it1; + if (curve_it2 != curve_endit) { + ++curve_it2; + } + counter++; + pathCounter++; + } + } + fillet_chamfer_values.param_set_and_write_new_value(result); +} + +void LPEFilletChamfer::doOnApply(SPLPEItem const *lpeItem) +{ + if (SP_IS_SHAPE(lpeItem)) { + std::vector<Point> point; + PathVector const &original_pathv = pathv_to_linear_and_cubic_beziers(SP_SHAPE(lpeItem)->_curve->get_pathvector()); + Piecewise<D2<SBasis> > 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()) + continue; + + 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(); + 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(); + } + } + int counter = 0; + while (curve_it1 != curve_endit) { + std::pair<std::size_t, std::size_t> positions = fillet_chamfer_values.get_positions(counter, original_pathv); + Geom::NodeType nodetype; + if (positions.second == 0) { + if (path_it->closed()) { + Piecewise<D2<SBasis> > 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; + } + } else { + nodetype = get_nodetype((*path_it)[counter - 1], *curve_it1); + } + if (nodetype == NODE_CUSP) { + point.push_back(Point(0, 1)); + } else { + point.push_back(Point(0, 0)); + } + ++curve_it1; + if (curve_it2 != curve_endit) { + ++curve_it2; + } + counter++; + } + } + fillet_chamfer_values.param_set_and_write_new_value(point); + } else { + g_warning("LPE Fillet can only be applied to shapes (not groups)."); + } +} + +void LPEFilletChamfer::doBeforeEffect(SPLPEItem const *lpeItem) +{ + if (SP_IS_SHAPE(lpeItem)) { + if(hide_knots){ + fillet_chamfer_values.set_helper_size(0); + } else { + 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<SPPath const *>(lpeItem) + ->get_original_curve() + : SP_SHAPE(lpeItem)->getCurve(); + std::vector<Point> filletChamferData = fillet_chamfer_values.data(); + if (!filletChamferData.empty() && getKnotsNumber(c) != (int) + filletChamferData.size()) { + PathVector const original_pathv = pathv_to_linear_and_cubic_beziers(c->get_pathvector()); + Piecewise<D2<SBasis> > pwd2_in = paths_to_pw(original_pathv); + fillet_chamfer_values.recalculate_controlpoints_for_new_pwd2(pwd2_in); + } + } else { + g_warning("LPE Fillet can only be applied to shapes (not groups)."); + } +} + +int LPEFilletChamfer::getKnotsNumber(SPCurve const *c) +{ + int nKnots = c->nodes_in_path(); + PathVector const pv = pathv_to_linear_and_cubic_beziers(c->get_pathvector()); + for (std::vector<Geom::Path>::const_iterator path_it = pv.begin(); + path_it != pv.end(); ++path_it) { + if (!(*path_it).closed()) { + nKnots--; + } + } + return nKnots; +} + +void +LPEFilletChamfer::adjustForNewPath(std::vector<Geom::Path> const &path_in) +{ + if (!path_in.empty()) { + fillet_chamfer_values.recalculate_controlpoints_for_new_pwd2(pathv_to_linear_and_cubic_beziers(path_in)[0].toPwSb()); + } +} + +std::vector<Geom::Path> +LPEFilletChamfer::doEffect_path(std::vector<Geom::Path> const &path_in) +{ + std::vector<Geom::Path> pathvector_out; + Piecewise<D2<SBasis> > pwd2_in = paths_to_pw(pathv_to_linear_and_cubic_beziers(path_in)); + pwd2_in = remove_short_cuts(pwd2_in, .01); + Piecewise<D2<SBasis> > der = derivative(pwd2_in); + Piecewise<D2<SBasis> > n = rot90(unitVector(der)); + fillet_chamfer_values.set_pwd2(pwd2_in, n); + std::vector<Point> filletChamferData = fillet_chamfer_values.data(); + unsigned int counter = 0; + const double K = (4.0 / 3.0) * (sqrt(2.0) - 1.0); + std::vector<Geom::Path> 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; + 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(); + 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(); + } + } + unsigned int counterCurves = 0; + while (curve_it1 != curve_endit) { + Curve *curve_it2Fixed = (*path_it->begin()).duplicate(); + if(!path_it->closed() || curve_it2 != curve_endit){ + curve_it2Fixed = (*curve_it2).duplicate(); + } + bool last = curve_it2 == curve_endit; + std::vector<double> 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(times[0])); + } + Curve *knotCurve2 = curve_it2Fixed->portion(times[2], 1); + Point startArcPoint = knotCurve1->finalPoint(); + 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<Geom::CubicBezier const *>(&*knotCurve1); + Ray ray1(startArcPoint, curve_it1->finalPoint()); + if (cubic1) { + ray1.setPoints((*cubic1)[2], startArcPoint); + } + Point handle1 = Point::polar(ray1.angle(),k1) + startArcPoint; + Geom::CubicBezier const *cubic2 = + dynamic_cast<Geom::CubicBezier const *>(&*knotCurve2); + Ray ray2(curve_it1->finalPoint(), endArcPoint); + if (cubic2) { + ray2.setPoints(endArcPoint, (*cubic2)[1]); + } + Point handle2 = endArcPoint - Point::polar(ray2.angle(),k2); + bool ccwToggle = cross(curve_it1->finalPoint() - startArcPoint, endArcPoint - startArcPoint) < 0; + double angle = angle_between(ray1, ray2, ccwToggle); + double handleAngle = ray1.angle() - angle; + if (ccwToggle) { + handleAngle = ray1.angle() + angle; + } + Point inverseHandle1 = Point::polar(handleAngle,k1) + startArcPoint; + handleAngle = ray2.angle() + angle; + if (ccwToggle) { + handleAngle = ray2.angle() - angle; + } + Point inverseHandle2 = endArcPoint - Point::polar(handleAngle,k2); + //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() && last){ + type = abs(filletChamferData[counter - counterCurves][Y]); + } else if (!path_it->closed() && last){ + //0 + } else { + type = abs(filletChamferData[counter + 1][Y]); + } + if (type == 3 || type == 4) { + if (type == 4) { + Geom::Point central = middle_point(startArcPoint, endArcPoint); + LineSegment chamferCenter(central, curve_it1->finalPoint()); + path_out.appendNew<Geom::LineSegment>(chamferCenter.pointAt(0.5)); + } + path_out.appendNew<Geom::LineSegment>(endArcPoint); + } else if (type == 2) { + if((is_straight_curve(*curve_it1) && is_straight_curve(*curve_it2Fixed) && method != FM_BEZIER )|| method == FM_ARC){ + ccwToggle = ccwToggle?0:1; + path_out.appendNew<SVGEllipticalArc>(rx, ry, angleArc, 0, ccwToggle, endArcPoint); + }else{ + path_out.appendNew<Geom::CubicBezier>(inverseHandle1, inverseHandle2, endArcPoint); + } + } else { + if((is_straight_curve(*curve_it1) && is_straight_curve(*curve_it2Fixed) && method != FM_BEZIER )|| method == FM_ARC){ + path_out.appendNew<SVGEllipticalArc>(rx, ry, angleArc, 0, ccwToggle, endArcPoint); + } else { + path_out.appendNew<Geom::CubicBezier>(handle1, handle2, endArcPoint); + } + } + } else { + path_out.append(*knotCurve1); + } + if (path_it->closed() && last) { + path_out.close(); + } + ++curve_it1; + if (curve_it2 != curve_endit) { + ++curve_it2; + } + counter++; + counterCurves++; + } + pathvector_out.push_back(path_out); + } + return pathvector_out; +} + +}; //namespace LivePathEffect +}; /* namespace Inkscape */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offset:((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-fillet-chamfer.h b/src/live_effects/lpe-fillet-chamfer.h new file mode 100644 index 000000000..873684101 --- /dev/null +++ b/src/live_effects/lpe-fillet-chamfer.h @@ -0,0 +1,101 @@ +#ifndef INKSCAPE_LPE_FILLET_CHAMFER_H +#define INKSCAPE_LPE_FILLET_CHAMFER_H + +/* + * Author(s): + * Jabiertxo Arraiza Cenoz <jabier.arraiza@marker.es> + * + * Copyright (C) 2014 Author(s) + * + * Special thanks to Johan Engelen for the base of the effect -powerstroke- + * Also to ScislaC for point me to the idea + * Also su_v for his construvtive feedback and time + * and finaly to Liam P. White for his big help on coding, that save me a lot of hours + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#if HAVE_CONFIG_H +# include "config.h" +#endif + +#if defined(GLIBMM_DISABLE_DEPRECATED) && defined(HAVE_GLIBMM_THREADS_H) +# include <glibmm/threads.h> +#endif + +#include "live_effects/parameter/enum.h" +#include "live_effects/parameter/bool.h" +#include "live_effects/parameter/unit.h" + +#include "live_effects/parameter/filletchamferpointarray.h" +#include "live_effects/effect.h" + +namespace Inkscape { +namespace LivePathEffect { + +enum FilletMethod { + FM_AUTO, + FM_ARC, + FM_BEZIER, + FM_END +}; + +class LPEFilletChamfer : public Effect { +public: + LPEFilletChamfer(LivePathEffectObject *lpeobject); + virtual ~LPEFilletChamfer(); + + virtual std::vector<Geom::Path> doEffect_path(std::vector<Geom::Path> const &path_in); + + virtual void doOnApply(SPLPEItem const *lpeItem); + virtual void doBeforeEffect(SPLPEItem const *lpeItem); + virtual void adjustForNewPath(std::vector<Geom::Path> const &path_in); + virtual Gtk::Widget* newWidget(); + + int getKnotsNumber(SPCurve const *c); + void toggleHide(); + void toggleFlexFixed(); + void chamfer(); + void fillet(); + void doubleChamfer(); + void inverse(); + void updateFillet(); + void doUpdateFillet(std::vector<Geom::Path> const& original_pathv, double power); + void doChangeType(std::vector<Geom::Path> const& original_pathv, int type); + bool nodeIsSelected(Geom::Point nodePoint, std::vector<Geom::Point> points); + void refreshKnots(); + + FilletChamferPointArrayParam fillet_chamfer_values; + +private: + + BoolParam hide_knots; + BoolParam ignore_radius_0; + BoolParam only_selected; + BoolParam flexible; + BoolParam use_knot_distance; + UnitParam unit; + EnumParam<FilletMethod> method; + ScalarParam radius; + ScalarParam helper_size; + + LPEFilletChamfer(const LPEFilletChamfer &); + LPEFilletChamfer &operator=(const LPEFilletChamfer &); + +}; + +} //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-interpolate_points.cpp b/src/live_effects/lpe-interpolate_points.cpp new file mode 100644 index 000000000..4ac139752 --- /dev/null +++ b/src/live_effects/lpe-interpolate_points.cpp @@ -0,0 +1,94 @@ +/** \file + * LPE interpolate_points implementation + * Interpolates between knots of the input path. + */ +/* + * Authors: + * Johan Engelen + * + * Copyright (C) Johan Engelen 2014 <j.b.c.engelen@alumnus.utwente.nl> + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "live_effects/lpe-interpolate_points.h" + +#include <2geom/path.h> + +#include "live_effects/lpe-powerstroke-interpolators.h" + +namespace Inkscape { +namespace LivePathEffect { + + +static const Util::EnumData<unsigned> InterpolatorTypeData[] = { + {Geom::Interpolate::INTERP_LINEAR , N_("Linear"), "Linear"}, + {Geom::Interpolate::INTERP_CUBICBEZIER , N_("CubicBezierFit"), "CubicBezierFit"}, + {Geom::Interpolate::INTERP_CUBICBEZIER_JOHAN , N_("CubicBezierJohan"), "CubicBezierJohan"}, + {Geom::Interpolate::INTERP_SPIRO , N_("SpiroInterpolator"), "SpiroInterpolator"}, + {Geom::Interpolate::INTERP_CENTRIPETAL_CATMULLROM, N_("Centripetal Catmull-Rom"), "CentripetalCatmullRom"} +}; +static const Util::EnumDataConverter<unsigned> InterpolatorTypeConverter(InterpolatorTypeData, sizeof(InterpolatorTypeData)/sizeof(*InterpolatorTypeData)); + + +LPEInterpolatePoints::LPEInterpolatePoints(LivePathEffectObject *lpeobject) + : Effect(lpeobject) + , 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_CENTRIPETAL_CATMULLROM) +{ + show_orig_path = false; + + registerParameter( &interpolator_type ); +} + +LPEInterpolatePoints::~LPEInterpolatePoints() +{ +} + + +Geom::PathVector +LPEInterpolatePoints::doEffect_path (Geom::PathVector const & path_in) +{ + Geom::PathVector path_out; + + std::auto_ptr<Geom::Interpolate::Interpolator> interpolator( Geom::Interpolate::Interpolator::create(static_cast<Geom::Interpolate::InterpolatorType>(interpolator_type.get_value())) ); + + for(Geom::PathVector::const_iterator path_it = path_in.begin(); path_it != path_in.end(); ++path_it) { + if (path_it->empty()) + continue; + + if (path_it->closed()) { + g_warning("Interpolate points LPE currently ignores whether path is closed or not."); + } + + std::vector<Geom::Point> pts; + pts.push_back(path_it->initialPoint()); + + for (Geom::Path::const_iterator it = path_it->begin(), e = path_it->end_default(); it != e; ++it) { + pts.push_back((*it).finalPoint()); + } + + Geom::Path path = interpolator->interpolateToPath(pts); + + path_out.push_back(path); + } + + 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-interpolate_points.h b/src/live_effects/lpe-interpolate_points.h new file mode 100644 index 000000000..7a3364747 --- /dev/null +++ b/src/live_effects/lpe-interpolate_points.h @@ -0,0 +1,51 @@ +#ifndef INKSCAPE_LPE_INTERPOLATEPOINTS_H +#define INKSCAPE_LPE_INTERPOLATEPOINTS_H + +/** \file + * LPE interpolate_points implementation, see lpe-interpolate_points.cpp. + */ + +/* + * Authors: + * Johan Engelen + * + * Copyright (C) Johan Engelen 2014 <j.b.c.engelen@alumnus.utwente.nl> + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "live_effects/parameter/enum.h" +#include "live_effects/effect.h" + +namespace Inkscape { +namespace LivePathEffect { + +class LPEInterpolatePoints : public Effect { +public: + LPEInterpolatePoints(LivePathEffectObject *lpeobject); + virtual ~LPEInterpolatePoints(); + + virtual std::vector<Geom::Path> doEffect_path (std::vector<Geom::Path> const & path_in); + +private: + EnumParam<unsigned> interpolator_type; + + LPEInterpolatePoints(const LPEInterpolatePoints&); + LPEInterpolatePoints& operator=(const LPEInterpolatePoints&); +}; + +} //namespace LivePathEffect +} //namespace Inkscape + +#endif // INKSCAPE_LPE_INTERPOLATEPOINTS_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 : 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<unsigned> 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<unsigned> 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<unsigned> CapTypeConverter(CapTypeData, sizeof(CapTypeData)/sizeof(*CapTypeData)); +static const Util::EnumDataConverter<unsigned> 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<SPLPEItem*>(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<SPLPEItem*>(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<Geom::Path> LPEJoinType::doEffect_path(std::vector<Geom::Path> const & path_in) +{ + return Outline::PathVectorOutline(path_in, line_width, static_cast<ButtTypeMod>(linecap_type.get_value()), + static_cast<LineJoinType>(linejoin_type.get_value()), + (attempt_force_join ? std::numeric_limits<double>::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 <Geom::Path> doEffect_path (std::vector<Geom::Path> const & path_in);
+
+private:
+ LPEJoinType(const LPEJoinType&);
+ LPEJoinType& operator=(const LPEJoinType&);
+
+ ScalarParam line_width;
+ EnumParam<unsigned> linecap_type;
+ EnumParam<unsigned> 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 <glibmm/i18n.h> +#include <gdk/gdk.h> #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<Geom::PathVector> &hp_vec); + virtual void addCanvasIndicators(SPLPEItem const *lpeitem, std::vector<Geom::PathVector> &hp_vec); + std::vector<Geom::Path> supplied_path; //for knotholder business private: void updateSwitcher(); diff --git a/src/live_effects/lpe-lattice.cpp b/src/live_effects/lpe-lattice.cpp index 8136569e8..a241a8a2e 100644 --- a/src/live_effects/lpe-lattice.cpp +++ b/src/live_effects/lpe-lattice.cpp @@ -176,6 +176,9 @@ void LPELattice::doBeforeEffect (SPLPEItem const* lpeitem) { original_bbox(lpeitem); + SPLPEItem * item = const_cast<SPLPEItem*>(lpeitem); + item->apply_to_clippath(item); + item->apply_to_mask(item); } void diff --git a/src/live_effects/lpe-lattice.h b/src/live_effects/lpe-lattice.h index a44dda3fd..5eb48909b 100644 --- a/src/live_effects/lpe-lattice.h +++ b/src/live_effects/lpe-lattice.h @@ -58,7 +58,6 @@ private: PointParam grid_point13; PointParam grid_point14; PointParam grid_point15; - LPELattice(const LPELattice&); LPELattice& operator=(const LPELattice&); }; diff --git a/src/live_effects/lpe-lattice2.cpp b/src/live_effects/lpe-lattice2.cpp new file mode 100644 index 000000000..8fadd79ed --- /dev/null +++ b/src/live_effects/lpe-lattice2.cpp @@ -0,0 +1,499 @@ +/** \file + * LPE <lattice2> implementation + + */ +/* + * Authors: + * Johan Engelen <j.b.c.engelen@utwente.nl> + * Steren Giannini + * Noé Falzon + * Victor Navez + * ~suv + * Jabiertxo Arraiza +* +* Copyright (C) 2007-2008 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "live_effects/lpe-lattice2.h" + +#include "sp-shape.h" +#include "sp-item.h" +#include "sp-path.h" +#include "display/curve.h" +#include "svg/svg.h" + +#include <2geom/sbasis.h> +#include <2geom/sbasis-2d.h> +#include <2geom/sbasis-geometric.h> +#include <2geom/bezier-to-sbasis.h> +#include <2geom/sbasis-to-bezier.h> +#include <2geom/d2.h> +#include <2geom/piecewise.h> +#include <2geom/transforms.h> +#include "ui/tools-switch.h" + +#include "desktop.h" // TODO: should be factored out (see below) + +using namespace Geom; + +namespace Inkscape { +namespace LivePathEffect { + +LPELattice2::LPELattice2(LivePathEffectObject *lpeobject) : + Effect(lpeobject), + // initialise your parameters here: + grid_point0(_("Control handle 0:"), _("Control handle 0 - Ctrl+Alt+Click to reset"), "gridpoint0", &wr, this), + grid_point1(_("Control handle 1:"), _("Control handle 1 - Ctrl+Alt+Click to reset"), "gridpoint1", &wr, this), + grid_point2(_("Control handle 2:"), _("Control handle 2 - Ctrl+Alt+Click to reset"), "gridpoint2", &wr, this), + grid_point3(_("Control handle 3:"), _("Control handle 3 - Ctrl+Alt+Click to reset"), "gridpoint3", &wr, this), + grid_point4(_("Control handle 4:"), _("Control handle 4 - Ctrl+Alt+Click to reset"), "gridpoint4", &wr, this), + grid_point5(_("Control handle 5:"), _("Control handle 5 - Ctrl+Alt+Click to reset"), "gridpoint5", &wr, this), + grid_point6(_("Control handle 6:"), _("Control handle 6 - Ctrl+Alt+Click to reset"), "gridpoint6", &wr, this), + grid_point7(_("Control handle 7:"), _("Control handle 7 - Ctrl+Alt+Click to reset"), "gridpoint7", &wr, this), + grid_point8x9(_("Control handle 8x9:"), _("Control handle 8x9 - Ctrl+Alt+Click to reset"), "gridpoint8x9", &wr, this), + grid_point10x11(_("Control handle 10x11:"), _("Control handle 10x11 - Ctrl+Alt+Click to reset"), "gridpoint10x11", &wr, this), + grid_point12(_("Control handle 12:"), _("Control handle 12 - Ctrl+Alt+Click to reset"), "gridpoint12", &wr, this), + grid_point13(_("Control handle 13:"), _("Control handle 13 - Ctrl+Alt+Click to reset"), "gridpoint13", &wr, this), + grid_point14(_("Control handle 14:"), _("Control handle 14 - Ctrl+Alt+Click to reset"), "gridpoint14", &wr, this), + grid_point15(_("Control handle 15:"), _("Control handle 15 - Ctrl+Alt+Click to reset"), "gridpoint15", &wr, this), + grid_point16(_("Control handle 16:"), _("Control handle 16 - Ctrl+Alt+Click to reset"), "gridpoint16", &wr, this), + grid_point17(_("Control handle 17:"), _("Control handle 17 - Ctrl+Alt+Click to reset"), "gridpoint17", &wr, this), + grid_point18(_("Control handle 18:"), _("Control handle 18 - Ctrl+Alt+Click to reset"), "gridpoint18", &wr, this), + grid_point19(_("Control handle 19:"), _("Control handle 19 - Ctrl+Alt+Click to reset"), "gridpoint19", &wr, this), + grid_point20x21(_("Control handle 20x21:"), _("Control handle 20x21 - Ctrl+Alt+Click to reset"), "gridpoint20x21", &wr, this), + grid_point22x23(_("Control handle 22x23:"), _("Control handle 22x23 - Ctrl+Alt+Click to reset"), "gridpoint22x23", &wr, this), + grid_point24x26(_("Control handle 24x26:"), _("Control handle 24x26 - Ctrl+Alt+Click to reset"), "gridpoint24x26", &wr, this), + grid_point25x27(_("Control handle 25x27:"), _("Control handle 25x27 - Ctrl+Alt+Click to reset"), "gridpoint25x27", &wr, this), + grid_point28x30(_("Control handle 28x30:"), _("Control handle 28x30 - Ctrl+Alt+Click to reset"), "gridpoint28x30", &wr, this), + grid_point29x31(_("Control handle 29x31:"), _("Control handle 29x31 - Ctrl+Alt+Click to reset"), "gridpoint29x31", &wr, this), + grid_point32x33x34x35(_("Control handle 32x33x34x35:"), _("Control handle 32x33x34x35 - Ctrl+Alt+Click to reset"), "gridpoint32x33x34x35", &wr, this) + + +{ + // register all your parameters here, so Inkscape knows which parameters this effect has: + registerParameter( dynamic_cast<Parameter *>(&grid_point0) ); + registerParameter( dynamic_cast<Parameter *>(&grid_point1) ); + registerParameter( dynamic_cast<Parameter *>(&grid_point2) ); + registerParameter( dynamic_cast<Parameter *>(&grid_point3) ); + registerParameter( dynamic_cast<Parameter *>(&grid_point4) ); + registerParameter( dynamic_cast<Parameter *>(&grid_point5) ); + registerParameter( dynamic_cast<Parameter *>(&grid_point6) ); + registerParameter( dynamic_cast<Parameter *>(&grid_point7) ); + registerParameter( dynamic_cast<Parameter *>(&grid_point8x9) ); + registerParameter( dynamic_cast<Parameter *>(&grid_point10x11) ); + registerParameter( dynamic_cast<Parameter *>(&grid_point12) ); + registerParameter( dynamic_cast<Parameter *>(&grid_point13) ); + registerParameter( dynamic_cast<Parameter *>(&grid_point14) ); + registerParameter( dynamic_cast<Parameter *>(&grid_point15) ); + registerParameter( dynamic_cast<Parameter *>(&grid_point16) ); + registerParameter( dynamic_cast<Parameter *>(&grid_point17) ); + registerParameter( dynamic_cast<Parameter *>(&grid_point18) ); + registerParameter( dynamic_cast<Parameter *>(&grid_point19) ); + registerParameter( dynamic_cast<Parameter *>(&grid_point20x21) ); + registerParameter( dynamic_cast<Parameter *>(&grid_point22x23) ); + registerParameter( dynamic_cast<Parameter *>(&grid_point24x26) ); + registerParameter( dynamic_cast<Parameter *>(&grid_point25x27) ); + registerParameter( dynamic_cast<Parameter *>(&grid_point28x30) ); + registerParameter( dynamic_cast<Parameter *>(&grid_point29x31) ); + registerParameter( dynamic_cast<Parameter *>(&grid_point32x33x34x35) ); +} + +LPELattice2::~LPELattice2() +{ +} + +Geom::Piecewise<Geom::D2<Geom::SBasis> > +LPELattice2::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in) +{ + D2<SBasis2d> sb2; + + //Initialisation of the sb2 + for(unsigned dim = 0; dim < 2; dim++) { + sb2[dim].us = 3; + sb2[dim].vs = 3; + const int depth = sb2[dim].us*sb2[dim].vs; + sb2[dim].resize(depth, Linear2d(0)); + } + + //Grouping the point params in a convenient vector + std::vector<Geom::Point *> handles(36); + + handles[0] = &grid_point0; + handles[1] = &grid_point1; + handles[2] = &grid_point2; + handles[3] = &grid_point3; + handles[4] = &grid_point4; + handles[5] = &grid_point5; + handles[6] = &grid_point6; + handles[7] = &grid_point7; + handles[8] = &grid_point8x9; + handles[9] = &grid_point8x9; + handles[10] = &grid_point10x11; + handles[11] = &grid_point10x11; + handles[12] = &grid_point12; + handles[13] = &grid_point13; + handles[14] = &grid_point14; + handles[15] = &grid_point15; + handles[16] = &grid_point16; + handles[17] = &grid_point17; + handles[18] = &grid_point18; + handles[19] = &grid_point19; + handles[20] = &grid_point20x21; + handles[21] = &grid_point20x21; + handles[22] = &grid_point22x23; + handles[23] = &grid_point22x23; + handles[24] = &grid_point24x26; + handles[25] = &grid_point25x27; + handles[26] = &grid_point24x26; + handles[27] = &grid_point25x27; + handles[28] = &grid_point28x30; + handles[29] = &grid_point29x31; + handles[30] = &grid_point28x30; + handles[31] = &grid_point29x31; + handles[32] = &grid_point32x33x34x35; + handles[33] = &grid_point32x33x34x35; + handles[34] = &grid_point32x33x34x35; + handles[35] = &grid_point32x33x34x35; + + Geom::Point origin = Geom::Point(boundingbox_X.min(),boundingbox_Y.min()); + + double width = boundingbox_X.extent(); + double height = boundingbox_Y.extent(); + + //numbering is based on 4 rectangles.16 + for(unsigned dim = 0; dim < 2; dim++) { + Geom::Point dir(0,0); + dir[dim] = 1; + for(unsigned vi = 0; vi < sb2[dim].vs; vi++) { + for(unsigned ui = 0; ui < sb2[dim].us; ui++) { + for(unsigned iv = 0; iv < 2; iv++) { + for(unsigned iu = 0; iu < 2; iu++) { + unsigned corner = iu + 2*iv; + unsigned i = ui + vi*sb2[dim].us; + + //This is the offset from the Upperleft point + Geom::Point base( (ui + iu*(4-2*ui))*width/4., + (vi + iv*(4-2*vi))*height/4.); + + //Special action for corners + if(vi == 0 && ui == 0) { + base = Geom::Point(0,0); + } + + // i = Upperleft corner of the considerated rectangle + // corner = actual corner of the rectangle + // origin = Upperleft point + double dl = dot((*handles[corner+4*i] - (base + origin)), dir)/dot(dir,dir); + sb2[dim][i][corner] = dl/( dim ? height : width )*pow(4.0,ui+vi); + } + } + } + } + } + + Piecewise<D2<SBasis> > output; + output.push_cut(0.); + for(unsigned i = 0; i < pwd2_in.size(); i++) { + D2<SBasis> B = pwd2_in[i]; + B[Geom::X] -= origin[Geom::X]; + B[Geom::X]*= 1/width; + B[Geom::Y] -= origin[Geom::Y]; + B[Geom::Y]*= 1/height; + //Here comes the magic + D2<SBasis> tB = compose_each(sb2,B); + tB[Geom::X] = tB[Geom::X] * width + origin[Geom::X]; + tB[Geom::Y] = tB[Geom::Y] * height + origin[Geom::Y]; + + output.push(tB,i+1); + } + return output; +} + +Gtk::Widget * +LPELattice2::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); + Gtk::Button* resetButton = Gtk::manage(new Gtk::Button(Glib::ustring(_("Reset grid")))); + resetButton->set_alignment(0.0, 0.5); + resetButton->signal_clicked().connect(sigc::mem_fun (*this,&LPELattice2::resetGrid)); + Gtk::Widget* resetButtonWidget = dynamic_cast<Gtk::Widget *>(resetButton); + resetButtonWidget->set_tooltip_text("Reset grid"); + vbox->pack_start(*resetButtonWidget, true, true,2); + std::vector<Parameter *>::iterator it = param_vector.begin(); + while (it != param_vector.end()) { + if ((*it)->widget_is_visible) { + Parameter * param = *it; + Gtk::Widget * widg = dynamic_cast<Gtk::Widget *>(param->param_newWidget()); + if(param->param_key == "grid"){ + widg = NULL; + } + 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<Gtk::Widget *>(vbox); +} + +void +LPELattice2::doBeforeEffect (SPLPEItem const* lpeitem) +{ + original_bbox(lpeitem); + setDefaults(); + SPLPEItem * item = const_cast<SPLPEItem*>(lpeitem); + item->apply_to_clippath(item); + item->apply_to_mask(item); +} + +void +LPELattice2::setDefaults() +{ + Geom::Point gp0((boundingbox_X.max()-boundingbox_X.min())/4*0+boundingbox_X.min(), + (boundingbox_Y.max()-boundingbox_Y.min())/4*0+boundingbox_Y.min()); + + Geom::Point gp1((boundingbox_X.max()-boundingbox_X.min())/4*4+boundingbox_X.min(), + (boundingbox_Y.max()-boundingbox_Y.min())/4*0+boundingbox_Y.min()); + + Geom::Point gp2((boundingbox_X.max()-boundingbox_X.min())/4*0+boundingbox_X.min(), + (boundingbox_Y.max()-boundingbox_Y.min())/4*4+boundingbox_Y.min()); + + Geom::Point gp3((boundingbox_X.max()-boundingbox_X.min())/4*4+boundingbox_X.min(), + (boundingbox_Y.max()-boundingbox_Y.min())/4*4+boundingbox_Y.min()); + + Geom::Point gp4((boundingbox_X.max()-boundingbox_X.min())/4*1+boundingbox_X.min(), + (boundingbox_Y.max()-boundingbox_Y.min())/4*0+boundingbox_Y.min()); + + Geom::Point gp5((boundingbox_X.max()-boundingbox_X.min())/4*3+boundingbox_X.min(), + (boundingbox_Y.max()-boundingbox_Y.min())/4*0+boundingbox_Y.min()); + + Geom::Point gp6((boundingbox_X.max()-boundingbox_X.min())/4*1+boundingbox_X.min(), + (boundingbox_Y.max()-boundingbox_Y.min())/4*4+boundingbox_Y.min()); + + Geom::Point gp7((boundingbox_X.max()-boundingbox_X.min())/4*3+boundingbox_X.min(), + (boundingbox_Y.max()-boundingbox_Y.min())/4*4+boundingbox_Y.min()); + + Geom::Point gp8x9((boundingbox_X.max()-boundingbox_X.min())/4*2+boundingbox_X.min(), + (boundingbox_Y.max()-boundingbox_Y.min())/4*0+boundingbox_Y.min()); + + Geom::Point gp10x11((boundingbox_X.max()-boundingbox_X.min())/4*2+boundingbox_X.min(), + (boundingbox_Y.max()-boundingbox_Y.min())/4*4+boundingbox_Y.min()); + + Geom::Point gp12((boundingbox_X.max()-boundingbox_X.min())/4*0+boundingbox_X.min(), + (boundingbox_Y.max()-boundingbox_Y.min())/4*1+boundingbox_Y.min()); + + Geom::Point gp13((boundingbox_X.max()-boundingbox_X.min())/4*4+boundingbox_X.min(), + (boundingbox_Y.max()-boundingbox_Y.min())/4*1+boundingbox_Y.min()); + + Geom::Point gp14((boundingbox_X.max()-boundingbox_X.min())/4*0+boundingbox_X.min(), + (boundingbox_Y.max()-boundingbox_Y.min())/4*3+boundingbox_Y.min()); + + Geom::Point gp15((boundingbox_X.max()-boundingbox_X.min())/4*4+boundingbox_X.min(), + (boundingbox_Y.max()-boundingbox_Y.min())/4*3+boundingbox_Y.min()); + + Geom::Point gp16((boundingbox_X.max()-boundingbox_X.min())/4*1+boundingbox_X.min(), + (boundingbox_Y.max()-boundingbox_Y.min())/4*1+boundingbox_Y.min()); + + Geom::Point gp17((boundingbox_X.max()-boundingbox_X.min())/4*3+boundingbox_X.min(), + (boundingbox_Y.max()-boundingbox_Y.min())/4*1+boundingbox_Y.min()); + + Geom::Point gp18((boundingbox_X.max()-boundingbox_X.min())/4*1+boundingbox_X.min(), + (boundingbox_Y.max()-boundingbox_Y.min())/4*3+boundingbox_Y.min()); + + Geom::Point gp19((boundingbox_X.max()-boundingbox_X.min())/4*3+boundingbox_X.min(), + (boundingbox_Y.max()-boundingbox_Y.min())/4*3+boundingbox_Y.min()); + + Geom::Point gp20x21((boundingbox_X.max()-boundingbox_X.min())/4*2+boundingbox_X.min(), + (boundingbox_Y.max()-boundingbox_Y.min())/4*1+boundingbox_Y.min()); + + Geom::Point gp22x23((boundingbox_X.max()-boundingbox_X.min())/4*2+boundingbox_X.min(), + (boundingbox_Y.max()-boundingbox_Y.min())/4*3+boundingbox_Y.min()); + + Geom::Point gp24x26((boundingbox_X.max()-boundingbox_X.min())/4*0+boundingbox_X.min(), + (boundingbox_Y.max()-boundingbox_Y.min())/4*2+boundingbox_Y.min()); + + Geom::Point gp25x27((boundingbox_X.max()-boundingbox_X.min())/4*4+boundingbox_X.min(), + (boundingbox_Y.max()-boundingbox_Y.min())/4*2+boundingbox_Y.min()); + + Geom::Point gp28x30((boundingbox_X.max()-boundingbox_X.min())/4*1+boundingbox_X.min(), + (boundingbox_Y.max()-boundingbox_Y.min())/4*2+boundingbox_Y.min()); + + Geom::Point gp29x31((boundingbox_X.max()-boundingbox_X.min())/4*3+boundingbox_X.min(), + (boundingbox_Y.max()-boundingbox_Y.min())/4*2+boundingbox_Y.min()); + + Geom::Point gp32x33x34x35((boundingbox_X.max()-boundingbox_X.min())/4*2+boundingbox_X.min(), + (boundingbox_Y.max()-boundingbox_Y.min())/4*2+boundingbox_Y.min()); + + grid_point0.param_update_default(gp0); + grid_point1.param_update_default(gp1); + grid_point2.param_update_default(gp2); + grid_point3.param_update_default(gp3); + grid_point4.param_update_default(gp4); + grid_point5.param_update_default(gp5); + grid_point6.param_update_default(gp6); + grid_point7.param_update_default(gp7); + grid_point8x9.param_update_default(gp8x9); + grid_point10x11.param_update_default(gp10x11); + grid_point12.param_update_default(gp12); + grid_point13.param_update_default(gp13); + grid_point14.param_update_default(gp14); + grid_point15.param_update_default(gp15); + grid_point16.param_update_default(gp16); + grid_point17.param_update_default(gp17); + grid_point18.param_update_default(gp18); + grid_point19.param_update_default(gp19); + grid_point20x21.param_update_default(gp20x21); + grid_point22x23.param_update_default(gp22x23); + grid_point24x26.param_update_default(gp24x26); + grid_point25x27.param_update_default(gp25x27); + grid_point28x30.param_update_default(gp28x30); + grid_point29x31.param_update_default(gp29x31); + grid_point32x33x34x35.param_update_default(gp32x33x34x35); +} + +void +LPELattice2::resetGrid() +{ + grid_point0.param_set_and_write_default(); + grid_point1.param_set_and_write_default(); + grid_point2.param_set_and_write_default(); + grid_point3.param_set_and_write_default(); + grid_point4.param_set_and_write_default(); + grid_point5.param_set_and_write_default(); + grid_point6.param_set_and_write_default(); + grid_point7.param_set_and_write_default(); + grid_point8x9.param_set_and_write_default(); + grid_point10x11.param_set_and_write_default(); + grid_point12.param_set_and_write_default(); + grid_point13.param_set_and_write_default(); + grid_point14.param_set_and_write_default(); + grid_point15.param_set_and_write_default(); + grid_point16.param_set_and_write_default(); + grid_point17.param_set_and_write_default(); + grid_point18.param_set_and_write_default(); + grid_point19.param_set_and_write_default(); + grid_point20x21.param_set_and_write_default(); + grid_point22x23.param_set_and_write_default(); + grid_point24x26.param_set_and_write_default(); + grid_point25x27.param_set_and_write_default(); + grid_point28x30.param_set_and_write_default(); + grid_point29x31.param_set_and_write_default(); + 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(); + tools_switch(desktop, TOOLS_SELECT); + tools_switch(desktop, TOOLS_NODES); +} + +void +LPELattice2::resetDefaults(SPItem const* item) +{ + Effect::resetDefaults(item); + original_bbox(SP_LPE_ITEM(item)); + setDefaults(); + resetGrid(); +} + +void +LPELattice2::calculateCurve(Geom::Point a,Geom::Point b, SPCurve* c, bool horizontal, bool move) +{ + using Geom::X; + using Geom::Y; + if(move) c->moveto(a); + Geom::Point cubic1 = a + (1./3)* (b - a); + Geom::Point cubic2 = b + (1./3)* (a - b); + if(horizontal) c->curveto(Geom::Point(cubic1[X],a[Y]),Geom::Point(cubic2[X],b[Y]),b); + else c->curveto(Geom::Point(a[X],cubic1[Y]),Geom::Point(b[X],cubic2[Y]),b); +} + +void +LPELattice2::addCanvasIndicators(SPLPEItem const */*lpeitem*/, std::vector<Geom::PathVector> &hp_vec) +{ + hp_vec.clear(); + + SPCurve *c = new SPCurve(); + calculateCurve(grid_point0,grid_point4, c,true, true); + calculateCurve(grid_point4,grid_point8x9, c,true, false); + calculateCurve(grid_point8x9,grid_point5, c,true, false); + calculateCurve(grid_point5,grid_point1, c,true, false); + + calculateCurve(grid_point12,grid_point16, c,true, true); + calculateCurve(grid_point16,grid_point20x21, c,true, false); + calculateCurve(grid_point20x21,grid_point17, c,true, false); + calculateCurve(grid_point17,grid_point13, c,true, false); + + calculateCurve(grid_point24x26,grid_point28x30, c,true, true); + calculateCurve(grid_point28x30,grid_point32x33x34x35, c,true, false); + calculateCurve(grid_point32x33x34x35,grid_point29x31, c,true, false); + calculateCurve(grid_point29x31,grid_point25x27, c,true, false); + + calculateCurve(grid_point14,grid_point18, c,true, true); + calculateCurve(grid_point18,grid_point22x23, c,true, false); + calculateCurve(grid_point22x23,grid_point19, c,true, false); + calculateCurve(grid_point19,grid_point15, c,true, false); + + calculateCurve(grid_point2,grid_point6, c,true, true); + calculateCurve(grid_point6,grid_point10x11, c,true, false); + calculateCurve(grid_point10x11,grid_point7, c,true, false); + calculateCurve(grid_point7,grid_point3, c,true, false); + + calculateCurve(grid_point0,grid_point12, c,false, true); + calculateCurve(grid_point12,grid_point24x26, c,false, false); + calculateCurve(grid_point24x26,grid_point14, c,false, false); + calculateCurve(grid_point14,grid_point2, c,false, false); + + calculateCurve(grid_point4,grid_point16, c,false, true); + calculateCurve(grid_point16,grid_point28x30, c,false, false); + calculateCurve(grid_point28x30,grid_point18, c,false, false); + calculateCurve(grid_point18,grid_point6, c,false, false); + + calculateCurve(grid_point8x9,grid_point20x21, c,false, true); + calculateCurve(grid_point20x21,grid_point32x33x34x35, c,false, false); + calculateCurve(grid_point32x33x34x35,grid_point22x23, c,false, false); + calculateCurve(grid_point22x23,grid_point10x11, c,false, false); + + calculateCurve(grid_point5,grid_point17, c, false, true); + calculateCurve(grid_point17,grid_point29x31, c,false, false); + calculateCurve(grid_point29x31,grid_point19, c,false, false); + calculateCurve(grid_point19,grid_point7, c,false, false); + + calculateCurve(grid_point1,grid_point13, c, false, true); + calculateCurve(grid_point13,grid_point25x27, c,false, false); + calculateCurve(grid_point25x27,grid_point15, c,false, false); + calculateCurve(grid_point15,grid_point3, c, false, false); + hp_vec.push_back(c->get_pathvector()); +} + + +/* ######################## */ + +} //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-lattice2.h b/src/live_effects/lpe-lattice2.h new file mode 100644 index 000000000..461f835c6 --- /dev/null +++ b/src/live_effects/lpe-lattice2.h @@ -0,0 +1,92 @@ +#ifndef INKSCAPE_LPE_LATTICE2_H +#define INKSCAPE_LPE_LATTICE2_H + +/** \file + * LPE <lattice2> implementation, see lpe-lattice2.cpp. + */ + +/* + * Authors: + * Johan Engelen + * Steren Giannini + * Noé Falzon + * Victor Navez + * ~suv + * Jabiertxo Arraiza +* +* Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl> + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "live_effects/parameter/enum.h" +#include "live_effects/effect.h" +#include "live_effects/parameter/pointreseteable.h" +#include "live_effects/lpegroupbbox.h" + +namespace Inkscape { +namespace LivePathEffect { + +class LPELattice2 : public Effect, GroupBBoxEffect { +public: + + LPELattice2(LivePathEffectObject *lpeobject); + virtual ~LPELattice2(); + + virtual Geom::Piecewise<Geom::D2<Geom::SBasis> > doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in); + + virtual void resetDefaults(SPItem const* item); + + virtual void doBeforeEffect(SPLPEItem const* lpeitem); + + virtual Gtk::Widget * newWidget(); + + virtual void calculateCurve(Geom::Point a,Geom::Point b, SPCurve *c, bool horizontal, bool move); + + virtual void setDefaults(); + + virtual void resetGrid(); + + //virtual void original_bbox(SPLPEItem const* lpeitem, bool absolute = false); + + //virtual void addCanvasIndicators(SPLPEItem const*/*lpeitem*/, std::vector<Geom::PathVector> &/*hp_vec*/); + + //virtual std::vector<Geom::PathVector> getHelperPaths(SPLPEItem const* lpeitem); +protected: + void addCanvasIndicators(SPLPEItem const */*lpeitem*/, std::vector<Geom::PathVector> &hp_vec); +private: + + PointReseteableParam grid_point0; + PointReseteableParam grid_point1; + PointReseteableParam grid_point2; + PointReseteableParam grid_point3; + PointReseteableParam grid_point4; + PointReseteableParam grid_point5; + PointReseteableParam grid_point6; + PointReseteableParam grid_point7; + PointReseteableParam grid_point8x9; + PointReseteableParam grid_point10x11; + PointReseteableParam grid_point12; + PointReseteableParam grid_point13; + PointReseteableParam grid_point14; + PointReseteableParam grid_point15; + PointReseteableParam grid_point16; + PointReseteableParam grid_point17; + PointReseteableParam grid_point18; + PointReseteableParam grid_point19; + PointReseteableParam grid_point20x21; + PointReseteableParam grid_point22x23; + PointReseteableParam grid_point24x26; + PointReseteableParam grid_point25x27; + PointReseteableParam grid_point28x30; + PointReseteableParam grid_point29x31; + PointReseteableParam grid_point32x33x34x35; + + LPELattice2(const LPELattice2&); + LPELattice2& operator=(const LPELattice2&); +}; + +} //namespace LivePathEffect +} //namespace Inkscape + +#endif diff --git a/src/live_effects/lpe-mirror_symmetry.cpp b/src/live_effects/lpe-mirror_symmetry.cpp index 4d4837d8d..0bb67a4a2 100644 --- a/src/live_effects/lpe-mirror_symmetry.cpp +++ b/src/live_effects/lpe-mirror_symmetry.cpp @@ -43,6 +43,14 @@ LPEMirrorSymmetry::~LPEMirrorSymmetry() } void +LPEMirrorSymmetry::doBeforeEffect (SPLPEItem const* lpeitem) +{ + SPLPEItem * item = const_cast<SPLPEItem*>(lpeitem); + item->apply_to_clippath(item); + item->apply_to_mask(item); +} + +void LPEMirrorSymmetry::doOnApply (SPLPEItem const* lpeitem) { using namespace Geom; diff --git a/src/live_effects/lpe-mirror_symmetry.h b/src/live_effects/lpe-mirror_symmetry.h index c01d1bf6f..a4a2b86c0 100644 --- a/src/live_effects/lpe-mirror_symmetry.h +++ b/src/live_effects/lpe-mirror_symmetry.h @@ -30,6 +30,8 @@ public: virtual void doOnApply (SPLPEItem const* lpeitem); + virtual void doBeforeEffect (SPLPEItem const* lpeitem); + virtual std::vector<Geom::Path> doEffect_path (std::vector<Geom::Path> const & path_in); private: diff --git a/src/live_effects/lpe-offset.cpp b/src/live_effects/lpe-offset.cpp index 192bd17ca..dc91775b7 100644 --- a/src/live_effects/lpe-offset.cpp +++ b/src/live_effects/lpe-offset.cpp @@ -55,6 +55,14 @@ static void append_half_circle(Geom::Piecewise<Geom::D2<Geom::SBasis> > &pwd2, pwd2.continuousConcat(cap_pwd2); } +void +LPEOffset::doBeforeEffect (SPLPEItem const* lpeitem) +{ + SPLPEItem * item = const_cast<SPLPEItem*>(lpeitem); + item->apply_to_clippath(item); + item->apply_to_mask(item); +} + Geom::Piecewise<Geom::D2<Geom::SBasis> > LPEOffset::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in) { diff --git a/src/live_effects/lpe-offset.h b/src/live_effects/lpe-offset.h index 9966fd45d..997311c7d 100644 --- a/src/live_effects/lpe-offset.h +++ b/src/live_effects/lpe-offset.h @@ -28,6 +28,8 @@ public: virtual void doOnApply (SPLPEItem const* lpeitem); + virtual void doBeforeEffect (SPLPEItem const* lpeitem); + virtual Geom::Piecewise<Geom::D2<Geom::SBasis> > doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in); private: diff --git a/src/live_effects/lpe-perspective-envelope.cpp b/src/live_effects/lpe-perspective-envelope.cpp new file mode 100644 index 000000000..4d8c79198 --- /dev/null +++ b/src/live_effects/lpe-perspective-envelope.cpp @@ -0,0 +1,387 @@ +/** \file + * LPE <perspective-envelope> implementation + + */ +/* + * Authors: + * Jabiertxof Code migration from python extensions envelope and perspective + * Aaron Spike, aaron@ekips.org from envelope and perspective phyton code + * Dmitry Platonov, shadowjack@mail.ru, 2006 perspective approach & math + * Jose Hevia (freon) Transform algorithm from envelope + * + * Copyright (C) 2007-2014 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include <gtkmm.h> +#include "live_effects/lpe-perspective-envelope.h" +#include "helper/geom.h" +#include "display/curve.h" +#include "svg/svg.h" +#include "ui/tools-switch.h" +#include <gsl/gsl_linalg.h> +#include "desktop.h" + +using namespace Geom; + +namespace Inkscape { +namespace LivePathEffect { + +enum DeformationType { + DEFORMATION_PERSPECTIVE, + DEFORMATION_ENVELOPE +}; + +static const Util::EnumData<unsigned> DeformationTypeData[] = { + {DEFORMATION_PERSPECTIVE , N_("Perspective"), "Perspective"}, + {DEFORMATION_ENVELOPE , N_("Envelope deformation"), "Envelope deformation"} +}; + +static const Util::EnumDataConverter<unsigned> DeformationTypeConverter(DeformationTypeData, sizeof(DeformationTypeData)/sizeof(*DeformationTypeData)); + +LPEPerspectiveEnvelope::LPEPerspectiveEnvelope(LivePathEffectObject *lpeobject) : + Effect(lpeobject), + // initialise your parameters here: + deform_type(_("Type"), _("Select the type of deformation"), "deform_type", DeformationTypeConverter, &wr, this, DEFORMATION_PERSPECTIVE), + Up_Left_Point(_("Top Left"), _("Top Left - Ctrl+Alt+Click to reset"), "Up_Left_Point", &wr, this), + Up_Right_Point(_("Top Right"), _("Top Right - Ctrl+Alt+Click to reset"), "Up_Right_Point", &wr, this), + Down_Left_Point(_("Down Left"), _("Down Left - Ctrl+Alt+Click to reset"), "Down_Left_Point", &wr, this), + Down_Right_Point(_("Down Right"), _("Down Right - Ctrl+Alt+Click to reset"), "Down_Right_Point", &wr, this) +{ + // register all your parameters here, so Inkscape knows which parameters this effect has: + registerParameter( dynamic_cast<Parameter *>(&deform_type)); + registerParameter( dynamic_cast<Parameter *>(&Up_Left_Point) ); + registerParameter( dynamic_cast<Parameter *>(&Up_Right_Point) ); + registerParameter( dynamic_cast<Parameter *>(&Down_Left_Point) ); + registerParameter( dynamic_cast<Parameter *>(&Down_Right_Point) ); +} + +LPEPerspectiveEnvelope::~LPEPerspectiveEnvelope() +{ +} + +void LPEPerspectiveEnvelope::doEffect(SPCurve *curve) { + using Geom::X; + using Geom::Y; + double projmatrix[3][3]; + if(deform_type == DEFORMATION_PERSPECTIVE){ + std::vector<Geom::Point> handles(4); + handles[0] = Down_Left_Point; + handles[1] = Up_Left_Point; + handles[2] = Up_Right_Point; + handles[3] = Down_Right_Point; + std::vector<Geom::Point> sourceHandles(4); + sourceHandles[0] = Geom::Point(boundingbox_X.min(), boundingbox_Y.max()); + sourceHandles[1] = Geom::Point(boundingbox_X.min(), boundingbox_Y.min()); + sourceHandles[2] = Geom::Point(boundingbox_X.max(), boundingbox_Y.min()); + sourceHandles[3] = Geom::Point(boundingbox_X.max(), boundingbox_Y.max()); + double solmatrix[8][8] = {{0}}; + double free_term[8] = {0}; + double gslSolmatrix[64]; + for(unsigned int i = 0; i < 4; ++i){ + solmatrix[i][0] = sourceHandles[i][X]; + solmatrix[i][1] = sourceHandles[i][Y]; + solmatrix[i][2] = 1; + solmatrix[i][6] = -handles[i][X] * sourceHandles[i][X]; + solmatrix[i][7] = -handles[i][X] * sourceHandles[i][Y]; + solmatrix[i+4][3] = sourceHandles[i][X]; + solmatrix[i+4][4] = sourceHandles[i][Y]; + solmatrix[i+4][5] = 1; + solmatrix[i+4][6] = -handles[i][Y] * sourceHandles[i][X]; + solmatrix[i+4][7] = -handles[i][Y] * sourceHandles[i][Y]; + free_term[i] = handles[i][X]; + free_term[i+4] = handles[i][Y]; + } + int h = 0; + for( int i = 0; i < 8; i++ ) { + for( int j = 0; j < 8; j++ ) { + gslSolmatrix[h] = solmatrix[i][j]; + h++; + } + } + //this is get by this page: + //http://www.gnu.org/software/gsl/manual/html_node/Linear-Algebra-Examples.html#Linear-Algebra-Examples + gsl_matrix_view m = gsl_matrix_view_array (gslSolmatrix, 8, 8); + gsl_vector_view b = gsl_vector_view_array (free_term, 8); + gsl_vector *x = gsl_vector_alloc (8); + int s; + gsl_permutation * p = gsl_permutation_alloc (8); + gsl_linalg_LU_decomp (&m.matrix, p, &s); + gsl_linalg_LU_solve (&m.matrix, p, &b.vector, x); + h = 0; + for( int i = 0; i < 3; i++ ) { + for( int j = 0; j < 3; j++ ) { + if(h==8){ + projmatrix[2][2] = 1.0; + continue; + } + projmatrix[i][j] = gsl_vector_get(x, h); + h++; + } + } + gsl_permutation_free (p); + gsl_vector_free (x); + } + Geom::PathVector const original_pathv = pathv_to_linear_and_cubic_beziers(curve->get_pathvector()); + curve->reset(); + Geom::CubicBezier const *cubic = NULL; + Geom::Point pointAt1(0, 0); + Geom::Point pointAt2(0, 0); + Geom::Point pointAt3(0, 0); + 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 + SPCurve *nCurve = new SPCurve(); + 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(); + + 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(); + } + } + if(deform_type == DEFORMATION_PERSPECTIVE){ + nCurve->moveto(project_point(curve_it1->initialPoint(),projmatrix)); + }else{ + nCurve->moveto(project_point(curve_it1->initialPoint())); + } + while (curve_it1 != curve_endit) { + cubic = dynamic_cast<Geom::CubicBezier const *>(&*curve_it1); + if (cubic) { + pointAt1 = (*cubic)[1]; + pointAt2 = (*cubic)[2]; + } else { + pointAt1 = curve_it1->initialPoint(); + pointAt2 = curve_it1->finalPoint(); + } + pointAt3 = curve_it1->finalPoint(); + if(deform_type == DEFORMATION_PERSPECTIVE){ + pointAt1 = project_point(pointAt1,projmatrix); + pointAt2 = project_point(pointAt2,projmatrix); + pointAt3 = project_point(pointAt3,projmatrix); + }else{ + pointAt1 = project_point(pointAt1); + pointAt2 = project_point(pointAt2); + pointAt3 = project_point(pointAt3); + } + nCurve->curveto(pointAt1, pointAt2, pointAt3); + ++curve_it1; + if(curve_it2 != curve_endit) { + ++curve_it2; + } + } + //y cerramos la curva + if (path_it->closed()) { + nCurve->move_endpoints(pointAt3, pointAt3); + nCurve->closepath_current(); + } + curve->append(nCurve, false); + nCurve->reset(); + delete nCurve; + } +} + +Geom::Point +LPEPerspectiveEnvelope::project_point(Geom::Point p){ + double width = boundingbox_X.extent(); + double height = boundingbox_Y.extent(); + double delta_x = boundingbox_X.min() - p[X]; + double delta_y = boundingbox_Y.max() - p[Y]; + Geom::Coord xratio = (delta_x * sgn(delta_x)) / width; + Geom::Coord yratio = (delta_y * sgn(delta_y)) / height; + Geom::Line* horiz = new Geom::Line(); + Geom::Line* vert = new Geom::Line(); + vert->setPoints (pointAtRatio(yratio,Down_Left_Point,Up_Left_Point),pointAtRatio(yratio,Down_Right_Point,Up_Right_Point)); + horiz->setPoints (pointAtRatio(xratio,Down_Left_Point,Down_Right_Point),pointAtRatio(xratio,Up_Left_Point,Up_Right_Point)); + + OptCrossing crossPoint = intersection(*horiz,*vert); + if(crossPoint){ + return horiz->pointAt(Geom::Coord(crossPoint->ta)); + }else{ + return p; + } +} + +Geom::Point +LPEPerspectiveEnvelope::project_point(Geom::Point p, double m[][3]){ + Geom::Coord x = p[0]; + Geom::Coord y = p[1]; + return Geom::Point( + Geom::Coord((x*m[0][0] + y*m[0][1] + m[0][2])/(x*m[2][0]+y*m[2][1]+m[2][2])), + Geom::Coord((x*m[1][0] + y*m[1][1] + m[1][2])/(x*m[2][0]+y*m[2][1]+m[2][2]))); +} + +Geom::Point +LPEPerspectiveEnvelope::pointAtRatio(Geom::Coord ratio,Geom::Point A, Geom::Point B){ + Geom::Coord x = A[X] + (ratio * (B[X]-A[X])); + Geom::Coord y = A[Y]+ (ratio * (B[Y]-A[Y])); + return Point(x, y); +} + + +Gtk::Widget * +LPEPerspectiveEnvelope::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(6); + std::vector<Parameter *>::iterator it = param_vector.begin(); + Gtk::HBox * hboxUpHandles = Gtk::manage(new Gtk::HBox(false,0)); + Gtk::HBox * hboxDownHandles = Gtk::manage(new Gtk::HBox(false,0)); + while (it != param_vector.end()) { + if ((*it)->widget_is_visible) { + Parameter * param = *it; + Gtk::Widget * widg = dynamic_cast<Gtk::Widget *>(param->param_newWidget()); + if (param->param_key == "Up_Left_Point" || + param->param_key == "Up_Right_Point" || + param->param_key == "Down_Left_Point" || + param->param_key == "Down_Right_Point") + { + Gtk::HBox * pointParameter = dynamic_cast<Gtk::HBox *>(widg); + std::vector< Gtk::Widget* > childList = pointParameter->get_children(); + Gtk::HBox * pointParameterHBox = dynamic_cast<Gtk::HBox *>(childList[0]); + std::vector< Gtk::Widget* > childList2 = pointParameterHBox->get_children(); + pointParameterHBox->remove(childList2[0][0]); + Glib::ustring * tip = param->param_getTooltip(); + if (widg) { + if(param->param_key == "Up_Left_Point"){ + Gtk::Label* handles = Gtk::manage(new Gtk::Label(Glib::ustring(_("Handles:")),Gtk::ALIGN_START)); + vbox->pack_start(*handles, false, false, 2); + hboxUpHandles->pack_start(*widg, true, true, 2); + hboxUpHandles->pack_start(*Gtk::manage(new Gtk::VSeparator()), Gtk::PACK_EXPAND_WIDGET); + }else if(param->param_key == "Up_Right_Point"){ + hboxUpHandles->pack_start(*widg, true, true, 2); + }else if(param->param_key == "Down_Left_Point"){ + hboxDownHandles->pack_start(*widg, true, true, 2); + hboxDownHandles->pack_start(*Gtk::manage(new Gtk::VSeparator()), Gtk::PACK_EXPAND_WIDGET); + }else{ + hboxDownHandles->pack_start(*widg, true, true, 2); + } + if (tip) { + widg->set_tooltip_text(*tip); + } else { + widg->set_tooltip_text(""); + widg->set_has_tooltip(false); + } + } + }else{ + 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; + } + vbox->pack_start(*hboxUpHandles,true, true, 2); + Gtk::HBox * hboxMiddle = Gtk::manage(new Gtk::HBox(true,2)); + hboxMiddle->pack_start(*Gtk::manage(new Gtk::HSeparator()), Gtk::PACK_EXPAND_WIDGET); + hboxMiddle->pack_start(*Gtk::manage(new Gtk::HSeparator()), Gtk::PACK_EXPAND_WIDGET); + vbox->pack_start(*hboxMiddle, false, true, 2); + vbox->pack_start(*hboxDownHandles, true, true, 2); + Gtk::HBox * hbox = Gtk::manage(new Gtk::HBox(false,0)); + Gtk::Button* resetButton = Gtk::manage(new Gtk::Button(Gtk::Stock::CLEAR)); + resetButton->signal_clicked().connect(sigc::mem_fun (*this,&LPEPerspectiveEnvelope::resetGrid)); + resetButton->set_size_request(140,45); + vbox->pack_start(*hbox, true,true,2); + hbox->pack_start(*resetButton, false, false,2); + return dynamic_cast<Gtk::Widget *>(vbox); +} + +void +LPEPerspectiveEnvelope::doBeforeEffect (SPLPEItem const* lpeitem) +{ + original_bbox(lpeitem); + SPLPEItem * item = const_cast<SPLPEItem*>(lpeitem); + item->apply_to_clippath(item); + item->apply_to_mask(item); + setDefaults(); +} + +void +LPEPerspectiveEnvelope::setDefaults() +{ + Geom::Point Up_Left(boundingbox_X.min(), boundingbox_Y.min()); + Geom::Point Up_Right(boundingbox_X.max(), boundingbox_Y.min()); + Geom::Point Down_Left(boundingbox_X.min(), boundingbox_Y.max()); + Geom::Point Down_Right(boundingbox_X.max(), boundingbox_Y.max()); + + Up_Left_Point.param_update_default(Up_Left); + Up_Right_Point.param_update_default(Up_Right); + Down_Right_Point.param_update_default(Down_Right); + Down_Left_Point.param_update_default(Down_Left); +} + +void +LPEPerspectiveEnvelope::resetGrid() +{ + Up_Left_Point.param_set_and_write_default(); + Up_Right_Point.param_set_and_write_default(); + Down_Right_Point.param_set_and_write_default(); + 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(); + tools_switch(desktop, TOOLS_SELECT); + tools_switch(desktop, TOOLS_NODES); +} + +void +LPEPerspectiveEnvelope::resetDefaults(SPItem const* item) +{ + Effect::resetDefaults(item); + original_bbox(SP_LPE_ITEM(item)); + setDefaults(); + resetGrid(); +} + +void +LPEPerspectiveEnvelope::addCanvasIndicators(SPLPEItem const */*lpeitem*/, std::vector<Geom::PathVector> &hp_vec) +{ + hp_vec.clear(); + + SPCurve *c = new SPCurve(); + c->reset(); + c->moveto(Up_Left_Point); + c->lineto(Up_Right_Point); + c->lineto(Down_Right_Point); + c->lineto(Down_Left_Point); + c->lineto(Up_Left_Point); + hp_vec.push_back(c->get_pathvector()); +} + + +/* ######################## */ + +} //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: file_type=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/live_effects/lpe-perspective-envelope.h b/src/live_effects/lpe-perspective-envelope.h new file mode 100644 index 000000000..2f253882e --- /dev/null +++ b/src/live_effects/lpe-perspective-envelope.h @@ -0,0 +1,70 @@ +#ifndef INKSCAPE_LPE_PERSPECTIVE_ENVELOPE_H +#define INKSCAPE_LPE_PERSPECTIVE_ENVELOPE_H + +/** \file + * LPE <perspective-envelope> implementation , see lpe-perspective-envelope.cpp. + + */ +/* + * Authors: + * Jabiertxof Code migration from python extensions envelope and perspective + * Aaron Spike, aaron@ekips.org from envelope and perspective phyton code + * Dmitry Platonov, shadowjack@mail.ru, 2006 perspective approach & math + * Jose Hevia (freon) Transform algorithm from envelope + * + * Copyright (C) 2007-2014 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "live_effects/parameter/enum.h" +#include "live_effects/effect.h" +#include "live_effects/parameter/pointreseteable.h" +#include "live_effects/lpegroupbbox.h" + +namespace Inkscape { +namespace LivePathEffect { + +class LPEPerspectiveEnvelope : public Effect, GroupBBoxEffect { +public: + + LPEPerspectiveEnvelope(LivePathEffectObject *lpeobject); + + virtual ~LPEPerspectiveEnvelope(); + + virtual void doEffect(SPCurve *curve); + + virtual Geom::Point project_point(Geom::Point p); + + virtual Geom::Point project_point(Geom::Point p, double m[][3]); + + virtual Geom::Point pointAtRatio(Geom::Coord ratio,Geom::Point A, Geom::Point B); + + virtual void resetDefaults(SPItem const* item); + + virtual void doBeforeEffect(SPLPEItem const* lpeitem); + + virtual Gtk::Widget * newWidget(); + + virtual void setDefaults(); + + virtual void resetGrid(); + +protected: + void addCanvasIndicators(SPLPEItem const */*lpeitem*/, std::vector<Geom::PathVector> &hp_vec); +private: + + EnumParam<unsigned> deform_type; + PointReseteableParam Up_Left_Point; + PointReseteableParam Up_Right_Point; + PointReseteableParam Down_Left_Point; + PointReseteableParam Down_Right_Point; + + LPEPerspectiveEnvelope(const LPEPerspectiveEnvelope&); + LPEPerspectiveEnvelope& operator=(const LPEPerspectiveEnvelope&); +}; + +} //namespace LivePathEffect +} //namespace Inkscape + +#endif diff --git a/src/live_effects/lpe-perspective_path.cpp b/src/live_effects/lpe-perspective_path.cpp index a2372131c..d43772cf7 100644 --- a/src/live_effects/lpe-perspective_path.cpp +++ b/src/live_effects/lpe-perspective_path.cpp @@ -18,6 +18,7 @@ #include "document.h" #include "document-private.h" #include "live_effects/lpe-perspective_path.h" +#include "live_effects/lpeobject.h" #include "sp-item-group.h" #include "knot-holder-entity.h" #include "knotholder.h" @@ -62,7 +63,7 @@ LPEPerspectivePath::LPEPerspectivePath(LivePathEffectObject *lpeobject) : concatenate_before_pwd2 = true; // don't split the path into its subpaths _provides_knotholder_entities = true; unapply = false; - Persp3D *persp = persp3d_document_first_persp(inkscape_active_document()); + Persp3D *persp = persp3d_document_first_persp(lpeobject->document); if(persp == 0 ){ char *msg = _("You need a BOX 3D object"); Gtk::MessageDialog dialog(msg, false, Gtk::MESSAGE_INFO, @@ -72,7 +73,7 @@ LPEPerspectivePath::LPEPerspectivePath(LivePathEffectObject *lpeobject) : return; } Proj::TransfMat3x4 pmat = persp->perspective_impl->tmat; - pmat = pmat * inkscape_active_desktop()->doc2dt(); + pmat = pmat * SP_ACTIVE_DESKTOP->doc2dt(); pmat.copy_tmat(tmat); } @@ -89,13 +90,16 @@ LPEPerspectivePath::doBeforeEffect (SPLPEItem const* lpeitem) SP_LPE_ITEM(lpeitem)->removeCurrentPathEffect(false); return; } + SPLPEItem * item = const_cast<SPLPEItem*>(lpeitem); + item->apply_to_clippath(item); + item->apply_to_mask(item); } void LPEPerspectivePath::refresh(Gtk::Entry* perspective) { perspectiveID = perspective->get_text(); Persp3D *first = 0; Persp3D *persp = 0; - for ( SPObject *child = inkscape_active_document()->getDefs()->firstChild(); child && !persp; child = child->getNext() ) { + for ( SPObject *child = this->lpeobj->document->getDefs()->firstChild(); child && !persp; child = child->getNext() ) { if (SP_IS_PERSP3D(child) && first == 0) { first = SP_PERSP3D(child); } @@ -125,7 +129,7 @@ void LPEPerspectivePath::refresh(Gtk::Entry* perspective) { dialog.run(); } Proj::TransfMat3x4 pmat = persp->perspective_impl->tmat; - pmat = pmat * inkscape_active_desktop()->doc2dt(); + pmat = pmat * SP_ACTIVE_DESKTOP->doc2dt(); pmat.copy_tmat(tmat); }; @@ -144,7 +148,7 @@ LPEPerspectivePath::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > cons Piecewise<SBasis> preimage[4]; //Geom::Point orig = Geom::Point(bounds_X.min(), bounds_Y.middle()); - //orig = Geom::Point(orig[X], sp_document_height(inkscape_active_document()) - orig[Y]); + //orig = Geom::Point(orig[X], sp_document_height(this->lpeobj->document) - orig[Y]); //double offset = uses_plane_xy ? boundingbox_X.extent() : 0.0; diff --git a/src/live_effects/lpe-powerstroke-interpolators.h b/src/live_effects/lpe-powerstroke-interpolators.h index ee4951882..e3ab37e27 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<Point> 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<CubicBezier>(p0, p1-0.75*dx, p1); + } else if (i == points.size() - 1) { + fit.appendNew<CubicBezier>(p0+0.75*dx, p1, p1); + } else { + fit.appendNew<CubicBezier>(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: @@ -168,8 +206,8 @@ private: SpiroInterpolator& operator=(const SpiroInterpolator&); }; +// Quick mockup for testing the behavior for powerstroke controlpoint interpolation class CentripetalCatmullRomInterpolator : public Interpolator { -// the code in this class can certainly be optimized and made more terse, feel free public: CentripetalCatmullRomInterpolator() {}; virtual ~CentripetalCatmullRomInterpolator() {}; @@ -179,12 +217,7 @@ public: Geom::Path fit(points.front()); - if (n_points < 2) return fit; - if (n_points < 3) { - // if only 2 points, return linear segment - fit.appendNew<Geom::LineSegment>(points[1]); - return fit; - } + if (n_points < 3) return fit; // TODO special cases for 0,1 and 2 input points // return n_points-1 cubic segments @@ -198,7 +231,6 @@ public: Point p3 = (i < n_points-3) ? points[i+3] : points[i+2]; fit.append(calc_bezier(p0, p1, p2, p3)); - // this is quite wasteful: the distances between the same points are repeatedly calculated by calc_bezier } return fit; @@ -254,7 +286,8 @@ private: CentripetalCatmullRomInterpolator& operator=(const CentripetalCatmullRomInterpolator&); }; -Interpolator* + +inline Interpolator* Interpolator::create(InterpolatorType type) { switch (type) { case INTERP_LINEAR: @@ -265,6 +298,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 087424c21..03a10807a 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,11 +190,12 @@ namespace Inkscape { namespace LivePathEffect { static const Util::EnumData<unsigned> 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"}, {Geom::Interpolate::INTERP_SPIRO , N_("SpiroInterpolator"), "SpiroInterpolator"}, - {Geom::Interpolate::INTERP_CENTRIPETAL_CATMULLROM , N_("Centripetal Catmull-Rom"), "CentripetalCatmullRom"} + {Geom::Interpolate::INTERP_CENTRIPETAL_CATMULLROM, N_("Centripetal Catmull-Rom"), "CentripetalCatmullRom"} }; static const Util::EnumDataConverter<unsigned> InterpolatorTypeConverter(InterpolatorTypeData, sizeof(InterpolatorTypeData)/sizeof(*InterpolatorTypeData)); @@ -231,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; @@ -265,20 +271,52 @@ void LPEPowerStroke::doOnApply(SPLPEItem const* lpeitem) { if (SP_IS_SHAPE(lpeitem)) { + SPLPEItem* item = const_cast<SPLPEItem*>(lpeitem); std::vector<Geom::Point> 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); @@ -287,6 +325,45 @@ LPEPowerStroke::doOnApply(SPLPEItem const* lpeitem) } } +void LPEPowerStroke::doOnRemove(SPLPEItem const* lpeitem) +{ + if (SP_IS_SHAPE(lpeitem)) { + SPLPEItem *item = const_cast<SPLPEItem*>(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<Geom::Path> const & path_in) { @@ -596,6 +673,9 @@ LPEPowerStroke::doEffect_path (std::vector<Geom::Path> const & path_in) if (Geom::Interpolate::CubicBezierJohan *johan = dynamic_cast<Geom::Interpolate::CubicBezierJohan*>(interpolator)) { johan->setBeta(interpolator_beta); } + if (Geom::Interpolate::CubicBezierSmooth *smooth = dynamic_cast<Geom::Interpolate::CubicBezierSmooth*>(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<Geom::Path> doEffect_path (std::vector<Geom::Path> 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<Geom::Path> const & path_in); diff --git a/src/live_effects/lpe-roughen.cpp b/src/live_effects/lpe-roughen.cpp new file mode 100644 index 000000000..ffd5433bf --- /dev/null +++ b/src/live_effects/lpe-roughen.cpp @@ -0,0 +1,339 @@ +/** + * @file + * Roughen LPE implementation. Creates roughen paths. + */ +/* Authors: + * Jabier Arraiza Cenoz <jabier.arraiza@marker.es> + * + * 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 <gtkmm.h> + +#include "live_effects/lpe-roughen.h" +#include "display/curve.h" +#include "live_effects/parameter/parameter.h" +#include "helper/geom.h" +#include <glibmm/i18n.h> +#include <cmath> + +namespace Inkscape { +namespace LivePathEffect { + +static const Util::EnumData<DivisionMethod> DivisionMethodData[DM_END] = { + { DM_SEGMENTS, N_("By number of segments"), "segments" }, + { DM_SIZE, N_("By max. segment size"), "size" } +}; +static const Util::EnumDataConverter<DivisionMethod> +DMConverter(DivisionMethodData, DM_END); + +LPERoughen::LPERoughen(LivePathEffectObject *lpeobject) + : Effect(lpeobject), + // initialise your parameters here: + unit(_("Unit"), _("Unit"), "unit", &wr, this), + method(_("Method"), _("Division method"), "method", DMConverter, &wr, + this, DM_SEGMENTS), + maxSegmentSize(_("Max. segment size"), _("Max. segment size"), + "maxSegmentSize", &wr, this, 10.), + segments(_("Number of segments"), _("Number of segments"), "segments", + &wr, this, 2), + displaceX(_("Max. displacement in X"), _("Max. displacement in X"), + "displaceX", &wr, this, 10.), + 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(&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); + SPLPEItem * item = const_cast<SPLPEItem*>(lpeitem); + item->apply_to_clippath(item); + item->apply_to_mask(item); +} + +Gtk::Widget *LPERoughen::newWidget() +{ + Gtk::VBox *vbox = Gtk::manage(new Gtk::VBox(Effect::newWidget())); + vbox->set_border_width(5); + vbox->set_homogeneous(false); + vbox->set_spacing(2); + std::vector<Parameter *>::iterator it = param_vector.begin(); + while (it != param_vector.end()) { + if ((*it)->widget_is_visible) { + Parameter *param = *it; + Gtk::Widget *widg = dynamic_cast<Gtk::Widget *>(param->param_newWidget()); + if (param->param_key == "unit") { + Gtk::Label *unitLabel = Gtk::manage(new Gtk::Label( + Glib::ustring(_("<b>Roughen unit</b>")), 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(_("<b>Add nodes</b> 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(_("<b>Jitter nodes</b> 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<Gtk::Widget *>(vbox); +} + +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"); + + Geom::Point output = Geom::Point(sign(displaceXParsed), sign(displaceYParsed)); + return output; +} + +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(); + 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(); + } + } + 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_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<Geom::CubicBezier const *>(&*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 (path_it->closed()) { + nCurve->closepath_current(); + } + 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<Geom::CubicBezier const *>(&*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<Geom::CubicBezier, Geom::CubicBezier> div = cubic->subdivide(t); + std::vector<Geom::Point> 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; +} + +SPCurve *LPERoughen::jitter(const Geom::Curve *A) +{ + SPCurve *out = new SPCurve(); + Geom::CubicBezier const *cubic = dynamic_cast<Geom::CubicBezier const *>(&*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])); +} + +}; //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-roughen.h b/src/live_effects/lpe-roughen.h new file mode 100644 index 000000000..d5ec726bd --- /dev/null +++ b/src/live_effects/lpe-roughen.h @@ -0,0 +1,63 @@ +/** @file + * @brief Roughen LPE effect, see lpe-roughen.cpp. + */ +/* Authors: + * Jabier Arraiza Cenoz <jabier.arraiza@marker.es> + * + * 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" +#include "live_effects/parameter/path.h" +#include "live_effects/parameter/bool.h" +#include "live_effects/parameter/unit.h" +#include "live_effects/parameter/random.h" + +namespace Inkscape { +namespace LivePathEffect { + +enum DivisionMethod { + 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(); + +private: + UnitParam unit; + EnumParam<DivisionMethod> method; + ScalarParam maxSegmentSize; + ScalarParam segments; + RandomParam displaceX; + RandomParam displaceY; + BoolParam shiftNodes; + BoolParam shiftNodeHandles; + LPERoughen(const LPERoughen &); + LPERoughen &operator=(const LPERoughen &); + +}; + +}; //namespace LivePathEffect +}; //namespace Inkscape +#endif diff --git a/src/live_effects/lpe-show_handles.cpp b/src/live_effects/lpe-show_handles.cpp new file mode 100644 index 000000000..7b2b445b7 --- /dev/null +++ b/src/live_effects/lpe-show_handles.cpp @@ -0,0 +1,211 @@ +/* + * Authors: + * Jabier Arraiza Cenoz +* +* Copyright (C) Jabier Arraiza Cenoz 2014 <jabier.arraiza@marker.es> + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include <gtkmm.h> +#include <glibmm/i18n.h> +#include "live_effects/lpe-show_handles.h" +#include "live_effects/parameter/parameter.h" +#include <2geom/sbasis-to-bezier.h> +#include <2geom/svg-path-parser.h> +#include "helper/geom.h" +#include "desktop-style.h" +#include "style.h" +#include "svg/svg.h" + +namespace Inkscape { +namespace LivePathEffect { + +LPEShowHandles::LPEShowHandles(LivePathEffectObject *lpeobject) + : Effect(lpeobject), + nodes(_("Show nodes"), _("Show nodes"), "nodes", &wr, this, true), + handles(_("Show handles"), _("Show handles"), "handles", &wr, this, true), + originalPath(_("Show path"), _("Show path"), "originalPath", &wr, this, true), + scaleNodesAndHandles(_("Scale nodes and handles"), _("Scale nodes and handles"), "scaleNodesAndHandles", &wr, this, 10) +{ + registerParameter(dynamic_cast<Parameter *>(&nodes)); + registerParameter(dynamic_cast<Parameter *>(&handles)); + registerParameter(dynamic_cast<Parameter *>(&originalPath)); + registerParameter(dynamic_cast<Parameter *>(&scaleNodesAndHandles)); + scaleNodesAndHandles.param_set_range(0, 500.); + scaleNodesAndHandles.param_set_increments(1, 1); + scaleNodesAndHandles.param_set_digits(2); + strokeWidth = 1.0; +} + +bool LPEShowHandles::alertsOff = false; + +/** + * Sets default styles to element + * this permanently remove.some styles of the element + */ + +void LPEShowHandles::doOnApply(SPLPEItem const* lpeitem) +{ + if(!alertsOff) { + char *msg = _("The \"show handles\" path effect will remove any custom style on the object you are applying it to. If this is not what you want, click Cancel."); + Gtk::MessageDialog dialog(msg, false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_OK_CANCEL, true); + gint response = dialog.run(); + alertsOff = true; + if(response == GTK_RESPONSE_CANCEL) { + SPLPEItem* item = const_cast<SPLPEItem*>(lpeitem); + item->removeCurrentPathEffect(false); + return; + } + } + SPLPEItem* item = const_cast<SPLPEItem*>(lpeitem); + SPCSSAttr *css = sp_repr_css_attr_new (); + sp_repr_css_set_property (css, "stroke", "black"); + sp_repr_css_set_property (css, "stroke-width", "1"); + sp_repr_css_set_property (css, "stroke-linecap", "butt"); + sp_repr_css_set_property(css, "fill", "none"); + + sp_desktop_apply_css_recursive(item, css, true); + sp_repr_css_attr_unref (css); +} + +void LPEShowHandles::doBeforeEffect (SPLPEItem const* lpeitem) +{ + SPItem const* item = SP_ITEM(lpeitem); + strokeWidth = item->style->stroke_width.computed; +} + +std::vector<Geom::Path> LPEShowHandles::doEffect_path (std::vector<Geom::Path> const & path_in) +{ + std::vector<Geom::Path> path_out; + Geom::PathVector const original_pathv = pathv_to_linear_and_cubic_beziers(path_in); + if(originalPath) { + for (unsigned int i=0; i < path_in.size(); i++) { + path_out.push_back(path_in[i]); + } + } + if(!outlinepath.empty()) { + outlinepath.clear(); + } + generateHelperPath(original_pathv); + for (unsigned int i=0; i < outlinepath.size(); i++) { + path_out.push_back(outlinepath[i]); + } + return path_out; +} + +void +LPEShowHandles::generateHelperPath(Geom::PathVector result) +{ + if(!handles && !nodes) { + return; + } + + Geom::CubicBezier const *cubic = NULL; + for (Geom::PathVector::iterator path_it = result.begin(); path_it != result.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 + + 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(); + } + } + if(nodes) { + drawNode(curve_it1->initialPoint()); + } + while (curve_it1 != curve_endit) { + cubic = dynamic_cast<Geom::CubicBezier const *>(&*curve_it1); + if (cubic) { + if(handles) { + if(!are_near((*cubic)[0],(*cubic)[1])){ + drawHandle((*cubic)[1]); + drawHandleLine((*cubic)[0],(*cubic)[1]); + } + if(!are_near((*cubic)[3],(*cubic)[2])){ + drawHandle((*cubic)[2]); + drawHandleLine((*cubic)[3],(*cubic)[2]); + } + } + } + if(nodes) { + drawNode(curve_it1->finalPoint()); + } + ++curve_it1; + if(curve_it2 != curve_endit){ + ++curve_it2; + } + } + } +} + +void +LPEShowHandles::drawNode(Geom::Point p) +{ + if(strokeWidth * scaleNodesAndHandles > 0.0) { + double diameter = strokeWidth * scaleNodesAndHandles; + char const * svgd; + svgd = "M 0.55,0.5 A 0.05,0.05 0 0 1 0.5,0.55 0.05,0.05 0 0 1 0.45,0.5 0.05,0.05 0 0 1 0.5,0.45 0.05,0.05 0 0 1 0.55,0.5 Z M 0,0 1,0 1,1 0,1 Z"; + Geom::PathVector pathv = sp_svg_read_pathv(svgd); + pathv *= Geom::Affine(diameter,0,0,diameter,0,0); + pathv += p - Geom::Point(diameter/2,diameter/2); + outlinepath.push_back(pathv[0]); + outlinepath.push_back(pathv[1]); + } +} + +void +LPEShowHandles::drawHandle(Geom::Point p) +{ + if(strokeWidth * scaleNodesAndHandles > 0.0) { + double diameter = strokeWidth * scaleNodesAndHandles; + char const * svgd; + svgd = "M 0.7,0.35 A 0.35,0.35 0 0 1 0.35,0.7 0.35,0.35 0 0 1 0,0.35 0.35,0.35 0 0 1 0.35,0 0.35,0.35 0 0 1 0.7,0.35 Z"; + Geom::PathVector pathv = sp_svg_read_pathv(svgd); + pathv *= Geom::Affine(diameter,0,0,diameter,0,0); + pathv += p - Geom::Point(diameter * 0.35,diameter * 0.35); + outlinepath.push_back(pathv[0]); + } +} + + +void +LPEShowHandles::drawHandleLine(Geom::Point p,Geom::Point p2) +{ + Geom::Path path; + double diameter = strokeWidth * scaleNodesAndHandles; + if(diameter > 0.0 && Geom::distance(p,p2) > (diameter * 0.35)){ + Geom::Ray ray2(p, p2); + p2 = p2 - Geom::Point::polar(ray2.angle(),(diameter * 0.35)); + } + path.start( p ); + path.appendNew<Geom::LineSegment>( p2 ); + outlinepath.push_back(path); +} + +}; //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-show_handles.h b/src/live_effects/lpe-show_handles.h new file mode 100644 index 000000000..278908bb5 --- /dev/null +++ b/src/live_effects/lpe-show_handles.h @@ -0,0 +1,61 @@ +#ifndef INKSCAPE_LPE_SHOW_HANDLES_H +#define INKSCAPE_LPE_SHOW_HANDLES_H + +/* + * Authors: + * Jabier Arraiza Cenoz +* +* Copyright (C) Jabier Arraiza Cenoz 2014 <jabier.arraiza@marker.es> + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "live_effects/effect.h" +#include "live_effects/lpegroupbbox.h" +#include "live_effects/parameter/bool.h" + +namespace Inkscape { +namespace LivePathEffect { + +class LPEShowHandles : public Effect , GroupBBoxEffect { + +public: + LPEShowHandles(LivePathEffectObject *lpeobject); + virtual ~LPEShowHandles(){} + + virtual void doOnApply(SPLPEItem const* lpeitem); + + virtual void doBeforeEffect (SPLPEItem const* lpeitem); + + virtual void generateHelperPath(Geom::PathVector result); + + virtual void drawNode(Geom::Point p); + + virtual void drawHandle(Geom::Point p); + + virtual void drawHandleLine(Geom::Point p,Geom::Point p2); + +protected: + + virtual std::vector<Geom::Path> doEffect_path (std::vector<Geom::Path> const & path_in); + +private: + + BoolParam nodes; + BoolParam handles; + BoolParam originalPath; + ScalarParam scaleNodesAndHandles; + double strokeWidth; + static bool alertsOff; + + Geom::PathVector outlinepath; + + LPEShowHandles(const LPEShowHandles &); + LPEShowHandles &operator=(const LPEShowHandles &); + +}; + +}; //namespace LivePathEffect +}; //namespace Inkscape +#endif + +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/live_effects/lpe-simplify.cpp b/src/live_effects/lpe-simplify.cpp new file mode 100644 index 000000000..c191fbbe6 --- /dev/null +++ b/src/live_effects/lpe-simplify.cpp @@ -0,0 +1,292 @@ +/* + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include <gtkmm.h> + +#include "live_effects/lpe-simplify.h" +#include "display/curve.h" +#include "live_effects/parameter/parameter.h" +#include <glibmm/i18n.h> +#include "helper/geom.h" +#include "livarot/Path.h" +#include "splivarot.h" +#include <2geom/svg-path-parser.h> +#include "desktop.h" +#include "inkscape.h" +#include "svg/svg.h" +#include "ui/tools/node-tool.h" +#include <2geom/d2.h> +#include <2geom/generic-rect.h> +#include <2geom/interval.h> +#include "ui/icon-names.h" + +namespace Inkscape { +namespace LivePathEffect { + +LPESimplify::LPESimplify(LivePathEffectObject *lpeobject) + : Effect(lpeobject), + steps(_("Steps:"),_("Change number of simplify steps "), "steps", &wr, this,1), + threshold(_("Roughly threshold:"), _("Roughly threshold:"), "threshold", &wr, this, 0.003), + helper_size(_("Helper size:"), _("Helper size"), "helper_size", &wr, this, 2.), + nodes(_("Helper nodes"), _("Show helper nodes"), "nodes", &wr, this, false, + "", INKSCAPE_ICON("on"), INKSCAPE_ICON("off")), + handles(_("Helper handles"), _("Show helper handles"), "handles", &wr, this, false, + "", INKSCAPE_ICON("on"), INKSCAPE_ICON("off")), + simplifyindividualpaths(_("Paths separately"), _("Simplifying paths (separately)"), "simplifyindividualpaths", &wr, this, false, + "", INKSCAPE_ICON("on"), INKSCAPE_ICON("off")), + simplifyJustCoalesce(_("Just coalesce"), _("Simplify just coalesce"), "simplifyJustCoalesce", &wr, this, false, + "", INKSCAPE_ICON("on"), INKSCAPE_ICON("off")) + { + registerParameter(dynamic_cast<Parameter *>(&steps)); + registerParameter(dynamic_cast<Parameter *>(&threshold)); + registerParameter(dynamic_cast<Parameter *>(&helper_size)); + registerParameter(dynamic_cast<Parameter *>(&nodes)); + registerParameter(dynamic_cast<Parameter *>(&handles)); + registerParameter(dynamic_cast<Parameter *>(&simplifyindividualpaths)); + registerParameter(dynamic_cast<Parameter *>(&simplifyJustCoalesce)); + threshold.param_set_range(0.0001, Geom::infinity()); + threshold.param_set_increments(0.0001, 0.0001); + threshold.param_set_digits(6); + steps.param_set_range(0, 100); + steps.param_set_increments(1, 1); + steps.param_set_digits(0); + helper_size.param_set_range(0.1, 100); + helper_size.param_set_increments(1, 1); + helper_size.param_set_digits(1); +} + +LPESimplify::~LPESimplify() {} + +void +LPESimplify::doBeforeEffect (SPLPEItem const* lpeitem) +{ + if(!hp.empty()){ + hp.clear(); + } + bbox = SP_ITEM(lpeitem)->visualBounds(); + SPLPEItem * item = const_cast<SPLPEItem*>(lpeitem); + item->apply_to_clippath(item); + item->apply_to_mask(item); + +} + +Gtk::Widget * +LPESimplify::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<Parameter *>::iterator it = param_vector.begin(); + Gtk::HBox * buttons = Gtk::manage(new Gtk::HBox(true,0)); + Gtk::HBox * buttonsTwo = Gtk::manage(new Gtk::HBox(true,0)); + while (it != param_vector.end()) { + if ((*it)->widget_is_visible) { + Parameter * param = *it; + Gtk::Widget * widg = dynamic_cast<Gtk::Widget *>(param->param_newWidget()); + if (param->param_key == "simplifyindividualpaths" || + param->param_key == "simplifyJustCoalesce") + { + Glib::ustring * tip = param->param_getTooltip(); + if (widg) { + buttonsTwo->pack_start(*widg, true, true, 2); + if (tip) { + widg->set_tooltip_text(*tip); + } else { + widg->set_tooltip_text(""); + widg->set_has_tooltip(false); + } + } + } else if (param->param_key == "nodes" || + param->param_key == "handles") + { + Glib::ustring * tip = param->param_getTooltip(); + if (widg) { + buttons->pack_start(*widg, true, true, 2); + if (tip) { + widg->set_tooltip_text(*tip); + } else { + widg->set_tooltip_text(""); + widg->set_has_tooltip(false); + } + } + }else{ + Glib::ustring * tip = param->param_getTooltip(); + if (widg) { + Gtk::HBox * scalarParameter = dynamic_cast<Gtk::HBox *>(widg); + std::vector< Gtk::Widget* > childList = scalarParameter->get_children(); + Gtk::Entry* entryWidg = dynamic_cast<Gtk::Entry *>(childList[1]); + entryWidg->set_width_chars(8); + 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; + } + vbox->pack_start(*buttons,true, true, 2); + vbox->pack_start(*buttonsTwo,true, true, 2); + return dynamic_cast<Gtk::Widget *>(vbox); +} + +void +LPESimplify::doEffect(SPCurve *curve) { + Geom::PathVector const original_pathv = pathv_to_linear_and_cubic_beziers(curve->get_pathvector()); + gdouble size = Geom::L2(bbox->dimensions()); + //size /= Geom::Affine(0,0,0,0,0,0).descrim(); + Path* pathliv = Path_for_pathvector(original_pathv); + if(simplifyindividualpaths){ + size = Geom::L2(Geom::bounds_fast(original_pathv)->dimensions()); + } + for (int unsigned i = 0; i < steps; i++){ + if ( simplifyJustCoalesce ) { + pathliv->Coalesce(threshold * size); + }else{ + pathliv->ConvertEvenLines(threshold * size); + pathliv->Simplify(threshold * size); + } + } + Geom::PathVector outres = Geom::parse_svg_path(pathliv->svg_dump_path()); + generateHelperPath(outres); + curve->set_pathvector(outres); + if(SP_ACTIVE_DESKTOP && INK_IS_NODE_TOOL(SP_ACTIVE_DESKTOP->event_context)){ + SPDesktop* desktop = SP_ACTIVE_DESKTOP; + Inkscape::UI::Tools::NodeTool *nt = static_cast<Inkscape::UI::Tools::NodeTool*>(desktop->event_context); + nt->update_helperpath(); + } +} + +void +LPESimplify::generateHelperPath(Geom::PathVector result) +{ + if(!handles && !nodes){ + return; + } + + if(steps < 1){ + return; + } + + Geom::CubicBezier const *cubic = NULL; + for (Geom::PathVector::iterator path_it = result.begin(); path_it != result.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 + + 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(); + } + } + if(nodes){ + drawNode(curve_it1->initialPoint()); + } + while (curve_it1 != curve_endit) { + cubic = dynamic_cast<Geom::CubicBezier const *>(&*curve_it1); + if (cubic) { + if(handles) { + if(!are_near((*cubic)[0],(*cubic)[1])){ + drawHandle((*cubic)[1]); + drawHandleLine((*cubic)[0],(*cubic)[1]); + } + if(!are_near((*cubic)[3],(*cubic)[2])){ + drawHandle((*cubic)[2]); + drawHandleLine((*cubic)[3],(*cubic)[2]); + } + } + } + if(nodes) { + drawNode(curve_it1->finalPoint()); + } + ++curve_it1; + if(curve_it2 != curve_endit){ + ++curve_it2; + } + } + } +} + +void +LPESimplify::drawNode(Geom::Point p) +{ + double r = helper_size/0.67; + char const * svgd; + svgd = "M 0.55,0.5 A 0.05,0.05 0 0 1 0.5,0.55 0.05,0.05 0 0 1 0.45,0.5 0.05,0.05 0 0 1 0.5,0.45 0.05,0.05 0 0 1 0.55,0.5 Z M 0,0 1,0 1,1 0,1 Z"; + Geom::PathVector pathv = sp_svg_read_pathv(svgd); + pathv *= Geom::Affine(r,0,0,r,0,0); + pathv += p - Geom::Point(0.5*r,0.5*r); + hp.push_back(pathv[0]); + hp.push_back(pathv[1]); +} + +void +LPESimplify::drawHandle(Geom::Point p) +{ + double r = helper_size/0.67; + char const * svgd; + svgd = "M 0.7,0.35 A 0.35,0.35 0 0 1 0.35,0.7 0.35,0.35 0 0 1 0,0.35 0.35,0.35 0 0 1 0.35,0 0.35,0.35 0 0 1 0.7,0.35 Z"; + Geom::PathVector pathv = sp_svg_read_pathv(svgd); + pathv *= Geom::Affine(r,0,0,r,0,0); + pathv += p - Geom::Point(0.35*r,0.35*r); + hp.push_back(pathv[0]); +} + + +void +LPESimplify::drawHandleLine(Geom::Point p,Geom::Point p2) +{ + Geom::Path path; + path.start( p ); + double diameter = helper_size/0.67; + if(helper_size > 0.0 && Geom::distance(p,p2) > (diameter * 0.35)){ + Geom::Ray ray2(p, p2); + p2 = p2 - Geom::Point::polar(ray2.angle(),(diameter * 0.35)); + } + path.appendNew<Geom::LineSegment>( p2 ); + hp.push_back(path); +} + +void +LPESimplify::addCanvasIndicators(SPLPEItem const */*lpeitem*/, std::vector<Geom::PathVector> &hp_vec) +{ + hp_vec.push_back(hp); +} + + +}; //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-simplify.h b/src/live_effects/lpe-simplify.h new file mode 100644 index 000000000..6acf2f2d4 --- /dev/null +++ b/src/live_effects/lpe-simplify.h @@ -0,0 +1,59 @@ +#ifndef INKSCAPE_LPE_SIMPLIFY_H +#define INKSCAPE_LPE_SIMPLIFY_H + +/* + * Inkscape::LPESimplify + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "live_effects/effect.h" +#include "live_effects/parameter/togglebutton.h" +#include "live_effects/lpegroupbbox.h" + +namespace Inkscape { +namespace LivePathEffect { + +class LPESimplify : public Effect , GroupBBoxEffect{ + +public: + LPESimplify(LivePathEffectObject *lpeobject); + virtual ~LPESimplify(); + + virtual void doEffect(SPCurve *curve); + + virtual void doBeforeEffect (SPLPEItem const* lpeitem); + + virtual void generateHelperPath(Geom::PathVector result); + + virtual Gtk::Widget * newWidget(); + + virtual void drawNode(Geom::Point p); + + virtual void drawHandle(Geom::Point p); + + virtual void drawHandleLine(Geom::Point p,Geom::Point p2); + +protected: + void addCanvasIndicators(SPLPEItem const */*lpeitem*/, std::vector<Geom::PathVector> &hp_vec); + +private: + ScalarParam steps; + ScalarParam threshold; + ScalarParam helper_size; + ToggleButtonParam nodes; + ToggleButtonParam handles; + ToggleButtonParam simplifyindividualpaths; + ToggleButtonParam simplifyJustCoalesce; + + Geom::PathVector hp; + Geom::OptRect bbox; + + LPESimplify(const LPESimplify &); + LPESimplify &operator=(const LPESimplify &); + +}; + +}; //namespace LivePathEffect +}; //namespace Inkscape +#endif 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 <glibmm/i18n.h> #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 <b>left</b> 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 <b>right</b> 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<D2<SBasis> > 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<D2<SBasis> > 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<Geom::D2<Geom::SBasis> > doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > 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 <inkscapebrony@gmail.com> + * + * 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 <glibmm/i18n.h> + +template<typename T> +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<unsigned> 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<unsigned> 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<SPLPEItem*>(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<SPLPEItem*>(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<D2<SBasis> > stretch_along(Piecewise<D2<SBasis> > 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<D2<SBasis> > 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<LineJoinType>(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<Geom::LineSegment>(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<D2<SBasis> >(); + 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<Geom::LineSegment>(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<LineJoinType>(join_type.get_value()), miter_limit); + + if (!Geom::are_near(real_path.finalPoint(), throwaway_path.initialPoint()) && real_path.size() >= 1) { + real_path.appendNew<Geom::LineSegment>(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<Geom::LineSegment>(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<Geom::Path> 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<D2<SBasis> > stretch_along(Piecewise<D2<SBasis> > 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<D2<SBasis> > output; + std::vector<Piecewise<D2<SBasis> > > pre_output; + + D2<Piecewise<SBasis> > patternd2 = make_cuts_independent(pattern.toPwSb()); + Piecewise<SBasis> x0 = Piecewise<SBasis>(patternd2[0]); + Piecewise<SBasis> y0 = Piecewise<SBasis>(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<Piecewise<D2<SBasis> > > paths_in; + paths_in = split_at_discontinuities(pwd2_in); + + for (unsigned idx = 0; idx < paths_in.size(); idx++) { + Piecewise<D2<SBasis> > path_i = paths_in[idx]; + Piecewise<SBasis> x = x0; + Piecewise<SBasis> y = y0; + Piecewise<D2<SBasis> > uskeleton = arc_length_parametrization(path_i,2,.1); + uskeleton = remove_short_cuts(uskeleton,.01); + Piecewise<D2<SBasis> > 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<nbCopies; i++) { + if (false) { + Piecewise<D2<SBasis> > output_piece = compose(uskeleton,x+offs)+y*compose(n,x+offs); + std::vector<Piecewise<D2<SBasis> > > 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<Geom::LineSegment*>(curve_in)) { + // special case for line segments + std::pair<Geom::LineSegment, Geom::LineSegment> 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<Geom::CubicBezier, Geom::CubicBezier> 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<LPETaperStroke *>(_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<D2<SBasis> > 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<LPETaperStroke *>(_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<D2<SBasis> > 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<LPETaperStroke const*> (_effect); + return lpe->start_attach_point; +} + +Geom::Point KnotHolderEntityAttachEnd::knot_get() const +{ + LPETaperStroke const * lpe = dynamic_cast<LPETaperStroke const*> (_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 <inkscapebrony@gmail.com> + * 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<unsigned> 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/lpe-vonkoch.cpp b/src/live_effects/lpe-vonkoch.cpp index 709c9e56e..b52816c87 100644 --- a/src/live_effects/lpe-vonkoch.cpp +++ b/src/live_effects/lpe-vonkoch.cpp @@ -262,6 +262,9 @@ LPEVonKoch::doBeforeEffect (SPLPEItem const* lpeitem) tmp_pathv.push_back(tmp_path); ref_path.set_new_value(tmp_pathv,true); } + SPLPEItem * item = const_cast<SPLPEItem*>(lpeitem); + item->apply_to_clippath(item); + item->apply_to_mask(item); } 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 <uri-references.h> -#include <stddef.h> #include <sigc++/sigc++.h> +#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 efdda686a..f990f41c7 100644 --- a/src/live_effects/parameter/Makefile_insert +++ b/src/live_effects/parameter/Makefile_insert @@ -11,6 +11,8 @@ ink_common_sources += \ live_effects/parameter/random.h \ live_effects/parameter/point.cpp \ live_effects/parameter/point.h \ + live_effects/parameter/pointreseteable.cpp \ + live_effects/parameter/pointreseteable.h \ live_effects/parameter/enum.h \ live_effects/parameter/path-reference.cpp \ live_effects/parameter/path-reference.h \ @@ -18,10 +20,18 @@ 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 \ live_effects/parameter/unit.h \ live_effects/parameter/vector.cpp \ diff --git a/src/live_effects/parameter/bool.h b/src/live_effects/parameter/bool.h index fafb1beca..b45eeb0b3 100644 --- a/src/live_effects/parameter/bool.h +++ b/src/live_effects/parameter/bool.h @@ -4,7 +4,7 @@ /* * Inkscape::LivePathEffectParameters * -* Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl> + * Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl> * * Released under GNU GPL, read the file 'COPYING' for more information */ diff --git a/src/live_effects/parameter/filletchamferpointarray.cpp b/src/live_effects/parameter/filletchamferpointarray.cpp new file mode 100644 index 000000000..e2b1aba61 --- /dev/null +++ b/src/live_effects/parameter/filletchamferpointarray.cpp @@ -0,0 +1,882 @@ +/* + * Copyright (C) Jabiertxo Arraiza Cenoz <jabier.arraiza@marker.es> + * Special thanks to Johan Engelen for the base of the effect -powerstroke- + * Also to ScislaC for point me to the idea + * Also su_v for his construvtive feedback and time + * and finaly to Liam P. White for his big help on coding, that save me a lot of + * hours + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#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" +#include "knotholder.h" +#include "sp-lpe-item.h" +#include "selection.h" + +// needed for on-canvas editting: +#include "desktop.h" +#include "live_effects/lpeobject.h" +#include "helper/geom-nodetype.h" +#include "helper/geom-curves.h" +#include "ui/tools/node-tool.h" + +// TODO due to internal breakage in glibmm headers, +// this has to be included last. +#include <glibmm/i18n.h> + + +using namespace Geom; + +namespace Inkscape { + +namespace LivePathEffect { + +FilletChamferPointArrayParam::FilletChamferPointArrayParam( + const Glib::ustring &label, const Glib::ustring &tip, + const Glib::ustring &key, Inkscape::UI::Widget::Registry *wr, + Effect *effect) + : ArrayParam<Point>(label, tip, key, wr, effect, 0) +{ + knot_shape = SP_KNOT_SHAPE_DIAMOND; + knot_mode = SP_KNOT_MODE_XOR; + knot_color = 0x00ff0000; +} + +FilletChamferPointArrayParam::~FilletChamferPointArrayParam() {} + +Gtk::Widget *FilletChamferPointArrayParam::param_newWidget() +{ + return NULL; + /* + Inkscape::UI::Widget::RegisteredTransformedPoint * pointwdg = + Gtk::manage( + new Inkscape::UI::Widget::RegisteredTransformedPoint( + param_label, + param_tooltip, + param_key, + *param_wr, + param_effect->getRepr(), + param_effect->getSPDoc() + ) ); + // TODO: fix to get correct desktop (don't use SP_ACTIVE_DESKTOP) + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + Affine transf = desktop->doc2dt(); + pointwdg->setTransform(transf); + pointwdg->setValue( *this ); + pointwdg->clearProgrammatically(); + pointwdg->set_undo_parameters(SP_VERB_DIALOG_LIVE_PATH_EFFECT, + _("Change point parameter")); + + Gtk::HBox * hbox = Gtk::manage( new Gtk::HBox() ); + static_cast<Gtk::HBox*>(hbox)->pack_start(*pointwdg, true, true); + static_cast<Gtk::HBox*>(hbox)->show_all_children(); + + return dynamic_cast<Gtk::Widget *> (hbox); + */ +} + +void +FilletChamferPointArrayParam::param_transform_multiply(Affine const &postmul, + bool /*set*/) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + if (prefs->getBool("/options/transform/rectcorners", true) && + _vector[1][X] <= 0) { + std::vector<Geom::Point> result; + for (std::vector<Point>::const_iterator point_it = _vector.begin(); + point_it != _vector.end(); ++point_it) { + Coord A = + (*point_it)[X] * ((postmul.expansionX() + postmul.expansionY()) / 2); + result.push_back(Point(A, (*point_it)[Y])); + } + param_set_and_write_new_value(result); + } + + // param_set_and_write_new_value( (*this) * postmul ); +} + +/** call this method to recalculate the controlpoints such that they stay at the + * same location relative to the new path. Useful after adding/deleting nodes to + * the path.*/ +void FilletChamferPointArrayParam::recalculate_controlpoints_for_new_pwd2( + Piecewise<D2<SBasis> > const &pwd2_in) +{ + if (!last_pwd2.empty()) { + PathVector const pathv = + path_from_piecewise(remove_short_cuts(pwd2_in, 0.1), 0.001); + PathVector last_pathv = + path_from_piecewise(remove_short_cuts(last_pwd2, 0.1), 0.001); + std::vector<Point> result; + unsigned long counter = 0; + unsigned long counterPaths = 0; + unsigned long counterCurves = 0; + long offset = 0; + long offsetPaths = 0; + Geom::NodeType nodetype; + for (PathVector::const_iterator path_it = pathv.begin(); + path_it != pathv.end(); ++path_it) { + if (path_it->empty()) { + counterPaths++; + counter++; + continue; + } + 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(); + if (path_it->closed() && path_it->back_closed().isDegenerate()) { + const Curve &closingline = path_it->back_closed(); + if (are_near(closingline.initialPoint(), closingline.finalPoint())) { + curve_endit = path_it->end_open(); + } + } + counterCurves = 0; + while (curve_it1 != curve_endit) { + //if start a path get node type + if (counterCurves == 0) { + if (path_it->closed()) { + if (path_it->back_closed().isDegenerate()) { + nodetype = get_nodetype(path_it->back_open(), *curve_it1); + } else { + nodetype = get_nodetype(path_it->back_closed(), *curve_it1); + } + } else { + nodetype = NODE_NONE; + } + } else { + //check node type also whith straight lines because get_nodetype + //return non cusp node in a node inserted inside a straight line + //todo: if the path remove some nodes whith the result of a straight + //line but with handles, the node inserted into dont fire the knot + // because is not handle as cusp node by get_nodetype function + bool this_is_line = true; + bool next_is_line = is_straight_curve(*curve_it1); + this_is_line = is_straight_curve((*path_it)[counterCurves - 1]); + nodetype = get_nodetype((*path_it)[counterCurves - 1], *curve_it1); + if (this_is_line || next_is_line) { + nodetype = NODE_CUSP; + } + } + if (last_pathv.size() > pathv.size() || + (last_pathv.size() > counterPaths && + last_pathv[counterPaths].size() > counter - offset && + !are_near(curve_it1->initialPoint(), + last_pathv[counterPaths][counter - offset].initialPoint(), + 0.1))) { + if ( curve_it2 == curve_endit) { + if (last_pathv[counterPaths].size() < pathv[counterPaths].size()) { + offset = abs(last_pathv[counterPaths].size() - + pathv[counterPaths].size()); + } else if (last_pathv[counterPaths].size() > + pathv[counterPaths].size()) { + offset = (abs(last_pathv[counterPaths].size() - + pathv[counterPaths].size())) * -1; + } else { + offset = 0; + } + offsetPaths += offset; + offset = offsetPaths; + } else if (counterCurves == 0 && last_pathv.size() <= pathv.size() && + counter - offset <= last_pathv[counterPaths].size() && + are_near(curve_it1->initialPoint(), + last_pathv[counterPaths].finalPoint(), 0.1) && + !last_pathv[counterPaths].closed()) { + long e = counter - offset + 1; + std::vector<Point> tmp = _vector; + for (unsigned long i = + last_pathv[counterPaths].size() + counter - offset; + i > counterCurves - offset + 1; i--) { + + if (tmp[i - 1][X] > 0) { + double fractpart, intpart; + fractpart = modf(tmp[i - 1][X], &intpart); + _vector[e] = Point(e + fractpart, tmp[i - 1][Y]); + } else { + _vector[e] = Point(tmp[i - 1][X], tmp[i - 1][Y]); + } + e++; + } + //delete temp vector + std::vector<Point>().swap(tmp); + if (last_pathv.size() > counterPaths) { + last_pathv[counterPaths] = last_pathv[counterPaths].reverse(); + } + } else { + if (last_pathv.size() > counterPaths) { + if (last_pathv[counterPaths].size() < + pathv[counterPaths].size()) { + offset++; + } else if (last_pathv[counterPaths].size() > + pathv[counterPaths].size()) { + offset--; + continue; + } + } else { + offset++; + } + } + double xPos = 0; + if (_vector[1][X] > 0) { + xPos = nearest_point(curve_it1->initialPoint(), pwd2_in); + } + if (nodetype == NODE_CUSP) { + result.push_back(Point(xPos, 1)); + } else { + result.push_back(Point(xPos, 0)); + } + } else { + double xPos = _vector[counter - offset][X]; + if (_vector.size() <= (unsigned)(counter - offset)) { + if (_vector[1][X] > 0) { + xPos = nearest_point(curve_it1->initialPoint(), pwd2_in); + } else { + xPos = 0; + } + } + if (nodetype == NODE_CUSP) { + double vectorY = _vector[counter - offset][Y]; + if (_vector.size() <= (unsigned)(counter - offset) || vectorY == 0) { + vectorY = 1; + } + result.push_back(Point(xPos, vectorY)); + } else { + if (_vector[1][X] < 0) { + xPos = 0; + } + result.push_back(Point(floor(xPos), 0)); + } + } + ++curve_it1; + if (curve_it2 != curve_endit) { + ++curve_it2; + } + counter++; + counterCurves++; + } + counterPaths++; + } + _vector = result; + write_to_SVG(); + } +} + +void FilletChamferPointArrayParam::recalculate_knots( + Piecewise<D2<SBasis> > const &pwd2_in) +{ + bool change = false; + PathVector pathv = path_from_piecewise(pwd2_in, 0.001); + if (!pathv.empty()) { + std::vector<Point> result; + int counter = 0; + int counterCurves = 0; + Geom::NodeType nodetype; + for (PathVector::const_iterator path_it = pathv.begin(); + path_it != pathv.end(); ++path_it) { + if (path_it->empty()) { + counter++; + continue; + } + 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(); + if (path_it->closed() && path_it->back_closed().isDegenerate()) { + const Curve &closingline = path_it->back_closed(); + if (are_near(closingline.initialPoint(), closingline.finalPoint())) { + curve_endit = path_it->end_open(); + } + } + counterCurves = 0; + while (curve_it1 != curve_endit) { + //if start a path get node type + if (counterCurves == 0) { + if (path_it->closed()) { + if (path_it->back_closed().isDegenerate()) { + nodetype = get_nodetype(path_it->back_open(), *curve_it1); + } else { + nodetype = get_nodetype(path_it->back_closed(), *curve_it1); + } + } else { + nodetype = NODE_NONE; + } + } else { + bool this_is_line = true; + bool next_is_line = is_straight_curve(*curve_it1); + this_is_line = is_straight_curve((*path_it)[counterCurves - 1]); + nodetype = get_nodetype((*path_it)[counterCurves - 1], *curve_it1); + if (this_is_line || next_is_line) { + nodetype = NODE_CUSP; + } + } + if (nodetype == NODE_CUSP) { + double vectorY = _vector[counter][Y]; + if (vectorY == 0) { + vectorY = 1; + change = true; + } + result.push_back(Point(_vector[counter][X], vectorY)); + } else { + double xPos = floor(_vector[counter][X]); + if (_vector[1][X] < 0) { + xPos = 0; + } + double vectorY = _vector[counter][Y]; + if (vectorY != 0) { + change = true; + } + result.push_back(Point(xPos, 0)); + } + ++curve_it1; + counter++; + if (curve_it2 != curve_endit) { + ++curve_it2; + } + counterCurves++; + } + } + if (change) { + _vector = result; + write_to_SVG(); + } + } +} + +void FilletChamferPointArrayParam::set_pwd2( + Piecewise<D2<SBasis> > const &pwd2_in, + Piecewise<D2<SBasis> > const &pwd2_normal_in) +{ + last_pwd2 = pwd2_in; + last_pwd2_normal = pwd2_normal_in; +} + +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; +} + +void FilletChamferPointArrayParam::updateCanvasIndicators() +{ + std::vector<Point> ts = data(); + hp.clear(); + unsigned int i = 0; + for (std::vector<Point>::const_iterator point_it = ts.begin(); + point_it != ts.end(); ++point_it) { + double Xvalue = to_time(i, (*point_it)[X]) -i; + if (Xvalue == 0) { + i++; + continue; + } + Geom::Point ptA = last_pwd2[i].valueAt(Xvalue); + Geom::Point derivA = unit_vector(derivative(last_pwd2[i]).valueAt(Xvalue)); + Geom::Rotate rot(Geom::Rotate::from_degrees(-90)); + derivA = derivA * rot; + Geom::Point C = ptA - derivA * helper_size; + Geom::Point D = ptA + derivA * helper_size; + Geom::Ray ray1(C, D); + char const * svgd = "M 1,0.25 0.5,0 1,-0.25 M 1,0.5 0,0 1,-0.5"; + Geom::PathVector pathv = sp_svg_read_pathv(svgd); + Geom::Affine aff = Geom::Affine(); + aff *= Geom::Scale(helper_size); + aff *= Geom::Rotate(ray1.angle() - deg_to_rad(270)); + pathv *= aff; + pathv += last_pwd2[i].valueAt(Xvalue); + hp.push_back(pathv[0]); + hp.push_back(pathv[1]); + i++; + } +} + +void FilletChamferPointArrayParam::addCanvasIndicators( + SPLPEItem const */*lpeitem*/, std::vector<Geom::PathVector> &hp_vec) +{ + hp_vec.push_back(hp); +} + +double FilletChamferPointArrayParam::rad_to_len(int index, double rad) +{ + double len = 0; + std::vector<Geom::Path> subpaths = path_from_piecewise(last_pwd2, 0.1); + std::pair<std::size_t, std::size_t> positions = get_positions(index, subpaths); + D2<SBasis> 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<SBasis> B = last_pwd2[index]; + Piecewise<D2<SBasis> > offset_curve0 = Piecewise<D2<SBasis> >(A)+rot90(unitVector(derivative(A)))*(rad); + Piecewise<D2<SBasis> > offset_curve1 = Piecewise<D2<SBasis> >(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<Geom::Path> subpaths = path_from_piecewise(last_pwd2, 0.1); + std::pair<std::size_t, std::size_t> positions = get_positions(index, subpaths); + Piecewise<D2<SBasis> > 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<double> 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<Geom::CubicBezier const *>(&*knotCurve1); + Ray ray1(startArcPoint, A->finalPoint()); + if (cubic1) { + ray1.setPoints((*cubic1)[2], startArcPoint); + } + Geom::CubicBezier const *cubic2 = dynamic_cast<Geom::CubicBezier const *>(&*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<double> FilletChamferPointArrayParam::get_times(int index, std::vector<Geom::Path> subpaths, bool last) +{ + const double tolerance = 0.001; + const double gapHelper = 0.00001; + std::pair<std::size_t, std::size_t> 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<double> out; + out.push_back(time_it1); + out.push_back(time_it1_B); + out.push_back(time_it2); + return out; +} + +std::pair<std::size_t, std::size_t> FilletChamferPointArrayParam::get_positions(int index, std::vector<Geom::Path> 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<std::size_t, std::size_t> out(first, second); + return out; +} + +int FilletChamferPointArrayParam::last_index(int index, std::vector<Geom::Path> 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; + if (last_pwd2.size() > (unsigned) index) { + if (len != 0) { + if (last_pwd2[index][0].degreesOfFreedom() != 2) { + Piecewise<D2<SBasis> > u; + u.push_cut(0); + u.push(last_pwd2[index], 1); + std::vector<double> t_roots = roots(arcLengthSb(u) - std::abs(len)); + if (t_roots.size() > 0) { + t = t_roots[0]; + } + } else { + double lenghtPart = 0; + if (last_pwd2.size() > (unsigned) index) { + lenghtPart = length(last_pwd2[index], EPSILON); + } + if (std::abs(len) < lenghtPart && lenghtPart != 0) { + t = std::abs(len) / lenghtPart; + } + } + } + t = double(index) + t; + } else { + t = double(last_pwd2.size() - 1); + } + + return t; +} + +double FilletChamferPointArrayParam::time_to_len(int index, double time) +{ + double intpart; + double len = 0; + time = modf(time, &intpart); + double lenghtPart = 0; + if (last_pwd2.size() <= (unsigned) index || time == 0) { + return len; + } + if (last_pwd2[index][0].degreesOfFreedom() != 2) { + Piecewise<D2<SBasis> > u; + u.push_cut(0); + u.push(last_pwd2[index], 1); + u = portion(u, 0, time); + return length(u, 0.001) * -1; + } + lenghtPart = length(last_pwd2[index], EPSILON); + return (time * lenghtPart) * -1; +} + +double FilletChamferPointArrayParam::to_time(int index, double A) +{ + if (A > 0) { + return A; + } else { + return len_to_time(index, A); + } +} + +double FilletChamferPointArrayParam::to_len(int index, double A) +{ + if (A > 0) { + return time_to_len(index, A); + } else { + return A; + } +} + +void FilletChamferPointArrayParam::set_oncanvas_looks(SPKnotShapeType shape, + SPKnotModeType mode, + guint32 color) +{ + knot_shape = shape; + knot_mode = mode; + knot_color = color; +} +/* +class FilletChamferPointArrayParamKnotHolderEntity : public KnotHolderEntity { +public: + FilletChamferPointArrayParamKnotHolderEntity(FilletChamferPointArrayParam +*p, unsigned int index); + virtual ~FilletChamferPointArrayParamKnotHolderEntity() {} + + virtual void knot_set(Point const &p, Point const &origin, guint state); + virtual Point knot_get() const; + virtual void knot_click(guint state); + virtual void knot_doubleclicked(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: + FilletChamferPointArrayParam *_pparam; + unsigned int _index; +}; +/*/ + +FilletChamferPointArrayParamKnotHolderEntity:: +FilletChamferPointArrayParamKnotHolderEntity( + FilletChamferPointArrayParam *p, unsigned int index) + : _pparam(p), _index(index) {} + +void FilletChamferPointArrayParamKnotHolderEntity::knot_set(Point const &p, + Point const &origin, + guint state) +{ + using namespace Geom; + + if (!valid_index(_index)) { + return; + } + /// @todo how about item transforms??? + Piecewise<D2<SBasis> > const &pwd2 = _pparam->get_pwd2(); + //todo: add snapping + //Geom::Point const s = snap_knot_position(p, state); + double t = nearest_point(p, pwd2[_index]); + if (t == 1) { + t = 0.9999; + } + t += _index; + + if (_pparam->_vector.at(_index)[X] <= 0) { + _pparam->_vector.at(_index) = + Point(_pparam->time_to_len(_index, t), _pparam->_vector.at(_index)[Y]); + } else { + _pparam->_vector.at(_index) = Point(t, _pparam->_vector.at(_index)[Y]); + } + sp_lpe_item_update_patheffect(SP_LPE_ITEM(item), false, false); +} + +Point FilletChamferPointArrayParamKnotHolderEntity::knot_get() const +{ + using namespace Geom; + + if (!valid_index(_index)) { + return Point(infinity(), infinity()); + } + + Piecewise<D2<SBasis> > const &pwd2 = _pparam->get_pwd2(); + + double time_it = _pparam->to_time(_index, _pparam->_vector.at(_index)[X]); + Point canvas_point = pwd2.valueAt(time_it); + + _pparam->updateCanvasIndicators(); + return canvas_point; + +} + +void FilletChamferPointArrayParamKnotHolderEntity::knot_click(guint state) +{ + if (state & GDK_CONTROL_MASK) { + if (state & GDK_MOD1_MASK) { + _pparam->_vector.at(_index) = Point(_index, _pparam->_vector.at(_index)[Y]); + _pparam->param_set_and_write_new_value(_pparam->_vector); + sp_lpe_item_update_patheffect(SP_LPE_ITEM(item), false, false); + }else{ + using namespace Geom; + double type = _pparam->_vector.at(_index)[Y] + 1; + if (type > 4) { + type = 1; + } + _pparam->_vector.at(_index) = Point(_pparam->_vector.at(_index)[X], type); + _pparam->param_set_and_write_new_value(_pparam->_vector); + sp_lpe_item_update_patheffect(SP_LPE_ITEM(item), false, false); + const gchar *tip; + if (type == 3) { + tip = _("<b>Chamfer</b>: <b>Ctrl+Click</b> toogle type, " + "<b>Shift+Click</b> open dialog, " + "<b>Ctrl+Alt+Click</b> reset"); + } else if (type == 2) { + tip = _("<b>Inverse Fillet</b>: <b>Ctrl+Click</b> toogle type, " + "<b>Shift+Click</b> open dialog, " + "<b>Ctrl+Alt+Click</b> reset"); + } else if (type == 1) { + tip = _("<b>Fillet</b>: <b>Ctrl+Click</b> toogle type, " + "<b>Shift+Click</b> open dialog, " + "<b>Ctrl+Alt+Click</b> reset"); + } else { + tip = _("<b>Double Chamfer</b>: <b>Ctrl+Click</b> toogle type, " + "<b>Shift+Click</b> open dialog, " + "<b>Ctrl+Alt+Click</b> reset"); + } + this->knot->tip = g_strdup(tip); + this->knot->show(); + } + } 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<Geom::Path> subpaths = path_from_piecewise(_pparam->last_pwd2, 0.1); + std::pair<std::size_t, std::size_t> positions = _pparam->get_positions(_index, subpaths); + D2<SBasis> A = _pparam->last_pwd2[_pparam->last_index(_index, subpaths)]; + if(positions.second != 0){ + A = _pparam->last_pwd2[_index-1]; + } + D2<SBasis> 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, _pparam->use_distance, aprox); + } + +} + +void FilletChamferPointArrayParamKnotHolderEntity::knot_set_offset( + Geom::Point offset) +{ + 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); +} + +void FilletChamferPointArrayParam::addKnotHolderEntities(KnotHolder *knotholder, + SPDesktop *desktop, + SPItem *item) +{ + recalculate_knots(get_pwd2()); + for (unsigned int i = 0; i < _vector.size(); ++i) { + if (_vector[i][Y] <= 0) { + continue; + } + const gchar *tip; + if (_vector[i][Y] == 3) { + tip = _("<b>Chamfer</b>: <b>Ctrl+Click</b> toogle type, " + "<b>Shift+Click</b> open dialog, " + "<b>Ctrl+Alt+Click</b> reset"); + } else if (_vector[i][Y] == 2) { + tip = _("<b>Inverse Fillet</b>: <b>Ctrl+Click</b> toogle type, " + "<b>Shift+Click</b> open dialog, " + "<b>Ctrl+Alt+Click</b> reset"); + } else if (_vector[i][Y] == 1) { + tip = _("<b>Fillet</b>: <b>Ctrl+Click</b> toogle type, " + "<b>Shift+Click</b> open dialog, " + "<b>Ctrl+Alt+Click</b> reset"); + } else { + tip = _("<b>Double Chamfer</b>: <b>Ctrl+Click</b> toogle type, " + "<b>Shift+Click</b> open dialog, " + "<b>Ctrl+Alt+Click</b> reset"); + } + FilletChamferPointArrayParamKnotHolderEntity *e = + new FilletChamferPointArrayParamKnotHolderEntity(this, i); + e->create(desktop, item, knotholder, Inkscape::CTRL_TYPE_UNKNOWN, _(tip), + knot_shape, knot_mode, knot_color); + knotholder->add(e); + } + updateCanvasIndicators(); +} + +} /* 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/filletchamferpointarray.h b/src/live_effects/parameter/filletchamferpointarray.h new file mode 100644 index 000000000..a1fa698ae --- /dev/null +++ b/src/live_effects/parameter/filletchamferpointarray.h @@ -0,0 +1,123 @@ +#ifndef INKSCAPE_LIVEPATHEFFECT_FILLET_CHAMFER_POINT_ARRAY_H +#define INKSCAPE_LIVEPATHEFFECT_FILLET_CHAMFER_POINT_ARRAY_H + +/* + * Inkscape::LivePathEffectParameters + * Copyright (C) Jabiertxo Arraiza Cenoz <jabier.arraiza@marker.es> + * Special thanks to Johan Engelen for the base of the effect -powerstroke- + * Also to ScislaC for point me to the idea + * Also su_v for his construvtive feedback and time + * and finaly to Liam P. White for his big help on coding, that save me a lot of + * hours + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include <glib.h> +#include <2geom/point.h> + +#include "live_effects/parameter/array.h" + +#include "knot-holder-entity.h" + +namespace Inkscape { + +namespace LivePathEffect { + +class FilletChamferPointArrayParamKnotHolderEntity; + +class FilletChamferPointArrayParam : public ArrayParam<Geom::Point> { +public: + FilletChamferPointArrayParam(const Glib::ustring &label, + const Glib::ustring &tip, + const Glib::ustring &key, + Inkscape::UI::Widget::Registry *wr, + Effect *effect); + virtual ~FilletChamferPointArrayParam(); + + virtual Gtk::Widget *param_newWidget(); + + virtual void param_transform_multiply(Geom::Affine const &postmul, + bool /*set*/); + + void set_oncanvas_looks(SPKnotShapeType shape, SPKnotModeType mode, + 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<std::size_t, std::size_t> get_positions(int index, std::vector<Geom::Path> subpaths); + virtual int last_index(int index, std::vector<Geom::Path> subpaths); + std::vector<double> get_times(int index, std::vector<Geom::Path> 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<Geom::PathVector> &hp_vec); + virtual bool providesKnotHolderEntities() const { + return true; + } + virtual void updateCanvasIndicators(); + virtual void addKnotHolderEntities(KnotHolder *knotholder, SPDesktop *desktop, + SPItem *item); + + void set_pwd2(Geom::Piecewise<Geom::D2<Geom::SBasis> > const &pwd2_in, + Geom::Piecewise<Geom::D2<Geom::SBasis> > const &pwd2_normal_in); + Geom::Piecewise<Geom::D2<Geom::SBasis> > const &get_pwd2() const { + return last_pwd2; + } + Geom::Piecewise<Geom::D2<Geom::SBasis> > const &get_pwd2_normal() const { + return last_pwd2_normal; + } + + void recalculate_controlpoints_for_new_pwd2( + Geom::Piecewise<Geom::D2<Geom::SBasis> > const &pwd2_in); + void recalculate_knots( + Geom::Piecewise<Geom::D2<Geom::SBasis> > const &pwd2_in); + friend class FilletChamferPointArrayParamKnotHolderEntity; + +private: + FilletChamferPointArrayParam(const FilletChamferPointArrayParam &); + FilletChamferPointArrayParam &operator=(const FilletChamferPointArrayParam &); + + SPKnotShapeType knot_shape; + SPKnotModeType knot_mode; + guint32 knot_color; + int helper_size; + bool use_distance; + const gchar *unit; + Geom::PathVector hp; + + Geom::Piecewise<Geom::D2<Geom::SBasis> > last_pwd2; + Geom::Piecewise<Geom::D2<Geom::SBasis> > last_pwd2_normal; +}; + +class FilletChamferPointArrayParamKnotHolderEntity : public KnotHolderEntity { +public: + FilletChamferPointArrayParamKnotHolderEntity(FilletChamferPointArrayParam *p, + unsigned int index); + virtual ~FilletChamferPointArrayParamKnotHolderEntity() {} + + virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, + guint state); + virtual Geom::Point knot_get() const; + virtual void knot_click(guint state); + virtual void knot_set_offset(Geom::Point offset); + + /*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: + FilletChamferPointArrayParam *_pparam; + unsigned int _index; +}; + +} //namespace LivePathEffect + +} //namespace Inkscape + +#endif 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 <flutterguy317@gmail.com> + * + * 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 <glibmm/threads.h> +#endif + +#include "live_effects/parameter/originalpatharray.h" + +#include <gtkmm/widget.h> +#include <gtkmm/icontheme.h> +#include <gtkmm/imagemenuitem.h> +#include <gtkmm/separatormenuitem.h> +#include <gtkmm/scrolledwindow.h> + +#include <glibmm/i18n.h> + +#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<PathAndDirection*> _colObject; + Gtk::TreeModelColumn<Glib::ustring> _colLabel; + Gtk::TreeModelColumn<bool> _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<PathAndDirection*>::iterator piter = _vector.begin(); + for (std::vector<PathAndDirection*>::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<int*>(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<PathAndDirection*>::iterator iter = _vector.begin(); iter != _vector.end(); i++, iter++) { + if (*iter == row[_model->_colObject]) { + std::vector<PathAndDirection*>::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<int*>(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<PathAndDirection*>::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<PathAndDirection*>::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<PathAndDirection*>(sigc::mem_fun(*this, &OriginalPathArrayParam::linked_delete), to)); + to->linked_modified_connection = new_obj->connectModified(sigc::bind<PathAndDirection*>(sigc::mem_fun(*this, &OriginalPathArrayParam::linked_modified), to)); + to->linked_transformed_connection = SP_ITEM(new_obj)->connectTransformed(sigc::bind<PathAndDirection*>(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<PathAndDirection*>(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<PathAndDirection*>(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<PathAndDirection *>(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<PathAndDirection*>::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 <flutterguy317@gmail.com> + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include <vector> + +#include <gtkmm/box.h> +#include <gtkmm/treeview.h> +#include <gtkmm/treestore.h> +#include <gtkmm/scrolledwindow.h> + +#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<Geom::Path> _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<Geom::PathVector> & /*hp_vec*/) {}; + + std::vector<PathAndDirection*> _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<Gtk::TreeStore> _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/parameter.cpp b/src/live_effects/parameter/parameter.cpp index ad2960cd9..7a2fd9769 100644 --- a/src/live_effects/parameter/parameter.cpp +++ b/src/live_effects/parameter/parameter.cpp @@ -42,6 +42,13 @@ Parameter::param_write_to_repr(const char * svgd) param_effect->getRepr()->setAttribute(param_key.c_str(), svgd); } +// In gtk2, this wasn't an issue; we could toss around +// G_MAXDOUBLE and not worry about size allocations. But +// in gtk3, it is an issue: it allocates widget size for the maxmium +// value you pass to it, leading to some insane lengths. +// If you need this to be more, please be conservative about it. +const double SCALARPARAM_G_MAXDOUBLE = 10000000000; + void Parameter::write_to_SVG(void) { gchar * str = param_getSVGValue(); @@ -57,8 +64,8 @@ ScalarParam::ScalarParam( const Glib::ustring& label, const Glib::ustring& tip, Effect* effect, gdouble default_value) : Parameter(label, tip, key, wr, effect), value(default_value), - min(-G_MAXDOUBLE), - max(G_MAXDOUBLE), + min(-SCALARPARAM_G_MAXDOUBLE), + max(SCALARPARAM_G_MAXDOUBLE), integer(false), defvalue(default_value), digits(2), @@ -114,8 +121,22 @@ ScalarParam::param_set_value(gdouble val) void ScalarParam::param_set_range(gdouble min, gdouble max) { - this->min = min; - this->max = max; + // if you look at client code, you'll see that many effects + // has a tendency to set an upper range of Geom::infinity(). + // Once again, in gtk2, this is not a problem. But in gtk3, + // widgets get allocated the amount of size they ask for, + // leading to excessively long widgets. + + if (min >= -SCALARPARAM_G_MAXDOUBLE) { + this->min = min; + } else { + this->min = -SCALARPARAM_G_MAXDOUBLE; + } + if (max <= SCALARPARAM_G_MAXDOUBLE) { + this->max = max; + } else { + this->max = SCALARPARAM_G_MAXDOUBLE; + } param_set_value(value); // reset value to see whether it is in ranges } diff --git a/src/live_effects/parameter/path.cpp b/src/live_effects/parameter/path.cpp index 89947034d..2a14d4208 100644 --- a/src/live_effects/parameter/path.cpp +++ b/src/live_effects/parameter/path.cpp @@ -28,8 +28,8 @@ #include "document-undo.h" // needed for on-canvas editting: -#include "tools-switch.h" -#include "shape-editor.h" +#include "ui/tools-switch.h" +#include "ui/shape-editor.h" #include "desktop-handles.h" #include "selection.h" // clipboard support diff --git a/src/live_effects/parameter/pointreseteable.cpp b/src/live_effects/parameter/pointreseteable.cpp new file mode 100644 index 000000000..c0f8858b8 --- /dev/null +++ b/src/live_effects/parameter/pointreseteable.cpp @@ -0,0 +1,210 @@ +/* + * Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl> + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "ui/widget/registered-widget.h" +#include "live_effects/parameter/pointreseteable.h" +#include "live_effects/effect.h" +#include "svg/svg.h" +#include "svg/stringstream.h" +#include "ui/widget/point.h" +#include "widgets/icon.h" +#include "inkscape.h" +#include "verbs.h" +#include "knotholder.h" +#include <glibmm/i18n.h> +#include "ui/tools-switch.h" +#include "ui/tools/node-tool.h" + +// needed for on-canvas editting: +#include "desktop.h" + +namespace Inkscape { + +namespace LivePathEffect { + +PointReseteableParam::PointReseteableParam( const Glib::ustring& label, const Glib::ustring& tip, + const Glib::ustring& key, Inkscape::UI::Widget::Registry* wr, + Effect* effect, const gchar *htip, Geom::Point default_value) + : Geom::Point(default_value), Parameter(label, tip, key, wr, effect), defvalue(default_value) +{ + knot_shape = SP_KNOT_SHAPE_DIAMOND; + knot_mode = SP_KNOT_MODE_XOR; + knot_color = 0xffffff00; + handle_tip = g_strdup(htip); +} + +PointReseteableParam::~PointReseteableParam() +{ + if (handle_tip) + g_free(handle_tip); +} + +void +PointReseteableParam::param_set_default() +{ + param_setValue(defvalue); +} + +void +PointReseteableParam::param_set_and_write_default() +{ + param_set_and_write_new_value(defvalue); +} + +void +PointReseteableParam::param_update_default(Geom::Point newpoint) +{ + this->defvalue = newpoint; +} + +bool +PointReseteableParam::param_readSVGValue(const gchar * strvalue) +{ + gchar ** strarray = g_strsplit(strvalue, ",", 2); + double newx, newy; + unsigned int success = sp_svg_number_read_d(strarray[0], &newx); + success += sp_svg_number_read_d(strarray[1], &newy); + g_strfreev (strarray); + if (success == 2) { + param_setValue( Geom::Point(newx, newy) ); + return true; + } + return false; +} + +gchar * +PointReseteableParam::param_getSVGValue() const +{ + Inkscape::SVGOStringStream os; + os << *dynamic_cast<Geom::Point const *>( this ); + gchar * str = g_strdup(os.str().c_str()); + return str; +} + +Gtk::Widget * +PointReseteableParam::param_newWidget() +{ + Inkscape::UI::Widget::RegisteredTransformedPoint * pointwdg = Gtk::manage( + new Inkscape::UI::Widget::RegisteredTransformedPoint( param_label, + param_tooltip, + param_key, + *param_wr, + param_effect->getRepr(), + param_effect->getSPDoc() ) ); + // TODO: fix to get correct desktop (don't use SP_ACTIVE_DESKTOP) + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + Geom::Affine transf = desktop->doc2dt(); + pointwdg->setTransform(transf); + pointwdg->setValue( *this ); + pointwdg->clearProgrammatically(); + pointwdg->set_undo_parameters(SP_VERB_DIALOG_LIVE_PATH_EFFECT, _("Change point parameter")); + + Gtk::HBox * hbox = Gtk::manage( new Gtk::HBox() ); + static_cast<Gtk::HBox*>(hbox)->pack_start(*pointwdg, true, true); + static_cast<Gtk::HBox*>(hbox)->show_all_children(); + + return dynamic_cast<Gtk::Widget *> (hbox); +} + +void +PointReseteableParam::param_setValue(Geom::Point newpoint) +{ + *dynamic_cast<Geom::Point *>( this ) = newpoint; + if(SP_ACTIVE_DESKTOP){ + SPDesktop* desktop = SP_ACTIVE_DESKTOP; + if (tools_isactive( desktop, TOOLS_NODES)) { + Inkscape::UI::Tools::NodeTool *nt = static_cast<Inkscape::UI::Tools::NodeTool*>( desktop->event_context); + nt->update_helperpath(); + } + } +} + +void +PointReseteableParam::param_set_and_write_new_value (Geom::Point newpoint) +{ + Inkscape::SVGOStringStream os; + os << newpoint; + gchar * str = g_strdup(os.str().c_str()); + param_write_to_repr(str); + g_free(str); +} + +void +PointReseteableParam::param_transform_multiply(Geom::Affine const& postmul, bool /*set*/) +{ + param_set_and_write_new_value( (*this) * postmul ); +} + + +void +PointReseteableParam::set_oncanvas_looks(SPKnotShapeType shape, SPKnotModeType mode, guint32 color) +{ + knot_shape = shape; + knot_mode = mode; + knot_color = color; +} + +class PointReseteableParamKnotHolderEntity : public KnotHolderEntity { +public: + PointReseteableParamKnotHolderEntity(PointReseteableParam *p) { this->pparam = p; } + virtual ~PointReseteableParamKnotHolderEntity() {} + + virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state); + virtual Geom::Point knot_get() const; + virtual void knot_click(guint state); + +private: + PointReseteableParam *pparam; +}; + +void +PointReseteableParamKnotHolderEntity::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state) +{ + Geom::Point const s = snap_knot_position(p, state); + pparam->param_setValue(s); + sp_lpe_item_update_patheffect(SP_LPE_ITEM(item), false, false); +} + +Geom::Point +PointReseteableParamKnotHolderEntity::knot_get() const +{ + return *pparam; +} + +void +PointReseteableParamKnotHolderEntity::knot_click(guint state) +{ + if (state & GDK_CONTROL_MASK) { + if (state & GDK_MOD1_MASK) { + this->pparam->param_set_default(); + sp_lpe_item_update_patheffect(SP_LPE_ITEM(item), false, false); + } + } +} + +void +PointReseteableParam::addKnotHolderEntities(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item) +{ + PointReseteableParamKnotHolderEntity *e = new PointReseteableParamKnotHolderEntity(this); + // TODO: can we ditch handleTip() etc. because we have access to handle_tip etc. itself??? + e->create(desktop, item, knotholder, Inkscape::CTRL_TYPE_UNKNOWN, handleTip(), knot_shape, knot_mode, knot_color); + knotholder->add(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/pointreseteable.h b/src/live_effects/parameter/pointreseteable.h new file mode 100644 index 000000000..5ae1fdf02 --- /dev/null +++ b/src/live_effects/parameter/pointreseteable.h @@ -0,0 +1,74 @@ +#ifndef INKSCAPE_LIVEPATHEFFECT_PARAMETER_POINT_RESETEABLE_H +#define INKSCAPE_LIVEPATHEFFECT_PARAMETER_POINT_RESETEABLE_H + +/* + * Inkscape::LivePathEffectParameters + * +* Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl> + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include <glib.h> +#include <2geom/point.h> + +#include "live_effects/parameter/parameter.h" + +#include "knot-holder-entity.h" + +namespace Inkscape { + +namespace LivePathEffect { + +class PointReseteableParamKnotHolderEntity; + +class PointReseteableParam : public Geom::Point, public Parameter { +public: + PointReseteableParam( const Glib::ustring& label, + const Glib::ustring& tip, + const Glib::ustring& key, + Inkscape::UI::Widget::Registry* wr, + Effect* effect, + const gchar *handle_tip = NULL, + Geom::Point default_value = Geom::Point(0,0) ); // tip for automatically associated on-canvas handle + virtual ~PointReseteableParam(); + + virtual Gtk::Widget * param_newWidget(); + + bool param_readSVGValue(const gchar * strvalue); + gchar * param_getSVGValue() const; + inline const gchar *handleTip() const { return handle_tip ? handle_tip : param_tooltip.c_str(); } + + void param_setValue(Geom::Point newpoint); + void param_set_default(); + void param_set_and_write_default(); + void param_update_default(Geom::Point newpoint); + + void param_set_and_write_new_value(Geom::Point newpoint); + + virtual void param_transform_multiply(Geom::Affine const& /*postmul*/, bool /*set*/); + + void set_oncanvas_looks(SPKnotShapeType shape, SPKnotModeType mode, guint32 color); + + virtual bool providesKnotHolderEntities() const { return true; } + virtual void addKnotHolderEntities(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item); + + friend class PointReseteableParamKnotHolderEntity; +private: + PointReseteableParam(const PointReseteableParam&); + PointReseteableParam& operator=(const PointReseteableParam&); + + Geom::Point defvalue; + + SPKnotShapeType knot_shape; + SPKnotModeType knot_mode; + guint32 knot_color; + gchar *handle_tip; +}; + + +} //namespace LivePathEffect + +} //namespace Inkscape + +#endif diff --git a/src/live_effects/parameter/powerstrokepointarray.cpp b/src/live_effects/parameter/powerstrokepointarray.cpp index 964ab13b0..e0c2f4c68 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 <glibmm/i18n.h> - +#include "ui/dialog/lpe-powerstroke-properties.h" #include "live_effects/parameter/powerstrokepointarray.h" #include "live_effects/effect.h" @@ -17,6 +16,8 @@ #include "preferences.h" // for proportional stroke/path scaling behavior +#include <glibmm/i18n.h> + namespace Inkscape { namespace LivePathEffect { @@ -41,7 +42,6 @@ PowerStrokePointArrayParam::param_newWidget() return NULL; } - void PowerStrokePointArrayParam::param_transform_multiply(Geom::Affine const &postmul, bool /*set*/) { // Check if proportional stroke-width scaling is on @@ -61,7 +61,6 @@ void PowerStrokePointArrayParam::param_transform_multiply(Geom::Affine const &po } } - /** call this method to recalculate the controlpoints such that they stay at the same location relative to the new path. Useful after adding/deleting nodes to the path.*/ void PowerStrokePointArrayParam::recalculate_controlpoints_for_new_pwd2(Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in) @@ -90,6 +89,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<Geom::D2<Geom::SBasis> > const & pwd2_in, Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_normal_in) { @@ -105,7 +121,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); @@ -115,7 +131,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); }; @@ -123,7 +139,7 @@ public: private: PowerStrokePointArrayParam *_pparam; unsigned int _index; -}; +};*/ PowerStrokePointArrayParamKnotHolderEntity::PowerStrokePointArrayParamKnotHolderEntity(PowerStrokePointArrayParam *p, unsigned int index) : _pparam(p), @@ -172,6 +188,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) { @@ -214,10 +236,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, - _("<b>Stroke width control point</b>: drag to alter the stroke width. <b>Ctrl+click</b> adds a control point, <b>Ctrl+Alt+click</b> deletes it."), + _("<b>Stroke width control point</b>: drag to alter the stroke width. <b>Ctrl+click</b> adds a control point, <b>Ctrl+Alt+click</b> deletes it, <b>Shift+click</b> 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); } } @@ -226,7 +253,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, - _("<b>Stroke width control point</b>: drag to alter the stroke width. <b>Ctrl+click</b> adds a control point, <b>Ctrl+Alt+click</b> deletes it."), + _("<b>Stroke width control point</b>: drag to alter the stroke width. <b>Ctrl+click</b> adds a control point, <b>Ctrl+Alt+click</b> deletes it, <b>Shift+click</b> 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..70b22e27e 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<Geom::Point> { public: PowerStrokePointArrayParam( const Glib::ustring& label, @@ -33,10 +31,12 @@ public: virtual Gtk::Widget * param_newWidget(); - virtual void param_transform_multiply(Geom::Affine const& /*postmul*/, bool /*set*/); + virtual void param_transform_multiply(Geom::Affine const& postmul, bool /*set*/); 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<Geom::D2<Geom::SBasis> > 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/togglebutton.cpp b/src/live_effects/parameter/togglebutton.cpp new file mode 100644 index 000000000..c5da8b858 --- /dev/null +++ b/src/live_effects/parameter/togglebutton.cpp @@ -0,0 +1,172 @@ +/* + * Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl> + * Copyright (C) Jabiertxo Arraiza Cenoz 2014 <j.b.c.engelen@utwente.nl> + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "ui/widget/registered-widget.h" +#include <glibmm/i18n.h> + +#include "live_effects/parameter/togglebutton.h" +#include "live_effects/effect.h" +#include "svg/svg.h" +#include "svg/stringstream.h" +#include "widgets/icon.h" +#include "inkscape.h" +#include "verbs.h" +#include "helper-fns.h" + +namespace Inkscape { + +namespace LivePathEffect { + +ToggleButtonParam::ToggleButtonParam( const Glib::ustring& label, const Glib::ustring& tip, + const Glib::ustring& key, Inkscape::UI::Widget::Registry* wr, + Effect* effect, bool default_value, const Glib::ustring& inactive_label, + char const * icon_active, char const * icon_inactive, + Inkscape::IconSize icon_size) + : Parameter(label, tip, key, wr, effect), value(default_value), defvalue(default_value), + inactiveLabel(inactive_label), iconActive(icon_active), iconInactive(icon_inactive), iconSize(icon_size) +{ + checkwdg = NULL; +} + +ToggleButtonParam::~ToggleButtonParam() +{ + if (_toggled_connection.connected()) { + _toggled_connection.disconnect(); + } +} + +void +ToggleButtonParam::param_set_default() +{ + param_setValue(defvalue); +} + +bool +ToggleButtonParam::param_readSVGValue(const gchar * strvalue) +{ + param_setValue(helperfns_read_bool(strvalue, defvalue)); + return true; // not correct: if value is unacceptable, should return false! +} + +gchar * +ToggleButtonParam::param_getSVGValue() const +{ + gchar * str = g_strdup(value ? "true" : "false"); + return str; +} + +Gtk::Widget * +ToggleButtonParam::param_newWidget() +{ + if (_toggled_connection.connected()) { + _toggled_connection.disconnect(); + } + + checkwdg = Gtk::manage( + new Inkscape::UI::Widget::RegisteredToggleButton( param_label, + param_tooltip, + param_key, + *param_wr, + false, + param_effect->getRepr(), + param_effect->getSPDoc()) ); +#if GTK_CHECK_VERSION(3,0,0) + GtkWidget * boxButton = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_set_homogeneous(GTK_BOX(boxButton), false); +#else + GtkWidget * boxButton = gtk_hbox_new (false, 0); +#endif + GtkWidget * labelButton = gtk_label_new (""); + if (!param_label.empty()) { + if(value || inactiveLabel.empty()){ + gtk_label_set_text(GTK_LABEL(labelButton), param_label.c_str()); + }else{ + gtk_label_set_text(GTK_LABEL(labelButton), inactiveLabel.c_str()); + } + } + gtk_widget_show(labelButton); + if ( iconActive ) { + if(!iconInactive){ + iconInactive = iconActive; + } + gtk_widget_show(boxButton); + GtkWidget *iconButton = sp_icon_new(iconSize, iconActive); + if(!value){ + iconButton = sp_icon_new(iconSize, iconInactive); + } + gtk_widget_show(iconButton); + gtk_box_pack_start (GTK_BOX(boxButton), iconButton, false, false, 1); + if (!param_label.empty()) { + gtk_box_pack_start (GTK_BOX(boxButton), labelButton, false, false, 1); + } + }else{ + gtk_box_pack_start (GTK_BOX(boxButton), labelButton, false, false, 1); + } + checkwdg->add(*Gtk::manage(Glib::wrap(boxButton))); + checkwdg->setActive(value); + checkwdg->setProgrammatically = false; + checkwdg->set_undo_parameters(SP_VERB_DIALOG_LIVE_PATH_EFFECT, _("Change togglebutton parameter")); + + _toggled_connection = checkwdg->signal_toggled().connect(sigc::mem_fun(*this, &ToggleButtonParam::toggled)); + + return checkwdg; +} + +void +ToggleButtonParam::refresh_button() +{ + if(!checkwdg){ + return; + } + Gtk::Widget * boxButton = checkwdg->get_child(); + if(!boxButton){ + return; + } + GList * childs = gtk_container_get_children(GTK_CONTAINER(boxButton->gobj())); + guint totalWidgets = g_list_length (childs); + if (!param_label.empty()) { + if(value || inactiveLabel.empty()){ + gtk_label_set_text(GTK_LABEL(g_list_nth_data(childs, totalWidgets-1)), param_label.c_str()); + }else{ + gtk_label_set_text(GTK_LABEL(g_list_nth_data(childs, totalWidgets-1)), inactiveLabel.c_str()); + } + } + if ( iconActive ) { + GdkPixbuf * iconPixbuf = sp_pixbuf_new( iconSize, iconActive ); + if(!value){ + iconPixbuf = sp_pixbuf_new( iconSize, iconInactive); + } + gtk_image_set_from_pixbuf (GTK_IMAGE(g_list_nth_data(childs, 0)), iconPixbuf); + } +} + +void +ToggleButtonParam::param_setValue(bool newvalue) +{ + value = newvalue; + refresh_button(); +} + +void +ToggleButtonParam::toggled() { + _signal_toggled.emit(); +} + +} /* 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/togglebutton.h b/src/live_effects/parameter/togglebutton.h new file mode 100644 index 000000000..4e545bcfd --- /dev/null +++ b/src/live_effects/parameter/togglebutton.h @@ -0,0 +1,77 @@ +#ifndef INKSCAPE_LIVEPATHEFFECT_PARAMETER_TOGGLEBUTTON_H +#define INKSCAPE_LIVEPATHEFFECT_PARAMETER_TOGGLEBUTTON_H + +/* + * Copyright (C) Jabiertxo Arraiza Cenoz 2014 + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include <glib.h> +#include <sigc++/connection.h> +#include <sigc++/signal.h> + +#include "live_effects/parameter/parameter.h" +#include "icon-size.h" +#include "ui/widget/registered-widget.h" + +namespace Inkscape { + +namespace LivePathEffect { + +/** + * class ToggleButtonParam: + * represents a Gtk::ToggleButton as a Live Path Effect parameter + */ +class ToggleButtonParam : public Parameter { +public: + ToggleButtonParam( const Glib::ustring& label, + const Glib::ustring& tip, + const Glib::ustring& key, + Inkscape::UI::Widget::Registry* wr, + Effect* effect, + bool default_value = false, + const Glib::ustring& inactive_label = "", + char const * icon_active = NULL, + char const * icon_inactive = NULL, + Inkscape::IconSize icon_size = Inkscape::ICON_SIZE_SMALL_TOOLBAR); + virtual ~ToggleButtonParam(); + + virtual Gtk::Widget * param_newWidget(); + + virtual bool param_readSVGValue(const gchar * strvalue); + virtual gchar * param_getSVGValue() const; + + void param_setValue(bool newvalue); + virtual void param_set_default(); + + bool get_value() const { return value; }; + + inline operator bool() const { return value; }; + + sigc::signal<void>& signal_toggled() { return _signal_toggled; } + virtual void toggled(); + +private: + ToggleButtonParam(const ToggleButtonParam&); + ToggleButtonParam& operator=(const ToggleButtonParam&); + + void refresh_button(); + bool value; + bool defvalue; + const Glib::ustring inactiveLabel; + const char * iconActive; + const char * iconInactive; + Inkscape::IconSize iconSize; + Inkscape::UI::Widget::RegisteredToggleButton * checkwdg; + + sigc::signal<void> _signal_toggled; + sigc::connection _toggled_connection; +}; + + +} //namespace LivePathEffect + +} //namespace Inkscape + +#endif 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 <flutterguy317@gmail.com> + * + * 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 <glibmm/i18n.h> + +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<Gtk::HBox*>(hbox)->pack_start(*pointwdg, true, true); + static_cast<Gtk::HBox*>(hbox)->show_all_children(); + + return dynamic_cast<Gtk::Widget *> (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 <flutterguy317@gmail.com> + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include <glib.h> +#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 <inkscapebrony@gmail.com>
+ *
+ * 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 <cstdio>
+
+#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<SBasis> const &curve, double t, double tol=0.01 )
+{
+ D2<SBasis> 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<D2<SBasis> > unitv = unitVector(dM,tol);
+ Piecewise<SBasis> dMlength = dot(Piecewise<D2<SBasis> >(dM),unitv);
+ Piecewise<SBasis> 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<Geom::Path> 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<Geom::SBasis> const & sbasis_in)
+{
+ std::vector<Geom::Point> temp;
+ sbasis_to_bezier(temp, sbasis_in, 4);
+ return Geom::CubicBezier( temp );
+}
+
+static boost::optional<Geom::Point> 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<Geom::SBasis> D2SB;
+typedef Geom::Piecewise<D2SB> PWD2;
+
+// UTILITY
+
+unsigned bezierOrder (const Geom::Curve* curve_in)
+{
+ using namespace Geom;
+ if ( const BezierCurve* bz = dynamic_cast<const BezierCurve*>(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<BezierLine>(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 <Point> 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<BezierLine>(*p);
+ }
+ }
+ path_builder.appendNew<BezierLine>(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<const BezierLine *>(&incoming)) || (dynamic_cast<const BezierLine *>(&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<Geom::LineSegment>(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<CubicBezier, CubicBezier> sub1 = bzr1.subdivide(cross[0].ta);
+ std::pair<CubicBezier, CubicBezier> 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 <CubicBezier> (sub1.first[1], sub1.first[2], sub2.second[0]);
+ path_builder.appendNew <CubicBezier> (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<BezierLine>(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_typ>(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<Geom::Path> path_out = std::vector<Geom::Path>();
+ 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<join_typ>(linejoin_type),
+ original_butt, miter_lim);
+ // fix memory leak
+ std::vector<Geom::Path> *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<LineJoinType>(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<join_typ>(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 <inkscapebrony@gmail.com> + * + * Copyright (C) 2014 Author + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include <livarot/Path.h> +#include <livarot/LivarotDefs.h> + +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<Geom::SBasis> const & sbasis_in); + std::vector<Geom::Path> split_at_cusps(const Geom::Path& in); +} + +namespace Outline +{ + unsigned bezierOrder (const Geom::Curve* curve_in); + std::vector<Geom::Path> PathVectorOutline(std::vector<Geom::Path> 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 : |
