Krita Source Code Documentation
Loading...
Searching...
No Matches
KarbonSimplifyPath.cpp
Go to the documentation of this file.
1/* This file is part of the KDE project
2 SPDX-FileCopyrightText: 2008 Fela Winkelmolen <fela.kde@gmail.com>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
8
9#include <KoCurveFit.h>
10#include <KoPathShape.h>
11#include <KoPathPoint.h>
12#include <QDebug>
13
14/*
15the algorithm proceeds as following:
16
171. divide the paths wherever it's not smooth
182. for each of the resulting paths that has at least three points, add points
19 recursively wherever the path is too "complicated"(*). TODO
203. apply bezierFit on the resulting points
214. remerge the paths
22
23(*) TODO: write definition of too complicated here
24
25FIXME: bezier fit seems to crash when getting to many points in input,
26 if there are to many point in one of the subpaths, split it
27*/
28
30{
31const qreal SUBDIVISION_COEFF = 100; // use error instead?
32const int MAX_RECURSIVE_DEPTH = 1024;
34
36
38
39// subdivides the path adding additional points where to "complicated"
40void subdivide(KoSubpath *subpath);
41// returns the points that needs to be inserted between p1 and p2
43// auxiliary function
44bool isSufficientlyFlat(QPointF curve[4]);
45
46// after this call the points _are_ owned by the subpaths
47void simplifySubpaths(QList<KoSubpath *> *subpaths, qreal error);
48// auxiliary function for the above
49void simplifySubpath(KoSubpath *subpath, qreal error);
50
51// put the result into path
52void mergeSubpaths(QList<KoSubpath *> subpaths, KoPathShape *path);
53}
54
55using namespace KarbonSimplifyPath;
56
57// TODO: rename to simplify subpath
58void karbonSimplifyPath(KoPathShape *path, qreal error)
59{
60 if (path->pointCount() == 0) {
61 return;
62 }
63
64 removeDuplicates(path);
65
66 bool isClosed = path->isClosedSubpath(0);
67 if (isClosed) {
68 // insert a copy of the first point at the end
69 KoPathPoint *firstPoint = path->pointByIndex(KoPathPointIndex(0, 0));
70 KoPathPointIndex end(0, path->pointCount());
71 path->insertPoint(new KoPathPoint(*firstPoint), end);
72 }
73
74 QList<KoSubpath *> subpaths = split(*path);
75 Q_FOREACH (KoSubpath *subpath, subpaths) {
76 subdivide(subpath);
77 }
78
79 simplifySubpaths(&subpaths, error);
80 mergeSubpaths(subpaths, path);
81
82 while (! subpaths.isEmpty()) {
83 KoSubpath *subpath = subpaths.takeLast();
84 qDeleteAll(*subpath);
85 delete subpath;
86 }
87
88 if (isClosed) {
89 path->closeMerge();
90 }
91}
92
94{
95 // NOTE: works because path has only has one subshape, if this ever moves in
96 // KoPathPoint it should be changed
97 for (int i = 1; i < path->pointCount(); ++i) {
98 KoPathPoint *p = path->pointByIndex(KoPathPointIndex(0, i));
99 KoPathPoint *prev = path->pointByIndex(KoPathPointIndex(0, i - 1));
100 QPointF diff = p->point() - prev->point();
101 // if diff = 0 remove point
102 if (qFuzzyCompare(diff.x() + 1, 1) && qFuzzyCompare(diff.y() + 1, 1)) {
103 if (prev->activeControlPoint1()) {
104 p->setControlPoint1(prev->controlPoint1());
105 } else {
106 p->removeControlPoint1();
107 }
108 delete path->removePoint(KoPathPointIndex(0, i - 1));
109 --i;
110 }
111 }
112}
113
115{
117 KoSubpath *subpath = new KoSubpath;
118 res.append(subpath);
119
120 for (int i = 0; i < path.pointCount(); ++i) {
121 KoPathPoint *p = path.pointByIndex(KoPathPointIndex(0, i));
122 // if the path separates two subpaths
123 // (if it isn't smooth nor the first or last point)
124 if (i != 0 && i != path.pointCount() - 1) {
125 KoPathPoint *prev = path.pointByIndex(KoPathPointIndex(0, i - 1));
126 KoPathPoint *next = path.pointByIndex(KoPathPointIndex(0, i + 1));
127 if (!p->isSmooth(prev, next)) {
128 // create a new subpath
129 subpath->append(new KoPathPoint(*p));
130 subpath = new KoSubpath;
131 res.append(subpath);
132 }
133 }
134 subpath->append(new KoPathPoint(*p));
135 }
136
137 return res;
138}
139
141{
142 for (int i = 1; i < subpath->size(); ++i) {
143 recursiveDepth = 0;
144 KoSubpath newPoints = subdivideAux((*subpath)[i - 1], (*subpath)[i]);
145 Q_FOREACH (KoPathPoint *p, newPoints) {
146 subpath->insert(i, p);
147 ++i;
148 }
149 }
150}
151
154{
155 if (!p1->activeControlPoint1() && !p2->activeControlPoint2()) {
156 return QList<KoPathPoint *>();
157 }
158
159 QPointF curve[4] = {
160 p1->point(),
161 p1->activeControlPoint2() ? p1->controlPoint2() : p1->point(),
162 p2->activeControlPoint1() ? p2->controlPoint1() : p2->point(),
163 p2->point()
164 };
165
166 // if there is no need to add points do nothing
167 if (isSufficientlyFlat(curve)) {
168 return QList<KoPathPoint *>();
169 }
170
173 qDebug() << "reached MAX_RECURSIVE_DEPTH";
175 return QList<KoPathPoint *>();
176 }
177
178 // calculate the new point using the de Casteljau algorithm
179 QPointF p[3];
180
181 for (unsigned short j = 1; j <= 3; ++j) {
182 for (unsigned short i = 0; i <= 3 - j; ++i) {
183 curve[i] = (curve[i] + curve[i + 1]) / 2.0;
184 }
185 // modify the new segment.
186 p[j - 1] = curve[0];
187 }
188
189 KoPathPoint *pm = new KoPathPoint(0, p[2]);
190 pm->setControlPoint1(p[1]);
191 pm->setControlPoint2(curve[1]);
192 p1->setControlPoint2(p[0]);
193 p2->setControlPoint1(curve[2]);
194
195 KoSubpath res;
196 res << subdivideAux(p1, pm) << pm << subdivideAux(pm, p2);
198 return res;
199}
200
202{
203 qreal ux = 3 * curve[1].x() - 2 * curve[0].x() - curve[3].x();
204 qreal uy = 3 * curve[1].y() - 2 * curve[0].y() - curve[3].y();
205 qreal vx = 3 * curve[2].x() - 2 * curve[3].x() - curve[0].x();
206 qreal vy = 3 * curve[2].x() - 2 * curve[3].x() - curve[0].x();
207
208 // calculate the square of the distance between the points
209 qreal dx = curve[0].x() - curve[3].y();
210 qreal dy = curve[0].y() - curve[3].y();
211 qreal dist2 = dx * dx + dy * dy;
212 qreal max2 = qMax(ux * ux, vx * vx) + qMax(uy * uy, vy * vy);
214
215 return max2 <= dist2;
216}
217
219 qreal error)
220{
221 Q_FOREACH (KoSubpath *subpath, *subpaths) {
222 if (subpath->size() > 2) {
223 simplifySubpath(subpath, error);
224 }
225 }
226}
227
229{
230 QList<QPointF> points;
231
232 for (int i = 0; i < subpath->size(); ++i) {
233 points.append((*subpath)[i]->point());
234 }
235
236 KoPathShape *simplified = bezierFit(points, error);
237
238 qDeleteAll(*subpath);
239 subpath->clear();
240
241 for (int i = 0; i < simplified->pointCount(); ++i) {
242 KoPathPointIndex index(0, i);
243 subpath->append(new KoPathPoint(*simplified->pointByIndex(index)));
244 }
245 //res->setPosition( position() );
246 delete simplified;
247}
248
250{
251 path->clear();
252 path->moveTo(subpaths.first()->first()->point());
253
254 // TODO: to make the code more readable use foreach and explicit
255 // counters with full name
256 // si: subpath index, pi: point index
257 for (int si = 0; si < subpaths.size(); ++si) {
258 for (int pi = 1; pi < subpaths[si]->size(); ++pi) {
259 KoPathPoint *point = (*subpaths[si])[pi];
260 path->lineTo(point->point());
261
262 // set the first control point
263 KoPathPointIndex index(0, path->pointCount() - 1);
264 KoPathPoint *p = path->pointByIndex(index);
265 if (point->activeControlPoint1()) {
267 }
268
269 // set the second control point of the previous point
270 index = KoPathPointIndex(0, path->pointCount() - 2);
271 p = path->pointByIndex(index);
272 KoPathPoint *prev = (*subpaths[si])[pi - 1];
273 if (prev->activeControlPoint2()) {
275 }
276 }
277 }
278}
void karbonSimplifyPath(KoPathShape *path, qreal error)
const Params2D p
QPointF p2
QPointF p1
KoPathShape * bezierFit(const QList< QPointF > &points, float error)
QList< KoPathPoint * > KoSubpath
a KoSubpath contains a path from a moveTo until a close or a new moveTo
Definition KoPathShape.h:31
QPair< int, int > KoPathPointIndex
Definition KoPathShape.h:28
A KoPathPoint represents a point in a path.
void setControlPoint1(const QPointF &point)
Set the control point 1.
QPointF point
void setControlPoint2(const QPointF &point)
Set the control point 2.
QPointF controlPoint1
bool activeControlPoint1
bool activeControlPoint2
QPointF controlPoint2
The position of a path point within a path shape.
Definition KoPathShape.h:63
int pointCount() const
Returns the number of points in the path.
KoPathPoint * pointByIndex(const KoPathPointIndex &pointIndex) const
Returns the path point specified by a path point index.
void clear()
Removes all subpaths and their points from the path.
static bool qFuzzyCompare(half p1, half p2)
KoSubpath subdivideAux(KoPathPoint *p1, KoPathPoint *p2)
QList< KoSubpath * > split(const KoPathShape &path)
void simplifySubpath(KoSubpath *subpath, qreal error)
void simplifySubpaths(QList< KoSubpath * > *subpaths, qreal error)
void subdivide(KoSubpath *subpath)
void mergeSubpaths(QList< KoSubpath * > subpaths, KoPathShape *path)
void removeDuplicates(KoPathShape *path)
bool isSufficientlyFlat(QPointF curve[4])