File indexing completed on 2024-06-23 04:48:43
0001 /* 0002 SPDX-FileCopyrightText: 2010 Till Theato <root@ttill.de> 0003 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0004 */ 0005 0006 #include "cubicbezierspline.h" 0007 #include <cmath> 0008 0009 /** @brief For sorting a Bezier spline. Whether a is before b. */ 0010 static bool pointLessThan(const BPoint &a, const BPoint &b) 0011 { 0012 return a.p.x() < b.p.x(); 0013 } 0014 0015 CubicBezierSpline::CubicBezierSpline() 0016 { 0017 m_points.append(BPoint(QPointF(0, 0), QPointF(0, 0), QPointF(.1, .1))); 0018 m_points.append(BPoint(QPointF(.9, .9), QPointF(1, 1), QPointF(1, 1))); 0019 } 0020 0021 CubicBezierSpline::CubicBezierSpline(const CubicBezierSpline &spline) 0022 : m_points(spline.m_points) 0023 { 0024 } 0025 0026 CubicBezierSpline &CubicBezierSpline::operator=(const CubicBezierSpline &spline) = default; 0027 0028 void CubicBezierSpline::fromString(const QString &spline) 0029 { 0030 m_points.clear(); 0031 const QStringList bpoints = spline.split(QLatin1Char('|')); 0032 for (const QString &bpoint : bpoints) { 0033 const QStringList points = bpoint.split(QLatin1Char('#')); 0034 QVector<QPointF> values; 0035 for (const QString &point : points) { 0036 const QStringList xy = point.split(QLatin1Char(';')); 0037 if (xy.count() == 2) { 0038 values.append(QPointF(xy.at(0).toDouble(), xy.at(1).toDouble())); 0039 } 0040 } 0041 if (values.count() == 3) { 0042 m_points.append(BPoint(values.at(0), values.at(1), values.at(2))); 0043 } 0044 } 0045 0046 keepSorted(); 0047 validatePoints(); 0048 } 0049 0050 QString CubicBezierSpline::toString() const 0051 { 0052 QStringList spline; 0053 for (const BPoint &p : m_points) { 0054 spline << QStringLiteral("%1;%2#%3;%4#%5;%6") 0055 .arg(QString::number(p.h1.x(), 'f'), QString::number(p.h1.y(), 'f'), QString::number(p.p.x(), 'f'), QString::number(p.p.y(), 'f'), 0056 QString::number(p.h2.x(), 'f'), QString::number(p.h2.y(), 'f')); 0057 } 0058 return spline.join(QLatin1Char('|')); 0059 } 0060 0061 int CubicBezierSpline::setPoint(int ix, const BPoint &point) 0062 { 0063 m_points[ix] = point; 0064 keepSorted(); 0065 validatePoints(); 0066 return indexOf(point); // in case it changed 0067 } 0068 0069 QList<BPoint> CubicBezierSpline::points() const 0070 { 0071 return m_points; 0072 } 0073 0074 void CubicBezierSpline::removePoint(int ix) 0075 { 0076 m_points.removeAt(ix); 0077 } 0078 0079 int CubicBezierSpline::addPoint(const BPoint &point) 0080 { 0081 m_points.append(point); 0082 keepSorted(); 0083 validatePoints(); 0084 return indexOf(point); 0085 } 0086 0087 int CubicBezierSpline::addPoint(const QPointF &point) 0088 { 0089 // Check if point is in range 0090 if (point.x() < m_points[0].p.x() || point.x() > m_points.back().p.x()) { 0091 return -1; 0092 } 0093 // first we find by dichotomy the previous and next points on the curve 0094 int prev = 0, next = m_points.size() - 1; 0095 while (prev < next - 1) { 0096 int mid = (prev + next) / 2; 0097 if (point.x() < m_points[mid].p.x()) { 0098 next = mid; 0099 } else { 0100 prev = mid; 0101 } 0102 } 0103 0104 // compute vector between adjacent points 0105 QPointF vec = m_points[next].p - m_points[prev].p; 0106 // normalize 0107 vec /= 10. * sqrt(vec.x() * vec.x() + vec.y() * vec.y()); 0108 // add resulting point 0109 return addPoint(BPoint(point - vec, point, point + vec)); 0110 } 0111 0112 BPoint CubicBezierSpline::getPoint(int ix, int normalisedWidth, int normalisedHeight, bool invertHeight) 0113 { 0114 BPoint p = m_points.at(ix); 0115 for (int i = 0; i < 3; ++i) { 0116 p[i].rx() *= normalisedWidth; 0117 p[i].ry() *= normalisedHeight; 0118 if (invertHeight) { 0119 p[i].ry() = normalisedHeight - p[i].y(); 0120 } 0121 } 0122 return p; 0123 } 0124 0125 void CubicBezierSpline::validatePoints() 0126 { 0127 BPoint p1, p2; 0128 for (int i = 0; i < m_points.count() - 1; ++i) { 0129 p1 = m_points.at(i); 0130 p2 = m_points.at(i + 1); 0131 p1.h2.setX(qBound(p1.p.x(), p1.h2.x(), p2.p.x())); 0132 p2.h1.setX(qBound(p1.p.x(), p2.h1.x(), p2.p.x())); 0133 m_points[i] = p1; 0134 m_points[i + 1] = p2; 0135 } 0136 } 0137 0138 void CubicBezierSpline::keepSorted() 0139 { 0140 std::sort(m_points.begin(), m_points.end(), pointLessThan); 0141 } 0142 0143 int CubicBezierSpline::indexOf(const BPoint &p) 0144 { 0145 if (m_points.indexOf(p) == -1) { 0146 // point changed during validation process 0147 for (int i = 0; i < m_points.count(); ++i) { 0148 // this condition should be sufficient, too 0149 if (m_points.at(i).p == p.p) { 0150 return i; 0151 } 0152 } 0153 } else { 0154 return m_points.indexOf(p); 0155 } 0156 return -1; 0157 } 0158 0159 int CubicBezierSpline::count() const 0160 { 0161 return m_points.size(); 0162 } 0163 0164 QList<BPoint> CubicBezierSpline::getPoints() const 0165 { 0166 return m_points; 0167 } 0168 0169 std::pair<int, BPoint::PointType> CubicBezierSpline::closestPoint(const QPointF &p) const 0170 { 0171 double nearestDistanceSquared = 1e100; 0172 BPoint::PointType selectedPoint = BPoint::PointType::P; 0173 int nearestIndex = -1; 0174 int i = 0; 0175 0176 // find out distance using the Pythagorean theorem 0177 for (const auto &point : m_points) { 0178 for (int j = 0; j < 3; ++j) { 0179 double dx = point[j].x() - p.x(); 0180 double dy = point[j].y() - p.y(); 0181 double distanceSquared = dx * dx + dy * dy; 0182 if (distanceSquared < nearestDistanceSquared) { 0183 nearestIndex = i; 0184 nearestDistanceSquared = distanceSquared; 0185 selectedPoint = BPoint::PointType(j); 0186 } 0187 } 0188 ++i; 0189 } 0190 return {nearestIndex, selectedPoint}; 0191 }