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