1 /*
2  *  Copyright (c) 2005 C. Boemann <cbo@boemann.dk>
3  *  Copyright (c) 2009 Dmitry Kazakov <dimula73@gmail.com>
4  *  Copyright (c) 2010 Cyrille Berger <cberger@cberger.net>
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  */
20 
21 #include "kis_cubic_curve.h"
22 
23 #include <QPointF>
24 #include <QList>
25 #include <QSharedData>
26 #include <QStringList>
27 
28 #include <algorithm>
29 
30 template <typename T>
31 class KisTridiagonalSystem
32 {
33     /*
34      * e.g.
35      *      |b0 c0  0   0   0| |x0| |f0|
36      *      |a0 b1 c1   0   0| |x1| |f1|
37      *      |0  a1 b2  c2   0|*|x2|=|f2|
38      *      |0   0 a2  b3  c3| |x3| |f3|
39      *      |0   0  0  a3  b4| |x4| |f4|
40      */
41 
42 public:
43 
44     /**
45      * @return - vector that is storing x[]
46      */
47     static
calculate(QList<T> & a,QList<T> & b,QList<T> & c,QList<T> & f)48     QVector<T> calculate(QList<T> &a,
49                          QList<T> &b,
50                          QList<T> &c,
51                          QList<T> &f) {
52         QVector<T> x;
53         QVector<T> alpha;
54         QVector<T> beta;
55 
56         int i;
57         int size = b.size();
58 
59         Q_ASSERT(a.size() == size - 1 &&
60                  c.size() == size - 1 &&
61                  f.size() == size);
62 
63         x.resize(size);
64 
65         /**
66          * Check for special case when
67          * order of the matrix is equal to 1
68          */
69         if (size == 1) {
70             x[0] = f[0] / b[0];
71             return x;
72         }
73 
74         /**
75          * Common case
76          */
77 
78         alpha.resize(size);
79         beta.resize(size);
80 
81 
82         alpha[1] = -c[0] / b[0];
83         beta[1] =  f[0] / b[0];
84 
85         for (i = 1; i < size - 1; i++) {
86             alpha[i+1] = -c[i] /
87                          (a[i-1] * alpha[i] + b[i]);
88 
89             beta[i+1] = (f[i] - a[i-1] * beta[i])
90                         /
91                         (a[i-1] * alpha[i] + b[i]);
92         }
93 
94         x.last() = (f.last() - a.last() * beta.last())
95                    /
96                    (b.last() + a.last() * alpha.last());
97 
98         for (i = size - 2; i >= 0; i--)
99             x[i] = alpha[i+1] * x[i+1] + beta[i+1];
100 
101         return x;
102     }
103 };
104 
105 template <typename T_point, typename T>
106 class KisCubicSpline
107 {
108     /**
109      *  s[i](x)=a[i] +
110      *          b[i] * (x-x[i]) +
111      *    1/2 * c[i] * (x-x[i])^2 +
112      *    1/6 * d[i] * (x-x[i])^3
113      *
114      *  h[i]=x[i+1]-x[i]
115      *
116      */
117 
118 protected:
119     QList<T> m_a;
120     QVector<T> m_b;
121     QVector<T> m_c;
122     QVector<T> m_d;
123 
124     QVector<T> m_h;
125     T m_begin;
126     T m_end;
127     int m_intervals;
128 
129 public:
KisCubicSpline()130     KisCubicSpline() {}
KisCubicSpline(const QList<T_point> & a)131     KisCubicSpline(const QList<T_point> &a) {
132         createSpline(a);
133     }
134 
135     /**
136      * Create new spline and precalculate some values
137      * for future
138      *
139      * @a - base points of the spline
140      */
createSpline(const QList<T_point> & a)141     void createSpline(const QList<T_point> &a) {
142         int intervals = m_intervals = a.size() - 1;
143         int i;
144         m_begin = a.first().x();
145         m_end = a.last().x();
146 
147         m_a.clear();
148         m_b.resize(intervals);
149         m_c.clear();
150         m_d.resize(intervals);
151         m_h.resize(intervals);
152 
153         for (i = 0; i < intervals; i++) {
154             m_h[i] = a[i+1].x() - a[i].x();
155             m_a.append(a[i].y());
156         }
157         m_a.append(a.last().y());
158 
159 
160         QList<T> tri_b;
161         QList<T> tri_f;
162         QList<T> tri_a; /* equals to @tri_c */
163 
164         for (i = 0; i < intervals - 1; i++) {
165             tri_b.append(2.*(m_h[i] + m_h[i+1]));
166 
167             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]));
168         }
169         for (i = 1; i < intervals - 1; i++)
170             tri_a.append(m_h[i]);
171 
172         if (intervals > 1) {
173             m_c = KisTridiagonalSystem<T>::calculate(tri_a, tri_b, tri_a, tri_f);
174         }
175         m_c.prepend(0);
176         m_c.append(0);
177 
178         for (i = 0; i < intervals; i++)
179             m_d[i] = (m_c[i+1] - m_c[i]) / m_h[i];
180 
181         for (i = 0; i < intervals; i++)
182             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];
183     }
184 
185     /**
186      * Get value of precalculated spline in the point @x
187      */
getValue(T x) const188     T getValue(T x) const {
189         T x0;
190         int i = findRegion(x, x0);
191         /* TODO: check for asm equivalent */
192         return m_a[i] +
193                m_b[i] *(x - x0) +
194                0.5 * m_c[i] *(x - x0) *(x - x0) +
195                (1 / 6.0)* m_d[i] *(x - x0) *(x - x0) *(x - x0);
196     }
197 
begin() const198     T begin() const {
199         return m_begin;
200     }
201 
end() const202     T end() const {
203         return m_end;
204     }
205 
206 protected:
207 
208     /**
209      * findRegion - Searches for the region containing @x
210      * @x0 - out parameter, containing beginning of the region
211      * @return - index of the region
212      */
findRegion(T x,T & x0) const213     int findRegion(T x, T &x0) const {
214         int i;
215         x0 = m_begin;
216         for (i = 0; i < m_intervals; i++) {
217             if (x >= x0 && x < x0 + m_h[i])
218                 return i;
219             x0 += m_h[i];
220         }
221         if (x >= x0) {
222             x0 -= m_h[m_intervals-1];
223             return m_intervals - 1;
224         }
225 
226         qDebug("X value: %f\n", x);
227         qDebug("m_begin: %f\n", m_begin);
228         qDebug("m_end  : %f\n", m_end);
229         Q_ASSERT_X(0, "findRegion", "X value is outside regions");
230         /* **never reached** */
231         return -1;
232     }
233 };
234 
pointLessThan(const QPointF & a,const QPointF & b)235 static bool pointLessThan(const QPointF &a, const QPointF &b)
236 {
237     return a.x() < b.x();
238 }
239 
240 struct KisCubicCurve::Data : public QSharedData {
DataKisCubicCurve::Data241     Data() {
242         init();
243     }
DataKisCubicCurve::Data244     Data(const Data& data) : QSharedData() {
245         init();
246         points = data.points;
247     }
initKisCubicCurve::Data248     void init() {
249         validSpline = false;
250         validU16Transfer = false;
251         validFTransfer = false;
252     }
~DataKisCubicCurve::Data253     ~Data() {
254     }
255 
256     mutable KisCubicSpline<QPointF, qreal> spline;
257     QList<QPointF> points;
258     mutable bool validSpline;
259     mutable QVector<quint16> u16Transfer;
260     mutable bool validU16Transfer;
261     mutable QVector<qreal> fTransfer;
262     mutable bool validFTransfer;
263     void updateSpline();
264     void keepSorted();
265     qreal value(qreal x);
266     void invalidate();
267     template<typename _T_, typename _T2_>
268     void updateTransfer(QVector<_T_>* transfer, bool& valid, _T2_ min, _T2_ max, int size);
269 };
270 
updateSpline()271 void KisCubicCurve::Data::updateSpline()
272 {
273     if (validSpline) return;
274     validSpline = true;
275     spline.createSpline(points);
276 }
277 
invalidate()278 void KisCubicCurve::Data::invalidate()
279 {
280     validSpline = false;
281     validFTransfer = false;
282     validU16Transfer = false;
283 }
284 
keepSorted()285 void KisCubicCurve::Data::keepSorted()
286 {
287 	std::sort(points.begin(), points.end(), pointLessThan);
288 }
289 
value(qreal x)290 qreal KisCubicCurve::Data::value(qreal x)
291 {
292     updateSpline();
293     /* Automatically extend non-existing parts of the curve
294      * (e.g. before the first point) and cut off big y-values
295      */
296     x = qBound(spline.begin(), x, spline.end());
297     qreal y = spline.getValue(x);
298     return qBound(qreal(0.0), y, qreal(1.0));
299 }
300 
301 template<typename _T_, typename _T2_>
updateTransfer(QVector<_T_> * transfer,bool & valid,_T2_ min,_T2_ max,int size)302 void KisCubicCurve::Data::updateTransfer(QVector<_T_>* transfer, bool& valid, _T2_ min, _T2_ max, int size)
303 {
304     if (!valid || transfer->size() != size) {
305         if (transfer->size() != size) {
306             transfer->resize(size);
307         }
308         qreal end = 1.0 / (size - 1);
309         for (int i = 0; i < size; ++i) {
310             /* Direct uncached version */
311             _T2_ val = value(i * end ) * max;
312             val = qBound(min, val, max);
313             (*transfer)[i] = val;
314         }
315         valid = true;
316     }
317 }
318 
319 struct KisCubicCurve::Private {
320     QSharedDataPointer<Data> data;
321 };
322 
KisCubicCurve()323 KisCubicCurve::KisCubicCurve() : d(new Private)
324 {
325     d->data = new Data;
326     QPointF p;
327     p.rx() = 0.0; p.ry() = 0.0;
328     d->data->points.append(p);
329     p.rx() = 1.0; p.ry() = 1.0;
330     d->data->points.append(p);
331 }
332 
KisCubicCurve(const QList<QPointF> & points)333 KisCubicCurve::KisCubicCurve(const QList<QPointF>& points) : d(new Private)
334 {
335     d->data = new Data;
336     d->data->points = points;
337     d->data->keepSorted();
338 }
339 
KisCubicCurve(const KisCubicCurve & curve)340 KisCubicCurve::KisCubicCurve(const KisCubicCurve& curve) : d(new Private(*curve.d))
341 {
342 }
343 
~KisCubicCurve()344 KisCubicCurve::~KisCubicCurve()
345 {
346     delete d;
347 }
348 
operator =(const KisCubicCurve & curve)349 KisCubicCurve& KisCubicCurve::operator=(const KisCubicCurve & curve)
350 {
351     if (&curve != this) {
352         *d = *curve.d;
353     }
354     return *this;
355 }
356 
operator ==(const KisCubicCurve & curve) const357 bool KisCubicCurve::operator==(const KisCubicCurve& curve) const
358 {
359     if (d->data == curve.d->data) return true;
360     return d->data->points == curve.d->data->points;
361 }
362 
value(qreal x) const363 qreal KisCubicCurve::value(qreal x) const
364 {
365     qreal value = d->data->value(x);
366     return value;
367 }
368 
points() const369 QList<QPointF> KisCubicCurve::points() const
370 {
371     return d->data->points;
372 }
373 
setPoints(const QList<QPointF> & points)374 void KisCubicCurve::setPoints(const QList<QPointF>& points)
375 {
376     d->data.detach();
377     d->data->points = points;
378     d->data->invalidate();
379 }
380 
setPoint(int idx,const QPointF & point)381 void KisCubicCurve::setPoint(int idx, const QPointF& point)
382 {
383     d->data.detach();
384     d->data->points[idx] = point;
385     d->data->keepSorted();
386     d->data->invalidate();
387 }
388 
addPoint(const QPointF & point)389 int KisCubicCurve::addPoint(const QPointF& point)
390 {
391     d->data.detach();
392     d->data->points.append(point);
393     d->data->keepSorted();
394     d->data->invalidate();
395 
396     return d->data->points.indexOf(point);
397 }
398 
removePoint(int idx)399 void KisCubicCurve::removePoint(int idx)
400 {
401     d->data.detach();
402     d->data->points.removeAt(idx);
403     d->data->invalidate();
404 }
405 
toString() const406 QString KisCubicCurve::toString() const
407 {
408     QString sCurve;
409 
410     if(d->data->points.count() < 1)
411         return sCurve;
412 
413     for(const QPointF & pair : d->data->points) {
414         sCurve += QString::number(pair.x());
415         sCurve += ',';
416         sCurve += QString::number(pair.y());
417         sCurve += ';';
418     }
419 
420     return sCurve;
421 }
422 
fromString(const QString & string)423 void KisCubicCurve::fromString(const QString& string)
424 {
425     QStringList data = string.split(';');
426 
427     QList<QPointF> points;
428 
429 	for(const QString & pair : data) {
430         if (pair.indexOf(',') > -1) {
431             QPointF p;
432             p.rx() = qBound(0.0, pair.section(',', 0, 0).toDouble(), 1.0);
433             p.ry() = qBound(0.0, pair.section(',', 1, 1).toDouble(), 1.0);
434             points.append(p);
435         }
436     }
437 
438 	if(points.size() < 2)
439 		points = { {0.0, 0.0}, {1.0, 1.0} };
440 
441     setPoints(points);
442 }
443 
uint16Transfer(int size) const444 const QVector<quint16> KisCubicCurve::uint16Transfer(int size) const
445 {
446     d->data->updateTransfer<quint16, int>(&d->data->u16Transfer, d->data->validU16Transfer, 0x0, 0xFFFF, size);
447     return d->data->u16Transfer;
448 }
449 
floatTransfer(int size) const450 const QVector<qreal> KisCubicCurve::floatTransfer(int size) const
451 {
452     d->data->updateTransfer<qreal, qreal>(&d->data->fTransfer, d->data->validFTransfer, 0.0, 1.0, size);
453     return d->data->fTransfer;
454 }
455