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 }