Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
powerstrokepointarray.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>
4 *
5 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
6 */
7
9
10#include <glibmm/i18n.h>
11
12#include <2geom/sbasis-2d.h>
14#include <2geom/piecewise.h>
16
17#include "helper/geom.h"
18#include "live_effects/effect.h"
20#include "object/sp-lpe-item.h"
22#include "ui/knot/knot-holder.h"
23
24
25#include "preferences.h" // for proportional stroke/path scaling behavior
26
27
28namespace Inkscape {
29
30namespace LivePathEffect {
31
32PowerStrokePointArrayParam::PowerStrokePointArrayParam( const Glib::ustring& label, const Glib::ustring& tip,
33 const Glib::ustring& key, Inkscape::UI::Widget::Registry* wr,
34 Effect* effect)
35 : ArrayParam<Geom::Point>(label, tip, key, wr, effect, 0)
37 , knot_color(0xff88ff00)
38{
39}
40
42
43Gtk::Widget *
48
50{
51 // Check if proportional stroke-width scaling is on
53 bool transform_stroke = prefs ? prefs->getBool("/options/transform/stroke", true) : true;
54 if (transform_stroke) {
55 std::vector<Geom::Point> result;
56 result.reserve(_vector.size()); // reserve space for the points that will be added in the for loop
57 for (auto point_it : _vector)
58 {
59 // scale each width knot with the average scaling in X and Y
60 Geom::Coord const A = point_it[Geom::Y] * postmul.descrim();
61 result.emplace_back(point_it[Geom::X], A);
62 }
64 }
65}
66
68void
70{
73 if (lpe) {
74 std::vector<Geom::Point> points;
75 for (size_t index = 0; index < _vector.size(); index++) {
77 }
79 }
80}
81
84{
85 using namespace Geom;
86
89 Point offset_point = _vector.at(index);
90 if (offset_point[X] > path_from_piecewise(pwd2,0.1).curveCount() || offset_point[X] < 0) {
91 g_warning("Broken powerstroke point at %f, I won't try to add that", offset_point[X]);
92 return Point(infinity(), infinity());
93 }
94 Point canvas_point = pwd2.valueAt(offset_point[X]) + (offset_point[Y] * _scale_width) * n.valueAt(offset_point[X]);
95 return canvas_point;
96}
97
98size_t sp_calculate_origin(size_t i, Geom::PathVector pathv) {
99 size_t pos = 0;
100 size_t origin = 0;
101 for (auto path : pathv) {
102 if (pos == i) {
103 break;
104 }
105 size_t psize = count_pathvector_curves(path);
106 origin += psize;
107 pos ++;
108 }
109 return origin;
110}
111
115 std::optional<Geom::PathVectorTime> pos = pathv.nearestTime(p);
116 if (pos) {
117 _vector.at(index)[Geom::X] = (double)sp_calculate_origin((*pos).path_index, pathv) + (double)(*pos).curve_index + (*pos).t;
118 }
119 return p;
120}
121
123std::vector<Geom::Point>
125{
126 std::vector<Geom::Point> controlpoints;
127 if (!last_pwd2.empty()) {
128 Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in_reverse = reverse(last_pwd2);
129 for (auto & i : _vector) {
130 Geom::Point control_pos = last_pwd2.valueAt(i[Geom::X]);
131 double new_pos = Geom::nearest_time(control_pos, pwd2_in_reverse);
132 controlpoints.emplace_back(new_pos,i[Geom::Y]);
133 i[Geom::X] = new_pos;
134 }
135 if (write) {
136 write_to_SVG();
137 _vector.clear();
138 _vector = controlpoints;
139 controlpoints.clear();
140 write_to_SVG();
141 return _vector;
142 }
143 }
144 return controlpoints;
145}
146
148{
149 auto const size = _vector.size();
150
151 if (size == 0) {
152 return 1;
153 }
154
155 if (size % 2 == 0) {
156 return (_vector[size / 2 - 1].y() + _vector[size / 2].y()) / 2;
157 }
158
159 return _vector[size / 2].y();
160}
161
162void
164{
165 last_pwd2 = pwd2_in;
166 last_pwd2_normal = pwd2_normal_in;
167}
168
170{
171 knot_shape = shape;
172 knot_color = color;
173}
174
175/*
176class PowerStrokePointArrayParamKnotHolderEntity : public KnotHolderEntity {
177public:
178 PowerStrokePointArrayParamKnotHolderEntity(PowerStrokePointArrayParam *p, unsigned int index);
179 virtual ~PowerStrokePointArrayParamKnotHolderEntity() {}
180
181 virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
182 virtual Geom::Point knot_get() const;
183 virtual void knot_click(guint state);
184
185 // Checks whether the index falls within the size of the parameter's vector
186 bool valid_index(unsigned int index) const {
187 return (_pparam->_vector.size() > index);
188 };
189
190private:
191 PowerStrokePointArrayParam *_pparam;
192 unsigned int _index;
193};*/
194
200
201void
203{
204 using namespace Geom;
205
206 if (!valid_index(_index)) {
207 return;
208 }
209 _pparam->current_path = true;
210 LPEPowerStroke *ps = dynamic_cast<LPEPowerStroke *>(_pparam->param_effect);
211 ps->knotdragging = true;
212 static gint prev_index = 0;
213 Piecewise<D2<SBasis> > const & pwd2 = _pparam->get_pwd2();
214 Piecewise<D2<SBasis> > pwd2port = _pparam->get_pwd2();
215 Geom::Point s = snap_knot_position(p, state);
216 double t2 = _pparam->_vector.at(_index)[Geom::X];
217 Geom::PathVector pathv = path_from_piecewise(pwd2port, 0.001);
218 Geom::Path pathin = pathv.pathAt(t2);
219 Geom::Coord begin = 0;
220 Geom::Coord end = 0;
221 // TODO: add this to 2Geom
222 size_t psize = 0;
223 size_t i = 0;
224 for (auto path : pathv) {
225 psize = count_pathvector_curves(path);
226 if (pathin == path) {
227 end = begin + psize;
229 break;
230 }
231 i++;
232 if (path.closed()) {
233 i++;
234 }
235 begin += psize;
236 }
238
240 double pos = 0;
241 if (_pparam->unplaced ) {
242 pos = nearest_time(s, pwd2);
243 } else {
244 pos = nearest_time(s, pwd2, begin, end);
245 }
246 gint index = std::floor(pos);
247 if (pos == begin || pos == end) {
248 if (pathv.size() > 1 && knot->is_grabbed()) {
249 _pparam->unplaced = true;
250 }
251 }
252 bool bigjump = false;
253 if (std::abs(prev_index - index) > 1 && pathv.pathAt(prev_index) == pathv.pathAt(index)) {
254 bigjump = true;
255 } else {
256 prev_index = index;
257 }
258
259 double t = 0;
260 if (_pparam->unplaced ) {
261 t = nearest_time(s, pwd2port);
262 } else {
263 t = nearest_time(s, pwd2port, begin, end);
264 }
265 double offset = 0.0;
266 if (ps && ps->not_jump && bigjump) {
267 t = _pparam->_vector.at(_index)[Geom::X];
268 }
269 offset = dot(s - pwd2.valueAt(t), n.valueAt(t));
271 if (_pparam->_vector.size() == 1 ) {
273 prefs->setDouble("/live_effects/powerstroke/width", offset);
274 }
275 sp_lpe_item_update_patheffect(cast<SPLPEItem>(item), false, false);
276}
277
286
287void
289{
290 if (_pparam->unplaced ) {
291 _pparam->unplaced = false;
292 _pparam->current_path = Glib::ustring::npos;
293 }
294 dynamic_cast<LPEPowerStroke *>(_pparam->param_effect)->knotdragging = false;
295 _pparam->param_effect->makeUndoDone(_("Move handle"));
296}
297
303
304void
306{
307 if (state & GDK_CONTROL_MASK) {
308 if (state & GDK_ALT_MASK) {
309 // delete the clicked knot
310 std::vector<Geom::Point> & vec = _pparam->_vector;
311 if (vec.size() > 1) { //Force don't remove last knot
312 vec.erase(vec.begin() + _index);
314 // shift knots down one index
315 for(auto & ent : parent_holder->entity) {
317 if ( pspa_ent && pspa_ent->_pparam == this->_pparam ) { // check if the knotentity belongs to this powerstrokepointarray parameter
318 if (pspa_ent->_index > this->_index) {
319 --pspa_ent->_index;
320 }
321 }
322 };
323 // temporary hide, when knotholder were recreated it finally drop
324 this->knot->hide();
325 }
326 return;
327 } else {
328 // add a knot to XML
329 std::vector<Geom::Point> & vec = _pparam->_vector;
330 vec.insert(vec.begin() + _index, 1, vec.at(_index)); // this clicked knot is duplicated
332
333 // shift knots up one index
334 for(auto & ent : parent_holder->entity) {
336 if ( pspa_ent && pspa_ent->_pparam == this->_pparam ) { // check if the knotentity belongs to this powerstrokepointarray parameter
337 if (pspa_ent->_index > this->_index) {
338 ++pspa_ent->_index;
339 }
340 }
341 };
342 // add knot to knotholder
344 e->create(this->desktop, this->item, parent_holder, Inkscape::CANVAS_ITEM_CTRL_TYPE_LPE, "LPE:PowerStroke",
345 _("<b>Stroke width control point</b>: drag to alter the stroke width. <b>Ctrl+click</b> adds a "
346 "control point, <b>Ctrl+Alt+click</b> deletes it, <b>Shift+click</b> launches width dialog."),
348 parent_holder->add(e);
349 }
350 } else if ((state & GDK_ALT_MASK) || (state & GDK_SHIFT_MASK)) {
353 }
354}
355
357{
358 for (unsigned int i = 0; i < _vector.size(); ++i) {
360 e->create(nullptr, item, knotholder, Inkscape::CANVAS_ITEM_CTRL_TYPE_LPE, "LPE:PowerStroke",
361 _("<b>Stroke width control point</b>: drag to alter the stroke width. <b>Ctrl+click</b> adds a "
362 "control point, <b>Ctrl+Alt+click</b> deletes it, <b>Shift+click</b> launches width dialog."),
363 knot_color);
364 knotholder->add(e);
365 }
366}
367
368} /* namespace LivePathEffect */
369
370} /* namespace Inkscape */
371
372/*
373 Local Variables:
374 mode:c++
375 c-file-style:"stroustrup"
376 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
377 indent-tabs-mode:nil
378 fill-column:99
379 End:
380*/
381// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
Point origin
Definition aa.cpp:227
Conversion between Bezier control points and SBasis curves.
3x3 matrix representing an affine transformation.
Definition affine.h:70
Coord descrim() const
Calculate the descriminant.
Definition affine.cpp:434
Adaptor that creates 2D functions from 1D ones.
Definition d2.h:55
Sequence of subpaths.
Definition pathvector.h:122
size_type size() const
Get the number of paths in the vector.
Definition pathvector.h:147
std::optional< PathVectorTime > nearestTime(Point const &p, Coord *dist=NULL) const
Path & pathAt(Coord t, Coord *rest=NULL)
Sequence of contiguous curves, aka spline.
Definition path.h:353
Function defined as discrete pieces.
Definition piecewise.h:71
bool empty() const
Definition piecewise.h:132
output_type valueAt(double t) const
Definition piecewise.h:102
Two-dimensional point that doubles as a vector.
Definition point.h:66
void param_set_and_write_new_value(std::vector< Geom::Point > const &new_vector)
Definition array.h:92
void makeUndoDone(Glib::ustring message)
Definition effect.cpp:1521
void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override
bool valid_index(unsigned int index) const
Checks whether the index falls within the size of the parameter's vector.
void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state) override
PowerStrokePointArrayParamKnotHolderEntity(PowerStrokePointArrayParam *p, unsigned int index)
Geom::Piecewise< Geom::D2< Geom::SBasis > > const & get_pwd2() const
Geom::Piecewise< Geom::D2< Geom::SBasis > > last_pwd2_normal
void addKnotHolderEntities(KnotHolder *knotholder, SPItem *item) override
Geom::Piecewise< Geom::D2< Geom::SBasis > > last_pwd2
PowerStrokePointArrayParam(const Glib::ustring &label, const Glib::ustring &tip, const Glib::ustring &key, Inkscape::UI::Widget::Registry *wr, Effect *effect)
void recalculate_controlpoints(Geom::PathVector pv)
call this method to recalculate the controlpoints such that they stay at the same location relative t...
void set_oncanvas_looks(CanvasItemCtrlShape shape, uint32_t color)
Geom::Point knot_reposition(size_t index, Geom::PathVector pathv)
void set_pwd2(Geom::Piecewise< Geom::D2< Geom::SBasis > > const &pwd2_in, Geom::Piecewise< Geom::D2< Geom::SBasis > > const &pwd2_normal_in)
std::vector< Geom::Point > reverse_controlpoints(bool write)
call this method to recalculate the controlpoints when path is reversed.
void param_transform_multiply(Geom::Affine const &postmul, bool) override
Geom::Piecewise< Geom::D2< Geom::SBasis > > const & get_pwd2_normal() const
Preference storage class.
Definition preferences.h:66
bool getBool(Glib::ustring const &pref_path, bool def=false)
Retrieve a Boolean value.
static Preferences * get()
Access the singleton Preferences object.
void setDouble(Glib::ustring const &pref_path, double value)
Set a floating point value.
static void showDialog(SPDesktop *desktop, Geom::Point const &knotpoint, LivePathEffect::PowerStrokePointArrayParamKnotHolderEntity *knot)
Geom::Point snap_knot_position(Geom::Point const &p, unsigned state)
void create(SPDesktop *desktop, SPItem *item, KnotHolder *parent, Inkscape::CanvasItemCtrlType type=Inkscape::CANVAS_ITEM_CTRL_TYPE_DEFAULT, Glib::ustring const &name="unknown", char const *tip="", uint32_t color=0xffffff00)
KnotHolder * parent_holder
void knot_ungrabbed_handler(SPKnot *knot, unsigned int state)
std::list< KnotHolderEntity * > entity
Definition knot-holder.h:85
void add(KnotHolderEntity *e)
Base class for visual SVG elements.
Definition sp-item.h:109
bool is_grabbed() const
Definition knot.h:171
void hide()
Hide knot on its canvas.
Definition knot.cpp:327
Css & result
constexpr Coord infinity()
Get a value representing infinity.
Definition coord.h:88
double Coord
Floating point type used to store coordinates.
Definition coord.h:76
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
size_t count_pathvector_curves(Geom::PathVector const &pathv)
Definition geom.cpp:749
Specific geometry functions for Inkscape, not provided my lib2geom.
SPItem * item
Dialog for editing power strokes.
PowerStroke LPE effect, see lpe-powerstroke.cpp.
double offset
Glib::ustring label
Geom::Point end
Various utility functions.
Definition affine.h:22
Bezier reverse(const Bezier &a)
Definition bezier.h:342
Coord nearest_time(Point const &p, Curve const &c)
Definition curve.h:354
PathVector path_from_piecewise(Piecewise< D2< SBasis > > const &B, double tol, bool only_cubicbeziers=false)
Make a path from a d2 sbasis.
T dot(D2< T > const &a, D2< T > const &b)
Definition d2.h:355
size_t sp_calculate_origin(size_t i, Geom::PathVector pathv)
Helper class to stream background task notifications as a series of messages.
@ CANVAS_ITEM_CTRL_SHAPE_DIAMOND
@ CANVAS_ITEM_CTRL_TYPE_LPE
static cairo_user_data_key_t key
Piecewise function class.
Singleton class to access the preferences file in a convenient way.
Obsolete 2D SBasis function class.
two-dimensional geometric operators.
void sp_lpe_item_update_patheffect(SPLPEItem *lpeitem, bool wholetree, bool write, bool with_satellites)
Calls any registered handlers for the update_patheffect action.
Base class for live path effect items.
int index