Krita Source Code Documentation
Loading...
Searching...
No Matches
KisBezierUtils Namespace Reference

Classes

class  BezierSegment
 
struct  Range
 

Functions

QPointF bezierCurve (const QList< QPointF > &points, qreal t)
 
QPointF bezierCurve (const QPointF p0, const QPointF p1, const QPointF p2, const QPointF p3, qreal t)
 
QPointF bezierCurveDeriv (const QPointF p0, const QPointF p1, const QPointF p2, const QPointF p3, qreal t)
 
QPointF bezierCurveDeriv2 (const QPointF p0, const QPointF p1, const QPointF p2, const QPointF p3, qreal t)
 
int bezierDegree (const QPointF p0, const QPointF p1, const QPointF p2, const QPointF p3)
 
template<typename Func >
std::pair< Range, RangecalcTightSrcRectRangeInParamSpace1D (const Range &searchParamRange, const Range &searchSrcRange, const Range &rect, Func func, qreal srcPrecision, std::optional< Range > squeezeRange=std::nullopt)
 
QPointF calculateGlobalPos (const std::array< QPointF, 12 > &points, const QPointF &localPoint)
 calculates global coordinate corresponding to the patch coordinate (u, v)
 
template<class PatchMethod >
QPointF calculateGlobalPosImpl (const std::array< QPointF, 12 > &points, const QPointF &localPoint)
 
QPointF calculateGlobalPosSVG2 (const std::array< QPointF, 12 > &points, const QPointF &localPoint)
 calculates global coordinate corresponding to the patch coordinate (u, v)
 
QPointF calculateLocalPos (const std::array< QPointF, 12 > &points, const QPointF &globalPoint)
 calculates local (u,v) coordinates of the patch corresponding to globalPoint
 
template<class PatchMethod >
QPointF calculateLocalPosImpl (const std::array< QPointF, 12 > &points, const QPointF &globalPoint)
 
QPointF calculateLocalPosSVG2 (const std::array< QPointF, 12 > &points, const QPointF &globalPoint)
 calculates local (u,v) coordinates of the patch corresponding to globalPoint
 
int controlPolygonZeros (const QList< QPointF > &controlPoints)
 
qreal curveLength (const QPointF p0, const QPointF p1, const QPointF p2, const QPointF p3, const qreal error)
 
qreal curveLengthAtPoint (const QPointF p0, const QPointF p1, const QPointF p2, const QPointF p3, qreal t, const qreal error)
 
qreal curveParamByProportion (const QPointF p0, const QPointF p1, const QPointF p2, const QPointF p3, qreal proportion, const qreal error)
 
qreal curveParamBySegmentLength (const QPointF p0, const QPointF p1, const QPointF p2, const QPointF p3, qreal expectedLength, qreal totalLength, const qreal error)
 
qreal curveProportionByParam (const QPointF p0, const QPointF p1, const QPointF p2, const QPointF p3, qreal t, const qreal error)
 
void deCasteljau (const QPointF &q0, const QPointF &q1, const QPointF &q2, const QPointF &q3, qreal t, QPointF *p0, QPointF *p1, QPointF *p2, QPointF *p3, QPointF *p4)
 
QPointF interpolateQuadric (const QPointF &p0, const QPointF &p2, const QPointF &pt, qreal t)
 Interpolates quadric curve passing through given points.
 
QVector< qreal > intersectWithLine (const QPointF &p0, const QPointF &p1, const QPointF &p2, const QPointF &p3, const QLineF &line, qreal eps)
 
QVector< qreal > intersectWithLineImpl (const QPointF &p0, const QPointF &p1, const QPointF &p2, const QPointF &p3, const QLineF &line, qreal eps, qreal alpha, qreal beta)
 
boost::optional< qreal > intersectWithLineNearest (const QPointF &p0, const QPointF &p1, const QPointF &p2, const QPointF &p3, const QLineF &line, const QPointF &nearestAnchor, qreal eps)
 
bool isLinearSegmentByControlPoints (const QPointF &p0, const QPointF &p1, const QPointF &p2, const QPointF &p3, const qreal eps=1e-4)
 
bool isLinearSegmentByDerivatives (const QPointF &p0, const QPointF &d0, const QPointF &p1, const QPointF &d1, const qreal eps=1e-4)
 
QVector< qreal > linearizeCurve (const QPointF p0, const QPointF p1, const QPointF p2, const QPointF p3, const qreal eps)
 
QVector< qreal > mergeLinearizationSteps (const QVector< qreal > &a, const QVector< qreal > &b)
 
qreal nearestPoint (const QList< QPointF > controlPoints, const QPointF &point, qreal *resultDistance, QPointF *resultPoint)
 
std::pair< QPointF, QPointF > offsetSegment (qreal t, const QPointF &offset)
 moves point t of the curve by offset offset
 
QDebug operator<< (QDebug dbg, const Range &r)
 
std::pair< QPointF, QPointF > removeBezierNode (const QPointF &p0, const QPointF &p1, const QPointF &p2, const QPointF &p3, const QPointF &q1, const QPointF &q2, const QPointF &q3)
 Adjusts position for the bezier control points after removing a node.
 

Function Documentation

◆ bezierCurve() [1/2]

QPointF KisBezierUtils::bezierCurve ( const QList< QPointF > & points,
qreal t )
inline

Definition at line 113 of file KisBezierUtils.h.

115{
116 QPointF result;
117
118 if (points.size() == 2) {
119 result = lerp(points.first(), points.last(), t);
120 } else if (points.size() == 3) {
121 result = bezierCurve(points[0], points[1], points[1], points[2], t);
122 } else if (points.size() == 4) {
123 result = bezierCurve(points[0], points[1], points[2], points[3], t);
124 } else {
125 KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "Unsupported number of bezier control points");
126 }
127
128 return result;
129}
QPointF lerp(const QPointF &p1, const QPointF &p2, qreal t)
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
QPointF bezierCurve(const QPointF p0, const QPointF p1, const QPointF p2, const QPointF p3, qreal t)

References bezierCurve(), KIS_SAFE_ASSERT_RECOVER_NOOP, and lerp().

◆ bezierCurve() [2/2]

QPointF KisBezierUtils::bezierCurve ( const QPointF p0,
const QPointF p1,
const QPointF p2,
const QPointF p3,
qreal t )
inline

Definition at line 88 of file KisBezierUtils.h.

93{
94#if 0
95 const qreal t_2 = pow2(t);
96 const qreal t_3 = t_2 * t;
97 const qreal t_inv = 1.0 - t;
98 const qreal t_inv_2 = pow2(t_inv);
99 const qreal t_inv_3 = t_inv_2 * t_inv;
100
101 return
102 t_inv_3 * p0 +
103 3 * t_inv_2 * t * p1 +
104 3 * t_inv * t_2 * p2 +
105 t_3 * p3;
106#else
107 QPointF q0, q1, q2, q3, q4;
108 deCasteljau(p0, p1, p2, p3, t, &q0, &q1, &q2, &q3, &q4);
109 return q2;
110#endif
111}
QPointF q1
QPointF q0
QPointF p0
QPointF q3
QPointF p2
QPointF q2
QPointF p3
QPointF p1
void deCasteljau(const std::array< QPointF, 4 > &points, qreal t, QPointF *p1, QPointF *p2, QPointF *p3, QPointF *p4, QPointF *p5)
T pow2(const T &x)
Definition kis_global.h:166

