Krita Source Code Documentation
Loading...
Searching...
No Matches
KisSprayRandomDistributions.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2022 Deif Lou <ginoba@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7#include <cmath>
8
9#include <kis_assert.h>
10#include <KisMpl.h>
11
13
15{
16public:
18 {
19 double x;
20 double cdfAtX;
22 };
23
24 std::vector<SampleInfo> samples;
25
26 template <typename Function>
27 void initialize(size_t numberOfSamples, double a, double b, Function f)
28 {
29 KIS_SAFE_ASSERT_RECOVER_RETURN(numberOfSamples > 0);
31
32 samples.clear();
33
34 if (numberOfSamples < 3) {
35 samples.push_back({a, 0.0, 0.0});
36 samples.push_back({b, 1.0, 1.0});
37 return;
38 }
39
40 // Create the CDF
41 const double domainSize = b - a;
42 const double intervalSize = domainSize / static_cast<double>(numberOfSamples - 1);
43 double sum = 0.0;
44 double lastX, lastY, lastSum;
45 size_t effectiveNumberOfSamples = numberOfSamples;
46
47 // Adjust the limits of the pdf if it has a 0 probability segment at
48 // the start or at the end
49 {
50 for (size_t i = 0; i < numberOfSamples; ++i) {
51 const double x = a + intervalSize * static_cast<double>(i);
52 const double y = f(x);
53 if (y > 0.0) {
54 if (i > 0) {
55 a += intervalSize * static_cast<double>(i - 1);
56 effectiveNumberOfSamples -= i - 1;
57 }
58 break;
59 }
60 if (i == numberOfSamples - 1) {
61 // The whole pdf must have 0 probability
62 return;
63 }
64 }
65
66 for (size_t i = 0; i < numberOfSamples; ++i) {
67 const double x = b - intervalSize * static_cast<double>(i);
68 const double y = f(x);
69 if (y > 0.0) {
70 if (i > 0) {
71 b -= intervalSize * static_cast<double>(i - 1);
72 effectiveNumberOfSamples -= i - 1;
73 }
74 break;
75 }
76 }
77 }
78 // Insert first point
79 {
80 samples.push_back({a, 0.0, 0.0});
81 lastX = a;
82 lastY = f(a);
83 lastSum = 0.0;
84 }
85 // Insert the rest of the points
86 {
87 // This angle serves as a reference to see if the cdf curve is
88 // roughly straight, and remove some points
89 constexpr double maximumAngleDeviationToSkip{M_PI / 1000.0};
90 double referenceAngle = 0.0;
91 bool mustCheckAngle = false;
92 int skippedPoints = 0;
93
94 for (size_t i = 1; i < effectiveNumberOfSamples; ++i) {
95 const double x = a + intervalSize * static_cast<double>(i);
96 const double y = f(x);
97 // Accumulate the area under the curve between the two points
98 sum += (x - lastX) * (y + lastY) / 2.0;
99 //
100 if (y == 0.0) {
101 if (lastY == 0.0) {
102 lastX = x;
103 lastY = y;
104 lastSum = sum;
105 ++skippedPoints;
106 continue;
107 } else {
108 mustCheckAngle = false;
109 }
110 } else {
111 if (lastY == 0.0) {
112 if (skippedPoints > 0) {
113 samples.push_back({lastX, lastSum, 0.0});
114 }
115 mustCheckAngle = false;
116 }
117 }
118
119 // Compute if the current point is nearly colinear to the last two points
120 if (i > 1 && mustCheckAngle) {
121 const int nPoints = samples.size();
122 const double angle = std::atan2(sum - samples[nPoints - 2].cdfAtX, x - samples[nPoints - 2].x);
123 if (std::abs(angle - referenceAngle) <= maximumAngleDeviationToSkip) {
124 samples.back().x = x;
125 samples.back().cdfAtX = sum;
126 continue;
127 }
128 }
129
130 samples.push_back({x, sum, 0.0});
131 referenceAngle = std::atan2(sum - lastSum, x - lastX);
132 mustCheckAngle = true;
133 skippedPoints = 0;
134
135 lastX = x;
136 lastY = y;
137 lastSum = sum;
138 }
139 }
140 // Scale the cdf to the [0..1] range and compute deltas
141 {
142 for (size_t i = 1; i < samples.size() - 1; ++i) {
143 samples[i].cdfAtX /= sum;
144 samples[i].oneOverCdfDy = 1.0 / (samples[i].cdfAtX - samples[i - 1].cdfAtX);
145 }
146 samples.back().cdfAtX = 1.0;
147 samples.back().oneOverCdfDy = 1.0 / (1.0 - samples[samples.size() - 2].cdfAtX);
148 }
149 }
150
151 double generate(double randomValue) const
152 {
153 // Find the first sample that has cdf greater than the passed value
154 auto sampleIterator =
155 std::upper_bound(samples.begin(), samples.end(), SampleInfo{0.0, randomValue, 0.0},
157 const double t = (randomValue - (sampleIterator - 1)->cdfAtX) * sampleIterator->oneOverCdfDy;
158 return (sampleIterator - 1)->x + t * (sampleIterator->x - (sampleIterator - 1)->x);
159 }
160};
161
165
166template <typename Function>
167KisSprayFunctionBasedDistribution::KisSprayFunctionBasedDistribution(int numberOfSamples, double a, double b, Function f)
168 : m_d(new Private)
169{
170 m_d->initialize(numberOfSamples, a, b, f);
171}
172
175
181
182KisSprayFunctionBasedDistribution& KisSprayFunctionBasedDistribution::KisSprayFunctionBasedDistribution::operator=(const KisSprayFunctionBasedDistribution &rhs)
183{
184 if (this != &rhs) {
185 m_d->samples = rhs.m_d->samples;
186 }
187 return *this;
188}
189
191{
192 return m_d->generate(rs->generateNormalized());
193}
194
196{
198
199 return m_d->samples.front().x;
200}
201
203{
205
206 return m_d->samples.back().x;
207}
208
210{
211 return m_d->samples.size() > 1;
212}
213
214template <typename Function>
215void KisSprayFunctionBasedDistribution::initialize(size_t numberOfSamples, double a, double b, Function f)
216{
217 m_d->initialize(numberOfSamples, a, b, f);
218}
219
224
229
232
233KisSprayNormalDistribution::KisSprayNormalDistribution(double mean, double standardDeviation)
234{
235 KIS_SAFE_ASSERT_RECOVER_RETURN(standardDeviation > 0.0);
236
237 const double m_c1 = 1.0 / (standardDeviation * std::sqrt(2.0 * M_PI));
238 const double m_c2 = 2.0 * standardDeviation * standardDeviation;
239 KisSprayFunctionBasedDistribution::initialize(1000, 0.0, standardDeviation * 5.0,
240 [mean, m_c1, m_c2](double x)
241 {
242 const double shiftedX = x - mean;
243 return m_c1 * std::exp(-(shiftedX * shiftedX / m_c2));
244 }
245 );
246}
247
250
252{
253 KIS_SAFE_ASSERT_RECOVER_RETURN(standardDeviation > 0.0);
254
255 const double m_c1 = 1.0 / (standardDeviation * std::sqrt(2.0 * M_PI));
256 const double m_c2 = 2.0 * standardDeviation * standardDeviation;
257 KisSprayFunctionBasedDistribution::initialize(1000, 0.0, standardDeviation * 5.0,
258 [mean, m_c1, m_c2](double x)
259 {
260 const double shiftedX = x - mean;
261 return 2.0 * x * m_c1 * std::exp(-(shiftedX * shiftedX / m_c2));
262 }
263 );
264}
265
268
270{
271 KIS_SAFE_ASSERT_RECOVER_RETURN(clusteringAmount >= -100.0 && clusteringAmount <= 100.0);
272
273 if (clusteringAmount == 0.0) {
274 // Uniform distribution basically
276 [](double)
277 {
278 return 1.0;
279 }
280 );
281 return;
282 }
283 const double sigma = 1.0 / std::abs(clusteringAmount);
284 const double m_c1 = 2.0 * sigma * sigma;
285 if (clusteringAmount < 0.0) {
286 KisSprayFunctionBasedDistribution::initialize(1000, 1.0 - std::min(1.0, sigma * 4.0), 1.0,
287 [m_c1](double x)
288 {
289 const double xx = 1.0 - x;
290 return std::exp(-(xx * xx / m_c1));
291 }
292 );
293 } else {
294 KisSprayFunctionBasedDistribution::initialize(1000, 0.0, std::min(1.0, sigma * 4.0),
295 [m_c1](double x)
296 {
297 return std::exp(-(x * x / m_c1));
298 }
299 );
300 }
301}
302
305
307{
308 KIS_SAFE_ASSERT_RECOVER_RETURN(clusteringAmount >= -100.0 && clusteringAmount <= 100.0);
309
310 if (clusteringAmount == 0.0) {
311 // Uniform distribution basically
313 [](double x)
314 {
315 return 2.0 * x;
316 }
317 );
318 return;
319 }
320 const double sigma = 1.0 / std::abs(clusteringAmount);
321 const double m_c1 = 2.0 * sigma * sigma;
322 if (clusteringAmount < 0.0) {
323 KisSprayFunctionBasedDistribution::initialize(1000, 1.0 - std::min(1.0, sigma * 4.0), 1.0,
324 [m_c1](double x)
325 {
326 const double xx = 1.0 - x;
327 return 2.0 * x * std::exp(-(xx * xx / m_c1));
328 }
329 );
330 } else {
331 KisSprayFunctionBasedDistribution::initialize(1000, 0.0, std::min(1.0, sigma * 4.0),
332 [m_c1](double x)
333 {
334 return 2.0 * x * std::exp(-(x * x / m_c1));
335 }
336 );
337 }
338}
339
342
344{
346
347 KisSprayFunctionBasedDistribution::initialize(((curve.points().size() % 4) + 1) * 1000 * repeat, 0.0, 1.0,
348 [curve, repeat](double x)
349 {
350 const double sx = x * static_cast<double>(repeat);
351 return curve.value(sx - std::floor(sx));
352 }
353 );
354}
355
358
360{
362
363 KisSprayFunctionBasedDistribution::initialize(((curve.points().size() % 4) + 1) * 1000 * repeat, 0.0, 1.0,
364 [curve, repeat](double x)
365 {
366 const double sx = x * static_cast<double>(repeat);
367 return 2.0 * x * curve.value(sx - std::floor(sx));
368 }
369 );
370}
qreal generateNormalized() const
KisSprayClusterBasedDistribution()
Construct an invalid KisSprayClusterBasedDistribution.
KisSprayCurveBasedDistribution()
Construct an invalid KisSprayCurveBasedDistribution.
void initialize(size_t numberOfSamples, double a, double b, Function f)
Class that can generate randomly distributed values in the range [a..b] following an arbitrary pdf.
double min() const
Return the minimum value that this distribution can produce.
void initialize(size_t numberOfSamples, double a, double b, Function f)
Function used to setup the distribution and put it in a valid state. See the constructor for the expl...
KisSprayFunctionBasedDistribution()
Construct an invalid KisSprayFunctionBasedDistribution.
bool isValid() const
Return if this object is correctly initialized and can be used to generate values.
double max() const
Return the maximum value that this distribution can produce.
double operator()(KisRandomSourceSP rs) const
Get a random value between min and max that follows the distribution.
KisSprayNormalDistribution()
Construct an invalid KisSprayNormalDistribution.
double operator()(KisRandomSourceSP rs) const
Get a random value between min and max that follows a uniform distribution.
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define M_PI
Definition kis_global.h:111
auto mem_less(MemTypeNoRef Class::*ptr, MemType &&value)
mem_less is an unary functor that compares a member of the object to a given value
Definition KisMpl.h:269
QList< KisCubicCurvePoint > points