File indexing completed on 2024-06-16 04:15:52
0001 /* 0002 * SPDX-FileCopyrightText: 2008 Cyrille Berger <cberger@cberger.net> 0003 * SPDX-FileCopyrightText: 2010 Geoffry Song <goffrie@gmail.com> 0004 * SPDX-FileCopyrightText: 2017 Scott Petrovic <scottpetrovic@gmail.com> 0005 * 0006 * SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 0009 #include "SplineAssistant.h" 0010 0011 #include <klocalizedstring.h> 0012 0013 #include <QPainter> 0014 #include <QPainterPath> 0015 #include <QLinearGradient> 0016 #include <QTransform> 0017 0018 #include <kis_canvas2.h> 0019 #include <kis_coordinates_converter.h> 0020 #include "kis_debug.h" 0021 #include "KisBezierUtils.h" 0022 0023 #include <math.h> 0024 #include <limits> 0025 #include <algorithm> 0026 0027 struct GoldenSearchParams 0028 { 0029 GoldenSearchParams(qreal lbound 0030 ,qreal ubound) 0031 :lbound(lbound) 0032 ,ubound(ubound) 0033 { 0034 samples = QVector<GoldenSearchPoint>(4); 0035 } 0036 0037 struct GoldenSearchPoint { 0038 GoldenSearchPoint(qreal xval) 0039 : x(xval) 0040 { 0041 } 0042 0043 GoldenSearchPoint(){} 0044 0045 void inv_norm(qreal l, qreal u) 0046 { 0047 this->xnorm = this->x * (u - l) + l; 0048 } 0049 0050 qreal fval; 0051 qreal xnorm; 0052 qreal x; 0053 }; 0054 0055 0056 qreal lbound; 0057 qreal ubound; 0058 QVector<GoldenSearchPoint> samples; 0059 }; 0060 0061 0062 struct SplineAssistant::Private { 0063 Private(); 0064 0065 QPointF prevStrokebegin; 0066 qreal prev_t {0}; 0067 }; 0068 0069 SplineAssistant::Private::Private() 0070 : prevStrokebegin(0,0) 0071 { 0072 } 0073 0074 SplineAssistant::SplineAssistant() 0075 : KisPaintingAssistant("spline", i18n("Spline assistant")) 0076 , m_d(new Private) 0077 { 0078 } 0079 0080 SplineAssistant::SplineAssistant(const SplineAssistant &rhs, QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap) 0081 : KisPaintingAssistant(rhs, handleMap) 0082 , m_canvas(rhs.m_canvas) 0083 , m_d(new Private) 0084 { 0085 } 0086 0087 KisPaintingAssistantSP SplineAssistant::clone(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap) const 0088 { 0089 return KisPaintingAssistantSP(new SplineAssistant(*this, handleMap)); 0090 } 0091 0092 // 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) 0093 inline QPointF B(qreal t, const QPointF& P0, const QPointF& P1, const QPointF& P2, const QPointF& P3) 0094 { 0095 const qreal tp = 1 - t; 0096 const qreal tp2 = tp * tp; 0097 const qreal t2 = t * t; 0098 0099 return ( tp2 * tp) * P0 + 0100 (3 * tp2 * t ) * P1 + 0101 (3 * tp * t2) * P2 + 0102 ( t * t2) * P3; 0103 } 0104 // squared distance from a point on the spline to given point: we want to minimize this 0105 inline qreal D(qreal t, const QPointF& P0, const QPointF& P1, const QPointF& P2, const QPointF& P3, const QPointF& p) 0106 { 0107 const qreal 0108 tp = 1 - t, 0109 tp2 = tp * tp, 0110 t2 = t * t, 0111 a = tp2 * tp, 0112 b = 3 * tp2 * t, 0113 c = 3 * tp * t2, 0114 d = t * t2, 0115 x_dist = a*P0.x() + b*P1.x() + c*P2.x() + d*P3.x() - p.x(), 0116 y_dist = a*P0.y() + b*P1.y() + c*P2.y() + d*P3.y() - p.y(); 0117 0118 return x_dist * x_dist + y_dist * y_dist; 0119 } 0120 0121 0122 inline qreal goldenSearch(const QPointF& pt 0123 , const QList<KisPaintingAssistantHandleSP> handles 0124 , qreal low 0125 , qreal high 0126 , qreal tolerance 0127 , uint max_iter) 0128 { 0129 GoldenSearchParams ovalues = GoldenSearchParams(low,high); 0130 QVector<GoldenSearchParams::GoldenSearchPoint> p = ovalues.samples; 0131 qreal u = ovalues.ubound; 0132 qreal l = ovalues.lbound; 0133 0134 const qreal ratio = 1 - 2/(1 + sqrt(5)); 0135 p[0].x = 0; 0136 p[1].x = ratio; 0137 p[2].x = 1 - p[1].x; 0138 p[3].x = 1; 0139 0140 p[1].inv_norm(l,u); 0141 p[2].inv_norm(l,u); 0142 0143 p[1].fval = D(p[1].xnorm, *handles[0], *handles[2], *handles[3], *handles[1], pt); 0144 p[2].fval = D(p[2].xnorm, *handles[0], *handles[2], *handles[3], *handles[1], pt); 0145 0146 GoldenSearchParams::GoldenSearchPoint xtemp = GoldenSearchParams::GoldenSearchPoint(0); 0147 0148 uint i = 0; // used to force early exit 0149 while ( qAbs(p[2].xnorm - p[1].xnorm) > tolerance && i < max_iter) { 0150 0151 if (p[1].fval < p[2].fval) { 0152 xtemp = p[1]; 0153 p[3] = p[2]; 0154 p[1].x = p[0].x + (p[2].x - p[1].x); 0155 p[2] = xtemp; 0156 0157 p[1].inv_norm(l,u); 0158 p[1].fval = D(p[1].xnorm, *handles[0], *handles[2], *handles[3], *handles[1], pt); 0159 0160 } else { 0161 xtemp = p[2]; 0162 p[0] = p[1]; 0163 p[2].x = p[1].x + (p[3].x - p[2].x); 0164 p[1] = xtemp; 0165 0166 p[2].inv_norm(l,u); 0167 p[2].fval = D(p[2].xnorm, *handles[0], *handles[2], *handles[3], *handles[1], pt); 0168 0169 } 0170 i++; 0171 } 0172 return (p[2].xnorm + p[1].xnorm) / 2; 0173 } 0174 0175 0176 QPointF SplineAssistant::project(const QPointF& pt, const QPointF& strokeBegin) const 0177 { 0178 Q_ASSERT(isAssistantComplete()); 0179 0180 // minimize d(t), but keep t in the same neighbourhood as before (unless starting a new stroke) 0181 bool stayClose = (m_d->prevStrokebegin == strokeBegin)? true : false; 0182 qreal min_t; 0183 0184 QList<QPointF> refs; 0185 QVector<int> hindex = {0,2,3,1}; // order handles as expected by KisBezierUtils 0186 Q_FOREACH(int i, hindex) { 0187 refs.append(*handles()[i]); 0188 } 0189 0190 if (stayClose){ 0191 // Search in the vicinity of previous t value. 0192 // This ensure unimodality for proper goldenSearch algorithm 0193 qreal delta = 1/10.0; 0194 qreal lbound = qBound(0.0,1.0, m_d->prev_t - delta); 0195 qreal ubound = qBound(0.0,1.0, m_d->prev_t + delta); 0196 min_t = goldenSearch(pt,handles(), lbound , ubound, 1e-6,1e+2); 0197 0198 } else { 0199 min_t = KisBezierUtils::nearestPoint(refs,pt); 0200 } 0201 0202 QPointF draw_pos = B(min_t, *handles()[0], *handles()[2], *handles()[3], *handles()[1]); 0203 0204 m_d->prev_t = min_t; 0205 m_d->prevStrokebegin = strokeBegin; 0206 0207 return draw_pos; 0208 } 0209 0210 QPointF SplineAssistant::adjustPosition(const QPointF& pt, const QPointF& strokeBegin, const bool /*snapToAny*/, qreal /*moveThresholdPt*/) 0211 { 0212 return project(pt, strokeBegin); 0213 } 0214 0215 void SplineAssistant::adjustLine(QPointF &point, QPointF &strokeBegin) 0216 { 0217 point = QPointF(); 0218 strokeBegin = QPointF(); 0219 } 0220 0221 void SplineAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached, KisCanvas2* canvas, bool assistantVisible, bool previewVisible) 0222 { 0223 gc.save(); 0224 gc.resetTransform(); 0225 QPoint mousePos; 0226 0227 if (canvas){ 0228 //simplest, cheapest way to get the mouse-position// 0229 mousePos= canvas->canvasWidget()->mapFromGlobal(QCursor::pos()); 0230 m_canvas = canvas; 0231 } 0232 else { 0233 //...of course, you need to have access to a canvas-widget for that.// 0234 mousePos = QCursor::pos();//this'll give an offset// 0235 dbgFile<<"canvas does not exist in spline, you may have passed arguments incorrectly:"<<canvas; 0236 } 0237 0238 0239 if (handles().size() > 1) { 0240 0241 QTransform initialTransform = converter->documentToWidgetTransform(); 0242 0243 // first we find the path that our point create. 0244 QPointF pts[4]; 0245 pts[0] = *handles()[0]; 0246 pts[1] = *handles()[1]; 0247 pts[2] = (handles().size() >= 3) ? (*handles()[2]) : (*handles()[0]); 0248 pts[3] = (handles().size() >= 4) ? (*handles()[3]) : (handles().size() >= 3) ? (*handles()[2]) : (*handles()[1]); 0249 gc.setTransform(initialTransform); 0250 0251 // Draw the spline 0252 QPainterPath path; 0253 path.moveTo(pts[0]); 0254 path.cubicTo(pts[2], pts[3], pts[1]); 0255 0256 //then we use this path to check the bounding rectangle// 0257 if (isSnappingActive() && path.boundingRect().contains(initialTransform.inverted().map(mousePos)) && previewVisible==true){ 0258 drawPreview(gc, path);//and we draw the preview. 0259 } 0260 } 0261 gc.restore(); 0262 0263 // 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 0264 // this issue only exists with this spline assistant...none of the others 0265 if (handles().size() > 2) { 0266 KisPaintingAssistant::drawAssistant(gc, updateRect, converter, cached, canvas, assistantVisible, previewVisible); 0267 } 0268 } 0269 0270 void SplineAssistant::drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible) 0271 { 0272 if (assistantVisible == false || handles().size() < 2 ){ 0273 return; 0274 } 0275 0276 QTransform initialTransform = converter->documentToWidgetTransform(); 0277 0278 QPointF pts[4]; 0279 pts[0] = *handles()[0]; 0280 pts[1] = *handles()[1]; 0281 pts[2] = (handles().size() >= 3) ? (*handles()[2]) : (*handles()[0]); 0282 pts[3] = (handles().size() >= 4) ? (*handles()[3]) : (handles().size() >= 3) ? (*handles()[2]) : (*handles()[1]); 0283 0284 gc.setTransform(initialTransform); 0285 0286 0287 { // Draw bezier handles control lines only if we are editing the assistant 0288 gc.save(); 0289 QColor assistantColor = effectiveAssistantColor(); 0290 QPen bezierlinePen(assistantColor); 0291 bezierlinePen.setStyle(Qt::DotLine); 0292 bezierlinePen.setWidth(2); 0293 0294 if (m_canvas->paintingAssistantsDecoration()->isEditingAssistants()) { 0295 0296 if (!isSnappingActive()) { 0297 QColor snappingColor = assistantColor; 0298 snappingColor.setAlpha(snappingColor.alpha() * 0.2); 0299 0300 bezierlinePen.setColor(snappingColor); 0301 } 0302 bezierlinePen.setCosmetic(true); 0303 0304 gc.setPen(bezierlinePen); 0305 gc.drawLine(pts[0], pts[2]); 0306 0307 if (isAssistantComplete()) { 0308 gc.drawLine(pts[1], pts[3]); 0309 } 0310 gc.setPen(QColor(0, 0, 0, 125)); 0311 } 0312 gc.restore(); 0313 } 0314 0315 0316 // Draw the spline 0317 QPainterPath path; 0318 path.moveTo(pts[0]); 0319 path.cubicTo(pts[2], pts[3], pts[1]); 0320 drawPath(gc, path, isSnappingActive()); 0321 0322 0323 } 0324 0325 QPointF SplineAssistant::getDefaultEditorPosition() const 0326 { 0327 return B(0.5, *handles()[0], *handles()[2], *handles()[3], *handles()[1]); 0328 } 0329 0330 bool SplineAssistant::isAssistantComplete() const 0331 { 0332 return handles().size() >= 4; // specify 4 corners to make assistant complete 0333 } 0334 0335 SplineAssistantFactory::SplineAssistantFactory() 0336 { 0337 } 0338 0339 SplineAssistantFactory::~SplineAssistantFactory() 0340 { 0341 } 0342 0343 QString SplineAssistantFactory::id() const 0344 { 0345 return "spline"; 0346 } 0347 0348 QString SplineAssistantFactory::name() const 0349 { 0350 return i18nc("A type of drawing assistants", "Spline"); 0351 } 0352 0353 KisPaintingAssistant* SplineAssistantFactory::createPaintingAssistant() const 0354 { 0355 return new SplineAssistant; 0356 }