References deCasteljau(), p0, p1, p2, p3, pow2(), q0, q1, q2, and q3.

◆ bezierCurveDeriv()

QPointF KisBezierUtils::bezierCurveDeriv ( const QPointF p0,
const QPointF p1,
const QPointF p2,
const QPointF p3,
qreal t )
inline

Definition at line 22 of file KisBezierUtils.h.

27{
28 const qreal t_2 = pow2(t);
29 const qreal t_inv = 1.0 - t;
30 const qreal t_inv_2 = pow2(t_inv);
31
32 return
33 3 * t_inv_2 * (p1 - p0) +
34 6 * t_inv * t * (p2 - p1) +
35 3 * t_2 * (p3 - p2);
36}

References p0, p1, p2, p3, and pow2().

◆ bezierCurveDeriv2()

QPointF KisBezierUtils::bezierCurveDeriv2 ( const QPointF p0,
const QPointF p1,
const QPointF p2,
const QPointF p3,
qreal t )
inline

Definition at line 38 of file KisBezierUtils.h.

43{
44 const qreal t_inv = 1.0 - t;
45
46 return
47 6 * t_inv * (p2 - 2 * p1 + p0) +
48 6 * t * (p3 - 2 * p2 + p1);
49}

References p0, p1, p2, and p3.

◆ bezierDegree()

int KisBezierUtils::bezierDegree ( const QPointF p0,
const QPointF p1,
const QPointF p2,
const QPointF p3 )
inline

Definition at line 158 of file KisBezierUtils.h.

162{
163 const qreal eps = 1e-4;
164
165 int degree = 3;
166
168 degree = 1;
170 degree = 2;
171 }
172
173 return degree;
174}
const qreal eps
bool fuzzyPointCompare(const QPointF &p1, const QPointF &p2)
bool isLinearSegmentByControlPoints(const QPointF &p0, const QPointF &p1, const QPointF &p2, const QPointF &p3, const qreal eps=1e-4)

References eps, KisAlgebra2D::fuzzyPointCompare(), isLinearSegmentByControlPoints(), p0, p1, p2, and p3.

◆ calcTightSrcRectRangeInParamSpace1D()

template<typename Func >
std::pair< Range, Range > KisBezierUtils::calcTightSrcRectRangeInParamSpace1D ( const Range & searchParamRange,
const Range & searchSrcRange,
const Range & rect,
Func func,
qreal srcPrecision,
std::optional< Range > squeezeRange = std::nullopt )

Approximate the rect in param-space of the bezier patch that fully covers passed rect in the source-space. Due to the nature of the patch mapping, the result cannot be calculated precisely (easily). The passed srcPresition is only meant to be used for approximation algorithm termination. There is no guarantee that the result covers rect with this precision.

The function uses a simple binary search approach to estimate the borders of the rectangle in question.

To get better precision use iterational approach by combining X- and Y-axis approximation using searchParamRange and squeezeRange.

Parameters
searchParamRangethe range in param-space where the passed rect is searched for. The rect must be guaranteed to be present in this range, otherwise the behavior is undeifned. When doing iterational precision search, pass externalRange from the previous pass here.
searchSrcRangethe range in source-space, where the rect is placed. It is only used to check is the rect is placed exactly at the border of that range.
rectrect in source-space that is searched for
functhe functor that maps a param in the param-space into a range in the source space
srcPrecisionsrc-space precision at which the search is stopped
squeezeRangewhen doing iterational search, pass the opposite-axis range of the rectangle
Returns
a pair of {externalRange, internalRange} which "covers" and "is covered" by the rectangle correspondingly

Definition at line 135 of file KisBezierPatchParamSpaceUtils.h.

141{
142 Range leftSideParamRange = searchParamRange;
143 Range rightSideParamRange = searchParamRange;
144
145 if (qFuzzyCompare(rect.start, searchSrcRange.start)) {
146 leftSideParamRange = {searchParamRange.start, searchParamRange.start};
147 } else {
148 // search left side
149 while (1) {
150 KIS_SAFE_ASSERT_RECOVER_BREAK(!leftSideParamRange.isEmpty());
151
152 qreal currentSplitParam = leftSideParamRange.mid();
153 Range currentSplitSrcRange = func(currentSplitParam);
154 if (squeezeRange) {
155 currentSplitSrcRange.squeezeRange(*squeezeRange);
156 }
157
158 std::optional<qreal> forwardDistance = currentSplitSrcRange.forwardDistanceTo(rect);
159
160 if (!forwardDistance || qFuzzyIsNull(*forwardDistance)) {
161 leftSideParamRange.end = currentSplitParam;
162 rightSideParamRange.start = std::max(currentSplitParam, rightSideParamRange.start);
163 } else if (*forwardDistance > 0) {
164 KIS_SAFE_ASSERT_RECOVER_NOOP(currentSplitParam >= leftSideParamRange.start);
165 leftSideParamRange.start = currentSplitParam;
166 rightSideParamRange.start = std::max(currentSplitParam, rightSideParamRange.start);
167 } else if (*forwardDistance < 0) {
168 KIS_SAFE_ASSERT_RECOVER_NOOP(currentSplitParam <= rightSideParamRange.end);
169 rightSideParamRange.end = currentSplitParam;
170 leftSideParamRange.end = currentSplitParam;
171 }
172
173 if (forwardDistance && std::abs(*forwardDistance) < srcPrecision) {
174 break;
175 }
176 }
177 }
178
179 if (qFuzzyCompare(rect.end, searchSrcRange.end)) {
180 rightSideParamRange = {searchParamRange.end, searchParamRange.end};
181 } else {
182 // search right side
183 while (1) {
184 KIS_SAFE_ASSERT_RECOVER_BREAK(!rightSideParamRange.isEmpty());
185
186 qreal currentSplitParam = rightSideParamRange.mid();
187 Range currentSplitSrcRange = func(currentSplitParam);
188
189 if (squeezeRange) {
190 currentSplitSrcRange.squeezeRange(*squeezeRange);
191 }
192
193 std::optional<qreal> forwardDistance = currentSplitSrcRange.forwardDistanceTo(rect);
194
195 if (!forwardDistance || qFuzzyIsNull(*forwardDistance)) {
196 rightSideParamRange.start = currentSplitParam;
197 } else if (*forwardDistance > 0) {
198 rightSideParamRange.start = currentSplitParam;
199 } else if (*forwardDistance < 0) {
200 rightSideParamRange.end = currentSplitParam;
201 }
202
203 if (forwardDistance && std::abs(*forwardDistance) < srcPrecision) {
204 break;
205 }
206 }
207 }
208
209 return std::make_pair(Range{leftSideParamRange.start, rightSideParamRange.end},
210 Range{leftSideParamRange.end, rightSideParamRange.start});
211}
static bool qFuzzyCompare(half p1, half p2)
static bool qFuzzyIsNull(half h)
#define KIS_SAFE_ASSERT_RECOVER_BREAK(cond)
Definition kis_assert.h:127

