Krita Source Code Documentation
Loading...
Searching...
No Matches
SplineAssistant.cc
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2008 Cyrille Berger <cberger@cberger.net>
3 * SPDX-FileCopyrightText: 2010 Geoffry Song <goffrie@gmail.com>
4 * SPDX-FileCopyrightText: 2017 Scott Petrovic <scottpetrovic@gmail.com>
5 *
6 * SPDX-License-Identifier: LGPL-2.0-or-later
7 */
8
9#include "SplineAssistant.h"
10
11#include <klocalizedstring.h>
12
13#include <QPainter>
14#include <QPainterPath>
15#include <QLinearGradient>
16#include <QTransform>
17
18#include <kis_canvas2.h>
20#include "kis_debug.h"
21#include "KisBezierUtils.h"
22
23#include <math.h>
24#include <limits>
25#include <algorithm>
26
28{
36
39 : x(xval)
40 {
41 }
42
44
45 void inv_norm(qreal l, qreal u)
46 {
47 this->xnorm = this->x * (u - l) + l;
48 }
49
50 qreal fval;
51 qreal xnorm;
52 qreal x;
53 };
54
55
56 qreal lbound;
57 qreal ubound;
59};
60
61
63 Private();
64
66 qreal prev_t {0};
67};
68
70 : prevStrokebegin(0,0)
71{
72}
73
75 : KisPaintingAssistant("spline", i18n("Spline assistant"))
76 , m_d(new Private)
77{
78}
79
80SplineAssistant::SplineAssistant(const SplineAssistant &rhs, QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap)
81 : KisPaintingAssistant(rhs, handleMap)
82 , m_canvas(rhs.m_canvas)
83 , m_d(new Private)
84{
85}
86
87KisPaintingAssistantSP SplineAssistant::clone(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap) const
88{
89 return KisPaintingAssistantSP(new SplineAssistant(*this, handleMap));
90}
91
92// parametric form of a cubic spline (B(t) = (1-t)^3 P0 + 3 (1-t)^2 t P1 + 3 (1-t) t^2 P2 + t^3 P3)
93inline QPointF B(qreal t, const QPointF& P0, const QPointF& P1, const QPointF& P2, const QPointF& P3)
94{
95 const qreal tp = 1 - t;
96 const qreal tp2 = tp * tp;
97 const qreal t2 = t * t;
98
99 return ( tp2 * tp) * P0 +
100 (3 * tp2 * t ) * P1 +
101 (3 * tp * t2) * P2 +
102 ( t * t2) * P3;
103}
104// squared distance from a point on the spline to given point: we want to minimize this
105inline qreal D(qreal t, const QPointF& P0, const QPointF& P1, const QPointF& P2, const QPointF& P3, const QPointF& p)
106{
107 const qreal
108 tp = 1 - t,
109 tp2 = tp * tp,
110 t2 = t * t,
111 a = tp2 * tp,
112 b = 3 * tp2 * t,
113 c = 3 * tp * t2,
114 d = t * t2,
115 x_dist = a*P0.x() + b*P1.x() + c*P2.x() + d*P3.x() - p.x(),
116 y_dist = a*P0.y() + b*P1.y() + c*P2.y() + d*P3.y() - p.y();
117
118 return x_dist * x_dist + y_dist * y_dist;
119}
120
121
122inline qreal goldenSearch(const QPointF& pt
124 , qreal low
125 , qreal high
126 , qreal tolerance
127 , uint max_iter)
128{
129 GoldenSearchParams ovalues = GoldenSearchParams(low,high);
131 qreal u = ovalues.ubound;
132 qreal l = ovalues.lbound;
133
134 const qreal ratio = 1 - 2/(1 + sqrt(5));
135 p[0].x = 0;
136 p[1].x = ratio;
137 p[2].x = 1 - p[1].x;
138 p[3].x = 1;
139
140 p[1].inv_norm(l,u);
141 p[2].inv_norm(l,u);
142
143 p[1].fval = D(p[1].xnorm, *handles[0], *handles[2], *handles[3], *handles[1], pt);
144 p[2].fval = D(p[2].xnorm, *handles[0], *handles[2], *handles[3], *handles[1], pt);
145
147
148 uint i = 0; // used to force early exit
149 while ( qAbs(p[2].xnorm - p[1].xnorm) > tolerance && i < max_iter) {
150
151 if (p[1].fval < p[2].fval) {
152 xtemp = p[1];
153 p[3] = p[2];
154 p[1].x = p[0].x + (p[2].x - p[1].x);
155 p[2] = xtemp;
156
157 p[1].inv_norm(l,u);
158 p[1].fval = D(p[1].xnorm, *handles[0], *handles[2], *handles[3], *handles[1], pt);
159
160 } else {
161 xtemp = p[2];
162 p[0] = p[1];
163 p[2].x = p[1].x + (p[3].x - p[2].x);
164 p[1] = xtemp;
165
166 p[2].inv_norm(l,u);
167 p[2].fval = D(p[2].xnorm, *handles[0], *handles[2], *handles[3], *handles[1], pt);
168
169 }
170 i++;
171 }
172 return (p[2].xnorm + p[1].xnorm) / 2;
173}
174
175
176QPointF SplineAssistant::project(const QPointF& pt, const QPointF& strokeBegin) const
177{
178 Q_ASSERT(isAssistantComplete());
179
180 // minimize d(t), but keep t in the same neighbourhood as before (unless starting a new stroke)
181 bool stayClose = (m_d->prevStrokebegin == strokeBegin)? true : false;
182 qreal min_t;
183
184 QList<QPointF> refs;
185 QVector<int> hindex = {0,2,3,1}; // order handles as expected by KisBezierUtils
186 Q_FOREACH(int i, hindex) {
187 refs.append(*handles()[i]);
188 }
189
190 if (stayClose){
191 // Search in the vicinity of previous t value.
192 // This ensure unimodality for proper goldenSearch algorithm
193 qreal delta = 1/10.0;
194 qreal lbound = qBound(0.0,1.0, m_d->prev_t - delta);
195 qreal ubound = qBound(0.0,1.0, m_d->prev_t + delta);
196 min_t = goldenSearch(pt,handles(), lbound , ubound, 1e-6,1e+2);
197
198 } else {
199 min_t = KisBezierUtils::nearestPoint(refs,pt);
200 }
201
202 QPointF draw_pos = B(min_t, *handles()[0], *handles()[2], *handles()[3], *handles()[1]);
203
204 m_d->prev_t = min_t;
205 m_d->prevStrokebegin = strokeBegin;
206
207 return draw_pos;
208}
209
210QPointF SplineAssistant::adjustPosition(const QPointF& pt, const QPointF& strokeBegin, const bool /*snapToAny*/, qreal /*moveThresholdPt*/)
211{
212 return project(pt, strokeBegin);
213}
214
215void SplineAssistant::adjustLine(QPointF &point, QPointF &strokeBegin)
216{
217 point = QPointF();
218 strokeBegin = QPointF();
219}
220
221void SplineAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached, KisCanvas2* canvas, bool assistantVisible, bool previewVisible)
222{
223 gc.save();
224 gc.resetTransform();
225 QPoint mousePos;
226
227 if (canvas){
228 //simplest, cheapest way to get the mouse-position//
229 mousePos= canvas->canvasWidget()->mapFromGlobal(QCursor::pos());
230 m_canvas = canvas;
231 }
232 else {
233 //...of course, you need to have access to a canvas-widget for that.//
234 mousePos = QCursor::pos();//this'll give an offset//
235 dbgFile<<"canvas does not exist in spline, you may have passed arguments incorrectly:"<<canvas;
236 }
237
238
239 if (handles().size() > 1) {
240
241 QTransform initialTransform = converter->documentToWidgetTransform();
242
243 // first we find the path that our point create.
244 QPointF pts[4];
245 pts[0] = *handles()[0];
246 pts[1] = *handles()[1];
247 pts[2] = (handles().size() >= 3) ? (*handles()[2]) : (*handles()[0]);
248 pts[3] = (handles().size() >= 4) ? (*handles()[3]) : (handles().size() >= 3) ? (*handles()[2]) : (*handles()[1]);
249 gc.setTransform(initialTransform);
250
251 // Draw the spline
252 QPainterPath path;
253 path.moveTo(pts[0]);
254 path.cubicTo(pts[2], pts[3], pts[1]);
255
256 //then we use this path to check the bounding rectangle//
257 if (isSnappingActive() && path.boundingRect().contains(initialTransform.inverted().map(mousePos)) && previewVisible==true){
258 drawPreview(gc, path);//and we draw the preview.
259 }
260 }
261 gc.restore();
262
263 // there is some odd rectangle that is getting rendered when there is only one point, so don't start rendering the line until after 2
264 // this issue only exists with this spline assistant...none of the others
265 if (handles().size() > 2) {
266 KisPaintingAssistant::drawAssistant(gc, updateRect, converter, cached, canvas, assistantVisible, previewVisible);
267 }
268}
269
270void SplineAssistant::drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible)
271{
272 if (assistantVisible == false || handles().size() < 2 ){
273 return;
274 }
275
276 QTransform initialTransform = converter->documentToWidgetTransform();
277
278 QPointF pts[4];
279 pts[0] = *handles()[0];
280 pts[1] = *handles()[1];
281 pts[2] = (handles().size() >= 3) ? (*handles()[2]) : (*handles()[0]);
282 pts[3] = (handles().size() >= 4) ? (*handles()[3]) : (handles().size() >= 3) ? (*handles()[2]) : (*handles()[1]);
283
284 gc.setTransform(initialTransform);
285
286
287 { // Draw bezier handles control lines only if we are editing the assistant
288 gc.save();
289 QColor assistantColor = effectiveAssistantColor();
290 QPen bezierlinePen(assistantColor);
291 bezierlinePen.setStyle(Qt::DotLine);
292 bezierlinePen.setWidth(2);
293
295
296 if (!isSnappingActive()) {
297 QColor snappingColor = assistantColor;
298 snappingColor.setAlpha(snappingColor.alpha() * 0.2);
299
300 bezierlinePen.setColor(snappingColor);
301 }
302 bezierlinePen.setCosmetic(true);
303
304 gc.setPen(bezierlinePen);
305 gc.drawLine(pts[0], pts[2]);
306
307 if (isAssistantComplete()) {
308 gc.drawLine(pts[1], pts[3]);
309 }
310 gc.setPen(QColor(0, 0, 0, 125));
311 }
312 gc.restore();
313 }
314
315
316 // Draw the spline
317 QPainterPath path;
318 path.moveTo(pts[0]);
319 path.cubicTo(pts[2], pts[3], pts[1]);
320 drawPath(gc, path, isSnappingActive());
321
322
323}
324
326{
327 return B(0.5, *handles()[0], *handles()[2], *handles()[3], *handles()[1]);
328}
329
331{
332 return handles().size() >= 4; // specify 4 corners to make assistant complete
333}
334
338
342
344{
345 return "spline";
346}
347
349{
350 return i18nc("A type of drawing assistants", "Spline");
351}
352
const Params2D p
qreal u
unsigned int uint
qreal D(qreal t, const QPointF &P0, const QPointF &P1, const QPointF &P2, const QPointF &P3, const QPointF &p)
qreal goldenSearch(const QPointF &pt, const QList< KisPaintingAssistantHandleSP > handles, qreal low, qreal high, qreal tolerance, uint max_iter)
KisAbstractCanvasWidget * canvasWidget
KisPaintingAssistantsDecorationSP paintingAssistantsDecoration() const
void drawPath(QPainter &painter, const QPainterPath &path, bool drawActive=true)
void drawPreview(QPainter &painter, const QPainterPath &path)
virtual void drawAssistant(QPainter &gc, const QRectF &updateRect, const KisCoordinatesConverter *converter, bool cached, KisCanvas2 *canvas=0, bool assistantVisible=true, bool previewVisible=true)
const QList< KisPaintingAssistantHandleSP > & handles() const
QString id() const override
KisPaintingAssistant * createPaintingAssistant() const override
QString name() const override
KisPaintingAssistantSP clone(QMap< KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP > &handleMap) const override
void drawAssistant(QPainter &gc, const QRectF &updateRect, const KisCoordinatesConverter *converter, bool cached, KisCanvas2 *canvas, bool assistantVisible=true, bool previewVisible=true) override
const QScopedPointer< Private > m_d
KisCanvas2 * m_canvas
used for getting the decoration so the bezier handles aren't drawn while editing
QPointF project(const QPointF &pt, const QPointF &strokeBegin) const
void adjustLine(QPointF &point, QPointF &strokeBegin) override
void drawCache(QPainter &gc, const KisCoordinatesConverter *converter, bool assistantVisible=true) override
performance layer where the graphics can be drawn from a cache instead of generated every render upda...
QPointF getDefaultEditorPosition() const override
bool isAssistantComplete() const override
QPointF adjustPosition(const QPointF &point, const QPointF &strokeBegin, const bool snapToAny, qreal moveThresholdPt) override
#define dbgFile
Definition kis_debug.h:53
QSharedPointer< KisPaintingAssistant > KisPaintingAssistantSP
Definition kis_types.h:189
qreal nearestPoint(const QList< QPointF > controlPoints, const QPointF &point, qreal *resultDistance, QPointF *resultPoint)
QVector< GoldenSearchPoint > samples
GoldenSearchParams(qreal lbound, qreal ubound)