Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_algebra_2d.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
7#include "kis_algebra_2d.h"
8
9#include <QTransform>
10#include <QPainterPath>
11#include <kis_debug.h>
12
13#include <boost/accumulators/accumulators.hpp>
14#include <boost/accumulators/statistics/stats.hpp>
15#include <boost/accumulators/statistics/min.hpp>
16#include <boost/accumulators/statistics/max.hpp>
17
18#include <array>
19#include <QVector2D>
20#include <QVector3D>
21
22#include <QtMath>
23
24#include <config-gsl.h>
25
26#ifdef HAVE_GSL
27#include <gsl/gsl_multimin.h>
28#endif /*HAVE_GSL*/
29
30#include <Eigen/Eigenvalues>
31
32#define SANITY_CHECKS
33
34namespace KisAlgebra2D {
35
36void adjustIfOnPolygonBoundary(const QPolygonF &poly, int polygonDirection, QPointF *pt)
37{
38 const int numPoints = poly.size();
39 for (int i = 0; i < numPoints; i++) {
40 int nextI = i + 1;
41 if (nextI >= numPoints) {
42 nextI = 0;
43 }
44
45 const QPointF &p0 = poly[i];
46 const QPointF &p1 = poly[nextI];
47
48 QPointF edge = p1 - p0;
49
50 qreal cross = crossProduct(edge, *pt - p0)
51 / (0.5 * edge.manhattanLength());
52
53 if (cross < 1.0 &&
54 isInRange(pt->x(), p0.x(), p1.x()) &&
55 isInRange(pt->y(), p0.y(), p1.y())) {
56
57 QPointF salt = 1.0e-3 * inwardUnitNormal(edge, polygonDirection);
58
59 QPointF adjustedPoint = *pt + salt;
60
61 // in case the polygon is self-intersecting, polygon direction
62 // might not help
63 if (kisDistanceToLine(adjustedPoint, QLineF(p0, p1)) < 1e-4) {
64 adjustedPoint = *pt - salt;
65
66#ifdef SANITY_CHECKS
67 if (kisDistanceToLine(adjustedPoint, QLineF(p0, p1)) < 1e-4) {
68 dbgKrita << ppVar(*pt);
69 dbgKrita << ppVar(adjustedPoint);
70 dbgKrita << ppVar(QLineF(p0, p1));
72
73 dbgKrita << ppVar(poly.containsPoint(*pt, Qt::OddEvenFill));
74
75 dbgKrita << ppVar(kisDistanceToLine(*pt, QLineF(p0, p1)));
76 dbgKrita << ppVar(kisDistanceToLine(adjustedPoint, QLineF(p0, p1)));
77 }
78
79 *pt = adjustedPoint;
80
81 KIS_ASSERT_RECOVER_NOOP(kisDistanceToLine(*pt, QLineF(p0, p1)) > 1e-4);
82#endif /* SANITY_CHECKS */
83 }
84 }
85 }
86}
87
88QPointF transformAsBase(const QPointF &pt, const QPointF &base1, const QPointF &base2) {
89 qreal len1 = norm(base1);
90 if (len1 < 1e-5) return pt;
91 qreal sin1 = base1.y() / len1;
92 qreal cos1 = base1.x() / len1;
93
94 qreal len2 = norm(base2);
95 if (len2 < 1e-5) return QPointF();
96 qreal sin2 = base2.y() / len2;
97 qreal cos2 = base2.x() / len2;
98
99 qreal sinD = sin2 * cos1 - cos2 * sin1;
100 qreal cosD = cos1 * cos2 + sin1 * sin2;
101 qreal scaleD = len2 / len1;
102
103 QPointF result;
104 result.rx() = scaleD * (pt.x() * cosD - pt.y() * sinD);
105 result.ry() = scaleD * (pt.x() * sinD + pt.y() * cosD);
106
107 return result;
108}
109
110qreal angleBetweenVectors(const QPointF &v1, const QPointF &v2)
111{
112 qreal a1 = std::atan2(v1.y(), v1.x());
113 qreal a2 = std::atan2(v2.y(), v2.x());
114
115 return a2 - a1;
116}
117
118qreal directionBetweenPoints(const QPointF &p1, const QPointF &p2, qreal defaultAngle)
119{
120 if (fuzzyPointCompare(p1, p2)) {
121 return defaultAngle;
122 }
123
124 const QVector2D diff(p2 - p1);
125 return std::atan2(diff.y(), diff.x());
126}
127
128QPainterPath smallArrow()
129{
130 QPainterPath p;
131
132 p.moveTo(5, 2);
133 p.lineTo(-3, 8);
134 p.lineTo(-5, 5);
135 p.lineTo( 2, 0);
136 p.lineTo(-5,-5);
137 p.lineTo(-3,-8);
138 p.lineTo( 5,-2);
139 p.arcTo(QRectF(3, -2, 4, 4), 90, -180);
140
141 return p;
142}
143
144template <class Point, class Rect>
145inline Point ensureInRectImpl(Point pt, const Rect &bounds)
146{
147 if (pt.x() > bounds.right()) {
148 pt.rx() = bounds.right();
149 } else if (pt.x() < bounds.left()) {
150 pt.rx() = bounds.left();
151 }
152
153 if (pt.y() > bounds.bottom()) {
154 pt.ry() = bounds.bottom();
155 } else if (pt.y() < bounds.top()) {
156 pt.ry() = bounds.top();
157 }
158
159 return pt;
160}
161
162QPoint ensureInRect(QPoint pt, const QRect &bounds)
163{
164 return ensureInRectImpl(pt, bounds);
165}
166
167QPointF ensureInRect(QPointF pt, const QRectF &bounds)
168{
169 return ensureInRectImpl(pt, bounds);
170}
171
172bool intersectLineRect(QLineF &line, const QRect rect, bool extend)
173{
174 return intersectLineRect(line, rect, extend, extend);
175}
176
177bool intersectLineRect(QLineF &line, const QRect rect, bool extendFirst, bool extendSecond)
178{
179 // using Liang-Barsky algorithm
180 // using parametric equation for a line:
181 // X = x1 + t(x2-x1) = x1 + t*p2
182 // Y = y1 + t(y2-y1) = y1 + t*p4
183 // note that we have a line *segment* here, not a line
184 // t1 = 0, t2 = 1, no matter what points we got
185
186 double p1 = -(line.x2() - line.x1());
187 double p2 = -p1;
188 double p3 = -(line.y2() - line.y1());
189 double p4 = -p3;
190
192 QVector<double> q = QVector<double>({line.x1() - rect.x(), rect.x() + rect.width() - line.x1(), line.y1() - rect.y(), rect.y() + rect.height() - line.y1()});
193
194 float tmin = extendFirst ? -std::numeric_limits<float>::infinity() : 0; // line.p1() - first point, or infinity
195 float tmax = extendSecond ? std::numeric_limits<float>::infinity() : 1; // line.p2() - second point, or infinity
196
197 for (int i = 0; i < p.length(); i++) {
198 // now clipping
199 if (p[i] == 0 && q[i] < 0) {
200 // line is parallel to the boundary and outside of it
201 return false;
202 } else if (p[i] < 0) {
203 // line entry point (should be tmin)
204 double t = q[i]/p[i];
205 if (t > tmax) {
206 if (extendSecond) {
207 tmax = t;
208 } else {
209 return false;
210 }
211 }
212 if (t > tmin) {
213 tmin = t;
214 }
215
216
217 } else if (p[i] > 0) {
218 // line leaving point (should be tmax)
219 double t = q[i]/p[i];
220 if (t < tmin) {
221 if (extendFirst) {
222 tmin = t;
223 } else {
224 return false;
225 }
226 }
227 if (t < tmax) {
228 tmax = t;
229 }
230 }
231 }
232
233 QPointF pt1 = line.p1();
234 QPointF pt2 = line.p2();
235
236 if (extendFirst || tmin > 0) {
237 pt1 = QPointF(line.x1() + tmin*(line.x2() - line.x1()), line.y1() + tmin*(line.y2() - line.y1()));
238 }
239 if (extendSecond || tmax < 1) {
240 pt2 = QPointF(line.x1() + tmax*(line.x2() - line.x1()), line.y1() + tmax*(line.y2() - line.y1()));
241 }
242
243 line.setP1(pt1);
244 line.setP2(pt2);
245
246 return true;
247
248}
249
250bool intersectLineConvexPolygon(QLineF &line, const QPolygonF polygon, bool extendFirst, bool extendSecond)
251{
252 qreal epsilon = 1e-07;
253
254 if (polygon.size() == 0) {
255 return false;
256 }
257
258 // trivial case: no extends, all points inside the polygon
259 if (!extendFirst && !extendSecond && polygon.containsPoint(line.p1(), Qt::WindingFill) && polygon.containsPoint(line.p2(), Qt::WindingFill)) {
260 return true;
261 }
262
263 // Cyrus-Beck algorithm: https://en.wikipedia.org/wiki/Cyrus%E2%80%93Beck_algorithm
264
265 // parametric equation for the line:
266 // p(t) = t*P1 + (1-t)*P2
267
268 // we can't use infinity here, because the points would all end up as (+/- inf, +/- inf)
269 // which would be useless if we have a very specific direction
270 // hence we use just "a big number"
271 float aBigNumber = 100000; // that will keep t*Px still on the line // this is LIMITATION of the algorithm
272 float tmin = extendFirst ? -aBigNumber : 0; // line.p1() - first point, or infinity
273 float tmax = extendSecond ? aBigNumber : 1; // line.p2() - second point, or infinity
274
275
276
277 QList<QLineF> clippingLines;
278 QList<QPointF> normals;
279
280 bool clockwise = false;
281 {
282 // only temporary values to check whether the polygon is clockwise or counterclockwise
283 QPointF vec1 = polygon[1] - polygon[0];
284 QPointF vec2 = polygon[2] - polygon[0];
285
286 QLineF line1(QPointF(0, 0), vec1);
287 QLineF line2(QPointF(0, 0), vec2);
288
289 qreal angle = line1.angleTo(line2); // range: <0, 360) // <0, 180) means counter-clockwise
290 if (angle >= 180) {
291 clockwise = true;
292 }
293 }
294
295
296 for (int i = 0; i < polygon.size() - 1; i++) {
297 clippingLines.append(QLineF(polygon[i], polygon[i + 1]));
298 float xdiff = polygon[i].x() - polygon[i + 1].x();
299 float ydiff = polygon[i].y() - polygon[i + 1].y();
300
301 if (!clockwise) {
302 normals.append(QPointF(ydiff, -xdiff));
303 } else {
304 normals.append(QPointF(-ydiff, xdiff));
305 }
306
307 }
308
309 if (!polygon.isClosed()) {
310 int i = polygon.size() - 1;
311 clippingLines.append(QLineF(polygon[i], polygon[0]));
312 float xdiff = polygon[i].x() - polygon[0].x();
313 float ydiff = polygon[i].y() - polygon[0].y();
314
315 if (!clockwise) {
316 normals.append(QPointF(ydiff, -xdiff));
317 } else {
318 normals.append(QPointF(-ydiff, xdiff));
319 }
320
321 }
322
323 QPointF lineVec = line.p2() - line.p1();
324 // ax + by + c = 0
325 // c = -ax - by
326// qreal cFromStandardEquation = -lineVec.x()*line.p1().x() - lineVec.y()*line.p1().y();
327
328
329 QPointF p1 = line.p1();
330 QPointF p2 = line.p2();
331
332 qreal pA, pB, pC; // standard equation for the line: ax + by + c = 0
333 if (qAbs(lineVec.x()) < epsilon) {
334 // x1 ~= x2, line looks like: x = -pC
335 pB = 0;
336 pA = 1;
337 pC = -p1.x();
338 } else {
339 pB = 1; // let b = 1 to simplify
340 qreal tmp = (p1.y() - p2.y())/(p1.x() - p2.x());
341 pA = -tmp;
342 pC = tmp * p1.x() - p1.y();
343 }
344
345
346 int pEFound = 0;
347
348
349 for (int i = 0; i < clippingLines.size(); i++) {
350
351 // is the clipping line parallel to the line?
352 QPointF clipVec = clippingLines[i].p2() - clippingLines[i].p1();
353
354 if (qFuzzyCompare(clipVec.x()*lineVec.y(), clipVec.y()*lineVec.x())) {
355
356 // vectors are parallel
357 // let's check if one of the clipping points is on the line (extended) to see if it's the same line; if not, proceed normally
358
359 qreal distanceBetweenLines = qAbs(pA*clippingLines[i].p1().x() + pB*clippingLines[i].p1().y() + pC)
360 /(sqrt(pA*pA + pB*pB));
361
362
363 if (qAbs(distanceBetweenLines) < epsilon) {
364 // they are the same line
365
366 qreal t1;
367 qreal t2;
368
369 if (qAbs(p2.x() - p1.x()) > epsilon) {
370 t1 = (clippingLines[i].p1().x() - p1.x())/(p2.x() - p1.x());
371 t2 = (clippingLines[i].p2().x() - p1.x())/(p2.x() - p1.x());
372 } else {
373 t1 = (clippingLines[i].p1().y() - p1.y())/(p2.y() - p1.y());
374 t2 = (clippingLines[i].p2().y() - p1.y())/(p2.y() - p1.y());
375 }
376
377 qreal tmin1 = qMin(t1, t2);
378 qreal tmax2 = qMax(t1, t2);
379
380
381
382 if (tmin < tmin1) {
383 tmin = tmin1;
384 }
385 if (tmax > tmax2) {
386 tmax = tmax2;
387 }
388 continue;
389 }
390 else {
391 // if clipping line points are P1 and P2, and some other point is P(t) (given by parametric equation),
392 // and N is the normal vector
393 // and one of the points of the line we're intersecting is K,
394 // then we have a following equation:
395 // K = P(t) + a * N
396 // where t and a are unknown.
397 // after simplifying we get:
398 // t*(p2 - p1) + a*(n) = k - p1
399 // and since we have two axis, we get a linear system of two equations
400 // A * [t; a] = B
401
402 Eigen::Matrix2f A;
403 Eigen::Vector2f b;
404 A << p2.x() - p1.x(), normals[i].x(),
405 p2.y() - p1.y(), normals[i].y();
406 b << clippingLines[i].p1().x() - p1.x(),
407 clippingLines[i].p1().y() - p1.y();
408
409 Eigen::Vector2f ta = A.colPivHouseholderQr().solve(b);
410
411 qreal tt = ta(1);
412 if (tt < 0) {
413 return false;
414 }
415 }
416
417 }
418
419
420 boost::optional<QPointF> pEOptional = intersectLines(clippingLines[i], line); // bounded, unbounded
421
422
423 if (pEOptional) {
424 pEFound++;
425
426 QPointF pE = pEOptional.value();
427 QPointF n = normals[i];
428
429 QPointF A = line.p2() - line.p1();
430 QPointF B = line.p1() - pE;
431
432 qreal over = (n.x()*B.x() + n.y()*B.y());
433 qreal under = (n.x()*A.x() + n.y()*A.y());
434 qreal t = -over/under;
435
436// if (pE.x() != p2.x()) {
437// qreal maybet = (pE.x() - p1.x())/(p2.x() - p1.x());
438// }
439
440 qreal tminvalue = tmin * under + over;
441 qreal tmaxvalue = tmax * under + over;
442
443 if (tminvalue > 0 && tmaxvalue > 0) {
444 // both outside of the edge
445 return false;
446 }
447 if (tminvalue <= 0 && tmaxvalue <= 0) {
448 // noop, both inside
449 }
450 if (tminvalue*tmaxvalue < 0) {
451
452 // on two different sides
453 if (tminvalue > 0) {
454 // tmin outside, tmax inside
455 if (t > tmin) {
456 tmin = t;
457 }
458 } else {
459 if (t < tmax) {
460 tmax = t;
461 }
462 }
463 }
464
465
466
467 if (tmax < tmin) {
468 return false;
469 }
470 }
471 else {
472 }
473
474 }
475
476 if (pEFound == 0) {
477 return false;
478 }
479
480 QLineF response = QLineF(tmin*line.p2() + (1-tmin)*line.p1(), tmax*line.p2() + (1-tmax)*line.p1());
481 line = response;
482 return true;
483}
484
485 template <class Rect, class Point>
487 {
488 QVector<Point> points;
489
490 Point m1 = 0.5 * (rect.topLeft() + rect.topRight());
491 Point m2 = 0.5 * (rect.bottomLeft() + rect.bottomRight());
492
493 points << rect.topLeft();
494 points << m1;
495 points << rect.topRight();
496
497 points << 0.5 * (rect.topLeft() + rect.bottomLeft());
498 points << 0.5 * (m1 + m2);
499 points << 0.5 * (rect.topRight() + rect.bottomRight());
500
501 points << rect.bottomLeft();
502 points << m2;
503 points << rect.bottomRight();
504
505 return points;
506 }
507
509 {
510 return sampleRectWithPoints<QRect, QPoint>(rect);
511 }
512
514 {
515 return sampleRectWithPoints<QRectF, QPointF>(rect);
516 }
517
518
519 template <class Rect, class Point, bool alignPixels>
521 {
522 using namespace boost::accumulators;
523 accumulator_set<qreal, stats<tag::min, tag::max > > accX;
524 accumulator_set<qreal, stats<tag::min, tag::max > > accY;
525
526 Q_FOREACH (const Point &pt, points) {
527 accX(pt.x());
528 accY(pt.y());
529 }
530
531 Rect resultRect;
532
533 if (alignPixels) {
534 resultRect.setCoords(std::floor(min(accX)), std::floor(min(accY)),
535 std::ceil(max(accX)), std::ceil(max(accY)));
536 } else {
537 resultRect.setCoords(min(accX), min(accY),
538 max(accX), max(accY));
539 }
540
541 return resultRect;
542 }
543
545 {
546 return approximateRectFromPointsImpl<QRect, QPoint, true>(points);
547 }
548
550 {
551 return approximateRectFromPointsImpl<QRectF, QPointF, false>(points);
552 }
553
554 QRect approximateRectWithPointTransform(const QRect &rect, std::function<QPointF(QPointF)> func)
555 {
557
558 using namespace boost::accumulators;
559 accumulator_set<qreal, stats<tag::min, tag::max > > accX;
560 accumulator_set<qreal, stats<tag::min, tag::max > > accY;
561
562 Q_FOREACH (const QPoint &pt, points) {
563 QPointF dstPt = func(pt);
564
565 accX(dstPt.x());
566 accY(dstPt.y());
567 }
568
569 QRect resultRect;
570 resultRect.setCoords(std::floor(min(accX)), std::floor(min(accY)),
571 std::ceil(max(accX)), std::ceil(max(accY)));
572
573 return resultRect;
574 }
575
576
577QRectF cutOffRect(const QRectF &rc, const KisAlgebra2D::RightHalfPlane &p)
578{
579 QVector<QPointF> points;
580
581 const QLineF cutLine = p.getLine();
582
583 points << rc.topLeft();
584 points << rc.topRight();
585 points << rc.bottomRight();
586 points << rc.bottomLeft();
587
588 QPointF p1 = points[3];
589 bool p1Valid = p.pos(p1) >= 0;
590
591 QVector<QPointF> resultPoints;
592
593 for (int i = 0; i < 4; i++) {
594 const QPointF p2 = points[i];
595 const bool p2Valid = p.pos(p2) >= 0;
596
597 if (p1Valid != p2Valid) {
598 QPointF intersection;
599 cutLine.intersects(QLineF(p1, p2), &intersection);
600 resultPoints << intersection;
601 }
602
603 if (p2Valid) {
604 resultPoints << p2;
605 }
606
607 p1 = p2;
608 p1Valid = p2Valid;
609 }
610
611 return approximateRectFromPoints(resultPoints);
612}
613
614int quadraticEquation(qreal a, qreal b, qreal c, qreal *x1, qreal *x2)
615{
616 int numSolutions = 0;
617
618 const qreal D = pow2(b) - 4 * a * c;
619 const qreal eps = 1e-14;
620
621 if (qAbs(D) <= eps) {
622 *x1 = -b / (2 * a);
623 numSolutions = 1;
624 } else if (D < 0) {
625 return 0;
626 } else {
627 const qreal sqrt_D = std::sqrt(D);
628
629 *x1 = (-b + sqrt_D) / (2 * a);
630 *x2 = (-b - sqrt_D) / (2 * a);
631 numSolutions = 2;
632 }
633
634 return numSolutions;
635}
636
637QVector<QPointF> intersectTwoCircles(const QPointF &center1, qreal r1,
638 const QPointF &center2, qreal r2)
639{
640 QVector<QPointF> points;
641
642 const QPointF diff = (center2 - center1);
643 const QPointF c1;
644 const QPointF c2 = diff;
645
646 const qreal centerDistance = norm(diff);
647
648 if (centerDistance > r1 + r2) return points;
649 if (centerDistance < qAbs(r1 - r2)) return points;
650
651 if (centerDistance < qAbs(r1 - r2) + 0.001) {
652 dbgKrita << "Skipping intersection" << ppVar(center1) << ppVar(center2) << ppVar(r1) << ppVar(r2) << ppVar(centerDistance) << ppVar(qAbs(r1-r2));
653 return points;
654 }
655
656 const qreal x_kp1 = diff.x();
657 const qreal y_kp1 = diff.y();
658
659 const qreal F2 =
660 0.5 * (pow2(x_kp1) +
661 pow2(y_kp1) + pow2(r1) - pow2(r2));
662
663 const qreal eps = 1e-6;
664
665 if (qAbs(diff.y()) < eps) {
666 qreal x = F2 / diff.x();
667 qreal y1, y2;
669 1, 0,
670 pow2(x) - pow2(r2),
671 &y1, &y2);
672
673 KIS_SAFE_ASSERT_RECOVER(result > 0) { return points; }
674
675 if (result == 1) {
676 points << QPointF(x, y1);
677 } else if (result == 2) {
679
680 QPointF p1(x, y1);
681 QPointF p2(x, y2);
682
683 if (p.pos(p1) >= 0) {
684 points << p1;
685 points << p2;
686 } else {
687 points << p2;
688 points << p1;
689 }
690 }
691 } else {
692 const qreal A = diff.x() / diff.y();
693 const qreal C = F2 / diff.y();
694
695 qreal x1, x2;
697 1 + pow2(A), -2 * A * C,
698 pow2(C) - pow2(r1),
699 &x1, &x2);
700
701 KIS_SAFE_ASSERT_RECOVER(result > 0) { return points; }
702
703 if (result == 1) {
704 points << QPointF(x1, C - x1 * A);
705 } else if (result == 2) {
707
708 QPointF p1(x1, C - x1 * A);
709 QPointF p2(x2, C - x2 * A);
710
711 if (p.pos(p1) >= 0) {
712 points << p1;
713 points << p2;
714 } else {
715 points << p2;
716 points << p1;
717 }
718 }
719 }
720
721 for (int i = 0; i < points.size(); i++) {
722 points[i] = center1 + points[i];
723 }
724
725 return points;
726}
727
728QTransform mapToRect(const QRectF &rect)
729{
730 return
731 QTransform(rect.width(), 0, 0, rect.height(),
732 rect.x(), rect.y());
733}
734
735QTransform mapToRectInverse(const QRectF &rect)
736{
737 return
738 QTransform::fromTranslate(-rect.x(), -rect.y()) *
739 QTransform::fromScale(rect.width() != 0 ? 1.0 / rect.width() : 0.0,
740 rect.height() != 0 ? 1.0 / rect.height() : 0.0);
741}
742
743bool fuzzyMatrixCompare(const QTransform &t1, const QTransform &t2, qreal delta) {
744 return
745 qAbs(t1.m11() - t2.m11()) < delta &&
746 qAbs(t1.m12() - t2.m12()) < delta &&
747 qAbs(t1.m13() - t2.m13()) < delta &&
748 qAbs(t1.m21() - t2.m21()) < delta &&
749 qAbs(t1.m22() - t2.m22()) < delta &&
750 qAbs(t1.m23() - t2.m23()) < delta &&
751 qAbs(t1.m31() - t2.m31()) < delta &&
752 qAbs(t1.m32() - t2.m32()) < delta &&
753 qAbs(t1.m33() - t2.m33()) < delta;
754}
755
756bool fuzzyPointCompare(const QPointF &p1, const QPointF &p2)
757{
758 return qFuzzyCompare(p1.x(), p2.x()) && qFuzzyCompare(p1.y(), p2.y());
759}
760
761
762bool fuzzyPointCompare(const QPointF &p1, const QPointF &p2, qreal delta)
763{
764 return qAbs(p1.x() - p2.x()) < delta && qAbs(p1.y() - p2.y()) < delta;
765}
766
767
768inline QTransform toQTransformStraight(const Eigen::Matrix3d &m)
769{
770 return QTransform(m(0,0), m(0,1), m(0,2),
771 m(1,0), m(1,1), m(1,2),
772 m(2,0), m(2,1), m(2,2));
773}
774
775inline Eigen::Matrix3d fromQTransformStraight(const QTransform &t)
776{
777 Eigen::Matrix3d m;
778
779 m << t.m11() , t.m12() , t.m13()
780 ,t.m21() , t.m22() , t.m23()
781 ,t.m31() , t.m32() , t.m33();
782
783 return m;
784}
785
786/********************************************************/
787/* DecomposedMatrix */
788/********************************************************/
789
793
795{
796 QTransform t(t0);
797
798 QTransform projMatrix;
799
800 if (t.m33() == 0.0 || t0.determinant() == 0.0) {
801 qWarning() << "Cannot decompose matrix!" << t;
802 valid = false;
803 return;
804 }
805
806 if (t.type() == QTransform::TxProject) {
807 QTransform affineTransform(
808 t.m11(), t.m12(), 0,
809 t.m21(), t.m22(), 0,
810 t.m31(), t.m32(), 1
811 );
812 projMatrix = affineTransform.inverted() * t;
813
814 t = affineTransform;
815 proj[0] = projMatrix.m13();
816 proj[1] = projMatrix.m23();
817 proj[2] = projMatrix.m33();
818 }
819
820 // can't use QVector3D, because they have too little accuracy for ellipse in perspective calculations
821 std::array<Eigen::Vector3d, 3> rows;
822
823
824 // << t.m11() << t.m12() << t.m13())
825 rows[0] = Eigen::Vector3d(t.m11(), t.m12(), t.m13());
826 rows[1] = Eigen::Vector3d(t.m21(), t.m22(), t.m23());
827 rows[2] = Eigen::Vector3d(t.m31(), t.m32(), t.m33());
828
829 if (!qFuzzyCompare(t.m33(), 1.0)) {
830 const qreal invM33 = 1.0 / t.m33();
831
832 for (auto &row : rows) {
833 row *= invM33;
834 }
835 }
836
837 dx = rows[2].x();
838 dy = rows[2].y();
839
840 rows[2] = Eigen::Vector3d(0,0,1);
841
842
843 scaleX = rows[0].norm();
844 rows[0] *= 1.0 / scaleX;
845
846 shearXY = rows[0].dot(rows[1]);
847 rows[1] = rows[1] - shearXY * rows[0];
848
849 scaleY = rows[1].norm();
850 rows[1] *= 1.0 / scaleY;
851 shearXY *= 1.0 / scaleY;
852
853 // If determinant is negative, one axis was flipped.
854 qreal determinant = rows[0].x() * rows[1].y() - rows[0].y() * rows[1].x();
855 if (determinant < 0) {
856 // Flip axis with minimum unit vector dot product.
857 if (rows[0].x() < rows[1].y()) {
858 scaleX = -scaleX;
859 rows[0] = -rows[0];
860 } else {
861 scaleY = -scaleY;
862 rows[1] = -rows[1];
863 }
864 shearXY = - shearXY;
865 }
866
867 angle = kisRadiansToDegrees(std::atan2(rows[0].y(), rows[0].x()));
868
869 if (angle != 0.0) {
870 // Rotate(-angle) = [cos(angle), sin(angle), -sin(angle), cos(angle)]
871 // = [row0x, -row0y, row0y, row0x]
872 // Thanks to the normalization above.
873
874 qreal sn = -rows[0].y();
875 qreal cs = rows[0].x();
876 qreal m11 = rows[0].x();
877 qreal m12 = rows[0].y();
878 qreal m21 = rows[1].x();
879 qreal m22 = rows[1].y();
880 rows[0][0] = (cs * m11 + sn * m21);
881 rows[0][1] = (cs * m12 + sn * m22);
882 rows[1][0] = (-sn * m11 + cs * m21);
883 rows[1][1] = (-sn * m12 + cs * m22);
884 }
885
886 QTransform leftOver(
887 rows[0].x(), rows[0].y(), rows[0].z(),
888 rows[1].x(), rows[1].y(), rows[1].z(),
889 rows[2].x(), rows[2].y(), rows[2].z());
890
891 if (/*true || */!fuzzyMatrixCompare(leftOver, QTransform(), 1e-4)) {
892 // what's wrong?
893 ENTER_FUNCTION() << "FAILING THE ASSERT BELOW!";
894 ENTER_FUNCTION() << ppVar(leftOver);
895 ENTER_FUNCTION() << "matrix to decompose was: " << ppVar(t0);
896 ENTER_FUNCTION() << ppVar(t.m33()) << ppVar(t0.determinant());
897 Eigen::Matrix3d mat1 = fromQTransformStraight(QTransform());
898 Eigen::Matrix3d mat2 = fromQTransformStraight(leftOver);
899 Eigen::Matrix3d mat3 = mat2 - mat1;
900 ENTER_FUNCTION() << mat3(0, 0) << mat3(0, 1) << mat3(0, 2);
901 ENTER_FUNCTION() << mat3(1, 0) << mat3(1, 1) << mat3(1, 2);
902 ENTER_FUNCTION() << mat3(2, 0) << mat3(2, 1) << mat3(2, 2);
903 //ENTER_FUNCTION() << ppVar(mat1 - mat2);
904 }
905
906
907 KIS_SAFE_ASSERT_RECOVER_NOOP(fuzzyMatrixCompare(leftOver, QTransform(), 1e-4));
908 KIS_ASSERT(fuzzyMatrixCompare(leftOver, QTransform(), 1e-4));
909}
910
911
912std::pair<QPointF, QTransform> transformEllipse(const QPointF &axes, const QTransform &fullLocalToGlobal)
913{
914 KisAlgebra2D::DecomposedMatrix decomposed(fullLocalToGlobal);
915 const QTransform localToGlobal =
916 decomposed.scaleTransform() *
917 decomposed.shearTransform() *
918
919 /* decomposed.projectTransform() * */
920 /*decomposed.translateTransform() * */
921
922 decomposed.rotateTransform();
923
924 const QTransform localEllipse = QTransform(1.0 / pow2(axes.x()), 0.0, 0.0,
925 0.0, 1.0 / pow2(axes.y()), 0.0,
926 0.0, 0.0, 1.0);
927
928
929 const QTransform globalToLocal = localToGlobal.inverted();
930
931 Eigen::Matrix3d eqM =
932 fromQTransformStraight(globalToLocal *
933 localEllipse *
934 globalToLocal.transposed());
935
936// std::cout << "eqM:" << std::endl << eqM << std::endl;
937
938 Eigen::EigenSolver<Eigen::Matrix3d> eigenSolver(eqM);
939
940 const Eigen::Matrix3d T = eigenSolver.eigenvalues().real().asDiagonal();
941 const Eigen::Matrix3d U = eigenSolver.eigenvectors().real();
942
943 const Eigen::Matrix3d Ti = eigenSolver.eigenvalues().imag().asDiagonal();
944 const Eigen::Matrix3d Ui = eigenSolver.eigenvectors().imag();
945
946 KIS_SAFE_ASSERT_RECOVER_NOOP(Ti.isZero());
948 KIS_SAFE_ASSERT_RECOVER_NOOP((U * U.transpose()).isIdentity());
949
950// std::cout << "T:" << std::endl << T << std::endl;
951// std::cout << "U:" << std::endl << U << std::endl;
952
953// std::cout << "Ti:" << std::endl << Ti << std::endl;
954// std::cout << "Ui:" << std::endl << Ui << std::endl;
955
956// std::cout << "UTU':" << std::endl << U * T * U.transpose() << std::endl;
957
958 const qreal newA = 1.0 / std::sqrt(T(0,0) * T(2,2));
959 const qreal newB = 1.0 / std::sqrt(T(1,1) * T(2,2));
960
961 const QTransform newGlobalToLocal = toQTransformStraight(U);
962 const QTransform newLocalToGlobal = QTransform::fromScale(-1,-1) *
963 newGlobalToLocal.inverted() *
964 decomposed.translateTransform();
965
966 return std::make_pair(QPointF(newA, newB), newLocalToGlobal);
967}
968
969QPointF alignForZoom(const QPointF &pt, qreal zoom)
970{
971 return QPointF((pt * zoom).toPoint()) / zoom;
972}
973
974boost::optional<QPointF> intersectLines(const QLineF &boundedLine, const QLineF &unboundedLine)
975{
976 const QPointF B1 = unboundedLine.p1();
977 const QPointF A1 = unboundedLine.p2() - unboundedLine.p1();
978
979 const QPointF B2 = boundedLine.p1();
980 const QPointF A2 = boundedLine.p2() - boundedLine.p1();
981
982 Eigen::Matrix<float, 2, 2> A;
983 A << A1.x(), -A2.x(),
984 A1.y(), -A2.y();
985
986 Eigen::Matrix<float, 2, 1> B;
987 B << B2.x() - B1.x(),
988 B2.y() - B1.y();
989
990 if (qFuzzyIsNull(A.determinant())) {
991 return boost::none;
992 }
993
994 Eigen::Matrix<float, 2, 1> T;
995
996 T = A.inverse() * B;
997
998 const qreal t2 = T(1,0);
999 double epsilon = 1e-06; // to avoid precision issues
1000
1001 if (t2 < 0.0 || t2 > 1.0) {
1002 if (qAbs(t2) > epsilon && qAbs(t2 - 1.0) > epsilon) {
1003 return boost::none;
1004 }
1005 }
1006
1007 return t2 * A2 + B2;
1008}
1009
1010boost::optional<QPointF> intersectLines(const QPointF &p1, const QPointF &p2,
1011 const QPointF &q1, const QPointF &q2)
1012{
1013 return intersectLines(QLineF(p1, p2), QLineF(q1, q2));
1014}
1015
1016QVector<QPointF> findTrianglePoint(const QPointF &p1, const QPointF &p2, qreal a, qreal b)
1017{
1018 using KisAlgebra2D::norm;
1020
1021 QVector<QPointF> result;
1022
1023 const QPointF p = p2 - p1;
1024
1025 const qreal pSq = dotProduct(p, p);
1026
1027 const qreal T = 0.5 * (-pow2(b) + pow2(a) + pSq);
1028
1029 if (p.isNull()) return result;
1030
1031 if (qAbs(p.x()) > qAbs(p.y())) {
1032 const qreal A = 1.0;
1033 const qreal B2 = -T * p.y() / pSq;
1034 const qreal C = pow2(T) / pSq - pow2(a * p.x()) / pSq;
1035
1036 const qreal D4 = pow2(B2) - A * C;
1037
1038 if (D4 > 0 || qFuzzyIsNull(D4)) {
1039 if (qFuzzyIsNull(D4)) {
1040 const qreal y = -B2 / A;
1041 const qreal x = (T - y * p.y()) / p.x();
1042 result << p1 + QPointF(x, y);
1043 } else {
1044 const qreal y1 = (-B2 + std::sqrt(D4)) / A;
1045 const qreal x1 = (T - y1 * p.y()) / p.x();
1046 result << p1 + QPointF(x1, y1);
1047
1048 const qreal y2 = (-B2 - std::sqrt(D4)) / A;
1049 const qreal x2 = (T - y2 * p.y()) / p.x();
1050 result << p1 + QPointF(x2, y2);
1051 }
1052 }
1053 } else {
1054 const qreal A = 1.0;
1055 const qreal B2 = -T * p.x() / pSq;
1056 const qreal C = pow2(T) / pSq - pow2(a * p.y()) / pSq;
1057
1058 const qreal D4 = pow2(B2) - A * C;
1059
1060 if (D4 > 0 || qFuzzyIsNull(D4)) {
1061 if (qFuzzyIsNull(D4)) {
1062 const qreal x = -B2 / A;
1063 const qreal y = (T - x * p.x()) / p.y();
1064 result << p1 + QPointF(x, y);
1065 } else {
1066 const qreal x1 = (-B2 + std::sqrt(D4)) / A;
1067 const qreal y1 = (T - x1 * p.x()) / p.y();
1068 result << p1 + QPointF(x1, y1);
1069
1070 const qreal x2 = (-B2 - std::sqrt(D4)) / A;
1071 const qreal y2 = (T - x2 * p.x()) / p.y();
1072 result << p1 + QPointF(x2, y2);
1073 }
1074 }
1075 }
1076
1077 return result;
1078}
1079
1080boost::optional<QPointF> findTrianglePointNearest(const QPointF &p1, const QPointF &p2, qreal a, qreal b, const QPointF &nearest)
1081{
1082 const QVector<QPointF> points = findTrianglePoint(p1, p2, a, b);
1083
1084 boost::optional<QPointF> result;
1085
1086 if (points.size() == 1) {
1087 result = points.first();
1088 } else if (points.size() > 1) {
1089 result = kisDistance(points.first(), nearest) < kisDistance(points.last(), nearest) ? points.first() : points.last();
1090 }
1091
1092 return result;
1093}
1094
1095QPointF moveElasticPoint(const QPointF &pt, const QPointF &base, const QPointF &newBase, const QPointF &wingA, const QPointF &wingB)
1096{
1097 using KisAlgebra2D::norm;
1100
1101 const QPointF vecL = base - pt;
1102 const QPointF vecLa = wingA - pt;
1103 const QPointF vecLb = wingB - pt;
1104
1105 const qreal L = norm(vecL);
1106 const qreal La = norm(vecLa);
1107 const qreal Lb = norm(vecLb);
1108
1109 const qreal sinMuA = crossProduct(vecLa, vecL) / (La * L);
1110 const qreal sinMuB = crossProduct(vecL, vecLb) / (Lb * L);
1111
1112 const qreal cosMuA = dotProduct(vecLa, vecL) / (La * L);
1113 const qreal cosMuB = dotProduct(vecLb, vecL) / (Lb * L);
1114
1115 const qreal dL = dotProduct(newBase - base, -vecL) / L;
1116
1117
1118 const qreal divB = (cosMuB + La / Lb * sinMuB * cosMuA / sinMuA) / L +
1119 (cosMuB + sinMuB * cosMuA / sinMuA) / Lb;
1120 const qreal dLb = dL / ( L * divB);
1121
1122 const qreal divA = (cosMuA + Lb / La * sinMuA * cosMuB / sinMuB) / L +
1123 (cosMuA + sinMuA * cosMuB / sinMuB) / La;
1124 const qreal dLa = dL / ( L * divA);
1125
1126 boost::optional<QPointF> result =
1127 KisAlgebra2D::findTrianglePointNearest(wingA, wingB, La + dLa, Lb + dLb, pt);
1128
1129 const QPointF resultPoint = result ? *result : pt;
1130
1131 return resultPoint;
1132}
1133
1134#ifdef HAVE_GSL
1135
1136struct ElasticMotionData
1137{
1138 QPointF oldBasePos;
1139 QPointF newBasePos;
1140 QVector<QPointF> anchorPoints;
1141
1142 QPointF oldResultPoint;
1143};
1144
1145double elasticMotionError(const gsl_vector * x, void *paramsPtr)
1146{
1147 using KisAlgebra2D::norm;
1150
1151 const QPointF newResultPoint(gsl_vector_get(x, 0), gsl_vector_get(x, 1));
1152
1153 const ElasticMotionData *p = static_cast<const ElasticMotionData*>(paramsPtr);
1154
1155 const QPointF vecL = newResultPoint - p->newBasePos;
1156 const qreal L = norm(vecL);
1157
1158 const qreal deltaL = L - kisDistance(p->oldBasePos, p->oldResultPoint);
1159
1160 QVector<qreal> deltaLi;
1161 QVector<qreal> Li;
1162 QVector<qreal> cosMuI;
1163 QVector<qreal> sinMuI;
1164 Q_FOREACH (const QPointF &anchorPoint, p->anchorPoints) {
1165 const QPointF vecLi = newResultPoint - anchorPoint;
1166 const qreal _Li = norm(vecLi);
1167
1168 Li << _Li;
1169 deltaLi << _Li - kisDistance(p->oldResultPoint, anchorPoint);
1170 cosMuI << dotProduct(vecLi, vecL) / (_Li * L);
1171 sinMuI << crossProduct(vecL, vecLi) / (_Li * L);
1172 }
1173
1174 qreal finalError = 0;
1175
1176 qreal tangentialForceSum = 0;
1177 for (int i = 0; i < p->anchorPoints.size(); i++) {
1178 const qreal sum = deltaLi[i] * sinMuI[i] / Li[i];
1179 tangentialForceSum += sum;
1180 }
1181
1182 finalError += pow2(tangentialForceSum);
1183
1184 qreal normalForceSum = 0;
1185 {
1186 qreal sum = 0;
1187 for (int i = 0; i < p->anchorPoints.size(); i++) {
1188 sum += deltaLi[i] * cosMuI[i] / Li[i];
1189 }
1190 normalForceSum = (-deltaL) / L - sum;
1191 }
1192
1193 finalError += pow2(normalForceSum);
1194
1195 return finalError;
1196}
1197
1198
1199#endif /* HAVE_GSL */
1200
1201QPointF moveElasticPoint(const QPointF &pt,
1202 const QPointF &base, const QPointF &newBase,
1203 const QVector<QPointF> &anchorPoints)
1204{
1205 const QPointF offset = newBase - base;
1206
1207 QPointF newResultPoint = pt + offset;
1208
1209#ifdef HAVE_GSL
1210
1211 ElasticMotionData data;
1212 data.newBasePos = newBase;
1213 data.oldBasePos = base;
1214 data.anchorPoints = anchorPoints;
1215 data.oldResultPoint = pt;
1216
1217 const gsl_multimin_fminimizer_type *T =
1218 gsl_multimin_fminimizer_nmsimplex2;
1219 gsl_multimin_fminimizer *s = 0;
1220 gsl_vector *ss, *x;
1221 gsl_multimin_function minex_func;
1222
1223 size_t iter = 0;
1224 int status;
1225 double size;
1226
1227 /* Starting point */
1228 x = gsl_vector_alloc (2);
1229 gsl_vector_set (x, 0, newResultPoint.x());
1230 gsl_vector_set (x, 1, newResultPoint.y());
1231
1232 /* Set initial step sizes to 0.1 */
1233 ss = gsl_vector_alloc (2);
1234 gsl_vector_set (ss, 0, 0.1 * offset.x());
1235 gsl_vector_set (ss, 1, 0.1 * offset.y());
1236
1237 /* Initialize method and iterate */
1238 minex_func.n = 2;
1239 minex_func.f = elasticMotionError;
1240 minex_func.params = (void*)&data;
1241
1242 s = gsl_multimin_fminimizer_alloc (T, 2);
1243 gsl_multimin_fminimizer_set (s, &minex_func, x, ss);
1244
1245 do
1246 {
1247 iter++;
1248 status = gsl_multimin_fminimizer_iterate(s);
1249
1250 if (status)
1251 break;
1252
1253 size = gsl_multimin_fminimizer_size (s);
1254 status = gsl_multimin_test_size (size, 1e-6);
1255
1261 if (status == GSL_SUCCESS && elasticMotionError(s->x, &data) > 0.5) {
1262 status = GSL_CONTINUE;
1263 }
1264
1265 if (status == GSL_SUCCESS)
1266 {
1267// qDebug() << "*******Converged to minimum";
1268// qDebug() << gsl_vector_get (s->x, 0)
1269// << gsl_vector_get (s->x, 1)
1270// << "|" << s->fval << size;
1271// qDebug() << ppVar(iter);
1272
1273 newResultPoint.rx() = gsl_vector_get (s->x, 0);
1274 newResultPoint.ry() = gsl_vector_get (s->x, 1);
1275 }
1276 }
1277 while (status == GSL_CONTINUE && iter < 10000);
1278
1279 if (status != GSL_SUCCESS) {
1280 ENTER_FUNCTION() << "failed to find point" << ppVar(pt) << ppVar(base) << ppVar(newBase);
1281 }
1282
1283 gsl_vector_free(x);
1284 gsl_vector_free(ss);
1285 gsl_multimin_fminimizer_free (s);
1286#endif
1287
1288 return newResultPoint;
1289}
1290
1291void cropLineToRect(QLineF &line, const QRect rect, bool extendFirst, bool extendSecond)
1292{
1293 bool intersects = intersectLineRect(line, rect, extendFirst, extendSecond);
1294 if (!intersects) {
1295 line = QLineF(); // empty line to help with drawing
1296 }
1297}
1298
1299void cropLineToConvexPolygon(QLineF &line, const QPolygonF polygon, bool extendFirst, bool extendSecond)
1300{
1301 bool intersects = intersectLineConvexPolygon(line, polygon, extendFirst, extendSecond);
1302 if (!intersects) {
1303 line = QLineF(); // empty line to help with drawing
1304 }
1305}
1306
1307
1308qreal findMinimumGoldenSection(std::function<qreal(qreal)> f, qreal xA, qreal xB, qreal eps, int maxIter = 100)
1309{
1310 // requirements:
1311 // only one local minimum between xA and xB
1312
1313 int i = 0;
1314 const double phi = 0.618033988749894; // 1/(golden ratio)
1315 qreal a = xA;
1316 qreal b = xB;
1317 qreal c = b - (b - a)*phi;
1318 qreal d = a + (b - a)*phi;
1319
1320 while (qAbs(b - a) > eps) {
1321 if (f(c) < f(d)) {
1322 b = d;
1323 } else {
1324 a = c;
1325 }
1326
1327 // Wikipedia says to recompute both c and d here to avoid loss of precision which may lead to incorrect results or infinite loop
1328 c = b - (b - a) * phi;
1329 d = a + (b - a) * phi;
1330
1331 i++;
1332 if (i > maxIter) {
1333 break;
1334 }
1335
1336 }
1337
1338 return (b + a)/2;
1339}
1340
1341qreal findMinimumTernarySection(std::function<qreal(qreal)> f, qreal xA, qreal xB, qreal eps, int maxIter = 100)
1342{
1343 // requirements:
1344 // only one local minimum between xA and xB
1345
1346 int i = 0;
1347 qreal l = qMin(xA, xB);
1348 qreal r = qMax(xA, xB);
1349
1350
1351 qreal m1 = l + (r - l)/3;
1352 qreal m2 = r - (r - l)/3;
1353
1354 while ((r - l) > eps) {
1355 qreal f1 = f(m1);
1356 qreal f2 = f(m2);
1357
1358 if (f1 > f2) {
1359 l = m1;
1360 } else {
1361 r = m2;
1362 }
1363
1364 // In Golden Section, Wikipedia says to recompute both c and d here to avoid loss of precision which may lead to incorrect results or infinite loop
1365 // so it's probably a good idea to do it here too
1366 m1 = l + (r - l)/3;
1367 m2 = r - (r - l)/3;
1368
1369 i++;
1370
1371 if (i > maxIter) {
1372 break;
1373 }
1374 }
1375
1376 return (l + r)/2;
1377}
1378
1379qreal pointToLineDistSquared(const QPointF &pt, const QLineF &line)
1380{
1381 // distance = |(p2 - p1) x (p1 - pt)| / |p2 - p1|
1382
1383 // magnitude of (p2 - p1) x (p1 - pt)
1384 const qreal cross = (line.dx() * (line.y1() - pt.y()) - line.dy() * (line.x1() - pt.x()));
1385
1386 return cross * cross / (line.dx() * line.dx() + line.dy() * line.dy());
1387}
1388
1389}
QPointF r2
QPointF q1
const Params2D p
QPointF p0
QPointF r1
QPointF p2
QPointF q2
QPointF p3
QPointF p1
static qreal B2(qreal u)
static qreal B1(qreal u)
qreal D(qreal t, const QPointF &P0, const QPointF &P1, const QPointF &P2, const QPointF &P3, const QPointF &p)
#define C(i, j)
static bool qFuzzyCompare(half p1, half p2)
static bool qFuzzyIsNull(half h)
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
#define KIS_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:97
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
#define KIS_ASSERT(cond)
Definition kis_assert.h:33
#define bounds(x, a, b)
const qreal eps
#define dbgKrita
Definition kis_debug.h:45
#define ENTER_FUNCTION()
Definition kis_debug.h:178
#define ppVar(var)
Definition kis_debug.h:155
qreal kisDistance(const QPointF &pt1, const QPointF &pt2)
Definition kis_global.h:190
T kisRadiansToDegrees(T radians)
Definition kis_global.h:181
T pow2(const T &x)
Definition kis_global.h:166
qreal kisDistanceToLine(const QPointF &m, const QLineF &line)
Definition kis_global.h:234
boost::optional< QPointF > findTrianglePointNearest(const QPointF &p1, const QPointF &p2, qreal a, qreal b, const QPointF &nearest)
QVector< QPointF > intersectTwoCircles(const QPointF &center1, qreal r1, const QPointF &center2, qreal r2)
Point ensureInRectImpl(Point pt, const Rect &bounds)
qreal findMinimumTernarySection(std::function< qreal(qreal)> f, qreal xA, qreal xB, qreal eps, int maxIter=100)
QTransform toQTransformStraight(const Eigen::Matrix3d &m)
QVector< Point > sampleRectWithPoints(const Rect &rect)
QRect approximateRectFromPoints(const QVector< QPoint > &points)
void adjustIfOnPolygonBoundary(const QPolygonF &poly, int polygonDirection, QPointF *pt)
std::pair< QPointF, QTransform > transformEllipse(const QPointF &axes, const QTransform &fullLocalToGlobal)
bool fuzzyMatrixCompare(const QTransform &t1, const QTransform &t2, qreal delta)
qreal directionBetweenPoints(const QPointF &p1, const QPointF &p2, qreal defaultAngle)
void cropLineToConvexPolygon(QLineF &line, const QPolygonF polygon, bool extendFirst, bool extendSecond)
QTransform mapToRectInverse(const QRectF &rect)
QPointF moveElasticPoint(const QPointF &pt, const QPointF &base, const QPointF &newBase, const QPointF &wingA, const QPointF &wingB)
moveElasticPoint moves point pt based on the model of elasticity
qreal angleBetweenVectors(const QPointF &v1, const QPointF &v2)
qreal pointToLineDistSquared(const QPointF &pt, const QLineF &line)
QPoint ensureInRect(QPoint pt, const QRect &bounds)
int quadraticEquation(qreal a, qreal b, qreal c, qreal *x1, qreal *x2)
QPointF alignForZoom(const QPointF &pt, qreal zoom)
void cropLineToRect(QLineF &line, const QRect rect, bool extendFirst, bool extendSecond)
bool isInRange(T x, T a, T b)
QPainterPath smallArrow()
qreal norm(const T &a)
QVector< QPointF > findTrianglePoint(const QPointF &p1, const QPointF &p2, qreal a, qreal b)
PointTypeTraits< T >::value_type dotProduct(const T &a, const T &b)
QRectF cutOffRect(const QRectF &rc, const KisAlgebra2D::RightHalfPlane &p)
boost::optional< QPointF > intersectLines(const QLineF &boundedLine, const QLineF &unboundedLine)
PointTypeTraits< T >::value_type crossProduct(const T &a, const T &b)
QPointF transformAsBase(const QPointF &pt, const QPointF &base1, const QPointF &base2)
int polygonDirection(const QVector< T > &polygon)
T inwardUnitNormal(const T &a, int polygonDirection)
Rect approximateRectFromPointsImpl(const QVector< Point > &points)
Eigen::Matrix3d fromQTransformStraight(const QTransform &t)
bool intersectLineConvexPolygon(QLineF &line, const QPolygonF polygon, bool extendFirst, bool extendSecond)
QRect approximateRectWithPointTransform(const QRect &rect, std::function< QPointF(QPointF)> func)
bool fuzzyPointCompare(const QPointF &p1, const QPointF &p2)
bool intersectLineRect(QLineF &line, const QRect rect, bool extend)
QTransform mapToRect(const QRectF &rect)
qreal findMinimumGoldenSection(std::function< qreal(qreal)> f, qreal xA, qreal xB, qreal eps, int maxIter=100)
const unsigned char salt[256][256]
Definition rand_salt.h:30
QTransform shearTransform() const
QTransform rotateTransform() const
QTransform scaleTransform() const
QTransform translateTransform() const