References KisBezierUtils::Range::end, KisBezierUtils::Range::forwardDistanceTo(), KisBezierUtils::Range::isEmpty(), KIS_SAFE_ASSERT_RECOVER_BREAK, KIS_SAFE_ASSERT_RECOVER_NOOP, KisBezierUtils::Range::mid(), qFuzzyCompare(), qFuzzyIsNull(), KisBezierUtils::Range::squeezeRange(), and KisBezierUtils::Range::start.

◆ calculateGlobalPos()

KRITAGLOBAL_EXPORT QPointF KisBezierUtils::calculateGlobalPos ( const std::array< QPointF, 12 > & points,
const QPointF & localPoint )

calculates global coordinate corresponding to the patch coordinate (u, v)

The function uses Krita's own level-based patch interpolation algorithm

Parameters
pointscontrol points as laid out in KisBezierPatch
localPointpoint in local coordinates
Returns
point in global coordinates

Definition at line 915 of file KisBezierUtils.cpp.

916{
917 return calculateGlobalPosImpl<LevelBasedPatchMethod>(points, localPoint);
918}

◆ calculateGlobalPosImpl()

template<class PatchMethod >
QPointF KisBezierUtils::calculateGlobalPosImpl ( const std::array< QPointF, 12 > & points,
const QPointF & localPoint )

Definition at line 882 of file KisBezierUtils.cpp.

883{
884 Params2D p;
885
886 p.p0 = points[KisBezierPatch::TL];
887 p.p1 = points[KisBezierPatch::TL_HC];
888 p.p2 = points[KisBezierPatch::TR_HC];
889 p.p3 = points[KisBezierPatch::TR];
890
891 p.q0 = points[KisBezierPatch::BL];
892 p.q1 = points[KisBezierPatch::BL_HC];
893 p.q2 = points[KisBezierPatch::BR_HC];
894 p.q3 = points[KisBezierPatch::BR];
895
896 p.r0 = points[KisBezierPatch::TL];
897 p.r1 = points[KisBezierPatch::TL_VC];
898 p.r2 = points[KisBezierPatch::BL_VC];
899 p.r3 = points[KisBezierPatch::BL];
900
901 p.s0 = points[KisBezierPatch::TR];
902 p.s1 = points[KisBezierPatch::TR_VC];
903 p.s2 = points[KisBezierPatch::BR_VC];
904 p.s3 = points[KisBezierPatch::BR];
905
906 PatchMethod f(localPoint.x(), localPoint.y(), p);
907 return f.value();
908}
const Params2D p

References KisBezierPatch::BL, KisBezierPatch::BL_HC, KisBezierPatch::BL_VC, KisBezierPatch::BR, KisBezierPatch::BR_HC, KisBezierPatch::BR_VC, p, KisBezierPatch::TL, KisBezierPatch::TL_HC, KisBezierPatch::TL_VC, KisBezierPatch::TR, KisBezierPatch::TR_HC, and KisBezierPatch::TR_VC.

◆ calculateGlobalPosSVG2()

KRITAGLOBAL_EXPORT QPointF KisBezierUtils::calculateGlobalPosSVG2 ( const std::array< QPointF, 12 > & points,
const QPointF & localPoint )

calculates global coordinate corresponding to the patch coordinate (u, v)

The function uses SVG2 toon patches algorithm

Parameters
pointscontrol points as laid out in KisBezierPatch
localPointpoint in local coordinates
Returns
point in global coordinates

Definition at line 925 of file KisBezierUtils.cpp.

926{
927 return calculateGlobalPosImpl<SvgPatchMethod>(points, localPoint);
928}

◆ calculateLocalPos()

KRITAGLOBAL_EXPORT QPointF KisBezierUtils::calculateLocalPos ( const std::array< QPointF, 12 > & points,
const QPointF & globalPoint )

calculates local (u,v) coordinates of the patch corresponding to globalPoint

The function uses Krita's own level-based patch interpolation algorithm

Parameters
pointscontrol points as laid out in KisBezierPatch
globalPointpoint in global coordinates
Returns
point in local coordinates

Definition at line 910 of file KisBezierUtils.cpp.

911{
912 return calculateLocalPosImpl<LevelBasedPatchMethod>(points, globalPoint);
913}

◆ calculateLocalPosImpl()

template<class PatchMethod >
QPointF KisBezierUtils::calculateLocalPosImpl ( const std::array< QPointF, 12 > & points,
const QPointF & globalPoint )

Definition at line 780 of file KisBezierUtils.cpp.

781{
782 QRectF patchBounds;
783
784 for (auto it = points.begin(); it != points.end(); ++it) {
785 KisAlgebra2D::accumulateBounds(*it, &patchBounds);
786 }
787
788 const QPointF approxStart = KisAlgebra2D::absoluteToRelative(globalPoint, patchBounds);
789 KIS_SAFE_ASSERT_RECOVER_NOOP(QRectF(0,0,1.0,1.0).contains(approxStart));
790
791#ifdef HAVE_GSL
792 const gsl_multimin_fdfminimizer_type *T =
793 gsl_multimin_fdfminimizer_vector_bfgs2;
794 gsl_multimin_fdfminimizer *s = 0;
795 gsl_vector *x;
796 gsl_multimin_function_fdf minex_func;
797
798 size_t iter = 0;
799 int status;
800
801 /* Starting point */
802 x = gsl_vector_alloc (2);
803 gsl_vector_set (x, 0, approxStart.x());
804 gsl_vector_set (x, 1, approxStart.y());
805
806 Params2D p;
807
808 p.p0 = points[KisBezierPatch::TL];
809 p.p1 = points[KisBezierPatch::TL_HC];
810 p.p2 = points[KisBezierPatch::TR_HC];
811 p.p3 = points[KisBezierPatch::TR];
812
813 p.q0 = points[KisBezierPatch::BL];
814 p.q1 = points[KisBezierPatch::BL_HC];
815 p.q2 = points[KisBezierPatch::BR_HC];
816 p.q3 = points[KisBezierPatch::BR];
817
818 p.r0 = points[KisBezierPatch::TL];
819 p.r1 = points[KisBezierPatch::TL_VC];
820 p.r2 = points[KisBezierPatch::BL_VC];
821 p.r3 = points[KisBezierPatch::BL];
822
823 p.s0 = points[KisBezierPatch::TR];
824 p.s1 = points[KisBezierPatch::TR_VC];
825 p.s2 = points[KisBezierPatch::BR_VC];
826 p.s3 = points[KisBezierPatch::BR];
827
828 p.dstPoint = globalPoint;
829
830 /* Initialize method and iterate */
831 minex_func.n = 2;
832 minex_func.f = my_f<PatchMethod>;
833 minex_func.params = (void*)&p;
834 minex_func.df = my_df<PatchMethod>;
835 minex_func.fdf = my_fdf<PatchMethod>;
836
837 s = gsl_multimin_fdfminimizer_alloc (T, 2);
838 gsl_multimin_fdfminimizer_set (s, &minex_func, x, 0.01, 0.1);
839
840 QPointF result;
841
842
843 result.rx() = gsl_vector_get (s->x, 0);
844 result.ry() = gsl_vector_get (s->x, 1);
845
846 do
847 {
848 iter++;
849 status = gsl_multimin_fdfminimizer_iterate(s);
850
851 if (status)
852 break;
853
854 status = gsl_multimin_test_gradient (s->gradient, 1e-4);
855
856 result.rx() = gsl_vector_get (s->x, 0);
857 result.ry() = gsl_vector_get (s->x, 1);
858
859 if (status == GSL_SUCCESS)
860 {
861 result.rx() = gsl_vector_get (s->x, 0);
862 result.ry() = gsl_vector_get (s->x, 1);
863 //qDebug() << "******* Converged to minimum" << ppVar(result);
864
865 }
866 }
867 while (status == GSL_CONTINUE && iter < 10000);
868
869// ENTER_FUNCTION()<< ppVar(iter) << ppVar(globalPoint) << ppVar(result);
870// ENTER_FUNCTION() << ppVar(meshForwardMapping(result.x(), result.y(), p));
871
872 gsl_vector_free(x);
873 gsl_multimin_fdfminimizer_free (s);
874
875 return result;
876#else
877 return approxStart;
878#endif
879}
QPointF absoluteToRelative(const QPointF &pt, const QRectF &rc)
void accumulateBounds(const Point &pt, Rect *bounds)

