Krita Source Code Documentation
Loading...
Searching...
No Matches
EllipseShape.cpp
Go to the documentation of this file.
1/* This file is part of the KDE project
2 SPDX-FileCopyrightText: 2006-2008 Thorsten Zachmann <zachmann@kde.org>
3 SPDX-FileCopyrightText: 2006, 2008 Jan Hambrecht <jaham@gmx.net>
4 SPDX-FileCopyrightText: 2009 Thomas Zander <zander@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8
9#include "EllipseShape.h"
10
11#include <KoPathPoint.h>
13#include <KoXmlWriter.h>
14#include <KoXmlNS.h>
15#include <KoUnit.h>
16#include <SvgSavingContext.h>
17#include <SvgLoadingContext.h>
18#include <SvgUtil.h>
19#include <SvgStyleWriter.h>
20
21#include <KoParameterShape_p.h>
22#include "kis_global.h"
23
24#include <math.h>
25
27 : m_startAngle(0)
28 , m_endAngle(0)
29 , m_kindAngle(M_PI)
30 , m_type(Arc)
31{
33 handles.push_back(QPointF(100, 50));
34 handles.push_back(QPointF(100, 50));
35 handles.push_back(QPointF(0, 50));
37 QSizeF size(100, 100);
38 m_radii = QPointF(size.width() / 2.0, size.height() / 2.0);
39 m_center = QPointF(m_radii.x(), m_radii.y());
41}
42
44 : KoParameterShape(rhs),
45 m_startAngle(rhs.m_startAngle),
46 m_endAngle(rhs.m_endAngle),
47 m_kindAngle(rhs.m_kindAngle),
48 m_center(rhs.m_center),
49 m_radii(rhs.m_radii),
50 m_type(rhs.m_type)
51{
52}
53
57
59{
60 return new EllipseShape(*this);
61}
62
63void EllipseShape::setSize(const QSizeF &newSize)
64{
65 QTransform matrix(resizeMatrix(newSize));
66 m_center = matrix.map(m_center);
67 m_radii = matrix.map(m_radii);
69}
70
72{
73 QPointF offset(KoParameterShape::normalize());
74 QTransform matrix;
75 matrix.translate(-offset.x(), -offset.y());
76 m_center = matrix.map(m_center);
77 return offset;
78}
79
80void EllipseShape::moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers)
81{
82 Q_UNUSED(modifiers);
83 QPointF p(point);
84
85 QPointF diff(m_center - point);
86 diff.setX(-diff.x());
87 qreal angle = 0;
88 if (diff.x() == 0) {
89 angle = (diff.y() < 0 ? 270 : 90) * M_PI / 180.0;
90 } else {
91 diff.setY(diff.y() * m_radii.x() / m_radii.y());
92 angle = atan(diff.y() / diff.x());
93 if (angle < 0) {
94 angle += M_PI;
95 }
96
97 if (diff.y() < 0) {
98 angle += M_PI;
99 }
100 }
101
103 switch (handleId) {
104 case 0:
105 p = QPointF(m_center + QPointF(cos(angle) * m_radii.x(), -sin(angle) * m_radii.y()));
107 handles[handleId] = p;
108 break;
109 case 1:
110 p = QPointF(m_center + QPointF(cos(angle) * m_radii.x(), -sin(angle) * m_radii.y()));
112 handles[handleId] = p;
113 break;
114 case 2: {
115 QList<QPointF> kindHandlePositions;
116 kindHandlePositions.push_back(QPointF(m_center + QPointF(cos(m_kindAngle) * m_radii.x(), -sin(m_kindAngle) * m_radii.y())));
117 kindHandlePositions.push_back(m_center);
118 kindHandlePositions.push_back((handles[0] + handles[1]) / 2.0);
119
120 QPointF diff = m_center * 2.0;
121 int handlePos = 0;
122 for (int i = 0; i < kindHandlePositions.size(); ++i) {
123 QPointF pointDiff(p - kindHandlePositions[i]);
124 if (i == 0 || qAbs(pointDiff.x()) + qAbs(pointDiff.y()) < qAbs(diff.x()) + qAbs(diff.y())) {
125 diff = pointDiff;
126 handlePos = i;
127 }
128 }
129 handles[handleId] = kindHandlePositions[handlePos];
130 m_type = EllipseType(handlePos);
131 }
132 break;
133 }
135
136 if (handleId != 2) {
138 }
139}
140
141void EllipseShape::updatePath(const QSizeF &size)
142{
143 Q_UNUSED(size);
144 QPointF startpoint(handles()[0]);
145
146 QPointF curvePoints[12];
147 const qreal distance = sweepAngle();
148
149 const bool sameAngles = distance > 359.9;
150 int pointCnt = arcToCurve(m_radii.x(), m_radii.y(), m_startAngle, distance, startpoint, curvePoints);
152
153 int curvePointCount = 1 + pointCnt / 3;
154 int requiredPointCount = curvePointCount;
155 if (m_type == Pie) {
156 requiredPointCount++;
157 } else if (m_type == Arc && sameAngles) {
158 curvePointCount--;
159 requiredPointCount--;
160 }
161
162 createPoints(requiredPointCount);
163
164 KoSubpath &points = *subpaths()[0];
165
166 int curveIndex = 0;
167 points[0]->setPoint(startpoint);
168 points[0]->removeControlPoint1();
169 points[0]->setProperty(KoPathPoint::StartSubpath);
170 for (int i = 1; i < curvePointCount; ++i) {
171 points[i - 1]->setControlPoint2(curvePoints[curveIndex++]);
172 points[i]->setControlPoint1(curvePoints[curveIndex++]);
173 points[i]->setPoint(curvePoints[curveIndex++]);
174 points[i]->removeControlPoint2();
175 }
176
177 if (m_type == Pie) {
178 points[requiredPointCount - 1]->setPoint(m_center);
179 points[requiredPointCount - 1]->removeControlPoint1();
180 points[requiredPointCount - 1]->removeControlPoint2();
181 } else if (m_type == Arc && sameAngles) {
182 points[curvePointCount - 1]->setControlPoint2(curvePoints[curveIndex]);
183 points[0]->setControlPoint1(curvePoints[++curveIndex]);
184 }
185
186 for (int i = 0; i < requiredPointCount; ++i) {
187 points[i]->unsetProperty(KoPathPoint::StopSubpath);
188 points[i]->unsetProperty(KoPathPoint::CloseSubpath);
189 }
190 subpaths()[0]->last()->setProperty(KoPathPoint::StopSubpath);
191 if (m_type == Arc && !sameAngles) {
192 subpaths()[0]->first()->unsetProperty(KoPathPoint::CloseSubpath);
193 subpaths()[0]->last()->unsetProperty(KoPathPoint::CloseSubpath);
194 } else {
195 subpaths()[0]->first()->setProperty(KoPathPoint::CloseSubpath);
196 subpaths()[0]->last()->setProperty(KoPathPoint::CloseSubpath);
197 }
198
200
201 normalize();
202}
203
204void EllipseShape::createPoints(int requiredPointCount)
205{
206 if (subpaths().count() != 1) {
207 clear();
208 subpaths().append(new KoSubpath());
209 }
210 int currentPointCount = subpaths()[0]->count();
211 if (currentPointCount > requiredPointCount) {
212 for (int i = 0; i < currentPointCount - requiredPointCount; ++i) {
213 delete subpaths()[0]->front();
214 subpaths()[0]->pop_front();
215 }
216 } else if (requiredPointCount > currentPointCount) {
217 for (int i = 0; i < requiredPointCount - currentPointCount; ++i) {
218 subpaths()[0]->append(new KoPathPoint(this, QPointF()));
219 }
220 }
221
223}
224
226{
227 qreal angle = 0.5 * (m_startAngle + m_endAngle);
228 if (m_startAngle > m_endAngle) {
229 angle += 180.0;
230 }
231
233
235 switch (m_type) {
236 case Arc:
237 handles[2] = m_center + QPointF(cos(m_kindAngle) * m_radii.x(), -sin(m_kindAngle) * m_radii.y());
238 break;
239 case Pie:
240 handles[2] = m_center;
241 break;
242 case Chord:
243 handles[2] = (handles[0] + handles[1]) / 2.0;
244 break;
245 }
247}
248
250{
254 handles[0] = m_center + QPointF(cos(startRadian) * m_radii.x(), -sin(startRadian) * m_radii.y());
255 handles[1] = m_center + QPointF(cos(endRadian) * m_radii.x(), -sin(endRadian) * m_radii.y());
257}
258
260{
263
264 qreal sAngle = a2 - a1;
265
266 if (a1 > a2) {
267 sAngle = 2 * M_PI + sAngle;
268 }
269
270 if (qAbs(a1 - a2) < 0.05 / M_PI) {
271 sAngle = 2 * M_PI;
272 }
273
274 return kisRadiansToDegrees(sAngle);
275}
276
283
288
290{
291 m_startAngle = angle;
294 updatePath(size());
295}
296
298{
299 return m_startAngle;
300}
301
303{
304 m_endAngle = angle;
307 updatePath(size());
308}
309
311{
312 return m_endAngle;
313}
314
316{
317 return EllipseShapeId;
318}
319
321{
322 // let basic path saiving code handle our saving
323 if (!isParametricShape()) return false;
324
325 if (type() == EllipseShape::Arc && startAngle() == endAngle()) {
326 const QSizeF size = this->size();
327 const bool isCircle = size.width() == size.height();
328 context.shapeWriter().startElement(isCircle ? "circle" : "ellipse");
329 context.shapeWriter().addAttribute("id", context.getID(this));
331 SvgStyleWriter::saveMetadata(this, context);
332
333 if (isCircle) {
334 context.shapeWriter().addAttribute("r", 0.5 * size.width());
335 } else {
336 context.shapeWriter().addAttribute("rx", 0.5 * size.width());
337 context.shapeWriter().addAttribute("ry", 0.5 * size.height());
338 }
339 context.shapeWriter().addAttribute("cx", 0.5 * size.width());
340 context.shapeWriter().addAttribute("cy", 0.5 * size.height());
341
342 SvgStyleWriter::saveSvgStyle(this, context);
343
344 context.shapeWriter().endElement();
345 } else {
346 context.shapeWriter().startElement("path");
347 context.shapeWriter().addAttribute("id", context.getID(this));
349
350 context.shapeWriter().addAttribute("sodipodi:type", "arc");
351
352 context.shapeWriter().addAttribute("sodipodi:rx", m_radii.x());
353 context.shapeWriter().addAttribute("sodipodi:ry", m_radii.y());
354
355 context.shapeWriter().addAttribute("sodipodi:cx", m_center.x());
356 context.shapeWriter().addAttribute("sodipodi:cy", m_center.y());
357
358 context.shapeWriter().addAttribute("sodipodi:start", 2 * M_PI - kisDegreesToRadians(endAngle()));
359 context.shapeWriter().addAttribute("sodipodi:end", 2 * M_PI - kisDegreesToRadians(startAngle()));
360
361 switch (type()) {
362 case Pie:
363 // noop
364 break;
365 case Chord:
366 context.shapeWriter().addAttribute("sodipodi:arc-type", "chord");
367 break;
368 case Arc:
369 context.shapeWriter().addAttribute("sodipodi:open", "true");
370 break;
371 }
372
373 context.shapeWriter().addAttribute("d", this->toString(context.userSpaceTransform()));
374
375 SvgStyleWriter::saveSvgStyle(this, context);
376
377 context.shapeWriter().endElement();
378 }
379
380 return true;
381}
382
383bool EllipseShape::loadSvg(const QDomElement &element, SvgLoadingContext &context)
384{
385 qreal rx = 0, ry = 0;
386 qreal cx = 0;
387 qreal cy = 0;
388 qreal start = 0;
389 qreal end = 0;
391
392 const QString extendedNamespace =
393 element.attribute("sodipodi:type") == "arc" ? "sodipodi" :
394 element.attribute("krita:type") == "arc" ? "krita" : "";
395
396 if (element.tagName() == "ellipse") {
397 rx = SvgUtil::parseUnitX(context.currentGC(), context.resolvedProperties(), element.attribute("rx"));
398 ry = SvgUtil::parseUnitY(context.currentGC(), context.resolvedProperties(), element.attribute("ry"));
399 cx = SvgUtil::parseUnitX(context.currentGC(), context.resolvedProperties(), element.attribute("cx", "0"));
400 cy = SvgUtil::parseUnitY(context.currentGC(), context.resolvedProperties(), element.attribute("cy", "0"));
401 } else if (element.tagName() == "circle") {
402 rx = ry = SvgUtil::parseUnitXY(context.currentGC(), context.resolvedProperties(), element.attribute("r"));
403 cx = SvgUtil::parseUnitX(context.currentGC(), context.resolvedProperties(), element.attribute("cx", "0"));
404 cy = SvgUtil::parseUnitY(context.currentGC(), context.resolvedProperties(), element.attribute("cy", "0"));
405
406 } else if (element.tagName() == "path" && !extendedNamespace.isEmpty()) {
407 rx = SvgUtil::parseUnitX(context.currentGC(), context.resolvedProperties(), element.attribute(extendedNamespace + ":rx"));
408 ry = SvgUtil::parseUnitY(context.currentGC(), context.resolvedProperties(), element.attribute(extendedNamespace + ":ry"));
409 cx = SvgUtil::parseUnitX(context.currentGC(), context.resolvedProperties(), element.attribute(extendedNamespace + ":cx", "0"));
410 cy = SvgUtil::parseUnitY(context.currentGC(), context.resolvedProperties(), element.attribute(extendedNamespace + ":cy", "0"));
411 start = 2 * M_PI - SvgUtil::parseNumber(element.attribute(extendedNamespace + ":end"));
412 end = 2 * M_PI - SvgUtil::parseNumber(element.attribute(extendedNamespace + ":start"));
413
414 const QString kritaArcType =
415 element.attribute("sodipodi:arc-type", element.attribute("krita:arcType"));
416
417 if (kritaArcType.isEmpty()) {
418 if (element.attribute("sodipodi:open", "false") == "false") {
419 type = Pie;
420 }
421 } else if (kritaArcType == "pie") {
422 type = Pie;
423 } else if (kritaArcType == "chord") {
424 type = Chord;
425 }
426 } else {
427 return false;
428 }
429
430 setSize(QSizeF(2 * rx, 2 * ry));
431 setPosition(QPointF(cx - rx, cy - ry));
432 if (rx == 0.0 || ry == 0.0) {
433 setVisible(false);
434 }
435
436 if (start != 0 || start != end) {
439 setType(type);
440 }
441
442 return true;
443}
#define EllipseShapeId
const Params2D p
qreal distance(const QPointF &p1, const QPointF &p2)
QList< KoPathPoint * > KoSubpath
a KoSubpath contains a path from a moveTo until a close or a new moveTo
Definition KoPathShape.h:31
void setType(EllipseType type)
void updatePath(const QSizeF &size) override
Update the path of the parameter shape.
qreal startAngle() const
Returns the actual ellipse start angle in degree.
QPointF normalize() override
Normalizes the path data.
void updateAngleHandles()
qreal m_startAngle
void setStartAngle(qreal angle)
~EllipseShape() override
void updateKindHandle()
qreal endAngle() const
Returns the actual ellipse end angle in degree.
QPointF m_radii
void setEndAngle(qreal angle)
EllipseType m_type
QString pathShapeId() const override
reimplemented
void moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers=Qt::NoModifier) override
Updates the internal state of a KoParameterShape.
EllipseType type() const
Returns the actual ellipse type.
bool loadSvg(const QDomElement &element, SvgLoadingContext &context) override
reimplemented from SvgShape
void setSize(const QSizeF &newSize) override
Resize the shape.
void createPoints(int requiredPointCount)
KoShape * cloneShape() const override
creates a deep copy of the shape or shape's subtree
qreal sweepAngle() const
bool saveSvg(SvgSavingContext &context) override
reimplemented from SvgShape
QPointF m_center
qreal m_kindAngle
EllipseType
the possible ellipse types
@ Pie
an ellipse pie
@ Chord
an ellipse chord
@ Arc
an ellipse arc
QPointF normalize() override
Normalizes the path data.
QList< QPointF > handles
the handles that the user can grab and change
void setSize(const QSizeF &size) override
reimplemented from KoShape
void setHandles(const QList< QPointF > &handles)
bool isParametricShape() const
Check if object is a parametric shape.
A KoPathPoint represents a point in a path.
@ StartSubpath
it starts a new subpath by a moveTo command
Definition KoPathPoint.h:38
@ CloseSubpath
it closes a subpath (only applicable on StartSubpath and StopSubpath)
Definition KoPathPoint.h:40
@ StopSubpath
it stops a subpath (last point of subpath)
Definition KoPathPoint.h:39
const KoSubpathList & subpaths() const
QTransform resizeMatrix(const QSizeF &newSize) const
QSizeF size() const override
reimplemented
void notifyPointsChanged()
int arcToCurve(qreal rx, qreal ry, qreal startAngle, qreal sweepAngle, const QPointF &offset, QPointF *curvePoints) const
Add an arc.
QString toString(const QTransform &matrix=QTransform()) const
Returns a odf/svg string representation of the path data with the given matrix applied.
void clear()
Removes all subpaths and their points from the path.
QTransform transformation() const
Returns the shapes local transformation matrix.
Definition KoShape.cpp:424
virtual void setPosition(const QPointF &position)
Set the position of the shape in pt.
Definition KoShape.cpp:295
void setVisible(bool on)
Definition KoShape.cpp:972
Contains data used for loading svg.
SvgGraphicsContext * currentGC() const
Returns the current graphics context.
KoSvgTextProperties resolvedProperties() const
These are the text properties, completely resolved, ensuring that everything is inherited and the siz...
Context for saving svg files.
QTransform userSpaceTransform() const
Returns the transformation used to transform into user space.
QScopedPointer< KoXmlWriter > shapeWriter
QString getID(const KoShape *obj)
Returns the unique id for the given shape.
static void saveSvgStyle(KoShape *shape, SvgSavingContext &context)
Saves the style of the specified shape.
static void saveMetadata(const KoShape *shape, SvgSavingContext &context)
static qreal parseUnitX(SvgGraphicsContext *gc, const KoSvgTextProperties &resolved, const QString &unit)
parses a length attribute in x-direction
Definition SvgUtil.cpp:304
static const char * parseNumber(const char *ptr, qreal &number)
parses the number into parameter number
Definition SvgUtil.cpp:378
static qreal parseUnitXY(SvgGraphicsContext *gc, const KoSvgTextProperties &resolved, const QString &unit)
parses a length attribute in xy-direction
Definition SvgUtil.cpp:322
static void writeTransformAttributeLazy(const QString &name, const QTransform &transform, KoXmlWriter &shapeWriter)
Writes a transform as an attribute name iff the transform is not empty.
Definition SvgUtil.cpp:124
static qreal parseUnitY(SvgGraphicsContext *gc, const KoSvgTextProperties &resolved, const QString &unit)
parses a length attribute in y-direction
Definition SvgUtil.cpp:313
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
T kisRadiansToDegrees(T radians)
Definition kis_global.h:181
std::enable_if< std::is_floating_point< T >::value, T >::type normalizeAngleDegrees(T a)
Definition kis_global.h:132
T kisDegreesToRadians(T degrees)
Definition kis_global.h:176
std::enable_if< std::is_floating_point< T >::value, T >::type normalizeAngle(T a)
Definition kis_global.h:121
#define M_PI
Definition kis_global.h:111