// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) Steren Giannini 2008 * * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #include "live_effects/lpe-envelope.h" #include "display/curve.h" // TODO due to internal breakage in glibmm headers, this must be last: #include namespace Inkscape { namespace LivePathEffect { LPEEnvelope::LPEEnvelope(LivePathEffectObject *lpeobject) : Effect(lpeobject), bend_path1(_("Top bend path:"), _("Top path along which to bend the original path"), "bendpath1", &wr, this, "M0,0 L1,0"), 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(_("_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(&yy); registerParameter(&xx); registerParameter(&bend_path1); registerParameter(&bend_path2); registerParameter(&bend_path3); registerParameter(&bend_path4); concatenate_before_pwd2 = true; apply_to_clippath_and_mask = true; } LPEEnvelope::~LPEEnvelope() = default; bool LPEEnvelope::doOnOpen(SPLPEItem const *lpeitem) { if (!is_load || is_applied) { return false; } bend_path1.reload(); bend_path2.reload(); bend_path3.reload(); bend_path4.reload(); return false; } void LPEEnvelope::transform_multiply(Geom::Affine const &postmul, bool /*set*/) { if (sp_lpe_item && sp_lpe_item->pathEffectsEnabled() && sp_lpe_item->optimizeTransforms()) { bend_path1.param_transform_multiply(postmul, false); bend_path2.param_transform_multiply(postmul, false); bend_path3.param_transform_multiply(postmul, false); bend_path4.param_transform_multiply(postmul, false); } } void LPEEnvelope::doBeforeEffect (SPLPEItem const* lpeitem) { // get the item bounding box original_bbox(lpeitem, false, true); if (is_load) { bend_path1.reload(); bend_path2.reload(); bend_path3.reload(); bend_path4.reload(); } } Geom::Piecewise > LPEEnvelope::doEffect_pwd2 (Geom::Piecewise > const & pwd2_in) { if(!xx.get_value() && !yy.get_value()) { return pwd2_in; } using namespace Geom; // Don't allow empty path parameters: if ( bend_path1.get_pathvector().empty() || bend_path2.get_pathvector().empty() || bend_path3.get_pathvector().empty() || bend_path4.get_pathvector().empty() ) { return pwd2_in; } /* The code below is inspired from the Bend Path code developed by jfb and mgsloan Please, read it before trying to understand this one */ Geom::Affine affine = bend_path1.get_relative_affine(); Piecewise > uskeleton1 = arc_length_parametrization(bend_path1.get_pwd2() * affine,2,.1); uskeleton1 = remove_short_cuts(uskeleton1,.01); Piecewise > n1 = rot90(derivative(uskeleton1)); n1 = force_continuity(remove_short_cuts(n1,.1)); affine = bend_path2.get_relative_affine(); Piecewise > uskeleton2 = arc_length_parametrization(bend_path2.get_pwd2() * affine,2,.1); uskeleton2 = remove_short_cuts(uskeleton2,.01); Piecewise > n2 = rot90(derivative(uskeleton2)); n2 = force_continuity(remove_short_cuts(n2,.1)); affine = bend_path3.get_relative_affine(); Piecewise > uskeleton3 = arc_length_parametrization(bend_path3.get_pwd2() * affine,2,.1); uskeleton3 = remove_short_cuts(uskeleton3,.01); Piecewise > n3 = rot90(derivative(uskeleton3)); n3 = force_continuity(remove_short_cuts(n3,.1)); affine = bend_path4.get_relative_affine(); Piecewise > uskeleton4 = arc_length_parametrization(bend_path4.get_pwd2() * affine,2,.1); uskeleton4 = remove_short_cuts(uskeleton4,.01); Piecewise > n4 = rot90(derivative(uskeleton4)); n4 = force_continuity(remove_short_cuts(n4,.1)); D2 > patternd2 = make_cuts_independent(pwd2_in); Piecewise x = Piecewise(patternd2[0]); Piecewise y = Piecewise(patternd2[1]); /*The *1.001 is a hack to avoid a small bug : path at x=0 and y=0 don't work well. */ x-= boundingbox_X.min()*1.001; y-= boundingbox_Y.min()*1.001; Piecewise x1 = x ; Piecewise y1 = y ; Piecewise x2 = x ; Piecewise y2 = y ; x2 -= boundingbox_X.extent(); Piecewise x3 = x ; Piecewise y3 = y ; y3 -= boundingbox_Y.extent(); Piecewise x4 = x ; Piecewise y4 = y ; /*Scaling to the Bend Path length*/ double scaling1 = uskeleton1.cuts.back()/boundingbox_X.extent(); if (scaling1 != 1.0) { x1*=scaling1; } double scaling2 = uskeleton2.cuts.back()/boundingbox_Y.extent(); if (scaling2 != 1.0) { y2*=scaling2; } double scaling3 = uskeleton3.cuts.back()/boundingbox_X.extent(); if (scaling3 != 1.0) { x3*=scaling3; } double scaling4 = uskeleton4.cuts.back()/boundingbox_Y.extent(); if (scaling4 != 1.0) { y4*=scaling4; } Piecewise xbis = x; Piecewise ybis = y; xbis *= -1.0; xbis += boundingbox_X.extent(); ybis *= -1.0; ybis += boundingbox_Y.extent(); /* This is important : y + ybis = constant and x +xbis = constant */ Piecewise > output; Piecewise > output1; Piecewise > output2; Piecewise > output_x; Piecewise > output_y; /* output_y : Deformation by Up and Down Bend Paths We use weighting : The closer a point is to a Band Path, the more it will be affected by this Bend Path. This is done by the line "ybis*Derformation1 + y*Deformation2" The result is a mix between the 2 deformed paths */ output_y = ybis*(compose((uskeleton1),x1) + y1*compose(n1,x1) ) + y*(compose((uskeleton3),x3) + y3*compose(n3,x3) ); output_y /= (boundingbox_Y.extent()); if(!xx.get_value() && yy.get_value()) { return output_y; } /*output_x : Deformation by Left and Right Bend Paths*/ output_x = x*(compose((uskeleton2),y2) + -x2*compose(n2,y2) ) + xbis*(compose((uskeleton4),y4) + -x4*compose(n4,y4) ); output_x /= (boundingbox_X.extent()); if(xx.get_value() && !yy.get_value()) { return output_x; } /*output : Deformation by Up, Left, Right and Down Bend Paths*/ if(xx.get_value() && yy.get_value()) { Piecewise xsqr = x*xbis; /* xsqr = x * (BBox_X - x) */ Piecewise ysqr = y*ybis; /* xsqr = y * (BBox_Y - y) */ Piecewise xsqrbis = xsqr; Piecewise ysqrbis = ysqr; xsqrbis *= -1; xsqrbis += boundingbox_X.extent()*boundingbox_X.extent()/4.; ysqrbis *= -1; ysqrbis += boundingbox_Y.extent()*boundingbox_Y.extent()/4.; /*This is important : xsqr + xsqrbis = constant*/ /* Here we mix the last two results : output_x and output_y output1 : The more a point is close to Up and Down, the less it will be affected by output_x. (This is done with the polynomial function) output2 : The more a point is close to Left and Right, the less it will be affected by output_y. output : we do the mean between output1 and output2 for all points. */ output1 = (ysqrbis*output_y) + (ysqr*output_x); output1 /= (boundingbox_Y.extent()*boundingbox_Y.extent()/4.); output2 = (xsqrbis*output_x) + (xsqr*output_y); output2 /= (boundingbox_X.extent()*boundingbox_X.extent()/4.); output = output1 + output2; output /= 2.; return output; /*Of course, the result is not perfect, but on a graphical point of view, this is sufficient.*/ } // do nothing when xx and yy are both false return pwd2_in; } void LPEEnvelope::resetDefaults(SPItem const* item) { Effect::resetDefaults(item); original_bbox(SP_LPE_ITEM(item), false, true); 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()); Geom::Path path1; path1.start( Up_Left ); path1.appendNew( Up_Right ); bend_path1.set_new_value( path1.toPwSb(), true ); Geom::Path path2; path2.start( Up_Right ); path2.appendNew( Down_Right ); bend_path2.set_new_value( path2.toPwSb(), true ); Geom::Path path3; path3.start( Down_Left ); path3.appendNew( Down_Right ); bend_path3.set_new_value( path3.toPwSb(), true ); Geom::Path path4; path4.start( Up_Left ); path4.appendNew( Down_Left ); bend_path4.set_new_value( path4.toPwSb(), true ); } } // namespace LivePathEffect } /* namespace Inkscape */