1 /*
2  *  Copyright (c) 2020 Dmitry Kazakov <dimula73@gmail.com>
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  */
18 
19 #include "KisBezierPatch.h"
20 
21 #include <QtMath>
22 #include <kis_algebra_2d.h>
23 #include "KisBezierUtils.h"
24 
25 #include "kis_debug.h"
26 
dstBoundingRect() const27 QRectF KisBezierPatch::dstBoundingRect() const {
28     QRectF result;
29 
30     for (auto it = points.begin(); it != points.end(); ++it) {
31         KisAlgebra2D::accumulateBounds(*it, &result);
32     }
33 
34     return result;
35 }
36 
srcBoundingRect() const37 QRectF KisBezierPatch::srcBoundingRect() const {
38     return originalRect;
39 }
40 
localToGlobal(const QPointF & pt) const41 QPointF KisBezierPatch::localToGlobal(const QPointF &pt) const
42 {
43     return KisBezierUtils::calculateGlobalPos(points, pt);
44 }
45 
globalToLocal(const QPointF & pt) const46 QPointF KisBezierPatch::globalToLocal(const QPointF &pt) const
47 {
48     return KisBezierUtils::calculateLocalPos(points, pt);
49 }
50 
sampleRegularGrid(QSize & gridSize,QVector<QPointF> & origPoints,QVector<QPointF> & transfPoints,const QPointF & dstStep) const51 void KisBezierPatch::sampleRegularGrid(QSize &gridSize, QVector<QPointF> &origPoints, QVector<QPointF> &transfPoints, const QPointF &dstStep) const
52 {
53     using KisAlgebra2D::lerp;
54     using KisBezierUtils::bezierCurve;
55 
56     const QRectF bounds = dstBoundingRect();
57     gridSize.rwidth() = qCeil(bounds.width() / dstStep.x());
58     gridSize.rheight() = qCeil(bounds.height() / dstStep.y());
59 
60     const qreal topLength = KisBezierUtils::curveLength(points[TL], points[TL_HC], points[TR_HC], points[TR], 0.01);
61     const qreal bottomLength = KisBezierUtils::curveLength(points[BL], points[BL_HC], points[BR_HC], points[BR], 0.01);
62 
63     const qreal leftLength = KisBezierUtils::curveLength(points[TL], points[TL_VC], points[BL_VC], points[BL], 0.01);
64     const qreal rightLength = KisBezierUtils::curveLength(points[TR], points[TR_VC], points[BR_VC], points[BR], 0.01);
65 
66     struct Split {
67         QPointF p0;
68         QPointF relP1;
69         QPointF relP2;
70         QPointF p3;
71         qreal coord1;
72         qreal coord2;
73         qreal proportion;
74     };
75 
76     std::vector<Split> verticalSplits;
77     std::vector<Split> horizontalSplits;
78 
79     for (int y = 0; y < gridSize.height(); y++) {
80         const qreal yProportion = qreal(y) / (gridSize.height() - 1);
81 
82         const qreal yCoord1 = KisBezierUtils::curveLengthAtPoint(points[TL], points[TL_VC], points[BL_VC], points[BL], yProportion, 0.01) / leftLength;
83         const qreal yCoord2 = KisBezierUtils::curveLengthAtPoint(points[TR], points[TR_VC], points[BR_VC], points[BR], yProportion, 0.01) / rightLength;
84 
85         const QPointF p0 = bezierCurve(points[TL], points[TL_VC], points[BL_VC], points[BL], yProportion);
86 
87         const QPointF relP1 = lerp(points[TL_HC] - points[TL], points[BL_HC] - points[BL], yProportion);
88         const QPointF relP2 = lerp(points[TR_HC] - points[TR], points[BR_HC] - points[BR], yProportion);
89 
90         const QPointF p3 = bezierCurve(points[TR], points[TR_VC], points[BR_VC], points[BR], yProportion);
91 
92         verticalSplits.push_back({p0, relP1, relP2, p3, yCoord1, yCoord2, yProportion});
93     }
94 
95     for (int x = 0; x < gridSize.width(); x++) {
96         const qreal xProportion = qreal(x) / (gridSize.width() - 1);
97 
98         const qreal xCoord1 = KisBezierUtils::curveLengthAtPoint(points[TL], points[TL_HC], points[TR_HC], points[TR], xProportion, 0.01) / topLength;
99         const qreal xCoord2 = KisBezierUtils::curveLengthAtPoint(points[BL], points[BL_HC], points[BR_HC], points[BR], xProportion, 0.01) / bottomLength;
100 
101         const QPointF q0 = bezierCurve(points[TL], points[TL_HC], points[TR_HC], points[TR], xProportion);
102 
103         const QPointF relQ1 = lerp(points[TL_VC] - points[TL], points[TR_VC] - points[TR], xProportion);
104         const QPointF relQ2 = lerp(points[BL_VC] - points[BL], points[BR_VC] - points[BR], xProportion);
105 
106         const QPointF q3 = bezierCurve(points[BL], points[BL_HC], points[BR_HC], points[BR], xProportion);
107 
108         horizontalSplits.push_back({q0, relQ1, relQ2, q3, xCoord1, xCoord2, xProportion});
109     }
110 
111     for (int y = 0; y < gridSize.height(); y++) {
112         for (int x = 0; x < gridSize.width(); x++) {
113             const Split &ySplit = verticalSplits[y];
114             const Split &xSplit = horizontalSplits[x];
115 
116             const QPointF transf1 = bezierCurve(ySplit.p0,
117                                                 ySplit.p0 + ySplit.relP1,
118                                                 ySplit.p3 + ySplit.relP2,
119                                                 ySplit.p3,
120                                                 xSplit.proportion);
121 
122 
123             const QPointF transf2 = bezierCurve(xSplit.p0,
124                                                 xSplit.p0 + xSplit.relP1,
125                                                 xSplit.p3 + xSplit.relP2,
126                                                 xSplit.p3,
127                                                 ySplit.proportion);
128 
129 
130             const QPointF transf = 0.5 * (transf1 + transf2);
131 
132             const QPointF localPt(lerp(xSplit.coord1, xSplit.coord2, ySplit.proportion),
133                                   lerp(ySplit.coord1, ySplit.coord2, xSplit.proportion));
134             const QPointF orig = KisAlgebra2D::relativeToAbsolute(localPt, originalRect);
135 
136             origPoints.append(orig);
137             transfPoints.append(transf);
138         }
139     }
140 }
141 
sampleRegularGridSVG2(QSize & gridSize,QVector<QPointF> & origPoints,QVector<QPointF> & transfPoints,const QPointF & dstStep) const142 void KisBezierPatch::sampleRegularGridSVG2(QSize &gridSize, QVector<QPointF> &origPoints, QVector<QPointF> &transfPoints, const QPointF &dstStep) const
143 {
144     using KisAlgebra2D::lerp;
145     using KisBezierUtils::bezierCurve;
146 
147     const QRectF bounds = dstBoundingRect();
148     gridSize.rwidth() = qCeil(bounds.width() / dstStep.x());
149     gridSize.rheight() = qCeil(bounds.height() / dstStep.y());
150 
151     const qreal topLength = KisBezierUtils::curveLength(points[TL], points[TL_HC], points[TR_HC], points[TR], 0.01);
152     const qreal bottomLength = KisBezierUtils::curveLength(points[BL], points[BL_HC], points[BR_HC], points[BR], 0.01);
153 
154     const qreal leftLength = KisBezierUtils::curveLength(points[TL], points[TL_VC], points[BL_VC], points[BL], 0.01);
155     const qreal rightLength = KisBezierUtils::curveLength(points[TR], points[TR_VC], points[BR_VC], points[BR], 0.01);
156 
157 
158     struct Split {
159         QPointF p0;
160         QPointF p3;
161         qreal coord1;
162         qreal coord2;
163         qreal proportion;
164     };
165 
166     std::vector<Split> verticalSplits;
167     std::vector<Split> horizontalSplits;
168 
169     for (int y = 0; y < gridSize.height(); y++) {
170         const qreal yProportion = qreal(y) / (gridSize.height() - 1);
171 
172         const qreal yCoord1 = KisBezierUtils::curveLengthAtPoint(points[TL], points[TL_VC], points[BL_VC], points[BL], yProportion, 0.01) / leftLength;
173         const qreal yCoord2 = KisBezierUtils::curveLengthAtPoint(points[TR], points[TR_VC], points[BR_VC], points[BR], yProportion, 0.01) / rightLength;
174 
175         const QPointF p0 = bezierCurve(points[TL], points[TL_VC], points[BL_VC], points[BL], yProportion);
176         const QPointF p3 = bezierCurve(points[TR], points[TR_VC], points[BR_VC], points[BR], yProportion);
177 
178         verticalSplits.push_back({p0, p3, yCoord1, yCoord2, yProportion});
179     }
180 
181     for (int x = 0; x < gridSize.width(); x++) {
182         const qreal xProportion = qreal(x) / (gridSize.width() - 1);
183 
184         const qreal xCoord1 = KisBezierUtils::curveLengthAtPoint(points[TL], points[TL_HC], points[TR_HC], points[TR], xProportion, 0.01) / topLength;
185         const qreal xCoord2 = KisBezierUtils::curveLengthAtPoint(points[BL], points[BL_HC], points[BR_HC], points[BR], xProportion, 0.01) / bottomLength;
186 
187         const QPointF q0 = bezierCurve(points[TL], points[TL_HC], points[TR_HC], points[TR], xProportion);
188         const QPointF q3 = bezierCurve(points[BL], points[BL_HC], points[BR_HC], points[BR], xProportion);
189 
190         horizontalSplits.push_back({q0, q3, xCoord1, xCoord2, xProportion});
191     }
192 
193     for (int y = 0; y < gridSize.height(); y++) {
194         for (int x = 0; x < gridSize.width(); x++) {
195             const Split &ySplit = verticalSplits[y];
196             const Split &xSplit = horizontalSplits[x];
197 
198             const QPointF Sc = lerp(xSplit.p0, xSplit.p3, ySplit.proportion);
199             const QPointF Sd = lerp(ySplit.p0, ySplit.p3, xSplit.proportion);
200 
201             const QPointF Sb =
202                     lerp(lerp(points[TL], points[TR], xSplit.proportion),
203                          lerp(points[BL], points[BR], xSplit.proportion),
204                          ySplit.proportion);
205 
206             const QPointF transf = Sc + Sd - Sb;
207 
208             const QPointF localPt(lerp(xSplit.coord1, xSplit.coord2, ySplit.proportion),
209                                   lerp(ySplit.coord1, ySplit.coord2, xSplit.proportion));
210             const QPointF orig = KisAlgebra2D::relativeToAbsolute(localPt, originalRect);
211 
212             origPoints.append(orig);
213             transfPoints.append(transf);
214         }
215     }
216 }
217 
operator <<(QDebug dbg,const KisBezierPatch & p)218 QDebug operator<<(QDebug dbg, const KisBezierPatch &p) {
219     dbg.nospace() << "Patch " << p.srcBoundingRect() << " -> " << p.dstBoundingRect() << "\n";
220     dbg.nospace() << "  ( " << p.points[KisBezierPatch::TL] << " "<< p.points[KisBezierPatch::TR] << " " << p.points[KisBezierPatch::BL] << " " << p.points[KisBezierPatch::BR] << ") ";
221     return dbg.nospace();
222 }
223