diff --git a/src/helper/geom-pathstroke.cpp b/src/helper/geom-pathstroke.cpp index 2472c20e71d5add96688acd1b3d223e925f28e79..707b5bbd2659144849d3b85eb4a7bfb8caef1504 100644 --- a/src/helper/geom-pathstroke.cpp +++ b/src/helper/geom-pathstroke.cpp @@ -992,6 +992,11 @@ void peak_cap(Geom::PathBuilder& res, Geom::Path const& with_dir, Geom::Path con namespace Inkscape { +FillRule sp_to_livarot(SPWindRule fillrule) +{ + return fillrule == SP_WIND_RULE_NONZERO ? fill_nonZero : fill_oddEven; +} + Geom::PathVector outline( Geom::Path const& input, double width, @@ -1262,7 +1267,7 @@ do_offset(Geom::PathVector const & path_in // flatten order the direcions and remove self intersections // we use user fill rule to match original view // after flatten all elements has the same direction in his widding - sp_flatten(closed_pathv, fillrule); + sp_flatten(closed_pathv, fillrule, false); if (to_offset < 0) { Geom::OptRect bbox = closed_pathv.boundsFast(); if (bbox) { @@ -1292,7 +1297,7 @@ do_offset(Geom::PathVector const & path_in } } else { auto with_dir_pv = Geom::PathVector(with_dir); - sp_flatten(with_dir_pv, fill_positive); + sp_flatten(with_dir_pv, fill_positive, false); for (auto path : with_dir_pv) { auto bbox = path.boundsFast(); if (bbox) { @@ -1314,10 +1319,10 @@ do_offset(Geom::PathVector const & path_in if (to_offset > 0) { outline.insert(outline.end(), outline_tmp.begin(), outline_tmp.end()); // this make a union propely without calling boolops - sp_flatten(outline, fill_positive); + sp_flatten(outline, fill_positive, false); } else { // this flatten in a fill_positive way that allow us erase it from the otiginal outline alwais (smaller) - sp_flatten(outline_tmp, fill_positive); + sp_flatten(outline_tmp, fill_positive, false); // this can produce small satellites that become removed after new offset impletation work in 1.4 outline = sp_pathvector_boolop(outline_tmp, outline, bool_op_diff, fill_nonZero, fill_nonZero, false); } diff --git a/src/helper/geom-pathstroke.h b/src/helper/geom-pathstroke.h index ba252583d7831464e077bb136634628da69c99fb..7a8937493b6846bc75511718b532071d79d1ea77 100644 --- a/src/helper/geom-pathstroke.h +++ b/src/helper/geom-pathstroke.h @@ -17,6 +17,10 @@ #include <2geom/pathvector.h> #include "path/path-boolop.h" +// fill rule conversion ¿better place? +#include "livarot/LivarotDefs.h" +#include "style-enums.h" + namespace Inkscape { enum LineJoinType { @@ -37,6 +41,8 @@ enum LineCapType { BUTT_PEAK, // This is not a line ending supported by the SVG standard. }; +FillRule sp_to_livarot(SPWindRule fillrule); + /** * Strokes the path given by @a input. * Joins may behave oddly if the width is negative. diff --git a/src/live_effects/CMakeLists.txt b/src/live_effects/CMakeLists.txt index 89703479cc443022f0bbbdf437f1fb8eb5e0d4f5..982124af0defa6a3754f21e527e1838df4d87256 100644 --- a/src/live_effects/CMakeLists.txt +++ b/src/live_effects/CMakeLists.txt @@ -24,6 +24,7 @@ set(live_effects_SRC lpe-fill-between-many.cpp lpe-fill-between-strokes.cpp lpe-fillet-chamfer.cpp + lpe-flatten.cpp lpe-gears.cpp lpe-interpolate.cpp lpe-interpolate_points.cpp @@ -121,6 +122,7 @@ set(live_effects_SRC lpe-fill-between-many.h lpe-fill-between-strokes.h lpe-fillet-chamfer.h + lpe-flatten.h lpe-gears.h lpe-interpolate.h lpe-interpolate_points.h diff --git a/src/live_effects/effect-enum.h b/src/live_effects/effect-enum.h index 0ae38eb4142168b7b26b59952f130734d3f96e39..48ca80b67ba83dc51fbf2a87bc6dc50944cdc5d3 100644 --- a/src/live_effects/effect-enum.h +++ b/src/live_effects/effect-enum.h @@ -59,6 +59,7 @@ enum EffectType { BOOL_OP, SLICE, TILING, + FLATTEN, // PUT NEW LPE BEFORE EXPERIMENTAL IN THE SAME ORDER AS IN effect.cpp // Visible Experimental LPE's ANGLE_BISECTOR, diff --git a/src/live_effects/effect.cpp b/src/live_effects/effect.cpp index fe31b7daa49ae2739d60cc73a2f9f634de35388b..38eef4e4cb8de7475128b0c4677280f7e9ed8aa1 100644 --- a/src/live_effects/effect.cpp +++ b/src/live_effects/effect.cpp @@ -42,6 +42,7 @@ #include "live_effects/lpe-fill-between-many.h" #include "live_effects/lpe-fill-between-strokes.h" #include "live_effects/lpe-fillet-chamfer.h" +#include "live_effects/lpe-flatten.h" #include "live_effects/lpe-gears.h" #include "live_effects/lpe-interpolate.h" #include "live_effects/lpe-interpolate_points.h" @@ -679,6 +680,21 @@ const EnumEffectData LPETypeData[] = { false ,//on_text false ,//experimental }, + /* 1.3 */ + { + FLATTEN, + NC_("path effect", "Flatten") ,//label + "flatten" ,//key + "flatten" ,//icon + N_("Flatten the item, join all same colors into the same item or child") ,//description + LPECategory::EditTools ,//category + true ,//on_path + true ,//on_shape + true ,//on_group + false ,//on_image + false ,//on_text + false ,//experimental + }, // VISIBLE experimental LPEs { ANGLE_BISECTOR, @@ -1101,6 +1117,9 @@ Effect::New(EffectType lpenr, LivePathEffectObject *lpeobj) case TILING: neweffect = static_cast ( new LPETiling(lpeobj) ); break; + case FLATTEN: + neweffect = static_cast ( new LPEFlatten(lpeobj) ); + break; default: g_warning("LivePathEffect::Effect::New called with invalid patheffect type (%d)", lpenr); neweffect = nullptr; diff --git a/src/live_effects/lpe-flatten.cpp b/src/live_effects/lpe-flatten.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3b41bf1c55f450ca6647797cedad28561d1647ff --- /dev/null +++ b/src/live_effects/lpe-flatten.cpp @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** \file + * LPE implementation + */ +/* + * Authors: + * Maximilian Albert + * Jabiertxo Arraiza + * + * Copyright (C) Johan Engelen 2007 + * Copyright (C) Maximilian Albert 2008 + * Copyright (C) Jabierto Arraiza 2015 + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "lpe-flatten.h" +#include "helper/geom.h" +#include "path/path-boolop.h" +#include "helper/geom-pathstroke.h" +#include "style.h" +#include "object/sp-item-group.h" +#include "object/sp-shape.h" +#include "style.h" + +// TODO due to internal breakage in glibmm headers, this must be last: +#include + +namespace Inkscape { +namespace LivePathEffect { + +static const Util::EnumData FillTypeData[] = { + { fill_oddEven, N_("even-odd"), "oddeven" }, + { fill_nonZero, N_("non-zero"), "nonzero" }, + { fill_positive, N_("positive"), "positive" }, + { fill_justDont, N_("take from object"), "from-curve" } +}; + +static const Util::EnumDataConverter FillTypeConverter(FillTypeData, sizeof(FillTypeData) / sizeof(*FillTypeData)); + + +LPEFlatten::LPEFlatten(LivePathEffectObject *lpeobject) : + Effect(lpeobject), + fillrule(_("Fill rule:"), _("Fill rule type (winding mode) for this item"), "fillrule", FillTypeConverter, &wr, this, fill_oddEven), + invert(_("Invert positive"), _("Invert fill"), "invert", &wr, this, false) +{ + show_orig_path = true; + registerParameter(&fillrule); + registerParameter(&invert); + apply_to_clippath_and_mask = true; +} + +void +LPEFlatten::addCanvasIndicators(SPLPEItem const *lpeitem, std::vector &hp_vec) +{ + hp_vec.push_back(helper_path); +} + +void +LPEFlatten::doBeforeEffect(SPLPEItem const *lpeitem) +{ + helper_path.clear(); +} + +Geom::PathVector +LPEFlatten::doEffect_path(Geom::PathVector const & path_in) +{ + Geom::PathVector open_pathv; + Geom::PathVector orig_pathv = pathv_to_linear_and_cubic_beziers(path_in); + Geom::PathVector pathv_out; // return path + helper_path.insert(helper_path.end(), orig_pathv.begin(), orig_pathv.end()); + // Store separated open/closed paths + for (auto &i : orig_pathv) { + // this improve offset in near closed paths + if (Geom::are_near(i.initialPoint(), i.finalPoint())) { + i.close(true); + } + if (i.closed()) { + pathv_out.push_back(i); + } else { + open_pathv.push_back(i); + } + } + sp_flatten(pathv_out, fillrule, invert); + pathv_out.insert(pathv_out.end(), open_pathv.begin(), open_pathv.end()); + return pathv_out; +} + +void sp_flatten_group(SPGroup * group, SPItem * item) { + auto prev_shape = cast(item); + //auto prev_group = cast(item); + Geom::Affine prev_trans = item->i2doc_affine(); + size_t index = item->getPosition(); + auto itemlist = group->item_list(); + std::reverse(itemlist.begin(),itemlist.end()); + for (auto &child2 : itemlist) { + if (index > child2->getPosition()) { + auto groupchild = cast(child2); + auto shape = cast(child2); + if (groupchild) { + sp_flatten_group(groupchild, item); + } else if (prev_shape && shape) { + auto prev_curve = prev_shape->curve(); + auto curve = shape->curve(); + Geom::Affine trans = shape->i2doc_affine(); + if (curve && prev_curve) { + FillRule prev_fillrule = Inkscape::sp_to_livarot(prev_shape->style->fill_rule.value); + FillRule fillrule = Inkscape::sp_to_livarot(shape->style->fill_rule.value); + auto pv = curve->get_pathvector(); + auto ppv = prev_curve->get_pathvector(); + pv *= trans; + ppv *= prev_trans; + pv = sp_pathvector_boolop(ppv, pv, bool_op_diff, fillrule, prev_fillrule); + auto c = new SPCurve(); + pv *= trans.inverse(); + c->set_pathvector(pv); + shape->setCurve(c); + } + } + } + } +} + +/* void sp_join(SPGroup * group, SPItem * item) { + auto prev_style = item->getAttribute("style"); + auto prev_shape = cast(item); + //auto prev_group = cast(item); + Geom::Affine prev_trans = item->i2doc_affine(); + size_t index = item->getPosition(); + auto itemlist = group->item_list(); + std::reverse(itemlist.begin(),itemlist.end()); + for (auto &child2 : itemlist) { + if (index > child2->getPosition()) { + auto groupchild = cast(child2); + auto shape = cast(child2); + if (groupchild) { + sp_join(groupchild, item); + } else if (prev_shape && shape != prev_shape) { + auto prev_curve = prev_shape->curve(); + auto style = shape->getAttribute("style"); + Geom::Affine trans = shape->i2doc_affine(); + if (!g_strcmp0(style, prev_style)) { + auto curve = shape->curve(); + if (curve && prev_curve) { + FillRule prev_fillrule = Inkscape::sp_to_livarot(prev_shape->style->fill_rule.value); + FillRule fillrule = Inkscape::sp_to_livarot(shape->style->fill_rule.value); + auto pv = curve->get_pathvector(); + auto ppv = prev_curve->get_pathvector(); + pv *= trans; + ppv *= prev_trans; + pv = sp_pathvector_boolop(pv, ppv, bool_op_union, fillrule, prev_fillrule); + auto c = new SPCurve(); + pv *= prev_trans.inverse(); + c->set_pathvector(pv); + prev_shape->setCurve(c); + shape->setCurve(SPCurve()); + } + } + } + } + } +} */ + +void +LPEFlatten::doAfterEffect (SPLPEItem const* lpeitem, SPCurve *curve) +{ + auto group = cast(sp_lpe_item); + if (group) { + auto itemlist = group->item_list(); + std::reverse(itemlist.begin(),itemlist.end()); + for (auto &child : itemlist) { + auto prev_shape = cast(child); + if (prev_shape) { + sp_flatten_group(group, prev_shape); + } + } + /* for (auto &child : itemlist) { + auto prev_shape = cast(child); + if (prev_shape) { + sp_join(group, prev_shape); + } + } */ + } +} + +} //namespace LivePathEffect +} /* namespace Inkscape */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-flattens:((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-flatten.h b/src/live_effects/lpe-flatten.h new file mode 100644 index 0000000000000000000000000000000000000000..5541b965acc94c54d24564a6a47290c731ef30c0 --- /dev/null +++ b/src/live_effects/lpe-flatten.h @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef INKSCAPE_LPE_FLATTEN_H +#define INKSCAPE_LPE_FLATTEN_H + +/** \file + * LPE implementation, see lpe-flatten.cpp. + */ + +/* + * Authors: + * Maximilian Albert + * Jabiertxo Arraiza + * + * Copyright (C) Johan Engelen 2007 + * Copyright (C) Maximilian Albert 2008 + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "live_effects/effect.h" +#include "live_effects/parameter/enum.h" +#include "live_effects/parameter/bool.h" +#include "live_effects/parameter/parameter.h" +#include "livarot/Path.h" +#include "livarot/Shape.h" + +namespace Inkscape { +namespace LivePathEffect { + +class LPEFlatten : public Effect { +public: + LPEFlatten(LivePathEffectObject *lpeobject); + Geom::PathVector doEffect_path (Geom::PathVector const &path_in) override; + void doBeforeEffect(SPLPEItem const *lpeitem) override; + void doAfterEffect(SPLPEItem const* lpeitem, SPCurve *curve) override; + void addCanvasIndicators(SPLPEItem const *lpeitem, std::vector &hp_vec) override; +private: + EnumParam fillrule; + BoolParam invert; + Geom::PathVector helper_path; + LPEFlatten(const LPEFlatten&); + LPEFlatten& operator=(const LPEFlatten&); +}; + +} //namespace LivePathEffect +} //namespace Inkscape + +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-flattens:((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-offset.cpp b/src/live_effects/lpe-offset.cpp index a8d28e35a51127d290c8e45e52513e1c5f112615..b5088be5f41f485f0807092d4f516a7734e95597 100644 --- a/src/live_effects/lpe-offset.cpp +++ b/src/live_effects/lpe-offset.cpp @@ -135,26 +135,13 @@ LPEOffset::doOnApply(SPLPEItem const* lpeitem) lpeversion.param_setValue("1.3", true); } -Glib::ustring -sp_get_fill_rule(SPObject *obj) { - SPCSSAttr *css; - css = sp_repr_css_attr (obj->getRepr() , "style"); - Glib::ustring val = sp_repr_css_property (css, "fill-rule", ""); - sp_repr_css_attr_unref(css); - return val; -} - void LPEOffset::modified(SPObject *obj, guint flags) { // we check for style changes and apply LPE to get appropiate fill rule on change if (flags & SP_OBJECT_STYLE_MODIFIED_FLAG && obj) { // Get the used fillrule - Glib::ustring fr = sp_get_fill_rule(obj); - FillRule fillrule_chan = fill_nonZero; - if (fr == "evenodd") { - fillrule_chan = fill_oddEven; - } + FillRule fillrule_chan = Inkscape::sp_to_livarot(obj->style->fill_rule.value); if (fillrule != fillrule_chan && sp_lpe_item) { sp_lpe_item_update_patheffect(sp_lpe_item, true, true); } @@ -294,11 +281,7 @@ LPEOffset::doEffect_path(Geom::PathVector const & path_in) return path_in; } // Get the used fillrule - Glib::ustring fr = sp_get_fill_rule(item); - fillrule = fill_nonZero; - if (fr == "evenodd") { - fillrule = fill_oddEven; - } + fillrule = Inkscape::sp_to_livarot(item->style->fill_rule.value); // ouline operations faster on live editing knot, on release it, get updated to -1 // todo study remove/change value. Use text paths to test if is needed double tolerance = -1; diff --git a/src/path/path-boolop.cpp b/src/path/path-boolop.cpp index 247f0349d076231ba07e5c92c9dc85e9d1f75044..d0f16bb98f4f47a2985705470660975a36aab6c7 100644 --- a/src/path/path-boolop.cpp +++ b/src/path/path-boolop.cpp @@ -105,13 +105,13 @@ static double get_threshold(Geom::PathVector const &pathv) * @param fill_rule The fill rule with which to flatten the path. * @param close_if_needed If the path is not closed, whether to add a closing segment. */ -static Shape make_shape(Path &path, int path_id = -1, FillRule fill_rule = fill_nonZero, bool close_if_needed = true) +static Shape make_shape(Path &path, int path_id = -1, FillRule fill_rule = fill_nonZero, bool close_if_needed = true, bool invert = false) { Shape result; Shape tmp; path.Fill(&tmp, path_id, false, close_if_needed); - result.ConvertToShape(&tmp, fill_rule); + result.ConvertToShape(&tmp, fill_rule, invert); return result; } @@ -142,10 +142,10 @@ static bool is_line(Path const &path) * Flattening */ -Geom::PathVector flattened(Geom::PathVector const &pathv, FillRule fill_rule) +Geom::PathVector flattened(Geom::PathVector const &pathv, FillRule fill_rule, bool invert) { auto path = make_path(pathv); - auto shape = make_shape(path, 0, fill_rule); + auto shape = make_shape(path, 0, fill_rule, true, invert); Path res; shape.ConvertToForme(&res, 1, std::begin({ &path })); @@ -153,9 +153,9 @@ Geom::PathVector flattened(Geom::PathVector const &pathv, FillRule fill_rule) return res.MakePathVector(); } -void sp_flatten(Geom::PathVector &pathv, FillRule fill_rule) +void sp_flatten(Geom::PathVector &pathv, FillRule fill_rule, bool invert) { - pathv = flattened(pathv, fill_rule); + pathv = flattened(pathv, fill_rule, invert); } /* diff --git a/src/path/path-boolop.h b/src/path/path-boolop.h index 1b283d384998a54a3a803614bde28592cd172f24..5f64339e52063a740dcff057525ecb76088574be 100644 --- a/src/path/path-boolop.h +++ b/src/path/path-boolop.h @@ -17,8 +17,8 @@ #include "livarot/LivarotDefs.h" // FillRule, BooleanOp /// Flatten a pathvector according to the given fill rule. -Geom::PathVector flattened(Geom::PathVector const &pathv, FillRule fill_rule); -void sp_flatten(Geom::PathVector &pathv, FillRule fill_rule); +Geom::PathVector flattened(Geom::PathVector const &pathv, FillRule fill_rule, bool invert = false); +void sp_flatten(Geom::PathVector &pathv, FillRule fill_rule, bool invert = false); /// Cut a pathvector along a collection of lines into several smaller pathvectors. std::vector pathvector_cut(Geom::PathVector const &pathv, Geom::PathVector const &lines); diff --git a/src/ui/tools/booleans-subitems.cpp b/src/ui/tools/booleans-subitems.cpp index 98017f2e788e55832374ad2c7e71b9d08c42f0e4..45b217a2204b7254f47c25059bcdca91a3837c61 100644 --- a/src/ui/tools/booleans-subitems.cpp +++ b/src/ui/tools/booleans-subitems.cpp @@ -113,11 +113,6 @@ static void extract_pathvectors_recursive(SPItem *root, SPItem *item, Pathvector } } -static FillRule sp_to_livarot(SPWindRule fillrule) -{ - return fillrule == SP_WIND_RULE_NONZERO ? fill_nonZero : fill_oddEven; -} - /** * Take a list of items and fracture into a list of SubItems ready for * use inside the booleans interactive tool.