Krita Source Code Documentation
Loading...
Searching...
No Matches
EnhancedPathCommand.cpp
Go to the documentation of this file.
1/* This file is part of the KDE project
2 * SPDX-FileCopyrightText: 2007 Jan Hambrecht <jaham@gmx.net>
3 * SPDX-FileCopyrightText: 2010 Thomas Zander <zander@kde.org>
4 *
5 * SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7
10#include "EnhancedPathShape.h"
11#include <KoPathPoint.h>
12#include <math.h>
13#include <QDebug>
14
15// radian to degree factor
16const qreal rad2deg = 180.0 / M_PI;
17
19 : m_command(command)
20 , m_parent(parent)
21{
22 Q_ASSERT(m_parent);
23}
24
28
30{
31 /*
32 * The parameters of the commands are in viewbox coordinates, which have
33 * to be converted to the shapes coordinate system by calling viewboxToShape
34 * on the enhanced path the command works on.
35 * Parameters which resemble angles are angles corresponding to the viewbox
36 * coordinate system. Those have to be transformed into angles corresponding
37 * to the normal mathematically coordinate system to be used for the arcTo
38 * drawing routine. This is done by computing (2*M_PI - angle).
39 */
41 const int pointsCount = points.size();
42
43 switch (m_command.unicode()) {
44 // starts new subpath at given position (x y) +
45 case 'M':
46 if (!pointsCount) {
47 return false;
48 }
49 m_parent->moveTo(points[0]);
50 if (pointsCount > 1)
51 for (int i = 1; i < pointsCount; i++) {
52 m_parent->lineTo(points[i]);
53 }
54 break;
55 // line from current point (x y) +
56 case 'L':
57 Q_FOREACH (const QPointF &point, points) {
58 m_parent->lineTo(point);
59 }
60 break;
61 // cubic bezier curve from current point (x1 y1 x2 y2 x y) +
62 case 'C':
63 for (int i = 0; i < pointsCount; i += 3) {
64 m_parent->curveTo(points[i], points[i + 1], points[i + 2]);
65 }
66 break;
67 // closes the current subpath
68 case 'Z':
69 m_parent->close();
70 break;
71 // ends the current set of subpaths
72 case 'N':
73 // N just ends the complete path
74 break;
75 // no fill for current set of subpaths
76 case 'F':
77 // TODO implement me
78 break;
79 // no stroke for current set of subpaths
80 case 'S':
81 // TODO implement me
82 break;
83 // segment of an ellipse (x y w h t0 t1) +
84 case 'T':
85 // same like T but with implied movement to starting point (x y w h t0 t1) +
86 case 'U': {
87 bool lineTo = m_command.unicode() == 'T';
88
89 for (int i = 0; i < pointsCount; i += 3) {
90 const QPointF &radii = points[i + 1];
91 const QPointF &angles = points[i + 2] / rad2deg;
92 // compute the ellipses starting point
93 QPointF start(radii.x() * cos(angles.x()), -1 * radii.y() * sin(angles.x()));
94 qreal sweepAngle = degSweepAngle(points[i + 2].x(), points[i + 2].y(), false);
95
96 if (lineTo) {
97 m_parent->lineTo(points[i] + start);
98 } else {
99 m_parent->moveTo(points[i] + start);
100 }
101
102 m_parent->arcTo(radii.x(), radii.y(), points[i + 2].x(), sweepAngle);
103 }
104 break;
105 }
106 // counter-clockwise arc (x1 y1 x2 y2 x3 y3 x y) +
107 case 'A':
108 // the same as A, with implied moveto to the starting point (x1 y1 x2 y2 x3 y3 x y) +
109 case 'B':
110 // clockwise arc (x1 y1 x2 y2 x3 y3 x y) +
111 case 'W':
112 // the same as W, but implied moveto (x1 y1 x2 y2 x3 y3 x y) +
113 case 'V': {
114 bool lineTo = ((m_command.unicode() == 'A') || (m_command.unicode() == 'W'));
115 bool clockwise = ((m_command.unicode() == 'W') || (m_command.unicode() == 'V'));
116 for (int i = 0; i < pointsCount; i += 4) {
117 QRectF bbox = rectFromPoints(points[i], points[i + 1]);
118 QPointF center = bbox.center();
119 qreal rx = 0.5 * bbox.width();
120 qreal ry = 0.5 * bbox.height();
121
122 if (rx == 0) {
123 rx = 1;
124 }
125
126 if (ry == 0) {
127 ry = 1;
128 }
129
130 QPointF startRadialVector = points[i + 2] - center;
131 QPointF endRadialVector = points[i + 3] - center;
132
133 // convert from ellipse space to unit-circle space
134 qreal x0 = startRadialVector.x() / rx;
135 qreal y0 = startRadialVector.y() / ry;
136
137 qreal x1 = endRadialVector.x() / rx;
138 qreal y1 = endRadialVector.y() / ry;
139
140 qreal startAngle = angleFromPoint(QPointF(x0, y0));
141 qreal stopAngle = angleFromPoint(QPointF(x1, y1));
142
143 // we are moving counter-clockwise to the end angle
144 qreal sweepAngle = radSweepAngle(startAngle, stopAngle, clockwise);
145 // compute the starting point to draw the line to
146 // as the point x3 y3 is not on the ellipse, spec says the point define radial vector
147 QPointF startPoint(rx * cos(startAngle), ry * sin(2 * M_PI - startAngle));
148
149 // if A or W is first command in enhanced path
150 // move to the starting point
151 bool isFirstCommandInPath = (m_parent->subpathCount() == 0);
152 bool isFirstCommandInSubpath = m_parent->isClosedSubpath(m_parent->subpathCount() - 1);
153
154 if (lineTo && !isFirstCommandInPath && !isFirstCommandInSubpath) {
155 m_parent->lineTo(center + startPoint);
156 } else {
157 m_parent->moveTo(center + startPoint);
158 }
159
160 m_parent->arcTo(rx, ry, startAngle * rad2deg, sweepAngle * rad2deg);
161 }
162 break;
163 }
164 // elliptical quadrant (initial segment tangential to x-axis) (x y) +
165 case 'X': {
166 KoPathPoint *lastPoint = lastPathPoint();
167 bool xDir = true;
168 foreach (const QPointF &point, points) {
169 qreal rx = point.x() - lastPoint->point().x();
170 qreal ry = point.y() - lastPoint->point().y();
171 qreal startAngle = xDir ? (ry > 0.0 ? 90.0 : 270.0) : (rx < 0.0 ? 0.0 : 180.0);
172 qreal sweepAngle = xDir ? (rx * ry < 0.0 ? 90.0 : -90.0) : (rx * ry > 0.0 ? 90.0 : -90.0);
173 lastPoint = m_parent->arcTo(fabs(rx), fabs(ry), startAngle, sweepAngle);
174 xDir = !xDir;
175 }
176 break;
177 }
178 // elliptical quadrant (initial segment tangential to y-axis) (x y) +
179 case 'Y': {
180 KoPathPoint *lastPoint = lastPathPoint();
181 bool xDir = false;
182 foreach (const QPointF &point, points) {
183 qreal rx = point.x() - lastPoint->point().x();
184 qreal ry = point.y() - lastPoint->point().y();
185 qreal startAngle = xDir ? (ry > 0.0 ? 90.0 : 270.0) : (rx < 0.0 ? 0.0 : 180.0);
186 qreal sweepAngle = xDir ? (rx * ry < 0.0 ? 90.0 : -90.0) : (rx * ry > 0.0 ? 90.0 : -90.0);
187 lastPoint = m_parent->arcTo(fabs(rx), fabs(ry), startAngle, sweepAngle);
188 xDir = !xDir;
189 }
190 break;
191 }
192 // quadratic bezier curve (x1 y1 x y)+
193 case 'Q':
194 for (int i = 0; i < pointsCount; i += 2) {
195 m_parent->curveTo(points[i], points[i + 1]);
196 }
197 break;
198 default:
199 break;
200 }
201 return true;
202}
203
205{
206 QList<QPointF> points;
207 QPointF p;
208
209 int paramCount = m_parameters.count();
210 for (int i = 0; i < paramCount - 1; i += 2) {
211 p.setX(m_parameters[i]->evaluate());
212 p.setY(m_parameters[i + 1]->evaluate());
213 points.append(p);
214 }
215
216 int mod = 1;
217 if (m_command.unicode() == 'C' || m_command.unicode() == 'U'
218 || m_command.unicode() == 'T') {
219 mod = 3;
220 } else if (m_command.unicode() == 'A' || m_command.unicode() == 'B'
221 || m_command.unicode() == 'W' || m_command.unicode() == 'V') {
222 mod = 4;
223 } else if (m_command.unicode() == 'Q') {
224 mod = 2;
225 }
226 if ((points.count() % mod) != 0) { // invalid command
227 qWarning() << "Invalid point count for command" << m_command << "ignoring" << "count:" << points.count() << "mod:" << mod;
228 return QList<QPointF>();
229 }
230
231 return points;
232}
233
235{
236 if (parameter) {
237 m_parameters.append(parameter);
238 }
239}
240
241qreal EnhancedPathCommand::angleFromPoint(const QPointF &point) const
242{
243 qreal angle = atan2(point.y(), point.x());
244 if (angle < 0.0) {
245 angle += 2 * M_PI;
246 }
247
248 return 2 * M_PI - angle;
249}
250
251qreal EnhancedPathCommand::radSweepAngle(qreal start, qreal stop, bool clockwise) const
252{
253 qreal sweepAngle = stop - start;
254 if (fabs(sweepAngle) < 0.1) {
255 return 2 * M_PI;
256 }
257 if (clockwise) {
258 // we are moving clockwise to the end angle
259 if (stop > start) {
260 sweepAngle = (stop - start) - 2 * M_PI;
261 }
262 } else {
263 // we are moving counter-clockwise to the stop angle
264 if (start > stop) {
265 sweepAngle = 2 * M_PI - (start - stop);
266 }
267 }
268
269 return sweepAngle;
270}
271
272qreal EnhancedPathCommand::degSweepAngle(qreal start, qreal stop, bool clockwise) const
273{
274 qreal sweepAngle = stop - start;
275 if (fabs(sweepAngle) < 0.1) {
276 return 360.0;
277 }
278 if (clockwise) {
279 // we are moving clockwise to the end angle
280 if (stop > start) {
281 sweepAngle = (stop - start) - 360.0;
282 }
283 } else {
284 // we are moving counter-clockwise to the stop angle
285 if (start > stop) {
286 sweepAngle = 360.0 - (start - stop);
287 }
288 }
289
290 return sweepAngle;
291}
292
294{
295 KoPathPoint *lastPoint = 0;
296 int subpathCount = m_parent->subpathCount();
297 if (subpathCount) {
298 int subpathPointCount = m_parent->subpathPointCount(subpathCount - 1);
299 lastPoint = m_parent->pointByIndex(KoPathPointIndex(subpathCount - 1, subpathPointCount - 1));
300 }
301 return lastPoint;
302}
303
304QRectF EnhancedPathCommand::rectFromPoints(const QPointF &tl, const QPointF &br) const
305{
306 return QRectF(tl, QSizeF(br.x() - tl.x(), br.y() - tl.y())).normalized();
307}
308
310{
311 QString cmd = m_command;
312
313 Q_FOREACH (EnhancedPathParameter *p, m_parameters) {
314 cmd += p->toString() + ' ';
315 }
316
317 return cmd.trimmed();
318}
const qreal rad2deg
const Params2D p
QPair< int, int > KoPathPointIndex
Definition KoPathShape.h:28
bool execute()
Executes the command on the specified path shape.
QString toString() const
Returns a string representation of the command.
QChar m_command
the actual command
QRectF rectFromPoints(const QPointF &tl, const QPointF &br) const
Returns rectangle from given points.
EnhancedPathShape * m_parent
the enhanced path owning the command
KoPathPoint * lastPathPoint() const
Returns the last path point of given path.
EnhancedPathCommand(const QChar &command, EnhancedPathShape *parent)
Constructs a new command from the given command type.
QList< QPointF > pointsFromParameters()
Returns a list of points, created from the parameter list.
QList< EnhancedPathParameter * > m_parameters
the commands parameters
void addParameter(EnhancedPathParameter *parameter)
Adds a new parameter to the command.
qreal radSweepAngle(qreal start, qreal stop, bool clockwise) const
Returns sweep angle from start to stop and given direction.
qreal degSweepAngle(qreal start, qreal stop, bool clockwise) const
Returns sweep angle from start to stop and given direction.
qreal angleFromPoint(const QPointF &point) const
Calculates angle from given point.
The abstract parameter class.
A KoPathPoint represents a point in a path.
QPointF point
int subpathPointCount(int subpathIndex) const
Returns the number of points in a subpath.
bool isClosedSubpath(int subpathIndex) const
Checks if a subpath is closed.
KoPathPoint * lineTo(const QPointF &p)
Adds a new line segment.
void close()
Closes the current subpath.
KoPathPoint * moveTo(const QPointF &p)
Starts a new Subpath.
KoPathPoint * arcTo(qreal rx, qreal ry, qreal startAngle, qreal sweepAngle)
Add an arc.
KoPathPoint * curveTo(const QPointF &c1, const QPointF &c2, const QPointF &p)
Adds a new cubic Bezier curve segment.
int subpathCount() const
Returns the number of subpaths in the path.
KoPathPoint * pointByIndex(const KoPathPointIndex &pointIndex) const
Returns the path point specified by a path point index.
#define M_PI
Definition kis_global.h:111