Krita Source Code Documentation
Loading...
Searching...
No Matches
KoShapeStroke.cpp
Go to the documentation of this file.
1/* This file is part of the KDE project
2 *
3 * SPDX-FileCopyrightText: 2006-2007 Thomas Zander <zander@kde.org>
4 * SPDX-FileCopyrightText: 2006-2008 Jan Hambrecht <jaham@gmx.net>
5 * SPDX-FileCopyrightText: 2007, 2009 Thorsten Zachmann <zachmann@kde.org>
6 * SPDX-FileCopyrightText: 2012 Inge Wallin <inge@lysator.liu.se>
7 *
8 * SPDX-License-Identifier: LGPL-2.0-or-later
9 */
10
11// Own
12#include "KoShapeStroke.h"
13
14// Posix
15#include <math.h>
16
17// Qt
18#include <QPainterPath>
19#include <QPainter>
20
21// Calligra
22
23// Flake
24#include "KoShape.h"
26#include "KoPathShape.h"
27#include "KoMarker.h"
28#include "KoInsets.h"
29#include <KoPathSegment.h>
30#include <KoPathPoint.h>
31#include <cmath>
33
34#include "kis_global.h"
35
36class Q_DECL_HIDDEN KoShapeStroke::Private
37{
38public:
39 Private(KoShapeStroke *_q) : q(_q) {}
41
42 void paintBorder(const KoShape *shape, QPainter &painter, const QPen &pen) const;
43 void paintMarkers(const KoShape *shape, QPainter &painter, const QPen &pen) const;
44 QColor color;
45 QPen pen;
46 QBrush brush;
47};
48
49namespace {
50QPair<qreal, qreal> anglesForSegment(KoPathSegment segment) {
51 const qreal eps = 1e-6;
52
53 if (segment.degree() < 3) {
54 segment = segment.toCubic();
55 }
56
57 QList<QPointF> points = segment.controlPoints();
58 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(points.size() == 4, qMakePair(0.0, 0.0));
59 QPointF vec1 = points[1] - points[0];
60 QPointF vec2 = points[3] - points[2];
61
62 if (vec1.manhattanLength() < eps) {
63 points[1] = segment.pointAt(eps);
64 vec1 = points[1] - points[0];
65 }
66
67 if (vec2.manhattanLength() < eps) {
68 points[2] = segment.pointAt(1.0 - eps);
69 vec2 = points[3] - points[2];
70 }
71
72 const qreal angle1 = std::atan2(vec1.y(), vec1.x());
73 const qreal angle2 = std::atan2(vec2.y(), vec2.x());
74 return qMakePair(angle1, angle2);
75}
76}
77
78void KoShapeStroke::Private::paintBorder(const KoShape *shape, QPainter &painter, const QPen &pen) const
79{
80 if (!pen.isCosmetic() && pen.style() != Qt::NoPen) {
81 const KoPathShape *pathShape = dynamic_cast<const KoPathShape *>(shape);
82 if (pathShape) {
83 QPainterPath path = pathShape->pathStroke(pen);
84
85 painter.fillPath(path, pen.brush());
86
87 return;
88 }
89
90 painter.strokePath(shape->outline(), pen);
91 }
92}
93void KoShapeStroke::Private::paintMarkers(const KoShape *shape, QPainter &painter, const QPen &pen) const
94{
95 if (!pen.isCosmetic() && pen.style() != Qt::NoPen) {
96 const KoPathShape *pathShape = dynamic_cast<const KoPathShape *>(shape);
97 if (pathShape) {
98
99 if (!pathShape->hasMarkers()) return;
100
101 const bool autoFillMarkers = pathShape->autoFillMarkers();
102 KoMarker *startMarker = pathShape->marker(KoFlake::StartMarker);
103 KoMarker *midMarker = pathShape->marker(KoFlake::MidMarker);
104 KoMarker *endMarker = pathShape->marker(KoFlake::EndMarker);
105
106 for (int i = 0; i < pathShape->subpathCount(); i++) {
107 const int numSubPoints = pathShape->subpathPointCount(i);
108 if (numSubPoints < 2) continue;
109
110 const bool isClosedSubpath = pathShape->isClosedSubpath(i);
111
112 qreal firstAngle = 0.0;
113 {
114 KoPathSegment segment = pathShape->segmentByIndex(KoPathPointIndex(i, 0));
115 firstAngle= anglesForSegment(segment).first;
116 }
117
118 const int numSegments = isClosedSubpath ? numSubPoints : numSubPoints - 1;
119
120 qreal lastAngle = 0.0;
121 {
122 KoPathSegment segment = pathShape->segmentByIndex(KoPathPointIndex(i, numSegments - 1));
123 lastAngle = anglesForSegment(segment).second;
124 }
125
126 qreal previousAngle = 0.0;
127
128 for (int j = 0; j < numSegments; j++) {
129 KoPathSegment segment = pathShape->segmentByIndex(KoPathPointIndex(i, j));
130 QPair<qreal, qreal> angles = anglesForSegment(segment);
131
132 const qreal angle1 = angles.first;
133 const qreal angle2 = angles.second;
134
135 if (j == 0 && startMarker) {
136 const qreal angle = isClosedSubpath ? bisectorAngle(firstAngle, lastAngle) : firstAngle;
137 if (autoFillMarkers) {
138 startMarker->applyShapeStroke(shape, q, segment.first()->point(), pen.widthF(), angle);
139 }
140 startMarker->paintAtPosition(&painter, segment.first()->point(), pen.widthF(), angle);
141 }
142
143 if (j > 0 && midMarker) {
144 const qreal angle = bisectorAngle(previousAngle, angle1);
145 if (autoFillMarkers) {
146 midMarker->applyShapeStroke(shape, q, segment.first()->point(), pen.widthF(), angle);
147 }
148 midMarker->paintAtPosition(&painter, segment.first()->point(), pen.widthF(), angle);
149 }
150
151 if (j == numSegments - 1 && endMarker) {
152 const qreal angle = isClosedSubpath ? bisectorAngle(firstAngle, lastAngle) : lastAngle;
153 if (autoFillMarkers) {
154 endMarker->applyShapeStroke(shape, q, segment.second()->point(), pen.widthF(), angle);
155 }
156 endMarker->paintAtPosition(&painter, segment.second()->point(), pen.widthF(), angle);
157 }
158
159 previousAngle = angle2;
160 }
161 }
162 }
163 }
164}
165
167 : d(new Private(this))
168{
169 d->color = QColor(Qt::black);
170 // we are not rendering stroke with zero width anymore
171 // so lets use a default width of 1.0
172 d->pen.setWidthF(1.0);
173}
174
176 : KoShapeStrokeModel(), d(new Private(this))
177{
178 d->color = other.d->color;
179 d->pen = other.d->pen;
180 d->brush = other.d->brush;
181}
182
183KoShapeStroke::KoShapeStroke(qreal lineWidth, const QColor &color)
184 : d(new Private(this))
185{
186 d->pen.setWidthF(qMax(qreal(0.0), lineWidth));
187 d->pen.setJoinStyle(Qt::MiterJoin);
188 d->color = color;
189}
190
192{
193 delete d;
194}
195
197{
198 if (this == &rhs)
199 return *this;
200
201 d->pen = rhs.d->pen;
202 d->color = rhs.d->color;
203 d->brush = rhs.d->brush;
204
205 return *this;
206}
207
208void KoShapeStroke::strokeInsets(const KoShape *shape, KoInsets &insets) const
209{
210 Q_UNUSED(shape);
211
212 // '0.5' --- since we draw a line half inside, and half outside the object.
213 qreal extent = 0.5 * (d->pen.widthF() >= 0 ? d->pen.widthF() : 1.0);
214
215 // if we have square cap, we need a little more space
216 // -> sqrt((0.5*penWidth)^2 + (0.5*penWidth)^2)
217 if (capStyle() == Qt::SquareCap) {
218 extent *= M_SQRT2;
219 }
220
221 if (joinStyle() == Qt::MiterJoin) {
222 // miter limit in Qt is normalized by the line width (and not half-width)
223 extent = qMax(extent, d->pen.widthF() * miterLimit());
224 }
225
226 insets.top = extent;
227 insets.bottom = extent;
228 insets.left = extent;
229 insets.right = extent;
230}
231
233{
234 qreal result = 0.0;
235
236 const KoPathShape *pathShape = dynamic_cast<const KoPathShape *>(shape);
237 if (pathShape && pathShape->hasMarkers()) {
238 const qreal lineWidth = d->pen.widthF();
239
241 markers << pathShape->marker(KoFlake::StartMarker);
242 markers << pathShape->marker(KoFlake::MidMarker);
243 markers << pathShape->marker(KoFlake::EndMarker);
244
245 Q_FOREACH (const KoMarker *marker, markers) {
246 if (marker) {
247 result = qMax(result, marker->maxInset(lineWidth));
248 }
249 }
250 }
251
252 return result;
253}
254
256{
257 return d->color.alpha() > 0;
258}
259
261{
262 QPen pen = d->pen;
263
264 if (d->brush.gradient()) {
265 pen.setBrush(d->brush);
266 } else {
267 pen.setColor(d->color.isValid() ? d->color : Qt::transparent);
268 }
269
270 return pen;
271}
272
273void KoShapeStroke::paint(const KoShape *shape, QPainter &painter) const
274{
275 KisQPainterStateSaver saver(&painter);
276
277 d->paintBorder(shape, painter, resultLinePen());
278}
279
280void KoShapeStroke::paintMarkers(const KoShape *shape, QPainter &painter) const
281{
282 KisQPainterStateSaver saver(&painter);
283
284 d->paintMarkers(shape, painter, resultLinePen());
285}
286
288{
289 if (!other) return false;
290
291 const KoShapeStroke *stroke = dynamic_cast<const KoShapeStroke*>(other);
292 if (!stroke) return false;
293
294 return (d->brush.gradient() && d->brush == stroke->d->brush) ||
295 (!d->brush.gradient() && d->color == stroke->d->color);
296}
297
299{
300 if (!other) return false;
301
302 const KoShapeStroke *stroke = dynamic_cast<const KoShapeStroke*>(other);
303 if (!stroke) return false;
304
305 QPen pen1 = d->pen;
306 QPen pen2 = stroke->d->pen;
307
308 // just a random color top avoid comparison of that property
309 pen1.setColor(Qt::magenta);
310 pen2.setColor(Qt::magenta);
311
312 return pen1 == pen2;
313}
314
316{
317 return d->pen.widthF() > 0 &&
318 (d->brush.gradient() || d->color.alpha() > 0);
319}
320
321void KoShapeStroke::setCapStyle(Qt::PenCapStyle style)
322{
323 d->pen.setCapStyle(style);
324}
325
326Qt::PenCapStyle KoShapeStroke::capStyle() const
327{
328 return d->pen.capStyle();
329}
330
331void KoShapeStroke::setJoinStyle(Qt::PenJoinStyle style)
332{
333 d->pen.setJoinStyle(style);
334}
335
336Qt::PenJoinStyle KoShapeStroke::joinStyle() const
337{
338 return d->pen.joinStyle();
339}
340
341void KoShapeStroke::setLineWidth(qreal lineWidth)
342{
343 d->pen.setWidthF(qMax(qreal(0.0), lineWidth));
344}
345
347{
348 return d->pen.widthF();
349}
350
351void KoShapeStroke::setMiterLimit(qreal miterLimit)
352{
353 d->pen.setMiterLimit(miterLimit);
354}
355
357{
358 return d->pen.miterLimit();
359}
360
361QColor KoShapeStroke::color() const
362{
363 return d->color;
364}
365
366void KoShapeStroke::setColor(const QColor &color)
367{
368 d->color = color;
369}
370
371void KoShapeStroke::setLineStyle(Qt::PenStyle style, const QVector<qreal> &dashes)
372{
373 if (style < Qt::CustomDashLine) {
374 d->pen.setStyle(style);
375 } else {
376 d->pen.setDashPattern(dashes);
377 }
378}
379
380Qt::PenStyle KoShapeStroke::lineStyle() const
381{
382 return d->pen.style();
383}
384
386{
387 return d->pen.dashPattern();
388}
389
390void KoShapeStroke::setDashOffset(qreal dashOffset)
391{
392 d->pen.setDashOffset(dashOffset);
393}
394
396{
397 return d->pen.dashOffset();
398}
399
400void KoShapeStroke::setLineBrush(const QBrush &brush)
401{
402 d->brush = brush;
403}
404
406{
407 return d->brush;
408}
QPair< int, int > KoPathPointIndex
Definition KoPathShape.h:28
void paintAtPosition(QPainter *painter, const QPointF &pos, qreal strokeWidth, qreal nodeAngle)
paintAtOrigin paints the marker at the position pos. Scales and rotates the marker if needed.
Definition KoMarker.cpp:228
void applyShapeStroke(const KoShape *shape, KoShapeStroke *stroke, const QPointF &pos, qreal strokeWidth, qreal nodeAngle)
Definition KoMarker.cpp:316
qreal maxInset(qreal strokeWidth) const
Definition KoMarker.cpp:243
QPointF point
A KoPathSegment consist of two neighboring KoPathPoints.
KoPathPoint * first
int degree() const
Returns the degree of the segment: 1 = line, 2 = quadratic, 3 = cubic, -1 = invalid.
QPointF pointAt(qreal t) const
Returns point at given t.
KoPathPoint * second
QList< QPointF > controlPoints() const
Returns ordered list of control points.
KoPathSegment toCubic() const
Returns cubic bezier curve segment of this segment.
The position of a path point within a path shape.
Definition KoPathShape.h:63
bool autoFillMarkers() const
int subpathPointCount(int subpathIndex) const
Returns the number of points in a subpath.
bool isClosedSubpath(int subpathIndex) const
Checks if a subpath is closed.
KoMarker * marker(KoFlake::MarkerPosition pos) const
QPainterPath pathStroke(const QPen &pen) const
int subpathCount() const
Returns the number of subpaths in the path.
KoPathSegment segmentByIndex(const KoPathPointIndex &pointIndex) const
Returns the segment specified by a path point index.
bool hasMarkers() const
QBrush lineBrush() const
Returns the strokes brush.
void setJoinStyle(Qt::PenJoinStyle style)
Sets the lines join style.
void setMiterLimit(qreal miterLimit)
Sets the miter limit.
KoShapeStroke * q
bool compareFillTo(const KoShapeStrokeModel *other) override
void setLineStyle(Qt::PenStyle style, const QVector< qreal > &dashes)
Sets the line style.
qreal strokeMaxMarkersInset(const KoShape *shape) const override
void paint(const KoShape *shape, QPainter &painter) const override
qreal miterLimit() const
Returns the miter limit.
KoShapeStroke()
Constructor for a thin line in black.
Private *const d
QPen resultLinePen() const
KoShapeStroke & operator=(const KoShapeStroke &rhs)
Assignment operator.
qreal lineWidth() const
Returns the line width.
void paintBorder(const KoShape *shape, QPainter &painter, const QPen &pen) const
qreal dashOffset() const
Returns the dash offset.
Qt::PenStyle lineStyle() const
Returns the line style.
void setColor(const QColor &color)
Sets the color.
Private(KoShapeStroke *_q)
Qt::PenCapStyle capStyle() const
Returns the lines cap style.
bool hasTransparency() const override
void setDashOffset(qreal dashOffset)
Sets the dash offset.
bool isVisible() const override
bool compareStyleTo(const KoShapeStrokeModel *other) override
~KoShapeStroke() override
void setLineBrush(const QBrush &brush)
Sets the strokes brush used to fill strokes of this border.
void strokeInsets(const KoShape *shape, KoInsets &insets) const override
Qt::PenJoinStyle joinStyle() const
Returns the lines join style.
void setLineWidth(qreal lineWidth)
Sets the line width.
void setCapStyle(Qt::PenCapStyle style)
Sets the lines cap style.
QVector< qreal > lineDashes() const
Returns the line dashes.
void paintMarkers(const KoShape *shape, QPainter &painter, const QPen &pen) const
virtual QPainterPath outline() const
Definition KoShape.cpp:630
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
const qreal eps
qreal bisectorAngle(qreal a, qreal b)
Definition kis_global.h:157
@ EndMarker
Definition KoFlake.h:44
@ StartMarker
Definition KoFlake.h:42
@ MidMarker
Definition KoFlake.h:43
qreal bottom
Bottom inset.
Definition KoInsets.h:50
qreal right
Right inset.
Definition KoInsets.h:52
qreal top
Top inset.
Definition KoInsets.h:49
qreal left
Left inset.
Definition KoInsets.h:51