Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_cubic_curve.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2005 C. Boemann <cbo@boemann.dk>
3 * SPDX-FileCopyrightText: 2009 Dmitry Kazakov <dimula73@gmail.com>
4 * SPDX-FileCopyrightText: 2010 Cyrille Berger <cberger@cberger.net>
5 *
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
9#include "kis_cubic_curve.h"
10
11#include <QPointF>
12#include <QList>
13#include <QSharedData>
14#include <QStringList>
15#include "kis_dom_utils.h"
16#include "kis_algebra_2d.h"
17
18KisCubicCurvePoint::KisCubicCurvePoint(const QPointF &position, bool setAsCorner)
19 : m_position(position), m_isCorner(setAsCorner)
20{}
21
22KisCubicCurvePoint::KisCubicCurvePoint(qreal x, qreal y, bool setAsCorner)
23 : m_position(x, y), m_isCorner(setAsCorner)
24{}
25
27{
28 return m_position == other.m_position && m_isCorner == other.m_isCorner;
29}
30
32{
33 return m_position.x();
34}
35
37{
38 return m_position.y();
39}
40
41const QPointF& KisCubicCurvePoint::position() const
42{
43 return m_position;
44}
45
47{
48 return m_isCorner;
49}
50
52{
53 m_position.setX(newX);
54}
55
57{
58 m_position.setY(newY);
59}
60
61void KisCubicCurvePoint::setPosition(const QPointF &newPosition)
62{
63 m_position = newPosition;
64}
65
66void KisCubicCurvePoint::setAsCorner(bool newIsSetAsCorner)
67{
68 m_isCorner = newIsSetAsCorner;
69}
70
72{
73 return a.x() < b.x();
74}
75
76struct Q_DECL_HIDDEN KisCubicCurve::Data : public QSharedData {
77 Data() {
78 }
79 Data(const Data& data) : QSharedData() {
80 points = data.points;
81 name = data.name;
82 }
84 }
85
86 mutable QString name;
89 mutable bool validSpline {false};
91 mutable bool validU8Transfer {false};
93 mutable bool validU16Transfer {false};
95 mutable bool validFTransfer {false};
96
98 void keepSorted();
99 qreal value(qreal x);
101
102 template<typename _T_, typename _T2_>
103 void updateTransfer(QVector<_T_>* transfer, bool& valid, _T2_ min, _T2_ max, int size);
104};
105
106void KisCubicCurve::Data::updateSpline()
107{
108 if (validSpline) return;
109 validSpline = true;
110 spline.createSpline(points);
111}
112
113void KisCubicCurve::Data::invalidate()
114{
115 validSpline = false;
116 validFTransfer = false;
117 validU16Transfer = false;
118}
119
120void KisCubicCurve::Data::keepSorted()
121{
122 std::sort(points.begin(), points.end(), pointLessThan);
123}
124
125qreal KisCubicCurve::Data::value(qreal x)
126{
127 updateSpline();
128 /* Automatically extend non-existing parts of the curve
129 * (e.g. before the first point) and cut off big y-values
130 */
131 x = qBound(points.first().x(), x, points.last().x());
132 qreal y = spline.getValue(x);
133 return qBound(qreal(0.0), y, qreal(1.0));
134}
135
136template<typename _T_, typename _T2_>
137void KisCubicCurve::Data::updateTransfer(QVector<_T_>* transfer, bool& valid, _T2_ min, _T2_ max, int size)
138{
139 if (!valid || transfer->size() != size) {
140 if (transfer->size() != size) {
141 transfer->resize(size);
142 }
143 qreal end = 1.0 / (size - 1);
144 for (int i = 0; i < size; ++i) {
145 /* Direct uncached version */
146 _T2_ val = value(i * end ) * max;
147 val = qBound(min, val, max);
148 (*transfer)[i] = val;
149 }
150 valid = true;
151 }
152}
153
154struct Q_DECL_HIDDEN KisCubicCurve::Private {
155 QSharedDataPointer<Data> data;
156};
157
159 : d(new Private)
160{
161 d->data = new Data;
162 d->data->points.append({ 0.0, 0.0, false });
163 d->data->points.append({ 1.0, 1.0, false });
164}
165
167 : d(new Private)
168{
169 d->data = new Data;
170 d->data->points.reserve(points.size());
171 Q_FOREACH(const QPointF p, points) {
172 d->data->points.append({ p, false });
173 }
174 d->data->keepSorted();
175}
176
178 : d(new Private)
179{
180 d->data = new Data;
181 d->data->points = points;
182 d->data->keepSorted();
183}
184
186 : d(new Private(*curve.d))
187{
188}
189
190KisCubicCurve::KisCubicCurve(const QString &curveString)
191 : d(new Private)
192{
193 // Curve string format: a semi-colon separated list of point entries.
194 // Previous point entry format: a pair of numbers, separated by a comma,
195 // that represent the x and y coordinates of the point, in that order.
196 // Examples: identity "0.0,0.0;1.0,1.0;"
197 // U-shaped curve "0.0,1.0;0.5,0.0;1.0,1.0"
198 // New point entry format: a list of comma separated point properties. The
199 // first and second properties are required and should be numbers
200 // that represent the x and y coordinates of the point, in that order.
201 // After those, some optional properties/flags may be present or not.
202 // If there are no optional properties for any point entry, then the
203 // format of the string is identical to the old one.
204 // Currently, only the "is_corner" flag is supported. All other
205 // present optional properties are ignored.
206 // Examples: identity "0.0,0.0;1.0,1.0;"
207 // V-shaped curve "0.0,1.0;0.5,0.0,is_corner;1.0,1.0"
208
209 d->data = new Data;
210
211 KIS_SAFE_ASSERT_RECOVER(!curveString.isEmpty()) {
212 *this = KisCubicCurve();
213 return;
214 }
215
216 const QStringList data = curveString.split(';', Qt::SkipEmptyParts);
217
219 Q_FOREACH (const QString &entry, data) {
220 const QStringList entryData = entry.split(',', Qt::SkipEmptyParts);
221 KIS_SAFE_ASSERT_RECOVER(entryData.size() > 1) {
222 *this = KisCubicCurve();
223 return;
224 }
225 bool ok;
226 const qreal x = KisDomUtils::toDouble(entryData[0], &ok);
228 *this = KisCubicCurve();
229 return;
230 }
231 const qreal y = KisDomUtils::toDouble(entryData[1], &ok);
233 *this = KisCubicCurve();
234 return;
235 }
236 bool isCorner = false;
237 for (int i = 2; i < entryData.size(); ++i) {
238 if (entryData[i] == "is_corner") {
239 isCorner = true;
240 }
241 }
242 points.append({ x, y, isCorner });
243 }
244
246}
247
249{
250 delete d;
251}
252
254{
255 if (&curve != this) {
256 *d = *curve.d;
257 }
258 return *this;
259}
260
262{
263 if (d->data == curve.d->data) return true;
264 return d->data->points == curve.d->data->points;
265}
266
267qreal KisCubicCurve::value(qreal x) const
268{
269 qreal value = d->data->value(x);
270 return value;
271}
272
274{
275 QList<QPointF> pointPositions;
276 Q_FOREACH(const KisCubicCurvePoint &point, d->data->points) {
277 pointPositions.append(point.position());
278 }
279 return pointPositions;
280}
281
283{
284 return d->data->points;
285}
286
288{
289 d->data.detach();
290 d->data->points.clear();
291 Q_FOREACH(const QPointF &p, points) {
292 d->data->points.append({ p, false });
293 }
294 d->data->invalidate();
295}
296
298{
299 d->data.detach();
300 d->data->points = points;
301 d->data->invalidate();
302}
303
305{
306 d->data.detach();
307 d->data->points[idx] = point;
308 d->data->keepSorted();
309 d->data->invalidate();
310}
311
312void KisCubicCurve::setPoint(int idx, const QPointF& position, bool setAsCorner)
313{
314 setPoint(idx, { position, setAsCorner });
315}
316
317void KisCubicCurve::setPoint(int idx, const QPointF& position)
318{
319 setPointPosition(idx, position);
320}
321
322void KisCubicCurve::setPointPosition(int idx, const QPointF& position)
323{
324 d->data.detach();
325 d->data->points[idx].setPosition(position);
326 d->data->keepSorted();
327 d->data->invalidate();
328}
329
330void KisCubicCurve::setPointAsCorner(int idx, bool setAsCorner)
331{
332 d->data.detach();
333 d->data->points[idx].setAsCorner(setAsCorner);
334 d->data->invalidate();
335}
336
338{
339 d->data.detach();
340 d->data->points.append(point);
341 d->data->keepSorted();
342 d->data->invalidate();
343
344 return d->data->points.indexOf(point);
345}
346
347int KisCubicCurve::addPoint(const QPointF& position, bool setAsCorner)
348{
349 return addPoint({ position, setAsCorner });
350}
351
352int KisCubicCurve::addPoint(const QPointF& position)
353{
354 return addPoint({ position, false });
355}
356
358{
359 d->data.detach();
360 d->data->points.removeAt(idx);
361 d->data->invalidate();
362}
363
365{
366 const QList<KisCubicCurvePoint> &points = d->data->points;
367
368 if (points.first().x() != 0.0 || points.first().y() != 0.0 ||
369 points.last().x() != 1.0 || points.last().y() != 1.0) {
370 return false;
371 }
372
373 for (int i = 1; i < points.size() - 1; i++) {
374 if (!qFuzzyCompare(points[i].x(), points[i].y())) {
375 return false;
376 }
377 }
378
379 return true;
380}
381
382bool KisCubicCurve::isConstant(qreal c) const
383{
384 const QList<KisCubicCurvePoint> &points = d->data->points;
385
386 Q_FOREACH (const KisCubicCurvePoint &pt, points) {
387 if (!qFuzzyCompare(c, pt.y())) {
388 return false;
389 }
390 }
391
392 return true;
393}
394
395const QString& KisCubicCurve::name() const
396{
397 return d->data->name;
398}
399
400qreal KisCubicCurve::interpolateLinear(qreal normalizedValue, const QVector<qreal> &transfer)
401{
402 const qreal maxValue = transfer.size() - 1;
403
404 const qreal bilinearX = qBound(0.0, maxValue * normalizedValue, maxValue);
405 const qreal xFloored = std::floor(bilinearX);
406 const qreal xCeiled = std::ceil(bilinearX);
407
408 const qreal t = bilinearX - xFloored;
409
410 constexpr qreal eps = 1e-6;
411
412 qreal newValue = normalizedValue;
413
414 if (t < eps) {
415 newValue = transfer[int(xFloored)];
416 } else if (t > (1.0 - eps)) {
417 newValue = transfer[int(xCeiled)];
418 } else {
419 qreal a = transfer[int(xFloored)];
420 qreal b = transfer[int(xCeiled)];
421
422 newValue = a + t * (b - a);
423 }
424
425 return KisAlgebra2D::copysign(newValue, normalizedValue);
426}
427
428void KisCubicCurve::setName(const QString& name)
429{
430 d->data->name = name;
431}
432
434{
435 // See comments in KisCubicCurve(const QString &curveString) for the
436 // specification of the string format
437
438 QString sCurve;
439
440 if(d->data->points.count() < 1)
441 return sCurve;
442
443 Q_FOREACH (const KisCubicCurvePoint &point, d->data->points) {
444 sCurve += QString::number(point.x());
445 sCurve += ',';
446 sCurve += QString::number(point.y());
447 if (point.isSetAsCorner()) {
448 sCurve += ',';
449 sCurve += "is_corner";
450 }
451 sCurve += ';';
452 }
453
454 return sCurve;
455}
456
457void KisCubicCurve::fromString(const QString& string)
458{
459 *this = KisCubicCurve(string);
460}
461
463{
464 d->data->updateTransfer<quint16, int>(&d->data->u16Transfer, d->data->validU16Transfer, 0x0, 0xFFFF, size);
465 return d->data->u16Transfer;
466}
467
469{
470 d->data->updateTransfer<qreal, qreal>(&d->data->fTransfer, d->data->validFTransfer, 0.0, 1.0, size);
471 return d->data->fTransfer;
472}
float value(const T *src, size_t ch)
const Params2D p
bool operator==(const KisCubicCurvePoint &other) const
KisCubicCurvePoint()=default
void setX(qreal newX)
void setY(qreal newY)
void setAsCorner(bool newIsSetAsCorner)
const QPointF & position() const
bool isSetAsCorner() const
void setPosition(const QPointF &newPosition)
static bool qFuzzyCompare(half p1, half p2)
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
static bool pointLessThan(const KisCubicCurvePoint &a, const KisCubicCurvePoint &b)
const qreal eps
T copysign(T x, T y)
double toDouble(const QString &str, bool *ok=nullptr)
int size(const Forest< T > &forest)
Definition KisForest.h:1232
constexpr std::enable_if< sizeof...(values)==0, size_t >::type max()
bool operator==(const KisCubicCurve &curve) const
QString toString() const
QVector< quint16 > u16Transfer
const QVector< quint16 > uint16Transfer(int size=256) const
void setPoint(int idx, const KisCubicCurvePoint &point)
const QVector< qreal > floatTransfer(int size=256) const
Data(const Data &data)
void setPointPosition(int idx, const QPointF &position)
QVector< quint8 > u8Transfer
KisCubicCurve & operator=(const KisCubicCurve &curve)
QList< KisCubicCurvePoint > points
Q_DECL_DEPRECATED void fromString(const QString &)
void setPointAsCorner(int idx, bool setAsCorner)
void setPoints(const QList< QPointF > &points)
void setName(const QString &name)
void updateTransfer(QVector< _T_ > *transfer, bool &valid, _T2_ min, _T2_ max, int size)
void removePoint(int idx)
void keepSorted()
bool isConstant(qreal c) const
KisCubicSpline< KisCubicCurvePoint, qreal > spline
QSharedDataPointer< Data > data
static qreal interpolateLinear(qreal normalizedValue, const QVector< qreal > &transfer)
void updateSpline()
QVector< qreal > fTransfer
qreal value(qreal x)
void invalidate()
const QList< KisCubicCurvePoint > & curvePoints() const
int addPoint(const KisCubicCurvePoint &point)
bool isIdentity() const
Private *const d