References KisAlgebra2D::absoluteToRelative(), KisAlgebra2D::accumulateBounds(), KisBezierPatch::BL, KisBezierPatch::BL_HC, KisBezierPatch::BL_VC, KisBezierPatch::BR, KisBezierPatch::BR_HC, KisBezierPatch::BR_VC, KIS_SAFE_ASSERT_RECOVER_NOOP, p, KisBezierPatch::TL, KisBezierPatch::TL_HC, KisBezierPatch::TL_VC, KisBezierPatch::TR, KisBezierPatch::TR_HC, and KisBezierPatch::TR_VC.

◆ calculateLocalPosSVG2()

KRITAGLOBAL_EXPORT QPointF KisBezierUtils::calculateLocalPosSVG2 ( const std::array< QPointF, 12 > & points,
const QPointF & globalPoint )

calculates local (u,v) coordinates of the patch corresponding to globalPoint

The function uses SVG2 toon patches algorithm

Parameters
pointscontrol points as laid out in KisBezierPatch
globalPointpoint in global coordinates
Returns
point in local coordinates

Definition at line 920 of file KisBezierUtils.cpp.

921{
922 return calculateLocalPosImpl<SvgPatchMethod>(points, globalPoint);
923}

◆ controlPolygonZeros()

KRITAGLOBAL_EXPORT int KisBezierUtils::controlPolygonZeros ( const QList< QPointF > & controlPoints)

Definition at line 491 of file KisBezierUtils.cpp.

492{
493 return static_cast<int>(BezierSegment::controlPolygonZeros(controlPoints));
494}

References KisBezierUtils::BezierSegment::controlPolygonZeros().

◆ curveLength()

KRITAGLOBAL_EXPORT qreal KisBezierUtils::curveLength ( const QPointF p0,
const QPointF p1,
const QPointF p2,
const QPointF p3,
const qreal error )

Definition at line 980 of file KisBezierUtils.cpp.

981{
982 /*
983 * This algorithm is implemented based on an idea by Jens Gravesen:
984 * "Adaptive subdivision and the length of Bezier curves" mat-report no. 1992-10, Mathematical Institute,
985 * The Technical University of Denmark.
986 *
987 * By subdividing the curve at parameter value t you only have to find the length of a full Bezier curve.
988 * If you denote the length of the control polygon by L1 i.e.:
989 * L1 = |P0 P1| +|P1 P2| +|P2 P3|
990 *
991 * and the length of the cord by L0 i.e.:
992 * L0 = |P0 P3|
993 *
994 * then
995 * L = 1/2*L0 + 1/2*L1
996 *
997 * is a good approximation to the length of the curve, and the difference
998 * ERR = L1-L0
999 *
1000 * is a measure of the error. If the error is to large, then you just subdivide curve at parameter value
1001 * 1/2, and find the length of each half.
1002 * If m is the number of subdivisions then the error goes to zero as 2^-4m.
1003 * If you don't have a cubic curve but a curve of degree n then you put
1004 * L = (2*L0 + (n-1)*L1)/(n+1)
1005 */
1006
1007 const int deg = bezierDegree(p0, p1, p2, p3);
1008
1009 if (deg == -1)
1010 return 0.0;
1011
1012 // calculate chord length
1013 const qreal chordLen = kisDistance(p0, p3);
1014
1015 if (deg == 1) {
1016 return chordLen;
1017 }
1018
1019 // calculate length of control polygon
1020 qreal polyLength = 0.0;
1021
1022 polyLength += kisDistance(p0, p1);
1023 polyLength += kisDistance(p1, p2);
1024 polyLength += kisDistance(p2, p3);
1025
1026 if ((polyLength - chordLen) > error) {
1027 QPointF q0, q1, q2, q3, q4;
1028 deCasteljau(p0, p1, p2, p3, 0.5, &q0, &q1, &q2, &q3, &q4);
1029
1030 return curveLength(p0, q0, q1, q2, error) +
1031 curveLength(q2, q3, q4, p3, error);
1032 } else {
1033 // the error is smaller than our tolerance
1034 if (deg == 3)
1035 return 0.5 * chordLen + 0.5 * polyLength;
1036 else
1037 return (2.0 * chordLen + polyLength) / 3.0;
1038 }
1039}
qreal kisDistance(const QPointF &pt1, const QPointF &pt2)
Definition kis_global.h:190
int bezierDegree(const QPointF p0, const QPointF p1, const QPointF p2, const QPointF p3)

References bezierDegree(), curveLength(), deCasteljau(), kisDistance(), p0, p1, p2, p3, q0, q1, q2, and q3.

◆ curveLengthAtPoint()

KRITAGLOBAL_EXPORT qreal KisBezierUtils::curveLengthAtPoint ( const QPointF p0,
const QPointF p1,
const QPointF p2,
const QPointF p3,
qreal t,
const qreal error )

Definition at line 1041 of file KisBezierUtils.cpp.

1042{
1043 QPointF q0, q1, q2, q3, q4;
1044 deCasteljau(p0, p1, p2, p3, t, &q0, &q1, &q2, &q3, &q4);
1045
1046 return curveLength(p0, q0, q1, q2, error);
1047}
qreal curveLength(const QPointF p0, const QPointF p1, const QPointF p2, const QPointF p3, const qreal error)

