File indexing completed on 2025-03-09 04:28:22

0001 /*
0002     SPDX-FileCopyrightText: 2005 Casper Boemann <cbr@boemann.dk>
0003     SPDX-FileCopyrightText: 2009 Dmitry Kazakov <dimula73@gmail.com>
0004     SPDX-FileCopyrightText: 2010 Cyrille Berger <cberger@cberger.net>
0005 
0006     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0007 */
0008 
0009 #include "kis_cubic_curve.h"
0010 
0011 #include <QPointF>
0012 #include <QSharedData>
0013 #include <QStringList>
0014 
0015 template <typename T> class KisTridiagonalSystem
0016 {
0017     /*
0018      * e.g.
0019      *      |b0 c0  0   0   0| |x0| |f0|
0020      *      |a0 b1 c1   0   0| |x1| |f1|
0021      *      |0  a1 b2  c2   0|*|x2|=|f2|
0022      *      |0   0 a2  b3  c3| |x3| |f3|
0023      *      |0   0  0  a3  b4| |x4| |f4|
0024      */
0025 
0026 public:
0027     /**
0028      * @return - vector that is storing x[]
0029      */
0030     static QVector<T> calculate(QList<T> &a, QList<T> &b, QList<T> &c, QList<T> &f)
0031     {
0032         QVector<T> x;
0033         QVector<T> alpha;
0034         QVector<T> beta;
0035 
0036         int i;
0037         int size = b.size();
0038 
0039         Q_ASSERT(a.size() == size - 1 && c.size() == size - 1 && f.size() == size);
0040 
0041         x.resize(size);
0042 
0043         /**
0044          * Check for special case when
0045          * order of the matrix is equal to 1
0046          */
0047         if (size == 1) {
0048             x[0] = f[0] / b[0];
0049             return x;
0050         }
0051 
0052         /**
0053          * Common case
0054          */
0055 
0056         alpha.resize(size);
0057         beta.resize(size);
0058 
0059         alpha[1] = -c[0] / b[0];
0060         beta[1] = f[0] / b[0];
0061 
0062         for (i = 1; i < size - 1; ++i) {
0063             alpha[i + 1] = -c[i] / (a[i - 1] * alpha[i] + b[i]);
0064 
0065             beta[i + 1] = (f[i] - a[i - 1] * beta[i]) / (a[i - 1] * alpha[i] + b[i]);
0066         }
0067 
0068         x.last() = (f.last() - a.last() * beta.last()) / (b.last() + a.last() * alpha.last());
0069 
0070         for (i = size - 2; i >= 0; --i) {
0071             x[i] = alpha[i + 1] * x[i + 1] + beta[i + 1];
0072         }
0073 
0074         return x;
0075     }
0076 };
0077 
0078 template <typename T_point, typename T> class KisCubicSpline
0079 {
0080     /**
0081      *  s[i](x)=a[i] +
0082      *          b[i] * (x-x[i]) +
0083      *    1/2 * c[i] * (x-x[i])^2 +
0084      *    1/6 * d[i] * (x-x[i])^3
0085      *
0086      *  h[i]=x[i+1]-x[i]
0087      *
0088      */
0089 
0090 protected:
0091     QList<T> m_a;
0092     QVector<T> m_b;
0093     QVector<T> m_c;
0094     QVector<T> m_d;
0095 
0096     QVector<T> m_h;
0097     T m_begin;
0098     T m_end;
0099     int m_intervals{0};
0100 
0101 public:
0102     KisCubicSpline()
0103         : m_begin(0)
0104         , m_end(0)
0105 
0106     {
0107     }
0108     explicit KisCubicSpline(const QList<T_point> &a)
0109         : m_begin(0)
0110         , m_end(0)
0111     {
0112         createSpline(a);
0113     }
0114 
0115     /**
0116      * Create new spline and precalculate some values
0117      * for future
0118      *
0119      * @a - base points of the spline
0120      */
0121     void createSpline(const QList<T_point> &a)
0122     {
0123         int intervals = m_intervals = a.size() - 1;
0124         int i;
0125         m_begin = a.constFirst().x();
0126         m_end = a.last().x();
0127 
0128         m_a.clear();
0129         m_b.resize(intervals);
0130         m_c.clear();
0131         m_d.resize(intervals);
0132         m_h.resize(intervals);
0133 
0134         for (i = 0; i < intervals; ++i) {
0135             m_h[i] = a[i + 1].x() - a[i].x();
0136             m_a.append(a[i].y());
0137         }
0138         m_a.append(a.last().y());
0139 
0140         QList<T> tri_b;
0141         QList<T> tri_f;
0142         QList<T> tri_a; /* equals to @tri_c */
0143 
0144         for (i = 0; i < intervals - 1; ++i) {
0145             tri_b.append(2. * (m_h[i] + m_h[i + 1]));
0146 
0147             tri_f.append(6. * ((m_a[i + 2] - m_a[i + 1]) / m_h[i + 1] - (m_a[i + 1] - m_a[i]) / m_h[i]));
0148         }
0149         for (i = 1; i < intervals - 1; ++i) {
0150             tri_a.append(m_h[i]);
0151         }
0152 
0153         if (intervals > 1) {
0154             KisTridiagonalSystem<T> tridia;
0155             m_c = tridia.calculate(tri_a, tri_b, tri_a, tri_f);
0156         }
0157         m_c.prepend(0);
0158         m_c.append(0);
0159 
0160         for (i = 0; i < intervals; ++i) {
0161             m_d[i] = (m_c[i + 1] - m_c[i]) / m_h[i];
0162         }
0163 
0164         for (i = 0; i < intervals; ++i) {
0165             m_b[i] = -0.5 * (m_c[i] * m_h[i]) - (1 / 6.0) * (m_d[i] * m_h[i] * m_h[i]) + (m_a[i + 1] - m_a[i]) / m_h[i];
0166         }
0167     }
0168 
0169     /**
0170      * @brief Get value of precalculated spline in the point at \@x
0171      */
0172     T getValue(T x) const
0173     {
0174         T x0;
0175         int i = findRegion(x, x0);
0176         /* TODO: check for asm equivalent */
0177         return m_a[i] + m_b[i] * (x - x0) + 0.5 * m_c[i] * (x - x0) * (x - x0) + (1 / 6.0) * m_d[i] * (x - x0) * (x - x0) * (x - x0);
0178     }
0179 
0180     T begin() const { return m_begin; }
0181 
0182     T end() const { return m_end; }
0183 
0184 protected:
0185     /**
0186      * @brief findRegion - Searches for the region containing \@x
0187      * @param x - out parameter, containing beginning of the region
0188      * @return - index of the region
0189      */
0190     int findRegion(T x, T &x0) const
0191     {
0192         int i;
0193         x0 = m_begin;
0194         for (i = 0; i < m_intervals; ++i) {
0195             if (x >= x0 && x < x0 + m_h[i]) {
0196                 return i;
0197             }
0198             x0 += m_h[i];
0199         }
0200         if (x >= x0) {
0201             x0 -= m_h[m_intervals - 1];
0202             return m_intervals - 1;
0203         }
0204 
0205         qDebug("X value: %f\n", x);
0206         qDebug("m_begin: %f\n", m_begin);
0207         qDebug("m_end  : %f\n", m_end);
0208         Q_ASSERT_X(0, "findRegion", "X value is outside regions");
0209         /* **never reached** */
0210         return -1;
0211     }
0212 };
0213 
0214 static bool pointLessThan(const QPointF &a, const QPointF &b)
0215 {
0216     return a.x() < b.x();
0217 }
0218 
0219 struct KisCubicCurve::Data : public QSharedData
0220 {
0221     Data() { init(); }
0222     Data(const Data &data)
0223         : QSharedData()
0224     {
0225         init();
0226         points = data.points;
0227     }
0228     void init()
0229     {
0230         validSpline = false;
0231         validU16Transfer = false;
0232         validFTransfer = false;
0233     }
0234     ~Data() = default;
0235     mutable KisCubicSpline<QPointF, qreal> spline;
0236     QList<QPointF> points;
0237     mutable bool validSpline;
0238     mutable QVector<quint16> u16Transfer;
0239     mutable bool validU16Transfer;
0240     mutable QVector<qreal> fTransfer;
0241     mutable bool validFTransfer;
0242     void updateSpline();
0243     void keepSorted();
0244     qreal value(qreal x);
0245     void invalidate();
0246     template <typename _T_, typename _T2_> void updateTransfer(QVector<_T_> *transfer, bool &valid, _T2_ min, _T2_ max, int size);
0247 };
0248 
0249 void KisCubicCurve::Data::updateSpline()
0250 {
0251     if (validSpline) {
0252         return;
0253     }
0254     validSpline = true;
0255     spline.createSpline(points);
0256 }
0257 
0258 void KisCubicCurve::Data::invalidate()
0259 {
0260     validSpline = false;
0261     validFTransfer = false;
0262     validU16Transfer = false;
0263 }
0264 
0265 void KisCubicCurve::Data::keepSorted()
0266 {
0267     std::sort(points.begin(), points.end(), pointLessThan);
0268 }
0269 
0270 qreal KisCubicCurve::Data::value(qreal x)
0271 {
0272     updateSpline();
0273     /* Automatically extend non-existing parts of the curve
0274      * (e.g. before the first point) and cut off big y-values
0275      */
0276     x = qBound(spline.begin(), x, spline.end());
0277     qreal y = spline.getValue(x);
0278     return qBound(qreal(0.0), y, qreal(1.0));
0279 }
0280 
0281 template <typename _T_, typename _T2_> void KisCubicCurve::Data::updateTransfer(QVector<_T_> *transfer, bool &valid, _T2_ min, _T2_ max, int size)
0282 {
0283     if (!valid || transfer->size() != size) {
0284         if (transfer->size() != size) {
0285             transfer->resize(size);
0286         }
0287         qreal end = 1.0 / (size - 1);
0288         for (int i = 0; i < size; ++i) {
0289             /* Direct uncached version */
0290             _T2_ val = value(i * end) * max;
0291             val = qBound(min, val, max);
0292             (*transfer)[i] = val;
0293         }
0294         valid = true;
0295     }
0296 }
0297 
0298 struct KisCubicCurve::Private
0299 {
0300     QSharedDataPointer<Data> data;
0301 };
0302 
0303 KisCubicCurve::KisCubicCurve()
0304     : d(new Private)
0305 {
0306     d->data = new Data;
0307     QPointF p;
0308     p.rx() = 0.0;
0309     p.ry() = 0.0;
0310     d->data->points.append(p);
0311     p.rx() = 1.0;
0312     p.ry() = 1.0;
0313     d->data->points.append(p);
0314 }
0315 
0316 KisCubicCurve::KisCubicCurve(const QList<QPointF> &points)
0317     : d(new Private)
0318 {
0319     d->data = new Data;
0320     d->data->points = points;
0321     d->data->keepSorted();
0322 }
0323 
0324 KisCubicCurve::KisCubicCurve(const KisCubicCurve &curve)
0325     : d(new Private(*curve.d))
0326 {
0327 }
0328 
0329 KisCubicCurve::~KisCubicCurve()
0330 {
0331     delete d;
0332 }
0333 
0334 KisCubicCurve &KisCubicCurve::operator=(const KisCubicCurve &curve)
0335 {
0336     *d = *curve.d;
0337     return *this;
0338 }
0339 
0340 bool KisCubicCurve::operator==(const KisCubicCurve &curve) const
0341 {
0342     if (d->data == curve.d->data) {
0343         return true;
0344     }
0345     return d->data->points == curve.d->data->points;
0346 }
0347 
0348 qreal KisCubicCurve::value(qreal x) const
0349 {
0350     return d->data->value(x);
0351 }
0352 
0353 QList<QPointF> KisCubicCurve::points() const
0354 {
0355     return d->data->points;
0356 }
0357 
0358 void KisCubicCurve::setPoints(const QList<QPointF> &points)
0359 {
0360     d->data.detach();
0361     d->data->points = points;
0362     d->data->invalidate();
0363 }
0364 
0365 int KisCubicCurve::setPoint(int idx, const QPointF &point)
0366 {
0367     d->data.detach();
0368     d->data->points[idx] = point;
0369     d->data->keepSorted();
0370     d->data->invalidate();
0371     return idx;
0372 }
0373 
0374 int KisCubicCurve::addPoint(const QPointF &point)
0375 {
0376     d->data.detach();
0377     d->data->points.append(point);
0378     d->data->keepSorted();
0379     d->data->invalidate();
0380     return d->data->points.indexOf(point);
0381 }
0382 
0383 void KisCubicCurve::removePoint(int idx)
0384 {
0385     d->data.detach();
0386     d->data->points.removeAt(idx);
0387     d->data->invalidate();
0388 }
0389 
0390 const QString KisCubicCurve::toString() const
0391 {
0392     QString sCurve;
0393     for (const QPointF &pair : qAsConst(d->data->points)) {
0394         sCurve += QString::number(pair.x());
0395         sCurve += QStringLiteral("/");
0396         sCurve += QString::number(pair.y());
0397         sCurve += QStringLiteral(";");
0398     }
0399     return sCurve;
0400 }
0401 
0402 void KisCubicCurve::fromString(const QString &string)
0403 {
0404     const QStringList data = string.split(QLatin1Char(';'));
0405 
0406     QList<QPointF> points;
0407     for (const QString &pair : data) {
0408         if (pair.indexOf('/') > -1) {
0409             QPointF p;
0410             p.rx() = pair.section(QLatin1Char('/'), 0, 0).toDouble();
0411             p.ry() = pair.section(QLatin1Char('/'), 1, 1).toDouble();
0412             points.append(p);
0413         }
0414     }
0415     setPoints(points);
0416 }
0417 
0418 int KisCubicCurve::count() const
0419 {
0420     return d->data->points.size();
0421 }
0422 
0423 QPointF KisCubicCurve::getPoint(int ix, int normalisedWidth, int normalisedHeight, bool invertHeight)
0424 {
0425     QPointF p = d->data->points.at(ix);
0426     p.rx() *= normalisedWidth;
0427     p.ry() *= normalisedHeight;
0428     if (invertHeight) {
0429         p.ry() = normalisedHeight - p.y();
0430     }
0431     return p;
0432 }