Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_polygonal_gradient_shape_strategy.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2014 Dmitry Kazakov <dimula73@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
8
9#include "kis_debug.h"
10
11#include "kis_algebra_2d.h"
12
13#include <config-gsl.h>
14
15#ifdef HAVE_GSL
16#include <gsl/gsl_multimin.h>
17#endif /* HAVE_GSL */
18
19#include <QtCore/qmath.h>
20#include <limits>
21
22#include <boost/math/distributions/normal.hpp>
23
24#include <QPainterPath>
25#include "krita_utils.h"
26
27
28
29namespace Private {
30
31 QPointF centerFromPath(const QPainterPath &selectionPath) {
32 QPointF center;
33 int numPoints = 0;
34
35 for (int i = 0; i < selectionPath.elementCount(); i++) {
36 QPainterPath::Element element = selectionPath.elementAt(i);
37 if (element.type == QPainterPath::CurveToDataElement) continue;
38
39 center += element;
40 numPoints++;
41 }
42 if (numPoints == 0) { // can only happen if the path is empty
43 return center;
44 }
45 center /= numPoints;
46
47 return center;
48 }
49
50 qreal getDisnormedGradientValue(const QPointF &pt, const QPainterPath &selectionPath, qreal exponent)
51 {
52 // FIXME: exponent = 2.0
53 // We explicitly use pow2() and sqrt() functions here
54 // for efficiency reasons.
56 const qreal minHiLevel = std::pow(0.5, 1.0 / exponent);
57 qreal ptWeightNode = 0.0;
58
59 for (int i = 0; i < selectionPath.elementCount(); i++) {
60 if (selectionPath.elementAt(i).isMoveTo()) continue;
61
62 const int prevI = i > 0 ? i - 1 : selectionPath.elementCount() - 1;
63 const QPointF edgeP1 = selectionPath.elementAt(prevI);
64 const QPointF edgeP2 = selectionPath.elementAt(i);
65
66 const QPointF edgeVec = edgeP1 - edgeP2;
67 const QPointF q1 = pt - edgeP1;
68 const QPointF q2 = pt - edgeP2;
69
70 const qreal proj1 = KisAlgebra2D::dotProduct(edgeVec, q1);
71 const qreal proj2 = KisAlgebra2D::dotProduct(edgeVec, q2);
72
73 qreal hi = 1.0;
74
75 // pt's projection lays outside the edge itself,
76 // when the projections has the same sign
77
78 if (proj1 * proj2 >= 0) {
79 QPointF nearestPointVec =
80 qAbs(proj1) < qAbs(proj2) ? q1 : q2;
81
82 hi = KisAlgebra2D::norm(nearestPointVec);
83 } else {
84 QLineF line(edgeP1, edgeP2);
85 hi = kisDistanceToLine(pt, line);
86 }
87
88 hi = qMax(minHiLevel, hi);
89
90 // disabled for efficiency reasons
91 // ptWeightNode += 1.0 / std::pow(hi, exponent);
92
93 ptWeightNode += 1.0 / pow2(hi);
94 }
95
96 // disabled for efficiency reasons
97 // return 1.0 / std::pow(ptWeightNode, 1.0 / exponent);
98
99 return 1.0 / std::sqrt(ptWeightNode);
100 }
101
102 qreal initialExtremumValue(bool searchForMax) {
103 return searchForMax ?
104 std::numeric_limits<qreal>::min() :
105 std::numeric_limits<qreal>::max();
106 }
107
108 bool findBestStartingPoint(int numSamples, const QPainterPath &path,
109 qreal exponent, bool searchForMax,
111 QPointF *result) {
112
113 const qreal minStepThreshold = 0.3;
114 const int numCandidatesThreshold = 4;
115
116 KIS_ASSERT_RECOVER(numSamples >= 4) {
117 *result = centerFromPath(path);
118 return true;
119 }
120
121 int totalSamples = numSamples;
122 int effectiveSamples = numSamples & 0x1 ?
123 (numSamples - 1) / 2 + 1 : numSamples;
124
125 QRectF rect = path.boundingRect();
126
127 qreal xOffset = rect.width() / (totalSamples + 1);
128 qreal xStep = effectiveSamples == totalSamples ? xOffset : 2 * xOffset;
129
130 qreal yOffset = rect.height() / (totalSamples + 1);
131 qreal yStep = effectiveSamples == totalSamples ? yOffset : 2 * yOffset;
132
133 if (xStep < minStepThreshold || yStep < minStepThreshold) {
134 return false;
135 }
136
137 int numFound = 0;
138 int numCandidates = 0;
139 QPointF extremumPoint;
140 qreal extremumValue = initialExtremumValue;
141
142 const qreal eps = 1e-3;
143 int sanityNumRows = 0;
144 for (qreal y = rect.y() + yOffset; y < rect.bottom() - eps; y += yStep) {
145 int sanityNumColumns = 0;
146
147 sanityNumRows++;
148 for (qreal x = rect.x() + xOffset; x < rect.right() - eps; x += xStep) {
149 sanityNumColumns++;
150
151 const QPointF pt(x, y);
152 if (!path.contains(pt)) continue;
153
154 qreal value = getDisnormedGradientValue(pt, path, exponent);
155 bool isExtremum = searchForMax ? value > extremumValue : value < extremumValue;
156
157 numCandidates++;
158
159 if (isExtremum) {
160 numFound++;
161 extremumPoint = pt;
162 extremumValue = value;
163 }
164 }
165
166 KIS_ASSERT_RECOVER_NOOP(sanityNumColumns == effectiveSamples);
167 }
168 KIS_ASSERT_RECOVER_NOOP(sanityNumRows == effectiveSamples);
169
170 bool success = numFound && numCandidates >= numCandidatesThreshold;
171
172 if (success) {
173 *result = extremumPoint;
174 } else {
175 int newNumSamples = 2 * numSamples + 1;
176 success = findBestStartingPoint(newNumSamples, path,
177 exponent, searchForMax,
178 extremumValue,
179 result);
180
181 if (!success && numFound > 0) {
182 *result = extremumPoint;
183 success = true;
184 }
185 }
186
187 return success;
188 }
189
190#ifdef HAVE_GSL
191
192 struct GradientDescentParams {
193 QPainterPath selectionPath;
194 qreal exponent;
195 bool searchForMax;
196 };
197
198 double errorFunc (const gsl_vector * x, void *paramsPtr)
199 {
200 double vX = gsl_vector_get(x, 0);
201 double vY = gsl_vector_get(x, 1);
202
203 const GradientDescentParams *params =
204 static_cast<const GradientDescentParams*>(paramsPtr);
205
206 qreal weight = getDisnormedGradientValue(QPointF(vX, vY),
207 params->selectionPath,
208 params->exponent);
209
210 return params->searchForMax ? 1.0 / weight : weight;
211 }
212
213 qreal calculateMaxWeight(const QPainterPath &selectionPath,
214 qreal exponent,
215 bool searchForMax)
216 {
217 const gsl_multimin_fminimizer_type *T =
218 gsl_multimin_fminimizer_nmsimplex2;
219 gsl_multimin_fminimizer *s = 0;
220 gsl_vector *ss, *x;
221 gsl_multimin_function minex_func;
222
223 size_t iter = 0;
224 int status;
225 double size;
226
227 QPointF center;
228 bool centerExists =
229 findBestStartingPoint(4, selectionPath,
230 exponent, searchForMax,
231 Private::initialExtremumValue(searchForMax),
232 &center);
233
234 if (!centerExists || !selectionPath.contains(center)) {
235
236 // if the path is too small just return default values
237 if (selectionPath.boundingRect().width() >= 2.0 &&
238 selectionPath.boundingRect().height() >= 2.0) {
239
240 KIS_ASSERT_RECOVER_NOOP(selectionPath.contains(center));
241 }
242
243 return searchForMax ? 1.0 : 0.0;
244 }
245
246 /* Starting point */
247 x = gsl_vector_alloc (2);
248 gsl_vector_set (x, 0, center.x());
249 gsl_vector_set (x, 1, center.y());
250
251 /* Set initial step sizes to 10 px */
252 ss = gsl_vector_alloc (2);
253 gsl_vector_set (ss, 0, 10);
254 gsl_vector_set (ss, 1, 10);
255
256 GradientDescentParams p;
257
258 p.selectionPath = selectionPath;
259 p.exponent = exponent;
260 p.searchForMax = searchForMax;
261
262 /* Initialize method and iterate */
263 minex_func.n = 2;
264 minex_func.f = errorFunc;
265 minex_func.params = (void*)&p;
266
267 s = gsl_multimin_fminimizer_alloc (T, 2);
268 gsl_multimin_fminimizer_set (s, &minex_func, x, ss);
269
270 qreal result = searchForMax ?
271 getDisnormedGradientValue(center, selectionPath, exponent) : 0.0;
272
273 do
274 {
275 iter++;
276 status = gsl_multimin_fminimizer_iterate(s);
277
278 if (status)
279 break;
280
281 size = gsl_multimin_fminimizer_size (s);
282 status = gsl_multimin_test_size (size, 1e-6);
283
284 if (status == GSL_SUCCESS)
285 {
286 // dbgKrita << "*******Converged to minimum";
287 // dbgKrita << gsl_vector_get (s->x, 0)
288 // << gsl_vector_get (s->x, 1)
289 // << "|" << s->fval << size;
290
291 result = searchForMax ? 1.0 / s->fval : s->fval;
292 }
293 }
294 while (status == GSL_CONTINUE && iter < 10000);
295
296 gsl_vector_free(x);
297 gsl_vector_free(ss);
298 gsl_multimin_fminimizer_free (s);
299
300 return result;
301 }
302
303#else /* HAVE_GSL */
304
305 qreal calculateMaxWeight(const QPainterPath &selectionPath,
306 qreal exponent,
307 bool searchForMax)
308 {
309 QPointF center = centerFromPath(selectionPath);
310 return searchForMax ?
311 getDisnormedGradientValue(center, selectionPath, exponent) : 0.0;
312 }
313
314#endif /* HAVE_GSL */
315
316}
317
318QPainterPath simplifyPath(const QPainterPath &path,
319 qreal sizePortion,
320 qreal minLinearSize,
321 int minNumSamples)
322{
323 QPainterPath finalPath;
324
325 QList<QPolygonF> polygons = path.toSubpathPolygons();
326 Q_FOREACH (const QPolygonF poly, polygons) {
327 QPainterPath p;
328 p.addPolygon(poly);
329
330 const qreal length = p.length();
331 const qreal lengthStep =
332 KritaUtils::maxDimensionPortion(poly.boundingRect(),
333 sizePortion, minLinearSize);
334
335 int numSamples = qMax(qCeil(length / lengthStep), minNumSamples);
336
337 if (numSamples > poly.size()) {
338 finalPath.addPolygon(poly);
339 finalPath.closeSubpath();
340 continue;
341 }
342
343 const qreal portionStep = 1.0 / numSamples;
344
345 QPolygonF newPoly;
346 for (qreal t = 0.0; t < 1.0; t += portionStep) {
347 newPoly << p.pointAtPercent(t);
348 }
349
350 finalPath.addPolygon(newPoly);
351 finalPath.closeSubpath();
352 }
353
354 return finalPath;
355}
356
358 qreal exponent)
359 : m_exponent(exponent)
360{
361 m_selectionPath = simplifyPath(selectionPath, 0.01, 3.0, 100);
362
365
367}
368
372
373double KisPolygonalGradientShapeStrategy::valueAt(double x, double y) const
374{
375 QPointF pt(x, y);
376 qreal value = 0.0;
377
378 if (m_selectionPath.contains(pt)) {
381 }
382
383 return value;
384}
385
386QPointF KisPolygonalGradientShapeStrategy::testingCalculatePathCenter(int numSamples, const QPainterPath &path, qreal exponent, bool searchForMax)
387{
388 QPointF result;
389
390 qreal extremumValue = Private::initialExtremumValue(searchForMax);
391 bool success = Private::findBestStartingPoint(numSamples, path,
392 exponent, searchForMax,
393 extremumValue,
394 &result);
395
396 if (!success) {
397 dbgKrita << "WARNING: Couldn't calculate findBestStartingPoint for:";
398 dbgKrita << ppVar(numSamples);
399 dbgKrita << ppVar(exponent);
400 dbgKrita << ppVar(searchForMax);
401 dbgKrita << ppVar(path);
402
403 }
404
405 return result;
406}
qreal length(const QPointF &vec)
Definition Ellipse.cc:82
float value(const T *src, size_t ch)
QPointF q1
const Params2D p
QPointF q2
static QPointF testingCalculatePathCenter(int numSamples, const QPainterPath &path, qreal exponent, bool searchForMax)
double valueAt(double x, double y) const override
KisPolygonalGradientShapeStrategy(const QPainterPath &selectionPath, qreal exponent)
static bool qFuzzyCompare(half p1, half p2)
#define KIS_ASSERT_RECOVER(cond)
Definition kis_assert.h:55
#define KIS_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:97
const qreal eps
#define dbgKrita
Definition kis_debug.h:45
#define ppVar(var)
Definition kis_debug.h:155
T pow2(const T &x)
Definition kis_global.h:166
qreal kisDistanceToLine(const QPointF &m, const QLineF &line)
Definition kis_global.h:234
QPainterPath simplifyPath(const QPainterPath &path, qreal sizePortion, qreal minLinearSize, int minNumSamples)
qreal norm(const T &a)
PointTypeTraits< T >::value_type dotProduct(const T &a, const T &b)
int size(const Forest< T > &forest)
Definition KisForest.h:1232
qreal KRITAIMAGE_EXPORT maxDimensionPortion(const QRectF &bounds, qreal portion, qreal minValue)
bool findBestStartingPoint(int numSamples, const QPainterPath &path, qreal exponent, bool searchForMax, qreal initialExtremumValue, QPointF *result)
qreal getDisnormedGradientValue(const QPointF &pt, const QPainterPath &selectionPath, qreal exponent)
qreal calculateMaxWeight(const QPainterPath &selectionPath, qreal exponent, bool searchForMax)
qreal initialExtremumValue(bool searchForMax)
QPointF centerFromPath(const QPainterPath &selectionPath)