References curveLength(), deCasteljau(), p0, p1, p2, p3, q0, q1, q2, and q3.

◆ curveParamByProportion()

KRITAGLOBAL_EXPORT qreal KisBezierUtils::curveParamByProportion ( const QPointF p0,
const QPointF p1,
const QPointF p2,
const QPointF p3,
qreal proportion,
const qreal error )

Definition at line 1068 of file KisBezierUtils.cpp.

1069{
1070 const qreal totalLength = curveLength(p0, p1, p2, p3, error);
1071 const qreal expectedLength = proportion * totalLength;
1072
1073 return curveParamBySegmentLength(p0, p1, p2, p3, expectedLength, totalLength, error);
1074}
qreal curveParamBySegmentLength(const QPointF p0, const QPointF p1, const QPointF p2, const QPointF p3, qreal expectedLength, qreal totalLength, const qreal error)

References curveLength(), curveParamBySegmentLength(), p0, p1, p2, and p3.

◆ curveParamBySegmentLength()

qreal KisBezierUtils::curveParamBySegmentLength ( const QPointF p0,
const QPointF p1,
const QPointF p2,
const QPointF p3,
qreal expectedLength,
qreal totalLength,
const qreal error )

Definition at line 1049 of file KisBezierUtils.cpp.

1050{
1051 const qreal splitAtParam = expectedLength / totalLength;
1052
1053 QPointF q0, q1, q2, q3, q4;
1054 deCasteljau(p0, p1, p2, p3, splitAtParam, &q0, &q1, &q2, &q3, &q4);
1055
1056 const qreal portionLength = curveLength(p0, q0, q1, q2, error);
1057
1058 if (std::abs(portionLength - expectedLength) < error) {
1059 return splitAtParam;
1060 } else if (portionLength < expectedLength) {
1061 return splitAtParam + (1.0 - splitAtParam) * curveParamBySegmentLength(q2, q3, q4, p3, expectedLength - portionLength, totalLength - portionLength, error);
1062 } else {
1063 return splitAtParam * curveParamBySegmentLength(p0, q0, q1, q2, expectedLength, portionLength, error);
1064 }
1065}

References curveLength(), curveParamBySegmentLength(), deCasteljau(), p0, p1, p2, p3, q0, q1, q2, and q3.

◆ curveProportionByParam()

KRITAGLOBAL_EXPORT qreal KisBezierUtils::curveProportionByParam ( const QPointF p0,
const QPointF p1,
const QPointF p2,
const QPointF p3,
qreal t,
const qreal error )

Definition at line 1076 of file KisBezierUtils.cpp.

1077{
1078 return curveLengthAtPoint(p0, p1, p2, p3, t, error) / curveLength(p0, p1, p2, p3, error);
1079}
qreal curveLengthAtPoint(const QPointF p0, const QPointF p1, const QPointF p2, const QPointF p3, qreal t, const qreal error)

References curveLength(), curveLengthAtPoint(), p0, p1, p2, and p3.

◆ deCasteljau()

void KisBezierUtils::deCasteljau ( const QPointF & q0,
const QPointF & q1,
const QPointF & q2,
const QPointF & q3,
qreal t,
QPointF * p0,
QPointF * p1,
QPointF * p2,
QPointF * p3,
QPointF * p4 )
inline

Definition at line 52 of file KisBezierUtils.h.

62{
63 QPointF q[4];
64
65 q[0] = q0;
66 q[1] = q1;
67 q[2] = q2;
68 q[3] = q3;
69
70 // points of the new segment after the split point
71 QPointF p[3];
72
73 // the De Casteljau algorithm
74 for (unsigned short j = 1; j <= 3; ++j) {
75 for (unsigned short i = 0; i <= 3 - j; ++i) {
76 q[i] = (1.0 - t) * q[i] + t * q[i + 1];
77 }
78 p[j - 1] = q[0];
79 }
80
81 *p0 = p[0];
82 *p1 = p[1];
83 *p2 = p[2];
84 *p3 = q[1];
85 *p4 = q[2];
86}

References p, p0, p1, p2, p3, q0, q1, q2, and q3.

◆ interpolateQuadric()

KRITAGLOBAL_EXPORT QPointF KisBezierUtils::interpolateQuadric ( const QPointF & p0,
const QPointF & p2,
const QPointF & pt,
qreal t )

Interpolates quadric curve passing through given points.

Interpolates quadric curve passing through p0, pt and p2 with ensuring that pt placed at position t

Returns
interpolated value for control point p1

Definition at line 930 of file KisBezierUtils.cpp.

931{
932 if (t <= 0.0 || t >= 1.0)
933 return lerp(p0, p2, 0.5);
934
935 /*
936 B(t) = [x2 y2] = (1-t)^2*P0 + 2t*(1-t)*P1 + t^2*P2
937
938 B(t) - (1-t)^2*P0 - t^2*P2
939 P1 = --------------------------
940 2t*(1-t)
941 */
942
943 QPointF c1 = pt - (1.0-t) * (1.0-t)*p0 - t * t * p2;
944
945 qreal denom = 2.0 * t * (1.0-t);
946
947 c1.rx() /= denom;
948 c1.ry() /= denom;
949
950 return c1;
951}

References lerp(), p0, and p2.

◆ intersectWithLine()

KRITAGLOBAL_EXPORT QVector< qreal > KisBezierUtils::intersectWithLine ( const QPointF & p0,
const QPointF & p1,
const QPointF & p2,
const QPointF & p3,
const QLineF & line,
qreal eps )

Find intersection points (their parameter values) between a cubic Bezier curve and a line.

Curve: p0, p1, p2, p3 Line: line

For cubic Bezier curves there can be at most three intersection points.

Definition at line 1242 of file KisBezierUtils.cpp.

1243{
1244 return intersectWithLineImpl(p0, p1, p2, p3, line, eps, 1.0, 0.0);
1245}
QVector< qreal > intersectWithLineImpl(const QPointF &p0, const QPointF &p1, const QPointF &p2, const QPointF &p3, const QLineF &line, qreal eps, qreal alpha, qreal beta)

References eps, intersectWithLineImpl(), p0, p1, p2, and p3.

◆ intersectWithLineImpl()

QVector< qreal > KisBezierUtils::intersectWithLineImpl ( const QPointF & p0,
const QPointF & p1,
const QPointF & p2,
const QPointF & p3,
const QLineF & line,
qreal eps,
qreal alpha,
qreal beta )

Definition at line 1208 of file KisBezierUtils.cpp.

