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