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