1209{
1211
1212 QVector<qreal> result;
1213
1214 const qreal length =
1215 kisDistance(p0, p1) +
1216 kisDistance(p1, p2) +
1217 kisDistance(p2, p3);
1218
1219 if (length < eps) {
1220 if (intersectLines(p0, p3, line.p1(), line.p2())) {
1221 result << alpha * 0.5 + beta;
1222 }
1223 } else {
1224
1225 const bool hasIntersections =
1226 intersectLines(p0, p1, line.p1(), line.p2()) ||
1227 intersectLines(p1, p2, line.p1(), line.p2()) ||
1228 intersectLines(p2, p3, line.p1(), line.p2());
1229
1230 if (hasIntersections) {
1231 QPointF q0, q1, q2, q3, q4;
1232
1233 deCasteljau(p0, p1, p2, p3, 0.5, &q0, &q1, &q2, &q3, &q4);
1234
1235 result << intersectWithLineImpl(p0, q0, q1, q2, line, eps, 0.5 * alpha, beta);
1236 result << intersectWithLineImpl(q2, q3, q4, p3, line, eps, 0.5 * alpha, beta + 0.5 * alpha);
1237 }
1238 }
1239 return result;
1240}
qreal length(const QPointF &vec)
Definition Ellipse.cc:82
boost::optional< QPointF > intersectLines(const QLineF &boundedLine, const QLineF &unboundedLine)

References deCasteljau(), eps, KisAlgebra2D::intersectLines(), intersectWithLineImpl(), kisDistance(), length(), p0, p1, p2, p3, q0, q1, q2, and q3.

◆ intersectWithLineNearest()

KRITAGLOBAL_EXPORT boost::optional< qreal > KisBezierUtils::intersectWithLineNearest ( const QPointF & p0,
const QPointF & p1,
const QPointF & p2,
const QPointF & p3,
const QLineF & line,
const QPointF & nearestAnchor,
qreal eps )

Find new nearest intersection point between a cubic Bezier curve and a line. The resulting point will be the nearest to nearestAnchor.

Definition at line 1247 of file KisBezierUtils.cpp.

1248{
1249 QVector<qreal> result = intersectWithLine(p0, p1, p2, p3, line, eps);
1250
1251 qreal minDistance = std::numeric_limits<qreal>::max();
1252 boost::optional<qreal> nearestRoot;
1253
1254 Q_FOREACH (qreal root, result) {
1255 const QPointF pt = bezierCurve(p0, p1, p2, p3, root);
1256 const qreal distance = kisDistance(pt, nearestAnchor);
1257
1258 if (distance < minDistance) {
1259 minDistance = distance;
1260 nearestRoot = root;
1261 }
1262 }
1263
1264 return nearestRoot;
1265}
qreal distance(const QPointF &p1, const QPointF &p2)
QVector< qreal > intersectWithLine(const QPointF &p0, const QPointF &p1, const QPointF &p2, const QPointF &p3, const QLineF &line, qreal eps)

References bezierCurve(), distance(), eps, intersectWithLine(), kisDistance(), p0, p1, p2, and p3.

◆ isLinearSegmentByControlPoints()

bool KisBezierUtils::isLinearSegmentByControlPoints ( const QPointF & p0,
const QPointF & p1,
const QPointF & p2,
const QPointF & p3,
const qreal eps = 1e-4 )
inline

Definition at line 151 of file KisBezierUtils.h.

154{
155 return isLinearSegmentByDerivatives(p0, (p1 - p0) * 3.0, p3, (p3 - p2) * 3.0, eps);
156}
bool isLinearSegmentByDerivatives(const QPointF &p0, const QPointF &d0, const QPointF &p1, const QPointF &d1, const qreal eps=1e-4)

References eps, isLinearSegmentByDerivatives(), p0, p1, p2, and p3.

◆ isLinearSegmentByDerivatives()

bool KisBezierUtils::isLinearSegmentByDerivatives ( const QPointF & p0,
const QPointF & d0,
const QPointF & p1,
const QPointF & d1,
const qreal eps = 1e-4 )
inline

Definition at line 131 of file KisBezierUtils.h.

134{
135 const QPointF diff = p1 - p0;
136 const qreal dist = KisAlgebra2D::norm(diff);
137
138 const qreal normCoeff = 1.0 / 3.0 / dist;
139
140 const qreal offset1 =
141 normCoeff * qAbs(KisAlgebra2D::crossProduct(diff, d0));
142 if (offset1 > eps) return false;
143
144 const qreal offset2 =
145 normCoeff * qAbs(KisAlgebra2D::crossProduct(diff, d1));
146 if (offset2 > eps) return false;
147
148 return true;
149}
qreal norm(const T &a)
PointTypeTraits< T >::value_type crossProduct(const T &a, const T &b)

References KisAlgebra2D::crossProduct(), eps, KisAlgebra2D::norm(), p0, and p1.

◆ linearizeCurve()

KRITAGLOBAL_EXPORT QVector< qreal > KisBezierUtils::linearizeCurve ( const QPointF p0,
const QPointF p1,
const QPointF p2,
const QPointF p3,
const qreal eps )

Definition at line 29 of file KisBezierUtils.cpp.

30{
31 const qreal minStepSize = 2.0 / kisDistance(p0, p3);
32
33 QVector<qreal> steps;
34 steps << 0.0;
35
36
38 stackedPoints.push(std::make_tuple(p3, 3 * (p3 - p2), 1.0));
39
40 QPointF lastP = p0;
41 QPointF lastD = 3 * (p1 - p0);
42 qreal lastT = 0.0;
43
44 while (!stackedPoints.isEmpty()) {
45 QPointF p = std::get<0>(stackedPoints.top());
46 QPointF d = std::get<1>(stackedPoints.top());
47 qreal t = std::get<2>(stackedPoints.top());
48
49 if (t - lastT < minStepSize ||
50 isLinearSegmentByDerivatives(lastP, lastD, p, d, eps)) {
51
52 lastP = p;
53 lastD = d;
54 lastT = t;
55 steps << t;
56 stackedPoints.pop();
57 } else {
58 t = 0.5 * (lastT + t);
59 p = bezierCurve(p0, p1, p2, p3, t);
60 d = bezierCurveDeriv(p0, p1, p2, p3, t);
61
62 stackedPoints.push(std::make_tuple(p, d, t));
63 }
64 }
65
66 return steps;
67}

References bezierCurve(), bezierCurveDeriv(), eps, isLinearSegmentByDerivatives(), kisDistance(), p, p0, p1, p2, and p3.

◆ mergeLinearizationSteps()

KRITAGLOBAL_EXPORT QVector< qreal > KisBezierUtils::mergeLinearizationSteps ( const QVector< qreal > & a,
const QVector< qreal > & b )

Definition at line 69 of file KisBezierUtils.cpp.

70{
71 QVector<qreal> result;
72
73 std::merge(a.constBegin(), a.constEnd(),
74 b.constBegin(), b.constEnd(),
75 std::back_inserter(result));
76 result.erase(
77 std::unique(result.begin(), result.end(),
78 [] (qreal x, qreal y) { return qFuzzyCompare(x, y); }),
79 result.end());
80
81 return result;
82}

◆ nearestPoint()

