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 }