KRITAGLOBAL_EXPORT qreal KisBezierUtils::nearestPoint ( const QList< QPointF > controlPoints,
const QPointF & point,
qreal * resultDistance,
QPointF * resultPoint )

Definition at line 324 of file KisBezierUtils.cpp.

325{
326 const int deg = controlPoints.size() - 1;
327
328 // use shortcut for line segments
329 if (deg == 1) {
330 // the segments chord
331 QPointF chord = controlPoints.last() - controlPoints.first();
332 // the point relative to the segment
333 QPointF relPoint = point - controlPoints.first();
334 // project point to chord (dot product)
335 qreal scale = chord.x() * relPoint.x() + chord.y() * relPoint.y();
336 // normalize using the chord length
337 scale /= chord.x() * chord.x() + chord.y() * chord.y();
338
339 if (scale < 0.0) {
340 return 0.0;
341 } else if (scale > 1.0) {
342 return 1.0;
343 } else {
344 return scale;
345 }
346 }
347
348 /* This function solves the "nearest point on curve" problem. That means, it
349 * calculates the point q (to be precise: it's parameter t) on this segment, which
350 * is located nearest to the input point P.
351 * The basic idea is best described (because it is freely available) in "Phoenix:
352 * An Interactive Curve Design System Based on the Automatic Fitting of
353 * Hand-Sketched Curves", Philip J. Schneider (Master thesis, University of
354 * Washington).
355 *
356 * For the nearest point q = C(t) on this segment, the first derivative is
357 * orthogonal to the distance vector "C(t) - P". In other words we are looking for
358 * solutions of f(t) = (C(t) - P) * C'(t) = 0.
359 * (C(t) - P) is a nth degree curve, C'(t) a n-1th degree curve => f(t) is a
360 * (2n - 1)th degree curve and thus has up to 2n - 1 distinct solutions.
361 * We solve the problem f(t) = 0 by using something called "Approximate Inversion Method".
362 * Let's write f(t) explicitly (with c_i = p_i - P and d_j = p_{j+1} - p_j):
363 *
364 * n n-1
365 * f(t) = SUM c_i * B^n_i(t) * SUM d_j * B^{n-1}_j(t)
366 * i=0 j=0
367 *
368 * n n-1
369 * = SUM SUM w_{ij} * B^{2n-1}_{i+j}(t)
370 * i=0 j=0
371 *
372 * with w_{ij} = c_i * d_j * z_{ij} and
373 *
374 * BinomialCoeff(n, i) * BinomialCoeff(n - i ,j)
375 * z_{ij} = -----------------------------------------------
376 * BinomialCoeff(2n - 1, i + j)
377 *
378 * This Bernstein-Bezier polynom representation can now be solved for its roots.
379 */
380
381 QList<QPointF> ctlPoints = controlPoints;
382
383 // Calculate the c_i = point(i) - P.
384 QPointF * c_i = new QPointF[ deg + 1 ];
385
386 for (int i = 0; i <= deg; ++i) {
387 c_i[ i ] = ctlPoints[ i ] - point;
388 }
389
390 // Calculate the d_j = point(j + 1) - point(j).
391 QPointF *d_j = new QPointF[deg];
392
393 for (int j = 0; j <= deg - 1; ++j) {
394 d_j[j] = 3.0 * (ctlPoints[j+1] - ctlPoints[j]);
395 }
396
397 // Calculate the dot products of c_i and d_i.
398 qreal *products = new qreal[deg * (deg + 1)];
399
400 for (int j = 0; j <= deg - 1; ++j) {
401 for (int i = 0; i <= deg; ++i) {
402 products[j * (deg + 1) + i] = d_j[j].x() * c_i[i].x() + d_j[j].y() * c_i[i].y();
403 }
404 }
405
406 // We don't need the c_i and d_i anymore.
407 delete[] d_j ;
408 delete[] c_i ;
409
410 // Calculate the control points of the new 2n-1th degree curve.
411 BezierSegment newCurve;
412 newCurve.setDegree(2 * deg - 1);
413 // Set up control points in the (u, f(u))-plane.
414 for (unsigned short u = 0; u <= 2 * deg - 1; ++u) {
415 newCurve.setPoint(u, QPointF(static_cast<qreal>(u) / static_cast<qreal>(2 * deg - 1), 0.0));
416 }
417
418 // Precomputed "z" for cubics
419 static const qreal z3[3*4] = {1.0, 0.6, 0.3, 0.1, 0.4, 0.6, 0.6, 0.4, 0.1, 0.3, 0.6, 1.0};
420 // Precomputed "z" for quadrics
421 static const qreal z2[2*3] = {1.0, 2./3., 1./3., 1./3., 2./3., 1.0};
422
423 const qreal *z = deg == 3 ? z3 : z2;
424
425 // Set f(u)-values.
426 for (int k = 0; k <= 2 * deg - 1; ++k) {
427 int min = qMin(k, deg);
428
429 for (unsigned short i = qMax(0, k - (deg - 1)); i <= min; ++i) {
430 unsigned short j = k - i;
431
432 // p_k += products[j][i] * z[j][i].
433 QPointF currentPoint = newCurve.point(k);
434 currentPoint.ry() += products[j * (deg + 1) + i] * z[j * (deg + 1) + i];
435 newCurve.setPoint(k, currentPoint);
436 }
437 }
438
439 // We don't need the c_i/d_i dot products and the z_{ij} anymore.
440 delete[] products;
441
442 // Find roots.
443 QList<qreal> rootParams = newCurve.roots();
444
445 // Now compare the distances of the candidate points.
446
447 // First candidate is the previous knot.
448 qreal distanceSquared = kisSquareDistance(point, controlPoints.first());
449 qreal minDistanceSquared = distanceSquared;
450 qreal resultParam = 0.0;
451 if (resultDistance) {
452 *resultDistance = std::sqrt(distanceSquared);
453 }
454 if (resultPoint) {
455 *resultPoint = controlPoints.first();
456 }
457
458 // Iterate over the found candidate params.
459 Q_FOREACH (qreal root, rootParams) {
460 const QPointF rootPoint = bezierCurve(controlPoints, root);
461 distanceSquared = kisSquareDistance(point, rootPoint);
462
463 if (distanceSquared < minDistanceSquared) {
464 minDistanceSquared = distanceSquared;
465 resultParam = root;
466 if (resultDistance) {
467 *resultDistance = std::sqrt(distanceSquared);
468 }
469 if (resultPoint) {
470 *resultPoint = rootPoint;
471 }
472 }
473 }
474
475 // Last candidate is the knot.
476 distanceSquared = kisSquareDistance(point, controlPoints.last());
477 if (distanceSquared < minDistanceSquared) {
478 minDistanceSquared = distanceSquared;
479 resultParam = 1.0;
480 if (resultDistance) {
481 *resultDistance = std::sqrt(distanceSquared);
482 }
483 if (resultPoint) {
484 *resultPoint = controlPoints.last();
485 }
486 }
487
488 return resultParam;
489}
qreal u
qreal kisSquareDistance(const QPointF &pt1, const QPointF &pt2)
Definition kis_global.h:194
T min(T a, T b, T c)

References bezierCurve(), kisSquareDistance(), KisBezierUtils::BezierSegment::point(), KisBezierUtils::BezierSegment::roots(), KisBezierUtils::BezierSegment::setDegree(), KisBezierUtils::BezierSegment::setPoint(), and u.

◆ offsetSegment()

KRITAGLOBAL_EXPORT std::pair< QPointF, QPointF > KisBezierUtils::offsetSegment ( qreal t,
const QPointF & offset )

moves point t of the curve by offset offset

Returns
proposed offsets for points p1 and p2 of the curve

Definition at line 953 of file KisBezierUtils.cpp.

954{
955 /*
956 * method from inkscape, original method and idea borrowed from Simon Budig
957 * <simon@gimp.org> and the GIMP
958 * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
959 *
960 * feel good is an arbitrary parameter that distributes the delta between handles
961 * if t of the drag point is less than 1/6 distance form the endpoint only
962 * the corresponding handle is adjusted. This matches the behavior in GIMP
963 */
964 qreal feel_good;
965 if (t <= 1.0 / 6.0)
966 feel_good = 0;
967 else if (t <= 0.5)
968 feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
969 else if (t <= 5.0 / 6.0)
970 feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
971 else
972 feel_good = 1;
973
974 const QPointF moveP1 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * offset;
975 const QPointF moveP2 = (feel_good/(3*t*t*(1-t))) * offset;
976
977 return std::make_pair(moveP1, moveP2);
978}

◆ operator<<()

QDebug KisBezierUtils::operator<< ( QDebug dbg,
const Range & r )

Definition at line 96 of file KisBezierPatchParamSpaceUtils.h.

97{
98 dbg.nospace() << "Range(" << r.start << ", " << r.end << ")";
99
100 return dbg.space();
101}

◆ removeBezierNode()

KRITAGLOBAL_EXPORT std::pair< QPointF, QPointF > KisBezierUtils::removeBezierNode ( const QPointF & p0,
const QPointF & p1,
const QPointF & p2,
const QPointF & p3,
const QPointF & q1,
const QPointF & q2,
const QPointF & q3 )

Adjusts position for the bezier control points after removing a node.

First source curve P: p0, p1, p2, p3 Second source curve Q: p3, q1, q2, q3

Node to remove: p3 and its control points p2 and q1

Returns
a pair of new positions for p1 and q2

Calculates the curve control point after removal of a node by minimizing squared error for the following problem:

Given two consequent 3rd order curves P and Q with lengths Lp and Lq, find 3rd order curve B, so that when splitting it at t = Lp / (Lp + Lq) (using de Casteljau algorithm) the control points of the resulting curves will have least square errors, compared to the corresponding control points of curves P and Q.

First we represent curves in matrix form:

B(t) = T * M * B, P(t) = T * Z1 * M * P, Q(t) = T * Z2 * M * Q,

where T = [1, t, t^2, t^3] M — 4x4 matrix of Bezier coefficients B, P, Q — vector of control points for the curves

Z1 — conversion matrix that splits the curve into range [0.0...t] Z2 — conversion matrix that splits the curve into range [t...1.0]

Then we represent vectors P and Q via B:

P = M^(-1) * Z1 * M * B Q = M^(-1) * Z2 * M * B

which in block matrix form looks like:

[ P ] [ M^(-1) * Z1 * M ] | | = | | * B [ Q ] [ M^(-1) * Z2 * M ]

    [ M^(-1) * Z1 * M ]

let C = | |, [ M^(-1) * Z2 * M ]

[ P ] R = | | [ Q ]

then

R = C * B

applying normal equation to find a solution with least square error, get the final result:

B = (C'C)^(-1) * C' * R

Definition at line 1081 of file KisBezierUtils.cpp.

1088{
1144 const qreal lenP = KisBezierUtils::curveLength(p0, p1, p2, p3, 0.001);
1145 const qreal lenQ = KisBezierUtils::curveLength(p3, q1, q2, q3, 0.001);
1146
1147 const qreal z = lenP / (lenP + lenQ);
1148
1149 Eigen::Matrix4f M;
1150 M << 1, 0, 0, 0,
1151 -3, 3, 0, 0,
1152 3, -6, 3, 0,
1153 -1, 3, -3, 1;
1154
1155 Eigen::DiagonalMatrix<float, 4> Z_1;
1156 Z_1.diagonal() << 1, z, pow2(z), pow3(z);
1157
1158 Eigen::Matrix4f Z_2;
1159 Z_2 << 1, z, pow2(z), pow3(z),
1160 0, 1 - z, 2 * z * (1 - z), 3 * pow2(z) * (1 - z),
1161 0, 0, pow2(1 - z), 3 * z * pow2(1 - z),
1162 0, 0, 0, pow3(1 - z);
1163
1164 Eigen::Matrix<float, 8, 2> R;
1165 R << p0.x(), p0.y(),
1166 p1.x(), p1.y(),
1167 p2.x(), p2.y(),
1168 p3.x(), p3.y(),
1169 p3.x(), p3.y(),
1170 q1.x(), q1.y(),
1171 q2.x(), q2.y(),
1172 q3.x(), q3.y();
1173
1174 Eigen::Matrix<float, 2, 2> B_const;
1175 B_const << p0.x(), p0.y(),
1176 q3.x(), q3.y();
1177
1178
1179 Eigen::Matrix4f M1 = M.inverse() * Z_1 * M;
1180 Eigen::Matrix4f M2 = M.inverse() * Z_2 * M;
1181
1182 Eigen::Matrix<float, 8, 4> C;
1183 C << M1, M2;
1184
1185 Eigen::Matrix<float, 8, 2> C_const;
1186 C_const << C.col(0), C.col(3);
1187
1188 Eigen::Matrix<float, 8, 2> C_var;
1189 C_var << C.col(1), C.col(2);
1190
1191 Eigen::Matrix<float, 8, 2> R_var;
1192 R_var = R - C_const * B_const;
1193
1194 Eigen::Matrix<float, 6, 2> R_reduced;
1195 R_reduced = R_var.block(1, 0, 6, 2);
1196
1197 Eigen::Matrix<float, 6, 2> C_reduced;
1198 C_reduced = C_var.block(1, 0, 6, 2);
1199
1200 Eigen::Matrix<float, 2, 2> result;
1201 result = (C_reduced.transpose() * C_reduced).inverse() * C_reduced.transpose() * R_reduced;
1202
1203 QPointF resultP0(result(0, 0), result(0, 1));
1204 QPointF resultP1(result(1, 0), result(1, 1));
1205
1206 return std::make_pair(resultP0, resultP1);
1207}
Eigen::Matrix< double, 4, 2 > R
#define C(i, j)
T pow3(const T &x)
Definition kis_global.h:171

References C, curveLength(), p0, p1, p2, p3, pow2(), pow3(), q1, q2, q3, and R.